Paykeeper Adapter — Data Model

База данных: paykeeper_adapter_db

erDiagram
    paykeeper_accounts ||--o{ paykeeper_terminals : "owns"
    paykeeper_accounts ||--o{ paykeeper_invoices : "routes"
    paykeeper_accounts ||--o{ paykeeper_payments : "routes"
    paykeeper_accounts ||--o{ paykeeper_refunds : "routes"
    paykeeper_accounts ||--o{ pk_outbox : "routes"
    paykeeper_accounts ||--o{ webhook_log : "routes"
    paykeeper_accounts ||--|| pk_token_cache : "has"
    paykeeper_accounts ||--o{ paykeeper_products : "syncs"
    paykeeper_accounts ||--o{ pk_catalog_sync_runs : "tracks"
    paykeeper_accounts ||--o{ paykeeper_users : "imported"
    paykeeper_accounts ||--o{ paykeeper_user_imports : "tracks"
    paykeeper_invoices ||--o{ pk_invoice_check_schedule : "polls"
    paykeeper_payments ||--o{ paykeeper_receipts : "has"
    paykeeper_payments ||--o{ paykeeper_refunds : "has"

    paykeeper_accounts {
        uuid id PK
        uuid legal_entity_id UNIQUE "NOT NULL"
        varchar pk_server_host "NOT NULL, (255)"
        varchar pk_login "NOT NULL, (100)"
        bytea pk_password_enc "NOT NULL — AES-GCM"
        bytea informer_seed_enc "NOT NULL — AES-GCM"
        varchar paykeeper_id "NULL, (50)"
        varchar status "NOT NULL, default active, (20)"
        timestamp onboarded_at "NOT NULL"
        timestamp last_token_at "NULL"
        timestamp created_at "NOT NULL"
        timestamp updated_at "NOT NULL"
        timestamp deleted_at "NULL"
    }

    paykeeper_terminals {
        uuid id PK
        uuid account_id FK "NOT NULL"
        uuid store_id UNIQUE "NOT NULL"
        varchar pk_terminal_id UNIQUE "NOT NULL, (100)"
        varchar pk_mpos_merchant_id "NOT NULL, (100)"
        varchar label "NULL, (100)"
        varchar status "NOT NULL, default active, (20)"
        timestamp created_at "NOT NULL"
        timestamp updated_at "NOT NULL"
    }

    paykeeper_invoices {
        uuid id PK
        uuid account_id FK "NOT NULL"
        uuid order_id UNIQUE "NOT NULL — ссылка на Order Service"
        varchar pk_invoice_id UNIQUE "NOT NULL, (50)"
        text pk_invoice_url "NOT NULL"
        varchar pk_status "NOT NULL, (20)"
        decimal pay_amount "NOT NULL, (12,2)"
        varchar orderid_bridge "NOT NULL — packed store_id:order_number"
        timestamp created_at "NOT NULL"
        timestamp paid_at "NULL"
        timestamp expiry_at "NULL"
    }

    paykeeper_payments {
        uuid id PK
        uuid account_id FK "NOT NULL"
        uuid order_id "NOT NULL"
        varchar pk_payment_id "NOT NULL, (50)"
        varchar pk_unique_id "NULL, (100)"
        decimal pay_amount "NOT NULL, (12,2)"
        integer payment_system_id "NULL"
        varchar status "NOT NULL, (20)"
        varchar bank_id "NULL, (100)"
        varchar card_last4 "NULL, (4)"
        timestamp pending_datetime "NULL"
        timestamp obtain_datetime "NULL"
        timestamp success_datetime "NULL"
        jsonb raw_informer_json "NOT NULL"
        boolean reconciled "NOT NULL, default false"
        timestamp received_at "NOT NULL"
    }

    paykeeper_receipts {
        uuid id PK
        uuid payment_id FK "NOT NULL"
        varchar pk_receipt_id "NOT NULL, (50)"
        varchar type "NOT NULL, (20)"
        boolean is_post_sale "NOT NULL, default false"
        boolean is_correction "NOT NULL, default false"
        varchar status "NOT NULL, (20)"
        decimal sum_cashless "NULL, (12,2)"
        decimal sum_cash "NULL, (12,2)"
        varchar fpd "NULL, (50)"
        varchar fnd "NULL, (50)"
        varchar fn "NULL, (50)"
        varchar rnkkt "NULL, (50)"
        integer shift_number "NULL"
        integer receipt_number "NULL"
        varchar fop_receipt_key "NULL, (100)"
        text fop_url "NULL"
        varchar ts "NULL, (20)"
        jsonb cart_json "NULL"
        jsonb error_json "NULL"
        timestamp received_at "NOT NULL"
    }

    paykeeper_refunds {
        uuid id PK
        uuid account_id FK "NOT NULL"
        uuid order_id "NOT NULL"
        uuid payment_id FK "NULL"
        varchar pk_refund_id "NULL, (50)"
        decimal amount "NOT NULL, (12,2)"
        decimal refund_total_so_far "NULL, (12,2)"
        boolean is_full_refund "NOT NULL"
        varchar status "NOT NULL, (20)"
        varchar initiated_by "NOT NULL, (10)"
        text reason "NULL"
        jsonb refund_cart "NULL"
        timestamp datetime "NULL"
        timestamp created_at "NOT NULL"
    }

    pk_outbox {
        uuid id PK
        uuid account_id FK "NOT NULL"
        varchar op_type "NOT NULL, (50)"
        jsonb payload_json "NOT NULL"
        varchar status "NOT NULL, default pending, (20)"
        integer attempts "NOT NULL, default 0"
        timestamp next_attempt_at "NOT NULL"
        text last_error "NULL"
        timestamp created_at "NOT NULL"
        timestamp sent_at "NULL"
    }

    webhook_log {
        uuid id PK
        uuid account_id FK "NULL"
        varchar endpoint "NOT NULL, (50)"
        text raw_body "NOT NULL"
        jsonb headers_json "NOT NULL"
        boolean signature_valid "NOT NULL"
        varchar dedup_key "NULL, (100)"
        boolean processed "NOT NULL, default false"
        text processing_error "NULL"
        timestamp created_at "NOT NULL"
    }

    pk_token_cache {
        uuid account_id PK
        varchar token "NOT NULL, (255)"
        timestamp expires_at "NOT NULL"
    }

    pk_invoice_check_schedule {
        uuid id PK
        uuid invoice_id FK "NOT NULL"
        timestamp next_run_at "NOT NULL"
        integer interval_min "NOT NULL, default 5"
        integer attempts "NOT NULL, default 0"
        varchar status "NOT NULL, default active, (20)"
        timestamp created_at "NOT NULL"
    }

    paykeeper_products {
        uuid id PK
        uuid account_id FK "NOT NULL"
        uuid erp_product_id "NOT NULL"
        uuid erp_structural_option_id "NULL"
        uuid erp_free_option_id "NULL"
        varchar variant_kind "NOT NULL, (20)"
        varchar sku "NOT NULL, (120)"
        varchar pk_product_id "NOT NULL, (100)"
        varchar hash "NOT NULL, (64)"
        varchar status "NOT NULL, default active, (20)"
        timestamp last_synced_at "NOT NULL"
        text last_error "NULL"
        timestamp created_at "NOT NULL"
        timestamp updated_at "NOT NULL"
    }

    pk_catalog_sync_runs {
        uuid id PK
        uuid account_id FK "NOT NULL"
        varchar trigger "NOT NULL, (20)"
        timestamp started_at "NOT NULL"
        timestamp finished_at "NULL"
        integer products_upserted "NOT NULL, default 0"
        integer products_deleted "NOT NULL, default 0"
        integer errors_count "NOT NULL, default 0"
        varchar status "NOT NULL, (20)"
        text last_error "NULL"
        jsonb errors_json "NULL"
    }

    paykeeper_users {
        uuid id PK
        uuid account_id FK "NOT NULL"
        varchar pk_user_id "NOT NULL, (50)"
        varchar pk_login "NOT NULL, (100)"
        uuid employee_id "NOT NULL"
        timestamp created_at "NOT NULL"
        timestamp updated_at "NOT NULL"
    }

    paykeeper_user_imports {
        uuid id PK
        uuid account_id FK "NOT NULL"
        varchar trigger "NOT NULL, default manual, (20)"
        uuid initiated_by_user_id "NOT NULL"
        timestamp started_at "NOT NULL"
        timestamp finished_at "NULL"
        integer users_total "NOT NULL, default 0"
        integer users_created "NOT NULL, default 0"
        integer users_linked "NOT NULL, default 0"
        integer users_updated "NOT NULL, default 0"
        integer users_skipped "NOT NULL, default 0"
        integer users_errored "NOT NULL, default 0"
        varchar status "NOT NULL, default running, (20)"
        text last_error "NULL"
        jsonb errors_json "NULL"
    }

Таблицы

paykeeper_accounts

Аккаунт PayKeeper — привязка ЛК PK к юрлицу.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
legal_entity_iduuidNOT NULLFK → User Service legal_entities (логический). UNIQUE
pk_server_hostvarchar(255)NOT NULLURL ЛК PK {tsp}.server.paykeeper.ru
pk_loginvarchar(100)NOT NULLЛогин ЛК PK
pk_password_encbyteaNOT NULLПароль, AES-GCM зашифровано
informer_seed_encbyteaNOT NULLСекретное слово для MD5-подписи webhook’ов, AES-GCM
paykeeper_idvarchar(50)NULLНомер договора PK (отображается в ЛК)
statusvarchar(20)NOT NULL'active'active / suspended
onboarded_attimestampNOT NULLnow()Дата подключения
last_token_attimestampNULLПоследний успешный рефреш security token
created_attimestampNOT NULLnow()
updated_attimestampNOT NULLnow()
deleted_attimestampNULLSoft delete

Индексы:

  • uq_pk_accounts_legal_entity — UNIQUE legal_entity_id WHERE deleted_at IS NULL
  • idx_pk_accounts_status — по status

paykeeper_terminals

Привязка ТТ к физическому mPOS-терминалу у PK.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
account_iduuidNOT NULLFK → paykeeper_accounts
store_iduuidNOT NULLFK → Store Service stores (логический). UNIQUE
pk_terminal_idvarchar(100)NOT NULLЗаводской ID терминала. UNIQUE глобально
pk_mpos_merchant_idvarchar(100)NOT NULLID мерчанта у PK
labelvarchar(100)NULL«Касса 1»
statusvarchar(20)NOT NULL'active'active / inactive
created_attimestampNOT NULLnow()
updated_attimestampNOT NULLnow()

Индексы:

  • uq_pk_terminals_store — UNIQUE store_id
  • uq_pk_terminals_pk_id — UNIQUE pk_terminal_id
  • idx_pk_terminals_accountaccount_id

paykeeper_invoices

Запись об инвойсе, созданном в PK для нашего заказа.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
account_iduuidNOT NULLFK → paykeeper_accounts
order_iduuidNOT NULLНаш заказ (логический FK → Order Service). UNIQUE
pk_invoice_idvarchar(50)NOT NULLID счёта у PK. UNIQUE
pk_invoice_urltextNOT NULLURL для оплаты (отдаётся клиенту / терминалу)
pk_statusvarchar(20)NOT NULL'created'created / sent / paid / expired
pay_amountdecimal(12,2)NOT NULLСумма
orderid_bridgevarchar(255)NOT NULLPacked bridge-ID (base64url(store_id:order_number)) — передаётся в PK как orderid, возвращается в webhook
created_attimestampNOT NULLnow()
paid_attimestampNULLКогда PK отметил как оплачен
expiry_attimestampNULLTTL инвойса

Индексы:

  • uq_pk_invoices_order — UNIQUE order_id
  • uq_pk_invoices_pk_id — UNIQUE pk_invoice_id
  • idx_pk_invoices_statuspk_status

paykeeper_payments

Informer success — факт оплаты, принятый от PK.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
account_iduuidNOT NULLFK → paykeeper_accounts
order_iduuidNOT NULLЛогический FK → Order Service
pk_payment_idvarchar(50)NOT NULLID платежа у PK
pk_unique_idvarchar(100)NULLУникальный ID транзакции у банка
pay_amountdecimal(12,2)NOT NULL
payment_system_idintegerNULLID платёжной системы PK
statusvarchar(20)NOT NULL'paid'paid / stuck (эквивалент PK success / stuck)
bank_idvarchar(100)NULLID привязки карты
card_last4varchar(4)NULLПоследние 4 цифры карты
pending_datetimetimestampNULLИз PK
obtain_datetimetimestampNULLИз PK
success_datetimetimestampNULLИз PK
raw_informer_jsonjsonbNOT NULLПолный payload informer’а для аудита
reconciledbooleanNOT NULLfalsetrue если запись создана через reconciliation, а не прямой informer
received_attimestampNOT NULLnow()

Индексы:

  • uq_pk_payments_dedup — UNIQUE (account_id, pk_payment_id)
  • idx_pk_payments_orderorder_id

paykeeper_receipts

Фискальный чек из PK (догружается после оплаты / приходит через callback §8.13).

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
payment_iduuidNOT NULLFK → paykeeper_payments
pk_receipt_idvarchar(50)NOT NULLID чека у PK
typevarchar(20)NOT NULLsale / refund / expense / expense-refund
is_post_salebooleanNOT NULLfalseЧек окончательного расчёта
is_correctionbooleanNOT NULLfalseЧек коррекции ФФД 1.2
statusvarchar(20)NOT NULLcreated / request_sent / success / timeout / failed / rejected
sum_cashless, sum_cashdecimal(12,2)NULL
fpdvarchar(50)NULLФискальный признак
fndvarchar(50)NULLНомер ФД
fnvarchar(50)NULLНомер ФН
rnkktvarchar(50)NULLРегистрационный номер ККТ
shift_numberintegerNULL
receipt_numberintegerNULLНомер чека в смене
fop_receipt_keyvarchar(100)NULLКлюч чека
fop_urltextNULLQR-контент
tsvarchar(20)NULLВремя формирования YYYYMMDDTHHmm
cart_jsonjsonbNULLКорзина
error_jsonjsonbNULLОшибка если rejected/failed
received_attimestampNOT NULLnow()

Индексы:

  • uq_pk_receipts_pk_id — UNIQUE pk_receipt_id
  • idx_pk_receipts_paymentpayment_id
  • idx_pk_receipts_statusstatus

paykeeper_refunds

Возврат платежа.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
account_iduuidNOT NULLFK → paykeeper_accounts
order_iduuidNOT NULLЛогический FK
payment_iduuidNULLFK → paykeeper_payments
pk_refund_idvarchar(50)NULLID возврата у PK (если есть)
amountdecimal(12,2)NOT NULLСумма возврата
refund_total_so_fardecimal(12,2)NULLСумма всех возвратов по платежу на момент события
is_full_refundbooleanNOT NULL
statusvarchar(20)NOT NULL'started'started / done / failed
initiated_byvarchar(10)NOT NULLadmin / pos / pk_external (через ЛК PK)
reasontextNULL
refund_cartjsonbNULLКорзина возврата для частичных
datetimetimestampNULLКогда PK выполнил
created_attimestampNOT NULLnow()

Индексы:

  • idx_pk_refunds_paymentpayment_id
  • idx_pk_refunds_orderorder_id
  • idx_pk_refunds_statusstatus

pk_outbox

Исходящие команды в PK (для гарантии доставки при временной недоступности).

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
account_iduuidNOT NULLНа какой ЛК PK слать
op_typevarchar(50)NOT NULLПлатежи: create_invoice / reverse_payment / capture / repeatcnt / post_sale_receipt. Каталог (BR 3.4): upsert_product / delete_product (включая виртуальные продукты из развёрнутых модификаторов) / sync_catalog_snapshot (full re-sync marker)
payload_jsonjsonbNOT NULLТело запроса
statusvarchar(20)NOT NULL'pending'pending / in_progress / done / dead_letter
attemptsintegerNOT NULL0
next_attempt_attimestampNOT NULLnow()
last_errortextNULL
created_attimestampNOT NULLnow()
sent_attimestampNULL

Индексы:

  • idx_pk_outbox_next_attempt(status, next_attempt_at) для worker’а
  • idx_pk_outbox_accountaccount_id

webhook_log

Лог всех входящих webhook’ов от PK — для аудита и дебага.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
account_iduuidNULLNULL если аккаунт не найден по URL
endpointvarchar(50)NOT NULLinformer / refund / receipt
raw_bodytextNOT NULLПолное тело запроса
headers_jsonjsonbNOT NULLЗаголовки
signature_validbooleanNOT NULLMD5/HMAC проверка прошла
dedup_keyvarchar(100)NULLpk_payment_id или pk_receipt_id
processedbooleanNOT NULLfalseОпубликовано в Kafka
processing_errortextNULL
created_attimestampNOT NULLnow()

Индексы:

  • idx_webhook_log_dedup(account_id, endpoint, dedup_key) — для дедупа
  • idx_webhook_log_createdcreated_at (для ретеншена, чистка >90 дней)

pk_token_cache

Кэш security token’ов PK (TTL 24ч).

КолонкаТипNullableDefaultОписание
account_iduuidNOT NULLPK
tokenvarchar(255)NOT NULL
expires_attimestampNOT NULL

Альтернатива — Redis (быстрее). На выбор команды при реализации.

pk_invoice_check_schedule

Планировщик per-invoice поллинга (reconciliation когда informer задерживается).

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
invoice_iduuidNOT NULLFK → paykeeper_invoices
next_run_attimestampNOT NULLПервый запуск = invoice.created_at + 25 min
interval_minintegerNOT NULL5Интервал между проверками
attemptsintegerNOT NULL0
statusvarchar(20)NOT NULL'active'active / completed / cancelled
created_attimestampNOT NULLnow()

Индексы:

  • idx_pk_invoice_check_next(status, next_run_at) для worker’а

paykeeper_products (BR 3.4)

Mapping «виртуальный PK-продукт ↔ ERP-товар/его вариант» в пределах одного PK-аккаунта. Одна запись = один товар в ЛК PK. Один erp_product_id может разворачиваться в N записей (базовый + структурные варианты + свободные addon’ы) — см. правило развёртывания.

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
account_iduuidNOT NULLFK → paykeeper_accounts
erp_product_iduuidNOT NULLproducts.id — корневой товар ERP
erp_structural_option_iduuidNULLmodifier_options.id если запись представляет структурный вариант (размер 30см и т.п.)
erp_free_option_iduuidNULLmodifier_options.id если запись — свободный addon (доп. сыр)
variant_kindvarchar(20)NOT NULLbase / structural_variant / free_addon
skuvarchar(120)NOT NULLЧеловеко-читаемый ключ: "{product_id}[:{struct_opt_id}][:+{free_opt_id}]". Кладём в поле sku ims-api, используется для reverse lookup
pk_product_idvarchar(100)NOT NULLID товара в ЛК PK (возвращается PK при upsert). Используется для обратного webhook’а когда PK его реализует
hashvarchar(64)NOT NULLSHA-256 от сериализованного состояния (name + price + tax + variant-specific fields)
statusvarchar(20)NOT NULL'active'active / deleted (soft при удалении товара в ERP или смене набора модификаторов)
last_synced_attimestampNOT NULLПоследний успешный push в PK
last_errortextNULLТекст последней ошибки (если sync упал)
created_attimestampNOT NULLnow()
updated_attimestampNOT NULLnow()

Индексы:

  • uq_pk_products_variant — UNIQUE (account_id, erp_product_id, COALESCE(erp_structural_option_id, '00000000-0000-0000-0000-000000000000'), COALESCE(erp_free_option_id, '00000000-0000-0000-0000-000000000000')) — одна запись на каждую комбинацию (product, struct_opt, free_opt)
  • uq_pk_products_pk — UNIQUE (account_id, pk_product_id) — для обратного webhook lookup
  • idx_pk_products_status(account_id, status)
  • idx_pk_products_erp_root(account_id, erp_product_id) — все варианты одного товара одним запросом
  • idx_pk_products_stalelast_synced_at (для алерта об устаревших)

Нет отдельных таблиц paykeeper_categories и paykeeper_modifier_groups

PK ims-api не имеет сущностей категорий и модификаторов. Категория попадает в PK как часть имени товара (category_path prefix). Группа модификаторов разворачивается в набор записей paykeeper_products с variant_kindbase. Отдельных mapping-таблиц для них не заводим.

pk_catalog_sync_runs (BR 3.4)

История полных прогонов синхронизации каталога — для аудита и UI «Журнал прогонов».

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
account_iduuidNOT NULLFK → paykeeper_accounts
triggervarchar(20)NOT NULLcron / manual / webhook_missed
started_attimestampNOT NULLnow()
finished_attimestampNULLNULL пока status=running
products_upsertedintegerNOT NULL0Счётчик PK-товаров добавленных/обновлённых (включая виртуальные варианты)
products_deletedintegerNOT NULL0Счётчик удалённых PK-товаров
errors_countintegerNOT NULL0
statusvarchar(20)NOT NULL'running'running / success / partial / failed
last_errortextNULLТоп-уровневая ошибка прогона (если прервался целиком)
errors_jsonjsonbNULLДетальные ошибки: [{ erp_product_id, variant_kind, variant_sku, message }]

Индексы:

  • idx_pk_catalog_runs_account(account_id, started_at DESC) — выборка последних прогонов
  • idx_pk_catalog_runs_status(account_id, status) — для детектирования running (блокировка параллельных запусков)

paykeeper_users (BR 3.5)

Mapping «наш сотрудник ↔ пользователь ЛК PK». Создаётся при импорте через wizard «Выгрузить из PK». Используется при получении чека от PK по полю user_login для построения отчётов «выручка по кассиру».

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
account_iduuidNOT NULLFK → paykeeper_accounts ON DELETE CASCADE
pk_user_idvarchar(50)NOT NULLid из PK API (GET /info/organization/users/)
pk_loginvarchar(100)NOT NULLlogin из PK API. Используется для матча с user_login в receipt webhook’ах
employee_iduuidNOT NULLЛогический FK → User Service employees. При удалении employee mapping удаляется через webhook user.deleted
created_attimestampNOT NULLnow()
updated_attimestampNOT NULLnow()

Индексы:

  • uq_pk_users_user — UNIQUE (account_id, pk_user_id) — один pk_user в одном ЛК = одна запись
  • uq_pk_users_employee — UNIQUE (account_id, employee_id) — один employee связан только с одним pk_user в данном ЛК (employee может быть связан с разными pk_user в разных ЛК)
  • idx_pk_users_employeeemployee_id — для lookup при чеке от PK
  • idx_pk_users_login(account_id, pk_login) — для матча по login если PK API не вернул user_id в чеке

paykeeper_user_imports (BR 3.5)

История прогонов импорта сотрудников из ЛК PK — для аудита и UI «Журнал импортов».

КолонкаТипNullableDefaultОписание
iduuidNOT NULLgen_random_uuid()PK
account_iduuidNOT NULLFK → paykeeper_accounts
triggervarchar(20)NOT NULL'manual'В P0 только manual (нет cron / webhook-trigger)
initiated_by_user_iduuidNOT NULLЛогический FK → employees (кто нажал «Выгрузить из PK»)
started_attimestampNOT NULLnow()
finished_attimestampNULLNULL пока status=running
users_totalintegerNOT NULL0Размер decisions[] в request
users_createdintegerNOT NULL0Создано новых employees (action create_new или create_with_alt_email)
users_linkedintegerNOT NULL0Связано с существующими employees (action link_existing)
users_updatedintegerNOT NULL0Обновлено существующих employees (action update_existing)
users_skippedintegerNOT NULL0Пропущено по решению владельца (action skip)
users_erroredintegerNOT NULL0С ошибками при создании/обновлении
statusvarchar(20)NOT NULL'running'running / success / partial / failed
last_errortextNULLТоп-уровневая ошибка (если прогон прервался целиком)
errors_jsonjsonbNULLДетали: [{ pk_user_id, pk_login, action, message }]

Индексы:

  • idx_pk_user_imports_account(account_id, started_at DESC) — выборка последних прогонов
  • idx_pk_user_imports_status(account_id, status)

Правила

  • Все суммы — decimal(12,2) для соответствия PK (PK возвращает строки с точкой-разделителем, парсим в BigDecimal).
  • Все секреты (pk_password_enc, informer_seed_enc) — AES-GCM at rest; дешифровка только в момент отправки запроса.
  • Retention webhook_log — 90 дней (cron-чистка).
  • Retention pk_outbox status=done — 30 дней.
  • Retention pk_catalog_sync_runs — 90 дней (BR 3.4).
  • Retention paykeeper_productsбессрочно: храним историю mapping’ов на случай восстановления soft-deleted товаров и для обратного webhook lookup (BR 3.4).
  • Retention paykeeper_usersбессрочно: mapping employee ↔ pk_user используется в построении отчётов по кассирам, не удаляется (BR 3.5).
  • Retention paykeeper_user_imports — 1 год (история импортов для аудита, потом cron-чистка) (BR 3.5).
  • Soft delete paykeeper_accounts (не CASCADE) — исторические платежи остаются.
  • При soft-delete paykeeper_accounts — mapping-записи (paykeeper_products и др.) остаются status как есть; sync не выполняется пока account не active.