using System;
using System.Collections.Generic;
using System.Linq;
using iRIS.Framework.Data;
using iRIS.InsuranceCore.Code.Implementations.Comparison;
using iRIS.InsuranceCore.Entities;
using iRIS.InsuranceCore.Entities.Custom;
using iRIS.KaskoLink2.Modification.Code.Constants;

namespace iRIS.KaskoLink2.Modification.Code.Implementations.ChangeValidations
{
    /// <summary>
    /// Валидатор допустимых для ВИ Link2 отличий.
    /// </summary>
    public class KaskoModificationChangesLink2Validator : BaseChangesValidator
    {
        private const int AllowedLevenshteinDistance = 2;

        private readonly string[] _driversFieldsAllowedForChanges = { ChangesPaths.DriverName, ChangesPaths.DriverMiddleName, ChangesPaths.DriverLastName };
        private readonly string[] _insurantFieldsAllowedForChanges = { ChangesPaths.InsurantName, ChangesPaths.InsurantMiddleName, ChangesPaths.InsurantLastName };
        private readonly string[] _ownerFieldsAllowedForChanges = { ChangesPaths.OwnerName, ChangesPaths.OwnerMiddleName, ChangesPaths.OwnerLastName };

        /// <summary>
        /// Initializes a new instance of the <see cref="KaskoModificationChangesLink2Validator"/> class.
        /// </summary>
        /// <param name="changes">Словарь с результатами сравнения корзин.</param>
        public KaskoModificationChangesLink2Validator(Dictionary<string, List<ObjectCompareResult>> changes)
            : base(changes)
        {
        }

        public ChangeValidationResult CheckAutoDeal()
        {
            var resultErrors = new List<string>();

            var autoDealChanges = GetChangedItems(DealPathConstants.AutoDeal);
            if (autoDealChanges.Count == 0 && resultErrors.Count == 0)
            {
                return ChangeValidationResult.Empty();
            }

            if (!CheckIsMultidriveChanged(autoDealChanges))
            {
                resultErrors.Add("Не допускается изменение признака мультидрайва.");
            }

            return new ChangeValidationResult(resultErrors);
        }

        private bool CheckIsMultidriveChanged(List<ObjectCompareResult> autoDealChanges) => !autoDealChanges.Any(dr => dr.ChangedFields.Any(f => f == ChangesPaths.AutoDealIsMultidrive));

        /// <summary>
        /// Выполняет проверку допустимости изменений данных страхователя.
        /// </summary>
        /// <returns>Результат проверки.</returns>
        public ChangeValidationResult CheckInsurant(bool? isInsurantGettingMarried)
        {
            var resultErrors = new List<string>();

            var insurantChanges = GetChangedItems(DealPathConstants.Insurant);
            if (insurantChanges.Count == 0)
            {
                return ChangeValidationResult.Empty();
            }

            if (!CheckLevenshteinDistanceForInsurantFullNameChanges(insurantChanges, isInsurantGettingMarried))
            {
                var errorMessage = isInsurantGettingMarried == true
                    ? "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле имя и отчество страхователя."
                    : "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле ФИО страхователя.";
                resultErrors.Add(errorMessage);
            }

            return new ChangeValidationResult(resultErrors);
        }

        /// <summary>
        /// Выполняет проверку допустимости изменений данных собственника.
        /// </summary>
        /// <returns>Результат проверки.</returns>
        public ChangeValidationResult CheckOwner(bool? isOwnerGettingMerried)
        {
            var resultErrors = new List<string>();

            var ownerChanges = GetChangedItems(DealPathConstants.Owner);
            if (ownerChanges.Count == 0)
            {
                return ChangeValidationResult.Empty();
            }

            // Если собственник полностью меняется, то не выводим ошибку по кол-ву измененных символов
            if (HasOwnerChangedCompletely(ownerChanges))
            {
                return ChangeValidationResult.Empty();
            }

            if (!CheckLevenshteinDistanceForOwnerFullNameChanges(ownerChanges, isOwnerGettingMerried))
            {
                var errorMessage = isOwnerGettingMerried == true
                    ? "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле имя и отчество собственника."
                    : "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле ФИО собственника.";
                resultErrors.Add(errorMessage);
            }

            return new ChangeValidationResult(resultErrors);
        }

        /// <summary>
        /// Проверяем полное изменение собственника или частичное.
        /// </summary>
        /// <param name="insurantChanges"></param>
        /// <returns></returns>
        private bool HasOwnerChangedCompletely(List<ObjectCompareResult> ownerChanges)
        {
            var fullNameChanges = ownerChanges.Any(change =>
                change.ChangedFields.Any(field => field == ChangesPaths.OwnerLastName || field == ChangesPaths.OwnerName || field == ChangesPaths.OwnerMiddleName));
            var dateOfBirthChange = ownerChanges.Any(change => change.ChangedFields.Any(field => field == ChangesPaths.OwnerDateOfBirth));

            var identityDocumentChanges = ownerChanges.Any(change => change.ChangedFields.Any(field => field == ChangesPaths.OwnerIdentityDocumentSeries || field == ChangesPaths.OwnerIdentityDocumentNumber));
            return fullNameChanges && dateOfBirthChange && identityDocumentChanges;
        }

        /// <summary>
        /// Выполняет проверку допустимости изменений данных водителей.
        /// </summary>
        /// <returns>Результат проверки.</returns>
        public ChangeValidationResult CheckDrivers(bool? isDriverGettingMerried)
        {
            var resultErrors = new List<string>();

            var driverChanges = GetChangedItems(DealPathConstants.Drivers);
            if (driverChanges.Count == 0)
            {
                return ChangeValidationResult.Empty();
            }

            if (!CheckLevenshteinDistanceForDriverFullNameChanges(driverChanges, isDriverGettingMerried))
            {
                var errorMessage = isDriverGettingMerried == true
                    ? "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле имя и отчество водителя."
                    : "Не допускается изменение более " + AllowedLevenshteinDistance + " символов в поле ФИО водителя.";
                resultErrors.Add(errorMessage);
            }

            return new ChangeValidationResult(resultErrors);
        }

        /// <summary>
        /// Выполняет проверку допустимости изменений данных авто.
        /// </summary>
        /// <param name="isPts">Признак документа ПТС.</param>
        /// <returns>Результат проверки.</returns>
        public ChangeValidationResult CheckAuto()
        {
            var resultErrors = new List<string>();

            var autoChanges = GetChangedItems(DealPathConstants.Auto);

            if (autoChanges.Count == 0)
            {
                return ChangeValidationResult.Empty();
            }

            if (!CheckHasAutostartChangesAndKeysAmount(autoChanges))
            {
                resultErrors.Add($"Не допускается изменение признака автозапуска и количества ключей.");
            }

            return new ChangeValidationResult(resultErrors);
        }

        private bool CheckHasAutostartChangesAndKeysAmount(List<ObjectCompareResult> autoChanges)
        {
            return !autoChanges.Any(dr => dr.ChangedFields.Any(f => f == ChangesPaths.AutoHasAutostart) 
                && dr.ChangedFields.Any(f => f == ChangesPaths.AutoOriginalKeysAmount));
        }

        private bool CheckLevenshteinDistanceForDriverFullNameChanges(List<ObjectCompareResult> driversChanges, bool? isDriverGettingMerried)
        {
            var allowedDrivesChanges = driversChanges
                .Where(dr => dr.ChangedFields.Any(f => _driversFieldsAllowedForChanges.Contains(f))).ToList();

            var fieldsValues = new Dictionary<string, Func<Driver, string>>
            {
                [ChangesPaths.DriverName] = d => d.Subject.IndividualEntity.Person.Name,
                [ChangesPaths.DriverMiddleName] = d => d.Subject.IndividualEntity.Person.MiddleName,
            };

            if (isDriverGettingMerried != true)
            {
                fieldsValues[ChangesPaths.DriverLastName] = d => d.Subject.IndividualEntity.Person.LastName;
            }

            var checkLevenshtein = CheckLevenshteinDistanceFor(
                allowedDrivesChanges,
                fieldsValues);
            return checkLevenshtein;
        }

        private bool CheckLevenshteinDistanceForInsurantFullNameChanges(List<ObjectCompareResult> insurantChanges, bool? isInsurantGettingMarried)
        {
            var allowedInsurantChanges = insurantChanges
                .Where(dr => dr.ChangedFields.Any(f => _insurantFieldsAllowedForChanges.Contains(f))).ToList();

            var fieldsValues = new Dictionary<string, Func<Insurant, string>>
            {
                [ChangesPaths.InsurantName] = i => i.Subject.IndividualEntity.Person.Name,
                [ChangesPaths.InsurantMiddleName] = i => i.Subject.IndividualEntity.Person.MiddleName
            };

            if (isInsurantGettingMarried != true)
            {
                fieldsValues[ChangesPaths.InsurantLastName] = i => i.Subject.IndividualEntity.Person.LastName;
            }

            var checkLevenshtein = CheckLevenshteinDistanceFor(
                allowedInsurantChanges,
                fieldsValues);

            return checkLevenshtein;
        }

        private bool CheckLevenshteinDistanceForOwnerFullNameChanges(List<ObjectCompareResult> ownerChanges, bool? isOwnerGettingMerried)
        {
            var allowedOwnerChanges = ownerChanges
                .Where(dr => dr.ChangedFields.Any(f => _ownerFieldsAllowedForChanges.Contains(f))).ToList();

            var fieldsValues = new Dictionary<string, Func<Owner, string>>
            {
                [ChangesPaths.OwnerName] = i => i.Subject.IndividualEntity.Person.Name,
                [ChangesPaths.OwnerMiddleName] = i => i.Subject.IndividualEntity.Person.MiddleName,
            };

            if (isOwnerGettingMerried != true)
            {
                fieldsValues[ChangesPaths.OwnerLastName] = i => i.Subject.IndividualEntity.Person.LastName;
            }

            var checkLevenshtein = CheckLevenshteinDistanceFor(
                allowedOwnerChanges,
                fieldsValues);
            return checkLevenshtein;
        }

        private bool CheckLevenshteinDistanceFor<T>(
            List<ObjectCompareResult> changes,
            Dictionary<string, Func<T, string>> fieldValues)
            where T : BaseEntity
        {
            return CheckLevenshteinDistanceFor(changes, fieldValues, AllowedLevenshteinDistance);
        }
    }
}

using System;
using System.Activities;
using System.Collections.Generic;
using System.Linq;
using AutoDictionaryClient.Code.KaskoClient.Interfaces;
using iRIS.Framework.Common;
using iRIS.Framework.Cqrs.Interfaces;
using iRIS.Framework.Data;
using iRIS.Framework.Entity.Dictionary.DictionaryVersionProcessor;
using iRIS.Framework.Entity.Dictionary.EntitySettings;
using iRIS.Framework.Entity.Metadata;
using iRIS.Framework.Entity.Metadata.Manager.Interfaces;
using iRIS.Framework.FileManagement;
using iRIS.Framework.Platform.Entities;
using iRIS.Framework.Security.Entities;
using iRIS.Framework.Validation;
using iRIS.Framework.Workflow.Activities;
using iRIS.Framework.Workflow.Data;
using iRIS.InsuranceCore.Code.Helpers;
using iRIS.InsuranceCore.Code.Queries;
using iRIS.InsuranceCore.Entities;
using iRIS.InsuranceCore.Entities.Custom;
using iRIS.InsuranceCore.Entities.EntityExtensions;
using iRIS.InsuranceCore.Entities.Enums;
using iRIS.InsuranceCore.Infrastructure.Helpers;
using iRIS.KaskoCore.Code.Extensions;
using iRIS.KaskoCore.Code.Helpers;
using iRIS.KaskoCore.Printing.Code;
using iRIS.KaskoLink2.Code.CreateOn.DealInitialization;
using iRIS.KaskoLink2.Code.TypeExtensions;
using iRIS.KaskoLink2.Implementations.Code.Implementations.AutoDictionary.Interfaces;
using iRIS.KaskoLink2.Modification.Code.Constants;
using iRIS.KaskoLink2.Modification.Code.CustomProcesses;
using iRIS.KaskoLink2.Modification.Code.Implementations.ChangeValidations;
using iRIS.KaskoLink2.Modification.Code.Models;
using iRIS.KaskoLink2.Modification.Code.Models.Validators;
using iRIS.Printing.Code.Activities;
using iRIS.Printing.Code.Helpers;
using iRIS.Printing.Code.Interfaces;
using iRIS.Printing.Code.Queries;
using Microsoft.Extensions.DependencyInjection;

namespace iRIS.KaskoLink2.Modification.Code.Procedures
{
    public class KaskoModificationProcedures : ActivityProcedures
    {
        public KaskoModificationProcedures(ActivityContext activityContext)
            : base(activityContext)
        {
        }

        public void InitializeDeals(Basket basket, List<string> productDictCodes)
        {
            var mediator = ServiceProvider.GetRequiredService<ICqrsMediator>();
            var metadataManager = ServiceProvider.GetRequiredService<IMetadataManager>();
            var dictionariesVersion = ServiceProvider.GetRequiredService<IDictionariesVersionControl>();

            foreach (var productDictCode in productDictCodes)
            {
                var product = mediator.ResolveQuery<IQueryProduct>().GetByProductDictCode(productDictCode);

                var dealInitializer = KaskoDealInitializerFactory.GetInitializerFor(null, basket, product, metadataManager);
                var deal = dealInitializer.InitializeDeal();
                dictionariesVersion.SetVersions(new List<BaseEntity>() { deal }, DT.Now);

                deal.IsInBasket = true;
                deal.IsVI = true;

                basket.Deals.Add(deal);
            }
        }

        public void UpdateBasket(Basket basket, KaskoModificationBasketModel update)
        {
            if (!basket.EntityContext.ContainsKey(nameof(KaskoModificationBasketModel)))
            {
                basket.EntityContext.Add(nameof(KaskoModificationBasketModel), update);
            }

            var deal = basket.GetKaskoDeal();
            if (deal != null)
            {
                if (update.Deal == null)
                {
                    return;
                }

                var updateDeal = update.Deal;
                var updateOwner = updateDeal.Owner?.Person;
                var updateInsurant = updateDeal.Insurant?.Person;
                var updateAuto = updateDeal.Auto;
                var updateTsIdentityCard = updateDeal.Auto?.TsIdentityCard;
                var updateDrivers = updateDeal.Drivers;

                deal.AutoDeal.IsMultidrive = updateDeal.IsMultidrive ?? deal.AutoDeal.IsMultidrive;
                deal.ChangesStartDate = updateDeal.ChangeDate;
                deal.AutoDeal.DriverMinAge = updateDeal.MinimalAge ?? deal.AutoDeal.DriverMinAge;
                deal.AutoDeal.DriverMinExperience = updateDeal.MinimalDrivingExperience ?? deal.AutoDeal.DriverMinExperience;

                var insurant = deal.Insurant;
                var owner = deal.GetOwner();
                var drivers = deal.AutoDeal.Drivers;
                var auto = deal.GetAuto();
                var tsIdentityCard = deal.AutoDeal?.TsIdentityCard;

                UpdateOwner(updateOwner, owner);
                UpdateInsurant(updateInsurant, insurant);
                UpdateDrivers(updateDrivers, drivers);
                UpdateAuto(updateAuto, auto);
                UpdateTsIdentityCard(updateTsIdentityCard, tsIdentityCard);
                UpdateSuggestedBank(updateDeal.Auto, deal);

                var autoDictionaryBasketUpdater = ServiceProvider.GetRequiredService<IAutoDictionaryBasketUpdater>();
                autoDictionaryBasketUpdater.UpdateAutoDictionaryValues(basket);
            }
        }

        private void UpdateSuggestedBank(KaskoModificationAutoModel auto, Deal deal)
        {
            if (auto == null)
            {
                return;
            }

            var queryBank = ServiceProvider.GetRequiredService<IQueryBank>();
            var dealAuto = deal.InsuranceObjects[0].Auto;

            var bank = queryBank.GetBankByBIK(auto.BankOfCreditBik) ?? queryBank.GetBankByName(auto.BankOfCredit);
            if (bank == null)
            {
                AddCustomError("Ошибка валидации: название банка или БИК не соответствует справочнику", ModificationErrorCodes.UnacceptableChanges);
                return;
            }

            if (auto.InCredit == null)
            {
                return;
            }

            if (auto.InCredit == true && dealAuto.IsCreditAuto == false)
            {
                var suggestedBank = new SuggestedBank
                {
                    Name = bank.Subject.LegalEntity.Name,
                    ShortName = bank.Subject.LegalEntity.ShortName,
                    BIK = bank.BIK,
                    Bank_Id = bank.Id > 0 ? bank.Id : null
                };

                deal.SetBank(suggestedBank);
                dealAuto.IsCreditAuto = true;
            }
            else if (auto.InCredit == false && dealAuto.IsCreditAuto == true)
            {
                dealAuto.IsCreditAuto = false;
                deal.SetBank(null);
            }
        }

        public void CapitalizeNames(Basket basket)
        {
            var deal = basket.GetKaskoDeal();

            if (deal != null)
            {
                var insurant = deal.GetInsurantPerson();
                var owner = deal.GetOwner()?.GetOwnerPerson();

                CapitalizePersonNames(insurant);
                CapitalizePersonNames(owner);

                deal.AutoDeal.Drivers.ForEach(d => CapitalizePersonNames(d.GetPerson()));
            }
        }

        public void ValidateRequestModel(KaskoModificationBasketModel model)
        {
            var modificationValidator = ServiceProvider.GetRequiredService<KaskoModificationBasketModelValidator>();
            var validationAggregator = ServiceProvider.GetRequiredService<IValidationResultsAggregator>();
            var autoDictionaryClient = ServiceProvider.GetRequiredService<IKaskoAutoDictionaryClientV1>();

            var result = modificationValidator.Validate(model);
            var auto = model.Deal.Auto;
            var transmissionTypes = autoDictionaryClient.GetAutoTransmissionTypes();
            var engineTypes = autoDictionaryClient.GetAutoEngineTypes();

            if (auto?.TransmissionTypeCode != null &&
                    !transmissionTypes.Any(t => t.Code == auto.TransmissionTypeCode))
            {
                AddCustomError("Допускается передача только кода по справочнику для transmissionTypeCode", ModificationErrorCodes.UnacceptableChanges);
            }

            if (auto?.EngineTypeCode != null &&
                    !engineTypes.Any(e => e.Code == auto.EngineTypeCode))
            {
                AddCustomError("Допускается передача только кода по справочнику для engineTypeCode", ModificationErrorCodes.UnacceptableChanges);
            }

            if (!result.IsValid)
            {
                foreach (var item in result.Errors)
                {
                    string formattedErrorMessage = item.PropertyName == "Deal.ChangeDate"
                        ? $"Код: {ModificationErrorCodes.IncorrectStartDate}. {item.ErrorMessage}."
                        : $"Код: {ModificationErrorCodes.UnacceptableChanges}. Недопустимые изменения: {item.ErrorMessage}.";

                    validationAggregator.AppendValidationErrors(new ValidationError
                    {
                        CriticalLevel = ValidationCriticalLevel.CantContProc,
                        ErrorMessage = formattedErrorMessage
                    });
                }
            }
        }

        public void CheckChanges(Basket basket, KaskoModificationBasketModel modification, ModificationChangesResult changesResult)
        {
            var changes = changesResult.Changes.ChangesDictionary;
            var deal = basket.GetKaskoDeal();

            var isInsurantGettingMarried = modification.Deal?.Insurant?.Person?.IsGettingMarried;
            var isOwnerGettingMerried = modification.Deal?.Owner?.Person?.IsGettingMarried;
            var isDriverGettingMerried = modification.Deal?.Drivers?.Any(d => d.IsGettingMarried == true);

            var validator = new KaskoModificationChangesLink2Validator(changes);

            var error = validator.CheckAutoDeal()
                .MergeWith(validator.CheckDrivers(isDriverGettingMerried))
                .MergeWith(validator.CheckAuto())
                .MergeWith(validator.CheckInsurant(isInsurantGettingMarried))
                .MergeWith(validator.CheckOwner(isOwnerGettingMerried));

            var validationAggregator = ServiceProvider.GetRequiredService<IValidationResultsAggregator>();

            // Проверка даты начала действия ДС
            if (modification.Deal.ChangeDate > deal.DateEnd?.AddDays(-1))
            {
                string errorMessage = $"Код: {ModificationErrorCodes.IncorrectStartDate}. Дата начала ДС не может быть раньше текущей даты + 1 день и позже даты окончания действия полиса - 1 день. Переданная в запросе дата начала действия ДС {modification.Deal.ChangeDate.ToShortDateString()} не попадает в указанный промежуток.";
                validationAggregator.AppendValidationErrors(new ValidationError
                {
                    CriticalLevel = ValidationCriticalLevel.CantContProc,
                    ErrorMessage = errorMessage
                });
            }

            foreach (string errorMessage in error.Errors)
            {
                string formattedErrorMessage = $"Код: {ModificationErrorCodes.UnacceptableChanges}. Недопустимые изменения: {errorMessage}";
                validationAggregator.AppendValidationErrors(new ValidationError
                {
                    CriticalLevel = ValidationCriticalLevel.CantContProc,
                    ErrorMessage = formattedErrorMessage
                });
            }
        }

        public void SavePrintingStore(Basket basket)
        {
            var printStoreManager = ServiceProvider.GetRequiredService<IPrintingStoreManager>();
            var settingsManager = ServiceProvider.GetRequiredService<IEntitySettingsManager>();
            var mediator = ServiceProvider.GetRequiredService<ICqrsMediator>();
            var deal = basket.GetKaskoDeal();
            var queryPrintingFormInfos = mediator.ResolveQuery<IQueryPrintingFormInfos>();

            // Используются созданные по отдельности PrintingFormInfo и вызовы методов printStoreManager для сохранения последовательности availableDocuments.
            var additionalPrintingFormInfo = queryPrintingFormInfos.GetPrintingFormInfos()
                .Where(x => x.Code == KaskoOsagoPrintingFormCodes.PRINTING_FORM_CODE_KASKO_ADDITIONAL_AGREEMENT)
                .ToList();

            var statementPrintingFormInfo = queryPrintingFormInfos.GetPrintingFormInfos()
                .Where(x => x.Code == KaskoOsagoPrintingFormCodes.PRINTING_FORM_CODE_KASKO_MODIFICATION_STATEMENT)
                .ToList();

            var fileTransferMethod = new FileTransferMethod
            {
                Code = settingsManager.GetSettingValue<string>("FileTransferMethod", new List<BaseEntity>() { deal, deal.Product, deal.DealIntermediaries.FirstOrDefault()?.Intermediary })
            };

            var printStores = new[]
            {
                printStoreManager.CreatePrintStores(deal, statementPrintingFormInfo, fileTransferMethod),
                printStoreManager.CreatePrintStores(deal, additionalPrintingFormInfo, fileTransferMethod)
            };

            foreach (var printStore in printStores)
            {
                printStoreManager.SavePrintStores(printStore, true);
            }
        }

        private void CapitalizePersonNames(Person person)
        {
            if (person != null)
            {
                person.LastName = person.LastName.Capitalize();
                person.Name = person.Name.Capitalize();
                person.MiddleName = person.MiddleName.Capitalize();
            }
        }

        private void UpdateTsIdentityCard(KaskoModificationTsIdentityCardModel updateTsIdentityCard, TsIdentityCard tsIdentityCard)
        {
            if (updateTsIdentityCard != null)
            {
                tsIdentityCard.isTheftDocument = updateTsIdentityCard.IsTheftDocuments;
                tsIdentityCard.Series = updateTsIdentityCard.Series ?? tsIdentityCard.Series;
                tsIdentityCard.Number = updateTsIdentityCard.Number ?? tsIdentityCard.Number;
                tsIdentityCard.TsIdentityCardTypeCode = updateTsIdentityCard.TsIdentityCardTypeCode ?? tsIdentityCard.TsIdentityCardTypeCode;
                tsIdentityCard.DateOfIssued = updateTsIdentityCard.DateOfIssued ?? tsIdentityCard.DateOfIssued;
            }
        }

        private void UpdateAuto(KaskoModificationAutoModel updateAuto, Auto auto)
        {
            if (updateAuto != null)
            {
                auto.AutoYearOfIssueCode = updateAuto.YearOfIssueCode ?? auto.AutoYearOfIssueCode;
                auto.AutoYearOfIssueValue = int.TryParse(updateAuto.YearOfIssueCode, out int year) ? year : auto.AutoYearOfIssueValue;
                auto.AutoEngineTypeCode = updateAuto.EngineTypeCode ?? auto.AutoEngineTypeCode;
                auto.AutoEnginePowerCode = updateAuto.EnginePowerCode ?? auto.AutoEnginePowerCode;
                auto.AutoEnginePowerValue = decimal.TryParse(updateAuto.EnginePowerCode, out decimal power) ? power : auto.AutoEnginePowerValue;
                auto.RegistrationNumber = updateAuto.RegNumber ?? auto.RegistrationNumber;
                auto.AutoTransmissionTypeCode = updateAuto.TransmissionTypeCode ?? auto.AutoTransmissionTypeCode;
                auto.Vin = updateAuto.Vin ?? auto.Vin;
                auto.OriginalKeysAmount = updateAuto.OriginalKeysAmount ?? auto.OriginalKeysAmount;
                auto.HasAutostart = updateAuto.HasAutostart == true ? updateAuto.HasAutostart : auto.HasAutostart;
                UpdateAutoBrandModel(auto, updateAuto);
            }
        }

        private void UpdateAutoBrandModel(Auto auto, KaskoModificationAutoModel updateAuto)
        {
            if (updateAuto.Brand == null || updateAuto.Model == null)
            {
                return;
            }

            var autoDictionaryClient = ServiceProvider.GetRequiredService<IKaskoAutoDictionaryClientV1>();

            var modifications = autoDictionaryClient.GetAutoBrandModelByBrandAndModelNames(
                updateAuto.Brand,
                updateAuto.Model,
                auto.MdmDictionariesVersionId,
                DT.Now);

            if (modifications.Count == 0)
            {
                AddCustomError("Недопустимые изменения: не найдена марка/модель авто по справочнику", ModificationErrorCodes.UnacceptableChanges);
                return;
            }

            auto.AutoBrandName = modifications[0].AutoBrand.Name;
            auto.AutoBrandCode = modifications[0].AutoBrand.Code;
            auto.AutoModelName = modifications[0].AutoModel.Name;
            auto.AutoModelCode = modifications[0].AutoModel.Code;
        }

        private void UpdateDrivers(List<KaskoModificationDriverModel> updateDrivers, ObservableList<Driver> drivers)
        {
            if (updateDrivers != null)
            {
                var driversToRemove = new List<Driver>();

                foreach (var driver in drivers)
                {
                    var driverPerson = driver.GetPerson();

                    var updateDriver = updateDrivers.FirstOrDefault(ud =>
                        StringHelper.CalculateLevensteinDistance(driverPerson.Name, ud.Name) <= 2
                            && StringHelper.CalculateLevensteinDistance(driverPerson.MiddleName, ud.MiddleName) <= 2
                                && (StringHelper.CalculateLevensteinDistance(driverPerson.LastName, ud.LastName) <= 2 || ud.IsGettingMarried == true));

                    if (updateDriver != null)
                    {
                        driverPerson.LastName = updateDriver.LastName ?? driverPerson.LastName;
                        driverPerson.Name = updateDriver.Name ?? driverPerson.Name;
                        driverPerson.MiddleName = updateDriver.MiddleName ?? driverPerson.MiddleName;
                        driver.DriverLicenseNumber = updateDriver.DriverLicenseNumber ?? driver.DriverLicenseNumber;
                        driver.DriverLicenseSeries = updateDriver.DriverLicenseSeries ?? driver.DriverLicenseSeries;
                    }
                    else
                    {
                        driversToRemove.Add(driver);
                    }
                }

                driversToRemove.ForEach(d => drivers.Remove(d));

                var newDrivers = updateDrivers.Where(ud => !drivers.Any(d =>
                    d.DriverLicenseSeries == ud.DriverLicenseSeries
                        && d.DriverLicenseNumber == ud.DriverLicenseNumber));

                drivers.AddRange(newDrivers.Select(nd =>
                new Driver
                {
                    DriverLicenseSeries = nd.DriverLicenseSeries,
                    DriverLicenseNumber = nd.DriverLicenseNumber,
                    IsForeignDrivingLicense = nd.DriverIsForeignDrivingLicense,
                    DriverLicenseIssueDate = nd.DriverLicenseIssueDate,
                    Subject = new Subject
                    {
                        IndividualEntity = new IndividualEntity
                        {
                            Person = new Person
                            {
                                LastName = nd.LastName,
                                Name = nd.Name,
                                MiddleName = nd.MiddleName,
                                DateOfBirth = nd.DateOfBirth
                            }
                        }
                    },
                    DrivingExperiences = new ObservableList<DrivingExperience>
                    {
                        new DrivingExperience
                        {
                            DrivingExperienceDateStart = nd.DrivingExperienceDateStart
                        }
                    }
                }));
            }
        }

        private void UpdateInsurant(KaskoModificationPersonModel updateInsurant, Insurant insurant)
        {
            var person = insurant.GetPerson();

            UpdatePerson(updateInsurant, person);
            UpdateIdentityDocument(updateInsurant?.IdentityDocument, insurant.IdentityDocument);
        }

        private void UpdateIdentityDocument(KaskoModificationIdentityDocumentModel updateDocument, IdentityDocument document)
        {
            if (updateDocument != null)
            {
                document.Series = updateDocument.Series ?? document.Series;
                document.Number = updateDocument.Number ?? document.Number;
                document.UnitCode = updateDocument.UnitCode ?? document.UnitCode;
                document.DateIssue = updateDocument.DateIssue ?? document.DateIssue;
                document.IssuedBy = updateDocument.IssuedBy ?? document.IssuedBy;
                document.CitizenshipCode = updateDocument.CitizenshipCode ?? document.CitizenshipCode;
            }
        }

        private void UpdateOwner(KaskoModificationPersonModel updateOwner, Owner owner)
        {
            var person = owner.GetPerson();

            UpdatePerson(updateOwner, person);
            UpdateIdentityDocument(updateOwner?.IdentityDocument, owner.IdentityDocument);
        }

        private void UpdatePerson(KaskoModificationPersonModel updatePerson, Person person)
        {
            if (updatePerson != null)
            {
                person.LastName = updatePerson.LastName ?? person.LastName;
                person.Name = updatePerson.Name ?? person.Name;
                person.MiddleName = updatePerson.MiddleName ?? person.MiddleName;
                person.Email1 = updatePerson.Email ?? person.Email1;
                person.DateOfBirth = updatePerson.DateOfBirth ?? person.DateOfBirth;
                person.Age = updatePerson.DateOfBirth != null ? DurationHelper.CalculateAge(updatePerson.DateOfBirth) : person.Age;

                if (updatePerson.Addresses != null)
                {
                    UpdateAddresses(person.Addresses, updatePerson.Addresses);
                }
            }
        }

        private void UpdateAddresses(ObservableList<Address> addresses, List<KaskoModificationAddressModel> updateAddresses)
        {
            foreach (var address in addresses)
            {
                var updateAddress = updateAddresses
                    .FirstOrDefault(x => x.AddressTypeCode == address.AddressTypeCode);

                if (updateAddress != null)
                {
                    address.Building = updateAddress.Building ?? address.Building;
                    address.City = updateAddress.City ?? address.City;
                    address.Country = updateAddress.Country ?? address.Country;
                    address.District = updateAddress.District ?? address.District;
                    address.House = updateAddress.House ?? address.House;
                    address.Housing = updateAddress.Housing ?? address.Housing;
                    address.KladrId = updateAddress.KladrId ?? address.KladrId;
                    address.Office = updateAddress.OfficeOrFlat ?? address.Office;
                    address.Region = updateAddress.Region ?? address.Region;
                    address.Settlement = updateAddress.Settlement ?? address.Settlement;
                    address.Street = updateAddress.Street ?? address.Street;
                    address.ZipCode = updateAddress.ZipCode ?? address.ZipCode;

                    CheckAddress(address);
                }
            }
        }

        private void CheckAddress(Address address)
        {
            var daDataHelper = ServiceProvider.GetRequiredService<IDaDataHelper>();
            var updateEnteredAddress = $"{address.GetDaDataFormatedAddress()}, кв {address.Office}";
            var parsedAddress = daDataHelper.ParseAddressAsync(updateEnteredAddress, true).GetAwaiter().GetResult();

            if (parsedAddress != null)
            {
                address.Building = parsedAddress.Building ?? address.Building;
                address.City = parsedAddress.City ?? address.City;
                address.Country = parsedAddress.Country ?? address.Country;
                address.District = parsedAddress.District ?? address.District;
                address.House = parsedAddress.House ?? address.House;
                address.Housing = parsedAddress.Housing ?? address.Housing;
                address.KladrId = parsedAddress.KladrId ?? address.KladrId;
                address.Office = parsedAddress.Office ?? address.Office;
                address.Region = parsedAddress.Region ?? address.Region;
                address.Settlement = parsedAddress.Settlement ?? address.Settlement;
                address.Street = parsedAddress.Street ?? address.Street;
                address.ZipCode = parsedAddress.ZipCode ?? address.ZipCode;
                address.EnteredAddress = parsedAddress.EnteredAddress ?? address.EnteredAddress;
            }
            else
            {
                var validationResultsAggregator = ServiceProvider.GetRequiredService<IValidationResultsAggregator>();

                validationResultsAggregator.AppendValidationErrors(new ValidationError
                {
                    CriticalLevel = ValidationCriticalLevel.CantContProc,
                    ErrorMessage = "Не удалось распознать передаваемый адрес. Укажите корректный адрес или заполните KladrId."
                });
            }
        }

        /// <summary>
        /// Создаёт файловые блоки для документов сделок.
        /// </summary>
        /// <param name="basket">Корзина.</param>
        public void PrepareDocsForModifyKaskoBasket(Basket basket)
        {
            var processDataService = (IProcessDataService)ServiceProvider.GetService(typeof(IProcessDataService));
            var dictionaryDate = processDataService.GetDictionaryDate(basket.RelatedProcessKey ?? basket.Number, 1);
            var kaskoDeal = basket.GetKaskoDeal();

            var fileBlockBuilder = (IFileBlockBuilder)ServiceProvider.GetService(typeof(IFileBlockBuilder));
            var fileBlockStructureProvider = (IFileBlockStructureProvider)ServiceProvider.GetService(typeof(IFileBlockStructureProvider));
            var validationResultsAggregator = (IValidationResultsAggregator)ServiceProvider.GetService(typeof(IValidationResultsAggregator));

            // инициализация полей каско при отсутствии
            var autoKasko = kaskoDeal.GetAuto();
            var ownerKasko = kaskoDeal.GetOwner();
            kaskoDeal.AutoDeal.TsIdentityCard ??= new TsIdentityCard();
            kaskoDeal.Insurant.IdentityDocument ??= new IdentityDocument();
            ownerKasko.IdentityDocument ??= new IdentityDocument();
            kaskoDeal.OtherInsuranceCompanyPolicy ??= new OtherInsuranceCompanyPolicy();

            // Временное решение, пока не разберемся с сохранением dictionaryDate
            if (dictionaryDate == DateTime.MinValue)
            {
                dictionaryDate = DT.Now;
            }

            // создаём файловые блоки
            if (kaskoDeal.AutoDeal.Drivers.Any(dr => dr.DriverLicenseScan is null))
            {
                fileBlockBuilder.BuildFileBlock(
                    basket,
                    fileBlockStructureProvider.GetFileBlockStructure("DriverLicense", dictionaryDate),
                    "Deals.Where(Product.InsuranceTypeCode == \"KASKO\").AutoDeal.Drivers.DriverLicenseScan");
            }

            if (kaskoDeal.AutoDeal.TsIdentityCard.FileBlock is null || kaskoDeal.AutoDeal.TsIdentityCard.FileBlock.FileGroups.Count == 0)
            {
                fileBlockBuilder.BuildFileBlock(
                    basket,
                    fileBlockStructureProvider.GetFileBlockStructure(nameof(TsIdentityCard), dictionaryDate),
                    "Deals.Where(Product.InsuranceTypeCode == \"KASKO\").AutoDeal.TsIdentityCard.FileBlock");

                if (kaskoDeal.AutoDeal.TsIdentityCard.OwnershipProof is null || kaskoDeal.AutoDeal.TsIdentityCard.OwnershipProof.FileGroups.Count == 0)
                {
                    fileBlockBuilder.BuildFileBlock(
                            basket,
                            fileBlockStructureProvider.GetFileBlockStructure("OwnershipProof", dictionaryDate),
                            "Deals.Where(Product.InsuranceTypeCode == \"KASKO\").AutoDeal.TsIdentityCard.OwnershipProof");
                }
            }

            if (kaskoDeal.Insurant.IdentityDocument.FileBlock is null || kaskoDeal.Insurant.IdentityDocument.FileBlock.FileGroups.Count == 0)
            {
                fileBlockBuilder.BuildFileBlock(
                    basket,
                    fileBlockStructureProvider.GetFileBlockStructure(nameof(IdentityDocument), dictionaryDate),
                    "Deals.Where(Product.InsuranceTypeCode == \"KASKO\").Insurant.IdentityDocument.FileBlock");
            }

            if (ownerKasko.IdentityDocument.FileBlock is null)
            {
                fileBlockBuilder.BuildFileBlock(
                    basket,
                    fileBlockStructureProvider.GetFileBlockStructure(nameof(IdentityDocument), dictionaryDate),
                    "Deals.Where(Product.ProductDictCode == \"KASKO\").InsuranceObjects.Where(InsuranceObjectTypeCode == \"Auto\").Owners[0].IdentityDocument.FileBlock");
            }

            if (kaskoDeal.OtherInsuranceCompanyPolicy.FileBlock is null)
            {
                fileBlockBuilder.BuildFileBlock(
                    basket,
                    fileBlockStructureProvider.GetFileBlockStructure("InsuranceCaseOther", dictionaryDate),
                    "Deals.Where(Product.ProductDictCode == \"KASKO\").OtherInsuranceCompanyPolicy.FileBlock");
            }
        }

        public void SetRequiredForFileBlocks(Basket basket, ModificationChangesResult changesResult)
        {
            var changes = getAllChanges(changesResult.Changes.ChangesDictionary);

            CheckAndSetMinFilesCountForTsDocuments(basket, changes);
            CheckAndSetMinFilesCountForInsurantDocuments(basket, changes);
            CheckAndSetMinFilesCountForOwnerDocuments(basket, changes);
            CheckAndSetMinFilesCountForDriverDocuments(basket, changes);
            CheckAndSetMinFilesCountForPtsDocuments(basket, changes);
        }

        private List<string> getAllChanges(Dictionary<string, List<ObjectCompareResult>> changesDictionary)
        {
            var keysToCheck = new[]
            {
                "AutoDeal.Drivers",
                "InsuranceObjects[0].Auto",
                "AutoDeal",
                "Insurant",
                "InsuranceObjects[0].Owners[0]"
            };

            var changes = keysToCheck
                .Where(key => changesDictionary.TryGetValue(key, out var results) && results.Count > 0)
                .SelectMany(key => changesDictionary[key].SelectMany(result => result.ChangedFields))
                .ToList();

            return changes;
        }

        private void CheckAndSetMinFilesCountForTsDocuments(Basket basket, List<string> changes)
        {
            if (changes.Contains(ChangesPaths.AutoDocNumber) || changes.Contains(ChangesPaths.AutoDocSeries) || changes.Contains(ChangesPaths.AutoDocDateOfIssued) || changes.Contains(ChangesPaths.AutoRegistrationNumber) || changes.Contains(ChangesPaths.AutoYearOfIssueCode) || changes.Contains(ChangesPaths.AutoEnginePowerCode) || changes.Contains(ChangesPaths.AutoVin))
            {
                SetMinFilesCountForTsDocuments(basket);
            }
        }

        private void CheckAndSetMinFilesCountForInsurantDocuments(Basket basket, List<string> changes)
        {
            if (changes.Contains(ChangesPaths.InsurantEnteredAddress0) || changes.Contains(ChangesPaths.InsurantEnteredAddress1) || changes.Contains(ChangesPaths.InsurantEnteredAddress2) 
                || changes.Contains(ChangesPaths.InsurantName) || changes.Contains(ChangesPaths.InsurantMiddleName) || changes.Contains(ChangesPaths.InsurantLastName)
                || changes.Contains(ChangesPaths.InsurantDateOfBirth) || changes.Contains(ChangesPaths.InsurantIdentityDocumentSeries) 
                || changes.Contains(ChangesPaths.InsurantIdentityDocumentNumber) || changes.Contains(ChangesPaths.InsurantIdentityDocumentType) || changes.Contains(ChangesPaths.InsurantIdentityDocumentUnitCode) 
                || changes.Contains(ChangesPaths.InsurantIdentityDocumentDateIssue) || changes.Contains(ChangesPaths.InsurantIdentityDocumentIssuedBy))
            {
                SetMinFilesCountForInsurantDocuments(basket);
            }
        }

        private void CheckAndSetMinFilesCountForOwnerDocuments(Basket basket, List<string> changes)
        {
            if (changes.Contains(ChangesPaths.OwnerEnteredAddress0) || changes.Contains(ChangesPaths.OwnerEnteredAddress1) || changes.Contains(ChangesPaths.OwnerEnteredAddress2) 
                || changes.Contains(ChangesPaths.OwnerName) || changes.Contains(ChangesPaths.OwnerMiddleName) || changes.Contains(ChangesPaths.OwnerLastName) 
                || changes.Contains(ChangesPaths.OwnerDateOfBirth) || changes.Contains(ChangesPaths.OwnerIdentityDocumentSeries) 
                || changes.Contains(ChangesPaths.OwnerIdentityDocumentNumber) || changes.Contains(ChangesPaths.OwnerIdentityDocumentType) 
                || changes.Contains(ChangesPaths.OwnerIdentityDocumentUnitCode) || changes.Contains(ChangesPaths.OwnerIdentityDocumentDateIssue) 
                || changes.Contains(ChangesPaths.OwnerIdentityDocumentIssuedBy))
            {
                SetMinFilesCountForOwnerDocuments(basket);
            }
        }

        private void CheckAndSetMinFilesCountForDriverDocuments(Basket basket, List<string> changes)
        {
            if (changes.Contains(ChangesPaths.DriverName) || changes.Contains(ChangesPaths.DriverMiddleName) || changes.Contains(ChangesPaths.DriverLastName))
            {
                SetMinFilesCountForDriverDocuments(basket, changes);
            }
        }

        private void CheckAndSetMinFilesCountForPtsDocuments(Basket basket, List<string> changes)
        {
            if (changes.Contains(ChangesPaths.AutoDocSeries) || changes.Contains(ChangesPaths.AutoDocNumber))
            {
                SetMinFilesCountForPtsDocuments(basket);
            }
        }

        private void SetMinFilesCountForTsDocuments(Basket basket)
        {
            basket.GetKaskoDeal().AutoDeal.TsIdentityCard.FileBlock.FileGroups.FirstOrDefault(f => f.Name == "TsDocuments").MinFilesCount = 1;
        }

        private void SetMinFilesCountForInsurantDocuments(Basket basket)
        {
            basket.GetKaskoDeal().Insurant.IdentityDocument.FileBlock.FileGroups.FirstOrDefault(f => f.Name == "Documents").MinFilesCount = 1;
        }

        private void SetMinFilesCountForOwnerDocuments(Basket basket)
        {
            basket.GetKaskoDeal().GetOwner().IdentityDocument.FileBlock.FileGroups.FirstOrDefault(f => f.Name == "Documents").MinFilesCount = 1;
        }

        private void SetMinFilesCountForDriverDocuments(Basket basket, List<string> changes)
        {
            var groups = basket.GetKaskoDeal().AutoDeal.Drivers.SelectMany(d => d.DriverLicenseScan.FileGroups.Where(f => f.Name == "DriverDocuments")).ToArray();
            var changedDriversCount = changes.Where(x => x == ChangesPaths.DriverPersonState).Count();

            for (int i = 0; i < changedDriversCount; i++)
            {
                groups[i].MinFilesCount = 1;
            }
        }

        private void SetMinFilesCountForPtsDocuments(Basket basket)
        {
            basket.GetKaskoDeal().AutoDeal.TsIdentityCard.FileBlock.FileGroups.FirstOrDefault(f => f.Name == "TsDocuments").MinFilesCount = 1;
        }

        public CdpPackage CreateDocumentsPack(Basket basket)
        {
            var feFinder = ServiceProvider.GetRequiredService<IFileEntityFinder>();
            var fileBlocks = feFinder.FindFileBlocks(new[] { basket.GetKaskoDeal() });

            var insurantPerson = basket.Deals.FirstOrDefault(d => d.Insurant != null)?.Insurant?.Subject?.IndividualEntity?.Person;

            var filesBlocks = fileBlocks
                .Where(b => (b.FileEntity.ObjectInstance as FileBlock).FileGroups
                .Any(g => g.MinFilesCount > 0 && g.FileInfos.Any()))
                .Select(b => b.FileEntity.ObjectInstance as FileBlock)
                .ToList();

            var cdpPackage = new CdpPackage
            {
                CreateDate = DT.Now,
                PolicyNumber = basket.GetKaskoDeal().PolicyNumber,
                Phone = insurantPerson.Phone1,

                ClientFullName = insurantPerson.GetFullName(),
                Status = (int)CdpPackageStates.Added
            };

            foreach (var block in filesBlocks)
            {
                cdpPackage.Documents.Add(new Document
                {
                    FileBlock = block
                });
            }

            return cdpPackage;
        }

        public void AddCustomError(string error, string code)
        {
            ((IValidationResultsAggregator)ServiceProvider.GetService(typeof(IValidationResultsAggregator))).AppendValidationErrors(new ValidationError
            {
                ErrorMessage = $"Код: {code}. {error}.",
                CriticalLevel = ValidationCriticalLevel.CantContProc
            });
        }

        public bool CheckKaskoPolicyModificationAllowed(string integrationLogin)
        {
            var settingsManager = ServiceProvider.GetRequiredService<IEntitySettingsManager>();

            return settingsManager.GetSettingForDefaultOrPartnerConfiguration(
                iRIS.KaskoLink2.Implementations.Code.Constants.EntitySettingCodes.KaskoPolicyModificationAllowed,
                iRIS.KaskoLink2.Implementations.Code.Constants.EntitySettingCodes.KaskoPolicyModificationAllowedDefault,
                iRIS.KaskoLink2.Implementations.Code.Constants.EntitySettingCodes.KaskoPolicyModificationAllowedPartner,
                new IntegrationLogin { IntegrationLoginName = integrationLogin },
                false);
        }

        public void SetSignDateToDeal(Basket basket)
        {
            var deal = basket.GetKaskoDeal();
            deal.DateSign = DT.Now;
        }

        /// <summary>
        /// Производится фильтрация документов, добавляется заявление если требуется.
        /// </summary>
        /// <param name="inputDocument">Дополнительное соглашение.</param>
        /// <param name="basket">Корзина.</param>
        /// <returns>Список документов для печати.</returns>
        public List<DocumentInfo> FilterPrintDocuments(DocumentInfo inputDocument, Basket basket)
        {
            List<DocumentInfo> outputDocuments = new List<DocumentInfo>();
            var deal = basket.GetKaskoDeal();

            if (deal.AutoDeal?.TsIdentityCard?.isTheftDocument == true &&
            deal.AutoDeal?.TsIdentityCard?.TsIdentityCardTypeCode == "pts")
            {
                outputDocuments.Add(inputDocument);

                var printingActivityHelper = new PrintingActivityHelper();
                var additionalDocument = printingActivityHelper.PrintDocument(ServiceProvider, KaskoOsagoPrintingFormCodes.PRINTING_FORM_CODE_KASKO_MODIFICATION_STATEMENT, basket, false, null);
                outputDocuments.Add(additionalDocument);
            }
            else
            {
                outputDocuments.Add(inputDocument);
            }

            return outputDocuments;
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Channels;
using iRIS.Framework.Entity.Metadata.Manager.Interfaces;
using iRIS.InsuranceApp.Infrastructure.Extensions;
using iRIS.InsuranceCore.Code.Implementations.Comparison;
using iRIS.InsuranceCore.Code.Implementations.Comparison.DisplayedChanges;
using iRIS.InsuranceCore.Entities;
using iRIS.InsuranceCore.Entities.Custom;
using iRIS.InsuranceCore.Entities.EntityExtensions;
using iRIS.InsuranceCore.Infrastructure.Constants;
using iRIS.InsuranceCore.Infrastructure.Helpers;
using iRIS.KaskoLink2.Modification.Code.Constants;

namespace iRIS.KaskoLink2.Modification.Code.Implementations.Comparison
{
    public class KaskoDisplayedChangesFormatter : DisplayedChangesFormatter
    {
        public KaskoDisplayedChangesFormatter(IPathManager pathManager, Dictionary<string, List<ObjectCompareResult>> changes)
            : base(pathManager, 30)
        {
            // Periods
            AddRule(
                period => FormatPeriod(period as Period),
                "Period.DateStart",
                "Period.DateEnd");

            // Insurant
            AddRuleWithInfo(
                "ФИО страхователя",
                "Данные были изменены в профиле личного кабинета",
                insurant => FormatPersonName((insurant as Insurant)?.Subject),
                "Insurant.Subject.IndividualEntity.Person.LastName",
                "Insurant.Subject.IndividualEntity.Person.Name",
                "Insurant.Subject.IndividualEntity.Person.MiddleName");
            AddRuleWithInfo(
                "Дата рождения страхователя",
                "Данные были изменены в профиле личного кабинета",
                "Insurant.Subject.IndividualEntity.Person.DateOfBirth");
            AddRuleWithInfo(
                "Гражданство",
                "Данные были изменены в профиле личного кабинета",
                insurant => FormatCitizenship((insurant as Insurant)?.IdentityDocument?.Citizenship),
                "Insurant.IdentityDocument.CitizenshipCode",
                "Insurant.IdentityDocument.Citizenship.Code");
            AddRuleWithInfo(
                "Паспортные данные страхователя",
                "Данные были изменены в профиле личного кабинета",
                insurant => FormatPersonIdentityDocument((insurant as Insurant)?.IdentityDocument),
                "Insurant.IdentityDocument.IdentityDocumentTypeCode",
                "Insurant.IdentityDocument.IdentityDocumentType.Code",
                "Insurant.IdentityDocument.Series",
                "Insurant.IdentityDocument.Number",
                "Insurant.IdentityDocument.UnitCode",
                "Insurant.IdentityDocument.DateIssue",
                "Insurant.IdentityDocument.IssuedBy");
            AddRuleWithCustomCaption(
                insurant => GetAddressCaption((insurant as Insurant).GetPerson().Addresses[0], nameof(Insurant)),
                insurant => (insurant as Insurant).GetPerson()?.Addresses[0]?.EnteredAddress,
                "Insurant.Subject.IndividualEntity.Person.Addresses[0].EnteredAddress");
            AddRuleWithCustomCaption(
                insurant => GetAddressCaption((insurant as Insurant).GetPerson().Addresses[1], nameof(Insurant)),
                insurant => (insurant as Insurant).GetPerson()?.Addresses[1]?.EnteredAddress,
                "Insurant.Subject.IndividualEntity.Person.Addresses[1].EnteredAddress");
            AddRule(
                "Email страхователя",
                "Insurant.Subject.IndividualEntity.Person.Email1");
            AddRule(
                "Телефон страхователя",
                "Insurant.Subject.IndividualEntity.Person.Phone1");
            AddRule(
                "СНИЛС страхователя",
                "Insurant.Subject.IndividualEntity.Person.SnilsNumber");

            // Auto
            changes.TryGetValue("InsuranceObjects[0].Auto", out var autoChanges);
            var actualAuto = autoChanges?.FirstOrDefault()?.ActualObject as Auto;
            var expectedAuto = autoChanges?.FirstOrDefault()?.ExpectedObject as Auto;

            AddRule("Год выпуска ТС", "Auto.AutoYearOfIssueCode");
            AddRule("VIN", "Auto.Vin");
            AddRule("Шасси (рама)", "Auto.ChassisNumber");
            AddRule("Кузов (прицеп)", "Auto.BodyNumber");
            AddRule("Смена рег. знака", "Auto.RegistrationNumber");
            AddRule("Мощность двигателя", "Auto.AutoEnginePowerCode");
            AddRule("Коробка передач", "Auto.AutoTransmissionTypeCode");
            AddRule("Тип двигателя ТС", "Auto.AutoEngineTypeCode");
            AddRule("Установка системы автоматического запуска двигателя", "Auto.HasAutostart");
            AddRule("Прицеп", auto => FormatAutoHasTrailer(auto as Auto), "Auto.HasTrailer");
            AddRule("Цель использования", auto => (auto as Auto)?.PurposeOfUsing?.Name, "Auto.PurposeOfUsingCode", "Auto.PurposeOfUsing.Code");
            AddRuleWithCustomCaption(
                auto => (auto as Auto)?.TsIdentityCards?.FirstOrDefault()?.TsIdentityCardType?.Name,
                auto => FormatAutoTsIdentityCard(auto as Auto),
                "Auto.TsIdentityCards[0].TsIdentityCardTypeCode",
                "Auto.TsIdentityCards[0].TsIdentityCardType.Code",
                "Auto.TsIdentityCards[0].Series",
                "Auto.TsIdentityCards[0].Number",
                "Auto.TsIdentityCards[0].DateOfIssued");
            AddRule("Номер диагностической карты", "Auto.DiagnosticCard.Number");
            AddRule("Дата окончания диагностической карты", "Auto.DiagnosticCard.DateEnd");
            AddRule("Марка и / или Модель", "Auto.AutoBrandName");
            AddRule("Марка и / или Модель", "Auto.AutoModelName");
            AddRule("Смена Выгодоприобретателя", "Auto.IsCreditAuto");

            if (actualAuto?.OriginalKeysAmount != 0 &&
                actualAuto?.OriginalKeysAmount != expectedAuto?.OriginalKeysAmount)
            {
                AddRule("Изменение количества ключей", "Auto.OriginalKeysAmount");
            }

            // Owner
            changes.TryGetValue("InsuranceObjects[0].Owners[0]", out var ownerChanges);
            var actualOwner = ownerChanges?.FirstOrDefault()?.ActualObject as Owner;
            var expectedOwner = ownerChanges?.FirstOrDefault()?.ExpectedObject as Owner;

            AddRule(
                "ФИО собственника",
                owner => FormatPersonName((owner as Owner)?.Subject),
                "Owner.Subject.IndividualEntity.Person.LastName",
                "Owner.Subject.IndividualEntity.Person.Name",
                "Owner.Subject.IndividualEntity.Person.MiddleName");
            AddRule(
                "Дата рождения собственника",
                "Owner.Subject.IndividualEntity.Person.DateOfBirth");
            AddRule(
                "Гражданство",
                owner => FormatCitizenship((owner as Owner)?.IdentityDocument?.Citizenship),
                "Owner.IdentityDocument.CitizenshipCode",
                "Owner.IdentityDocument.Citizenship.Code");
            AddRule(
                "Паспортные данные собственника",
                owner => FormatPersonIdentityDocument((owner as Owner)?.IdentityDocument),
                "Owner.IdentityDocument.IdentityDocumentTypeCode",
                "Owner.IdentityDocument.IdentityDocumentType.Code",
                "Owner.IdentityDocument.Series",
                "Owner.IdentityDocument.Number",
                "Owner.IdentityDocument.UnitCode",
                "Owner.IdentityDocument.DateIssue",
                "Owner.IdentityDocument.IssuedBy");
            AddRuleWithCustomCaption(
                owner => GetAddressCaption((owner as Owner).GetPerson().Addresses[0], nameof(Owner)),
                owner => (owner as Owner).GetPerson()?.Addresses[0]?.EnteredAddress,
                "Owner.Subject.IndividualEntity.Person.Addresses[0].EnteredAddress");
            AddRuleWithCustomCaption(
                owner => GetAddressCaption((owner as Owner).GetPerson().Addresses[1], nameof(Owner)),
                owner => (owner as Owner).GetPerson()?.Addresses[1]?.EnteredAddress,
                "Owner.Subject.IndividualEntity.Person.Addresses[1].EnteredAddress");
            AddRule(
                "СНИЛС собственника",
                "Owner.Subject.IndividualEntity.Person.SnilsNumber");
            AddRule(
                "Email собственника",
                "Owner.Subject.IndividualEntity.Person.Email1");
            
            if (IsOwnerChangedCompletely(actualOwner, expectedOwner)
                && (expectedAuto?.IsCreditAuto == false || actualAuto?.IsCreditAuto == false))
            {
                AddRule(
                    "Смена Собственника и Выгодоприобретателя",
                    owner => FormatPersonName((owner as Owner)?.Subject),
                    "Owner.Subject.IndividualEntity.Person.LastName",
                    "Owner.Subject.IndividualEntity.Person.Name",
                    "Owner.Subject.IndividualEntity.Person.MiddleName");
            }

            // Drivers
            AddRule(
                "ФИО водителя",
                driver => FormatDriverName(driver as Driver),
                "Driver.Subject.IndividualEntity.Person.LastName",
                "Driver.Subject.IndividualEntity.Person.Name",
                "Driver.Subject.IndividualEntity.Person.MiddleName");
            AddRule(
                null,
                driver => FormatDriverDateOfBirth(driver as Driver),
                "Driver.Subject.IndividualEntity.Person.DateOfBirth");
            AddRule(
                null,
                driver => FormatDriverLicense(driver as Driver),
                "Driver.DriverLicenseSeries",
                "Driver.DriverLicenseNumber");
            AddRule(
                null,
                driver => FormatIsForeingDriverLicense(driver as Driver),
                "Driver.IsForeignDrivingLicense");
            AddRule(
                null,
                driver => FormatDriverDrivingExperiences(driver as Driver),
                "Driver.DrivingExperiences[0].DrivingExperienceDateStart");

            // AutoDeal
            changes.TryGetValue("AutoDeal", out var autoDealChanges);
            var actualAutodeal = autoDealChanges?.FirstOrDefault()?.ActualObject as AutoDeal;
            var expectedAutodeal = autoDealChanges?.FirstOrDefault()?.ExpectedObject as AutoDeal;

            AddRule(
                "Изменен тип использования",
                autoDeal => FormatPeriodType(autoDeal as AutoDeal),
                "AutoDeal.InsurancePeriodTypeCode");
            AddRule(
                "Изменен тип использования",
                autoDeal => FormatIsMultidrive(autoDeal as AutoDeal),
                "AutoDeal.IsMultidrive");
            AddRule(
                "ПТС",
                autoDeal => FormatAutoTsIdentityCard(autoDeal as AutoDeal),
                "AutoDeal.TsIdentityCard.TsIdentityCardTypeCode",
                "AutoDeal.TsIdentityCard.TsIdentityCardType.Code",
                "AutoDeal.TsIdentityCard.Series",
                "AutoDeal.TsIdentityCard.Number",
                "AutoDeal.TsIdentityCard.DateOfIssued");

            AddRuleForMinAgeExperience(actualAutodeal, expectedAutodeal);

            // TransformRules
            AddTransformRule(new DisplayedChangesFormatterTransformRule
            {
                ChangedField = "AutoDeal.InsurancePeriodTypeCode",
                SourcePath = DealPathConstants.AutoDeal,
                DestinationPath = DealPathConstants.DealPeriods,
                ItemsShouldBeRemovedFunc = autoDeal => (autoDeal as AutoDeal)?.InsurancePeriodTypeCode == InsurancePeriodTypeCodes.OneYear,
                ItemsShouldBeIsAddedFunc = autoDeal => (autoDeal as AutoDeal)?.InsurancePeriodTypeCode == InsurancePeriodTypeCodes.UsePeriod
            });
            AddTransformRule(new DisplayedChangesFormatterTransformRule
            {
                ChangedField = "AutoDeal.IsMultidrive",
                SourcePath = DealPathConstants.AutoDeal,
                DestinationPath = DealPathConstants.Drivers,
                ItemsShouldBeRemovedFunc = autoDeal => (autoDeal as AutoDeal)?.IsMultidrive == true,
                ItemsShouldBeIsAddedFunc = autoDeal => (autoDeal as AutoDeal)?.IsMultidrive == false
            });
        }

        private bool IsOwnerChangedCompletely(Owner actualOwner, Owner expectedOwner)
        {
            if (actualOwner == null || expectedOwner == null)
            {
                return false;
            }

            var fullNameChanges = InsuranceApp.Infrastructure.Procedures.LevenshteinDistance(FormatPersonName(actualOwner.Subject), FormatPersonName(expectedOwner.Subject)) > 2;
            var dateOfBirthChange = actualOwner.Subject.IndividualEntity.Person.DateOfBirth != expectedOwner.Subject.IndividualEntity.Person.DateOfBirth;
            var identityDocumentChanges = actualOwner.IdentityDocument.Series != expectedOwner.IdentityDocument.Series || actualOwner.IdentityDocument.Number != expectedOwner.IdentityDocument.Number;

            return fullNameChanges && dateOfBirthChange && identityDocumentChanges;
        }

        private void AddRuleForMinAgeExperience(AutoDeal actualAutodeal, AutoDeal expectedAutodeal)
        {
            var driverMinAge = expectedAutodeal?.DriverMinAge ?? 18;
            var driverMinExperience = expectedAutodeal?.DriverMinExperience ?? 0;

            if (actualAutodeal?.DriverMinAge > driverMinAge)
            {
                AddRule("Увеличение минимального возраста", "AutoDeal.DriverMinAge");
            }
            else if (actualAutodeal?.DriverMinAge < driverMinAge)
            {
                AddRule("Уменьшение минимального возраста", "AutoDeal.DriverMinAge");
            }

            if (actualAutodeal?.DriverMinExperience > driverMinExperience)
            {
                AddRule("Увеличение минимального стажа", "AutoDeal.DriverMinExperience");
            }
            else if (actualAutodeal?.DriverMinExperience < driverMinExperience)
            {
                AddRule("Уменьшение минимального стажа", "AutoDeal.DriverMinExperience");
            }
        }

        private string GetAddressCaption(Address address, string typeName) =>
            address.AddressTypeCode switch
            {
                AddressTypeCodes.Registration when typeName == nameof(Insurant) => ChangesCaptions.InsurantRegistrationAddress,
                AddressTypeCodes.Registration when typeName == nameof(Owner) => ChangesCaptions.OwnerRegistrationAddress,
                AddressTypeCodes.Fact when typeName == nameof(Insurant) => ChangesCaptions.InsurantFactAddress,
                AddressTypeCodes.Fact when typeName == nameof(Owner) => ChangesCaptions.OwnerFactAddress,
                _ => string.Empty,
            };

        protected override int GetObjectOrderNumber(string path)
        {
            switch (path)
            {
                case DealPathConstants.DealPeriods:
                    return 1;

                case DealPathConstants.Insurant:
                    return 2;

                case DealPathConstants.Owner:
                    return 3;

                case DealPathConstants.Auto:
                    return 4;

                case DealPathConstants.Drivers:
                    return 5;

                default:
                    return int.MaxValue;
            }
        }

        protected override string GetObjectTitle(string path)
        {
            switch (path)
            {
                case DealPathConstants.DealPeriods:
                    return "Периоды использования";

                case DealPathConstants.Insurant:
                    return "Страхователь";

                case DealPathConstants.Owner:
                    return "Владелец ТС";

                case DealPathConstants.Auto:
                    return "Транспортное средство";

                case DealPathConstants.Drivers:
                    return "Изменение списка ЛДУ";
            }

            return null;
        }

        protected override string GetItemName(string path)
        {
            if (path == DealPathConstants.Drivers)
            {
                return "водитель";
            }

            if (path == DealPathConstants.DealPeriods)
            {
                return "период";
            }

            return null;
        }

        protected override string GetItemText(string path, ObjectCompareResult compareResult)
        {
            if (path == DealPathConstants.DealPeriods)
            {
                if (compareResult.IsAdded)
                {
                    return FormatPeriod(compareResult.ActualObject as Period);
                }

                if (compareResult.IsDeleted)
                {
                    return FormatPeriod(compareResult.ExpectedObject as Period);
                }

                return null;
            }

            if (path == DealPathConstants.Drivers)
            {
                if (compareResult.IsAdded)
                {
                    return FormatDriver(compareResult.ActualObject as Driver);
                }

                if (compareResult.IsDeleted)
                {
                    return FormatDriverName(compareResult.ExpectedObject as Driver);
                }

                if (!compareResult.ChangedFields.Any(x => x.EndsWith("Name")))
                {
                    return FormatDriverName(compareResult.ExpectedObject as Driver);
                }
            }

            return null;
        }

        private static string FormatPeriodType(AutoDeal autoDeal)
        {
            if (autoDeal == null)
            {
                return null;
            }

            return autoDeal.InsurancePeriodTypeCode == InsurancePeriodTypeCodes.OneYear
                ? "Годовое использование"
                : "Периоды использования";
        }

        private static string FormatPeriod(Period period)
        {
            if (period == null)
            {
                return null;
            }

            var monthDifference = DurationHelper.FormatMonthDifference(
                period.DateStart.GetValueOrDefault(),
                period.DateEnd.GetValueOrDefault());

            return $"{monthDifference}, {period.DateStart?.ToShortDateForUi()} - {period.DateEnd?.ToShortDateForUi()}";
        }

        private static string FormatPersonName(Subject subject)
        {
            var person = subject?.IndividualEntity?.Person;

            if (person == null)
            {
                return null;
            }

            return $"{person.LastName} {person.Name} {person.MiddleName}".TrimEnd();
        }

        private static string FormatCitizenship(Citizenship citizenship)
        {
            if (citizenship == null)
            {
                return null;
            }

            if (citizenship.Code == "other")
            {
                return "Иное";
            }

            return citizenship.FullName;
        }

        private static string FormatPersonIdentityDocument(IdentityDocument document)
        {
            if (document == null)
            {
                return null;
            }

            var unitCodeString = string.IsNullOrEmpty(document.UnitCode)
                ? string.Empty
                : $", код подразделения {document.UnitCode}";

            var issueInfoString = document.DateIssue.HasValue || !string.IsNullOrEmpty(document.IssuedBy)
                ? $", {document.DateIssue?.ToShortDateForUi()} {document.IssuedBy}"
                : string.Empty;

            return $"{document.IdentityDocumentType?.Name} {document.Series} {document.Number}{unitCodeString}{issueInfoString}"
                .Trim();
        }

        private static string FormatAutoHasTrailer(Auto auto)
        {
            return auto?.HasTrailer == true ? "С прицепом" : "Без прицепа";
        }

        private static string FormatAutoTsIdentityCard(Auto auto)
        {
            var tsIdentityCard = auto?.TsIdentityCards?.FirstOrDefault();
            if (tsIdentityCard == null)
            {
                return null;
            }

            var dateOfIssuedString = tsIdentityCard.DateOfIssued.HasValue
                ? $", дата выдачи {tsIdentityCard.DateOfIssued.Value.ToShortDateForUi()}"
                : string.Empty;

            return $"{tsIdentityCard.Series} {tsIdentityCard.Number}{dateOfIssuedString}";
        }

        private static string FormatAutoTsIdentityCard(AutoDeal autoDeal)
        {
            var tsIdentityCard = autoDeal?.TsIdentityCard;
            if (tsIdentityCard == null)
            {
                return null;
            }

            var dateOfIssuedString = tsIdentityCard.DateOfIssued.HasValue
                ? $", дата выдачи {tsIdentityCard.DateOfIssued.Value.ToShortDateForUi()}"
                : string.Empty;

            return $"{tsIdentityCard.Series} {tsIdentityCard.Number}{dateOfIssuedString}";
        }

        private static string FormatIsMultidrive(AutoDeal autoDeal)
        {
            if (autoDeal == null)
            {
                return null;
            }

            return autoDeal.IsMultidrive == true ? "Без ограничения" : "Ограниченный круг водителей";
        }

        public static string FormatDriver(Driver driver)
        {
            if (driver == null)
            {
                return null;
            }

            var fields = new List<string>(4)
            {
                FormatDriverName(driver),
                FormatDriverDateOfBirth(driver),
                FormatDriverLicense(driver),
                FormatDriverDrivingExperiences(driver)
            };

            return string.Join(", ", fields.Where(x => x != null));
        }

        private static string FormatDriverName(Driver driver)
        {
            if (driver == null)
            {
                return null;
            }

            return FormatPersonName(driver.Subject);
        }

        private static string FormatDriverDateOfBirth(Driver driver)
        {
            var dateOfBirth = driver?.Subject?.IndividualEntity?.Person?.DateOfBirth;

            return dateOfBirth.HasValue
                ? $"дата рождения {dateOfBirth.Value.ToShortDateForUi()}"
                : null;
        }

        private static string FormatDriverLicense(Driver driver)
        {
            if (driver == null || (string.IsNullOrEmpty(driver.DriverLicenseSeries) && string.IsNullOrEmpty(driver.DriverLicenseNumber)))
            {
                return null;
            }

            return $"ВУ №{driver.DriverLicenseSeries} {driver.DriverLicenseNumber}";
        }

        private static string FormatIsForeingDriverLicense(Driver driver)
        {
            if (driver?.IsForeignDrivingLicense == null)
            {
                return null;
            }

            var result = "Иностранное ВУ";

            return driver.IsForeignDrivingLicense == true
                ? $"{result} Да"
                : $"{result} Нет";
        }

        private static string FormatDriverDrivingExperiences(Driver driver)
        {
            return driver?.DrivingExperiences.Count > 0
                ? $"стаж вождения с {driver.DrivingExperiences[0].DrivingExperienceDateStart?.ToShortDateForUi()}"
                : null;
        }
    }
}

Мне надо поменять логику определения изменений для Ф.И.О. или дата рождения Страхователя, Собственника, ЛДУ. 
Старая логика: 
Если изменение произошло по причине вступления / расторжения брака, т.е. передан признак isGettingMarried :true, то можно полностью изменить фамилию.



Новая логика:

Если в хотя бы из одном полей ФИО и ДР изменили более 2х символов, но при этом не изменили паспортные данные (блок identityDocument не передан), то считаем, что это корректировка данных существующего участника.

Если изменили менее 2х символов в полях ФИО и ДР, но при этом также изменили паспортные данные (блок identityDocument передан), то считаем, что это корректировка данных существующего участника.

Если для объекта owner/insurant в поле фамилии изменили более 2х символов, а в имени, отчестве и др 2 или менее символов и изменили паспортные данные (блок identityDocument передан), а также передан признак isGettingMarried = true, то считаем, что это корректировка данных существующего участника.

Если для объекта owner хотя бы в одном из полей ФИО и ДР изменили более 2х символов, и изменили паспортные данные (блок identityDocument передан) и признак isGettingMarried не передан, то считаем, что это изменение участника договора, т.е. изменение 43.

Если для объекта insurant хотя бы в одном из полей ФИО и ДР изменили более 2х символов, и изменили паспортные данные (блок identityDocument передан) и признак isGettingMarried не передан, то выводить error 510105 "Недопустимые изменения: не допускается изменение Страхователя на другое физическое лицо" 

По водителям также убрать ограничение на изменение 2х символов. Для определения, произошла ли корректировка данных или добавление нового водителя, сравнивать серию и номер ВУ из запроса и в БД.

Не добавляй новых методов, все дописывай в существующих и пиши названия классов куда внести изменения.