M2M ИИС: приём «всё или ничего» (настройка AcceptIIS, по умолчанию выкл)
Правильная обработка входящих переводов на ИИС вместо прежнего сплошного отказа.
Настройка AcceptIIS (карточка «Кто отвечает на переводы M2M»):
- выкл (по умолчанию) — поведение не меняется: перевод на ИИС отклоняется целиком (M2M06);
- вкл — ИИС принимается ТОЛЬКО целиком: все бумаги зачисляются на ИИС-счёт клиента;
если хоть одной бумаги нет в учёте / не проходит сверку — отказ по ВСЕМУ переводу.
Изменения:
- iisDecisions: «всё или ничего» (при выкл — отказ всем; при вкл — либо все принять,
либо все отклонить, сохраняя реальные коды отказа по проблемным бумагам);
- depo_check: при вкл приёме ИИС убран искусственный отказ «Перевод не на ИИС»,
добавлена проверка «Счёт зачисления — ИИС» (partyChecks получил флаг wantIIS);
- настройка AcceptIIS в Settings + геттер/сеттер + обработчик /admin/setup/accept-iis
+ тумблер в part_nsd.html; тесты обновлены.
Жёсткий запрет частичного ИИС в answerIncoming (коммит dc8b785) остаётся как страховка.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -368,8 +368,18 @@ func finishResult(res *depoResult) {
|
||||
}
|
||||
|
||||
// partyChecks — общие проверки инвестора/счёта/ИИС/квал (одинаковы для обеих сверок).
|
||||
func partyChecks(p depoParty, ser, num, fio string) []depoCheck {
|
||||
func partyChecks(p depoParty, ser, num, fio string, wantIIS bool) []depoCheck {
|
||||
ser = strings.ReplaceAll(strings.TrimSpace(ser), " ", "")
|
||||
// Счёт зачисления: для обычного перевода должен быть НЕ ИИС; для перевода на ИИС —
|
||||
// наоборот, обязан быть ИИС-счётом клиента (иначе ИИС-бумаги зачислять некуда).
|
||||
iisChk := depoCheck{Name: "Счёт не ИИС", OK: p.iisOK, Code: iisRejectCode, Detail: p.iisDet}
|
||||
if wantIIS {
|
||||
det := p.iisDet
|
||||
if p.iisOK {
|
||||
det = "перевод на ИИС, но счёт зачисления клиента НЕ ИИС-счёт — зачислять ИИС-бумаги некуда (" + p.iisDet + ")"
|
||||
}
|
||||
iisChk = depoCheck{Name: "Счёт зачисления — ИИС", OK: !p.iisOK, Code: iisRejectCode, Detail: det}
|
||||
}
|
||||
return []depoCheck{
|
||||
// Инвестор — наш клиент: лицо по паспорту с тем же ФИО И хотя бы один торговый счёт (договор).
|
||||
// Физлицо в картотеке без счёта у нас — не клиент депозитария: отказ M2M04, а не M2M02 (16.09.2026).
|
||||
@@ -382,7 +392,7 @@ func partyChecks(p depoParty, ser, num, fio string) []depoCheck {
|
||||
Detail: pick3(p.accAny != "", p.accAny, pick3(p.faceOK, "у инвестора нет счёта "+depoAccPrefix()+"… в OD_CLIENTS", "не проверяется — инвестор не найден"))},
|
||||
{Name: "Статус счёта строго «открыт»", OK: p.accOpen != "", Code: "M2M02",
|
||||
Detail: pick3(p.accAny == "", "не проверяется — счёта нет", pick3(p.accOpen != "", p.accOpen+" — открыт (STATE=0, без даты закрытия)", p.accAny+" — НЕ открыт (закрыт или STATE≠0)"))},
|
||||
{Name: "Счёт не ИИС", OK: p.iisOK, Code: iisRejectCode, Detail: p.iisDet},
|
||||
iisChk,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,9 +424,9 @@ func runDepoCheck(it *IncomingTransfer) depoResult {
|
||||
for _, s := range it.Securities {
|
||||
isin := strings.ToUpper(strings.TrimSpace(s.ISIN))
|
||||
sr := depoSecResult{ReferenceID: s.ReferenceID, Security: pick(isin, s.SecurityCode)}
|
||||
if incomingIsIIS(it) { // решение 25.09.2026: входящие переводы на ИИС не принимаем совсем
|
||||
if incomingIsIIS(it) && !m2mAcceptIIS() { // приём ИИС выключен — отклоняем перевод целиком
|
||||
sr.Checks = append(sr.Checks, depoCheck{Name: "Перевод не на ИИС", OK: false, Code: iisRejectCode,
|
||||
Detail: "перевод на ИИС (в запросе договор ИИС) — входящие переводы на ИИС не принимаем, отказ по всем бумагам"})
|
||||
Detail: "перевод на ИИС (в запросе договор ИИС) — приём ИИС выключен, отказ по всем бумагам"})
|
||||
}
|
||||
ruOK := strings.HasPrefix(isin, "RU")
|
||||
sr.Checks = append(sr.Checks, depoCheck{Name: "Российский выпуск (RU-ISIN)", OK: ruOK, Code: "M2M01",
|
||||
@@ -431,7 +441,7 @@ func runDepoCheck(it *IncomingTransfer) depoResult {
|
||||
qualOnly, qdet = depoSecurityQualOnly(ctx, db, valID)
|
||||
}
|
||||
sr.Checks = append(sr.Checks, qualOnlyCheck(found, qualOnly, qdet, p))
|
||||
sr.Checks = append(sr.Checks, partyChecks(p, it.DocSeries, it.DocNumber, fio)...)
|
||||
sr.Checks = append(sr.Checks, partyChecks(p, it.DocSeries, it.DocNumber, fio, incomingIsIIS(it) && m2mAcceptIIS())...)
|
||||
blockOK, blockDet := true, "не проверяется — нет открытого счёта или бумаги"
|
||||
if p.accOpen != "" && found {
|
||||
blockOK, blockDet = depoBlocking(ctx, db, valID, p.clientID)
|
||||
@@ -556,7 +566,7 @@ func runDepoCheckOutAccount(last, first, middle, ser, num, birth string, secs []
|
||||
qualOnly, qdet = depoSecurityQualOnly(ctx, db, valID)
|
||||
}
|
||||
sr.Checks = append(sr.Checks, outQualCheck(found, qualOnly, qdet, p))
|
||||
sr.Checks = append(sr.Checks, partyChecks(p, ser, num, fio)...)
|
||||
sr.Checks = append(sr.Checks, partyChecks(p, ser, num, fio, false)...)
|
||||
if birthCheck != nil {
|
||||
sr.Checks = append(sr.Checks, *birthCheck)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user