# PR-1: Go-модели M2M + парсер windows-1251 + round-trip тесты

## Цель

Реализовать типизированную доменную модель сообщений M2M по XSD НРД,
парсер/сериализатор XML в windows-1251 и round-trip тесты на эталонах.
Это первый осмысленный код в проекте, на нём основываются все
последующие модули (`nsd-adapter`, `m2m-core`, `lk-gateway`,
`lk-emulator`).

## Источники правды

- `DOC/M2MSchemas_260408/*.xsd` — все типы и структуры, namespace
  `http://nsd.ru/schemas/m2m/...`, version `2026-04-08`.
- `DOC/Примеры/*.xml` — примеры всех типов сообщений (windows-1251).
- `DOC/Эталонные сообщения/*.xml` — эталоны для приёмочной проверки.

## Состав PR

### 1. `internal/m2m/types.go`

Все simple-типы и enum'ы из XSD как Go-типы.

**Enum'ы (со списком допустимых значений):**

- `StatusCode` — `INFO | ERROR`.
- `IIAContractType` — **`T12 | T03`** (именно так — T12 = обмен ИИС-1/ИИС-2,
  T03 = ИИС-3; не T01/T02/T03).
- `SecurityClassification` — `BOND | SHAR | MFUN`.
- `SecurityCategory` — `ORDN | PREF | UKWN`.
- `IdentityDocumentCode` — `01 | 02 | 03 | 04 | 05 | 06 | 07 | 09 | 10 | 11 | 12 | 13 | 14 | 21 | 22 | 23 | 26 | 27 | 91`.
- `IsolationStatus` — только `SGDN`.

**Simple-типы как Go-обёртки над `string`:**

- `DeponentCode`, `ReferenceId`, `ISIN`, `OrganizationINN`, `UUID`,
  `AccountId`, `SecurityCode`, `IdentityDocSerial`.

### 2. `internal/m2m/validators.go`

Метод `Validate() error` на каждом типе из пункта 1, проверяющий pattern
и длину из XSD:

| Тип | Pattern | Длина |
|-----|---------|-------|
| `ReferenceId` | `^M2M[A-Z0-9]{13}$` | ровно 16 |
| `ISIN` | `^[A-Z]{2}[A-Z0-9]{9}[0-9]$` | ровно 12 |
| `OrganizationINN` | `^[0-9]{10}$` | ровно 10 |
| `DeponentCode` | `^[A-Z0-9]+$` | до 12 |
| `UUID` | стандартный UUID v1-5 pattern | 36 |
| `SecurityCode` | `^[0-9A-Z_/-]+$` | ровно 12 |
| `IdentityDocSerial` | `^\S+$` | от 1 |

Enum'ы валидируются проверкой принадлежности к допустимому множеству.

### 3. `internal/m2m/messages.go`

Структуры всех 6 типов сообщений M2M:

- `M2MTransferRequest` — Header (UUID, NSDDateTime, SenderCode,
  ReceiverCode, CostInfo, опц. IIAAgreementDetails) + Data (IsM2M=true,
  InvestorInformation, TransferringDepository, ReceivingDepository,
  TransferredSecurities) + опц. NSDInfo.
- `M2MTransferDecision` — Header (тот же набор минус IIAAgreementDetails)
  + Data (ReceivingDepository, Security[] с TransferDecision Choice).
- `M2MTransferResponse` — GUID + StatusCode + Response[]
  (ReferenceId, Code, Text).
- `M2MTransferHandbook` + `M2MTransferHandbookRequest`.
- `M2MTransferParticipantForm`.

**Choice-типы.** Реализуй через указатели на взаимоисключающие поля:

- `CostInfo { Yes *Yes; No *No }` — ровно один не nil.
- `Quantity { Whole *uint64; Fractional *Decimal16 }`.
- `SecurityDetails { ISIN *ISIN; SecurityInfo *SecurityDescription }`.
- `IdentificationDetails { RegNumber *string; FundShares *FundShares }`.
- `DecisionTransfer { Confirmation *Confirmation; Rejection *Rejection }`.

В `Validate()` каждого choice — проверка «ровно одно поле задано».

**Фиксированные значения.** `IsM2M` всегда `true` — захардкодь
в `MarshalXML` (не выноси в поле, заполняй автоматически).

**XML-теги.** Для каждого поля укажи `xml:"..."` с правильным namespace
из XSD. Используй namespace-aliases:

- `rt` для `http://nsd.ru/schemas/m2m/request`,
- `dn` для `http://nsd.ru/schemas/m2m/decision`,
- `hk` для `http://nsd.ru/schemas/m2m/handbook`,
- `hr` для `http://nsd.ru/schemas/m2m/handbook/request`,
- `pf` для `http://nsd.ru/schemas/m2m/participant/form`,
- `m2m` для `http://nsd.ru/schemas/m2m/types`.

### 4. `internal/nsdxml/datetime.go`

Тип `NSDDateTime` для нестандартного формата НРД.

- Формат: `YYYY-MM-DDThh:mm:ss(МСК[+-N])`, пример: `2026-03-02T14:30:45(МСК+2)`.
- Regex из XSD:
  `^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\(МСК([+-][0-9]{1,2})?\)$`.
- Внутреннее представление — `time.Time` в location `Europe/Moscow`
  плюс смещение (если есть).
- Реализуй: `MarshalXML`, `UnmarshalXML`, `MarshalText`, `UnmarshalText`,
  `String() string`, `Now() NSDDateTime`.
- Кейсы для тестов: `(МСК)`, `(МСК+2)`, `(МСК-1)`, `(МСК+12)`.

### 5. `internal/nsdxml/codec.go`

Marshal/Unmarshal XML в windows-1251.

- Зависимость: `golang.org/x/text/encoding/charmap`.
- `Marshal(v any) ([]byte, error)`:
  1. Сериализуй в utf-8 через `encoding/xml`.
  2. Прогони через `charmap.Windows1251.NewEncoder()`.
  3. Замени первую строку на
     `<?xml version="1.0" encoding="windows-1251"?>`.
- `Unmarshal(data []byte, v any) error`:
  1. Создай `xml.Decoder` с `CharsetReader`, в котором при
     `charset == "windows-1251"` оборачиваем reader в
     `charmap.Windows1251.NewDecoder().Reader(...)`.
  2. Декодируй в `v`.
- Проверь, что кириллица в эталонах (ФИО инвестора, наименования
  организаций) читается и пишется корректно.

### 6. `internal/m2m/messages_test.go`

Round-trip тесты на всех эталонах.

- Таблица «файл → ожидаемый тип сообщения» для всех XML в
  `DOC/Примеры/` и `DOC/Эталонные сообщения/`.
- Для каждого файла:
  1. `Unmarshal` → структура `S1`.
  2. `S1.Validate()` — без ошибок.
  3. `Marshal(S1)` → байты `B2`.
  4. `Unmarshal(B2)` → структура `S2`.
  5. `reflect.DeepEqual(S1, S2)` — true.
- Юнит-тесты валидаторов на негативных кейсах (короткий ИНН, неверный
  префикс ReferenceId, неизвестное значение enum).
- Юнит-тесты `NSDDateTime` на всех вариантах зоны.

## Требования к коду

- **Без эмодзи** в коде и комментариях.
- Комментарии в коде — на русском.
- Имена типов и полей — на английском, как в XSD (`SettlementAccount`,
  `IsolationStatus`, `IIAAgreementDetails` и т. п.).
- `go mod tidy` после добавления зависимостей (понадобится
  `golang.org/x/text/encoding/charmap`).
- `make ci` (`tidy + fmt + vet + lint + test + build`) — зелёный.
- Покрытие тестами `internal/m2m/` и `internal/nsdxml/` — не менее 70%.

## Коммит

Один commit, сообщение:

```
feat(m2m): доменная модель сообщений + парсер windows-1251 + round-trip тесты

- internal/m2m/types.go: enum'ы и simple-типы из XSD НРД (M2MSchemas_260408)
- internal/m2m/validators.go: pattern-валидаторы ReferenceId/ISIN/INN/...
- internal/m2m/messages.go: структуры 6 типов сообщений M2M
- internal/nsdxml/datetime.go: тип NSDDateTime (формат "YYYY-MM-DDThh:mm:ss(МСК+N)")
- internal/nsdxml/codec.go: Marshal/Unmarshal XML в windows-1251
- internal/m2m/messages_test.go: round-trip тесты на всех эталонах из DOC/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
```

Push в main (история линейная, ревью через diff в Gitea постфактум).

## Проверка перед коммитом

```bash
make ci
```

Если зелёное — push. Если красное — фиксируй ошибки до зелёного,
не коммить промежуточные правки.

## После коммита

1. Обнови `docs/tasks/README.md`: PR-1 — «выполнено», sha коммита.
2. Обнови `internal/m2m/README.md` и `internal/nsdxml/README.md`,
   убери пометку «реализация — задача M1» и добавь короткое описание
   что есть.
