V13-FE-011: finalize search list layout slice

This commit is contained in:
2026-08-09 02:57:26 +09:00
parent 9efd202e76
commit 6422cb2b13
984 changed files with 120811 additions and 1498 deletions
@@ -0,0 +1,90 @@
-- KBX Golden Screen reference schema.
-- In a real modular monolith, schema ownership should stay with the owning module.
create schema if not exists oms;
create schema if not exists catalog;
create schema if not exists inventory;
create schema if not exists audit;
create schema if not exists integration;
create table if not exists oms.customers (
id uuid primary key,
code varchar(50) not null unique,
name varchar(200) not null,
is_active boolean not null default true
);
create table if not exists catalog.items (
id uuid primary key,
code varchar(80) not null unique,
name varchar(300) not null,
is_active boolean not null default true
);
create table if not exists inventory.warehouses (
id uuid primary key,
code varchar(50) not null unique,
name varchar(200) not null,
is_active boolean not null default true
);
create table if not exists oms.orders (
id uuid primary key,
order_no varchar(80) not null unique,
order_date date not null,
customer_id uuid not null references oms.customers(id),
warehouse_id uuid not null references inventory.warehouses(id),
receiver_name varchar(100) not null,
phone varchar(50) not null,
postal_code varchar(20),
address1 varchar(500) not null,
address2 varchar(500),
status varchar(30) not null,
version bigint not null default 1 check (version > 0),
created_at timestamptz not null default now(),
created_by varchar(200) not null,
updated_at timestamptz,
updated_by varchar(200)
);
create index if not exists ix_orders_order_date on oms.orders(order_date desc);
create index if not exists ix_orders_customer on oms.orders(customer_id, order_date desc);
create index if not exists ix_orders_status on oms.orders(status, order_date desc);
create table if not exists oms.order_lines (
id uuid primary key,
order_id uuid not null references oms.orders(id),
line_no integer not null check (line_no > 0),
item_id uuid not null references catalog.items(id),
quantity numeric(18,4) not null check (quantity > 0),
unit_price numeric(18,4) not null check (unit_price >= 0),
amount numeric(20,4) not null check (amount >= 0),
remark varchar(500),
unique(order_id, line_no)
);
create index if not exists ix_order_lines_item on oms.order_lines(item_id);
create table if not exists audit.entries (
id uuid primary key,
aggregate_type varchar(100) not null,
aggregate_id uuid not null,
action varchar(100) not null,
actor varchar(200) not null,
occurred_at timestamptz not null,
data jsonb not null default '{}'::jsonb
);
create index if not exists ix_audit_aggregate on audit.entries(aggregate_type, aggregate_id, occurred_at desc);
create table if not exists integration.outbox (
id uuid primary key,
event_type varchar(200) not null,
aggregate_id uuid not null,
payload jsonb not null,
occurred_at timestamptz not null,
status varchar(30) not null,
processed_at timestamptz,
retry_count integer not null default 0 check (retry_count >= 0),
last_error text
);
create index if not exists ix_outbox_pending on integration.outbox(status, occurred_at) where status = 'PENDING';
@@ -0,0 +1,82 @@
-- WMS-PICK-001 Golden Screen reference schema.
create schema if not exists wms;
create table if not exists wms.picking_tasks (
id uuid primary key,
task_no varchar(80) not null unique,
status varchar(30) not null check (status in ('READY','IN_PROGRESS','BLOCKED','COMPLETED','CANCELLED')),
assigned_to varchar(200),
version bigint not null default 1 check (version > 0),
started_at timestamptz,
completed_at timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz
);
create index if not exists ix_picking_tasks_status on wms.picking_tasks(status, created_at);
create table if not exists wms.picking_lines (
id uuid primary key,
task_id uuid not null references wms.picking_tasks(id),
line_no integer not null check (line_no > 0),
location_code varchar(80) not null,
location_barcode varchar(120) not null,
location_confirmed boolean not null default false,
item_id uuid not null references catalog.items(id),
item_option varchar(200),
barcode varchar(120) not null,
required_qty numeric(18,4) not null check (required_qty > 0),
picked_qty numeric(18,4) not null default 0 check (picked_qty >= 0 and picked_qty <= required_qty),
status varchar(30) not null default 'READY' check (status in ('READY','IN_PROGRESS','COMPLETED','BLOCKED')),
completed_at timestamptz,
updated_at timestamptz,
unique(task_id, line_no)
);
create index if not exists ix_picking_lines_task_status on wms.picking_lines(task_id, status, line_no);
create index if not exists ix_picking_lines_barcode on wms.picking_lines(barcode);
-- Stores the exact response for each scanner command so network retries are safe and deterministic.
create table if not exists wms.scan_receipts (
id uuid primary key,
task_id uuid not null references wms.picking_tasks(id),
idempotency_key varchar(200) not null,
barcode varchar(200) not null,
source varchar(40) not null,
occurred_at timestamptz not null,
actor varchar(200) not null,
response_payload jsonb not null,
created_at timestamptz not null default now(),
unique(task_id, idempotency_key)
);
create index if not exists ix_scan_receipts_task_created on wms.scan_receipts(task_id, created_at desc);
create table if not exists wms.picking_exceptions (
id uuid primary key,
task_id uuid not null references wms.picking_tasks(id),
line_id uuid references wms.picking_lines(id),
exception_type varchar(50) not null,
memo varchar(300),
status varchar(30) not null check (status in ('OPEN','RESOLVED','CANCELLED')),
reported_at timestamptz not null,
reported_by varchar(200) not null,
resolved_at timestamptz,
resolved_by varchar(200),
idempotency_key varchar(200) not null,
unique(task_id, idempotency_key)
);
create index if not exists ix_picking_exceptions_open on wms.picking_exceptions(task_id, status) where status='OPEN';
create table if not exists wms.quantity_receipts (
id uuid primary key,
task_id uuid not null references wms.picking_tasks(id),
idempotency_key varchar(200) not null,
line_id uuid not null references wms.picking_lines(id),
picked_qty numeric(18,4) not null,
actor varchar(200) not null,
response_payload jsonb not null,
created_at timestamptz not null default now(),
unique(task_id, idempotency_key)
);
@@ -0,0 +1,35 @@
-- KBX v4 reference projections. In production these can be maintained by application writes,
-- triggers, or projectors; the UX contract only requires stable read models.
create table if not exists erp_item_search_projection (
id uuid primary key,
code varchar(50) not null,
name varchar(200) not null,
category_name varchar(120) not null default '',
specification varchar(200) not null default '',
unit varchar(20) not null default 'EA',
barcode varchar(100) not null default '',
default_warehouse_id uuid null,
default_warehouse_name varchar(120) not null default '',
lot_managed boolean not null default false,
expiry_managed boolean not null default false,
active boolean not null default true,
version bigint not null default 1,
search_text text not null default ''
);
create table if not exists erp_inventory_snapshot_projection (
snapshot_key varchar(100) primary key,
item_id uuid not null,
item_code varchar(50) not null,
item_name varchar(200) not null,
specification varchar(200) not null default '',
warehouse_id uuid not null,
warehouse_name varchar(120) not null,
location_id uuid null,
location_code varchar(80) null,
on_hand_qty numeric(18,4) not null default 0,
allocated_qty numeric(18,4) not null default 0,
hold_qty numeric(18,4) not null default 0,
available_qty numeric(18,4) not null default 0,
search_text text not null default ''
);
create index if not exists ix_erp_inventory_snapshot_item on erp_inventory_snapshot_projection(item_id);
@@ -0,0 +1,86 @@
create schema if not exists kbx;
create table if not exists kbx.import_sessions (
id uuid primary key,
tenant_id varchar(100) not null,
user_id varchar(200) not null,
screen_id varchar(100) not null,
import_type varchar(150) not null,
original_file_name varchar(300) not null,
status varchar(40) not null,
source_columns jsonb not null default '[]'::jsonb,
mapping jsonb not null default '[]'::jsonb,
total_rows integer not null default 0 check (total_rows >= 0),
valid_rows integer not null default 0 check (valid_rows >= 0),
invalid_rows integer not null default 0 check (invalid_rows >= 0),
warning_rows integer not null default 0 check (warning_rows >= 0),
created_rows integer not null default 0 check (created_rows >= 0),
updated_rows integer not null default 0 check (updated_rows >= 0),
progress_percent integer not null default 0 check (progress_percent between 0 and 100),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
completed_at timestamptz,
expires_at timestamptz not null default (now() + interval '14 days'),
failure_code varchar(120),
failure_message text
);
create index if not exists ix_import_sessions_owner
on kbx.import_sessions(tenant_id, user_id, created_at desc);
create index if not exists ix_import_sessions_status
on kbx.import_sessions(status, updated_at);
create index if not exists ix_import_sessions_expiry
on kbx.import_sessions(expires_at);
-- File bytes are separated from session metadata so normal session queries never load XLSX blobs.
-- This PostgreSQL-backed starter avoids a distributed filesystem dependency. The interface can
-- later be replaced with object storage without changing the import workflow.
create table if not exists kbx.import_files (
session_id uuid primary key references kbx.import_sessions(id) on delete cascade,
content_type varchar(150) not null,
file_size bigint not null check (file_size >= 0),
sha256 varchar(64) not null,
data bytea not null
);
create table if not exists kbx.import_rows (
session_id uuid not null references kbx.import_sessions(id) on delete cascade,
row_number integer not null check (row_number >= 2),
raw_data jsonb not null,
normalized_data jsonb,
status varchar(30) not null default 'PENDING',
errors jsonb not null default '[]'::jsonb,
warnings jsonb not null default '[]'::jsonb,
domain_key varchar(300),
primary key(session_id, row_number)
);
create index if not exists ix_import_rows_status
on kbx.import_rows(session_id, status, row_number);
create index if not exists ix_import_rows_domain_key
on kbx.import_rows(session_id, domain_key)
where domain_key is not null;
create table if not exists kbx.saved_import_mappings (
id uuid primary key,
tenant_id varchar(100) not null,
user_id varchar(200) not null,
import_type varchar(150) not null,
name varchar(200) not null,
source_signature varchar(64) not null,
mapping jsonb not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique(tenant_id, user_id, import_type, name)
);
create index if not exists ix_saved_import_mapping_signature
on kbx.saved_import_mappings(tenant_id, user_id, import_type, source_signature);
create table if not exists kbx.import_job_receipts (
session_id uuid not null references kbx.import_sessions(id) on delete cascade,
operation varchar(40) not null,
job_id varchar(100) not null,
requested_at timestamptz not null default now(),
primary key(session_id, operation)
);
@@ -0,0 +1,86 @@
create schema if not exists kbx;
-- Operational projection only. OMS/WMS/ERP remain the source of truth.
create table if not exists kbx.work_items (
id uuid primary key,
tenant_id varchar(100) not null,
source_module varchar(20) not null,
source_type varchar(100) not null,
source_id varchar(200) not null,
reference_no varchar(200) not null,
source_screen_id varchar(100),
source_version bigint,
code varchar(120) not null,
title varchar(240) not null,
detail text,
severity varchar(20) not null check (severity in ('info','warning','critical')),
status varchar(20) not null check (status in ('open','claimed','resolved','ignored')),
owner_id varchar(200),
owner_name varchar(200),
retry_action_key varchar(160),
allow_manual_resolution boolean not null default false,
context jsonb not null default '{}'::jsonb,
occurred_at timestamptz not null,
due_at timestamptz,
resolved_at timestamptz,
resolution_reason text,
version bigint not null default 1,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique(tenant_id, source_module, source_type, source_id, code)
);
create index if not exists ix_work_items_queue
on kbx.work_items(tenant_id, status, severity, occurred_at desc);
create index if not exists ix_work_items_owner
on kbx.work_items(tenant_id, owner_id, status, occurred_at desc);
create index if not exists ix_work_items_source
on kbx.work_items(tenant_id, source_module, source_type, source_id);
create index if not exists ix_work_items_reference
on kbx.work_items(tenant_id, reference_no);
create table if not exists kbx.work_item_audit (
id uuid primary key,
work_item_id uuid not null references kbx.work_items(id) on delete cascade,
tenant_id varchar(100) not null,
action varchar(80) not null,
actor_id varchar(200) not null,
actor_name varchar(200) not null,
reason text,
before_status varchar(20),
after_status varchar(20),
occurred_at timestamptz not null default now()
);
create index if not exists ix_work_item_audit_item
on kbx.work_item_audit(work_item_id, occurred_at desc);
-- Comparison projection. It stores evidence/result, not authoritative inventory/order state.
create table if not exists kbx.reconcile_items (
id uuid primary key,
tenant_id varchar(100) not null,
reconcile_type varchar(120) not null,
reference_no varchar(200) not null,
source_label varchar(160) not null,
target_label varchar(160) not null,
expected_value varchar(300) not null,
actual_value varchar(300) not null,
difference_value varchar(300),
reason_code varchar(120),
reason_text text,
status varchar(20) not null check (status in ('matched','mismatch','pending','resolved')),
source_id varchar(200),
target_id varchar(200),
identity_key varchar(420) not null,
evidence jsonb not null default '{}'::jsonb,
occurred_at timestamptz not null,
resolved_at timestamptz,
version bigint not null default 1,
updated_at timestamptz not null default now(),
unique(tenant_id, reconcile_type, reference_no, identity_key)
);
create index if not exists ix_reconcile_queue
on kbx.reconcile_items(tenant_id, reconcile_type, status, occurred_at desc);
create index if not exists ix_reconcile_reference
on kbx.reconcile_items(tenant_id, reference_no);
@@ -0,0 +1,37 @@
create schema if not exists kbx;
create table if not exists kbx.user_suggestions (
id uuid primary key,
tenant_id uuid not null,
user_id uuid not null,
category varchar(32) not null check (category in ('inconvenience','bug','improvement')),
message varchar(2000) not null,
screen_id varchar(64) not null,
screen_version varchar(32) not null,
route varchar(512) not null,
app_version varchar(64) not null,
user_role varchar(128) not null,
context jsonb null,
status varchar(32) not null default 'NEW',
created_at timestamptz not null default now()
);
create index if not exists ix_user_suggestions_screen_created
on kbx.user_suggestions(screen_id, created_at desc);
create index if not exists ix_user_suggestions_status_created
on kbx.user_suggestions(status, created_at desc);
-- AI telemetry intentionally excludes the user's full question and business payload.
create table if not exists kbx.ai_interactions (
id uuid primary key,
tenant_id uuid not null,
user_id uuid not null,
screen_id varchar(64) not null,
screen_version varchar(32) not null,
capability varchar(32) not null,
result_kind varchar(32) not null,
provider varchar(64) null,
duration_ms integer null,
created_at timestamptz not null default now()
);
create index if not exists ix_ai_interactions_screen_created
on kbx.ai_interactions(screen_id, created_at desc);
@@ -0,0 +1,49 @@
-- KBX v8 reference process coverage. Source modules remain owners of their business state.
create schema if not exists oms;
create schema if not exists erp;
create table if not exists oms.claims (
id uuid primary key,
tenant_id uuid not null,
claim_no varchar(50) not null,
order_id uuid not null,
order_no varchar(50) not null,
channel_name varchar(100) not null,
type varchar(20) not null,
reason varchar(300) not null,
requested_qty numeric(18,4) not null check (requested_qty > 0),
status varchar(30) not null,
owner_name varchar(100),
requested_at timestamptz not null,
version bigint not null default 1,
unique(tenant_id, claim_no)
);
create or replace view oms.claim_search_projection as
select id,claim_no,order_no,channel_name,type,reason,requested_qty,status,owner_name,requested_at from oms.claims;
create table if not exists erp.purchase_orders (
id uuid primary key,
tenant_id uuid not null,
purchase_no varchar(50) not null,
purchase_date date not null,
supplier_id uuid not null,
warehouse_id uuid not null,
status varchar(30) not null,
version bigint not null default 1,
unique(tenant_id,purchase_no)
);
create table if not exists erp.purchase_order_lines (
id uuid primary key, purchase_order_id uuid not null references erp.purchase_orders(id), item_id uuid not null,
quantity numeric(18,4) not null check(quantity>0), unit_price numeric(18,4) not null check(unit_price>=0), due_date date
);
create table if not exists erp.inventory_moves (
id uuid primary key, tenant_id uuid not null, move_no varchar(50) not null, move_date date not null,
from_warehouse_id uuid not null, to_warehouse_id uuid not null, status varchar(30) not null, version bigint not null default 1,
check(from_warehouse_id <> to_warehouse_id), unique(tenant_id,move_no)
);
create table if not exists erp.inventory_move_lines (
id uuid primary key, move_id uuid not null references erp.inventory_moves(id), item_id uuid not null,
move_qty numeric(18,4) not null check(move_qty>0), lot_no varchar(100), remark varchar(500)
);
@@ -0,0 +1,59 @@
-- KBX v9 production runtime projections.
-- These tables hold operational state, not domain truth.
create schema if not exists kbx;
create table if not exists kbx.operation_runs (
id uuid primary key,
tenant_id uuid not null,
user_id uuid,
operation_type varchar(80) not null,
title varchar(200) not null,
source_screen_id varchar(60),
status varchar(30) not null,
requested_at timestamptz not null default now(),
started_at timestamptz,
completed_at timestamptz,
processed bigint not null default 0 check(processed >= 0),
total bigint check(total is null or total >= 0),
succeeded bigint not null default 0 check(succeeded >= 0),
failed bigint not null default 0 check(failed >= 0),
correlation_id varchar(100),
idempotency_key varchar(160),
result_message varchar(1000),
result jsonb,
expires_at timestamptz,
unique(tenant_id, idempotency_key)
);
create index if not exists ix_kbx_operation_runs_user_recent on kbx.operation_runs(tenant_id, user_id, requested_at desc);
create index if not exists ix_kbx_operation_runs_status on kbx.operation_runs(tenant_id, status, requested_at desc);
create table if not exists kbx.user_notifications (
id uuid primary key,
tenant_id uuid not null,
user_id uuid not null,
severity varchar(20) not null,
category varchar(50) not null,
title varchar(200) not null,
message varchar(1000),
screen_id varchar(60),
entity_type varchar(80),
entity_id varchar(120),
action jsonb,
created_at timestamptz not null default now(),
read_at timestamptz,
expires_at timestamptz
);
create index if not exists ix_kbx_user_notifications_unread on kbx.user_notifications(tenant_id, user_id, created_at desc) where read_at is null;
create table if not exists kbx.runtime_incidents (
id uuid primary key,
tenant_id uuid,
mode varchar(20) not null,
title varchar(200) not null,
message varchar(1000),
started_at timestamptz not null,
ended_at timestamptz,
correlation_id varchar(100),
metadata jsonb
);
create index if not exists ix_kbx_runtime_incidents_active on kbx.runtime_incidents(started_at desc) where ended_at is null;
@@ -0,0 +1,14 @@
-- KBX v14 API command idempotency receipt.
-- This is infrastructure state, not a domain aggregate.
create schema if not exists kbx;
create table if not exists kbx.command_receipts (
operation_id varchar(150) not null,
idempotency_key varchar(200) not null,
response_json jsonb not null,
created_at timestamptz not null default now(),
primary key(operation_id, idempotency_key)
);
create index if not exists ix_command_receipts_created_at
on kbx.command_receipts(created_at);
@@ -0,0 +1,15 @@
create schema if not exists kbx;
create table if not exists kbx.sensitive_data_disclosures (
id bigserial primary key,
tenant_id uuid not null,
user_id uuid not null,
policy_id text not null,
entity_type text not null,
entity_id text not null,
fields jsonb not null,
reason text null,
correlation_id text not null,
occurred_at timestamptz not null default now()
);
create index if not exists ix_sensitive_disclosures_lookup
on kbx.sensitive_data_disclosures(tenant_id, occurred_at desc, policy_id);
@@ -0,0 +1,40 @@
create schema if not exists kbx;
create table if not exists kbx.ux_events (
id uuid primary key,
tenant_id uuid not null,
event_name varchar(80) not null,
screen_id varchar(80),
screen_version varchar(20),
session_id uuid not null,
task_session_id uuid,
app_version varchar(40),
occurred_at timestamptz not null,
received_at timestamptz not null default now(),
duration_ms integer,
event_count integer not null default 1 check(event_count > 0),
attributes jsonb not null default '{}'::jsonb,
check(duration_ms is null or duration_ms between 0 and 86400000)
);
create index if not exists ix_ux_events_tenant_time on kbx.ux_events(tenant_id,occurred_at desc);
create index if not exists ix_ux_events_tenant_event_time on kbx.ux_events(tenant_id,event_name,occurred_at desc);
create index if not exists ix_ux_events_screen_time on kbx.ux_events(tenant_id,screen_id,occurred_at desc) where screen_id is not null;
-- Server-side business outcome counters are intentionally separate from browser interaction events.
-- No order/customer/item identity is stored; only low-cardinality work type and counts.
create table if not exists kbx.ux_business_outcomes (
tenant_id uuid not null,
business_date date not null,
screen_id varchar(80) not null default '',
work_type varchar(80) not null,
reason_code varchar(80) not null default '',
observed_count integer not null check(observed_count >= 0),
auto_processed_count integer not null check(auto_processed_count >= 0),
manual_intervention_count integer not null check(manual_intervention_count >= 0),
updated_at timestamptz not null default now(),
primary key(tenant_id,business_date,screen_id,work_type,reason_code),
check(auto_processed_count + manual_intervention_count <= observed_count)
);
comment on table kbx.ux_events is 'KBX semantic UX telemetry. Raw business values and sensitive data are prohibited.';
comment on table kbx.ux_business_outcomes is 'Aggregated business workload counts used for Manual Intervention Rate; no business entity identifiers.';
@@ -0,0 +1,43 @@
create schema if not exists kbx;
-- Runtime overrides are operational controls; code contracts remain the canonical definition of safe experiment scope.
create table if not exists kbx.experiment_runtime (
tenant_id uuid not null,
experiment_id varchar(160) not null,
state varchar(24) not null check(state in ('draft','running','paused','rolled-back','graduated','completed')),
rollout_percent integer not null check(rollout_percent between 0 and 100),
kill_switch boolean not null default false,
version integer not null default 1,
updated_by uuid,
updated_at timestamptz not null default now(),
primary key(tenant_id,experiment_id)
);
create table if not exists kbx.experiment_assignments (
tenant_id uuid not null,
experiment_id varchar(160) not null,
user_id uuid not null,
variant varchar(80) not null,
assigned_at timestamptz not null default now(),
primary key(tenant_id,experiment_id,user_id)
);
create index if not exists ix_experiment_assignments_experiment on kbx.experiment_assignments(tenant_id,experiment_id,variant);
create table if not exists kbx.experiment_audit (
id uuid primary key,
tenant_id uuid not null,
experiment_id varchar(160) not null,
action varchar(40) not null,
before_state jsonb,
after_state jsonb,
actor_id uuid,
reason varchar(500) not null,
correlation_id varchar(100),
occurred_at timestamptz not null default now()
);
create index if not exists ix_experiment_audit_time on kbx.experiment_audit(tenant_id,experiment_id,occurred_at desc);
-- Optional experiment context is low-cardinality metadata only; no business/customer identity is added.
alter table kbx.ux_events add column if not exists experiment_id varchar(160);
alter table kbx.ux_events add column if not exists experiment_variant varchar(80);
create index if not exists ix_ux_events_experiment_time on kbx.ux_events(tenant_id,experiment_id,experiment_variant,occurred_at desc) where experiment_id is not null;
@@ -0,0 +1,37 @@
create schema if not exists kbx;
create table if not exists kbx.integration_attempts (
id uuid primary key,
tenant_id uuid not null,
integration_id text not null,
message_id uuid not null,
aggregate_type text not null,
aggregate_id text not null,
aggregate_version bigint null,
attempt_no integer not null check (attempt_no > 0),
state text not null check (state in ('queued','delivering','retrying','delivered','failed','suspended')),
failure_kind text null check (failure_kind in ('transient','permanent')),
failure_code text null,
detail text null,
http_status integer null,
external_reference text null,
correlation_id text not null,
manual_retry_key text null,
started_at timestamptz not null,
completed_at timestamptz null,
next_retry_at timestamptz null,
unique (tenant_id, integration_id, message_id, attempt_no)
);
create index if not exists ix_integration_attempts_retry on kbx.integration_attempts(tenant_id,state,next_retry_at) where state='retrying';
create index if not exists ix_integration_attempts_aggregate on kbx.integration_attempts(tenant_id,aggregate_type,aggregate_id,started_at desc);
create unique index if not exists ux_integration_manual_retry on kbx.integration_attempts(tenant_id,manual_retry_key) where manual_retry_key is not null;
create table if not exists kbx.integration_receipts (
tenant_id uuid not null,
integration_id text not null,
message_key text not null,
received_at timestamptz not null,
processed_at timestamptz null,
result jsonb null,
primary key (tenant_id,integration_id,message_key)
);
@@ -0,0 +1,44 @@
create schema if not exists kbx;
create table if not exists kbx.external_data_cache (
tenant_id uuid not null,
dataset_id text not null,
provider_id text not null,
cache_key text not null,
request_descriptor jsonb not null,
normalized_data jsonb not null,
provider_observed_at timestamptz null,
requested_at timestamptz not null,
received_at timestamptz not null,
ingested_at timestamptz not null,
fresh_until timestamptz null,
usable_until timestamptz null,
payload_sha256 char(64) not null,
normalizer_version text not null,
state text not null check(state in ('fresh','stale','expired','unavailable')),
correlation_id text not null,
primary key(tenant_id,dataset_id,cache_key),
check(usable_until is null or fresh_until is null or usable_until >= fresh_until)
);
create index if not exists ix_external_data_cache_state on kbx.external_data_cache(tenant_id,dataset_id,state,fresh_until);
create table if not exists kbx.external_data_observations (
id bigint generated always as identity primary key,
tenant_id uuid not null,
dataset_id text not null,
provider_id text not null,
cache_key text not null,
payload_sha256 char(64) not null,
normalizer_version text not null,
provider_observed_at timestamptz null,
received_at timestamptz not null,
ingested_at timestamptz not null,
state text not null,
correlation_id text not null
);
create index if not exists ix_external_data_observations_lookup on kbx.external_data_observations(tenant_id,dataset_id,cache_key,ingested_at desc);
comment on table kbx.external_data_cache is 'External provider normalized read cache. It is not domain truth and does not retain raw provider payload by default.';
comment on column kbx.external_data_cache.payload_sha256 is 'Evidence hash for reproducibility without default raw payload retention.';
comment on column kbx.external_data_cache.request_descriptor is 'Canonical non-secret request inputs only. Credentials, bearer tokens, and provider secrets are forbidden.';
@@ -0,0 +1,30 @@
-- KBX v26: independent T04 Golden Screen + T05 inventory history projection.
create table if not exists erp.item_prices (
id uuid primary key,
item_id uuid not null references catalog.items(id),
effective_date date not null,
unit_price numeric(18,4) not null check(unit_price >= 0),
remark varchar(500),
version bigint not null default 1 check(version > 0),
created_at timestamptz not null default now(),
created_by varchar(200) not null,
updated_at timestamptz,
updated_by varchar(200),
unique(item_id,effective_date)
);
create index if not exists ix_erp_item_prices_effective on erp.item_prices(item_id,effective_date desc);
create table if not exists erp_inventory_ledger_projection (
entry_id uuid primary key,
item_id uuid not null,
occurred_at timestamptz not null,
business_type varchar(80) not null,
reference_no varchar(120) not null default '',
warehouse_name varchar(120) not null,
location_code varchar(80),
inbound_qty numeric(18,4) not null default 0 check(inbound_qty >= 0),
outbound_qty numeric(18,4) not null default 0 check(outbound_qty >= 0),
balance_qty numeric(18,4) not null default 0,
actor varchar(200) not null default 'system'
);
create index if not exists ix_erp_inventory_ledger_item_time on erp_inventory_ledger_projection(item_id,occurred_at desc);
@@ -0,0 +1,26 @@
-- KBX v27: normalized item write detail and record lifecycle indexes.
create table if not exists catalog.item_details (
item_id uuid primary key references catalog.items(id),
category_name varchar(120) not null default '',
specification varchar(200) not null default '',
unit varchar(20) not null default 'EA',
barcode varchar(100) not null default '',
default_warehouse_id uuid null references inventory.warehouses(id),
lot_managed boolean not null default false,
expiry_managed boolean not null default false,
version bigint not null default 1 check(version > 0),
created_at timestamptz not null default now(),
created_by varchar(200) not null default 'system',
updated_at timestamptz,
updated_by varchar(200)
);
create unique index if not exists ux_catalog_item_details_barcode on catalog.item_details(barcode) where barcode <> '';
create index if not exists ix_audit_item_lifecycle on audit.entries(aggregate_id, occurred_at desc) where aggregate_type='Item';
create index if not exists ix_audit_order_lifecycle on audit.entries(aggregate_id, occurred_at desc) where aggregate_type='Order';
-- Backfill existing canonical item identities from the v4 read projection so v27 writes do not strand pre-existing rows.
insert into catalog.item_details(item_id,category_name,specification,unit,barcode,default_warehouse_id,lot_managed,expiry_managed,version,created_by)
select p.id,p.category_name,p.specification,p.unit,p.barcode,p.default_warehouse_id,p.lot_managed,p.expiry_managed,greatest(p.version,1),'migration-v27'
from erp_item_search_projection p
join catalog.items i on i.id=p.id
on conflict(item_id) do nothing;