From b2f86523817b2498d09a0bcf5812cabe5a64ad69 Mon Sep 17 00:00:00 2001 From: zuevav Date: Wed, 23 Sep 2026 16:48:25 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D1=81=D1=88=D0=B8=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20Fancy=20DEPO=20=D0=B4=D0=BB=D1=8F=20M2M=20?= =?UTF-8?q?(=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D0=B4=D1=83=D1=80=D1=8B=20?= =?UTF-8?q?=D0=A4=D0=BE=D0=BD=D1=82=D0=B2=D1=8C=D0=B5=D0=BB=D1=8C,=20?= =?UTF-8?q?=D1=81=D0=BD=D1=8F=D1=82=D0=BE=2023.09):=20=D1=82=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D1=82=D1=8B,=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8?= =?UTF-8?q?=D0=BC=D0=BE=D1=81=D1=82=D0=B8,=20=D0=BF=D0=B0=D1=80=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D1=80=D1=8B=20=D0=B4=D0=BB=D1=8F=20=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA=D0=B8=20=D1=83=20?= =?UTF-8?q?=D0=B4=D1=80=D1=83=D0=B3=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=BB=D0=B8?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL --- deploy/depo/fancy-m2m/FTV_M2M_JOB.sql | 71 +++++ .../depo/fancy-m2m/FTV_M2M_PROCESS_CREATE.sql | 188 ++++++++++++ .../depo/fancy-m2m/FTV_M2M_PROCESS_EVENTS.sql | 277 ++++++++++++++++++ .../fancy-m2m/FTV_M2M_PROCESS_OPERATION.sql | 165 +++++++++++ .../depo/fancy-m2m/FTV_M2M_PROCESS_WIRING.sql | 172 +++++++++++ deploy/depo/fancy-m2m/README.md | 37 +++ 6 files changed, 910 insertions(+) create mode 100644 deploy/depo/fancy-m2m/FTV_M2M_JOB.sql create mode 100644 deploy/depo/fancy-m2m/FTV_M2M_PROCESS_CREATE.sql create mode 100644 deploy/depo/fancy-m2m/FTV_M2M_PROCESS_EVENTS.sql create mode 100644 deploy/depo/fancy-m2m/FTV_M2M_PROCESS_OPERATION.sql create mode 100644 deploy/depo/fancy-m2m/FTV_M2M_PROCESS_WIRING.sql create mode 100644 deploy/depo/fancy-m2m/README.md diff --git a/deploy/depo/fancy-m2m/FTV_M2M_JOB.sql b/deploy/depo/fancy-m2m/FTV_M2M_JOB.sql new file mode 100644 index 0000000..62ad39b --- /dev/null +++ b/deploy/depo/fancy-m2m/FTV_M2M_JOB.sql @@ -0,0 +1,71 @@ +CREATE OR ALTER procedure [dbo].[FTV_M2M_JOB] + + as + set nocount on; + + declare + @ID int, + @STATE varchar(64); + + declare + @ERROR_NUMBER int, + @COMMENT varchar(MAX); + + --Новые поручения + declare cur cursor local fast_forward read_only for select ID from FTV_M2M M where M.STATE = 'TODO' order by M.TimeNew; + + open cur; + while 0 = 0 begin fetch next from cur into @ID; + if @@FETCH_STATUS <> 0 break; + + --Тут можно отправить уведомление, создать входящее сообщение и т.д. + update FTV_M2M set TimeBeg = GETDATE() where ID = @ID; + + --Главный процесс + begin try + exec FTV_M2M_PROCESS_CREATE @ID; + set @STATE = 'SUCCESS'; + end try + begin catch + set @ERROR_NUMBER = ERROR_NUMBER(); + set @COMMENT = ERROR_MESSAGE() + ' // ' + ERROR_PROCEDURE() + ' // ' + trim(str(ERROR_LINE())); + set @STATE = 'ERROR' + end catch + + --Тут можно уведомить об ошибочном статусе + update FTV_M2M set STATE = @STATE, TimeEnd = GETDATE(), COMMENT = @COMMENT where ID = @ID; + + end; + close cur; + deallocate cur; + + + --Новые события + declare cur cursor local fast_forward read_only for select ID from FTV_M2M_EVENTS ME where ME.STATE = 'TODO' order by ME.TimeNew; + + open cur; + while 0 = 0 begin fetch next from cur into @ID; + if @@FETCH_STATUS <> 0 break; + + --Тут можно отправить уведомление, создать входящее сообщение и т.д. + update FTV_M2M_EVENTS set TimeBeg = GETDATE() where ID = @ID; + + --Процесс изменения событий + begin try + exec FTV_M2M_PROCESS_EVENTS @ID; + set @STATE = 'SUCCESS'; + end try + begin catch + set @ERROR_NUMBER = ERROR_NUMBER(); + set @COMMENT = ERROR_MESSAGE() + ' // ' + ERROR_PROCEDURE() + ' // ' + trim(str(ERROR_LINE())); + set @STATE = 'ERROR' + end catch + + --Тут можно уведомить об ошибочном статусе + update FTV_M2M_EVENTS set STATE = @STATE, TimeEnd = GETDATE(), COMMENT = @COMMENT where ID = @ID; + + end; + close cur; + deallocate cur; + + /* тестовый блок удалён при переносе в репозиторий */ diff --git a/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_CREATE.sql b/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_CREATE.sql new file mode 100644 index 0000000..611c8e3 --- /dev/null +++ b/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_CREATE.sql @@ -0,0 +1,188 @@ +CREATE OR ALTER procedure [dbo].[FTV_M2M_PROCESS_CREATE] (@ID int) + + /* + TODO: + Дата поручения - сейчас getdate, но можно передавать, для этого ввести пновое поле в FTV_M2M, или потом брать з события и изменять + Для входящий переводов из СПБ возможно следует дать raiserror + */ + + as + set nocount on; + + if user = 'dbo' execute as user = 'SYSDBA'; + + print 'FTV_M2M_PROCESS_CREATE'; + print @ID; + + --Создаем M2M-поручение вместе с подвалом документа + + declare + @WHOS int = 383118, + @GUID uniqueidentifier, + --шапка + @DIRECTION varchar(64), + @CONTRAGENT_M2M_CODE varchar(255), + @DEPONENT_NAME varchar(255), + @DEPONENT_DOC varchar(255), + --подвал + @REFERENCE varchar(36), + @ISIN varchar(12), + @SEC_CODE varchar(255), + @AMOUNT numeric(25, 7), + @PLACE_NUMBER varchar(255), + @LINK_NUMBER varchar(128), + @DEPO_NUMBER varchar(128), + --переменные документа + @D_CAT int, + @NUM varchar(64), + @D_DATE datetime, + @CONTRAGENT int, + @DEPONENT int, + @SHARE int, + @PLACE int, + @DEPO int, + @CLISER_ID int, + @FILER_ID int, + @FILER_NUM varchar(64), + @FILER_DATE datetime, + @DOL_ID int; + + select + @GUID = GUID, + @DIRECTION = DIRECTION, + @CONTRAGENT_M2M_CODE = CONTRAGENT_M2M_CODE, + @DEPONENT_NAME = DEPONENT_NAME, + @DEPONENT_DOC = DEPONENT_DOC + from FTV_M2M where ID = @ID; + + --1. Критические ошибки + --1.1. Неуникальный GUID + if exists (select top 1 1 from D_CLISER_M2M where DOC_GUID = @GUID) + raiserror ('Неуникальный GUID', 16, 1); + + + --1.2. Направление перевода + if @DIRECTION = 'IN' + select @D_CAT = ID from OD_DOC_CATS where SYS_NAME = 'I_CLISER_M2M_ZACH' + else if @DIRECTION = 'OUT' + select @D_CAT = ID from OD_DOC_CATS where SYS_NAME = 'I_CLISER_M2M_SPIS' + else + raiserror ('Ошибочное направление DIRECTION (должно быть IN, OUT)', 16, 1); + + + --1.3. Контрагент + select @CONTRAGENT = C.FACE + from OD_FACE_CODERS C + left join OD_FACES F on F.SELF_ID = C.CODER and F.LAST_FLAG = 1 + where F.MNEM = 'M2M' and C.CODE = @CONTRAGENT_M2M_CODE; + if @CONTRAGENT is null + raiserror ('Не найден депозитарий-контрагент', 16, 1); + + + --1.4. Депонент + select @DEPONENT = F.SELF_ID + from OD_FACES F + where F.LAST_FLAG = 1 + and replace(F.NAME, ' ', '') = replace(@DEPONENT_NAME, ' ', '') + and replace(F.DOC_SER + F.DOC_NUM, ' ', '') = replace(@DEPONENT_DOC, ' ', '') + + + --1.5. Шапка документа + exec GEN_ID 'OD_DOC_ID_GEN', 1, @CLISER_ID out; + --TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + set @D_DATE = getdate(); + + exec FU_NEXT_DOC_NUM @D_CAT, @D_DATE, null, @CLISER_ID, @NUM out, null; + + insert into OD_DOCS + (ID, NUM, PARAM, D_CAT, D_DATE, STATE, DIRECTION, WHOS, KVIT) + select + @CLISER_ID, @NUM, null, @D_CAT, @D_DATE, 0, 0, @WHOS, null; + + insert into D_CLISER_M2M + (DOC, DEPONENT, DOC_GUID, DOC_STATUS, IIS_TYPE, IIS_INN, IIS_NUM, IIS_DATE) + select + @CLISER_ID, @DEPONENT, @GUID, 0, 0, null, null, null; + + if @DIRECTION = 'IN' + insert into OD_IN_DOCS (DOC, FROM_FACE, PASS_KIND) values (@CLISER_ID, @CONTRAGENT, 8); + else if @DIRECTION = 'OUT' + insert into OD_OUT_DOCS (DOC, TO_FACE, PASS_KIND) values (@CLISER_ID, @CONTRAGENT, 8); + + --1.6. Обогащаем FTV_M2M + update FTV_M2M set + DOC = @CLISER_ID, + CONTRAGENT = @CONTRAGENT, + DEPONENT = @DEPONENT + where GUID = @GUID; + + + --1.7. Подвал документа (только в курсоре) + declare cur cursor local fast_forward read_only for + select ML.REFERENCE, ML.ISIN, ML.SEC_CODE, ML.AMOUNT, ML.PLACE_NUMBER, ML.LINK_NUMBER, ML.DEPO_NUMBER + from FTV_M2M_LINES ML + join FTV_M2M M on M.GUID = ML.GUID + where ML.GUID = @GUID and ML.STATE = 'TODO' + order by ML.ID; + + open cur; + while 0 = 0 begin fetch next from cur into @REFERENCE, @ISIN, @SEC_CODE, @AMOUNT, @PLACE_NUMBER, @LINK_NUMBER, @DEPO_NUMBER; + if @@FETCH_STATUS <> 0 break; + + --Бумага + select @SHARE = V.ID + from OD_VALUES V + left join OD_VALUE_CODERS C on C.SHARE = V.ID + left join OD_FACES F on F.SELF_ID = C.CODER and F.LAST_FLAG = 1 + where V.ISIN = @ISIN and F.MNEM = 'НДЦ' and C.CODE = @SEC_CODE; + + --Счет МХ + select @PLACE = C.ID + from OD_CLIENTS C + left join OD_CLIENTS_REC CR on CR.CLIENT = C.SELF_ID and CR.E_DATE = '9999-01-01' + where C.NUMBER like '%' + @PLACE_NUMBER + '%' and CR.LINK_NUMBER = @LINK_NUMBER; + + --Счет клиента + select @DEPO = C.ID + from OD_CLIENTS C + where C.NUMBER = @DEPO_NUMBER; + + exec GEN_ID 'OD_DOL_ID_GEN', 1, @DOL_ID out; + + insert into OD_DOLS (ID, DOC) values (@DOL_ID, @CLISER_ID); + + insert into D_CLISER_M2M_DOL (DOL, SHARE, AMOUNT, DOL_REF, PLACE, CLIENT) values (@DOL_ID, @SHARE, @AMOUNT, @REFERENCE, @PLACE, @DEPO); + + --Обогащаем FTV_M2M_LINES + update FTV_M2M_LINES set + STATE = 'SUCCESS', + DOC = @CLISER_ID, + DOL = @DOL_ID, + SHARE = @SHARE, + PLACE = @PLACE, + DEPO = @DEPO + where GUID = @GUID and REFERENCE = @REFERENCE; + + end; + close cur; + deallocate cur; + + + --1.8. Входящая запись только для списания (исходящего перевода OUT) + if @DIRECTION = 'OUT' begin + exec GEN_ID 'OD_DOC_ID_GEN', 1, @FILER_ID out; + + --TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + set @FILER_DATE = getdate(); + + exec FU_NEXT_DOC_NUM @D_CAT, @FILER_DATE, null, @FILER_ID, @FILER_NUM out, null; + + insert into OD_DOCS (ID, NUM, D_CAT, D_DATE, STATE, DIRECTION, WHOS) + values (@FILER_ID, @FILER_NUM, 255, @FILER_DATE, 2, 1, @WHOS); + + insert into D_FILER (DOC, PASS_KIND, RUBRICA, COMMENT, DOC_NUM, DOC_DATE, CORR_FACE, FUND, ACCOUNT, PLACE, ORI_DATE, CHILD_CAT) + values (@FILER_ID, 7, 173, 'M2M поручение на списание через MOEX-МОСТ', @NUM, cast(@D_DATE as date), @DEPONENT, @DEPONENT, @DEPO, @PLACE, @FILER_DATE, @D_CAT); + + --Связываем с М2М-поручением + update OD_DOCS set ID = 0, KVIT = @FILER_ID where ID = @CLISER_ID; + end; diff --git a/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_EVENTS.sql b/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_EVENTS.sql new file mode 100644 index 0000000..51b0b29 --- /dev/null +++ b/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_EVENTS.sql @@ -0,0 +1,277 @@ +CREATE OR ALTER procedure [dbo].[FTV_M2M_PROCESS_EVENTS] (@ID int) + + as + set nocount on; + /* + Вынести создание операции в отдельную процедуру FTV_M2M_PROCESS_OPERATION (параметры - @GUID и @REFERENCE, но проверку параметров оставить тут). Будем возвращать @OPER_ID + */ + + if user = 'dbo' execute as user = 'SYSDBA'; + + print 'FTV_M2M_PROCESS_EVENTS'; + print @ID; + + --Обрабатываем событие по M2M-поручению + + declare + @WHOS int = 383118, + @GUID uniqueidentifier, + @REFERENCE varchar(255), + @EVENT_TYPE varchar(64), + @EVENT_DATE datetime, + @M2M_REJECT_CODE varchar, + @PARAMETERS varchar(max), + @DIRECTION varchar(64), + -- + @CONTRAGENT_FACE int, + @DEPONENT_FACE int, + @SHARE int, + @AMOUNT numeric(27, 7), + @PLACE int, + @CLIENT int, + @FACE int, + @CLISER_ID int, + @DOL_ID int, + -- + @D_CAT int, + @NUM varchar(64), + @D_DATE datetime, + @OPER_ID int, + @DD_DATE datetime, + @DD_NUM varchar(64); + + + -- FTV_M2M_EVENTS.REFERENCE может быть пустой, в этом случае событие относится ко всему М2М-поручению + select + @GUID = ME.GUID, + @REFERENCE = ME.REFERENCE, + @EVENT_TYPE = ME.EVENT_TYPE, + @EVENT_DATE = ME.EVENT_DATE, + @M2M_REJECT_CODE = ME.M2M_REJECT_CODE, + @DIRECTION = M.DIRECTION + from FTV_M2M_EVENTS ME + left join FTV_M2M M on M.GUID = ME.GUID + where ME.ID = @ID; + + if @REFERENCE is null begin + --Не все события могут быть применены ко всему М2М-поручению + if not (@EVENT_TYPE in ('BJ_RECEIVED', 'BJ_SENT', 'BJ_ORDER_SENT', 'BJ_ORDER_ACCEPTED', 'BJ_EXECUTED') or @EVENT_TYPE like 'BJ_QUIK_%') + raiserror ('Событие этого типа не может быть указано без REFERENCE', 16, 1); + + select + @REFERENCE = ME.REFERENCE, + @CLISER_ID = H.DOC, + @DOL_ID = F.DOL, + @DD_DATE = cast(D.D_DATE as date), + @DD_NUM = D.NUM, + @SHARE = F.SHARE, + @AMOUNT = F.AMOUNT, + @PLACE = F.PLACE, + @CLIENT = F.CLIENT, + @FACE = H.DEPONENT + from FTV_M2M_EVENTS ME + left join FTV_M2M_LINES ML on ML.GUID = ME.GUID and ML.REFERENCE = ME.REFERENCE + left join FTV_M2M M on M.GUID = ME.GUID + left join D_CLISER_M2M_DOL F on F.DOL_REF = ME.REFERENCE + left join D_CLISER_M2M H on H.DOC_GUID = ME.GUID + left join OD_DOCS D on D.ID = H.DOC + where ME.ID = @ID; + end + else begin + select + @REFERENCE = ME.REFERENCE, + @CLISER_ID = H.DOC, + @DOL_ID = F.DOL, + @DD_DATE = cast(D.D_DATE as date), + @DD_NUM = D.NUM, + @SHARE = F.SHARE, + @AMOUNT = F.AMOUNT, + @PLACE = F.PLACE, + @CLIENT = F.CLIENT, + @FACE = H.DEPONENT + from FTV_M2M_EVENTS ME + left join FTV_M2M_LINES ML on ML.GUID = ME.GUID and ML.REFERENCE = ME.REFERENCE + left join FTV_M2M M on M.GUID = ME.GUID + left join D_CLISER_M2M_DOL F on F.DOL_REF = ME.REFERENCE + left join D_CLISER_M2M H on H.DOC_GUID = ME.GUID + left join OD_DOCS D on D.ID = H.DOC + where ME.ID = @ID; + + --Критические ошибки + if @DOL_ID is null + raiserror ('Не найдена строка M2M-поручение по GUID + REFERENCE', 16, 1); + end; + + + --Пишем в признаки документа + insert into OD_INVALIDS (DOC, STATUS, COMMENT, DO_ING) + values ( + @CLISER_ID, + 0, + 'Обработано событие: ' + FORMAT(@EVENT_DATE, 'dd.MM.yyyy HH:mm:ss') + ' - ' + @EVENT_TYPE + coalesce (' (референс - ' + @REFERENCE + ')', '') + coalesce (' (парамерты - ' + @PARAMETERS + ')', ''), + 'Время начала обработки: ' + FORMAT(GETDATE(), 'dd.MM.yyyy HH:mm:ss') + ) + + print @EVENT_TYPE; + + --ОБРАБОТКА КАЖДОГО ТИПА СОБЫТИЯ + + if @EVENT_TYPE = 'BJ_RECEIVED' and @DIRECTION = 'OUT' begin + update OD_DOCS set ID = 0, STATE = 1 where ID = @CLISER_ID; --START - Начало обработки (зарегистрирован) + end + + else + if @EVENT_TYPE = 'BJ_SENT' and @DIRECTION = 'IN' begin + update OD_DOCS set ID = 0, STATE = 1 where ID = @CLISER_ID; --START - Начало обработки (зарегистрирован) + end + + else + if @EVENT_TYPE = 'BJ_DECISION_REJECT' and @DIRECTION = 'IN' begin + --M2M_STATUS = 10 TODO!!!!!!!!!!!!!!!!! + update D_CLISER_M2M_DOL set M2M_STATUS = 10, M2M_TEXT = '???' where DOL = @DOL_ID; + --только если отказ по всем строкам подвала документа + if not exists (select top 1 DOL from D_CLISER_M2M_DOL F left join OD_DOLS L on L.ID = F.DOL where L.DOC = @CLISER_ID and (M2M_STATUS is null or M2M_TEXT is null)) begin + update OD_DOCS set ID = 0, STATE = 3 where ID = @CLISER_ID; --REJECT - Отмена обработки + end; + end + + else + if @EVENT_TYPE = 'BJ_CP_REJECT' and @DIRECTION = 'OUT' begin + --M2M_STATUS = 10 TODO!!!!!!!!!!!!!!!!! + update D_CLISER_M2M_DOL set M2M_STATUS = 10, M2M_TEXT = '???' where DOL = @DOL_ID; + --только если отказ по всем строкам подвала документа + --TODO зачем проверять все строки подвала, если мы не будем делать многострочные исходящие??? + if not exists (select top 1 DOL from D_CLISER_M2M_DOL F left join OD_DOLS L on L.ID = F.DOL where L.DOC = @CLISER_ID and (M2M_STATUS is null or M2M_TEXT is null)) begin + update OD_DOCS set ID = 0, STATE = 3 where ID = @CLISER_ID; --REJECT - Отмена обработки + end; + end + + else + --TODO что делать если мы проставили статус не по всем строкам документа?? + if @EVENT_TYPE = 'BJ_DECISION_CONFIRM' and @DIRECTION = 'IN' begin + update OD_DOCS set ID = 0, STATE = 1 where ID = @CLISER_ID; --START - Начало обработки (зарегистрирован) --ОНО И ТАК УЖЕ ДОЛЖНО БЫТЬ В ЭТОМ СОСТОЯНИИ!!! + end + + else + --TODO что делать если контрагент проставил статус не по всем строкам документа?? + if @EVENT_TYPE = 'BJ_CP_CONFIRM' and @DIRECTION = 'OUT' begin + update OD_DOCS set ID = 0, STATE = 1 where ID = @CLISER_ID; --START - Начало обработки (зарегистрирован) --ОНО И ТАК УЖЕ ДОЛЖНО БЫТЬ В ЭТОМ СОСТОЯНИИ!!! + end + + else + if @EVENT_TYPE = 'BJ_SERVICE_ERROR' or @EVENT_TYPE = 'BJ_CANCELLED' begin + update OD_DOCS set ID = 0, STATE = 3 where ID = @CLISER_ID; --REJECT - Отмена обработки + end + + else + if @EVENT_TYPE = 'BJ_ORDER_ACCEPTED' begin + -----TODO exec FTV_M2M_PROCESS_OPERATION @GUID, @REFERENCE, @EVENT_DATE, @OPER_ID out; + + --Направление + select @D_CAT = DC.ID + from OD_DOC_CATS DC + where DC.SYS_NAME = case when @DIRECTION = 'IN' then 'OP_INV_ZACH' else 'OP_INV_SPIS' end; + + set @D_DATE = @EVENT_DATE; + + exec GEN_ID 'OD_DOC_ID_GEN', 1, @OPER_ID out; + exec FU_NEXT_DOC_NUM @D_CAT, @D_DATE, null, @OPER_ID, @NUM out, @D_DATE out; + + insert into OD_DOCS (ID, NUM, PARAM, D_CAT, D_DATE, STATE, DIRECTION, WHOS, KVIT) + values (@OPER_ID, @NUM, null, @D_CAT, @D_DATE, 0, 0, @WHOS, @CLISER_ID); + + if @DIRECTION = 'IN' begin + --порядок полей - по форме документа (сверху вниз) + insert into U_OP_INV_ZACH + ( + DOC, + DD_TYPE, + DD_DATE, DD_NUM, + SHARE, AMOUNT, BLANKS, + PLACE, + D_NUM, + DEPO, + K_NUM, + MARKET, + CONDITION, IS_REG, TAX_R, + B_TYPE, + B_DATE, B_NUM, + WIR_DATE, NOTIFY_DATE, REG_DATE, + IS_BLOCK, + FROM_FACE, + REJECT_REASON, + COMMENT + ) + values + ( + @OPER_ID, + 1 /*Клиентское*/, + @DD_DATE, @DD_NUM, + @SHARE, @AMOUNT, null, + @PLACE, + 314, /*98015 - 0 - Ценные бумаги и цифровые активы на хранении в других депозитариях (НОСТРО депо расчетный)*/ + @CLIENT, + 836, /*98070 - 4 - Ценные бумаги и цифровые активы на торгах*/ + 8106, /*ММВБ - ПАО Московская Биржа*/ + null, null, null, + 31, /*Отчет депозитария*/ + @EVENT_DATE, 'Вопрос, что тут пишем', + @EVENT_DATE, @EVENT_DATE, @EVENT_DATE, --Берем из BJ + 0, + @FACE, + null, --А есди это отмена?? + 'Перевод M2M (без поручения), GUID ' + cast(@GUID as varchar(36)) + ', референс ' + @REFERENCE + ); + end + + else + if @DIRECTION = 'OUT' begin + insert into U_OP_INV_SPIS + ( + DOC, + DD_TYPE, + DD_DATE, DD_NUM, + SHARE, AMOUNT, BLANKS, + PLACE, + D_NUM, + DEPO, + K_NUM, + MARKET, + OLD_CONDITIONS, IS_REG, TAX_R, + B_TYPE, + B_DATE, B_NUM, + WIR_DATE, NOTIFY_DATE, REG_DATE, + IS_BLOCK, + FROM_FACE, + REJECT_REASON, + COMMENT + ) + values + ( + @OPER_ID, + 1 /*Клиентское*/, + @DD_DATE, @DD_NUM, + @SHARE, @AMOUNT, null, + @PLACE, + 314, /*98015 - 0 - Ценные бумаги и цифровые активы на хранении в других депозитариях (НОСТРО депо расчетный)*/ + @CLIENT, + 836, /*98070 - 4 - Ценные бумаги и цифровые активы на торгах*/ + 8106, /*ММВБ - ПАО Московская Биржа*/ + null, null, null, + 31, /*Отчет депозитария*/ + @EVENT_DATE, 'Вопрос, что тут пишем', + @EVENT_DATE, @EVENT_DATE, @EVENT_DATE, --Берем из BJ + 0, + @FACE, + null, --А есди это отмена?? + 'Отчет НКО АО НРД [номер, дата]. Перевод M2M, GUID ' + cast(@GUID as varchar(36)) + ', референс ' + @REFERENCE + ); + end; + + --И сразу проводим + exec FTV_M2M_PROCESS_WIRING @OPER_ID; + + end + else if @EVENT_TYPE = 'CREATE_WIRING' begin + print 'CREATE_WIRING' + end; diff --git a/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_OPERATION.sql b/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_OPERATION.sql new file mode 100644 index 0000000..4e8892c --- /dev/null +++ b/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_OPERATION.sql @@ -0,0 +1,165 @@ +CREATE OR ALTER procedure [dbo].[FTV_M2M_PROCESS_OPERATION] @GUID uniqueidentifier, @REFERENCE varchar(255), @OPER_DATE datetime, @OPER_ID int output + + /* + Создает операцию, возвращает OPER_ID созданной операции + Параметры нацелены прежде всего на строки уже созданного М2М-поручения D_CLISER_M2M_DOL, а не на буферные таблицы (к ним больше не обращаемся) + Все проверки идут до вызова, но элементарные проверки можно сделать и тут. Или дать raiserror + Мы существенно разгружаем FTV_M2M_PROCESS_EVENTS + + declare @OPER_ID int; + exec FTV_M2M_PROCESS_OPERATION @GUID = 'BAE1FA60-EB0A-4FEE-B12C-018D1EAFCBD2', @REFERENCE = 'M2M0917165643657', @OPER_DATE = '20260922', @OPER_ID = @OPER_ID out; + select @OPER_ID; + + */ + + as + set nocount on; + + if user = 'dbo' execute as user = 'SYSDBA'; + + print 'FTV_M2M_PROCESS_OPERATION' + ' GUID = ' + cast(@GUID as varchar(36)) + ', REFERENCE = ' + @REFERENCE; + + declare + @WHOS int = 383118, + @CLISER_ID int, + @CLISER_NUM varchar(64), + @CLISER_DATE datetime, + @DIRECTION varchar(64), + @DD_DATE datetime, + @DD_NUM varchar(64), + @SHARE int, + @AMOUNT numeric(27, 7), + @CLIENT int, + @FACE int, + @PLACE int, + @D_CAT int, + @NUM varchar(64); + + select + @CLISER_ID = H.DOC, + @CLISER_NUM = D.NUM, + @CLISER_DATE= D.D_DATE, + @DIRECTION = case + when DC.SYS_NAME = 'I_CLISER_M2M_ZACH' then 'IN' + when DC.SYS_NAME = 'I_CLISER_M2M_SPIS' then 'OUT' + end, + @DD_DATE = cast(D.D_DATE as date), + @DD_NUM = D.NUM, + @SHARE = F.SHARE, + @AMOUNT = F.AMOUNT, + @CLIENT = F.CLIENT, + @FACE = H.DEPONENT, + @PLACE = F.PLACE + from D_CLISER_M2M_DOL F + left join OD_DOLS L on L.ID = F.DOL + left join OD_DOCS D on D.ID = L.DOC + left join D_CLISER_M2M H on H.DOC = D.ID + left join OD_DOC_CATS DC on DC.ID = D.D_CAT + where H.DOC_GUID = @GUID and F.DOL_REF = @REFERENCE; + + --Элеметарные проверки + if @CLISER_ID is null + raiserror ('Не найдено M2M-поручение по GUID + REFERENCE', 16, 1); + + --Направление + select @D_CAT = DC.ID + from OD_DOC_CATS DC + where DC.SYS_NAME = case when @DIRECTION = 'IN' then 'OP_INV_ZACH' else 'OP_INV_SPIS' end; + + --ID и номер операции + exec GEN_ID 'OD_DOC_ID_GEN', 1, @OPER_ID out; + exec FU_NEXT_DOC_NUM @D_CAT, @OPER_DATE, null, @OPER_ID, @NUM out, null; + + --Создаем документ + insert into OD_DOCS (ID, NUM, PARAM, D_CAT, D_DATE, STATE, DIRECTION, WHOS, KVIT) + values (@OPER_ID, @NUM, null, @D_CAT, @OPER_DATE, 0, 0, @WHOS, @CLISER_ID); + + if @DIRECTION = 'IN' begin + insert into U_OP_INV_ZACH + ( + DOC, + DD_TYPE, + DD_DATE, DD_NUM, + SHARE, AMOUNT, BLANKS, + PLACE, + D_NUM, + DEPO, + K_NUM, + MARKET, + CONDITION, IS_REG, TAX_R, + B_TYPE, + B_DATE, B_NUM, + WIR_DATE, NOTIFY_DATE, REG_DATE, + IS_BLOCK, + FROM_FACE, + REJECT_REASON, + COMMENT + ) + values + ( + @OPER_ID, + 1 /*Клиентское*/, + @DD_DATE, @DD_NUM, + @SHARE, @AMOUNT, null, + @PLACE, + 314, /*98015 - 0 - Ценные бумаги и цифровые активы на хранении в других депозитариях (НОСТРО депо расчетный)*/ + @CLIENT, + 836, /*98070 - 4 - Ценные бумаги и цифровые активы на торгах*/ + 8106, /*ММВБ - ПАО Московская Биржа*/ + null, null, null, + 31, /*Отчет депозитария*/ + @CLISER_DATE, @CLISER_NUM, + @OPER_DATE, @OPER_DATE, @OPER_DATE, + 0, + @FACE, + null, + 'Перевод M2M (без поручения), GUID ' + cast(@GUID as varchar(36)) + ', референс ' + @REFERENCE + ); + end + + else + if @DIRECTION = 'OUT' begin + insert into U_OP_INV_SPIS + ( + DOC, + DD_TYPE, + DD_DATE, DD_NUM, + SHARE, AMOUNT, BLANKS, + PLACE, + D_NUM, + DEPO, + K_NUM, + MARKET, + OLD_CONDITIONS, IS_REG, TAX_R, + B_TYPE, + B_DATE, B_NUM, + WIR_DATE, NOTIFY_DATE, REG_DATE, + IS_BLOCK, + FROM_FACE, + REJECT_REASON, + COMMENT + ) + values + ( + @OPER_ID, + 1 /*Клиентское*/, + @DD_DATE, @DD_NUM, + @SHARE, @AMOUNT, null, + @PLACE, + 314, /*98015 - 0 - Ценные бумаги и цифровые активы на хранении в других депозитариях (НОСТРО депо расчетный)*/ + @CLIENT, + 836, /*98070 - 4 - Ценные бумаги и цифровые активы на торгах*/ + 8106, /*ММВБ - ПАО Московская Биржа*/ + null, null, null, + 31, /*Отчет депозитария*/ + @CLISER_DATE, @CLISER_NUM, + @OPER_DATE, @OPER_DATE, @OPER_DATE, + 0, + @FACE, + null, + 'Отчет НКО АО НРД [номер, дата]. Перевод M2M, GUID ' + cast(@GUID as varchar(36)) + ', референс ' + @REFERENCE + ); + end; + + --И сразу проводим + exec FTV_M2M_PROCESS_WIRING @OPER_ID; diff --git a/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_WIRING.sql b/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_WIRING.sql new file mode 100644 index 0000000..d53493b --- /dev/null +++ b/deploy/depo/fancy-m2m/FTV_M2M_PROCESS_WIRING.sql @@ -0,0 +1,172 @@ +CREATE OR ALTER procedure [dbo].[FTV_M2M_PROCESS_WIRING] (@OPER_ID int) + + as + set nocount on; + + /* + --Выполняет директиву "Обраотать" для документа "Зачисление" или "Списание" + --Все значения переменных берем только из самого документа, М2М-поручения и трансфертные таблицы тут не используются + + exec FTV_M2M_PROCESS_WIRING 75069916; + */ + + if user = 'dbo' execute as user = 'SYSDBA'; + declare @USER int = 84; + + declare + @DIRECTION varchar(3), + @D_CAT int, + @SHARE int, + @PLACE int, + @DEPO int, + @AMOUNT numeric(22,7), + @COMMENT varchar(255), + @B_TYPE int, + @B_NUM varchar(255), + @B_DATE datetime, + @WIR_DATE datetime, + @D_NUM int, + @K_NUM int, + @MARKET int, + @NAME varchar(255), + @TEXT_B_TYPE varchar(255), + @WIR_ID int; + + declare + @D_REG_1 int, @K_REG_1 int, + @D_REG_2 int, @K_REG_2 int, + @D_REG_3 int, @K_REG_3 int, + @D_REG_4 int, @K_REG_4 int, + @D_REG_5 int, @K_REG_5 int, + @D_REG_6 int, @K_REG_6 int; + + declare + @DIR_ID int, + @OUT_STATE int, + @STEP_ID int; + + + --НАПРАВЛЕНИЕ + if exists (select top 1 DOC from U_OP_INV_ZACH where DOC = @OPER_ID) set @DIRECTION = 'IN'; + else + if exists(select top 1 DOC from U_OP_INV_SPIS where DOC = @OPER_ID) set @DIRECTION = 'OUT'; + else begin + print 'Не найден документ ID = ' + trim(str(@OPER_ID)); + return; --TODO исключительная ситуация + end; + + + --ПРОВЕРКИ, ЧТО ДОКУМЕНТ МОЖНО ПРОВОДИТЬ + --Состояние документа + --Связь с M2M + --TODO + + + --ЗНАЧЕНИЯ ПЕРЕМЕННЫХ + if @DIRECTION = 'IN' begin + --Смотрим шапку операции + select + @D_CAT = D.D_CAT, + @D_NUM = H.D_NUM, + @K_NUM = H.K_NUM, + @B_TYPE = H.B_TYPE, + @B_NUM = H.B_NUM, + @B_DATE = H.B_DATE, + @SHARE = H.SHARE, + @AMOUNT = H.AMOUNT, + @PLACE = H.PLACE, + @DEPO = H.DEPO, + @MARKET = H.MARKET, + @WIR_DATE = H.WIR_DATE, + @NAME = 'Зачисление' + from OD_DOCS D + left join U_OP_INV_ZACH H ON H.DOC = D.ID + where D.ID = @OPER_ID; + + --D_REG + select + @D_REG_1 = C.SELF_ID, + @D_REG_2 = C.FACE, + @D_REG_3 = 0, + @D_REG_4 = 0, + @D_REG_5 = 0, + @D_REG_6 = @PLACE + from OD_CLIENTS C where C.ID = @PLACE; + + --K_REG + select + @K_REG_1 = C.SELF_ID, + @K_REG_2 = @MARKET, + @K_REG_3 = 0, + @K_REG_4 = @D_REG_1, + @K_REG_5 = @PLACE, + @K_REG_6 = @DEPO + from OD_CLIENTS C where C.ID = @DEPO; + end + else if @DIRECTION = 'OUT' begin + --Смотрим шапку операции + select + @D_CAT = D.D_CAT, + @D_NUM = H.K_NUM, --реверс + @K_NUM = H.D_NUM, --реверс + @B_TYPE = H.B_TYPE, + @B_NUM = H.B_NUM, + @B_DATE = H.B_DATE, + @SHARE = H.SHARE, + @AMOUNT = H.AMOUNT, + @PLACE = H.PLACE, + @DEPO = H.DEPO, + @MARKET = H.MARKET, + @WIR_DATE = H.WIR_DATE, + @NAME = 'Списание' + from OD_DOCS D + left join U_OP_INV_SPIS H ON H.DOC = D.ID + where D.ID = @OPER_ID; + + --K_REG + select + @K_REG_1 = C.SELF_ID, + @K_REG_2 = C.FACE, + @K_REG_3 = 0, + @K_REG_4 = 0, + @K_REG_5 = 0, + @K_REG_6 = @PLACE + from OD_CLIENTS C where C.ID = @PLACE; + + --D_REG + select + @D_REG_1 = C.SELF_ID, + @D_REG_2 = @MARKET, + @D_REG_3 = 0, + @D_REG_4 = @K_REG_1, + @D_REG_5 = @PLACE, + @D_REG_6 = @DEPO + from OD_CLIENTS C where C.ID = @DEPO; + end; + + + --ДОПОЛНИТЕЛЬНЫЕ ПАРАМЕТРЫ + select @TEXT_B_TYPE = NAME from OD_SYS_TABS where CODE = 'DEPO96_DCLA' and NUM = @B_TYPE; + set @COMMENT = FORMATMESSAGE('%s; %s N%s от %s', @COMMENT, @TEXT_B_TYPE, @B_NUM, convert(varchar, @B_DATE, 104)); + + + --ДИРЕКТИВА + select @DIR_ID = ID, @OUT_STATE = OUT_STATE from OD_STEP_TYPES where SYS_NAME = 'PROCESS' and D_CAT = @D_CAT; + exec GEN_ID 'OD_STEPS_ID_GEN', 1, @STEP_ID out; + insert into OD_STEPS (ID, DOC, S_TYPE, S_DATE, ERR_CODE, OUT_STATE, OPER) values (@STEP_ID, @OPER_ID, @DIR_ID, GETDATE(), 0, @OUT_STATE, @USER); + --update OD_STEPS set OPER = @USER where ID = @STEP_ID; может и не пригодится + + + --ПРОВОДКА + exec OD_PR_INS_WIRS + null, 4, @WIR_DATE, @NAME, @COMMENT, @AMOUNT, @AMOUNT, null, 0, 0, null, @STEP_ID, null, @USER, 'F', + @D_NUM, @SHARE, @D_REG_1, @D_REG_2, @D_REG_3, @D_REG_4, @D_REG_5, @D_REG_6, --дебет + @K_NUM, @SHARE, @K_REG_1, @K_REG_2, @K_REG_3, @K_REG_4, @K_REG_5, @K_REG_6, --кредит + @WIR_ID out, null, null, null; --return + + + --СОСТОЯНИЕ ДОКУМЕНТА + update OD_DOCS set STATE = @OUT_STATE where ID = @OPER_ID; + + --select @WIR_ID; + --select * from OD_WIRING where ID = @WIR_ID; diff --git a/deploy/depo/fancy-m2m/README.md b/deploy/depo/fancy-m2m/README.md new file mode 100644 index 0000000..28bdae0 --- /dev/null +++ b/deploy/depo/fancy-m2m/README.md @@ -0,0 +1,37 @@ +# Расширение Fancy DEPO для M2M (bj2depo) — процедуры Фонтвьель + +Код написан программистом Фонтвьель (В. Кокин) как собственное дополнение к Fancy DEPO — код Fancy не меняется, +это штатная для Fancy практика. Сняты из базы SQL002/BJ_DEPO_DATA 23.09.2026 (`sys.sql_modules`), тестовые вставки удалены. + +## Что делает +- `FTV_M2M_JOB` — точка входа джобы SQL Agent (bj ставит её как `bj2depo`, раз в N секунд; режим «bj» — bj вызывает сам): + берёт `FTV_M2M` со `STATE='TODO'` → `FTV_M2M_PROCESS_CREATE`; затем `FTV_M2M_EVENTS` со `STATE='TODO'` → `FTV_M2M_PROCESS_EVENTS`; + результат — `STATE` (SUCCESS/ERROR), `TimeBeg/TimeEnd`, `COMMENT` (текст ошибки // место // код). +- `FTV_M2M_PROCESS_CREATE @ID` — создаёт M2M-поручение DEPO (`OD_DOCS`, `OD_DOLS`, `D_CLISER_M2M`, `D_CLISER_M2M_DOL`, `OD_IN_DOCS/OD_OUT_DOCS`, `D_FILER`), + ищет клиента по паспорту (`OD_FACES`, `OD_CLIENTS`), наш счёт в НРД по `LINK_NUMBER`/`PLACE_NUMBER` (`OD_CLIENTS_REC`), пишет номера в `FTV_M2M.DOC`, `FTV_M2M_LINES.DOL`. +- `FTV_M2M_PROCESS_EVENTS @ID` — событие по строке (GUID+REFERENCE): решения, поручения, исполнение → операции зачисления/списания + (`U_OP_INV_ZACH`, `U_OP_INV_SPIS`) через `FTV_M2M_PROCESS_OPERATION` и проводки через `FTV_M2M_PROCESS_WIRING` (`OD_PR_INS_WIRS`, `OD_STEPS`). +- `FTV_M2M_PROCESS_OPERATION`, `FTV_M2M_PROCESS_WIRING` — служебные. + +## От чего зависит на стороне Fancy (должно быть в DEPO клиента) +Таблицы поставки Fancy с поддержкой M2M (у Фонтвьель появились 25.08.2026): `D_CLISER_M2M`, `D_CLISER_M2M_DOL`, `OD_M2M_ACCS`, +`OD_ONLINE_M2M_DEPO` (+ триггер `OD_ONLINE_M2M_DEPO_II`); функции ядра `FU_NEXT_DOC_NUM`, `GEN_ID`, процедура `OD_PR_INS_WIRS`; +типы документов/шагов `OD_DOC_CATS`, `OD_STEP_TYPES`, `OD_SYS_TABS`. Наши трансферные таблицы `FTV_M2M*` создаёт bj («Трансферные таблицы DEPO»). + +## Что привязано к базе Фонтвьель (параметризовать перед установкой у другого клиента) +| Где | Значение | Смысл | Как получить в чужой базе | +|---|---|---|---| +| CREATE/EVENTS/OPERATION | `@WHOS int = 383118` | ID лица депозитария (ООО «ИК Фонтвьель») — «кто» в документах | `SELECT FACE FROM OD_FACE_CODERS WHERE CODE = <код участника НРД>` (bj знает код: «Определить из DEPO») | +| EVENTS/OPERATION | `8106` | место/рынок «ММВБ — ПАО Московская Биржа» в операции | справочник мест DEPO клиента | +| EVENTS/OPERATION | `836` | тип счёта/валюта (комментарий «НОСТРО депо расчётный») | справочник DEPO клиента | +| EVENTS/OPERATION | `31` | тип документа «Отчёт депозитария» | `OD_DOC_CATS` клиента | +| все | `execute as user = 'SYSDBA'` | контекст выполнения | учётка SYSDBA есть в любой Fancy | +Предложение автору: вынести эти значения в таблицу `FTV_M2M_SETTINGS` (или вычислять по коду участника) — тогда набор ставится у любого клиента без правок. + +## Порядок установки у клиента +1. bj → «Обмен Fancy → Подключение» (логин bj_user: datareader+datawriter на FTV_M2M*). +2. bj → «Трансферные таблицы DEPO»: создать (логином с правами) или скрипт DBA. +3. Этот каталог: выполнить 5 файлов в порядке WIRING → OPERATION → CREATE → EVENTS → JOB (CREATE OR ALTER, повторно безопасно). + Права: `GRANT EXECUTE ON dbo.FTV_M2M_JOB TO bj_user` (для режима «bj»). +4. bj → «Обработка DEPO»: режим `sql-auto` (bj создаёт джобу `bj2depo`: `EXEC dbo.FTV_M2M_JOB`, интервал по настройке) или `sql-manual`. +5. bj → «Проверить готовность»: событие `BJ_PING` должно получить STATE от джобы.