Входящие M2M: пакет только целиком и отказ M2M08 для бумаг «для квалифицированных инвесторов»
Сверка с DEPO: у бумаги проверяется опция RESTRICTED («Ограничение ценной бумаги по обращению», 1 — предназначена для квалифицированных инвесторов). Если инвестор не квалифицирован — отказ M2M08; квалифицирован по всем видам — проходит; по части видов — предупреждение, решает оператор. Пакет принимается только целиком (решение 16.09): при отказе хотя бы по одной бумаге рекомендация — отказ по всем с кодом первой отказной, и так же нормализуется отправляемое решение (packageDecisions); в форме выбор отказа по одной бумаге переключает на отказ все. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -84,6 +84,7 @@ type depoParty struct {
|
|||||||
iisDet string
|
iisDet string
|
||||||
qualOK bool
|
qualOK bool
|
||||||
qualWarn bool
|
qualWarn bool
|
||||||
|
qualAll bool // квалифицирован по всем видам бумаг
|
||||||
qualDet string
|
qualDet string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +151,7 @@ func depoResolveParty(ctx context.Context, db *sql.DB, fs FancyMSSQLSettings, se
|
|||||||
p.qualWarn, p.qualDet = true, "инвестор не найден — квал не проверялся"
|
p.qualWarn, p.qualDet = true, "инвестор не найден — квал не проверялся"
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
p.qualWarn, p.qualDet = depoFaceQualified(ctx, db, p.faceID)
|
p.qualWarn, p.qualAll, p.qualDet = depoFaceQualified(ctx, db, p.faceID)
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,8 +170,8 @@ var depoQualKinds = []string{
|
|||||||
|
|
||||||
// depoFaceQualified — признак квалифицированного инвестора: действующая на сегодня опция лица
|
// depoFaceQualified — признак квалифицированного инвестора: действующая на сегодня опция лица
|
||||||
// IS_QUAL («Вхождение в реестр лиц, признанных квалифицированными инвесторами»). Значение —
|
// IS_QUAL («Вхождение в реестр лиц, признанных квалифицированными инвесторами»). Значение —
|
||||||
// маска видов бумаг (511 и -1 — все виды, 0 — ни одного). Возвращает (предупреждение, пояснение).
|
// маска видов бумаг (511 и -1 — все виды, 0 — ни одного). Возвращает (предупреждение, по всем видам, пояснение).
|
||||||
func depoFaceQualified(ctx context.Context, db *sql.DB, faceID int64) (bool, string) {
|
func depoFaceQualified(ctx context.Context, db *sql.DB, faceID int64) (warn, all bool, det string) {
|
||||||
var val sql.NullString
|
var val sql.NullString
|
||||||
err := db.QueryRowContext(ctx, `SELECT TOP 1 CAST(o.VAL AS varchar(20)) FROM OD_OPTIONS o
|
err := db.QueryRowContext(ctx, `SELECT TOP 1 CAST(o.VAL AS varchar(20)) FROM OD_OPTIONS o
|
||||||
JOIN OP_FIELDS f ON f.ID = o.DESCR AND f.AS_NAME = 'IS_QUAL'
|
JOIN OP_FIELDS f ON f.ID = o.DESCR AND f.AS_NAME = 'IS_QUAL'
|
||||||
@@ -178,21 +179,20 @@ func depoFaceQualified(ctx context.Context, db *sql.DB, faceID int64) (bool, str
|
|||||||
ORDER BY o.B_DATE DESC`, sql.Named("p1", faceID)).Scan(&val)
|
ORDER BY o.B_DATE DESC`, sql.Named("p1", faceID)).Scan(&val)
|
||||||
const notQual = " — если бумага только для квал., нужен отказ M2M08; решает оператор"
|
const notQual = " — если бумага только для квал., нужен отказ M2M08; решает оператор"
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
return true, "инвестор НЕ квалифицированный (нет опции «Вхождение в реестр квалифицированных инвесторов» в DEPO)" + notQual
|
return true, false, "инвестор НЕ квалифицированный (нет опции «Вхождение в реестр квалифицированных инвесторов» в DEPO)" + notQual
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, "признак квалифицированного инвестора не прочитан: " + quikErrorDetail(err)
|
return true, false, "признак квалифицированного инвестора не прочитан: " + quikErrorDetail(err)
|
||||||
}
|
}
|
||||||
mask, e := strconv.ParseInt(strings.TrimSpace(val.String), 10, 64)
|
mask, e := strconv.ParseInt(strings.TrimSpace(val.String), 10, 64)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return true, fmt.Sprintf("признак квалифицированного инвестора в DEPO не распознан (%q) — решает оператор", val.String)
|
return true, false, fmt.Sprintf("признак квалифицированного инвестора в DEPO не распознан (%q) — решает оператор", val.String)
|
||||||
}
|
}
|
||||||
if mask == 0 {
|
if mask == 0 {
|
||||||
return true, "инвестор НЕ квалифицированный (опция в DEPO = 0)" + notQual
|
return true, false, "инвестор НЕ квалифицированный (опция в DEPO = 0)" + notQual
|
||||||
}
|
}
|
||||||
all := mask < 0 || mask&511 == 511
|
if mask < 0 || mask&511 == 511 {
|
||||||
if all {
|
return false, true, "инвестор — квалифицированный по всем видам бумаг (опция DEPO)"
|
||||||
return false, "инвестор — квалифицированный по всем видам бумаг (опция DEPO)"
|
|
||||||
}
|
}
|
||||||
var kinds []string
|
var kinds []string
|
||||||
for bit, name := range depoQualKinds {
|
for bit, name := range depoQualKinds {
|
||||||
@@ -200,7 +200,7 @@ func depoFaceQualified(ctx context.Context, db *sql.DB, faceID int64) (bool, str
|
|||||||
kinds = append(kinds, name)
|
kinds = append(kinds, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, "инвестор — квалифицированный (опция DEPO): " + strings.Join(kinds, "; ")
|
return false, false, "инвестор — квалифицированный (опция DEPO): " + strings.Join(kinds, "; ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// depoAccountIIS — относится ли торговый счёт (OD_CLIENTS.ID) к договору ИИС: название договора
|
// depoAccountIIS — относится ли торговый счёт (OD_CLIENTS.ID) к договору ИИС: название договора
|
||||||
@@ -248,6 +248,46 @@ func depoSecurity(ctx context.Context, db *sql.DB, isin string) (id int64, name
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// depoSecurityQualOnly — бумага «Предназначена для квалифицированных инвесторов»: действующая на сегодня
|
||||||
|
// опция бумаги RESTRICTED («Ограничение ценной бумаги по обращению», 1 — для квал. инвесторов).
|
||||||
|
func depoSecurityQualOnly(ctx context.Context, db *sql.DB, valID int64) (bool, string) {
|
||||||
|
var val sql.NullString
|
||||||
|
err := db.QueryRowContext(ctx, `SELECT TOP 1 CAST(o.VAL AS varchar(20)) FROM OD_OPTIONS o
|
||||||
|
JOIN OP_FIELDS f ON f.ID = o.DESCR AND f.AS_NAME = 'RESTRICTED'
|
||||||
|
WHERE o.OBJECT = @p1 AND o.B_DATE <= GETDATE() AND (o.E_DATE IS NULL OR o.E_DATE > GETDATE())
|
||||||
|
ORDER BY o.B_DATE DESC`, sql.Named("p1", valID)).Scan(&val)
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, sql.ErrNoRows):
|
||||||
|
return false, "ограничений по обращению нет (опция DEPO)"
|
||||||
|
case err != nil:
|
||||||
|
return false, "ограничение бумаги по обращению не прочитано: " + quikErrorDetail(err)
|
||||||
|
case strings.TrimSpace(val.String) == "1":
|
||||||
|
return true, "бумага предназначена для квалифицированных инвесторов (опция DEPO)"
|
||||||
|
}
|
||||||
|
return false, "ограничений по обращению нет (опция DEPO)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// qualOnlyCheck — бумага только для квал. инвесторов против квалификации инвестора.
|
||||||
|
func qualOnlyCheck(found, qualOnly bool, qdet string, p depoParty) depoCheck {
|
||||||
|
c := depoCheck{Name: "Бумага только для квал. инвесторов", OK: true, Code: "M2M08", Detail: qdet}
|
||||||
|
switch {
|
||||||
|
case !found:
|
||||||
|
c.Detail = "не проверяется — бумаги нет в справочнике"
|
||||||
|
case !qualOnly:
|
||||||
|
case !p.faceOK:
|
||||||
|
c.Detail += " — инвестор не найден, квалификация не проверялась"
|
||||||
|
case p.qualAll:
|
||||||
|
c.Detail += " — инвестор квалифицированный по всем видам бумаг"
|
||||||
|
case p.qualWarn:
|
||||||
|
c.OK = false
|
||||||
|
c.Detail += " — инвестор НЕ квалифицированный: операция нарушит закон РФ"
|
||||||
|
default:
|
||||||
|
c.Warn = true
|
||||||
|
c.Detail += " — " + p.qualDet + "; вид бумаги сверяет оператор"
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
// depoBlocking — есть ли блокировка бумаги на счёте (BLOCK_DATE либо остаток на 98070.05/.07/.08/.09).
|
// depoBlocking — есть ли блокировка бумаги на счёте (BLOCK_DATE либо остаток на 98070.05/.07/.08/.09).
|
||||||
func depoBlocking(ctx context.Context, db *sql.DB, valID, clientID int64) (ok bool, det string) {
|
func depoBlocking(ctx context.Context, db *sql.DB, valID, clientID int64) (ok bool, det string) {
|
||||||
var blockDate sql.NullTime
|
var blockDate sql.NullTime
|
||||||
@@ -281,22 +321,35 @@ func finishSec(sr *depoSecResult) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// finishResult — итог по переводу. Пакет принимается только целиком (решение 16.09.2026): если хотя
|
||||||
|
// бы по одной бумаге отказ, отказ рекомендуется по всем бумагам с кодом первой отказной.
|
||||||
func finishResult(res *depoResult) {
|
func finishResult(res *depoResult) {
|
||||||
confirmN, rejectN := 0, 0
|
first := -1
|
||||||
for _, s := range res.Secs {
|
for i, s := range res.Secs {
|
||||||
if s.Recommend == "confirm" {
|
if s.Recommend != "confirm" {
|
||||||
confirmN++
|
first = i
|
||||||
} else {
|
break
|
||||||
rejectN++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case rejectN == 0 && confirmN > 0:
|
case len(res.Secs) == 0:
|
||||||
|
case first < 0:
|
||||||
res.Recommend, res.Summary = "confirm", "принять"
|
res.Recommend, res.Summary = "confirm", "принять"
|
||||||
case confirmN == 0 && rejectN > 0:
|
default:
|
||||||
res.Recommend, res.Summary = "reject", "отказ "+res.Secs[0].RejectCode
|
code, sec := res.Secs[first].RejectCode, res.Secs[first].Security
|
||||||
case confirmN > 0 && rejectN > 0:
|
passed := 0
|
||||||
res.Recommend, res.Summary = "mixed", fmt.Sprintf("принять %d, отказ %d", confirmN, rejectN)
|
for i := range res.Secs {
|
||||||
|
if res.Secs[i].Recommend == "confirm" {
|
||||||
|
passed++
|
||||||
|
res.Secs[i].Recommend, res.Secs[i].RejectCode = "reject", code
|
||||||
|
res.Secs[i].Checks = append(res.Secs[i].Checks, depoCheck{Name: "Пакет принимается только целиком", OK: false, Code: code,
|
||||||
|
Detail: "сама бумага проверку прошла; отказ всем пакетом из-за " + sec + " (" + code + ")"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.Recommend, res.Summary = "reject", "отказ "+code
|
||||||
|
if passed > 0 {
|
||||||
|
res.Summary = fmt.Sprintf("отказ пакетом %s (из-за %s)", code, sec)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,6 +405,11 @@ func runDepoCheck(it *IncomingTransfer) depoResult {
|
|||||||
Detail: pick3(found, vname+" (ID "+fmt.Sprint(valID)+")", "ISIN "+isin+" в справочнике OD_VALUES нет")})
|
Detail: pick3(found, vname+" (ID "+fmt.Sprint(valID)+")", "ISIN "+isin+" в справочнике OD_VALUES нет")})
|
||||||
sr.Checks = append(sr.Checks, depoCheck{Name: "Принята на обслуживание (галка)", OK: accepted, Code: "M2M05",
|
sr.Checks = append(sr.Checks, depoCheck{Name: "Принята на обслуживание (галка)", OK: accepted, Code: "M2M05",
|
||||||
Detail: pick3(!found, "не проверяется — бумаги нет в справочнике", pick3(accepted, "галка стоит (IS_IN=1)", "бумага есть, но галка НЕ стоит (IS_IN≠1)"))})
|
Detail: pick3(!found, "не проверяется — бумаги нет в справочнике", pick3(accepted, "галка стоит (IS_IN=1)", "бумага есть, но галка НЕ стоит (IS_IN≠1)"))})
|
||||||
|
qualOnly, qdet := false, ""
|
||||||
|
if found {
|
||||||
|
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)...)
|
||||||
blockOK, blockDet := true, "не проверяется — нет открытого счёта или бумаги"
|
blockOK, blockDet := true, "не проверяется — нет открытого счёта или бумаги"
|
||||||
if p.accOpen != "" && found {
|
if p.accOpen != "" && found {
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package lkgateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Пакет принимается только целиком: отказ по одной бумаге — отказ по всем с тем же кодом.
|
||||||
|
func TestFinishResultWholePackage(t *testing.T) {
|
||||||
|
res := depoResult{Secs: []depoSecResult{
|
||||||
|
{Security: "RU0009029540", Recommend: "confirm"},
|
||||||
|
{Security: "RU0007661625", Recommend: "reject", RejectCode: "M2M05"},
|
||||||
|
{Security: "RU000A0JUR61", Recommend: "confirm"},
|
||||||
|
}}
|
||||||
|
finishResult(&res)
|
||||||
|
if res.Recommend != "reject" || !strings.Contains(res.Summary, "отказ пакетом M2M05") {
|
||||||
|
t.Fatalf("итог: %s %q", res.Recommend, res.Summary)
|
||||||
|
}
|
||||||
|
for _, s := range res.Secs {
|
||||||
|
if s.Recommend != "reject" || s.RejectCode != "M2M05" {
|
||||||
|
t.Fatalf("бумага %s: %s %s", s.Security, s.Recommend, s.RejectCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last := res.Secs[0].Checks[len(res.Secs[0].Checks)-1]; last.OK || !strings.Contains(last.Detail, "RU0007661625") {
|
||||||
|
t.Fatalf("пояснение отказа пакетом: %+v", last)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := depoResult{Secs: []depoSecResult{{Recommend: "confirm"}, {Recommend: "confirm"}}}
|
||||||
|
finishResult(&ok)
|
||||||
|
if ok.Recommend != "confirm" {
|
||||||
|
t.Fatalf("все бумаги прошли: %s", ok.Recommend)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPackageDecisions(t *testing.T) {
|
||||||
|
in := []SecurityDecision{{ReferenceID: "A", Confirm: true}, {ReferenceID: "B", RejectCode: "M2M08"}, {ReferenceID: "C", Confirm: true}}
|
||||||
|
out := packageDecisions(in)
|
||||||
|
for _, d := range out {
|
||||||
|
if d.Confirm || d.RejectCode != "M2M08" {
|
||||||
|
t.Fatalf("частичное согласие не должно уходить: %+v", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !in[0].Confirm {
|
||||||
|
t.Fatal("исходный срез не должен меняться")
|
||||||
|
}
|
||||||
|
all := []SecurityDecision{{ReferenceID: "A", Confirm: true}, {ReferenceID: "B", Confirm: true}}
|
||||||
|
if got := packageDecisions(all); !got[0].Confirm || !got[1].Confirm {
|
||||||
|
t.Fatalf("согласие по всем: %+v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQualOnlyCheck(t *testing.T) {
|
||||||
|
notQual := depoParty{faceOK: true, qualWarn: true, qualDet: "инвестор НЕ квалифицированный"}
|
||||||
|
qualAll := depoParty{faceOK: true, qualAll: true}
|
||||||
|
partial := depoParty{faceOK: true, qualDet: "инвестор — квалифицированный (опция DEPO): облигации"}
|
||||||
|
const q = "бумага предназначена для квалифицированных инвесторов (опция DEPO)"
|
||||||
|
|
||||||
|
if c := qualOnlyCheck(true, true, q, notQual); c.OK || c.Code != "M2M08" {
|
||||||
|
t.Fatalf("неквал + бумага для квал: %+v", c)
|
||||||
|
}
|
||||||
|
if c := qualOnlyCheck(true, true, q, qualAll); !c.OK || c.Warn {
|
||||||
|
t.Fatalf("квал по всем видам: %+v", c)
|
||||||
|
}
|
||||||
|
if c := qualOnlyCheck(true, true, q, partial); !c.OK || !c.Warn {
|
||||||
|
t.Fatalf("квал по части видов — решает оператор: %+v", c)
|
||||||
|
}
|
||||||
|
if c := qualOnlyCheck(true, false, "ограничений по обращению нет (опция DEPO)", notQual); !c.OK || c.Warn {
|
||||||
|
t.Fatalf("бумага без ограничений: %+v", c)
|
||||||
|
}
|
||||||
|
if c := qualOnlyCheck(true, true, q, depoParty{}); !c.OK {
|
||||||
|
t.Fatalf("инвестор не найден — отказ даёт M2M04, не эта проверка: %+v", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ func TestDepoQualifiedLive(t *testing.T) {
|
|||||||
if rows.Scan(&v, &face) != nil {
|
if rows.Scan(&v, &face) != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
warn, det := depoFaceQualified(ctx, db, face)
|
warn, _, det := depoFaceQualified(ctx, db, face)
|
||||||
t.Logf("VAL=%s лицо %d: предупреждение=%v — %s", v, face, warn, det)
|
t.Logf("VAL=%s лицо %d: предупреждение=%v — %s", v, face, warn, det)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ func answerIncoming(ctx context.Context, deps incomingDeps, id string, decisions
|
|||||||
}
|
}
|
||||||
nsd := cfg.NSD
|
nsd := cfg.NSD
|
||||||
|
|
||||||
|
decisions = packageDecisions(decisions)
|
||||||
|
|
||||||
// Карта решений по ReferenceID.
|
// Карта решений по ReferenceID.
|
||||||
byRef := make(map[string]SecurityDecision, len(decisions))
|
byRef := make(map[string]SecurityDecision, len(decisions))
|
||||||
for _, d := range decisions {
|
for _, d := range decisions {
|
||||||
@@ -151,3 +153,34 @@ func answerIncoming(ctx context.Context, deps incomingDeps, id string, decisions
|
|||||||
func transferringINN(_ Settings) string {
|
func transferringINN(_ Settings) string {
|
||||||
return "7703807489"
|
return "7703807489"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// packageDecisions — пакет принимается только целиком (решение 16.09.2026): если хотя бы по одной бумаге
|
||||||
|
// отказ, отказываем по всем; принятым бумагам ставится код первой отказной.
|
||||||
|
func packageDecisions(decisions []SecurityDecision) []SecurityDecision {
|
||||||
|
code := ""
|
||||||
|
for _, d := range decisions {
|
||||||
|
if !d.Confirm {
|
||||||
|
code = d.RejectCode
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if code == "" {
|
||||||
|
for _, d := range decisions {
|
||||||
|
if !d.Confirm {
|
||||||
|
code = "M2M05"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if code == "" {
|
||||||
|
return decisions
|
||||||
|
}
|
||||||
|
out := make([]SecurityDecision, len(decisions))
|
||||||
|
for i, d := range decisions {
|
||||||
|
if d.Confirm {
|
||||||
|
d.Confirm, d.RejectCode = false, code
|
||||||
|
}
|
||||||
|
out[i] = d
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
@@ -138,6 +138,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<p class="muted" style="font-size:12.5px;margin:10px 0 0">Пакет принимается только целиком: при отказе по одной бумаге отказ ставится всем бумагам с тем же кодом (так и будет отправлено).</p>
|
||||||
<div style="display:flex;gap:8px;margin-top:14px;flex-wrap:wrap">
|
<div style="display:flex;gap:8px;margin-top:14px;flex-wrap:wrap">
|
||||||
<button type="submit" class="btn btn-ok">Отправить решение роботу</button>
|
<button type="submit" class="btn btn-ok">Отправить решение роботу</button>
|
||||||
<button type="button" class="btn" onclick="m2mSetAll('confirm')">Принять все</button>
|
<button type="button" class="btn" onclick="m2mSetAll('confirm')">Принять все</button>
|
||||||
@@ -155,7 +156,19 @@
|
|||||||
if(dash) dash.style.display = show ? 'none' : '';
|
if(dash) dash.style.display = show ? 'none' : '';
|
||||||
}
|
}
|
||||||
document.querySelectorAll('input.dec').forEach(function(r){
|
document.querySelectorAll('input.dec').forEach(function(r){
|
||||||
r.addEventListener('change', function(){ toggle(r.getAttribute('data-ref')); });
|
r.addEventListener('change', function(){
|
||||||
|
toggle(r.getAttribute('data-ref'));
|
||||||
|
// Пакет целиком: отказ по одной бумаге — отказ по всем с тем же кодом.
|
||||||
|
if(r.value === 'reject' && r.checked){
|
||||||
|
var src = document.querySelector('select[name="reject_'+r.getAttribute('data-ref')+'"]');
|
||||||
|
document.querySelectorAll('input.dec[value="reject"]').forEach(function(o){
|
||||||
|
if(o.checked) return;
|
||||||
|
o.checked = true; toggle(o.getAttribute('data-ref'));
|
||||||
|
var sel = document.querySelector('select[name="reject_'+o.getAttribute('data-ref')+'"]');
|
||||||
|
if(sel && src) sel.value = src.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
window.m2mSetAll = function(val){
|
window.m2mSetAll = function(val){
|
||||||
document.querySelectorAll('input.dec[value="'+val+'"]').forEach(function(r){
|
document.querySelectorAll('input.dec[value="'+val+'"]').forEach(function(r){
|
||||||
|
|||||||
Reference in New Issue
Block a user