feat: 管理端列表返回客户名称/编码,并加固绑定与客户会话
订单/包裹/库存/店铺授权/店铺订单列表补充 customerName、customerCode;库存绑定按会话客户解析并兼容可用量;修复客户 mapId 识别与 ConcurrencyStamp 迁移。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
dc3071c90d
commit
f4ddd993b9
5
.gitignore
vendored
5
.gitignore
vendored
@ -9,8 +9,13 @@ riderModule.iml
|
||||
.idea/
|
||||
/.vs
|
||||
/Fake.sln.DotSettings.user
|
||||
*.DotSettings.user
|
||||
SimpleAdmin.sln.DotSettings.user
|
||||
|
||||
Logs/
|
||||
*.log
|
||||
api-*.log
|
||||
build-warnings.txt
|
||||
|
||||
node_modules/
|
||||
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AClient_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fc7d66b24252bb52da52d1262d99fd654ffc18aedde818f147c3daf6aee3e63_003FClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityMaintenance_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F38bf349295324397b2c739e7e632b4bd2d5000_003F49_003Fee705f67_003FEntityMaintenance_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAggregateRoot_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6015bad29db142679ead208ae4ecd55c7800_003F0e_003F66feb813_003FIAggregateRoot_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAggregateRoot_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6015bad29db142679ead208ae4ecd55c7800_003F0e_003F66feb813_003FIAggregateRoot_002Ecs_002Fz_003A2_002D0/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAggregateRoot_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa1494e50f25d476db4fff59a546c2b857800_003F4e_003Fb27c424d_003FIAggregateRoot_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAggregateRoot_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa1494e50f25d476db4fff59a546c2b857800_003F4e_003Fb27c424d_003FIAggregateRoot_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAggregateRoot_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6015bad29db142679ead208ae4ecd55c7800_003F9a_003Fea4337c7_003FIAggregateRoot_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAggregateRoot_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa1494e50f25d476db4fff59a546c2b857800_003F7b_003Fe45647d2_003FIAggregateRoot_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIAggregateRoot_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa1494e50f25d476db4fff59a546c2b857800_003F7b_003Fe45647d2_003FIAggregateRoot_00601_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIEntity_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa1494e50f25d476db4fff59a546c2b857800_003F59_003F8ca0c929_003FIEntity_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIRepository_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6015bad29db142679ead208ae4ecd55c7800_003F85_003Fa6db724e_003FIRepository_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIRepository_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F98ce6bfbfce34b1eabb7b03e820f0eea7800_003F66_003F56b27e93_003FIRepository_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIRepository_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa1494e50f25d476db4fff59a546c2b857800_003Fcc_003F73f30804_003FIRepository_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AObjectMethodExecutorAwaitable_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F61426bb2f5bd47ebb048d0e7051942f91de910_003Fe1_003F409ba3a2_003FObjectMethodExecutorAwaitable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AQueryBuilder_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F38bf349295324397b2c739e7e632b4bd2d5000_003Fc7_003F431a0b15_003FQueryBuilder_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReflectionInoCacheService_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F38bf349295324397b2c739e7e632b4bd2d5000_003Fbb_003F446201ec_003FReflectionInoCacheService_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReflectionInoCore_00601_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F38bf349295324397b2c739e7e632b4bd2d5000_003Fc1_003F8f35c8ed_003FReflectionInoCore_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASqlBuilderProvider_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F38bf349295324397b2c739e7e632b4bd2d5000_003F71_003F77c8fbe4_003FSqlBuilderProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASqlSugarClient_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F38bf349295324397b2c739e7e632b4bd2d5000_003Fe8_003Ffcf6435d_003FSqlSugarClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASqlSugarProvider_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F38bf349295324397b2c739e7e632b4bd2d5000_003F26_003Fce9a7a77_003FSqlSugarProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUnauthorizedAccessException_002Ecs_002Fl_003AC_0021_003FUsers_003FBSI_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3e10648195244ebfa276c71cb5b3f877c90750_003F74_003F8418b441_003FUnauthorizedAccessException_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=9c7e285e_002D68dc_002D4e8e_002D88cb_002De8df37de5da5/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from &lt;src&gt;\&lt;SimpleAdmin.Api.IntegrationTests&gt;" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<Project Location="D:\my\itfx\admin\src\SimpleAdmin.Api.IntegrationTests" Presentation="&lt;src&gt;\&lt;SimpleAdmin.Api.IntegrationTests&gt;" />
|
||||
</SessionState></s:String></wpf:ResourceDictionary>
|
||||
63
sql/fix_concurrency_stamp_missing.sql
Normal file
63
sql/fix_concurrency_stamp_missing.sql
Normal file
@ -0,0 +1,63 @@
|
||||
-- 给缺少 ConcurrencyStamp 的 OMS 表补列(可重复执行)
|
||||
-- Fake AggregateRoot 自带该字段,SqlSugar 查询会带上;库表没有就会报:
|
||||
-- Unknown column 'ConcurrencyStamp' in 'field list'
|
||||
--
|
||||
-- 注意:不要用 DEFAULT (UUID())。在 STATEMENT/MIXED 复制下会触发:
|
||||
-- 1674 Statement is unsafe because it uses a system function that may return a different value on the replica.
|
||||
-- 默认用固定 GUID;插入/更新时由 ORM 写入真实 UUID。
|
||||
--
|
||||
-- 用法:在业务库执行本脚本;若仍报错,把报错 SQL 里的表名追加到下方列表。
|
||||
|
||||
SET @tables := 't_order_stock_bind,C_inventory,C_parcel,C_parcel_item,C_parcel_arrival,C_sku,C_bin_location,C_inbound_notice,C_inbound_box,C_inbound_box_line,C_sku_platform_map,t_sales_store,t_sales_store_order,t_sales_store_order_line,t_customer_sender_address';
|
||||
|
||||
DROP PROCEDURE IF EXISTS sp_add_concurrency_stamp_if_missing;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE sp_add_concurrency_stamp_if_missing()
|
||||
BEGIN
|
||||
DECLARE done INT DEFAULT 0;
|
||||
DECLARE tname VARCHAR(128);
|
||||
DECLARE cur CURSOR FOR
|
||||
SELECT TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(@tables, ',', n.n), ',', -1)) AS t
|
||||
FROM (
|
||||
SELECT a.N + b.N * 10 + 1 AS n
|
||||
FROM
|
||||
(SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) a,
|
||||
(SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9) b
|
||||
) n
|
||||
WHERE n.n <= 1 + (LENGTH(@tables) - LENGTH(REPLACE(@tables, ',', '')));
|
||||
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
|
||||
|
||||
OPEN cur;
|
||||
read_loop: LOOP
|
||||
FETCH cur INTO tname;
|
||||
IF done THEN
|
||||
LEAVE read_loop;
|
||||
END IF;
|
||||
IF tname IS NULL OR tname = '' THEN
|
||||
ITERATE read_loop;
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tname
|
||||
) THEN
|
||||
ITERATE read_loop;
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = tname AND COLUMN_NAME = 'ConcurrencyStamp'
|
||||
) THEN
|
||||
SET @ddl := CONCAT(
|
||||
'ALTER TABLE `', tname,
|
||||
'` ADD COLUMN `ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT ''00000000-0000-0000-0000-000000000000'' COMMENT ''并发戳'''
|
||||
);
|
||||
PREPARE stmt FROM @ddl;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
END IF;
|
||||
END LOOP;
|
||||
CLOSE cur;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
CALL sp_add_concurrency_stamp_if_missing();
|
||||
DROP PROCEDURE IF EXISTS sp_add_concurrency_stamp_if_missing;
|
||||
@ -1,5 +1,6 @@
|
||||
-- 物流单绑定包裹/库存:t_order_stock_bind + C_inventory/C_parcel_item 冻结与可用字段
|
||||
-- 见 oms系统设计-050801.md §4.2.11 / §4.4.6
|
||||
-- 见 oms系统设计-050801.md §4.2.11 / §4.4.6 / §3.1.2
|
||||
-- ConcurrencyStamp 默认用固定 GUID,避免 DEFAULT (UUID()) 触发复制 1674
|
||||
-- 可重复执行
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `t_order_stock_bind` (
|
||||
@ -23,6 +24,7 @@ CREATE TABLE IF NOT EXISTS `t_order_stock_bind` (
|
||||
`AttributeText` VARCHAR(256) NULL,
|
||||
`Remark` VARCHAR(512) NULL,
|
||||
`IsDeleted` TINYINT NOT NULL DEFAULT 0,
|
||||
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000' COMMENT '并发戳',
|
||||
`CreateUserId` INT NOT NULL,
|
||||
`CreateTime` DATETIME NOT NULL,
|
||||
`UpdateUserId` INT NULL,
|
||||
@ -33,6 +35,18 @@ CREATE TABLE IF NOT EXISTS `t_order_stock_bind` (
|
||||
KEY `idx_osb_sku` (`CustomerId`, `WarehouseId`, `SkuId`, `Status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物流单仓储绑定明细';
|
||||
|
||||
-- 已建表但缺 ConcurrencyStamp 时补列(可重复执行)
|
||||
SET @col_exists := (
|
||||
SELECT COUNT(1) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 't_order_stock_bind' AND COLUMN_NAME = 'ConcurrencyStamp'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col_exists = 0,
|
||||
'ALTER TABLE `t_order_stock_bind` ADD COLUMN `ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT ''00000000-0000-0000-0000-000000000000'' COMMENT ''并发戳'' AFTER `IsDeleted`',
|
||||
'SELECT ''t_order_stock_bind.ConcurrencyStamp already exists'' AS info'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- C_inventory.FrozenQty
|
||||
SET @col_exists := (
|
||||
SELECT COUNT(1) FROM information_schema.COLUMNS
|
||||
|
||||
@ -95,6 +95,16 @@ public class OrderOutput
|
||||
/// </summary>
|
||||
public int? CustomerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 客户名称。
|
||||
/// </summary>
|
||||
public string? CustomerName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 客户编码。
|
||||
/// </summary>
|
||||
public string? CustomerCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单来源。
|
||||
/// </summary>
|
||||
|
||||
@ -24,6 +24,10 @@ public class PlatformStoreOutput
|
||||
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
public string? CustomerName { get; set; }
|
||||
|
||||
public string? CustomerCode { get; set; }
|
||||
|
||||
public string PlatformCode { get; set; } = string.Empty;
|
||||
|
||||
public string PlatformName { get; set; } = string.Empty;
|
||||
@ -186,6 +190,10 @@ public class PlatformStoreOrderOutput
|
||||
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
public string? CustomerName { get; set; }
|
||||
|
||||
public string? CustomerCode { get; set; }
|
||||
|
||||
public int StoreId { get; set; }
|
||||
|
||||
public string StoreNickname { get; set; } = string.Empty;
|
||||
|
||||
@ -373,6 +373,10 @@ public class WarehouseParcelOutput
|
||||
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
public string? CustomerName { get; set; }
|
||||
|
||||
public string? CustomerCode { get; set; }
|
||||
|
||||
public int WarehouseId { get; set; }
|
||||
|
||||
public string TrackingNo { get; set; } = string.Empty;
|
||||
@ -441,6 +445,12 @@ public class WarehouseInventoryListInput : WarehousePageInput
|
||||
|
||||
public class WarehouseInventoryOutput
|
||||
{
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
public string? CustomerName { get; set; }
|
||||
|
||||
public string? CustomerCode { get; set; }
|
||||
|
||||
public int SkuId { get; set; }
|
||||
|
||||
public string SkuCode { get; set; } = string.Empty;
|
||||
|
||||
@ -79,6 +79,16 @@ public partial class OrderService
|
||||
var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
|
||||
orders.SelectMany(x => new[] { x.CreateUserId, x.UpdateUserId }));
|
||||
|
||||
var customerIds = orders
|
||||
.Where(x => x.CustomerId is > 0)
|
||||
.Select(x => x.CustomerId!.Value)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
var customers = customerIds.Length == 0
|
||||
? []
|
||||
: await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
||||
var customerMap = customers.ToDictionary(x => x.Id);
|
||||
|
||||
return orders.Select(order =>
|
||||
{
|
||||
senderMap.TryGetValue(order.Id, out var sender);
|
||||
@ -91,6 +101,14 @@ public partial class OrderService
|
||||
var receivableAmount = feeOutputs
|
||||
.Where(x => x.Type == OrderFeeTypeCodes.Receivable)
|
||||
.Sum(x => x.Amount);
|
||||
string? customerName = null;
|
||||
string? customerCode = null;
|
||||
if (order.CustomerId is > 0 && customerMap.TryGetValue(order.CustomerId.Value, out var customer))
|
||||
{
|
||||
customerName = customer.CustomerName;
|
||||
customerCode = customer.CustomerCode;
|
||||
}
|
||||
|
||||
return new OrderOutput
|
||||
{
|
||||
Id = order.Id,
|
||||
@ -119,6 +137,8 @@ public partial class OrderService
|
||||
? warehouse.WarehouseName
|
||||
: null,
|
||||
CustomerId = order.CustomerId,
|
||||
CustomerName = customerName,
|
||||
CustomerCode = customerCode,
|
||||
OrderSource = string.IsNullOrWhiteSpace(order.OrderSource)
|
||||
? OrderSourceCodes.Manual
|
||||
: order.OrderSource,
|
||||
|
||||
@ -52,12 +52,12 @@ public partial class OrderService
|
||||
var order = await GetOrderOrThrowAsync(input.OrderId);
|
||||
EnsureOrderCanBindStock(order);
|
||||
var warehouseId = ResolveBindWarehouseId(order, input.WarehouseId);
|
||||
var customerId = RequireOrderCustomerId(order);
|
||||
var customerId = await ResolveStockBindCustomerIdAsync(order);
|
||||
|
||||
var inventories = await StockBindInventoryRepository.GetListAsync(x =>
|
||||
x.CustomerId == customerId
|
||||
&& x.WarehouseId == warehouseId
|
||||
&& x.AvailableQty > 0);
|
||||
&& (x.AvailableQty > 0 || x.Qty > x.FrozenQty));
|
||||
|
||||
var skuIds = inventories.Select(x => x.SkuId).Distinct().ToArray();
|
||||
var skus = skuIds.Length == 0
|
||||
@ -111,7 +111,7 @@ public partial class OrderService
|
||||
var order = await GetOrderOrThrowAsync(input.OrderId);
|
||||
EnsureOrderCanBindStock(order);
|
||||
var warehouseId = ResolveBindWarehouseId(order, input.WarehouseId);
|
||||
var customerId = RequireOrderCustomerId(order);
|
||||
var customerId = await ResolveStockBindCustomerIdAsync(order);
|
||||
|
||||
var lifecycle = input.LifecycleStatus?.Trim();
|
||||
var filterLifecycle = !string.IsNullOrWhiteSpace(lifecycle)
|
||||
@ -194,7 +194,7 @@ public partial class OrderService
|
||||
var order = await GetOrderOrThrowAsync(input.OrderId);
|
||||
EnsureOrderCanBindStock(order);
|
||||
await EnsureNoDeductedBindsAsync(order.Id);
|
||||
var customerId = RequireOrderCustomerId(order);
|
||||
var customerId = await ResolveStockBindCustomerIdAsync(order);
|
||||
var warehouseId = ResolveBindWarehouseId(order, null);
|
||||
var items = input.Items ?? [];
|
||||
|
||||
@ -500,7 +500,7 @@ public partial class OrderService
|
||||
x.CustomerId == customerId
|
||||
&& x.WarehouseId == warehouseId
|
||||
&& x.SkuId == skuId
|
||||
&& x.AvailableQty > 0);
|
||||
&& (x.AvailableQty > 0 || x.Qty > x.FrozenQty));
|
||||
rows = rows.OrderBy(x => x.BinLocationId).ToList();
|
||||
}
|
||||
|
||||
@ -641,6 +641,23 @@ public partial class OrderService
|
||||
return order.CustomerId.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绑定客户上下文:客户登录与仓储模块一致(会话客户);管理端按订单客户。
|
||||
/// </summary>
|
||||
private async Task<int> ResolveStockBindCustomerIdAsync(Order order)
|
||||
{
|
||||
var sessionCustomerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
||||
CustomerRepository,
|
||||
CurrentUser,
|
||||
null);
|
||||
if (sessionCustomerId is > 0)
|
||||
{
|
||||
return sessionCustomerId.Value;
|
||||
}
|
||||
|
||||
return RequireOrderCustomerId(order);
|
||||
}
|
||||
|
||||
private static int ResolveBindWarehouseId(Order order, int? requestedWarehouseId)
|
||||
{
|
||||
if (requestedWarehouseId is > 0)
|
||||
|
||||
@ -483,10 +483,19 @@ public partial class OrderService : ApplicationService
|
||||
return;
|
||||
}
|
||||
|
||||
if (order.CustomerId != customerId.Value)
|
||||
if (order.CustomerId == customerId.Value)
|
||||
{
|
||||
throw new BusinessException("订单不存在");
|
||||
return;
|
||||
}
|
||||
|
||||
// 兼容历史脏数据:下单人是当前客户账号,但订单 CustomerId 未写对
|
||||
var mapId = OmsSessionHelper.TryGetCustomerMapId(CurrentUser);
|
||||
if (mapId is > 0 && order.CreateUserId == mapId.Value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new BusinessException("订单不存在");
|
||||
}
|
||||
|
||||
private async Task<Order> GetOrderOrThrowAsync(int id)
|
||||
|
||||
@ -71,6 +71,12 @@ public class PlatformStoreOrderService : ApplicationService
|
||||
: await OrderRepository.GetListAsync(x => linkedOrderIds.Contains(x.Id));
|
||||
var linkedOrderMap = linkedOrders.ToDictionary(x => x.Id);
|
||||
|
||||
var customerIds = entities.Select(x => x.CustomerId).Distinct().ToArray();
|
||||
var customers = customerIds.Length == 0
|
||||
? []
|
||||
: await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
||||
var customerMap = customers.ToDictionary(x => x.Id);
|
||||
|
||||
return new OmsPageResult<PlatformStoreOrderOutput>
|
||||
{
|
||||
Items = entities.Select(x =>
|
||||
@ -82,7 +88,14 @@ public class PlatformStoreOrderService : ApplicationService
|
||||
linkedServiceNo = linkedOrder.ServiceNo;
|
||||
}
|
||||
|
||||
return MapOrderOutput(x, store?.StoreNickname ?? string.Empty, [], linkedServiceNo: linkedServiceNo);
|
||||
customerMap.TryGetValue(x.CustomerId, out var customer);
|
||||
return MapOrderOutput(
|
||||
x,
|
||||
store?.StoreNickname ?? string.Empty,
|
||||
[],
|
||||
linkedServiceNo: linkedServiceNo,
|
||||
customerName: customer?.CustomerName,
|
||||
customerCode: customer?.CustomerCode);
|
||||
}).ToList(),
|
||||
Total = total
|
||||
};
|
||||
@ -110,8 +123,16 @@ public class PlatformStoreOrderService : ApplicationService
|
||||
: await SkuRepository.GetListAsync(x => skuIds.Contains(x.Id));
|
||||
var skuMap = skus.ToDictionary(x => x.Id);
|
||||
var linkedServiceNo = await ResolveLinkedServiceNoAsync(order.LinkedOrderId);
|
||||
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == order.CustomerId);
|
||||
|
||||
return MapOrderOutput(order, store?.StoreNickname ?? string.Empty, lines, skuMap, linkedServiceNo);
|
||||
return MapOrderOutput(
|
||||
order,
|
||||
store?.StoreNickname ?? string.Empty,
|
||||
lines,
|
||||
skuMap,
|
||||
linkedServiceNo,
|
||||
customer?.CustomerName,
|
||||
customer?.CustomerCode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -692,11 +713,15 @@ public class PlatformStoreOrderService : ApplicationService
|
||||
string storeNickname,
|
||||
IReadOnlyCollection<SalesStoreOrderLine> lines,
|
||||
IReadOnlyDictionary<int, Domain.WarehouseAggregate.Entities.CSku>? skuMap = null,
|
||||
string? linkedServiceNo = null) =>
|
||||
string? linkedServiceNo = null,
|
||||
string? customerName = null,
|
||||
string? customerCode = null) =>
|
||||
new()
|
||||
{
|
||||
Id = order.Id,
|
||||
CustomerId = order.CustomerId,
|
||||
CustomerName = customerName,
|
||||
CustomerCode = customerCode,
|
||||
StoreId = order.StoreId,
|
||||
StoreNickname = storeNickname,
|
||||
PlatformCode = order.PlatformCode,
|
||||
|
||||
@ -43,9 +43,15 @@ public class PlatformStoreService : ApplicationService
|
||||
new Dictionary<string, bool> { [nameof(SalesStore.UpdateTime)] = false, [nameof(SalesStore.Id)] = false });
|
||||
var total = await StoreRepository.CountAsync(predicate);
|
||||
|
||||
var customerIds = entities.Select(x => x.CustomerId).Distinct().ToArray();
|
||||
var customers = customerIds.Length == 0
|
||||
? []
|
||||
: await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
||||
var customerMap = customers.ToDictionary(x => x.Id);
|
||||
|
||||
return new OmsPageResult<PlatformStoreOutput>
|
||||
{
|
||||
Items = entities.Select(MapOutput).ToList(),
|
||||
Items = entities.Select(x => MapOutput(x, customerMap)).ToList(),
|
||||
Total = total
|
||||
};
|
||||
}
|
||||
@ -445,11 +451,24 @@ public class PlatformStoreService : ApplicationService
|
||||
};
|
||||
}
|
||||
|
||||
private static PlatformStoreOutput MapOutput(SalesStore entity) =>
|
||||
new()
|
||||
private static PlatformStoreOutput MapOutput(
|
||||
SalesStore entity,
|
||||
IReadOnlyDictionary<int, Domain.CustomersAggregate.Entities.Customer>? customerMap = null)
|
||||
{
|
||||
string? customerName = null;
|
||||
string? customerCode = null;
|
||||
if (customerMap != null && customerMap.TryGetValue(entity.CustomerId, out var customer))
|
||||
{
|
||||
customerName = customer.CustomerName;
|
||||
customerCode = customer.CustomerCode;
|
||||
}
|
||||
|
||||
return new PlatformStoreOutput
|
||||
{
|
||||
Id = entity.Id,
|
||||
CustomerId = entity.CustomerId,
|
||||
CustomerName = customerName,
|
||||
CustomerCode = customerCode,
|
||||
PlatformCode = entity.PlatformCode,
|
||||
PlatformName = ResolvePlatformName(entity.PlatformCode),
|
||||
StoreNickname = entity.StoreNickname,
|
||||
@ -462,6 +481,7 @@ public class PlatformStoreService : ApplicationService
|
||||
Remark = entity.Remark,
|
||||
CreateTime = entity.CreateTime
|
||||
};
|
||||
}
|
||||
|
||||
private static string ResolvePlatformName(string platformCode) => platformCode.ToLowerInvariant() switch
|
||||
{
|
||||
|
||||
@ -32,14 +32,24 @@ public partial class WarehouseService
|
||||
(!hasCustomerFilter || x.CustomerId == customerIdValue) && skuIds.Contains(x.Id));
|
||||
var skuMap = skus.ToDictionary(x => x.Id);
|
||||
|
||||
var customerIds = inventories.Select(x => x.CustomerId).Distinct().ToArray();
|
||||
var customers = customerIds.Length == 0
|
||||
? []
|
||||
: await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
||||
var customerMap = customers.ToDictionary(x => x.Id);
|
||||
|
||||
var keyword = input.Keyword?.Trim();
|
||||
var grouped = inventories
|
||||
.GroupBy(x => new { x.SkuId, x.WarehouseId })
|
||||
.GroupBy(x => new { x.CustomerId, x.SkuId, x.WarehouseId })
|
||||
.Select(g =>
|
||||
{
|
||||
skuMap.TryGetValue(g.Key.SkuId, out var sku);
|
||||
customerMap.TryGetValue(g.Key.CustomerId, out var customer);
|
||||
return new WarehouseInventoryOutput
|
||||
{
|
||||
CustomerId = g.Key.CustomerId,
|
||||
CustomerName = customer?.CustomerName,
|
||||
CustomerCode = customer?.CustomerCode,
|
||||
SkuId = g.Key.SkuId,
|
||||
SkuCode = sku?.SkuCode ?? string.Empty,
|
||||
SkuName = sku?.SkuName ?? string.Empty,
|
||||
@ -53,8 +63,11 @@ public partial class WarehouseService
|
||||
.Where(x =>
|
||||
string.IsNullOrWhiteSpace(keyword)
|
||||
|| x.SkuCode.Contains(keyword!, StringComparison.OrdinalIgnoreCase)
|
||||
|| x.SkuName.Contains(keyword!, StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(x => x.SkuCode)
|
||||
|| x.SkuName.Contains(keyword!, StringComparison.OrdinalIgnoreCase)
|
||||
|| (x.CustomerName?.Contains(keyword!, StringComparison.OrdinalIgnoreCase) ?? false)
|
||||
|| (x.CustomerCode?.Contains(keyword!, StringComparison.OrdinalIgnoreCase) ?? false))
|
||||
.OrderBy(x => x.CustomerCode)
|
||||
.ThenBy(x => x.SkuCode)
|
||||
.ThenBy(x => x.WarehouseId)
|
||||
.ToList();
|
||||
|
||||
|
||||
@ -55,9 +55,25 @@ public partial class WarehouseService
|
||||
? []
|
||||
: await ParcelItemRepository.GetListAsync(x => parcelIds.Contains(x.ParcelId));
|
||||
|
||||
var customerIds = entities.Select(x => x.CustomerId).Distinct().ToArray();
|
||||
var customers = customerIds.Length == 0
|
||||
? []
|
||||
: await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
||||
var customerMap = customers.ToDictionary(x => x.Id);
|
||||
|
||||
return new OmsPageResult<WarehouseParcelOutput>
|
||||
{
|
||||
Items = entities.Select(x => MapParcelOutput(x, items.Where(i => i.ParcelId == x.Id))).ToList(),
|
||||
Items = entities.Select(x =>
|
||||
{
|
||||
var output = MapParcelOutput(x, items.Where(i => i.ParcelId == x.Id));
|
||||
if (customerMap.TryGetValue(x.CustomerId, out var customer))
|
||||
{
|
||||
output.CustomerName = customer.CustomerName;
|
||||
output.CustomerCode = customer.CustomerCode;
|
||||
}
|
||||
|
||||
return output;
|
||||
}).ToList(),
|
||||
Total = total
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using System.Security.Claims;
|
||||
using Fake.Application;
|
||||
using Fake.Users;
|
||||
using Oms.Domain.CustomersAggregate.Entities;
|
||||
@ -15,16 +16,32 @@ public static class OmsSessionHelper
|
||||
/// </summary>
|
||||
public const string LoginTypeClaim = "loginType";
|
||||
|
||||
/// <summary>
|
||||
/// JWT 中 RBAC mapId Claim 名称(与 FakeClaimTypes.UserId 对齐)。
|
||||
/// </summary>
|
||||
public const string MapIdClaim = "mapId";
|
||||
|
||||
private const string CustomerLoginType = "1";
|
||||
private const string CustomerRoleName = "Customer";
|
||||
|
||||
/// <summary>
|
||||
/// 当前会话是否为客户门户登录。
|
||||
/// </summary>
|
||||
public static bool IsCustomerLogin(ICurrentUser currentUser) =>
|
||||
string.Equals(
|
||||
public static bool IsCustomerLogin(ICurrentUser currentUser)
|
||||
{
|
||||
if (string.Equals(
|
||||
currentUser.FindClaimOrNull(LoginTypeClaim)?.Value,
|
||||
CustomerLoginType,
|
||||
StringComparison.Ordinal);
|
||||
StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 兼容 loginType 丢失但 role=Customer 仍在的令牌
|
||||
var role = currentUser.FindClaimOrNull(ClaimTypes.Role)?.Value
|
||||
?? currentUser.FindClaimOrNull("role")?.Value;
|
||||
return string.Equals(role, CustomerRoleName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户登录时返回 mapId(对应 t_customer.UserId / 订单 CreateUserId);管理员返回 null。
|
||||
@ -37,7 +54,19 @@ public static class OmsSessionHelper
|
||||
}
|
||||
|
||||
var userId = currentUser.GetId<int>();
|
||||
return userId > 0 ? userId : null;
|
||||
if (userId > 0)
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
|
||||
// GetId 未绑到 mapId 时,直接读 claim,避免客户会话被当成管理员(不过滤)
|
||||
var mapIdRaw = currentUser.FindClaimOrNull(MapIdClaim)?.Value;
|
||||
if (int.TryParse(mapIdRaw, out var mapId) && mapId > 0)
|
||||
{
|
||||
return mapId;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user