BR 3.1 — Customer Service (new)
Репо
erp-customer-serviceуже создан на GitHub пустым. Порт:3013. Весь сервис с нуля.
Контракты
Задачи
1. Skeleton проекта
-
pom.xml— Spring Boot 3.x, Java 21, Liquibase, PostgreSQL driver, Kafka client, Validation API, Lombok -
application.yml/application-docker.yml:server.port: 3013spring.datasource.url: ${DATABASE_URL}spring.liquibase.change-log: classpath:db/changelog/db.changelog-master.xmlspring.kafka.bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS}spring.kafka.consumer.group-id: customer-service-group- JWT secret / service-token из env
- Cron для scheduler:
customer.groups-recompute-cron: '0 0 3 * * *'
-
Dockerfile(multi-stage build: Maven build → JRE runtime) -
docker-compose.yml— локальная отладка (PostgreSQL + Kafka) - GitHub Actions CI (build + test + image push) — по образцу
erp-user-service -
.claude/CLAUDE.md— инструкции для Claude в репе (читай свой файл из Decomposition, работай только в своём репо)
2. Liquibase миграции
-
001-create-customers.xml— таблицаcustomers+ индексы:uq_customers_franchise_phoneunique(franchise_id, phone) WHERE deleted_at IS NULLidx_customers_franchiseidx_customers_registered_at DESC
-
002-create-customer-addresses.xml— таблицаcustomer_addresses+ индексidx_customer_addresses_customer+ unique(customer_id) WHERE is_default = true -
003-create-customer-groups.xml— таблица + индексы +CHECKconstrainttypevsrules_json -
004-create-customer-group-members.xml— таблица + PK + индекс(customer_id)
3. Entity + Repository + DTO
-
Customerentity (все поля),CustomerAddress,CustomerGroup,CustomerGroupMember - Repositories с методами для поиска (
findByFranchiseIdAndPhoneAndDeletedAtIsNull,findByFranchiseIdOrderByRegisteredAtDescс Pageable, и т.д.) - DTOs:
CustomerResponse,CreateCustomerRequest,UpdateCustomerRequest,CustomerSearchRequest,CustomerAddressRequest,CustomerAddressResponse,CustomerGroupResponse,CustomerGroupRequest,MergeCustomerRequest
4. Security
-
JwtAuthenticationFilter— локальная валидация JWT (скопировать паттерн изerp-user-service) -
ServiceTokenFilter— для internal endpoints (X-Service-Token) -
JwtUserprincipal с методомhasPermission(String key)
5. Controllers
-
CustomerController— Public:- GET /customers (список + фильтры + поиск + пагинация)
- GET /customers/search (по phone)
- GET /customers/{id}
- POST /customers
- PATCH /customers/{id}
- DELETE /customers/{id}
- POST /customers/{id}/merge
-
CustomerAddressController— Public:- GET/POST/PATCH/DELETE /customers/{id}/addresses (и /addresses/{address_id})
-
CustomerGroupController— Public:- CRUD /customer-groups
- GET/POST/DELETE /customer-groups/{id}/members
- POST /customer-groups/{id}/recompute
-
InternalCustomerController— Internal:- GET /internal/customers/{id}
- POST /internal/customer-groups/recompute-for-customer
6. Service layer
-
CustomerService— создание, обновление, soft delete с анонимизацией PII, merge, phone normalization (E.164), уникальность, поиск -
CustomerAddressService— CRUD + логика «один default» -
CustomerGroupService— CRUD групп -
CustomerGroupMembershipService— add/remove members (static) -
CustomerGroupRulesEngine— парсингrules_json, применение к клиенту, вычисление membership -
CustomerGroupRecomputeService— полный пересчёт (scheduler) + точечный (для клиента) -
PhoneNormalizer— утилита приведения к E.164 -
PiiAnonymizer— обнуление полей при soft delete
7. Kafka
- Producer: publish
customer.created/customer.updated/customer.deleted/customer-group.member-changed - Consumer:
order.completed→ Customer Service приcustomer_id IS NOT NULLдёргаетCustomerGroupRecomputeService.recomputeForCustomer(customer_id) - Unit / integration тесты consumer’а (embedded-kafka)
8. Scheduler
-
CustomerGroupsScheduler—@Scheduled(cron = "${customer.groups-recompute-cron}")→ вызовCustomerGroupRecomputeService.recomputeAllFranchises() - Логирование:
INFOс франшизой и кол-вом групп/клиентов
9. Exception handler
-
GlobalExceptionHandler—ApiException→ соответствующий HTTP код + JSON{ error: { code, message, details } } - Safety net на
DataIntegrityViolationException→ 409DB_CONSTRAINT(как в Warehouse Service) - Коды ошибок согласно API: CUSTOMER_NOT_FOUND, CUSTOMER_PHONE_TAKEN, ADDRESS_NOT_FOUND, GROUP_NOT_FOUND, GROUP_NAME_TAKEN, GROUP_WRONG_TYPE, MERGE_SAME_CUSTOMER, FORBIDDEN, VALIDATION_ERROR
10. Интеграция с Order Service
- HTTP client (
CustomerOrderStatsClient) дляGET /internal/orders/customer-summary— используется движком правил при пересчётеtotal_spent_*иdays_inactive - Circuit breaker / retry (Resilience4j или простой ручной) — если Order Service недоступен, пересчёт откладывается (не падает)
11. Тесты
- Unit: PhoneNormalizer, PiiAnonymizer, CustomerGroupRulesEngine (каждый тип правила), валидации
- Integration: CRUD customers, поиск, merge, soft delete; CRUD groups; scheduler
- Kafka: consume
order.completed, publishcustomer.*
12. Deploy
- Добавить сервис в
erp-infrastructure/docker-compose.yml(после approve пользователя — инфра-правка) -
customer_dbчерез общий Postgres (уже используется), автосоздаётся через init-script либо вручную SQLCREATE DATABASE customer_db; - Добавить в
.claude/skills/deploy-all/SKILL.mdмаппинг compose-name:customer-service→erp-customer-service
Definition of Done
- Все endpoints из API.md работают (smoke test)
- Scheduler выполняется локально — можно проверить через
@Scheduledlog - Consumer успешно обрабатывает
order.completed(embedded kafka тест) - Заказ с привязанным клиентом → пересчёт groups → смена membership
- Soft delete — phone обезличен, история заказов с этим
customer_idсохранилась - Merge — источник soft-deleted, адреса перенесены, группы перенесены