Отвечай на вопросы, используя следующий контекст:
ЗАДАЧА LKA-1935
КАСКО. Оформление кредитных авто с условиями банков. ч.1: Определение доступных/недоступных опций

1. Постановка задачи:
Я как посредник хочу оформлять полисы КАСКО на кредитные авто и видеть только доступные условия по каждому банку, чтобы соблюдать требования банка

Актуальная таблица с условиями банков размещена в вики: Файл с настройками по банкам

Данный функционал должен быть реализован только для партнёров B2P

2. Системные критерии
При переданном параметре "inCredit": true, в ответе на расчёт должны быть учтены условия, которые соответствуют требованиям банка.

Условия и опции, которые отсутствуют в таблице банков - выводятся по умолчанию, в зависимости от того, как они настроены в оффере (например, MILELIMIT - ограничение пробега)

Требования необходимо получить из таблицы BankSettings для конкретного банка по переданному на calculate bankOfCreditBik. Значение поля "code" в таблице BankSettings соответствует bankOfCreditBik

Также, необходимо учесть, что у некоторых банков в таблице различные доступные условия в зависимости от филиала продаж. 

1) Логика определения необходимой записи в таблице BankSettings :

Партнёр делает расчёт с кредитным ТС и переданным банком
Мы на своей стороне ищем в BankSettings запись, где code = bankOfCreditBik
          2.1. Запись найдена - в дальнейшем учитываем требования банка исходя из БД

          2.2. Найдено несколько записей - находим нужную по коду филиала. BankSettings.code = bankOfCreditBik AND BankSettings.DivisionID = Deal.BranchCode

          2.3. Не найдено ни одной записи по bankOfCreditBik - считаем, что передан иной банк, выводим стандартные условия в каждом оффере

4) Логика определения доступных условий исходя из переданного банка:

Риски, опции, варианты возмещения.

Реализуем через заведение правил в таблице Rule и ConditionSetting

В качестве примера возьмём опцию AVARCOMANY (Аварком на любые происшествия). Этой опции соответствует атрибут Avarkom3 из таблицы BankSettings. По умолчанию эта опция во всех офферах B2P не предустановленная и не обязательная.

а) У банка Avarkom3 = NULL - никаких изменений по опции не требуется. в ответе calculate по опции AVARCOMANY так и остаётся "isRequired": false, "isDefault": false

б) У банка Avarkom3 = 1 (опция обязательна) - в ответе calculate по опции AVARCOMANY в офферах проставляется "isRequired": true, "isDefault": true., "isSelected": true. Тем самым, флаг с этой опцией на UI CS становится неактивным и выбранным по умолчанию

Важно: нужно учесть взаимозаменяемые опции, и если выбрана одна, другую выбрать уже нельзя. Список взаимозаменяемых опций:

AVARCOM2PAR, AVARCOMACCIDENT, AVARCOMANY
GLASS1T, GLASSMANY
BODY3P1T, BODY5P2T
TECHCENTER, DEALERTECHCENTER, INSURANTTECHCENTER
в) У банка Avarkom3 = 0 (опция недоступна) - опция AVARCOMANY в офферах не выводится

Есть ряд опций/рисков, которые в некоторых офферах являются обязательными и убрать их нельзя. Но у банка этот риск/опция могут быть запрещены (пример: АО "Авто Финанс Банк", риск "ущерб" (DAMAGE) запрещен).

В этом случае в ответе расчёта не выводить такой оффер. Список офферов и обязательных опций в оффере.

Представляю git изменения кода в рамках решения задачи:

#Добавлен новый файл C:\Code\b-app-kasko\sources\App\src\Modules\iRIS.KaskoLink2\ChangeScripts\KaskoLink2.00017.LKA-1935.AddBanksRules.sql:

CREATE PROCEDURE tmp_AddRule
	@structId int
AS
DECLARE @productId int = (SELECT TOP 1 Id FROM Product WITH(NOLOCK) WHERE ProductDictCode = 'KASKO' AND State = 2 and IsDeleted = 0),
		@condition varchar(2000) = CONCAT('DiKaskoConstructorExternalFunctionsInvoker.IsConditionAllowedByBank(Deal,', @structId, ')'),
		@ruleId int;

SELECT @ruleId = Id FROM [Rule] WITH(NOLOCK) WHERE Condition = @condition AND Product_Id=@productId

IF @ruleId IS NULL
BEGIN 
	INSERT INTO [Rule] (Condition, EntityId, AreaId, CreateDate, State, DictionaryDate,	IsDeleted, Product_Id, InitializationType)
	VALUES (@condition, NEXT VALUE FOR SQ_Rule, 1,	GETDATE(), 2, GETDATE(), 0, @productId, 'FirstInitialization')
	
	SELECT @ruleId = SCOPE_IDENTITY()
END


IF NOT EXISTS (SELECT 1 FROM ConditionSetting WITH(NOLOCK) WHERE structId = @structId and Rule_Id = @ruleId)
BEGIN
	INSERT INTO ConditionSetting (StructId, 
									IsDefault, IsRequired,
									IsAvailable, [Check], Uncheck,
									EntityId, AreaId,
									CreateDate, State, DictionaryDate,
									IsDeleted, Rule_Id)
	VALUES (@structId,	--StructId
						NULL, NULL,	--IsDefault, IsRequired,
						0, NULL, NULL,	--IsAvailable, [Check], Uncheck,
						NEXT VALUE FOR SQ_ConditionSetting, 1,	--EntityId, AreaId,
						GETDATE(), 2, GETDATE(),	--CreateDate, State, DictionaryDate,
						0, @ruleId)	--IsDeleted, Rule_Id
END


IF NOT EXISTS (SELECT 1 FROM StructIdForRule WITH(NOLOCK) WHERE structId = @structId and Rule_Id = @ruleId)
BEGIN
	INSERT INTO StructIdForRule (StructId,
									EntityId, AreaId,
									CreateDate, State, DictionaryDate,
									IsDeleted, Rule_Id)
	VALUES (@structId,	--StructId
						NEXT VALUE FOR SQ_StructIdForRule, 1,	--EntityId, AreaId,
						GETDATE(), 2, GETDATE(),	--CreateDate, State, DictionaryDate,
						0, @ruleId)	--IsDeleted, Rule_Id
END
GO

EXEC tmp_AddRule 5012
EXEC tmp_AddRule 5004

DROP PROCEDURE tmp_AddRule
GO

# файл изменен C:\Code\b-app-kasko\sources\App\src\Modules\iRIS.KaskoLink2\Code\Constants\StructIdsCodes.cs:

diff --git a/sources/App/src/Modules/iRIS.KaskoLink2/Code/Constants/StructIdsCodes.cs b/sources/App/src/Modules/iRIS.KaskoLink2/Code/Constants/StructIdsCodes.cs
index 42d8f7c214..68af17cddb 100644
--- a/sources/App/src/Modules/iRIS.KaskoLink2/Code/Constants/StructIdsCodes.cs
+++ b/sources/App/src/Modules/iRIS.KaskoLink2/Code/Constants/StructIdsCodes.cs
@@ -2,28 +2,113 @@
 {
     public static class StructIdsCodes
     {
-        public const int Milelimit = 5023;
+        public const int AddEquipment = 5034;
 
-        public const int Damage = 5016;
+        /// <summary>
+        /// Аварийный комиссар: выезд на любое ДТП.
+        /// </summary>
+        public const int AvarcomAccident = 5013;
 
-        public const int TotalLoss = 5029;
+        /// <summary>
+        /// Аварийный комиссар: выезд на любое страховое событие.
+        /// </summary>
+        public const int AvarcomAny = 5551;
 
-        public const int ProductKasko = 3042;
+        /// <summary>
+        /// Аварийный комиссар: сбор справок.
+        /// </summary>
+        public const int AvarcomNote = 5014;
 
-        public const int AddEquipment = 5034;
+        /// <summary>
+        /// Аварийный комиссар: выезд на ДТП с двумя и более участниками или пострадавшими по жизни и здоровью.
+        /// </summary>
+        public const int Avarcom2Par = 5012;
+
+        /// <summary>
+        /// Угон/хищение.
+        /// </summary>
+        public const int CarjackingCode = 5031;
+
+        /// <summary>
+        /// Ущерб.
+        /// </summary>
+        public const int Damage = 5016;
+
+        /// <summary>
+        /// Ремонт на СТОА дилера по направлению Страховщика.
+        /// </summary>
+        public const int DealerTechcenter = 5485;
+
+        /// <summary>
+        /// Эвакуация.
+        /// </summary>
+        public const int Evacuation = 5004;
 
         public const int FranchiseCauser = 5027;
 
         public const int FranchiseSecondIncEvent = 5028;
 
+        /// <summary>
+        /// GAP (страхование на полную стоимость).
+        /// </summary>
+        public const int Gap = 5036;
+
+        /// <summary>
+        /// Стекла без справок 1 раз в год.
+        /// </summary>
+        public const int Glass1T = 5018;
+
+        /// <summary>
+        /// Стекла без справок без ограничений.
+        /// </summary>
+        public const int GlassMany = 5019;
+
+        public const int InstallmentPaymentsCode = 5096;
+
+        /// <summary>
+        /// Ремонт на СТОА по выбору Страхователя.
+        /// </summary>
+        public const int InsurantTechcenter = 5486;
+
+        public const int Milelimit = 5023;
+
+        public const int OneTimePaymentCode = 5097;
+
+        /// <summary>
+        /// Выплата без справок по кузовным деталям до 3% от страховой суммы один раз в год (BODY3P1T).
+        /// </summary>
         public const int Percent3 = 5020;
 
+        /// <summary>
+        /// Выплата без справок по кузовным деталям до 5% от страховой суммы два раза в год (BODY5P2T).
+        /// </summary>
         public const int Percent5 = 5021;
 
-        public const int OneTimePaymentCode = 5097;
+        public const int ProductKasko = 3042;
 
-        public const int InstallmentPaymentsCode = 5096;
+        /// <summary>
+        /// Аренда автомобиля.
+        /// </summary>
+        public const int Rent = 5011;
 
-        public const int CarjackingCode = 5031;
+        /// <summary>
+        /// Такси.
+        /// </summary>
+        public const int Taxi = 5005;
+
+        /// <summary>
+        /// Ремонт на СТОА (кроме дилеров) по направлению Страховщика.
+        /// </summary>
+        public const int Techcenter = 5484;
+
+        /// <summary>
+        /// Техпомощь.
+        /// </summary>
+        public const int TechHelp = 5006;
+
+        /// <summary>
+        /// Ущерб только на условиях "Полная гибель".
+        /// </summary>
+        public const int TotalLoss = 5029;
     }
 }


# Файл изменен C:\Code\b-app-kasko\sources\App\src\Modules\iRIS.KaskoLink2\Code\ExternalFunctions\DiKaskoConstructorExternalFunctionsInvoker.cs:

diff --git a/sources/App/src/Modules/iRIS.KaskoLink2/Code/ExternalFunctions/DiKaskoConstructorExternalFunctionsInvoker.cs b/sources/App/src/Modules/iRIS.KaskoLink2/Code/ExternalFunctions/DiKaskoConstructorExternalFunctionsInvoker.cs
index a4281ae05e..a736a87b65 100644
--- a/sources/App/src/Modules/iRIS.KaskoLink2/Code/ExternalFunctions/DiKaskoConstructorExternalFunctionsInvoker.cs
+++ b/sources/App/src/Modules/iRIS.KaskoLink2/Code/ExternalFunctions/DiKaskoConstructorExternalFunctionsInvoker.cs
@@ -1,21 +1,22 @@
-﻿using iRIS.Framework.RequestPipeline.Extensions;
+﻿using System;
+using System.Collections.Generic;
+using System.Linq;
+using iRIS.Framework.RequestPipeline.Extensions;
 using iRIS.Framework.Statement.Engine;
 using iRIS.InsuranceCore.Code.ConstructorCore.Models;
 using iRIS.InsuranceCore.Code.Extensions.Helpers;
 using iRIS.InsuranceCore.Code.Interfaces.ConstructorCore;
 using iRIS.InsuranceCore.Entities;
 using iRIS.InsuranceCore.Entities.EntityExtensions;
+using iRIS.KaskoCore.Code.Constants;
 using iRIS.KaskoLink2.Code.Constants;
 using iRIS.KaskoLink2.Code.Constructor.Helpers.EntityExtensions;
 using iRIS.KaskoLink2.Code.Interfaces;
+using iRIS.KaskoLink2.Code.Interfaces.BankSettings;
 using iRIS.KaskoLink2.Code.Link2;
 using iRIS.KaskoLink2.Code.TypeExtensions;
 using iRIS.KaskoLink2.ViewModels;
 using Microsoft.Extensions.DependencyInjection;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using iRIS.KaskoCore.Code.Constants;
 
 namespace iRIS.KaskoLink2.Code.ExternalFunctions
 {
@@ -290,5 +291,23 @@ namespace iRIS.KaskoLink2.Code.ExternalFunctions
 
             return true;
         }
+
+        public bool IsConditionAllowedByBank(Deal deal, int structId)
+        {
+            return CheckConditionByBank(deal, structId, false);
+        }
+
+        public bool IsConditionRequiredByBank(Deal deal, int structId)
+        {
+            return CheckConditionByBank(deal, structId, true);
+        }
+
+        private bool CheckConditionByBank(Deal deal, int structId, bool expectedValue)
+        {
+            var conditionChecker = _serviceProvider.GetRequiredService<IBankSettingsConditionChecker>();
+            var checkResult = conditionChecker.CheckCondition(deal, structId, expectedValue);
+
+            return checkResult;
+        }
     }
 }


# Файл добавлен C:\Code\b-app-kasko\sources\App\src\Modules\iRIS.KaskoLink2\Code\Implementations\BankSettings\BankSettingsConditionChecker.cs:

using System.Collections.Generic;
using System.Linq;
using iRIS.InsuranceCore.Entities;
using iRIS.KaskoLink2.Code.Constants;
using iRIS.KaskoLink2.Code.Interfaces.BankSettings;

namespace iRIS.KaskoLink2.Code.Implementations.BankSettings
{
    public class BankSettingsConditionChecker : IBankSettingsConditionChecker
    {
        private readonly IBankSettingsQueryInvoker _bankSettingsQueryInvoker;
        private readonly Dictionary<int, string> _structIdsToBankSettings = new Dictionary<int, string>
        {
            { StructIdsCodes.Avarcom2Par, "Avarkom1" },
            { StructIdsCodes.AvarcomAccident, "Avarkom2" },
            { StructIdsCodes.AvarcomAny, "Avarkom3" },
            { StructIdsCodes.AvarcomNote, "AvarkomSertificates" },
            { StructIdsCodes.Glass1T, "Mirrors1" },
            { StructIdsCodes.GlassMany, "Mirrors2" },
            { StructIdsCodes.Percent3, "Percent3" },
            { StructIdsCodes.Percent5, "Percent5" },
            { StructIdsCodes.Evacuation, "Evacuation" },
            { StructIdsCodes.TechHelp, "EmergencyRepair" },
            { StructIdsCodes.Taxi, "Taxi" },
            { StructIdsCodes.Rent, "CarRental" },
            { StructIdsCodes.Damage, "DmgEnabled" },
            { StructIdsCodes.CarjackingCode, "HJEnabled" },
            { StructIdsCodes.TotalLoss, "TotalEnabled" },
            { StructIdsCodes.Gap, "GAP" },
            { StructIdsCodes.Techcenter, "RepairStoaWithoutDealer" },
            { StructIdsCodes.DealerTechcenter, "RepairStoa" },
            { StructIdsCodes.InsurantTechcenter, "RepairStoaInsurant" }
        };

        public BankSettingsConditionChecker(IBankSettingsQueryInvoker bankSettingsQueryInvoker)
        {
            _bankSettingsQueryInvoker = bankSettingsQueryInvoker;
        }

        public bool CheckCondition(Deal deal, int structId, bool expectedValue)
        {
            var bank = deal.SuggestedBanks.FirstOrDefault();

            if (bank == null)
            {
                return false;
            }

            var bankSettings = _bankSettingsQueryInvoker.GetBankSettingsAsync(bank.BIK).GetAwaiter().GetResult();
            var setting = bankSettings.FirstOrDefault(x => x.DivisionID == deal.BranchCode) ?? bankSettings.FirstOrDefault(x => x.DivisionID == null);

            if (setting == null)
            {
                return false;
            }

            var settingProperty = setting.GetType().GetProperty(_structIdsToBankSettings[structId]);
            var settingValue = (bool?)settingProperty?.GetValue(setting);

            return settingValue.HasValue && settingValue.Value == expectedValue;
        }
    }
}

# Файл изменен C:\Code\b-app-kasko\sources\App\src\Modules\iRIS.KaskoLink2\Code\Implementations\BankSettings\BankSettingsQueryInvoker.cs:
diff --git a/sources/App/src/Modules/iRIS.KaskoLink2/Code/Implementations/BankSettings/BankSettingsQueryInvoker.cs b/sources/App/src/Modules/iRIS.KaskoLink2/Code/Implementations/BankSettings/BankSettingsQueryInvoker.cs
index ee49bee17b..7914ea28d4 100644
--- a/sources/App/src/Modules/iRIS.KaskoLink2/Code/Implementations/BankSettings/BankSettingsQueryInvoker.cs
+++ b/sources/App/src/Modules/iRIS.KaskoLink2/Code/Implementations/BankSettings/BankSettingsQueryInvoker.cs
@@ -1,11 +1,8 @@
 ﻿using System;
 using System.Collections.Generic;
 using System.Data.SqlClient;
-using System.Linq;
 using System.Threading.Tasks;
 using Dapper;
-using iRIS.Framework.Common;
-using iRIS.KaskoLink2.Code.Gazprom.Dto;
 using iRIS.KaskoLink2.Code.Interfaces.BankSettings;
 
 namespace iRIS.KaskoLink2.Code.Implementations.BankSettings
@@ -19,9 +16,25 @@ namespace iRIS.KaskoLink2.Code.Implementations.BankSettings
             _connectionString = connectionString ?? throw new ArgumentNullException(nameof(_connectionString));
         }
 
+        public async Task<IEnumerable<iRIS.KaskoLink2.Entities.BankSettings>> GetBankSettingsAsync(string bik)
+        {
+            var sqlQueryStr = @"SELECT * FROM BankSettings WITH (NOLOCK)
+                    WHERE
+                        Code = @bik"
+            ;
+
+            var command = new CommandDefinition(sqlQueryStr, new { bik });
+
+            using var connection = new SqlConnection(_connectionString);
+            connection.Open();
+
+            var result = await connection.QueryAsync<iRIS.KaskoLink2.Entities.BankSettings>(command);
+
+            return result;
+        }
+
         public async Task<IEnumerable<BankSettingsDictionaryDto>> GetBankSettingsDictionaryAsync()
         {
-            var dt = DT.Now;
             var sqlQueryStr = @"SELECT le.[Name]
                   ,bs.[Code] AS BIK
                   ,bs.[DivisionID]
@@ -89,7 +102,7 @@ namespace iRIS.KaskoLink2.Code.Implementations.BankSettings
             using var connection = new SqlConnection(_connectionString);
             connection.Open();
 
-            var queryResult = await connection.QueryAsync<BankSettingsDictionaryDto>(sqlQueryStr, new { dt });
+            var queryResult = await connection.QueryAsync<BankSettingsDictionaryDto>(sqlQueryStr);
 
             return queryResult;
         }

# Файл добавлен C:\Code\b-app-kasko\sources\App\src\Modules\iRIS.KaskoLink2\Code\Interfaces\BankSettings\IBankSettingsConditionChecker.cs: 
using iRIS.InsuranceCore.Entities;

namespace iRIS.KaskoLink2.Code.Interfaces.BankSettings
{
    public interface IBankSettingsConditionChecker
    {
        bool CheckCondition(Deal deal, int structId, bool expectedValue);
    }
}

# Файл изменен C:\Code\b-app-kasko\sources\App\src\Modules\iRIS.KaskoLink2\Code\Interfaces\BankSettings\IBankSettingsQueryInvoker.cs:

diff --git a/sources/App/src/Modules/iRIS.KaskoLink2/Code/Interfaces/BankSettings/IBankSettingsQueryInvoker.cs b/sources/App/src/Modules/iRIS.KaskoLink2/Code/Interfaces/BankSettings/IBankSettingsQueryInvoker.cs
index cc95c1fd33..0920b09e43 100644
--- a/sources/App/src/Modules/iRIS.KaskoLink2/Code/Interfaces/BankSettings/IBankSettingsQueryInvoker.cs
+++ b/sources/App/src/Modules/iRIS.KaskoLink2/Code/Interfaces/BankSettings/IBankSettingsQueryInvoker.cs
@@ -5,6 +5,8 @@ namespace iRIS.KaskoLink2.Code.Interfaces.BankSettings
 {
     public interface IBankSettingsQueryInvoker
     {
+        Task<IEnumerable<iRIS.KaskoLink2.Entities.BankSettings>> GetBankSettingsAsync(string bik);
+
         Task<IEnumerable<BankSettingsDictionaryDto>> GetBankSettingsDictionaryAsync();
     }
 }

Как мне сделать мою задачу:

Я как посредник хочу оформлять полисы КАСКО на кредитные авто и видеть только доступные условия по каждому банку, чтобы соблюдать требования банка

Системные критерии
1) Вывод доступных франшиз

Так же, как и с опциями из задачи LKA-1935 реализуем через заведение правил в Rule и ConditionSetting

В таблице BankSettings в поле FranType, указана доступность франшизы/тип доступной франшизы для конкретного банка:

а) FranType = NULL - доступна любая франшиза. Выводим все доступные в оффере франшизы

б) FranType = 0 - франшиза недоступна. Банк не принимает полисы с франшизой. В этом случае в предложениях должны быть только офферы "Конструктор" и "От тотальных убытков"

в) FranType = 1 - доступна только безусловная франшиза. В B2P нет этого типа франшизы. В этом случае считаем, что 1 = NULL и следуем логике из а)

г) FranType = 2 - доступна только франшиза 2INSEVENT (со 2го СС). Выводить все офферы (в т. ч. "От тотальных убытков"), за исключением "Оптимальная защита". На практике таких банков в таблице не существует. Можно не делать эту логику

д) FranType = 3 - доступна только франшиза CAUSER (виновника). Выводить все офферы (в т. ч. "От тотальных убытков"), за исключением "Аккуратным водителям". На практике таких банков в таблице не существует. Можно не делать эту логику

Так же, как и опции, франшизы 2INSEVENT и CAUSER взаимозаменяемы (нельзя выбрать сразу две)