feat(M09-D2): 完成商品订单与库存占用生命周期

This commit is contained in:
Codex
2026-08-11 07:04:23 +08:00
parent 93af128002
commit 1a3ea7bdf0
30 changed files with 3773 additions and 91 deletions
@@ -0,0 +1,23 @@
DROP TRIGGER IF EXISTS qipai_product_refund_events_no_delete;
DROP TRIGGER IF EXISTS qipai_product_refund_events_no_update;
DROP TRIGGER IF EXISTS qipai_product_order_events_no_delete;
DROP TRIGGER IF EXISTS qipai_product_order_events_no_update;
DROP TRIGGER IF EXISTS qipai_product_order_items_no_delete;
DROP TRIGGER IF EXISTS qipai_product_order_items_no_update;
DELETE rp FROM qipai_role_permissions rp
INNER JOIN qipai_permissions p ON p.id = rp.permission_id
WHERE p.code IN ('goods.order.read', 'goods.order.manage');
DELETE FROM qipai_permissions
WHERE code IN ('goods.order.read', 'goods.order.manage');
DROP TABLE IF EXISTS qipai_product_refund_events;
DROP TABLE IF EXISTS qipai_product_payment_callbacks;
DROP TABLE IF EXISTS qipai_product_refunds;
DROP TABLE IF EXISTS qipai_product_payments;
DROP TABLE IF EXISTS qipai_product_order_events;
DROP TABLE IF EXISTS qipai_product_order_items;
DROP TABLE IF EXISTS qipai_product_orders;
DELETE FROM qipai_schema_migrations WHERE version = '2026081108';
@@ -0,0 +1,459 @@
CREATE TABLE IF NOT EXISTS qipai_product_orders (
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 NULL,
room_order_id BIGINT UNSIGNED NULL,
order_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,
inventory_business_id VARCHAR(128) COLLATE utf8mb4_bin NOT NULL,
inventory_request_id VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
fulfillment_mode VARCHAR(20) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'PENDING_PAYMENT',
item_count SMALLINT UNSIGNED NOT NULL,
total_quantity BIGINT UNSIGNED NOT NULL,
total_amount_cents BIGINT UNSIGNED NOT NULL,
paid_amount_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
refunded_amount_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
order_note VARCHAR(1024) NOT NULL DEFAULT '',
inventory_status VARCHAR(16) NOT NULL DEFAULT 'LOCKED',
expires_at DATETIME(3) NOT NULL,
paid_at DATETIME(3) NULL,
accepted_at DATETIME(3) NULL,
delivering_at DATETIME(3) NULL,
completed_at DATETIME(3) NULL,
cancelled_at DATETIME(3) NULL,
created_source VARCHAR(16) NOT NULL,
created_by BIGINT UNSIGNED 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),
deleted_at DATETIME(3) NULL,
CONSTRAINT fk_qipai_product_order_tenant
FOREIGN KEY (tenant_id) REFERENCES qipai_tenants(id),
CONSTRAINT fk_qipai_product_order_store
FOREIGN KEY (tenant_id, store_id) REFERENCES qipai_stores(tenant_id, id),
CONSTRAINT fk_qipai_product_order_inventory_request
FOREIGN KEY (tenant_id, inventory_request_id)
REFERENCES qipai_product_inventory_requests(tenant_id, request_id),
CONSTRAINT fk_qipai_product_order_created_by
FOREIGN KEY (tenant_id, created_by) REFERENCES qipai_users(tenant_id, id),
UNIQUE KEY uq_qipai_product_order_scope_id (tenant_id, store_id, id),
UNIQUE KEY uq_qipai_product_order_no (tenant_id, order_no),
UNIQUE KEY uq_qipai_product_order_client_request (tenant_id, client_request_id),
UNIQUE KEY uq_qipai_product_order_inventory_business
(tenant_id, inventory_business_id),
UNIQUE KEY uq_qipai_product_order_inventory_request
(tenant_id, inventory_request_id),
KEY idx_qipai_product_order_store_status
(tenant_id, store_id, status, created_at, id),
KEY idx_qipai_product_order_room
(tenant_id, store_id, room_order_id, created_at),
KEY idx_qipai_product_order_member
(tenant_id, member_id, created_at, id),
CONSTRAINT chk_qipai_product_order_fulfillment CHECK (
(fulfillment_mode = 'DELIVERY' AND room_order_id IS NOT NULL)
OR (fulfillment_mode = 'SELF_SERVICE' AND room_order_id IS NULL)
),
CONSTRAINT chk_qipai_product_order_status CHECK (
status IN (
'PENDING_PAYMENT', 'PAID', 'ACCEPTED', 'DELIVERING',
'READY_FOR_SELF_SERVICE', 'COMPLETED', 'CANCELLED',
'REFUNDING', 'REFUNDED', 'REFUND_FAILED'
)
),
CONSTRAINT chk_qipai_product_order_counts CHECK (
item_count BETWEEN 1 AND 100
AND total_quantity BETWEEN 1 AND 1000000000
),
CONSTRAINT chk_qipai_product_order_amounts CHECK (
total_amount_cents BETWEEN 1 AND 100000000000000
AND paid_amount_cents <= total_amount_cents
AND refunded_amount_cents <= paid_amount_cents
),
CONSTRAINT chk_qipai_product_order_inventory_status CHECK (
inventory_status IN ('LOCKED', 'DEDUCTED', 'RELEASED', 'RETURNED')
),
CONSTRAINT chk_qipai_product_order_source CHECK (
created_source IN ('MEMBER', 'STAFF', 'SYSTEM')
AND (created_source <> 'MEMBER' OR member_id IS NOT NULL)
AND (created_source <> 'STAFF' OR created_by IS NOT NULL)
),
CONSTRAINT chk_qipai_product_order_fingerprint CHECK (
request_fingerprint REGEXP '^[0-9a-f]{64}$'
),
CONSTRAINT chk_qipai_product_order_version CHECK (version >= 1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS qipai_product_order_items (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
store_id BIGINT UNSIGNED NOT NULL,
order_id BIGINT UNSIGNED NOT NULL,
line_no SMALLINT UNSIGNED NOT 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,
unit_price_cents INT UNSIGNED NOT NULL,
quantity BIGINT UNSIGNED NOT NULL,
subtotal_cents BIGINT UNSIGNED NOT NULL,
item_note VARCHAR(512) NOT NULL DEFAULT '',
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
CONSTRAINT fk_qipai_product_order_item_order
FOREIGN KEY (tenant_id, store_id, order_id)
REFERENCES qipai_product_orders(tenant_id, store_id, id),
CONSTRAINT fk_qipai_product_order_item_product
FOREIGN KEY (tenant_id, product_id) REFERENCES qipai_products(tenant_id, id),
CONSTRAINT fk_qipai_product_order_item_sku
FOREIGN KEY (tenant_id, sku_id) REFERENCES qipai_product_skus(tenant_id, id),
UNIQUE KEY uq_qipai_product_order_item_line
(tenant_id, store_id, order_id, line_no),
KEY idx_qipai_product_order_item_sku
(tenant_id, store_id, sku_id, created_at),
CONSTRAINT chk_qipai_product_order_item_line CHECK (line_no BETWEEN 1 AND 100),
CONSTRAINT chk_qipai_product_order_item_amount CHECK (
unit_price_cents BETWEEN 1 AND 100000000
AND quantity BETWEEN 1 AND 1000000000
AND subtotal_cents = unit_price_cents * quantity
AND subtotal_cents <= 100000000000000
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS qipai_product_order_events (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
store_id BIGINT UNSIGNED NOT NULL,
order_id BIGINT UNSIGNED NOT NULL,
request_id VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
from_status VARCHAR(32) NULL,
to_status VARCHAR(32) NOT NULL,
action VARCHAR(32) NOT NULL,
actor_type VARCHAR(16) NOT NULL,
actor_id BIGINT UNSIGNED NULL,
inventory_request_id VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin 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_order_event_order
FOREIGN KEY (tenant_id, store_id, order_id)
REFERENCES qipai_product_orders(tenant_id, store_id, id),
CONSTRAINT fk_qipai_product_order_event_actor
FOREIGN KEY (tenant_id, actor_id) REFERENCES qipai_users(tenant_id, id),
CONSTRAINT fk_qipai_product_order_event_inventory_request
FOREIGN KEY (tenant_id, inventory_request_id)
REFERENCES qipai_product_inventory_requests(tenant_id, request_id),
UNIQUE KEY uq_qipai_product_order_event_request (tenant_id, request_id),
UNIQUE KEY uq_qipai_product_order_event_version
(tenant_id, store_id, order_id, version_after),
KEY idx_qipai_product_order_event_history
(tenant_id, store_id, order_id, created_at, id),
CONSTRAINT chk_qipai_product_order_event_from_status CHECK (
from_status IS NULL OR from_status IN (
'PENDING_PAYMENT', 'PAID', 'ACCEPTED', 'DELIVERING',
'READY_FOR_SELF_SERVICE', 'COMPLETED', 'CANCELLED',
'REFUNDING', 'REFUNDED', 'REFUND_FAILED'
)
),
CONSTRAINT chk_qipai_product_order_event_to_status CHECK (
to_status IN (
'PENDING_PAYMENT', 'PAID', 'ACCEPTED', 'DELIVERING',
'READY_FOR_SELF_SERVICE', 'COMPLETED', 'CANCELLED',
'REFUNDING', 'REFUNDED', 'REFUND_FAILED'
)
),
CONSTRAINT chk_qipai_product_order_event_actor CHECK (
actor_type IN ('MEMBER', 'STAFF', 'CALLBACK', 'SYSTEM')
AND (actor_type <> 'STAFF' OR actor_id IS NOT NULL)
),
CONSTRAINT chk_qipai_product_order_event_version CHECK (version_after >= 1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS qipai_product_payments (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
store_id BIGINT UNSIGNED NOT NULL,
order_id BIGINT UNSIGNED NOT NULL,
payment_config_id BIGINT UNSIGNED NULL,
payment_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,
provider VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
channel VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'PENDING',
amount_cents BIGINT UNSIGNED NOT NULL,
refunded_amount_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
currency CHAR(3) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT 'CNY',
provider_payment_id VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NULL,
provider_prepay_id VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NULL,
expires_at DATETIME(3) NOT NULL,
paid_at DATETIME(3) NULL,
failed_at DATETIME(3) NULL,
failure_code VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
raw_response JSON 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),
active_order_id BIGINT UNSIGNED
GENERATED ALWAYS AS (
CASE WHEN status IN ('PENDING', 'PROCESSING') THEN order_id ELSE NULL END
) STORED,
CONSTRAINT fk_qipai_product_payment_order
FOREIGN KEY (tenant_id, store_id, order_id)
REFERENCES qipai_product_orders(tenant_id, store_id, id),
CONSTRAINT fk_qipai_product_payment_config
FOREIGN KEY (payment_config_id) REFERENCES qipai_payment_configs(id),
UNIQUE KEY uq_qipai_product_payment_scope_id
(tenant_id, store_id, order_id, id),
UNIQUE KEY uq_qipai_product_payment_no (tenant_id, payment_no),
UNIQUE KEY uq_qipai_product_payment_client_request
(tenant_id, client_request_id),
UNIQUE KEY uq_qipai_product_payment_provider_id
(tenant_id, provider, provider_payment_id),
UNIQUE KEY uq_qipai_product_payment_active_order
(tenant_id, store_id, active_order_id),
KEY idx_qipai_product_payment_status
(tenant_id, store_id, status, created_at, id),
CONSTRAINT chk_qipai_product_payment_status CHECK (
status IN (
'PENDING', 'PROCESSING', 'SUCCEEDED', 'FAILED', 'CLOSED',
'COMPENSATION_REQUIRED', 'REFUNDED'
)
),
CONSTRAINT chk_qipai_product_payment_amount CHECK (
amount_cents BETWEEN 1 AND 100000000000000
AND refunded_amount_cents <= amount_cents
),
CONSTRAINT chk_qipai_product_payment_currency CHECK (currency = 'CNY'),
CONSTRAINT chk_qipai_product_payment_fingerprint CHECK (
request_fingerprint REGEXP '^[0-9a-f]{64}$'
),
CONSTRAINT chk_qipai_product_payment_version CHECK (version >= 1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS qipai_product_refunds (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
store_id BIGINT UNSIGNED NOT NULL,
order_id BIGINT UNSIGNED NOT NULL,
payment_id BIGINT UNSIGNED NOT NULL,
refund_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,
provider VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
provider_refund_id VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NULL,
status VARCHAR(16) NOT NULL DEFAULT 'PENDING',
amount_cents BIGINT UNSIGNED NOT NULL,
reason VARCHAR(512) NOT NULL,
inventory_disposition VARCHAR(16) NOT NULL,
inventory_status VARCHAR(16) NOT NULL,
inventory_request_id VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NULL,
requested_by BIGINT UNSIGNED NULL,
completed_at DATETIME(3) NULL,
failure_code VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
raw_response JSON 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),
active_payment_id BIGINT UNSIGNED
GENERATED ALWAYS AS (
CASE WHEN status IN ('PENDING', 'PROCESSING') THEN payment_id ELSE NULL END
) STORED,
CONSTRAINT fk_qipai_product_refund_payment
FOREIGN KEY (tenant_id, store_id, order_id, payment_id)
REFERENCES qipai_product_payments(tenant_id, store_id, order_id, id),
CONSTRAINT fk_qipai_product_refund_inventory_request
FOREIGN KEY (tenant_id, inventory_request_id)
REFERENCES qipai_product_inventory_requests(tenant_id, request_id),
CONSTRAINT fk_qipai_product_refund_requested_by
FOREIGN KEY (tenant_id, requested_by) REFERENCES qipai_users(tenant_id, id),
UNIQUE KEY uq_qipai_product_refund_scope_id
(tenant_id, store_id, order_id, payment_id, id),
UNIQUE KEY uq_qipai_product_refund_no (tenant_id, refund_no),
UNIQUE KEY uq_qipai_product_refund_client_request
(tenant_id, client_request_id),
UNIQUE KEY uq_qipai_product_refund_provider_id
(tenant_id, provider, provider_refund_id),
UNIQUE KEY uq_qipai_product_refund_inventory_request
(tenant_id, inventory_request_id),
UNIQUE KEY uq_qipai_product_refund_active_payment
(tenant_id, store_id, order_id, active_payment_id),
KEY idx_qipai_product_refund_status
(tenant_id, store_id, status, created_at, id),
CONSTRAINT chk_qipai_product_refund_status CHECK (
status IN ('PENDING', 'PROCESSING', 'SUCCEEDED', 'FAILED')
),
CONSTRAINT chk_qipai_product_refund_amount CHECK (
amount_cents BETWEEN 1 AND 100000000000000
),
CONSTRAINT chk_qipai_product_refund_inventory CHECK (
(inventory_disposition = 'RESTOCK'
AND inventory_status IN ('PENDING', 'SUCCEEDED', 'FAILED')
AND (inventory_status = 'PENDING' OR inventory_request_id IS NOT NULL))
OR (inventory_disposition = 'NO_RESTOCK'
AND inventory_status = 'NOT_REQUIRED'
AND inventory_request_id IS NULL)
),
CONSTRAINT chk_qipai_product_refund_reason CHECK (CHAR_LENGTH(reason) BETWEEN 1 AND 512),
CONSTRAINT chk_qipai_product_refund_fingerprint CHECK (
request_fingerprint REGEXP '^[0-9a-f]{64}$'
),
CONSTRAINT chk_qipai_product_refund_version CHECK (version >= 1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS qipai_product_payment_callbacks (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
store_id BIGINT UNSIGNED NOT NULL,
order_id BIGINT UNSIGNED NOT NULL,
payment_id BIGINT UNSIGNED NOT NULL,
refund_id BIGINT UNSIGNED NULL,
provider VARCHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
callback_id VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
callback_type VARCHAR(16) NOT NULL,
payload_fingerprint CHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
verified TINYINT(1) NOT NULL DEFAULT 0,
processing_status VARCHAR(16) NOT NULL DEFAULT 'RECEIVED',
payload JSON NOT NULL,
error_code VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
received_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
processed_at DATETIME(3) NULL,
CONSTRAINT fk_qipai_product_payment_callback_payment
FOREIGN KEY (tenant_id, store_id, order_id, payment_id)
REFERENCES qipai_product_payments(tenant_id, store_id, order_id, id),
CONSTRAINT fk_qipai_product_payment_callback_refund
FOREIGN KEY (tenant_id, store_id, order_id, payment_id, refund_id)
REFERENCES qipai_product_refunds(tenant_id, store_id, order_id, payment_id, id),
UNIQUE KEY uq_qipai_product_payment_callback_provider
(tenant_id, provider, callback_id),
KEY idx_qipai_product_payment_callback_status
(tenant_id, store_id, processing_status, received_at, id),
CONSTRAINT chk_qipai_product_payment_callback_type CHECK (
(callback_type = 'PAYMENT' AND refund_id IS NULL)
OR (callback_type = 'REFUND' AND refund_id IS NOT NULL)
),
CONSTRAINT chk_qipai_product_payment_callback_verified CHECK (verified IN (0, 1)),
CONSTRAINT chk_qipai_product_payment_callback_status CHECK (
processing_status IN ('RECEIVED', 'PROCESSED', 'FAILED', 'IGNORED')
),
CONSTRAINT chk_qipai_product_payment_callback_fingerprint CHECK (
payload_fingerprint REGEXP '^[0-9a-f]{64}$'
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS qipai_product_refund_events (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT UNSIGNED NOT NULL,
store_id BIGINT UNSIGNED NOT NULL,
order_id BIGINT UNSIGNED NOT NULL,
payment_id BIGINT UNSIGNED NOT NULL,
refund_id BIGINT UNSIGNED NOT NULL,
request_id VARCHAR(128) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
from_status VARCHAR(16) NULL,
to_status VARCHAR(16) NOT NULL,
action VARCHAR(32) NOT NULL,
inventory_status VARCHAR(16) NOT NULL,
inventory_request_id VARCHAR(64) CHARACTER SET ascii COLLATE ascii_bin NULL,
actor_type VARCHAR(16) NOT NULL,
actor_id BIGINT UNSIGNED 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_refund_event_refund
FOREIGN KEY (tenant_id, store_id, order_id, payment_id, refund_id)
REFERENCES qipai_product_refunds(tenant_id, store_id, order_id, payment_id, id),
CONSTRAINT fk_qipai_product_refund_event_inventory_request
FOREIGN KEY (tenant_id, inventory_request_id)
REFERENCES qipai_product_inventory_requests(tenant_id, request_id),
CONSTRAINT fk_qipai_product_refund_event_actor
FOREIGN KEY (tenant_id, actor_id) REFERENCES qipai_users(tenant_id, id),
UNIQUE KEY uq_qipai_product_refund_event_request (tenant_id, request_id),
UNIQUE KEY uq_qipai_product_refund_event_version
(tenant_id, store_id, refund_id, version_after),
KEY idx_qipai_product_refund_event_history
(tenant_id, store_id, refund_id, created_at, id),
CONSTRAINT chk_qipai_product_refund_event_from_status CHECK (
from_status IS NULL OR from_status IN ('PENDING', 'PROCESSING', 'SUCCEEDED', 'FAILED')
),
CONSTRAINT chk_qipai_product_refund_event_to_status CHECK (
to_status IN ('PENDING', 'PROCESSING', 'SUCCEEDED', 'FAILED')
),
CONSTRAINT chk_qipai_product_refund_event_inventory_status CHECK (
inventory_status IN ('PENDING', 'SUCCEEDED', 'FAILED', 'NOT_REQUIRED')
),
CONSTRAINT chk_qipai_product_refund_event_actor CHECK (
actor_type IN ('STAFF', 'CALLBACK', 'SYSTEM')
AND (actor_type <> 'STAFF' OR actor_id IS NOT NULL)
),
CONSTRAINT chk_qipai_product_refund_event_version CHECK (version_after >= 1)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TRIGGER IF EXISTS qipai_product_order_events_no_update;
CREATE TRIGGER qipai_product_order_events_no_update
BEFORE UPDATE ON qipai_product_order_events
FOR EACH ROW SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'PRODUCT_ORDER_EVENT_IMMUTABLE';
DROP TRIGGER IF EXISTS qipai_product_order_events_no_delete;
CREATE TRIGGER qipai_product_order_events_no_delete
BEFORE DELETE ON qipai_product_order_events
FOR EACH ROW SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'PRODUCT_ORDER_EVENT_IMMUTABLE';
DROP TRIGGER IF EXISTS qipai_product_order_items_no_update;
CREATE TRIGGER qipai_product_order_items_no_update
BEFORE UPDATE ON qipai_product_order_items
FOR EACH ROW SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'PRODUCT_ORDER_ITEM_IMMUTABLE';
DROP TRIGGER IF EXISTS qipai_product_order_items_no_delete;
CREATE TRIGGER qipai_product_order_items_no_delete
BEFORE DELETE ON qipai_product_order_items
FOR EACH ROW SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'PRODUCT_ORDER_ITEM_IMMUTABLE';
DROP TRIGGER IF EXISTS qipai_product_refund_events_no_update;
CREATE TRIGGER qipai_product_refund_events_no_update
BEFORE UPDATE ON qipai_product_refund_events
FOR EACH ROW SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'PRODUCT_REFUND_EVENT_IMMUTABLE';
DROP TRIGGER IF EXISTS qipai_product_refund_events_no_delete;
CREATE TRIGGER qipai_product_refund_events_no_delete
BEFORE DELETE ON qipai_product_refund_events
FOR EACH ROW SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'PRODUCT_REFUND_EVENT_IMMUTABLE';
INSERT IGNORE INTO qipai_permissions (code, name, category) VALUES
('goods.order.read', '查看商品订单', 'goods'),
('goods.order.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.order.read', 'goods.order.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 ('2026081108', 'm09d2_product_order_payment_inventory');
@@ -0,0 +1,197 @@
SELECT table_name
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name IN (
'qipai_product_orders',
'qipai_product_order_items',
'qipai_product_order_events',
'qipai_product_payments',
'qipai_product_refunds',
'qipai_product_payment_callbacks',
'qipai_product_refund_events'
)
ORDER BY table_name;
SELECT table_name, column_name, data_type, is_nullable, collation_name
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND (
(table_name = 'qipai_product_orders'
AND column_name IN (
'order_no', 'client_request_id', 'request_fingerprint', 'inventory_business_id',
'inventory_request_id', 'fulfillment_mode', 'status',
'total_amount_cents', 'inventory_status', 'version', 'deleted_at'
))
OR (table_name = 'qipai_product_order_items'
AND column_name IN (
'product_code', 'product_name', 'sku_code', 'sku_name',
'unit_price_cents', 'quantity', 'subtotal_cents', 'item_note'
))
OR (table_name = 'qipai_product_order_events'
AND column_name IN ('request_id', 'from_status', 'to_status', 'version_after'))
OR (table_name = 'qipai_product_payments'
AND column_name IN (
'payment_no', 'client_request_id', 'request_fingerprint', 'provider', 'provider_payment_id',
'status', 'amount_cents', 'refunded_amount_cents', 'version', 'active_order_id'
))
OR (table_name = 'qipai_product_refunds'
AND column_name IN (
'refund_no', 'client_request_id', 'request_fingerprint', 'provider_refund_id', 'status',
'amount_cents', 'inventory_disposition', 'inventory_status',
'inventory_request_id', 'version', 'active_payment_id'
))
OR (table_name = 'qipai_product_payment_callbacks'
AND column_name IN (
'callback_id', 'callback_type', 'payload_fingerprint',
'processing_status', 'refund_id'
))
OR (table_name = 'qipai_product_refund_events'
AND column_name IN (
'request_id', 'from_status', 'to_status', 'inventory_status', 'version_after'
))
)
ORDER BY table_name, ordinal_position;
SELECT table_name, index_name
FROM information_schema.statistics
WHERE table_schema = DATABASE()
AND index_name IN (
'uq_qipai_product_order_scope_id',
'uq_qipai_product_order_no',
'uq_qipai_product_order_client_request',
'uq_qipai_product_order_inventory_business',
'uq_qipai_product_order_inventory_request',
'uq_qipai_product_order_item_line',
'uq_qipai_product_order_event_request',
'uq_qipai_product_order_event_version',
'uq_qipai_product_payment_scope_id',
'uq_qipai_product_payment_no',
'uq_qipai_product_payment_client_request',
'uq_qipai_product_payment_provider_id',
'uq_qipai_product_payment_active_order',
'uq_qipai_product_refund_scope_id',
'uq_qipai_product_refund_no',
'uq_qipai_product_refund_client_request',
'uq_qipai_product_refund_provider_id',
'uq_qipai_product_refund_inventory_request',
'uq_qipai_product_refund_active_payment',
'uq_qipai_product_payment_callback_provider',
'uq_qipai_product_refund_event_request',
'uq_qipai_product_refund_event_version'
)
GROUP BY table_name, index_name
ORDER BY table_name, index_name;
SELECT constraint_name
FROM information_schema.table_constraints
WHERE constraint_schema = DATABASE()
AND constraint_type = 'FOREIGN KEY'
AND constraint_name IN (
'fk_qipai_product_order_store',
'fk_qipai_product_order_inventory_request',
'fk_qipai_product_order_created_by',
'fk_qipai_product_order_item_order',
'fk_qipai_product_order_item_product',
'fk_qipai_product_order_item_sku',
'fk_qipai_product_order_event_order',
'fk_qipai_product_order_event_actor',
'fk_qipai_product_order_event_inventory_request',
'fk_qipai_product_payment_order',
'fk_qipai_product_refund_payment',
'fk_qipai_product_refund_inventory_request',
'fk_qipai_product_refund_requested_by',
'fk_qipai_product_payment_callback_payment',
'fk_qipai_product_payment_callback_refund',
'fk_qipai_product_refund_event_refund',
'fk_qipai_product_refund_event_inventory_request',
'fk_qipai_product_refund_event_actor'
)
ORDER BY constraint_name;
SELECT constraint_name
FROM information_schema.table_constraints
WHERE constraint_schema = DATABASE()
AND constraint_type = 'CHECK'
AND constraint_name IN (
'chk_qipai_product_order_fulfillment',
'chk_qipai_product_order_status',
'chk_qipai_product_order_counts',
'chk_qipai_product_order_amounts',
'chk_qipai_product_order_inventory_status',
'chk_qipai_product_order_source',
'chk_qipai_product_order_fingerprint',
'chk_qipai_product_order_version',
'chk_qipai_product_order_item_line',
'chk_qipai_product_order_item_amount',
'chk_qipai_product_order_event_from_status',
'chk_qipai_product_order_event_to_status',
'chk_qipai_product_order_event_actor',
'chk_qipai_product_order_event_version',
'chk_qipai_product_payment_status',
'chk_qipai_product_payment_amount',
'chk_qipai_product_payment_currency',
'chk_qipai_product_payment_fingerprint',
'chk_qipai_product_payment_version',
'chk_qipai_product_refund_status',
'chk_qipai_product_refund_amount',
'chk_qipai_product_refund_inventory',
'chk_qipai_product_refund_reason',
'chk_qipai_product_refund_fingerprint',
'chk_qipai_product_refund_version',
'chk_qipai_product_payment_callback_type',
'chk_qipai_product_payment_callback_verified',
'chk_qipai_product_payment_callback_status',
'chk_qipai_product_payment_callback_fingerprint',
'chk_qipai_product_refund_event_from_status',
'chk_qipai_product_refund_event_to_status',
'chk_qipai_product_refund_event_inventory_status',
'chk_qipai_product_refund_event_actor',
'chk_qipai_product_refund_event_version'
)
ORDER BY constraint_name;
SELECT trigger_name, event_manipulation
FROM information_schema.triggers
WHERE trigger_schema = DATABASE()
AND trigger_name IN (
'qipai_product_order_events_no_update',
'qipai_product_order_events_no_delete',
'qipai_product_order_items_no_update',
'qipai_product_order_items_no_delete',
'qipai_product_refund_events_no_update',
'qipai_product_refund_events_no_delete'
)
ORDER BY trigger_name;
SELECT code
FROM qipai_permissions
WHERE code IN ('goods.order.read', 'goods.order.manage')
ORDER BY code;
SELECT COUNT(*) AS managed_role_count
FROM qipai_roles r
WHERE r.code IN ('STAFF', 'STORE_ADMIN', 'TENANT_ADMIN', 'PLATFORM_ADMIN')
AND r.status = 'ACTIVE'
AND r.deleted_at IS NULL
HAVING COUNT(*) = (
SELECT COUNT(*)
FROM (
SELECT granted_role.tenant_id, granted_role.id
FROM qipai_roles granted_role
INNER JOIN qipai_role_permissions rp
ON rp.tenant_id = granted_role.tenant_id AND rp.role_id = granted_role.id
INNER JOIN qipai_permissions p ON p.id = rp.permission_id
WHERE granted_role.code IN ('STAFF', 'STORE_ADMIN', 'TENANT_ADMIN', 'PLATFORM_ADMIN')
AND granted_role.status = 'ACTIVE'
AND granted_role.deleted_at IS NULL
AND p.code IN ('goods.order.read', 'goods.order.manage')
GROUP BY granted_role.tenant_id, granted_role.id
HAVING COUNT(DISTINCT p.code) = 2
) fully_granted_goods_order_roles
);
SELECT version, name
FROM qipai_schema_migrations
WHERE version = '2026081108'
AND name = 'm09d2_product_order_payment_inventory'
AND checksum REGEXP '^[0-9a-f]{64}$';