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: 3013
    • spring.datasource.url: ${DATABASE_URL}
    • spring.liquibase.change-log: classpath:db/changelog/db.changelog-master.xml
    • spring.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_phone unique (franchise_id, phone) WHERE deleted_at IS NULL
    • idx_customers_franchise
    • idx_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 — таблица + индексы + CHECK constraint type vs rules_json
  • 004-create-customer-group-members.xml — таблица + PK + индекс (customer_id)

3. Entity + Repository + DTO

  • Customer entity (все поля), 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)
  • JwtUser principal с методом 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

  • GlobalExceptionHandlerApiException → соответствующий HTTP код + JSON { error: { code, message, details } }
  • Safety net на DataIntegrityViolationException → 409 DB_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, publish customer.*

12. Deploy

  • Добавить сервис в erp-infrastructure/docker-compose.yml (после approve пользователя — инфра-правка)
  • customer_db через общий Postgres (уже используется), автосоздаётся через init-script либо вручную SQL CREATE DATABASE customer_db;
  • Добавить в .claude/skills/deploy-all/SKILL.md маппинг compose-name: customer-serviceerp-customer-service

Definition of Done

  • Все endpoints из API.md работают (smoke test)
  • Scheduler выполняется локально — можно проверить через @Scheduled log
  • Consumer успешно обрабатывает order.completed (embedded kafka тест)
  • Заказ с привязанным клиентом → пересчёт groups → смена membership
  • Soft delete — phone обезличен, история заказов с этим customer_id сохранилась
  • Merge — источник soft-deleted, адреса перенесены, группы перенесены