Добавлен заказ накладной
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
---
|
||||
id: 032
|
||||
title: Rework init-payment contract to camelCase and structured address/contact
|
||||
status: TODO
|
||||
created: 2026-05-13
|
||||
---
|
||||
|
||||
## Context
|
||||
Фронт переходит на новый контракт ручки `POST /api/v1/delivery/order`. В нём
|
||||
адрес/контакт структурированы по-новому, появились флаг юр-лица и реквизиты
|
||||
(`isCompany`/`companyName`/`inn`/`kpp`/`phoneExt`), описание груза и вес
|
||||
(`content.description`), даты `pickupDate`/`deliveryDate`,
|
||||
`accountEmail`, блок `systemData` с зафиксированным тарифом, parcelType,
|
||||
docPackaging и dimensions. Поле наименования — camelCase. Это breaking change
|
||||
без обратной совместимости.
|
||||
|
||||
## Goal
|
||||
Привести бэкенд к новому контракту `/api/v1/delivery/order`, сохранив текущий
|
||||
flow «валидация цены через CDEK → создание payment URL в TBank → persist в
|
||||
PostgreSQL → возврат `payment_url`; webhook `CONFIRMED` → регистрация заказа в
|
||||
CDEK».
|
||||
|
||||
## Constraints
|
||||
- camelCase в API; внутренние имена остаются Python-friendly через Pydantic
|
||||
alias-generator. JSON принимается ТОЛЬКО в camelCase.
|
||||
- `orderUuid` и `systemData.tariff.tariffCode` приходят от фронта.
|
||||
- `systemData.tariff.price` — итоговая сумма в копейках; backend использует её
|
||||
как `amount_kopecks` для TBank без пересчёта.
|
||||
- Расширить `senderAddress` и `receiverAddress` обязательным полем `cityId: int`
|
||||
(то же, что в `/price`), чтобы резолвить CDEK city code из `cities_map`.
|
||||
- При `isCompany=true` поля `companyName`, `inn`, `kpp` обязательны.
|
||||
- При `parcelType='doc'` `dimensions` принимает значение `null`; CDEK packages
|
||||
отправляются без length/width/height.
|
||||
- При `parcelType='parcel'` `dimensions` обязателен.
|
||||
- Адрес для CDEK собирается строкой `"{city}, {street}, {house}, кв. {apartment}"`;
|
||||
если `apartment` пустой/отсутствует — без хвоста `, кв. ...`.
|
||||
- `pickupDate` → CDEK `shipment_point.date`; `deliveryDate` → CDEK
|
||||
`delivery_point.date` (если задан).
|
||||
- `content.description` пробрасывается в CDEK `packages[0].items[0].name` и
|
||||
`comment` верхнего уровня.
|
||||
- `phoneExt` пробрасывается в CDEK `phones[0].additional`.
|
||||
- Сохранять весь принятый payload в JSONB-колонке `payload` записи `orders`.
|
||||
- ORM-модель `orders` рефакторится: вместо колонок `sender/recipient/
|
||||
from_location/to_location/packages/services/comment/delivery_type` —
|
||||
одна колонка `payload JSONB NOT NULL`, плюс `account_email VARCHAR`.
|
||||
Колонки `order_uuid`, `payment_url`, `price`, `tariff_code`, `payment_status`,
|
||||
`tbank_payment_id`, `cdek_order_uuid`, `created_at`, `updated_at` сохраняются.
|
||||
|
||||
## Acceptance criteria
|
||||
- `POST /api/v1/delivery/order` принимает новый camelCase payload и возвращает
|
||||
`InitPaymentResponse` без изменений (`{ "payment_url": str }`).
|
||||
- Pydantic-модели валидируют: обязательность реквизитов юр-лица при
|
||||
`isCompany=true`; `parcelType='doc'` ⇒ `dimensions=null`/отсутствует;
|
||||
`parcelType='parcel'` ⇒ `dimensions` обязателен; `price > 0`.
|
||||
- Service вызывает `payment_price_validation_adapter.get_payment_price(request)`
|
||||
и `payment_adapter.create_payment_link(order_uuid, amount_kopecks)` с
|
||||
`amount_kopecks = request.systemData.tariff.price`.
|
||||
- CDEK order mapper из нового `InitPaymentRequest` формирует payload с полями:
|
||||
`number`, `type=2`, `tariff_code`, `sender`, `recipient`,
|
||||
`from_location.code/address/postal_code`, `to_location.code/address/postal_code`,
|
||||
`packages[0]` с `weight` (граммы) и опциональными dimensions,
|
||||
`packages[0].items` с описанием груза, `shipment_point.date`,
|
||||
`delivery_point.date` (если есть), `comment`.
|
||||
- При `isCompany=true` CDEK получает `contragent_type="LEGAL_ENTITY"`, `company`,
|
||||
`inn`, `kpp` для соответствующей стороны.
|
||||
- ORM `Order` хранит весь payload в `payload` JSONB; репозиторий сохраняет и
|
||||
читает его без потерь; миграция переводит таблицу со старой структуры на
|
||||
новую (drop старые колонки, добавить `payload`, `account_email`).
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Pydantic-модели `payment.py` переписаны под новый camelCase-контракт с
|
||||
обязательными валидациями.
|
||||
- [ ] CDEK order mapper и `_build_payment_price_payload` собирают payload из
|
||||
новой структуры.
|
||||
- [ ] `AggregatorService.init_payment` использует `systemData.tariff.price` и
|
||||
`orderUuid` напрямую; persist использует обновлённый `OrderData`.
|
||||
- [ ] ORM-модель `Order`, `OrderData` и репозиторий перешли на `payload` JSONB
|
||||
и `account_email`.
|
||||
- [ ] Alembic-миграция `20260513_032_rework_orders_payload.py` применяется и
|
||||
откатывается.
|
||||
- [ ] Тесты на schemas, mapper, payment validation, controller и service
|
||||
обновлены и проходят.
|
||||
- [ ] `http-client.http` обновлён под новый payload.
|
||||
|
||||
## Tests
|
||||
- `tests/controllers/v1/test_init_payment.py` — fixture/assertion обновлены под
|
||||
camelCase; добавлены кейсы валидации юр-лица и doc/parcel dimensions.
|
||||
- `tests/adapters/delivery_providers/cdek/test_order_mapper.py` —
|
||||
mapping `phoneExt → additional`, контрагент юр-лица, address composition,
|
||||
doc без dimensions, shipment/delivery point dates.
|
||||
- `tests/adapters/delivery_providers/cdek/test_payment_price_validation.py` —
|
||||
validation payload собирается из `senderAddress.cityId`, `systemData.weight`,
|
||||
`systemData.dimensions`.
|
||||
- `tests/services/test_init_payment.py` — `init_payment` берёт сумму из
|
||||
`systemData.tariff.price`, `orderUuid` пробрасывается.
|
||||
- `tests/repositories/order/test_repository.py` (если есть) — обновлён под
|
||||
новую модель `Order`.
|
||||
|
||||
## Commands
|
||||
- `pytest tests/`
|
||||
- `alembic upgrade head` (smoke на пустой базе)
|
||||
- `python3 spec/gen_spec_index.py --check`
|
||||
Reference in New Issue
Block a user