diff --git a/README.md b/README.md index cea820a..862f78b 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ - 主站前台地址:`http://localhost:5189` - 主站后台地址:`http://localhost:5189/admin/login`(超级管理员默认 `admin` / `admin123456`) -- 分销管理后台:`http://localhost:5190`(分销操作员 `test_fx1`) -- 分销专属前台:`http://localhost:5191`(独立端口隔离,默认直连 `test_1` 分销站) +- 分销管理后台:`http://localhost:5190`(在主站后台“分销站点”开通站点并配置分销操作员登录) +- 分销专属前台:`http://localhost:5191`(独立端口隔离,支持 Host 域名与 X-Distribution-Site 头识别站点) 如需拆分调试,可手动执行: diff --git a/backend/internal/modules/distribution/settlement_guard_test.go b/backend/internal/modules/distribution/settlement_guard_test.go index 238f43d..a9b82f0 100644 --- a/backend/internal/modules/distribution/settlement_guard_test.go +++ b/backend/internal/modules/distribution/settlement_guard_test.go @@ -61,7 +61,7 @@ func mustCreateDraftBatch(t *testing.T, db *gorm.DB, siteID uint64, batchNo stri return batch } -// TestSettlementItemOrderPartyUniqueKey 断言 000070 唯一键生效: +// TestSettlementItemOrderPartyUniqueKey 断言 (order_id, party_id) 唯一键生效: // 同一 (order_id, party_id) 插入第二条结算明细必须失败。 func TestSettlementItemOrderPartyUniqueKey(t *testing.T) { db := distributionTestDB(t) diff --git a/backend/migrations/000065_distribution_init.sql b/backend/migrations/000065_distribution_init.sql index 14cd71c..3ade800 100644 --- a/backend/migrations/000065_distribution_init.sql +++ b/backend/migrations/000065_distribution_init.sql @@ -12,6 +12,7 @@ CREATE TABLE distribution_sites ( domain VARCHAR(255) NOT NULL, status VARCHAR(16) NOT NULL DEFAULT 'active', display_account_enabled TINYINT(1) NOT NULL DEFAULT 1, + settlement_hold_days INT NOT NULL DEFAULT 7 COMMENT '订单完成后结算释放冷却天数,0=立即释放', logo_url VARCHAR(512) NOT NULL DEFAULT '', favicon_url VARCHAR(512) NOT NULL DEFAULT '', banners_json TEXT NULL, @@ -111,12 +112,15 @@ CREATE TABLE distribution_order_financials ( split_base_amount_cent BIGINT NOT NULL DEFAULT 0, rounding_adjustment_cent BIGINT NOT NULL DEFAULT 0, settlement_status VARCHAR(24) NOT NULL DEFAULT 'pending', + release_at DATETIME(6) NULL COMMENT '预计释放时间(订单完成时间+站点冷却期)', + released_at DATETIME(6) NULL COMMENT '实际释放入账时间', settled_at DATETIME NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_distribution_order_financial_order (order_id), KEY idx_distribution_order_financial_site_status (site_id, settlement_status), + KEY idx_distribution_financial_release (settlement_status, release_at), CONSTRAINT fk_distribution_order_financial_order FOREIGN KEY (order_id) REFERENCES rental_orders (id), CONSTRAINT fk_distribution_order_financial_site FOREIGN KEY (site_id) REFERENCES distribution_sites (id), CONSTRAINT fk_distribution_order_financial_rule FOREIGN KEY (rule_version_id) REFERENCES distribution_rule_versions (id) @@ -172,6 +176,7 @@ CREATE TABLE distribution_settlement_items ( created_at DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_distribution_settlement_item (batch_id, order_id, party_id), + UNIQUE KEY uk_distribution_settlement_item_party (order_id, party_id), KEY idx_distribution_settlement_items_order (order_id), CONSTRAINT fk_distribution_settlement_items_batch FOREIGN KEY (batch_id) REFERENCES distribution_settlement_batches (id), CONSTRAINT fk_distribution_settlement_items_order FOREIGN KEY (order_id) REFERENCES rental_orders (id), @@ -286,7 +291,8 @@ INSERT INTO distribution_rule_versions ( ) VALUES (NULL, 'two_party_distributor_receive', 1, 'active', 0, 40, 35, 0, 0, 0, 25, 'percent', 50, 0, 100, NOW(), NOW()), (NULL, 'two_party_platform_receive', 1, 'active', 35, 0, 0, 40, 0, 0, 25, 'percent', 50, 0, 100, NOW(), NOW()), - (NULL, 'three_party', 1, 'active', 0, 0, 0, 0, 35, 40, 25, 'percent', 50, 0, 100, NOW(), NOW()); + (NULL, 'three_party', 1, 'active', 0, 0, 0, 0, 35, 40, 25, 'percent', 50, 0, 100, NOW(), NOW()), + (NULL, 'site_self_sell', 1, 'active', 0, 0, 0, 75, 0, 0, 25, 'percent', 50, 0, 100, NOW(), NOW()); -- +goose Down diff --git a/backend/migrations/000068_seed_test_distribution_site.sql b/backend/migrations/000068_seed_test_distribution_site.sql deleted file mode 100644 index 0338dfe..0000000 --- a/backend/migrations/000068_seed_test_distribution_site.sql +++ /dev/null @@ -1,101 +0,0 @@ --- +goose Up - --- 初始化本地测试分销站点 test_1、分销管理员 test_fx1 (密码 admin123456) 以及对应的金库钱包与商品货架 - --- 1. 插入或激活测试分销站点 test_1 -INSERT INTO distribution_sites ( - site_code, site_name, domain, status, display_account_enabled, tax_enabled, tax_rate_bps, created_at, updated_at -) VALUES ( - 'test_1', '测试分销商1', 'test_1.localhost', 'active', 1, 0, 100, NOW(), NOW() -) ON DUPLICATE KEY UPDATE - site_name = VALUES(site_name), - domain = VALUES(domain), - status = 'active', - display_account_enabled = 1, - updated_at = NOW(); - --- 2. 插入或激活测试分销管理员 test_fx1 (初始密码: admin123456) -INSERT INTO admin_users ( - username, password_hash, nickname, status, created_at, updated_at -) VALUES ( - 'test_fx1', - '$2a$10$zj3KqZJArsYVKVjA7uHDbeP7sWML20xjxRfb3a100xBx.f20Gu.8O', - '分销操作员1', - 'active', - NOW(), - NOW() -) ON DUPLICATE KEY UPDATE - status = 'active', - password_hash = VALUES(password_hash), - updated_at = NOW(); - --- 3. 关联分销商成员 (operator 角色) -INSERT INTO distribution_site_members ( - site_id, admin_user_id, member_type, status, data_scope, created_at, updated_at -) -SELECT s.id, u.id, 'operator', 'active', 'site', NOW(), NOW() -FROM distribution_sites s -JOIN admin_users u ON u.username = 'test_fx1' -WHERE s.site_code = 'test_1' -ON DUPLICATE KEY UPDATE - status = 'active', - member_type = 'operator', - updated_at = NOW(); - --- 4. 初始化 test_1 分销站金库钱包账户 (user_id = 0, distribution_site_id = site.id) -INSERT INTO wallet_accounts ( - user_id, distribution_site_id, available_balance_cent, frozen_balance_cent, status, created_at, updated_at -) -SELECT 0, s.id, 0, 0, 'active', NOW(), NOW() -FROM distribution_sites s -WHERE s.site_code = 'test_1' -ON DUPLICATE KEY UPDATE status = 'active'; - --- 5. 初始化 test_1 分销站点专属规则版本 (两方-分销卖出,收号 35%、卖出 40%、服务费 25%) -INSERT INTO distribution_rule_versions ( - site_id, mode, version, status, - platform_receive_ratio, distributor_sell_ratio, service_fee_value, service_fee_unit, payment_fee_bps, - effective_from, created_at -) -SELECT s.id, 'two_party_platform_receive', 1, 'active', - 35, 40, 25, 'percent', 50, - NOW(), NOW() -FROM distribution_sites s -WHERE s.site_code = 'test_1' -ON DUPLICATE KEY UPDATE - status = 'active', - platform_receive_ratio = 35, - distributor_sell_ratio = 40, - service_fee_value = 25; - --- 6. 默认将当前已发布的合规商品直接铺入 test_1 货架 -INSERT IGNORE INTO distribution_site_accounts ( - site_id, account_id, listing_id, display_status, markup_amount_cent, shelf_ratio_delta, created_at, updated_at -) -SELECT s.id, l.account_id, l.id, 'enabled', 0, 0, NOW(), NOW() -FROM distribution_sites s -CROSS JOIN rental_listings l -WHERE s.site_code = 'test_1' - AND l.status = 'published' - AND l.review_status = 'approved'; - --- +goose Down - -DELETE dsa FROM distribution_site_accounts dsa -JOIN distribution_sites s ON s.id = dsa.site_id -WHERE s.site_code = 'test_1'; - -DELETE drv FROM distribution_rule_versions drv -JOIN distribution_sites s ON s.id = drv.site_id -WHERE s.site_code = 'test_1'; - -DELETE wa FROM wallet_accounts wa -JOIN distribution_sites s ON s.id = wa.distribution_site_id -WHERE s.site_code = 'test_1' AND wa.user_id = 0; - -DELETE dsm FROM distribution_site_members dsm -JOIN distribution_sites s ON s.id = dsm.site_id -WHERE s.site_code = 'test_1'; - -DELETE FROM admin_users WHERE username = 'test_fx1'; -DELETE FROM distribution_sites WHERE site_code = 'test_1'; diff --git a/backend/migrations/000069_distribution_settlement_release.sql b/backend/migrations/000069_distribution_settlement_release.sql deleted file mode 100644 index be5120b..0000000 --- a/backend/migrations/000069_distribution_settlement_release.sql +++ /dev/null @@ -1,30 +0,0 @@ --- +goose Up - --- 分销站点结算冷却期:订单完成后 N 天自动释放到可提现余额,0 表示立即释放。 -ALTER TABLE distribution_sites - ADD COLUMN settlement_hold_days INT NOT NULL DEFAULT 7 COMMENT '订单完成后结算释放冷却天数,0=立即释放'; - --- 财务快照记录释放时间:release_at 为订单完成时间 + 站点冷却期,released_at 为实际入账时间。 -ALTER TABLE distribution_order_financials - ADD COLUMN release_at DATETIME(6) NULL COMMENT '预计释放时间(订单完成时间+站点冷却期)', - ADD COLUMN released_at DATETIME(6) NULL COMMENT '实际释放入账时间', - ADD KEY idx_distribution_financial_release (settlement_status, release_at); - --- 回填历史已完成订单的释放时间,使其纳入自动释放。 -UPDATE distribution_order_financials f -JOIN rental_orders ro ON ro.id = f.order_id -JOIN distribution_sites s ON s.id = f.site_id -SET f.release_at = DATE_ADD(COALESCE(ro.settled_at, f.created_at), INTERVAL s.settlement_hold_days DAY) -WHERE f.settlement_status = 'pending' - AND f.release_at IS NULL - AND ro.status = 'completed'; - --- +goose Down - -ALTER TABLE distribution_order_financials - DROP KEY idx_distribution_financial_release, - DROP COLUMN released_at, - DROP COLUMN release_at; - -ALTER TABLE distribution_sites - DROP COLUMN settlement_hold_days; diff --git a/backend/migrations/000070_distribution_settlement_item_party_unique.sql b/backend/migrations/000070_distribution_settlement_item_party_unique.sql deleted file mode 100644 index c1f66ae..0000000 --- a/backend/migrations/000070_distribution_settlement_item_party_unique.sql +++ /dev/null @@ -1,12 +0,0 @@ --- +goose Up - --- 同一订单同一参与方只允许一条结算明细,防止自动释放与手工批次并发重复入账。 --- 自动释放按 NOT EXISTS 选参与方,并发下两条路径可能同时落明细并各自入账, --- 由该唯一键兜底,写明细失败重复错误时跳过该参与方。 -ALTER TABLE distribution_settlement_items - ADD UNIQUE KEY uk_distribution_settlement_item_party (order_id, party_id); - --- +goose Down - -ALTER TABLE distribution_settlement_items - DROP KEY uk_distribution_settlement_item_party; diff --git a/backend/migrations/000071_distribution_release_at_backfill_closed.sql b/backend/migrations/000071_distribution_release_at_backfill_closed.sql deleted file mode 100644 index aa150ae..0000000 --- a/backend/migrations/000071_distribution_release_at_backfill_closed.sql +++ /dev/null @@ -1,17 +0,0 @@ --- +goose Up - --- 000069 回填历史释放时间时只覆盖了 completed,遗漏了同样是可释放终态的 closed 订单, --- 导致不退款关单(AdminRejectRefund close、AdminSeal 等)的订单 release_at 永久为 NULL。 --- 这里补齐 closed,使这些订单重新纳入自动释放扫描。 -UPDATE distribution_order_financials f -JOIN rental_orders ro ON ro.id = f.order_id -JOIN distribution_sites s ON s.id = f.site_id -SET f.release_at = DATE_ADD(COALESCE(ro.settled_at, f.created_at), INTERVAL s.settlement_hold_days DAY) -WHERE f.settlement_status = 'pending' - AND f.release_at IS NULL - AND ro.status IN ('completed', 'closed'); - --- +goose Down - --- 回填不可逆:无法区分补写的 release_at 与原有值,Down 用占位语句保持 goose 格式。 -SELECT 1;