ALTER TABLE qipai_product_order_items
  ADD UNIQUE KEY uq_qipai_product_order_item_scope_id
    (tenant_id, store_id, order_id, id);

CREATE TABLE IF NOT EXISTS qipai_product_storage_records (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  tenant_id BIGINT UNSIGNED NOT NULL,
  store_id BIGINT UNSIGNED NOT NULL,
  member_id BIGINT UNSIGNED NOT NULL,
  source_order_id BIGINT UNSIGNED NULL,
  storage_no VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  client_request_id VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  request_fingerprint CHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  source_type VARCHAR(16) NOT NULL,
  status VARCHAR(32) NOT NULL DEFAULT 'STORED',
  item_count SMALLINT UNSIGNED NOT NULL,
  total_quantity BIGINT UNSIGNED NOT NULL,
  remaining_quantity BIGINT UNSIGNED NOT NULL,
  expires_at DATETIME(3) NOT NULL,
  claim_credential_hash CHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  claim_credential_version INT UNSIGNED NOT NULL DEFAULT 1,
  claim_credential_rotated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  claim_credential_consumed_at DATETIME(3) NULL,
  created_source VARCHAR(16) NOT NULL,
  created_by BIGINT UNSIGNED NULL,
  cancelled_at DATETIME(3) NULL,
  cancel_reason VARCHAR(512) NOT NULL DEFAULT '',
  expired_at DATETIME(3) NULL,
  version INT UNSIGNED NOT NULL DEFAULT 1,
  created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
    ON UPDATE CURRENT_TIMESTAMP(3),
  CONSTRAINT fk_qipai_product_storage_tenant
    FOREIGN KEY (tenant_id) REFERENCES qipai_tenants(id),
  CONSTRAINT fk_qipai_product_storage_store
    FOREIGN KEY (tenant_id, store_id) REFERENCES qipai_stores(tenant_id, id),
  CONSTRAINT fk_qipai_product_storage_member
    FOREIGN KEY (tenant_id, member_id) REFERENCES qipai_users(tenant_id, id),
  CONSTRAINT fk_qipai_product_storage_source_order
    FOREIGN KEY (tenant_id, store_id, source_order_id)
    REFERENCES qipai_product_orders(tenant_id, store_id, id),
  CONSTRAINT fk_qipai_product_storage_created_by
    FOREIGN KEY (tenant_id, created_by) REFERENCES qipai_users(tenant_id, id),
  UNIQUE KEY uq_qipai_product_storage_scope_id (tenant_id, store_id, id),
  UNIQUE KEY uq_qipai_product_storage_no (tenant_id, storage_no),
  UNIQUE KEY uq_qipai_product_storage_client_request (tenant_id, client_request_id),
  UNIQUE KEY uq_qipai_product_storage_source_order (tenant_id, source_order_id),
  KEY idx_qipai_product_storage_store_status
    (tenant_id, store_id, status, expires_at, id),
  KEY idx_qipai_product_storage_member
    (tenant_id, member_id, created_at, id),
  CONSTRAINT chk_qipai_product_storage_source CHECK (
    (source_type = 'ORDER' AND source_order_id IS NOT NULL)
    OR (source_type = 'MANUAL' AND source_order_id IS NULL)
  ),
  CONSTRAINT chk_qipai_product_storage_status CHECK (
    status IN ('STORED', 'PARTIALLY_RETRIEVED', 'RETRIEVED', 'EXPIRED', 'CANCELLED')
  ),
  CONSTRAINT chk_qipai_product_storage_counts CHECK (
    item_count BETWEEN 1 AND 100
    AND total_quantity BETWEEN 1 AND 1000000000
    AND remaining_quantity <= total_quantity
  ),
  CONSTRAINT chk_qipai_product_storage_credential CHECK (
    claim_credential_hash REGEXP '^[0-9a-f]{64}$'
    AND claim_credential_version >= 1
  ),
  CONSTRAINT chk_qipai_product_storage_actor CHECK (
    created_source IN ('MEMBER', 'STAFF', 'SYSTEM')
    AND (created_source <> 'MEMBER' OR source_type = 'ORDER')
    AND (created_source <> 'STAFF' OR created_by IS NOT NULL)
  ),
  CONSTRAINT chk_qipai_product_storage_version CHECK (version >= 1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS qipai_product_storage_items (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  tenant_id BIGINT UNSIGNED NOT NULL,
  store_id BIGINT UNSIGNED NOT NULL,
  storage_id BIGINT UNSIGNED NOT NULL,
  line_no SMALLINT UNSIGNED NOT NULL,
  source_order_id BIGINT UNSIGNED NULL,
  source_order_item_id BIGINT UNSIGNED NULL,
  product_id BIGINT UNSIGNED NOT NULL,
  sku_id BIGINT UNSIGNED NOT NULL,
  product_code VARCHAR(64) NOT NULL,
  product_name VARCHAR(128) NOT NULL,
  sku_code VARCHAR(64) NOT NULL,
  sku_name VARCHAR(128) NOT NULL,
  unit_name VARCHAR(32) NOT NULL,
  attributes_snapshot JSON NULL,
  total_quantity BIGINT UNSIGNED NOT NULL,
  remaining_quantity BIGINT UNSIGNED NOT NULL,
  created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
    ON UPDATE CURRENT_TIMESTAMP(3),
  CONSTRAINT fk_qipai_product_storage_item_record
    FOREIGN KEY (tenant_id, store_id, storage_id)
    REFERENCES qipai_product_storage_records(tenant_id, store_id, id),
  CONSTRAINT fk_qipai_product_storage_item_source
    FOREIGN KEY (tenant_id, store_id, source_order_id, source_order_item_id)
    REFERENCES qipai_product_order_items(tenant_id, store_id, order_id, id),
  CONSTRAINT fk_qipai_product_storage_item_product
    FOREIGN KEY (tenant_id, product_id) REFERENCES qipai_products(tenant_id, id),
  CONSTRAINT fk_qipai_product_storage_item_sku
    FOREIGN KEY (tenant_id, sku_id) REFERENCES qipai_product_skus(tenant_id, id),
  UNIQUE KEY uq_qipai_product_storage_item_line
    (tenant_id, store_id, storage_id, line_no),
  UNIQUE KEY uq_qipai_product_storage_item_source
    (tenant_id, source_order_item_id),
  UNIQUE KEY uq_qipai_product_storage_item_scope_id
    (tenant_id, store_id, storage_id, id),
  KEY idx_qipai_product_storage_item_sku
    (tenant_id, store_id, sku_id, created_at),
  CONSTRAINT chk_qipai_product_storage_item_line CHECK (line_no BETWEEN 1 AND 100),
  CONSTRAINT chk_qipai_product_storage_item_source CHECK (
    (source_order_id IS NULL AND source_order_item_id IS NULL)
    OR (source_order_id IS NOT NULL AND source_order_item_id IS NOT NULL)
  ),
  CONSTRAINT chk_qipai_product_storage_item_quantity CHECK (
    total_quantity BETWEEN 1 AND 1000000000
    AND remaining_quantity <= total_quantity
  )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS qipai_product_storage_events (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  tenant_id BIGINT UNSIGNED NOT NULL,
  store_id BIGINT UNSIGNED NOT NULL,
  storage_id BIGINT UNSIGNED NOT NULL,
  request_id VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  request_fingerprint CHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  from_status VARCHAR(32) NULL,
  to_status VARCHAR(32) NOT NULL,
  action VARCHAR(32) NOT NULL,
  quantity BIGINT UNSIGNED NOT NULL DEFAULT 0,
  actor_type VARCHAR(16) NOT NULL,
  actor_id BIGINT UNSIGNED NULL,
  credential_version_after INT UNSIGNED NOT NULL,
  version_after INT UNSIGNED NOT NULL,
  reason VARCHAR(512) NOT NULL DEFAULT '',
  trace_id VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  metadata JSON NULL,
  created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  CONSTRAINT fk_qipai_product_storage_event_record
    FOREIGN KEY (tenant_id, store_id, storage_id)
    REFERENCES qipai_product_storage_records(tenant_id, store_id, id),
  CONSTRAINT fk_qipai_product_storage_event_actor
    FOREIGN KEY (tenant_id, actor_id) REFERENCES qipai_users(tenant_id, id),
  UNIQUE KEY uq_qipai_product_storage_event_request (tenant_id, request_id),
  UNIQUE KEY uq_qipai_product_storage_event_version
    (tenant_id, store_id, storage_id, version_after),
  UNIQUE KEY uq_qipai_product_storage_event_scope_id
    (tenant_id, store_id, storage_id, id),
  KEY idx_qipai_product_storage_event_history
    (tenant_id, store_id, storage_id, created_at, id),
  CONSTRAINT chk_qipai_product_storage_event_fingerprint CHECK (
    request_fingerprint REGEXP '^[0-9a-f]{64}$'
  ),
  CONSTRAINT chk_qipai_product_storage_event_from_status CHECK (
    from_status IS NULL OR from_status IN (
      'STORED', 'PARTIALLY_RETRIEVED', 'RETRIEVED', 'EXPIRED', 'CANCELLED'
    )
  ),
  CONSTRAINT chk_qipai_product_storage_event_to_status CHECK (
    to_status IN ('STORED', 'PARTIALLY_RETRIEVED', 'RETRIEVED', 'EXPIRED', 'CANCELLED')
  ),
  CONSTRAINT chk_qipai_product_storage_event_action CHECK (
    action IN ('STORED', 'RETRIEVED', 'CREDENTIAL_ROTATED', 'CANCELLED', 'EXPIRED')
  ),
  CONSTRAINT chk_qipai_product_storage_event_actor CHECK (
    actor_type IN ('MEMBER', 'STAFF', 'SYSTEM')
    AND (actor_type <> 'STAFF' OR actor_id IS NOT NULL)
  ),
  CONSTRAINT chk_qipai_product_storage_event_versions CHECK (
    credential_version_after >= 1 AND version_after >= 1
  )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS qipai_product_storage_event_items (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  tenant_id BIGINT UNSIGNED NOT NULL,
  store_id BIGINT UNSIGNED NOT NULL,
  storage_id BIGINT UNSIGNED NOT NULL,
  event_id BIGINT UNSIGNED NOT NULL,
  storage_item_id BIGINT UNSIGNED NOT NULL,
  line_no SMALLINT UNSIGNED NOT NULL,
  sku_id BIGINT UNSIGNED NOT NULL,
  quantity BIGINT UNSIGNED NOT NULL,
  remaining_after BIGINT UNSIGNED NOT NULL,
  created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  CONSTRAINT fk_qipai_product_storage_event_item_event
    FOREIGN KEY (tenant_id, store_id, storage_id, event_id)
    REFERENCES qipai_product_storage_events(tenant_id, store_id, storage_id, id),
  CONSTRAINT fk_qipai_product_storage_event_item_storage_item
    FOREIGN KEY (tenant_id, store_id, storage_id, storage_item_id)
    REFERENCES qipai_product_storage_items(tenant_id, store_id, storage_id, id),
  CONSTRAINT fk_qipai_product_storage_event_item_sku
    FOREIGN KEY (tenant_id, sku_id) REFERENCES qipai_product_skus(tenant_id, id),
  UNIQUE KEY uq_qipai_product_storage_event_item_line
    (tenant_id, store_id, storage_id, event_id, line_no),
  KEY idx_qipai_product_storage_event_item_storage
    (tenant_id, store_id, storage_id, storage_item_id, created_at),
  CONSTRAINT chk_qipai_product_storage_event_item_quantity CHECK (
    line_no BETWEEN 1 AND 100
    AND quantity BETWEEN 1 AND 1000000000
    AND remaining_after <= 1000000000
  )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

DROP TRIGGER IF EXISTS qipai_product_storage_events_no_update;
CREATE TRIGGER qipai_product_storage_events_no_update
BEFORE UPDATE ON qipai_product_storage_events
FOR EACH ROW SIGNAL SQLSTATE '45000'
  SET MESSAGE_TEXT = 'PRODUCT_STORAGE_EVENT_IMMUTABLE';

DROP TRIGGER IF EXISTS qipai_product_storage_events_no_delete;
CREATE TRIGGER qipai_product_storage_events_no_delete
BEFORE DELETE ON qipai_product_storage_events
FOR EACH ROW SIGNAL SQLSTATE '45000'
  SET MESSAGE_TEXT = 'PRODUCT_STORAGE_EVENT_IMMUTABLE';

DROP TRIGGER IF EXISTS qipai_product_storage_event_items_no_update;
CREATE TRIGGER qipai_product_storage_event_items_no_update
BEFORE UPDATE ON qipai_product_storage_event_items
FOR EACH ROW SIGNAL SQLSTATE '45000'
  SET MESSAGE_TEXT = 'PRODUCT_STORAGE_EVENT_ITEM_IMMUTABLE';

DROP TRIGGER IF EXISTS qipai_product_storage_event_items_no_delete;
CREATE TRIGGER qipai_product_storage_event_items_no_delete
BEFORE DELETE ON qipai_product_storage_event_items
FOR EACH ROW SIGNAL SQLSTATE '45000'
  SET MESSAGE_TEXT = 'PRODUCT_STORAGE_EVENT_ITEM_IMMUTABLE';

INSERT IGNORE INTO qipai_permissions (code, name, category) VALUES
  ('goods.storage.read', '查看商品寄存', 'goods'),
  ('goods.storage.manage', '处理商品寄存', 'goods');

INSERT IGNORE INTO qipai_role_permissions (tenant_id, role_id, permission_id)
SELECT r.tenant_id, r.id, p.id
FROM qipai_roles r
INNER JOIN qipai_permissions p
  ON p.code IN ('goods.storage.read', 'goods.storage.manage')
WHERE r.code IN ('STAFF', 'STORE_ADMIN', 'TENANT_ADMIN', 'PLATFORM_ADMIN')
  AND r.status = 'ACTIVE'
  AND r.deleted_at IS NULL;

INSERT IGNORE INTO qipai_schema_migrations (version, name)
VALUES ('2026081109', 'm09d3_product_storage');
