feat: 代发进度统计、申报HS分列与异常事件表迁移脚本
补充代发进度 Tab 计数、PrimarySkuCodes 与 Im/Ex HS 编码输出;新增 t_order_exception_event 幂等迁移(Handler*、ConcurrencyStamp)并同步 db.sql。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9d813a5f89
commit
2018e45a7d
1
db.sql
1
db.sql
@ -490,6 +490,7 @@ CREATE TABLE `t_order_exception_event` (
|
||||
`ClaimReason` VARCHAR(512) NULL COMMENT '理赔原因',
|
||||
`Remark` VARCHAR(512) NULL COMMENT '操作备注',
|
||||
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
|
||||
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
|
||||
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '操作人',
|
||||
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
|
||||
`UpdateUserId` INT NULL COMMENT '更新人',
|
||||
|
||||
118
sql/migrate_t_order_exception_event_handler.sql
Normal file
118
sql/migrate_t_order_exception_event_handler.sql
Normal file
@ -0,0 +1,118 @@
|
||||
-- 订单异常事件表:补齐 Handler* / ConcurrencyStamp(幂等;列已存在则自动跳过)
|
||||
-- 修复:Unknown column 'HandlerNames' / 'ConcurrencyStamp' in 'field list'
|
||||
-- MySQL 5.7+ / 8.0
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
-- HandlerNames
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order_exception_event'
|
||||
AND COLUMN_NAME = 'HandlerNames'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col = 0,
|
||||
'ALTER TABLE `t_order_exception_event` ADD COLUMN `HandlerNames` VARCHAR(256) NULL COMMENT ''处理人快照''',
|
||||
'SELECT ''skip HandlerNames'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- HandlerType
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order_exception_event'
|
||||
AND COLUMN_NAME = 'HandlerType'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col = 0,
|
||||
'ALTER TABLE `t_order_exception_event` ADD COLUMN `HandlerType` VARCHAR(16) NULL COMMENT ''处理人类型''',
|
||||
'SELECT ''skip HandlerType'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- HandlerUserId
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order_exception_event'
|
||||
AND COLUMN_NAME = 'HandlerUserId'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col = 0,
|
||||
'ALTER TABLE `t_order_exception_event` ADD COLUMN `HandlerUserId` INT NULL COMMENT ''处理人用户Id(customer 时为订单客户账号)''',
|
||||
'SELECT ''skip HandlerUserId'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ConcurrencyStamp(AggregateRoot 并发戳;避免 DEFAULT(UUID()) 触发 1674 复制不安全)
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order_exception_event'
|
||||
AND COLUMN_NAME = 'ConcurrencyStamp'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col = 0,
|
||||
'ALTER TABLE `t_order_exception_event` ADD COLUMN `ConcurrencyStamp` CHAR(36) NULL COMMENT ''并发戳''',
|
||||
'SELECT ''skip ConcurrencyStamp add'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order_exception_event'
|
||||
AND COLUMN_NAME = 'ConcurrencyStamp'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col > 0,
|
||||
'UPDATE `t_order_exception_event`
|
||||
SET `ConcurrencyStamp` = LOWER(CONCAT(
|
||||
LPAD(HEX(`Id`), 8, ''0''),
|
||||
''-0000-4000-8000-'',
|
||||
LPAD(HEX(`Id` * 9973), 12, ''0'')
|
||||
))
|
||||
WHERE `ConcurrencyStamp` IS NULL OR `ConcurrencyStamp` = ''''',
|
||||
'SELECT ''skip ConcurrencyStamp backfill'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order_exception_event'
|
||||
AND COLUMN_NAME = 'ConcurrencyStamp'
|
||||
);
|
||||
SET @isNullable := (
|
||||
SELECT IFNULL(MAX(CASE WHEN IS_NULLABLE = 'YES' THEN 1 ELSE 0 END), 0)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order_exception_event'
|
||||
AND COLUMN_NAME = 'ConcurrencyStamp'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col > 0 AND @isNullable = 1,
|
||||
'ALTER TABLE `t_order_exception_event` MODIFY COLUMN `ConcurrencyStamp` CHAR(36) NOT NULL COMMENT ''并发戳''',
|
||||
'SELECT ''skip ConcurrencyStamp not null'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
@ -1,14 +1,58 @@
|
||||
-- 代发订单:t_order 履约类型字段(逐条执行;若列已存在请跳过对应 ALTER)
|
||||
-- 代发订单:t_order 履约类型字段(幂等;列/索引已存在则自动跳过)
|
||||
-- MySQL 5.7+ / 8.0
|
||||
|
||||
ALTER TABLE `t_order`
|
||||
ADD COLUMN `FulfillmentType` VARCHAR(16) NULL DEFAULT 'direct' COMMENT '履约类型 direct/warehouse' AFTER `AdditionalServices`;
|
||||
SET @db := DATABASE();
|
||||
|
||||
ALTER TABLE `t_order`
|
||||
ADD COLUMN `FulfillmentSource` VARCHAR(32) NULL COMMENT '代发来源 storeFulfill/postBind' AFTER `FulfillmentType`;
|
||||
-- FulfillmentType
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order'
|
||||
AND COLUMN_NAME = 'FulfillmentType'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col = 0,
|
||||
'ALTER TABLE `t_order` ADD COLUMN `FulfillmentType` VARCHAR(16) NULL DEFAULT ''direct'' COMMENT ''履约类型 direct/warehouse'' AFTER `AdditionalServices`',
|
||||
'SELECT ''skip FulfillmentType'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
ALTER TABLE `t_order`
|
||||
ADD KEY `idx_ord_fulfillment_customer` (`FulfillmentType`, `CustomerId`, `OrderStatus`);
|
||||
-- FulfillmentSource
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order'
|
||||
AND COLUMN_NAME = 'FulfillmentSource'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col = 0,
|
||||
'ALTER TABLE `t_order` ADD COLUMN `FulfillmentSource` VARCHAR(32) NULL COMMENT ''代发来源 storeFulfill/postBind'' AFTER `FulfillmentType`',
|
||||
'SELECT ''skip FulfillmentSource'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- idx_ord_fulfillment_customer
|
||||
SET @idx := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_order'
|
||||
AND INDEX_NAME = 'idx_ord_fulfillment_customer'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@idx = 0,
|
||||
'ALTER TABLE `t_order` ADD KEY `idx_ord_fulfillment_customer` (`FulfillmentType`, `CustomerId`, `OrderStatus`)',
|
||||
'SELECT ''skip idx_ord_fulfillment_customer'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 历史:已有绑定行的订单标记为 warehouse
|
||||
UPDATE `t_order` o
|
||||
|
||||
@ -23,6 +23,13 @@ public class OrderExportInput
|
||||
[Newtonsoft.Json.JsonProperty("fulfillmentType")]
|
||||
public string? FulfillmentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 代发进度筛选(pendingBind / domesticInTransit / arrivedPendingPick / outboundDone)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("fulfillProgress")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("fulfillProgress")]
|
||||
public string? FulfillProgress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关键词。
|
||||
/// </summary>
|
||||
|
||||
@ -51,10 +51,20 @@ public class OrderItemOutput
|
||||
public decimal? Weight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// HS 编码。
|
||||
/// HS 编码(兼容旧字段,优先出口编码)。
|
||||
/// </summary>
|
||||
public string? HsCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 进口 HS 编码。
|
||||
/// </summary>
|
||||
public string? ImHsCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出口 HS 编码。
|
||||
/// </summary>
|
||||
public string? ExHsCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SKU 图片。
|
||||
/// </summary>
|
||||
|
||||
@ -175,6 +175,11 @@ public class OrderOutput
|
||||
/// </summary>
|
||||
public string? PrimaryBinCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 绑定 SKU 编码汇总(逗号分隔,列表展示/拣货筛选用)。
|
||||
/// </summary>
|
||||
public string? PrimarySkuCodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联揽收单 Id。
|
||||
/// </summary>
|
||||
|
||||
@ -20,4 +20,10 @@ public class OrderStatusCountsOutput
|
||||
/// </summary>
|
||||
public Dictionary<string, OrderHandleStatusCountsOutput> HandleCounts { get; set; } =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// 代发进度分组数量(fulfillmentType=warehouse 时返回)。
|
||||
/// </summary>
|
||||
public Dictionary<string, int> FulfillProgressCounts { get; set; } =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
@ -187,6 +187,7 @@ public partial class OrderService
|
||||
bindSummary?.LineCount ?? 0,
|
||||
bindSummary?.HasInTransitParcel ?? false),
|
||||
PrimaryBinCode = bindSummary?.PrimaryBinCode,
|
||||
PrimarySkuCodes = bindSummary?.PrimarySkuCodes,
|
||||
PickupOrderId = order.PickupOrderId,
|
||||
PickupNo = order.PickupOrderId is > 0
|
||||
&& pickupNoMap.TryGetValue(order.PickupOrderId.Value, out var pickupNo)
|
||||
@ -280,6 +281,8 @@ public partial class OrderService
|
||||
public bool HasInTransitParcel { get; init; }
|
||||
|
||||
public string? PrimaryBinCode { get; init; }
|
||||
|
||||
public string? PrimarySkuCodes { get; init; }
|
||||
}
|
||||
|
||||
private async Task<Dictionary<int, StockBindListSummary>> LoadStockBindListSummariesAsync(int[] orderIds)
|
||||
@ -342,6 +345,12 @@ public partial class OrderService
|
||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||
.OrderBy(code => code, StringComparer.OrdinalIgnoreCase)
|
||||
.FirstOrDefault();
|
||||
var skuCodes = rows
|
||||
.Select(row => row.SkuCode?.Trim())
|
||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(code => code, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
return new StockBindListSummary
|
||||
{
|
||||
BindStatus = bindStatus,
|
||||
@ -349,7 +358,8 @@ public partial class OrderService
|
||||
QtyTotal = rows.Sum(x => x.BindQty),
|
||||
TrackingNosText = trackingNos.Length == 0 ? null : string.Join(", ", trackingNos),
|
||||
HasInTransitParcel = hasInTransitParcel,
|
||||
PrimaryBinCode = primaryBinCode
|
||||
PrimaryBinCode = primaryBinCode,
|
||||
PrimarySkuCodes = skuCodes.Length == 0 ? null : string.Join(", ", skuCodes),
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -732,6 +742,8 @@ public partial class OrderService
|
||||
Amount = x.Amount,
|
||||
Weight = x.Weight,
|
||||
HsCode = x.ExHsCode ?? x.ImHsCode ?? x.HsCode,
|
||||
ImHsCode = x.ImHsCode ?? x.HsCode,
|
||||
ExHsCode = x.ExHsCode ?? x.HsCode,
|
||||
SkuImage = x.SkuImage,
|
||||
Category = x.Category,
|
||||
Brand = x.Brand,
|
||||
|
||||
@ -93,12 +93,18 @@ public partial class OrderService : ApplicationService
|
||||
Processed = x.Value.Processed
|
||||
},
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
return new OrderStatusCountsOutput
|
||||
var output = new OrderStatusCountsOutput
|
||||
{
|
||||
Total = rows.Sum(x => x.Count),
|
||||
Counts = counts,
|
||||
HandleCounts = handleCounts
|
||||
};
|
||||
if (string.Equals(filter.FulfillmentType, FulfillmentTypeCodes.Warehouse, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
output.FulfillProgressCounts = await OrderRepository.GetFulfillProgressCountsByFilterAsync(filter);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -626,6 +632,7 @@ public partial class OrderService : ApplicationService
|
||||
Status = input.Status,
|
||||
OrderSource = input.OrderSource,
|
||||
FulfillmentType = NormalizeFulfillmentTypeFilter(input.FulfillmentType),
|
||||
FulfillProgress = NormalizeFulfillProgressFilter(input.FulfillProgress),
|
||||
Keyword = NormalizeOrderKeyword(input.Keyword),
|
||||
CreateTimeStart = input.CreateTimeStart,
|
||||
CreateTimeEnd = input.CreateTimeEnd,
|
||||
|
||||
@ -86,6 +86,10 @@ public interface IOrderRepository : IRepository<Order>
|
||||
OrderListFilter filter,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<Dictionary<string, int>> GetFulfillProgressCountsByFilterAsync(
|
||||
OrderListFilter filter,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<int> CountByFilterAndCreateTimeRangeAsync(
|
||||
OrderListFilter filter,
|
||||
DateTime startInclusive,
|
||||
|
||||
@ -1212,6 +1212,56 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, int>> GetFulfillProgressCountsByFilterAsync(
|
||||
OrderListFilter filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var db = await GetDbContextAsync(cancellationToken);
|
||||
var baseFilter = new OrderListFilter
|
||||
{
|
||||
Keyword = filter.Keyword,
|
||||
CreateTimeStart = filter.CreateTimeStart,
|
||||
CreateTimeEnd = filter.CreateTimeEnd,
|
||||
CustomerId = filter.CustomerId,
|
||||
WarehouseId = filter.WarehouseId,
|
||||
FulfillmentType = filter.FulfillmentType,
|
||||
StockBindStatus = filter.StockBindStatus,
|
||||
OrderSource = filter.OrderSource,
|
||||
FieldFilters = filter.FieldFilters,
|
||||
SortField = filter.SortField,
|
||||
SortOrder = filter.SortOrder,
|
||||
ScopeOrganizationIds = filter.ScopeOrganizationIds,
|
||||
ScopeSecretLevels = filter.ScopeSecretLevels,
|
||||
ScopeDataScope = filter.ScopeDataScope,
|
||||
ScopePersonalOwnerMapId = filter.ScopePersonalOwnerMapId,
|
||||
ScopePersonalOwnerAccount = filter.ScopePersonalOwnerAccount,
|
||||
ScopeOwnerMapIds = filter.ScopeOwnerMapIds,
|
||||
ScopeBizFilters = filter.ScopeBizFilters,
|
||||
ScopeCustomFilters = filter.ScopeCustomFilters,
|
||||
};
|
||||
|
||||
async Task<int> CountAsync(string? fulfillProgress)
|
||||
{
|
||||
var query = ApplyListFilter(db.Queryable<Order>(), baseFilter);
|
||||
if (!string.IsNullOrWhiteSpace(fulfillProgress))
|
||||
{
|
||||
query = ApplyFulfillProgressFilter(query, fulfillProgress);
|
||||
}
|
||||
|
||||
return await query.CountAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var result = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["all"] = await CountAsync(null),
|
||||
[FulfillmentProgressCodes.PendingBind] = await CountAsync(FulfillmentProgressCodes.PendingBind),
|
||||
[FulfillmentProgressCodes.DomesticInTransit] = await CountAsync(FulfillmentProgressCodes.DomesticInTransit),
|
||||
[FulfillmentProgressCodes.ArrivedPendingPick] = await CountAsync(FulfillmentProgressCodes.ArrivedPendingPick),
|
||||
[FulfillmentProgressCodes.OutboundDone] = await CountAsync(FulfillmentProgressCodes.OutboundDone),
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<int> CountByFilterAndCreateTimeRangeAsync(
|
||||
OrderListFilter filter,
|
||||
DateTime startInclusive,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user