Compare commits
10 Commits
9d813a5f89
...
7700baf127
| Author | SHA1 | Date | |
|---|---|---|---|
| 7700baf127 | |||
| 4fcc0bf7a9 | |||
| b81432c0af | |||
| 25b4211b46 | |||
| eba1fa4867 | |||
| 7e9a5d4ae0 | |||
| a70f6f2a87 | |||
| 78e06d01b9 | |||
| ce1349572d | |||
| 2018e45a7d |
34
.gitea/workflows/deploy.yml
Normal file
34
.gitea/workflows/deploy.yml
Normal file
@ -0,0 +1,34 @@
|
||||
# 复制到 admin .gitea/workflows/deploy.yml
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, main]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CSPROJ: src/SimpleAdmin.Api/SimpleAdmin.Api.csproj
|
||||
IMAGE_NAME: admin_api
|
||||
DEPLOY_NAME: admin-api
|
||||
CONTAINER_NAME: admin-api
|
||||
TCR_REGISTRY: ccr.ccs.tencentyun.com
|
||||
TCR_NAMESPACE: ideship
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: k3s-host
|
||||
steps:
|
||||
- name: Build and deploy
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
|
||||
TAG="${{ gitea.sha }}"
|
||||
IMG="${{ env.TCR_REGISTRY }}/${{ env.TCR_NAMESPACE }}/${{ env.IMAGE_NAME }}:${TAG}"
|
||||
REPO="${{ gitea.repository }}"
|
||||
test -n "$REPO" && [[ "$REPO" == */* ]]
|
||||
echo "REPO=$REPO TAG=$TAG host=$(hostname)"
|
||||
cd /data/k3s
|
||||
./scripts/ci-publish-container.sh "$REPO" "${{ env.CSPROJ }}" \
|
||||
"${{ env.IMAGE_NAME }}" "$TAG" "$TAG"
|
||||
./scripts/ci-deploy-api.sh "${{ env.DEPLOY_NAME }}" \
|
||||
"${{ env.CONTAINER_NAME }}" "$IMG"
|
||||
@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="xiaolipro.cn" value="https://baget.xiaolipro.cn/v3/index.json" />
|
||||
<clear />
|
||||
<add key="ideship" value="https://baget.ideship.com/v3/index.json" />
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
||||
@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "items", "items", "{BE5132AD
|
||||
oms表结构与接口设计20260427.md = oms表结构与接口设计20260427.md
|
||||
db.sql = db.sql
|
||||
Directory.Build.props = Directory.Build.props
|
||||
.gitea\workflows\deploy.yml = .gitea\workflows\deploy.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleAdmin.Api", "src\SimpleAdmin.Api\SimpleAdmin.Api.csproj", "{229ED167-9ECC-4736-8DFB-522EEA8FF680}"
|
||||
|
||||
97
db.sql
97
db.sql
@ -34,6 +34,8 @@ DROP TABLE IF EXISTS `t_kb_category`;
|
||||
DROP TABLE IF EXISTS `t_mcp_tool`;
|
||||
DROP TABLE IF EXISTS `t_pickup_order_line`;
|
||||
DROP TABLE IF EXISTS `t_pickup_order`;
|
||||
DROP TABLE IF EXISTS `t_9610_order_item`;
|
||||
DROP TABLE IF EXISTS `t_9610_order`;
|
||||
DROP TABLE IF EXISTS `t_ai_skill`;
|
||||
DROP TABLE IF EXISTS `t_ai_provider`;
|
||||
DROP TABLE IF EXISTS `t_xhs_ai_provider`;
|
||||
@ -490,6 +492,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 '更新人',
|
||||
@ -506,6 +509,7 @@ CREATE TABLE `t_sales_product` (
|
||||
`MinTimeliness` INT NULL COMMENT '最小时效(天)',
|
||||
`MaxTimeliness` INT NULL COMMENT '最大时效(天)',
|
||||
`TradeTerms` VARCHAR(16) NULL COMMENT '贸易条款:DDP/DDU',
|
||||
`RouteSort` INT NOT NULL DEFAULT 0 COMMENT '官网线路排序,越小越靠前',
|
||||
`AdditionalServices` VARCHAR(512) NULL COMMENT '可选附加服务字典code JSON数组',
|
||||
`CustomerWaybill` VARCHAR(32) NULL COMMENT '系统面单/客户面单',
|
||||
`BlacklistAreaId` INT NULL COMMENT '黑名单区ID',
|
||||
@ -527,6 +531,7 @@ CREATE TABLE `t_sales_product` (
|
||||
PRIMARY KEY (`Id`),
|
||||
UNIQUE KEY `uk_sp_code` (`ProductCode`),
|
||||
KEY `idx_sp_group_active` (`ProductGroup`, `IsActive`),
|
||||
KEY `idx_sp_route_sort` (`RouteSort`, `IsActive`),
|
||||
KEY `idx_sp_scheme` (`Scheme`),
|
||||
KEY `idx_sp_weight_rule` (`WeightRuleId`),
|
||||
KEY `idx_sp_weight_rounding` (`WeightRoundingId`)
|
||||
@ -1919,6 +1924,98 @@ CREATE TABLE `t_pickup_order_line` (
|
||||
KEY `idx_pickup_line_header` (`PickupOrderId`, `IsDeleted`)
|
||||
) COMMENT='揽收单关联物流订单(1揽收:N物流)';
|
||||
|
||||
CREATE TABLE `t_9610_order` (
|
||||
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`OrderId` INT NOT NULL COMMENT '关联运单 t_order.Id',
|
||||
`ServiceNo` VARCHAR(64) NULL COMMENT '运单号冗余',
|
||||
`CustomerId` INT NOT NULL DEFAULT 0 COMMENT '客户Id',
|
||||
`Status` VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending待申报 / declared已申报',
|
||||
`PlatformCode` VARCHAR(16) NOT NULL DEFAULT '' COMMENT '电商平台',
|
||||
`EcomCorpCode` VARCHAR(18) NULL COMMENT '电商企业代码',
|
||||
`EcomCorpName` VARCHAR(100) NULL COMMENT '电商企业名称',
|
||||
`ProducerCode` VARCHAR(18) NULL COMMENT '生产销售单位代码',
|
||||
`ProducerName` VARCHAR(100) NULL COMMENT '生产销售单位',
|
||||
`ClientNo` VARCHAR(48) NOT NULL DEFAULT '' COMMENT '客户单号(对齐 t_order.ClientNo)',
|
||||
`BusinessType` CHAR(1) NOT NULL DEFAULT 'B' COMMENT '业务类型 A简化/B汇总',
|
||||
`Region` VARCHAR(10) NULL COMMENT '区域 SZ/other',
|
||||
`AgentDeclare` VARCHAR(2) NULL COMMENT '是否代理申报 是/否',
|
||||
`ActualSellerCode` VARCHAR(18) NULL COMMENT '实际卖家信用代码',
|
||||
`PreRecordFlag` CHAR(1) NULL COMMENT '汇总预录 0已预录 1未预录',
|
||||
`IsSummarySimple` TINYINT NOT NULL DEFAULT 0 COMMENT '是否汇总简化申报',
|
||||
`GrossWeight` DECIMAL(19,5) NOT NULL DEFAULT 0 COMMENT '毛重KG',
|
||||
`Currency` VARCHAR(4) NOT NULL DEFAULT 'CNY' COMMENT '币制',
|
||||
`DestCountry` VARCHAR(4) NOT NULL DEFAULT '' COMMENT '目的国',
|
||||
`Freight` DECIMAL(19,5) NULL COMMENT '运费',
|
||||
`FreightCurrency` VARCHAR(4) NULL COMMENT '运费币制',
|
||||
`FreightMark` INT NULL COMMENT '运费标志 1率2单价3总价',
|
||||
`PackageType` INT NULL COMMENT '包装种类代码',
|
||||
`PackageQty` INT NULL COMMENT '件数',
|
||||
`ShipmentNo` VARCHAR(32) NULL COMMENT '出货单号',
|
||||
`CustomsOrderNo` VARCHAR(32) NULL COMMENT '海关订单号(申报回写)',
|
||||
`DownstreamRefNo` VARCHAR(32) NULL COMMENT '申报回写的 referenceNo 快照',
|
||||
`EnterpriseBizNo` VARCHAR(48) NULL COMMENT '企业唯一编号(上游 copNo)',
|
||||
`ManifestNo` VARCHAR(48) NULL COMMENT '清单编号(上游 invtNo)',
|
||||
`ClearanceTime` VARCHAR(48) NULL COMMENT '结关日期原文',
|
||||
`CustomsInboundTime` VARCHAR(48) NULL COMMENT '海关入库时间原文',
|
||||
`CustomsState` VARCHAR(4) NULL COMMENT '海关状态',
|
||||
`DeclareCustomsCode` VARCHAR(4) NULL COMMENT '申报地海关代码',
|
||||
`PlatformCorpCode` VARCHAR(18) NULL COMMENT '电商平台代码',
|
||||
`PlatformName` VARCHAR(100) NULL COMMENT '电商平台名称',
|
||||
`LogisticsCorpCode` VARCHAR(18) NULL COMMENT '物流企业代码',
|
||||
`LogisticsCorpName` VARCHAR(100) NULL COMMENT '物流企业名称',
|
||||
`IeMark` VARCHAR(4) NULL COMMENT '进出口标记 I/E',
|
||||
`ExportPortCode` VARCHAR(4) NULL COMMENT '出口口岸代码',
|
||||
`ExportDate` VARCHAR(32) NULL COMMENT '出口日期原文',
|
||||
`DeclareAgentCode` VARCHAR(18) NULL COMMENT '申报企业代码',
|
||||
`DeclareAgentName` VARCHAR(100) NULL COMMENT '申报企业名称',
|
||||
`TradeModeCode` VARCHAR(4) NULL COMMENT '贸易方式代码',
|
||||
`TransportMode` VARCHAR(4) NULL COMMENT '运输方式代码',
|
||||
`DeclareTime` DATETIME NULL COMMENT '申报成功时间',
|
||||
`ManifestSyncTime` DATETIME NULL COMMENT '清单数据最近同步时间',
|
||||
`FailReason` VARCHAR(512) NULL COMMENT '最近失败原因',
|
||||
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除',
|
||||
`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 '更新人',
|
||||
`UpdateTime` DATETIME NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`Id`),
|
||||
UNIQUE KEY `uk_9610_order_id` (`OrderId`),
|
||||
KEY `idx_9610_status_ctime` (`Status`, `CreateTime`),
|
||||
KEY `idx_9610_service_no` (`ServiceNo`),
|
||||
KEY `idx_9610_client_no` (`ClientNo`),
|
||||
KEY `idx_9610_customer` (`CustomerId`, `CreateTime`),
|
||||
KEY `idx_9610_manifest` (`ManifestNo`)
|
||||
) COMMENT='9610汇总申报单';
|
||||
|
||||
CREATE TABLE `t_9610_order_item` (
|
||||
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`Customs9610OrderId` INT NOT NULL COMMENT 't_9610_order.Id',
|
||||
`OrderDeclarationId` INT NULL COMMENT '溯源 t_order_declaration.Id',
|
||||
`Sku` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'SKU(对齐 t_order_declaration.Sku)',
|
||||
`CnName` VARCHAR(250) NOT NULL DEFAULT '' COMMENT '中文品名(对齐 t_order_declaration.CnName)',
|
||||
`ExHsCode` VARCHAR(10) NULL COMMENT '出口海关编码(对齐 t_order_declaration.ExHsCode)',
|
||||
`DeclareElement` VARCHAR(255) NULL COMMENT '申报要素',
|
||||
`Barcode` VARCHAR(255) NULL COMMENT '条形码',
|
||||
`DestCountry` VARCHAR(255) NULL COMMENT '目的国(地区)代码',
|
||||
`ItemCurrency` VARCHAR(3) NULL COMMENT '币制',
|
||||
`UnitPrice` DECIMAL(19,5) NOT NULL DEFAULT 0 COMMENT '单价(对齐 t_order_declaration.UnitPrice)',
|
||||
`Amount` DECIMAL(19,5) NULL COMMENT '总价(对齐 t_order_declaration.Amount)',
|
||||
`Quantity` DECIMAL(19,5) NOT NULL DEFAULT 0 COMMENT '数量(对齐 t_order_declaration.Quantity)',
|
||||
`LegalQty` DECIMAL(19,5) NULL COMMENT '法定数量',
|
||||
`Unit` VARCHAR(3) NULL COMMENT '计量单位(对齐 t_order_declaration.Unit)',
|
||||
`LegalUnit` VARCHAR(3) NULL COMMENT '法定计量单位',
|
||||
`Freight` DECIMAL(12,4) NULL COMMENT '行运费',
|
||||
`SortNo` INT NOT NULL DEFAULT 0 COMMENT '排序',
|
||||
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除',
|
||||
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
|
||||
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`UpdateUserId` INT NULL COMMENT '更新人',
|
||||
`UpdateTime` DATETIME NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`Id`),
|
||||
KEY `idx_9610_item_header` (`Customs9610OrderId`, `IsDeleted`)
|
||||
) COMMENT='9610申报商品明细';
|
||||
|
||||
CREATE TABLE `t_kb_category` (
|
||||
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`Code` VARCHAR(64) NOT NULL COMMENT '稳定键,如 pricing',
|
||||
|
||||
80
sql/migrate_dict_employee_position.sql
Normal file
80
sql/migrate_dict_employee_position.sql
Normal file
@ -0,0 +1,80 @@
|
||||
-- 员工档案「岗位 / 职级」字典初始数据(幂等:已存在同 DictType+DictCode 则跳过)
|
||||
-- dictType: employee_position / employee_job_level
|
||||
-- MySQL 5.7+ / 8.0
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- ========== 岗位 employee_position ==========
|
||||
INSERT INTO `t_dict_type` (`DictType`, `Remark`, `TypeSort`, `IsDeleted`, `CreateUserId`)
|
||||
SELECT 'employee_position', '员工岗位', 0, 0, 0
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `t_dict_type` WHERE `DictType` = 'employee_position' AND `IsDeleted` = 0
|
||||
);
|
||||
|
||||
INSERT INTO `t_dict` (`DictType`, `DictSort`, `DictCode`, `DictName`, `IsDeleted`, `CreateUserId`)
|
||||
SELECT v.`DictType`, v.`DictSort`, v.`DictCode`, v.`DictName`, 0, 0
|
||||
FROM (
|
||||
SELECT 'employee_position' AS DictType, 1 AS DictSort, 'generalManager' AS DictCode, '总经理' AS DictName
|
||||
UNION ALL SELECT 'employee_position', 2, 'deputyGeneralManager', '副总经理'
|
||||
UNION ALL SELECT 'employee_position', 3, 'logisticsAdvisor', '物流顾问'
|
||||
UNION ALL SELECT 'employee_position', 4, 'csManager', '客服主管'
|
||||
UNION ALL SELECT 'employee_position', 5, 'csSpecialist', '客服专员'
|
||||
UNION ALL SELECT 'employee_position', 6, 'opsManager', '运营经理'
|
||||
UNION ALL SELECT 'employee_position', 7, 'opsSpecialist', '运营专员'
|
||||
UNION ALL SELECT 'employee_position', 8, 'financeManager', '财务经理'
|
||||
UNION ALL SELECT 'employee_position', 9, 'financeSpecialist', '财务专员'
|
||||
UNION ALL SELECT 'employee_position', 10, 'warehouseManager', '仓储主管'
|
||||
UNION ALL SELECT 'employee_position', 11, 'warehouseClerk', '仓管员'
|
||||
UNION ALL SELECT 'employee_position', 12, 'customsSpecialist', '关务专员'
|
||||
UNION ALL SELECT 'employee_position', 13, 'productManager', '产品经理'
|
||||
UNION ALL SELECT 'employee_position', 14, 'techEngineer', '技术工程师'
|
||||
UNION ALL SELECT 'employee_position', 15, 'hrAdmin', '人事行政'
|
||||
UNION ALL SELECT 'employee_position', 16, 'marketingSpecialist', '市场专员'
|
||||
UNION ALL SELECT 'employee_position', 17, 'dispatcher', '调度员'
|
||||
) AS v
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM `t_dict` d
|
||||
WHERE d.`DictType` = v.`DictType`
|
||||
AND d.`DictCode` = v.`DictCode`
|
||||
AND d.`IsDeleted` = 0
|
||||
);
|
||||
|
||||
UPDATE `t_dict`
|
||||
SET `IsDeleted` = 1,
|
||||
`UpdateTime` = NOW()
|
||||
WHERE `DictType` = 'employee_position'
|
||||
AND `DictCode` IN (
|
||||
'salesDirector',
|
||||
'salesManager',
|
||||
'salesSpecialist',
|
||||
'logisticsAdvisorDirector',
|
||||
'logisticsAdvisorManager'
|
||||
)
|
||||
AND `IsDeleted` = 0;
|
||||
|
||||
-- ========== 职级 employee_job_level(一级~五级) ==========
|
||||
INSERT INTO `t_dict_type` (`DictType`, `Remark`, `TypeSort`, `IsDeleted`, `CreateUserId`)
|
||||
SELECT 'employee_job_level', '员工职级', 0, 0, 0
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM `t_dict_type` WHERE `DictType` = 'employee_job_level' AND `IsDeleted` = 0
|
||||
);
|
||||
|
||||
INSERT INTO `t_dict` (`DictType`, `DictSort`, `DictCode`, `DictName`, `IsDeleted`, `CreateUserId`)
|
||||
SELECT v.`DictType`, v.`DictSort`, v.`DictCode`, v.`DictName`, 0, 0
|
||||
FROM (
|
||||
SELECT 'employee_job_level' AS DictType, 1 AS DictSort, '1' AS DictCode, '一级' AS DictName
|
||||
UNION ALL SELECT 'employee_job_level', 2, '2', '二级'
|
||||
UNION ALL SELECT 'employee_job_level', 3, '3', '三级'
|
||||
UNION ALL SELECT 'employee_job_level', 4, '4', '四级'
|
||||
UNION ALL SELECT 'employee_job_level', 5, '5', '五级'
|
||||
) AS v
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM `t_dict` d
|
||||
WHERE d.`DictType` = v.`DictType`
|
||||
AND d.`DictCode` = v.`DictCode`
|
||||
AND d.`IsDeleted` = 0
|
||||
);
|
||||
36
sql/migrate_sales_product_route_sort.sql
Normal file
36
sql/migrate_sales_product_route_sort.sql
Normal file
@ -0,0 +1,36 @@
|
||||
-- t_sales_product:官网线路展示排序(幂等;列已存在则跳过)
|
||||
-- 数值越小越靠前;默认 0
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_sales_product'
|
||||
AND COLUMN_NAME = 'RouteSort'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col = 0,
|
||||
'ALTER TABLE `t_sales_product` ADD COLUMN `RouteSort` INT NOT NULL DEFAULT 0 COMMENT ''官网线路排序,越小越靠前'' AFTER `TradeTerms`',
|
||||
'SELECT ''skip RouteSort'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @idx := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_sales_product'
|
||||
AND INDEX_NAME = 'idx_sp_route_sort'
|
||||
);
|
||||
SET @sql_idx := IF(
|
||||
@idx = 0,
|
||||
'ALTER TABLE `t_sales_product` ADD KEY `idx_sp_route_sort` (`RouteSort`, `IsActive`)',
|
||||
'SELECT ''skip idx_sp_route_sort'' AS msg'
|
||||
);
|
||||
PREPARE stmt_idx FROM @sql_idx;
|
||||
EXECUTE stmt_idx;
|
||||
DEALLOCATE PREPARE stmt_idx;
|
||||
81
sql/migrate_t_9610_align_item_fields.sql
Normal file
81
sql/migrate_t_9610_align_item_fields.sql
Normal file
@ -0,0 +1,81 @@
|
||||
-- 9610 商品明细命名对齐 t_order_declaration(幂等)
|
||||
-- ItemNo→Sku, ItemName→CnName, Gcode→ExHsCode, Price→UnitPrice, Qty→Quantity,
|
||||
-- TotalAmount→Amount, DeclareUnit→Unit
|
||||
-- 海关专属保留:Gmodel / Barcode / DestCountry / ItemCurrency / LegalQty / LegalUnit / Freight / SortNo
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
-- ItemNo → Sku
|
||||
SET @hasOld := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='ItemNo');
|
||||
SET @hasNew := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Sku');
|
||||
SET @sql := IF(@hasOld>0 AND @hasNew=0,
|
||||
'ALTER TABLE `t_9610_order_item` CHANGE COLUMN `ItemNo` `Sku` VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''SKU(对齐 t_order_declaration.Sku / 上游 itemNo)''',
|
||||
'SELECT ''skip ItemNo->Sku'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ItemName → CnName
|
||||
SET @hasOld := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='ItemName');
|
||||
SET @hasNew := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='CnName');
|
||||
SET @sql := IF(@hasOld>0 AND @hasNew=0,
|
||||
'ALTER TABLE `t_9610_order_item` CHANGE COLUMN `ItemName` `CnName` VARCHAR(250) NOT NULL DEFAULT '''' COMMENT ''中文品名(对齐 t_order_declaration.CnName / 上游 itemName)''',
|
||||
'SELECT ''skip ItemName->CnName'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Gcode → ExHsCode
|
||||
SET @hasOld := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Gcode');
|
||||
SET @hasNew := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='ExHsCode');
|
||||
SET @sql := IF(@hasOld>0 AND @hasNew=0,
|
||||
'ALTER TABLE `t_9610_order_item` CHANGE COLUMN `Gcode` `ExHsCode` VARCHAR(10) NULL COMMENT ''出口海关编码(对齐 t_order_declaration.ExHsCode / 上游 gcode)''',
|
||||
'SELECT ''skip Gcode->ExHsCode'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Price → UnitPrice
|
||||
SET @hasOld := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Price');
|
||||
SET @hasNew := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='UnitPrice');
|
||||
SET @sql := IF(@hasOld>0 AND @hasNew=0,
|
||||
'ALTER TABLE `t_9610_order_item` CHANGE COLUMN `Price` `UnitPrice` DECIMAL(19,5) NOT NULL DEFAULT 0 COMMENT ''单价(对齐 t_order_declaration.UnitPrice)''',
|
||||
'SELECT ''skip Price->UnitPrice'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- TotalAmount → Amount(可能尚未存在 TotalAmount)
|
||||
SET @hasOld := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='TotalAmount');
|
||||
SET @hasNew := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Amount');
|
||||
SET @sql := IF(@hasOld>0 AND @hasNew=0,
|
||||
'ALTER TABLE `t_9610_order_item` CHANGE COLUMN `TotalAmount` `Amount` DECIMAL(19,5) NULL COMMENT ''总价(对齐 t_order_declaration.Amount / 上游 totalPrice)''',
|
||||
'SELECT ''skip TotalAmount->Amount'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @hasNew := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Amount');
|
||||
SET @sql := IF(@hasNew=0,
|
||||
'ALTER TABLE `t_9610_order_item` ADD COLUMN `Amount` DECIMAL(19,5) NULL COMMENT ''总价(对齐 t_order_declaration.Amount)'' AFTER `UnitPrice`',
|
||||
'SELECT ''skip add Amount'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Qty → Quantity
|
||||
SET @hasOld := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Qty');
|
||||
SET @hasNew := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Quantity');
|
||||
SET @sql := IF(@hasOld>0 AND @hasNew=0,
|
||||
'ALTER TABLE `t_9610_order_item` CHANGE COLUMN `Qty` `Quantity` DECIMAL(19,5) NOT NULL DEFAULT 0 COMMENT ''数量(对齐 t_order_declaration.Quantity,精度保留海关小数)''',
|
||||
'SELECT ''skip Qty->Quantity'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- DeclareUnit → Unit
|
||||
SET @hasOld := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='DeclareUnit');
|
||||
SET @hasNew := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Unit');
|
||||
SET @sql := IF(@hasOld>0 AND @hasNew=0,
|
||||
'ALTER TABLE `t_9610_order_item` CHANGE COLUMN `DeclareUnit` `Unit` VARCHAR(3) NULL COMMENT ''计量单位(对齐 t_order_declaration.Unit / 上游 unit)''',
|
||||
'SELECT ''skip DeclareUnit->Unit'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @hasNew := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Unit');
|
||||
SET @sql := IF(@hasNew=0,
|
||||
'ALTER TABLE `t_9610_order_item` ADD COLUMN `Unit` VARCHAR(3) NULL COMMENT ''计量单位(对齐 t_order_declaration.Unit)'' AFTER `LegalQty`',
|
||||
'SELECT ''skip add Unit'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Gmodel 注释:海关申报要素(由运单 Model/Specifications/Attributes/Material 拼接)
|
||||
SET @has := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Gmodel');
|
||||
SET @sql := IF(@has>0,
|
||||
'ALTER TABLE `t_9610_order_item` MODIFY COLUMN `Gmodel` VARCHAR(255) NULL COMMENT ''申报要素(海关;由运单型号/规格等拼接,上游 gmodel)''',
|
||||
'SELECT ''skip Gmodel comment'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
118
sql/migrate_t_9610_align_order_nos.sql
Normal file
118
sql/migrate_t_9610_align_order_nos.sql
Normal file
@ -0,0 +1,118 @@
|
||||
-- 9610 单号命名对齐运单 t_order(幂等)
|
||||
-- ReferenceNo → ClientNo(客户单号)
|
||||
-- LogisticsNo 并入 ServiceNo(运单号)后删除 LogisticsNo
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
-- 1) ReferenceNo → ClientNo
|
||||
SET @hasRef := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ReferenceNo'
|
||||
);
|
||||
SET @hasClient := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ClientNo'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@hasRef > 0 AND @hasClient = 0,
|
||||
'ALTER TABLE `t_9610_order` CHANGE COLUMN `ReferenceNo` `ClientNo` VARCHAR(48) NOT NULL DEFAULT '''' COMMENT ''客户单号(对齐 t_order.ClientNo / 上游 referenceNo)''',
|
||||
'SELECT ''skip ReferenceNo->ClientNo'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 索引名顺带调整(旧 idx_9610_ref → idx_9610_client_no)
|
||||
SET @idxRef := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND INDEX_NAME='idx_9610_ref'
|
||||
);
|
||||
SET @idxClient := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND INDEX_NAME='idx_9610_client_no'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@idxRef > 0 AND @idxClient = 0,
|
||||
'ALTER TABLE `t_9610_order` RENAME INDEX `idx_9610_ref` TO `idx_9610_client_no`',
|
||||
'SELECT ''skip rename idx_9610_ref'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @idxClient := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND INDEX_NAME='idx_9610_client_no'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@idxClient = 0,
|
||||
'ALTER TABLE `t_9610_order` ADD KEY `idx_9610_client_no` (`ClientNo`)',
|
||||
'SELECT ''skip add idx_9610_client_no'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 2) LogisticsNo → 回填 ServiceNo,再删列
|
||||
SET @hasLog := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='LogisticsNo'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@hasLog > 0,
|
||||
'UPDATE `t_9610_order` SET `ServiceNo` = NULLIF(TRIM(`LogisticsNo`), '''') WHERE (`ServiceNo` IS NULL OR TRIM(`ServiceNo`) = '''') AND `LogisticsNo` IS NOT NULL AND TRIM(`LogisticsNo`) <> ''''',
|
||||
'SELECT ''skip backfill ServiceNo'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @idxLog := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND INDEX_NAME='idx_9610_logistics'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@idxLog > 0,
|
||||
'ALTER TABLE `t_9610_order` DROP INDEX `idx_9610_logistics`',
|
||||
'SELECT ''skip drop idx_9610_logistics'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @hasLog := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='LogisticsNo'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@hasLog > 0,
|
||||
'ALTER TABLE `t_9610_order` DROP COLUMN `LogisticsNo`',
|
||||
'SELECT ''skip drop LogisticsNo'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ServiceNo 索引(检索)
|
||||
SET @idxSvc := (
|
||||
SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND INDEX_NAME='idx_9610_service_no'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@idxSvc = 0,
|
||||
'ALTER TABLE `t_9610_order` ADD KEY `idx_9610_service_no` (`ServiceNo`)',
|
||||
'SELECT ''skip idx_9610_service_no'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- OrderNo 注释对齐:海关下游订单编号(非运单 DownstreamServiceNo)
|
||||
SET @hasOrderNo := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='OrderNo'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@hasOrderNo > 0,
|
||||
'ALTER TABLE `t_9610_order` MODIFY COLUMN `OrderNo` VARCHAR(32) NULL COMMENT ''海关下游订单编号(申报回写,非 t_order.DownstreamServiceNo)''',
|
||||
'SELECT ''skip OrderNo comment'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- DownstreamRefNo 注释
|
||||
SET @hasDr := (
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='DownstreamRefNo'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@hasDr > 0,
|
||||
'ALTER TABLE `t_9610_order` MODIFY COLUMN `DownstreamRefNo` VARCHAR(32) NULL COMMENT ''申报回写的 referenceNo 快照''',
|
||||
'SELECT ''skip DownstreamRefNo comment'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
170
sql/migrate_t_9610_manifest_fields.sql
Normal file
170
sql/migrate_t_9610_manifest_fields.sql
Normal file
@ -0,0 +1,170 @@
|
||||
-- 9610:补齐「获取清单数据」回写字段(幂等;列已存在则跳过)
|
||||
-- 字段名为 OMS 自有命名,与上游 CEB 字段对照见注释
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
-- ========== t_9610_order ==========
|
||||
|
||||
-- EnterpriseBizNo ← copNo 企业唯一编号
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='EnterpriseBizNo');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `EnterpriseBizNo` VARCHAR(48) NULL COMMENT ''企业唯一编号(上游 copNo)'' AFTER `DownstreamRefNo`', 'SELECT ''skip EnterpriseBizNo'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ManifestNo ← invtNo 清单编号
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ManifestNo');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `ManifestNo` VARCHAR(48) NULL COMMENT ''清单编号(上游 invtNo)'' AFTER `EnterpriseBizNo`', 'SELECT ''skip ManifestNo'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ClearanceTime ← clearanceTime 结关日期(原文,格式不固定)
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ClearanceTime');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `ClearanceTime` VARCHAR(48) NULL COMMENT ''结关日期原文'' AFTER `ManifestNo`', 'SELECT ''skip ClearanceTime'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- CustomsInboundTime ← warehouseTime 海关入库时间
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='CustomsInboundTime');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `CustomsInboundTime` VARCHAR(48) NULL COMMENT ''海关入库时间原文'' AFTER `ClearanceTime`', 'SELECT ''skip CustomsInboundTime'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- CustomsState ← customsStatus 海关状态
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='CustomsState');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `CustomsState` VARCHAR(4) NULL COMMENT ''海关状态'' AFTER `CustomsInboundTime`', 'SELECT ''skip CustomsState'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- DeclareCustomsCode ← customsCode 申报地海关
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='DeclareCustomsCode');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `DeclareCustomsCode` VARCHAR(4) NULL COMMENT ''申报地海关代码'' AFTER `CustomsState`', 'SELECT ''skip DeclareCustomsCode'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- EbpCode ← ebpCode
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='EbpCode');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `EbpCode` VARCHAR(18) NULL COMMENT ''电商平台代码'' AFTER `DeclareCustomsCode`', 'SELECT ''skip EbpCode'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- EbpName ← ebpName
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='EbpName');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `EbpName` VARCHAR(100) NULL COMMENT ''电商平台名称'' AFTER `EbpCode`', 'SELECT ''skip EbpName'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- LogisticsCorpCode ← logisticsCode
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='LogisticsCorpCode');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `LogisticsCorpCode` VARCHAR(18) NULL COMMENT ''物流企业代码'' AFTER `EbpName`', 'SELECT ''skip LogisticsCorpCode'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- LogisticsCorpName ← logisticsName
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='LogisticsCorpName');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `LogisticsCorpName` VARCHAR(100) NULL COMMENT ''物流企业名称'' AFTER `LogisticsCorpCode`', 'SELECT ''skip LogisticsCorpName'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- IeMark ← ieFlag I/E
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='IeMark');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `IeMark` VARCHAR(4) NULL COMMENT ''进出口标记 I/E'' AFTER `LogisticsCorpName`', 'SELECT ''skip IeMark'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ExportPortCode ← portCode
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ExportPortCode');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `ExportPortCode` VARCHAR(4) NULL COMMENT ''出口口岸代码'' AFTER `IeMark`', 'SELECT ''skip ExportPortCode'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ExportDate ← ieDate
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ExportDate');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `ExportDate` VARCHAR(32) NULL COMMENT ''出口日期原文'' AFTER `ExportPortCode`', 'SELECT ''skip ExportDate'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- DeclareAgentCode ← agentCode
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='DeclareAgentCode');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `DeclareAgentCode` VARCHAR(18) NULL COMMENT ''申报企业代码'' AFTER `ExportDate`', 'SELECT ''skip DeclareAgentCode'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- DeclareAgentName ← agentName
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='DeclareAgentName');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `DeclareAgentName` VARCHAR(100) NULL COMMENT ''申报企业名称'' AFTER `DeclareAgentCode`', 'SELECT ''skip DeclareAgentName'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- TradeModeCode ← tradeMode
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='TradeModeCode');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `TradeModeCode` VARCHAR(4) NULL COMMENT ''贸易方式代码'' AFTER `DeclareAgentName`', 'SELECT ''skip TradeModeCode'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- TransportMode ← trafMode
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='TransportMode');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `TransportMode` VARCHAR(4) NULL COMMENT ''运输方式代码'' AFTER `TradeModeCode`', 'SELECT ''skip TransportMode'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- FreightCurrency ← fCurrency
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='FreightCurrency');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `FreightCurrency` VARCHAR(4) NULL COMMENT ''运费币制'' AFTER `Freight`', 'SELECT ''skip FreightCurrency'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- FreightMark ← fFlag 1率/2单价/3总价
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='FreightMark');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `FreightMark` INT NULL COMMENT ''运费标志 1率2单价3总价'' AFTER `FreightCurrency`', 'SELECT ''skip FreightMark'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- PackageType ← wrapType
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='PackageType');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `PackageType` INT NULL COMMENT ''包装种类代码'' AFTER `FreightMark`', 'SELECT ''skip PackageType'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- PackageQty ← packNo
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='PackageQty');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `PackageQty` INT NULL COMMENT ''件数'' AFTER `PackageType`', 'SELECT ''skip PackageQty'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ManifestSyncTime 最近一次清单回写时间
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ManifestSyncTime');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order` ADD COLUMN `ManifestSyncTime` DATETIME NULL COMMENT ''清单数据最近同步时间'' AFTER `DeclareTime`', 'SELECT ''skip ManifestSyncTime'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 运费精度对齐上游 DECIMAL(19,5)
|
||||
SET @colType := (
|
||||
SELECT COLUMN_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='Freight'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@colType IS NOT NULL AND @colType NOT LIKE 'decimal(19,5)%',
|
||||
'ALTER TABLE `t_9610_order` MODIFY COLUMN `Freight` DECIMAL(19,5) NULL COMMENT ''运费''',
|
||||
'SELECT ''skip Freight precision'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ManifestNo 索引(幂等)
|
||||
SET @idx := (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND INDEX_NAME='idx_9610_manifest');
|
||||
SET @sql := IF(@idx=0, 'ALTER TABLE `t_9610_order` ADD KEY `idx_9610_manifest` (`ManifestNo`)', 'SELECT ''skip idx_9610_manifest'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ========== t_9610_order_item ==========
|
||||
|
||||
-- Barcode ← barCode
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Barcode');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order_item` ADD COLUMN `Barcode` VARCHAR(255) NULL COMMENT ''条形码'' AFTER `Gmodel`', 'SELECT ''skip Barcode'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- DestCountry ← country 目的国
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='DestCountry');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order_item` ADD COLUMN `DestCountry` VARCHAR(255) NULL COMMENT ''目的国(地区)代码'' AFTER `Barcode`', 'SELECT ''skip DestCountry'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ItemCurrency ← currency
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='ItemCurrency');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order_item` ADD COLUMN `ItemCurrency` VARCHAR(3) NULL COMMENT ''币制'' AFTER `DestCountry`', 'SELECT ''skip ItemCurrency'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- LegalQty ← qty1 法定数量
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='LegalQty');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order_item` ADD COLUMN `LegalQty` DECIMAL(19,5) NULL COMMENT ''法定数量'' AFTER `Quantity`', 'SELECT ''skip LegalQty'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Unit ← unit(对齐运单;旧名 DeclareUnit 由 align_item_fields 迁移)
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Unit');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order_item` ADD COLUMN `Unit` VARCHAR(3) NULL COMMENT ''计量单位'' AFTER `LegalQty`', 'SELECT ''skip Unit'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- LegalUnit ← unit1
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='LegalUnit');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order_item` ADD COLUMN `LegalUnit` VARCHAR(3) NULL COMMENT ''法定计量单位'' AFTER `Unit`', 'SELECT ''skip LegalUnit'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Amount ← totalPrice
|
||||
SET @col := (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Amount');
|
||||
SET @sql := IF(@col=0, 'ALTER TABLE `t_9610_order_item` ADD COLUMN `Amount` DECIMAL(19,5) NULL COMMENT ''总价'' AFTER `UnitPrice`', 'SELECT ''skip Amount'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
122
sql/migrate_t_9610_order.sql
Normal file
122
sql/migrate_t_9610_order.sql
Normal file
@ -0,0 +1,122 @@
|
||||
-- 9610 汇总申报单(ExportCustomsMode=9610Summary)
|
||||
-- 幂等:表已存在则跳过
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
-- t_9610_order
|
||||
SET @exists := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_9610_order'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@exists = 0,
|
||||
'CREATE TABLE `t_9610_order` (
|
||||
`Id` INT NOT NULL AUTO_INCREMENT COMMENT ''主键'',
|
||||
`OrderId` INT NOT NULL COMMENT ''关联运单 t_order.Id'',
|
||||
`ServiceNo` VARCHAR(64) NULL COMMENT ''运单号冗余'',
|
||||
`CustomerId` INT NOT NULL DEFAULT 0 COMMENT ''客户Id'',
|
||||
`Status` VARCHAR(16) NOT NULL DEFAULT ''pending'' COMMENT ''pending待申报 / declared已申报'',
|
||||
`PlatformCode` VARCHAR(16) NOT NULL DEFAULT '''' COMMENT ''电商平台'',
|
||||
`EcomCorpCode` VARCHAR(18) NULL COMMENT ''电商企业代码'',
|
||||
`EcomCorpName` VARCHAR(100) NULL COMMENT ''电商企业名称'',
|
||||
`ProducerCode` VARCHAR(18) NULL COMMENT ''生产销售单位代码'',
|
||||
`ProducerName` VARCHAR(100) NULL COMMENT ''生产销售单位'',
|
||||
`ClientNo` VARCHAR(48) NOT NULL DEFAULT '''' COMMENT ''客户单号(对齐 t_order.ClientNo)'',
|
||||
`BusinessType` CHAR(1) NOT NULL DEFAULT ''B'' COMMENT ''业务类型 A简化/B汇总'',
|
||||
`Region` VARCHAR(10) NULL COMMENT ''区域 SZ/other'',
|
||||
`AgentDeclare` VARCHAR(2) NULL COMMENT ''是否代理申报 是/否'',
|
||||
`ActualSellerCode` VARCHAR(18) NULL COMMENT ''实际卖家信用代码'',
|
||||
`PreRecordFlag` CHAR(1) NULL COMMENT ''汇总预录 0已预录 1未预录'',
|
||||
`IsSummarySimple` TINYINT NOT NULL DEFAULT 0 COMMENT ''是否汇总简化申报'',
|
||||
`GrossWeight` DECIMAL(19,5) NOT NULL DEFAULT 0 COMMENT ''毛重KG'',
|
||||
`Currency` VARCHAR(4) NOT NULL DEFAULT ''CNY'' COMMENT ''币制'',
|
||||
`DestCountry` VARCHAR(4) NOT NULL DEFAULT '''' COMMENT ''目的国'',
|
||||
`Freight` DECIMAL(19,5) NULL COMMENT ''运费'',
|
||||
`FreightCurrency` VARCHAR(4) NULL COMMENT ''运费币制'',
|
||||
`FreightMark` INT NULL COMMENT ''运费标志 1率2单价3总价'',
|
||||
`PackageType` INT NULL COMMENT ''包装种类代码'',
|
||||
`PackageQty` INT NULL COMMENT ''件数'',
|
||||
`ShipmentNo` VARCHAR(32) NULL COMMENT ''出货单号'',
|
||||
`CustomsOrderNo` VARCHAR(32) NULL COMMENT ''海关订单号(申报回写)'',
|
||||
`DownstreamRefNo` VARCHAR(32) NULL COMMENT ''申报回写的 referenceNo 快照'',
|
||||
`EnterpriseBizNo` VARCHAR(48) NULL COMMENT ''企业唯一编号(上游 copNo)'',
|
||||
`ManifestNo` VARCHAR(48) NULL COMMENT ''清单编号(上游 invtNo)'',
|
||||
`ClearanceTime` VARCHAR(48) NULL COMMENT ''结关日期原文'',
|
||||
`CustomsInboundTime` VARCHAR(48) NULL COMMENT ''海关入库时间原文'',
|
||||
`CustomsState` VARCHAR(4) NULL COMMENT ''海关状态'',
|
||||
`DeclareCustomsCode` VARCHAR(4) NULL COMMENT ''申报地海关代码'',
|
||||
`PlatformCorpCode` VARCHAR(18) NULL COMMENT ''电商平台代码'',
|
||||
`PlatformName` VARCHAR(100) NULL COMMENT ''电商平台名称'',
|
||||
`LogisticsCorpCode` VARCHAR(18) NULL COMMENT ''物流企业代码'',
|
||||
`LogisticsCorpName` VARCHAR(100) NULL COMMENT ''物流企业名称'',
|
||||
`IeMark` VARCHAR(4) NULL COMMENT ''进出口标记 I/E'',
|
||||
`ExportPortCode` VARCHAR(4) NULL COMMENT ''出口口岸代码'',
|
||||
`ExportDate` VARCHAR(32) NULL COMMENT ''出口日期原文'',
|
||||
`DeclareAgentCode` VARCHAR(18) NULL COMMENT ''申报企业代码'',
|
||||
`DeclareAgentName` VARCHAR(100) NULL COMMENT ''申报企业名称'',
|
||||
`TradeModeCode` VARCHAR(4) NULL COMMENT ''贸易方式代码'',
|
||||
`TransportMode` VARCHAR(4) NULL COMMENT ''运输方式代码'',
|
||||
`DeclareTime` DATETIME NULL COMMENT ''申报成功时间'',
|
||||
`ManifestSyncTime` DATETIME NULL COMMENT ''清单数据最近同步时间'',
|
||||
`FailReason` VARCHAR(512) NULL COMMENT ''最近失败原因'',
|
||||
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT ''软删除'',
|
||||
`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 ''更新人'',
|
||||
`UpdateTime` DATETIME NULL COMMENT ''更新时间'',
|
||||
PRIMARY KEY (`Id`),
|
||||
UNIQUE KEY `uk_9610_order_id` (`OrderId`),
|
||||
KEY `idx_9610_status_ctime` (`Status`, `CreateTime`),
|
||||
KEY `idx_9610_service_no` (`ServiceNo`),
|
||||
KEY `idx_9610_client_no` (`ClientNo`),
|
||||
KEY `idx_9610_customer` (`CustomerId`, `CreateTime`),
|
||||
KEY `idx_9610_manifest` (`ManifestNo`)
|
||||
) COMMENT=''9610汇总申报单''',
|
||||
'SELECT ''skip t_9610_order'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- t_9610_order_item
|
||||
SET @exists := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 't_9610_order_item'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@exists = 0,
|
||||
'CREATE TABLE `t_9610_order_item` (
|
||||
`Id` INT NOT NULL AUTO_INCREMENT COMMENT ''主键'',
|
||||
`Customs9610OrderId` INT NOT NULL COMMENT ''t_9610_order.Id'',
|
||||
`OrderDeclarationId` INT NULL COMMENT ''溯源 t_order_declaration.Id'',
|
||||
`Sku` VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''SKU(对齐 t_order_declaration.Sku)'',
|
||||
`CnName` VARCHAR(250) NOT NULL DEFAULT '''' COMMENT ''中文品名(对齐 t_order_declaration.CnName)'',
|
||||
`ExHsCode` VARCHAR(10) NULL COMMENT ''出口海关编码(对齐 t_order_declaration.ExHsCode)'',
|
||||
`DeclareElement` VARCHAR(255) NULL COMMENT ''申报要素'',
|
||||
`Barcode` VARCHAR(255) NULL COMMENT ''条形码'',
|
||||
`DestCountry` VARCHAR(255) NULL COMMENT ''目的国(地区)代码'',
|
||||
`ItemCurrency` VARCHAR(3) NULL COMMENT ''币制'',
|
||||
`UnitPrice` DECIMAL(19,5) NOT NULL DEFAULT 0 COMMENT ''单价(对齐 t_order_declaration.UnitPrice)'',
|
||||
`Amount` DECIMAL(19,5) NULL COMMENT ''总价(对齐 t_order_declaration.Amount)'',
|
||||
`Quantity` DECIMAL(19,5) NOT NULL DEFAULT 0 COMMENT ''数量(对齐 t_order_declaration.Quantity)'',
|
||||
`LegalQty` DECIMAL(19,5) NULL COMMENT ''法定数量'',
|
||||
`Unit` VARCHAR(3) NULL COMMENT ''计量单位(对齐 t_order_declaration.Unit)'',
|
||||
`LegalUnit` VARCHAR(3) NULL COMMENT ''法定计量单位'',
|
||||
`Freight` DECIMAL(12,4) NULL COMMENT ''行运费'',
|
||||
`SortNo` INT NOT NULL DEFAULT 0 COMMENT ''排序'',
|
||||
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT ''软删除'',
|
||||
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT ''创建人'',
|
||||
`CreateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ''创建时间'',
|
||||
`UpdateUserId` INT NULL COMMENT ''更新人'',
|
||||
`UpdateTime` DATETIME NULL COMMENT ''更新时间'',
|
||||
PRIMARY KEY (`Id`),
|
||||
KEY `idx_9610_item_header` (`Customs9610OrderId`, `IsDeleted`)
|
||||
) COMMENT=''9610申报商品明细''',
|
||||
'SELECT ''skip t_9610_order_item'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
90
sql/migrate_t_9610_rename_by_meaning.sql
Normal file
90
sql/migrate_t_9610_rename_by_meaning.sql
Normal file
@ -0,0 +1,90 @@
|
||||
-- 9610 其余字段按业务含义重命名(幂等 CHANGE)
|
||||
-- 申报头:Ebp→PlatformCode, Ebc*→EcomCorp*, Owner*→Producer*, FormType→BusinessType,
|
||||
-- IsAgent→AgentDeclare, ActBuyer→ActualSellerCode, SoLabelFlag→PreRecordFlag,
|
||||
-- IsSimple→IsSummarySimple, Country→DestCountry, BlNo→ShipmentNo,
|
||||
-- EbpCode/EbpName→PlatformCorpCode/PlatformName, OrderNo→CustomsOrderNo
|
||||
-- 明细:Gmodel→DeclareElement
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
-- helper pattern repeated
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='Ebp');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='PlatformCode');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `Ebp` `PlatformCode` VARCHAR(16) NOT NULL DEFAULT '''' COMMENT ''电商平台''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='EbcCode');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='EcomCorpCode');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `EbcCode` `EcomCorpCode` VARCHAR(18) NULL COMMENT ''电商企业代码''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='EbcName');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='EcomCorpName');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `EbcName` `EcomCorpName` VARCHAR(100) NULL COMMENT ''电商企业名称''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='OwnerCode');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ProducerCode');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `OwnerCode` `ProducerCode` VARCHAR(18) NULL COMMENT ''生产销售单位代码''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='OwnerName');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ProducerName');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `OwnerName` `ProducerName` VARCHAR(100) NULL COMMENT ''生产销售单位''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='FormType');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='BusinessType');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `FormType` `BusinessType` CHAR(1) NOT NULL DEFAULT ''B'' COMMENT ''业务类型 A简化/B汇总''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='IsAgent');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='AgentDeclare');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `IsAgent` `AgentDeclare` VARCHAR(2) NULL COMMENT ''是否代理申报 是/否''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ActBuyer');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ActualSellerCode');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `ActBuyer` `ActualSellerCode` VARCHAR(18) NULL COMMENT ''实际卖家信用代码''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='SoLabelFlag');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='PreRecordFlag');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `SoLabelFlag` `PreRecordFlag` CHAR(1) NULL COMMENT ''汇总预录 0已预录/1未预录''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='IsSimple');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='IsSummarySimple');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `IsSimple` `IsSummarySimple` TINYINT NOT NULL DEFAULT 0 COMMENT ''是否汇总简化申报''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='Country');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='DestCountry');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `Country` `DestCountry` VARCHAR(4) NOT NULL DEFAULT '''' COMMENT ''目的国''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='BlNo');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='ShipmentNo');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `BlNo` `ShipmentNo` VARCHAR(32) NULL COMMENT ''出货单号''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='OrderNo');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='CustomsOrderNo');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `OrderNo` `CustomsOrderNo` VARCHAR(32) NULL COMMENT ''海关订单号''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='EbpCode');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='PlatformCorpCode');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `EbpCode` `PlatformCorpCode` VARCHAR(18) NULL COMMENT ''电商平台代码''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='EbpName');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order' AND COLUMN_NAME='PlatformName');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order` CHANGE COLUMN `EbpName` `PlatformName` VARCHAR(100) NULL COMMENT ''电商平台名称''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @o:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='Gmodel');
|
||||
SET @n:=(SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='t_9610_order_item' AND COLUMN_NAME='DeclareElement');
|
||||
SET @sql:=IF(@o>0 AND @n=0,'ALTER TABLE `t_9610_order_item` CHANGE COLUMN `Gmodel` `DeclareElement` VARCHAR(255) NULL COMMENT ''申报要素''','SELECT 1');
|
||||
PREPARE s FROM @sql; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
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
|
||||
|
||||
49
sql/migrate_t_pickup_order_line_service_no.sql
Normal file
49
sql/migrate_t_pickup_order_line_service_no.sql
Normal file
@ -0,0 +1,49 @@
|
||||
-- 揽收明细:补齐运单号/客户单号快照(幂等;列已存在则跳过)
|
||||
-- 对应实体 PickupOrderLine.ServiceNo / ClientNo
|
||||
-- 报错 Unknown column 'ServiceNo' in 'field list' 时执行本脚本
|
||||
-- MySQL 5.7+ / 8.0
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
-- ServiceNo
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_pickup_order_line'
|
||||
AND COLUMN_NAME = 'ServiceNo'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col = 0,
|
||||
'ALTER TABLE `t_pickup_order_line` ADD COLUMN `ServiceNo` VARCHAR(64) NULL COMMENT ''运单号快照(便于列表)'' AFTER `OrderId`',
|
||||
'SELECT ''skip ServiceNo'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ClientNo
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 't_pickup_order_line'
|
||||
AND COLUMN_NAME = 'ClientNo'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col = 0,
|
||||
'ALTER TABLE `t_pickup_order_line` ADD COLUMN `ClientNo` VARCHAR(64) NULL COMMENT ''客户单号快照'' AFTER `ServiceNo`',
|
||||
'SELECT ''skip ClientNo'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 存量行从订单表回填快照(仅空值)
|
||||
UPDATE `t_pickup_order_line` pl
|
||||
INNER JOIN `t_order` o ON o.`Id` = pl.`OrderId`
|
||||
SET
|
||||
pl.`ServiceNo` = IFNULL(NULLIF(pl.`ServiceNo`, ''), o.`ServiceNo`),
|
||||
pl.`ClientNo` = IFNULL(NULLIF(pl.`ClientNo`, ''), o.`ClientNo`)
|
||||
WHERE pl.`IsDeleted` = 0
|
||||
AND (pl.`ServiceNo` IS NULL OR pl.`ServiceNo` = '' OR pl.`ClientNo` IS NULL OR pl.`ClientNo` = '');
|
||||
20
sql/migrate_user_wecom_kf_url.sql
Normal file
20
sql/migrate_user_wecom_kf_url.sql
Normal file
@ -0,0 +1,20 @@
|
||||
-- user 表:企业微信客服接入链接(幂等;列已存在则跳过)
|
||||
-- MySQL 5.7+ / 8.0
|
||||
|
||||
SET @db := DATABASE();
|
||||
|
||||
SET @col := (
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = @db
|
||||
AND TABLE_NAME = 'user'
|
||||
AND COLUMN_NAME = 'WecomKfUrl'
|
||||
);
|
||||
SET @sql := IF(
|
||||
@col = 0,
|
||||
'ALTER TABLE `user` ADD COLUMN `WecomKfUrl` VARCHAR(512) NULL COMMENT ''企业微信客服接入链接''',
|
||||
'SELECT ''skip WecomKfUrl'' AS msg'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
@ -40,6 +40,11 @@ public class SalesProductDetailOutput
|
||||
/// </summary>
|
||||
public string? TradeTerms { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 官网线路排序,数值越小越靠前。
|
||||
/// </summary>
|
||||
public int RouteSort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可选附加服务字典 code JSON 数组。
|
||||
/// </summary>
|
||||
|
||||
@ -35,6 +35,11 @@ public class SalesProductListOutput
|
||||
/// </summary>
|
||||
public int? MaxTimeliness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 官网线路排序,数值越小越靠前。
|
||||
/// </summary>
|
||||
public int RouteSort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可选附加服务字典 code JSON 数组。
|
||||
/// </summary>
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
namespace Oms.Application.Dtos.Channels;
|
||||
|
||||
/// <summary>
|
||||
/// 官网线路目录出参(公开字段)。
|
||||
/// </summary>
|
||||
public class SalesProductPortalCatalogOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 分组列表(已排除国内揽收,且仅含有产品的分组)。
|
||||
/// </summary>
|
||||
public List<SalesProductPortalGroupOutput> Groups { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 启用中的销售产品(已排除国内揽收)。
|
||||
/// </summary>
|
||||
public List<SalesProductPortalItemOutput> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 官网线路分组。
|
||||
/// </summary>
|
||||
public class SalesProductPortalGroupOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 分组编码(字典 code)。
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 分组名称。
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 官网线路产品卡片字段。
|
||||
/// </summary>
|
||||
public class SalesProductPortalItemOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键。
|
||||
/// </summary>
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 产品名称。
|
||||
/// </summary>
|
||||
public string ProductName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 产品分组编码。
|
||||
/// </summary>
|
||||
public string? ProductGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 产品分组名称。
|
||||
/// </summary>
|
||||
public string ProductGroupName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 最小时效(天)。
|
||||
/// </summary>
|
||||
public int? MinTimeliness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大时效(天)。
|
||||
/// </summary>
|
||||
public int? MaxTimeliness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 贸易条款(DDP/DDU)。
|
||||
/// </summary>
|
||||
public string? TradeTerms { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 官网线路排序,数值越小越靠前。
|
||||
/// </summary>
|
||||
public int RouteSort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 产品说明。
|
||||
/// </summary>
|
||||
public string? ProductDescription { get; set; }
|
||||
}
|
||||
@ -42,6 +42,11 @@ public class SalesProductSaveInput
|
||||
/// </summary>
|
||||
public string? TradeTerms { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 官网线路排序,数值越小越靠前。
|
||||
/// </summary>
|
||||
public int RouteSort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可选附加服务字典 code JSON 数组。
|
||||
/// </summary>
|
||||
|
||||
@ -118,4 +118,10 @@ public class CreateCustomerInput
|
||||
[StringLength(16)]
|
||||
public string? InviteCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 顾问邀请码(所属顾问绑定,值为员工 Account;仅 CustomerService 为空时生效)。
|
||||
/// </summary>
|
||||
[StringLength(64)]
|
||||
public string? AdvisorCode { get; set; }
|
||||
|
||||
}
|
||||
|
||||
@ -19,4 +19,9 @@ public class CustomerServiceAdvisorOutput
|
||||
/// 微信二维码(URL 或 Base64)。
|
||||
/// </summary>
|
||||
public string? WechatQrCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 企业微信客服接入链接。
|
||||
/// </summary>
|
||||
public string? WecomKfUrl { get; set; }
|
||||
}
|
||||
|
||||
@ -32,12 +32,32 @@ public class ReceivablePayablePaginatedInput
|
||||
public int? CustomerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单创建时间起。
|
||||
/// 客户编码关键词(模糊匹配)。
|
||||
/// </summary>
|
||||
public string? CustomerCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 仅保留各运单费用项的当前生效阶段。
|
||||
/// </summary>
|
||||
public bool EffectiveStageOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单创建时间起(timeField=createTime 时生效,兼容旧参数)。
|
||||
/// </summary>
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单创建时间止。
|
||||
/// 订单创建时间止(timeField=createTime 时生效,兼容旧参数)。
|
||||
/// </summary>
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间筛选字段:createTime / receiveTime / shipTime / payTime。
|
||||
/// </summary>
|
||||
public string? TimeField { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付状态:0 未支付,1 已支付。
|
||||
/// </summary>
|
||||
public int? IsPaid { get; set; }
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ namespace Oms.Application.Dtos.Finance;
|
||||
/// </summary>
|
||||
public class ReceivablePayablePaginatedOutput
|
||||
{
|
||||
public int OrderId { get; set; }
|
||||
|
||||
public string ServiceNo { get; set; } = string.Empty;
|
||||
|
||||
public string? ClientNo { get; set; }
|
||||
@ -37,6 +39,8 @@ public class ReceivablePayablePaginatedOutput
|
||||
|
||||
public string CostItem { get; set; } = string.Empty;
|
||||
|
||||
public string? CostItemName { get; set; }
|
||||
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
public string Currency { get; set; } = string.Empty;
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
namespace Oms.Application.Dtos.Finance;
|
||||
|
||||
/// <summary>
|
||||
/// 应收应付汇总出参。
|
||||
/// </summary>
|
||||
public class ReceivablePayableSummaryOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 匹配筛选条件的费用明细总行数。
|
||||
/// </summary>
|
||||
public int TotalLines { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 按费用类型 + 币种汇总。
|
||||
/// </summary>
|
||||
public List<ReceivablePayableSummaryItemOutput> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
public class ReceivablePayableSummaryItemOutput
|
||||
{
|
||||
public string FeeType { get; set; } = string.Empty;
|
||||
|
||||
public string? FeeTypeName { get; set; }
|
||||
|
||||
public string Currency { get; set; } = string.Empty;
|
||||
|
||||
public decimal TotalAmount { get; set; }
|
||||
|
||||
public int LineCount { get; set; }
|
||||
}
|
||||
313
src/Oms.Application/Dtos/Orders/Customs9610Dtos.cs
Normal file
313
src/Oms.Application/Dtos/Orders/Customs9610Dtos.cs
Normal file
@ -0,0 +1,313 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Oms.Application.Dtos.Orders;
|
||||
|
||||
public class Customs9610ListInput
|
||||
{
|
||||
public int Page { get; set; } = 1;
|
||||
|
||||
public int PageSize { get; set; } = 30;
|
||||
|
||||
public int? CustomerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// pending / declared;空=全部。
|
||||
/// </summary>
|
||||
public string? Status { get; set; }
|
||||
|
||||
public string? Keyword { get; set; }
|
||||
|
||||
public DateTime? CreateTimeStart { get; set; }
|
||||
|
||||
public DateTime? CreateTimeEnd { get; set; }
|
||||
}
|
||||
|
||||
public class Customs9610IdInput
|
||||
{
|
||||
[Required]
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
public class Customs9610IdsInput
|
||||
{
|
||||
[Required]
|
||||
[MinLength(1)]
|
||||
public List<int> Ids { get; set; } = [];
|
||||
}
|
||||
|
||||
public class Customs9610StatusCountsOutput
|
||||
{
|
||||
public int Total { get; set; }
|
||||
|
||||
public int Pending { get; set; }
|
||||
|
||||
public int Declared { get; set; }
|
||||
}
|
||||
|
||||
public class Customs9610ItemInput
|
||||
{
|
||||
public int? Id { get; set; }
|
||||
|
||||
public int? OrderDeclarationId { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(32)]
|
||||
public string Sku { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(250)]
|
||||
public string CnName { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(10)]
|
||||
public string? ExHsCode { get; set; }
|
||||
|
||||
[StringLength(255)]
|
||||
public string? DeclareElement { get; set; }
|
||||
|
||||
[Required]
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
[Required]
|
||||
public decimal Quantity { get; set; }
|
||||
|
||||
public decimal? Freight { get; set; }
|
||||
|
||||
public int SortNo { get; set; }
|
||||
}
|
||||
|
||||
public class Customs9610UpdateInput
|
||||
{
|
||||
[Required]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(16)]
|
||||
public string PlatformCode { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(18)]
|
||||
public string? EcomCorpCode { get; set; }
|
||||
|
||||
[StringLength(100)]
|
||||
public string? EcomCorpName { get; set; }
|
||||
|
||||
[StringLength(18)]
|
||||
public string? ProducerCode { get; set; }
|
||||
|
||||
[StringLength(100)]
|
||||
public string? ProducerName { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(48)]
|
||||
public string ClientNo { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(48)]
|
||||
public string ServiceNo { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[StringLength(1)]
|
||||
public string BusinessType { get; set; } = "B";
|
||||
|
||||
[StringLength(10)]
|
||||
public string? Region { get; set; }
|
||||
|
||||
[StringLength(2)]
|
||||
public string? AgentDeclare { get; set; }
|
||||
|
||||
[StringLength(18)]
|
||||
public string? ActualSellerCode { get; set; }
|
||||
|
||||
[StringLength(1)]
|
||||
public string? PreRecordFlag { get; set; }
|
||||
|
||||
public bool IsSummarySimple { get; set; }
|
||||
|
||||
[Required]
|
||||
public decimal GrossWeight { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(4)]
|
||||
public string Currency { get; set; } = "CNY";
|
||||
|
||||
[Required]
|
||||
[StringLength(4)]
|
||||
public string DestCountry { get; set; } = string.Empty;
|
||||
|
||||
public decimal? Freight { get; set; }
|
||||
|
||||
[StringLength(32)]
|
||||
public string? ShipmentNo { get; set; }
|
||||
|
||||
[MinLength(1)]
|
||||
public List<Customs9610ItemInput> Items { get; set; } = [];
|
||||
}
|
||||
|
||||
public class Customs9610ItemOutput
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int? OrderDeclarationId { get; set; }
|
||||
|
||||
public string Sku { get; set; } = string.Empty;
|
||||
|
||||
public string CnName { get; set; } = string.Empty;
|
||||
|
||||
public string? ExHsCode { get; set; }
|
||||
|
||||
public string? DeclareElement { get; set; }
|
||||
|
||||
public string? Barcode { get; set; }
|
||||
|
||||
public string? DestCountry { get; set; }
|
||||
|
||||
public string? ItemCurrency { get; set; }
|
||||
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
public decimal? Amount { get; set; }
|
||||
|
||||
public decimal Quantity { get; set; }
|
||||
|
||||
public decimal? LegalQty { get; set; }
|
||||
|
||||
public string? Unit { get; set; }
|
||||
|
||||
public string? LegalUnit { get; set; }
|
||||
|
||||
public decimal? Freight { get; set; }
|
||||
|
||||
public int SortNo { get; set; }
|
||||
}
|
||||
|
||||
public class Customs9610OrderOutput
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public int OrderId { get; set; }
|
||||
|
||||
public string? ServiceNo { get; set; }
|
||||
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
public string? CustomerName { get; set; }
|
||||
|
||||
public string Status { get; set; } = string.Empty;
|
||||
|
||||
public string PlatformCode { get; set; } = string.Empty;
|
||||
|
||||
public string? EcomCorpCode { get; set; }
|
||||
|
||||
public string? EcomCorpName { get; set; }
|
||||
|
||||
public string? ProducerCode { get; set; }
|
||||
|
||||
public string? ProducerName { get; set; }
|
||||
|
||||
public string ClientNo { get; set; } = string.Empty;
|
||||
|
||||
public string BusinessType { get; set; } = "B";
|
||||
|
||||
public string? Region { get; set; }
|
||||
|
||||
public string? AgentDeclare { get; set; }
|
||||
|
||||
public string? ActualSellerCode { get; set; }
|
||||
|
||||
public string? PreRecordFlag { get; set; }
|
||||
|
||||
public bool IsSummarySimple { get; set; }
|
||||
|
||||
public decimal GrossWeight { get; set; }
|
||||
|
||||
public string Currency { get; set; } = "CNY";
|
||||
|
||||
public string DestCountry { get; set; } = string.Empty;
|
||||
|
||||
public decimal? Freight { get; set; }
|
||||
|
||||
public string? FreightCurrency { get; set; }
|
||||
|
||||
public int? FreightMark { get; set; }
|
||||
|
||||
public int? PackageType { get; set; }
|
||||
|
||||
public int? PackageQty { get; set; }
|
||||
|
||||
public string? ShipmentNo { get; set; }
|
||||
|
||||
public string? CustomsOrderNo { get; set; }
|
||||
|
||||
public string? DownstreamRefNo { get; set; }
|
||||
|
||||
public string? EnterpriseBizNo { get; set; }
|
||||
|
||||
public string? ManifestNo { get; set; }
|
||||
|
||||
public string? ClearanceTime { get; set; }
|
||||
|
||||
public string? CustomsInboundTime { get; set; }
|
||||
|
||||
public string? CustomsState { get; set; }
|
||||
|
||||
public string? DeclareCustomsCode { get; set; }
|
||||
|
||||
public string? PlatformCorpCode { get; set; }
|
||||
|
||||
public string? PlatformName { get; set; }
|
||||
|
||||
public string? LogisticsCorpCode { get; set; }
|
||||
|
||||
public string? LogisticsCorpName { get; set; }
|
||||
|
||||
public string? IeMark { get; set; }
|
||||
|
||||
public string? ExportPortCode { get; set; }
|
||||
|
||||
public string? ExportDate { get; set; }
|
||||
|
||||
public string? DeclareAgentCode { get; set; }
|
||||
|
||||
public string? DeclareAgentName { get; set; }
|
||||
|
||||
public string? TradeModeCode { get; set; }
|
||||
|
||||
public string? TransportMode { get; set; }
|
||||
|
||||
public DateTime? DeclareTime { get; set; }
|
||||
|
||||
public DateTime? ManifestSyncTime { get; set; }
|
||||
|
||||
public string? FailReason { get; set; }
|
||||
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
public DateTime? UpdateTime { get; set; }
|
||||
|
||||
public List<Customs9610ItemOutput>? Items { get; set; }
|
||||
}
|
||||
|
||||
public class Customs9610DeclareResult
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public bool Success { get; set; }
|
||||
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
public string? CustomsOrderNo { get; set; }
|
||||
|
||||
public string? Status { get; set; }
|
||||
|
||||
public int? HttpStatusCode { get; set; }
|
||||
|
||||
public string? ResponseBody { get; set; }
|
||||
}
|
||||
|
||||
public class Customs9610DeclareBatchOutput
|
||||
{
|
||||
public int SuccessCount { get; set; }
|
||||
|
||||
public int FailCount { get; set; }
|
||||
|
||||
public List<Customs9610DeclareResult> Items { get; set; } = [];
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
namespace Oms.Application.Dtos.Orders;
|
||||
|
||||
/// <summary>
|
||||
/// 工作台代发订单统计。
|
||||
/// </summary>
|
||||
public class FulfillWorkplaceStatsOutput
|
||||
{
|
||||
/// <summary>待绑定包裹/库存。</summary>
|
||||
public int PendingBind { get; set; }
|
||||
|
||||
/// <summary>国内快递在途。</summary>
|
||||
public int DomesticInTransit { get; set; }
|
||||
|
||||
/// <summary>已到仓,待仓库拣货。</summary>
|
||||
public int ArrivedPendingPick { get; set; }
|
||||
|
||||
/// <summary>已出库(拣货扣减完成)。</summary>
|
||||
public int OutboundDone { get; set; }
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -57,6 +57,11 @@ public class OrderPriceCheckOutput
|
||||
/// </summary>
|
||||
public string? ProductGroupName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 产品说明(线路简要说明,对客展示)。
|
||||
/// </summary>
|
||||
public string? ProductDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 体积重量。
|
||||
/// </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);
|
||||
}
|
||||
|
||||
@ -21,4 +21,9 @@ public class OrderUploadInput
|
||||
/// 默认发件地址 ID(地址管理中的发件人地址)。
|
||||
/// </summary>
|
||||
public int? SenderAddressId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 客户主键(管理端代客下单时必传)。
|
||||
/// </summary>
|
||||
public int? CustomerId { get; set; }
|
||||
}
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
namespace Oms.Application.Dtos.Orders;
|
||||
|
||||
/// <summary>
|
||||
/// 工作台业务线趋势(运单 / 代发 / 揽收新增量)。
|
||||
/// </summary>
|
||||
public class OrderWorkplaceBusinessTrendOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 维度:day / week / month。
|
||||
/// </summary>
|
||||
public string Dimension { get; set; } = "day";
|
||||
|
||||
/// <summary>
|
||||
/// 趋势点(按时间正序)。
|
||||
/// </summary>
|
||||
public List<OrderWorkplaceBusinessTrendPointOutput> Points { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个时间桶的三类业务量。
|
||||
/// </summary>
|
||||
public class OrderWorkplaceBusinessTrendPointOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 展示标签。
|
||||
/// </summary>
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 运单新增(非仓内代发)。
|
||||
/// </summary>
|
||||
public int WaybillCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 代发订单新增(仓内履约)。
|
||||
/// </summary>
|
||||
public int FulfillCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 揽收单新增。
|
||||
/// </summary>
|
||||
public int PickupCount { get; set; }
|
||||
}
|
||||
@ -64,4 +64,14 @@ public class OrderWorkplaceStatsOutput
|
||||
/// 店铺订单待处理数量(履约状态 pending)。
|
||||
/// </summary>
|
||||
public int StoreOrderPending { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 代发订单统计(仓内履约)。
|
||||
/// </summary>
|
||||
public FulfillWorkplaceStatsOutput Fulfill { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 揽收订单统计。
|
||||
/// </summary>
|
||||
public PickupWorkplaceStatsOutput Pickup { get; set; } = new();
|
||||
}
|
||||
|
||||
@ -100,9 +100,24 @@ public class PickupOrderListInput
|
||||
|
||||
public string? Keyword { get; set; }
|
||||
|
||||
public int? WarehouseId { get; set; }
|
||||
|
||||
public DateTime? CreateTimeStart { get; set; }
|
||||
|
||||
public DateTime? CreateTimeEnd { get; set; }
|
||||
|
||||
public DateTime? ExpectPickupTimeStart { get; set; }
|
||||
|
||||
public DateTime? ExpectPickupTimeEnd { get; set; }
|
||||
}
|
||||
|
||||
public class PickupOrderStatusCountsOutput
|
||||
{
|
||||
public int Total { get; set; }
|
||||
|
||||
public int PendingPayment { get; set; }
|
||||
|
||||
public Dictionary<string, int> Counts { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public class PickupOrderConfirmFreightInput
|
||||
@ -267,6 +282,8 @@ public class PickupOrderOutput
|
||||
|
||||
public DateTime? CreateTime { get; set; }
|
||||
|
||||
public DateTime? UpdateTime { get; set; }
|
||||
|
||||
public List<PickupOrderLineOutput> Orders { get; set; } = [];
|
||||
|
||||
public List<PickupOrderRejectItem> Rejected { get; set; } = [];
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
namespace Oms.Application.Dtos.Orders;
|
||||
|
||||
/// <summary>
|
||||
/// 工作台揽收订单统计。
|
||||
/// </summary>
|
||||
public class PickupWorkplaceStatsOutput
|
||||
{
|
||||
/// <summary>待支付(有应收且未支付,不含取消/失败)。</summary>
|
||||
public int PendingPayment { get; set; }
|
||||
|
||||
/// <summary>草稿,待确认下发。</summary>
|
||||
public int PendingSchedule { get; set; }
|
||||
|
||||
/// <summary>已预约/已派单,待取件。</summary>
|
||||
public int PendingPickup { get; set; }
|
||||
|
||||
/// <summary>已取件,待到仓。</summary>
|
||||
public int PendingInbound { get; set; }
|
||||
}
|
||||
@ -11,6 +11,11 @@ public class UserProfileData
|
||||
public string? WechatId { get; set; }
|
||||
|
||||
public string? WechatQrCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 企业微信客服接入链接(微信外接入)。
|
||||
/// </summary>
|
||||
public string? WecomKfUrl { get; set; }
|
||||
}
|
||||
|
||||
public class OmsUserCreateInput : UserCreateDto
|
||||
@ -22,6 +27,8 @@ public class OmsUserCreateInput : UserCreateDto
|
||||
public string? WechatId { get; set; }
|
||||
|
||||
public string? WechatQrCode { get; set; }
|
||||
|
||||
public string? WecomKfUrl { get; set; }
|
||||
}
|
||||
|
||||
public class OmsUserUpdateInput : UserUpdateDto
|
||||
@ -31,6 +38,8 @@ public class OmsUserUpdateInput : UserUpdateDto
|
||||
public string? WechatId { get; set; }
|
||||
|
||||
public string? WechatQrCode { get; set; }
|
||||
|
||||
public string? WecomKfUrl { get; set; }
|
||||
}
|
||||
|
||||
public class OmsUserDetailDto : UserDto
|
||||
@ -42,4 +51,6 @@ public class OmsUserDetailDto : UserDto
|
||||
public string? WechatId { get; set; }
|
||||
|
||||
public string? WechatQrCode { get; set; }
|
||||
|
||||
public string? WecomKfUrl { get; set; }
|
||||
}
|
||||
|
||||
@ -32,14 +32,13 @@ public class NoticeCreateInput
|
||||
public byte IsPopup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收对象类型:user/customer。
|
||||
/// 接收对象类型:user/customer。公告全体广播时可空(后端按 user 占位)。
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(16)]
|
||||
public string ReceiverType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 接收主体 ID 列表。
|
||||
/// 接收主体 ID 列表。公告传空表示全体生效;站内信必填。
|
||||
/// </summary>
|
||||
public List<int> ReceiverIds { get; set; } = [];
|
||||
}
|
||||
|
||||
@ -579,6 +579,7 @@ public class WarehouseParcelArrivalListInput : WarehousePageInput
|
||||
|
||||
public class WarehouseParcelArrivalCreateInput
|
||||
{
|
||||
/// <summary>已废弃:到仓按仓库+快递单号定位包裹,客户归属取自包裹。</summary>
|
||||
public int? CustomerId { get; set; }
|
||||
|
||||
[Required]
|
||||
|
||||
@ -21,4 +21,15 @@ public interface IItfxForwardClient
|
||||
string appSecret,
|
||||
object body,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 通用 ForwardWithSubscribe(standardCode 可配,如 9610 申报暂定码)。
|
||||
/// </summary>
|
||||
Task<ItfxForwardResult> ForwardWithSubscribeAsync(
|
||||
string standardCode,
|
||||
string supplierCode,
|
||||
string appKey,
|
||||
string appSecret,
|
||||
object body,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
@ -21,7 +21,24 @@ internal sealed class ItfxForwardClient(
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
public async Task<ItfxForwardResult> ForwardCreateOrderOneAsync(
|
||||
public Task<ItfxForwardResult> ForwardCreateOrderOneAsync(
|
||||
string supplierCode,
|
||||
string appKey,
|
||||
string appSecret,
|
||||
object body,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return ForwardWithSubscribeAsync(
|
||||
CreateOrderOneStandardCode,
|
||||
supplierCode,
|
||||
appKey,
|
||||
appSecret,
|
||||
body,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<ItfxForwardResult> ForwardWithSubscribeAsync(
|
||||
string standardCode,
|
||||
string supplierCode,
|
||||
string appKey,
|
||||
string appSecret,
|
||||
@ -34,9 +51,19 @@ internal sealed class ItfxForwardClient(
|
||||
throw new InvalidOperationException("Itfx BaseUrl not configured");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(standardCode))
|
||||
{
|
||||
throw new InvalidOperationException("Itfx standardCode is empty");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(supplierCode))
|
||||
{
|
||||
throw new InvalidOperationException("Itfx supplierCode is empty");
|
||||
}
|
||||
|
||||
var root = $"{settings.BaseUrl.Trim().TrimEnd('/')}/InterfaceForward/ForwardWithSubscribe";
|
||||
var url =
|
||||
$"{root}?standardCode={CreateOrderOneStandardCode}&supplierCode={Uri.EscapeDataString(supplierCode)}";
|
||||
$"{root}?standardCode={Uri.EscapeDataString(standardCode.Trim())}&supplierCode={Uri.EscapeDataString(supplierCode.Trim())}";
|
||||
var requestJson = JsonSerializer.Serialize(body, JsonOptions);
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
|
||||
@ -13,4 +13,24 @@ public class ItfxGatewayOptions
|
||||
public string BaseUrl { get; set; } = "http://api.nanafa.com/itfx";
|
||||
|
||||
public int TimeoutSeconds { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// 9610 汇总申报上游标准码(暂定,可在配置覆盖)。
|
||||
/// </summary>
|
||||
public string Customs9610StandardCode { get; set; } = "customs9610Declare";
|
||||
|
||||
/// <summary>
|
||||
/// 9610 获取清单数据上游标准码(暂定)。
|
||||
/// </summary>
|
||||
public string Customs9610ManifestStandardCode { get; set; } = "customs9610Manifest";
|
||||
|
||||
/// <summary>
|
||||
/// 9610 获取报关单上游标准码(暂定)。
|
||||
/// </summary>
|
||||
public string Customs9610DeclarationFormStandardCode { get; set; } = "customs9610DeclarationForm";
|
||||
|
||||
/// <summary>
|
||||
/// 9610 申报默认 supplierCode;空则回退订单 DownstreamSupplierCode。
|
||||
/// </summary>
|
||||
public string? Customs9610SupplierCode { get; set; }
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ public class CustomerServiceAdvisorResolver(FakeRbacDbContext dbContext) : IScop
|
||||
Name = row.Name,
|
||||
WechatId = row.WechatId,
|
||||
WechatQrCode = row.WechatQrCode,
|
||||
WecomKfUrl = row.WecomKfUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@ -44,7 +45,8 @@ public class CustomerServiceAdvisorResolver(FakeRbacDbContext dbContext) : IScop
|
||||
SELECT
|
||||
u.`Name` AS `Name`,
|
||||
u.`WechatId` AS `WechatId`,
|
||||
u.`WechatQrCode` AS `WechatQrCode`
|
||||
u.`WechatQrCode` AS `WechatQrCode`,
|
||||
u.`WecomKfUrl` AS `WecomKfUrl`
|
||||
FROM `user` u
|
||||
WHERE u.`IsDeleted` = 0
|
||||
AND u.`userid` = {0}
|
||||
@ -62,7 +64,8 @@ public class CustomerServiceAdvisorResolver(FakeRbacDbContext dbContext) : IScop
|
||||
SELECT
|
||||
u.`Name` AS `Name`,
|
||||
u.`WechatId` AS `WechatId`,
|
||||
u.`WechatQrCode` AS `WechatQrCode`
|
||||
u.`WechatQrCode` AS `WechatQrCode`,
|
||||
u.`WecomKfUrl` AS `WecomKfUrl`
|
||||
FROM `user` u
|
||||
WHERE u.`IsDeleted` = 0
|
||||
AND (u.`Name` = {0} OR u.`Account` = {0})
|
||||
@ -82,5 +85,7 @@ public class CustomerServiceAdvisorResolver(FakeRbacDbContext dbContext) : IScop
|
||||
public string? WechatId { get; set; }
|
||||
|
||||
public string? WechatQrCode { get; set; }
|
||||
|
||||
public string? WecomKfUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ internal static class OmsUserProfileMapper
|
||||
detail.EmployeeNo = profile.EmployeeNo;
|
||||
detail.WechatId = profile.WechatId;
|
||||
detail.WechatQrCode = profile.WechatQrCode;
|
||||
detail.WecomKfUrl = profile.WecomKfUrl;
|
||||
return detail;
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,8 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
|
||||
u.`Phone` AS `Phone`,
|
||||
u.`EmployeeNo` AS `EmployeeNo`,
|
||||
u.`WechatId` AS `WechatId`,
|
||||
u.`WechatQrCode` AS `WechatQrCode`
|
||||
u.`WechatQrCode` AS `WechatQrCode`,
|
||||
u.`WecomKfUrl` AS `WecomKfUrl`
|
||||
FROM `user` u
|
||||
WHERE u.`Id` = {0} AND u.`IsDeleted` = 0
|
||||
LIMIT 1
|
||||
@ -41,6 +42,7 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
|
||||
EmployeeNo = row.EmployeeNo,
|
||||
WechatId = row.WechatId,
|
||||
WechatQrCode = row.WechatQrCode,
|
||||
WecomKfUrl = row.WecomKfUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@ -60,7 +62,8 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
|
||||
u.`Phone` AS `Phone`,
|
||||
u.`EmployeeNo` AS `EmployeeNo`,
|
||||
u.`WechatId` AS `WechatId`,
|
||||
u.`WechatQrCode` AS `WechatQrCode`
|
||||
u.`WechatQrCode` AS `WechatQrCode`,
|
||||
u.`WecomKfUrl` AS `WecomKfUrl`
|
||||
FROM `user` u
|
||||
WHERE u.`Id` IN ({inClause}) AND u.`IsDeleted` = 0
|
||||
""";
|
||||
@ -74,6 +77,7 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
|
||||
EmployeeNo = x.EmployeeNo,
|
||||
WechatId = x.WechatId,
|
||||
WechatQrCode = x.WechatQrCode,
|
||||
WecomKfUrl = x.WecomKfUrl,
|
||||
});
|
||||
}
|
||||
|
||||
@ -85,7 +89,8 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
|
||||
`Phone` = {1},
|
||||
`EmployeeNo` = {2},
|
||||
`WechatId` = {3},
|
||||
`WechatQrCode` = {4}
|
||||
`WechatQrCode` = {4},
|
||||
`WecomKfUrl` = {5}
|
||||
WHERE `Id` = {0} AND `IsDeleted` = 0
|
||||
""";
|
||||
|
||||
@ -98,6 +103,7 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
|
||||
(object?)NormalizeOptional(profile.EmployeeNo),
|
||||
(object?)NormalizeOptional(profile.WechatId),
|
||||
(object?)NormalizeOptional(profile.WechatQrCode),
|
||||
(object?)NormalizeOptional(profile.WecomKfUrl),
|
||||
],
|
||||
cancellationToken);
|
||||
}
|
||||
@ -112,7 +118,8 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
|
||||
SET
|
||||
`Phone` = {1},
|
||||
`WechatId` = {2},
|
||||
`WechatQrCode` = {3}
|
||||
`WechatQrCode` = {3},
|
||||
`WecomKfUrl` = {4}
|
||||
WHERE `Id` = {0} AND `IsDeleted` = 0
|
||||
""";
|
||||
|
||||
@ -123,6 +130,7 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
|
||||
(object?)NormalizeOptional(profile.Phone),
|
||||
(object?)NormalizeOptional(profile.WechatId),
|
||||
(object?)NormalizeOptional(profile.WechatQrCode),
|
||||
(object?)NormalizeOptional(profile.WecomKfUrl),
|
||||
],
|
||||
cancellationToken);
|
||||
}
|
||||
@ -208,6 +216,8 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
|
||||
public string? WechatId { get; set; }
|
||||
|
||||
public string? WechatQrCode { get; set; }
|
||||
|
||||
public string? WecomKfUrl { get; set; }
|
||||
}
|
||||
|
||||
private sealed class UserProfileListRow
|
||||
@ -221,6 +231,8 @@ public class UserProfileRepository(FakeRbacDbContext dbContext) : IScopedDepende
|
||||
public string? WechatId { get; set; }
|
||||
|
||||
public string? WechatQrCode { get; set; }
|
||||
|
||||
public string? WecomKfUrl { get; set; }
|
||||
}
|
||||
|
||||
private sealed class EmployeeNoRow
|
||||
|
||||
@ -211,6 +211,7 @@ public partial class SalesProductService
|
||||
MinTimeliness = product.MinTimeliness,
|
||||
MaxTimeliness = product.MaxTimeliness,
|
||||
TradeTerms = product.TradeTerms,
|
||||
RouteSort = product.RouteSort,
|
||||
AdditionalServices = product.AdditionalServices,
|
||||
CustomerWaybill = product.CustomerWaybill,
|
||||
LabelTemplateId = product.LabelTemplateId,
|
||||
@ -221,7 +222,8 @@ public partial class SalesProductService
|
||||
WeightRoundingId = product.WeightRoundingId,
|
||||
ProductDescription = product.ProductDescription,
|
||||
PreStorageScript = product.PreStorageScript,
|
||||
CustomerGuideMarkdown = product.CustomerGuideMarkdown,
|
||||
// 对客说明不常变动,且有独立保存入口;规则发布快照跳过,避免配置发布被其 diff 干扰
|
||||
CustomerGuideMarkdown = null,
|
||||
IsActive = product.IsActive,
|
||||
Scheme = product.Scheme
|
||||
};
|
||||
@ -333,6 +335,7 @@ public partial class SalesProductService
|
||||
entity.MinTimeliness = input.MinTimeliness;
|
||||
entity.MaxTimeliness = input.MaxTimeliness;
|
||||
entity.TradeTerms = NormalizeTradeTerms(input.TradeTerms);
|
||||
entity.RouteSort = input.RouteSort < 0 ? 0 : input.RouteSort;
|
||||
entity.AdditionalServices = NormalizeAdditionalServices(input.AdditionalServices);
|
||||
entity.CustomerWaybill = input.CustomerWaybill;
|
||||
entity.LabelTemplateId = input.LabelTemplateId;
|
||||
@ -344,7 +347,7 @@ public partial class SalesProductService
|
||||
entity.WeightRoundingId = input.WeightRoundingId;
|
||||
entity.ProductDescription = input.ProductDescription;
|
||||
entity.PreStorageScript = input.PreStorageScript;
|
||||
entity.CustomerGuideMarkdown = input.CustomerGuideMarkdown;
|
||||
// 对客说明仅通过 /customer-guide/save 维护,产品配置保存/发布不覆盖
|
||||
entity.IsActive = input.IsActive;
|
||||
entity.Scheme = string.IsNullOrWhiteSpace(input.Scheme) ? AllocationSchemeCodes.CostLowest : input.Scheme;
|
||||
}
|
||||
@ -444,6 +447,7 @@ public partial class SalesProductService
|
||||
ProductGroup = entity.ProductGroup,
|
||||
MinTimeliness = entity.MinTimeliness,
|
||||
MaxTimeliness = entity.MaxTimeliness,
|
||||
RouteSort = entity.RouteSort,
|
||||
AdditionalServices = entity.AdditionalServices,
|
||||
CustomerWaybill = entity.CustomerWaybill,
|
||||
LabelTemplateId = entity.LabelTemplateId,
|
||||
@ -473,6 +477,7 @@ public partial class SalesProductService
|
||||
MinTimeliness = entity.MinTimeliness,
|
||||
MaxTimeliness = entity.MaxTimeliness,
|
||||
TradeTerms = entity.TradeTerms,
|
||||
RouteSort = entity.RouteSort,
|
||||
AdditionalServices = entity.AdditionalServices,
|
||||
CustomerWaybill = entity.CustomerWaybill,
|
||||
LabelTemplateId = entity.LabelTemplateId,
|
||||
|
||||
@ -0,0 +1,130 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oms.Application.Dtos.Channels;
|
||||
using Oms.Domain.Shared.Enums;
|
||||
using Oms.Domain.SystemAggregate.Repositories;
|
||||
|
||||
namespace Oms.Application.Services.Channels;
|
||||
|
||||
public partial class SalesProductService
|
||||
{
|
||||
private const string SalesProductGroupDictType = "sales_product_group";
|
||||
private const string UngroupedProductGroupCode = "_ungrouped";
|
||||
private const string DomesticPickupGroupNameKeyword = "国内揽收";
|
||||
|
||||
private IDictRepository DictRepository =>
|
||||
LazyServiceProvider.GetRequiredService<IDictRepository>();
|
||||
|
||||
/// <summary>
|
||||
/// 官网线路目录(匿名):仅启用产品,排除「国内揽收」分组。
|
||||
/// </summary>
|
||||
[AllowAnonymous]
|
||||
[HttpGet("/channel/sales-product/portal/catalog")]
|
||||
public async Task<SalesProductPortalCatalogOutput> GetPortalCatalogAsync()
|
||||
{
|
||||
var products = await SalesProductRepository.GetListAsync(x => x.IsActive == EnableStatus.Enabled);
|
||||
var dictRows = await DictRepository.GetListAsync(x => x.DictType == SalesProductGroupDictType);
|
||||
|
||||
var domesticPickupGroupCodes = dictRows
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.DictName)
|
||||
&& x.DictName.Trim().Contains(DomesticPickupGroupNameKeyword))
|
||||
.Select(x => (x.DictCode ?? string.Empty).Trim())
|
||||
.Where(code => !string.IsNullOrWhiteSpace(code))
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var groupNameMap = dictRows
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.DictCode))
|
||||
.GroupBy(x => x.DictCode!.Trim(), StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g =>
|
||||
{
|
||||
var name = g.First().DictName?.Trim();
|
||||
return string.IsNullOrWhiteSpace(name) ? g.Key : name;
|
||||
},
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
groupNameMap[UngroupedProductGroupCode] = "未分组";
|
||||
|
||||
var filtered = products
|
||||
.Where(p =>
|
||||
{
|
||||
var groupCode = string.IsNullOrWhiteSpace(p.ProductGroup)
|
||||
? UngroupedProductGroupCode
|
||||
: p.ProductGroup.Trim();
|
||||
return !domesticPickupGroupCodes.Contains(groupCode);
|
||||
})
|
||||
.OrderBy(p => p.RouteSort)
|
||||
.ThenBy(p => p.MinTimeliness ?? int.MaxValue)
|
||||
.ThenBy(p => p.ProductName, StringComparer.OrdinalIgnoreCase)
|
||||
.ThenBy(p => p.Id)
|
||||
.ToList();
|
||||
|
||||
var usedGroupCodes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var items = filtered.Select(p =>
|
||||
{
|
||||
var groupCode = string.IsNullOrWhiteSpace(p.ProductGroup)
|
||||
? UngroupedProductGroupCode
|
||||
: p.ProductGroup.Trim();
|
||||
usedGroupCodes.Add(groupCode);
|
||||
groupNameMap.TryGetValue(groupCode, out var groupName);
|
||||
return new SalesProductPortalItemOutput
|
||||
{
|
||||
Id = p.Id,
|
||||
ProductName = p.ProductName,
|
||||
ProductGroup = groupCode == UngroupedProductGroupCode ? null : groupCode,
|
||||
ProductGroupName = string.IsNullOrWhiteSpace(groupName) ? groupCode : groupName,
|
||||
MinTimeliness = p.MinTimeliness,
|
||||
MaxTimeliness = p.MaxTimeliness,
|
||||
TradeTerms = string.IsNullOrWhiteSpace(p.TradeTerms) ? null : p.TradeTerms.Trim().ToUpperInvariant(),
|
||||
RouteSort = p.RouteSort,
|
||||
ProductDescription = p.ProductDescription
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
var groups = new List<SalesProductPortalGroupOutput>();
|
||||
var consumed = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var dict in dictRows.OrderBy(x => x.DictSort).ThenBy(x => x.DictCode))
|
||||
{
|
||||
var code = dict.DictCode?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(code)
|
||||
|| domesticPickupGroupCodes.Contains(code)
|
||||
|| !usedGroupCodes.Contains(code)
|
||||
|| !consumed.Add(code))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
groups.Add(new SalesProductPortalGroupOutput
|
||||
{
|
||||
Code = code,
|
||||
Name = string.IsNullOrWhiteSpace(dict.DictName) ? code : dict.DictName.Trim()
|
||||
});
|
||||
}
|
||||
|
||||
if (usedGroupCodes.Contains(UngroupedProductGroupCode) && consumed.Add(UngroupedProductGroupCode))
|
||||
{
|
||||
groups.Add(new SalesProductPortalGroupOutput
|
||||
{
|
||||
Code = UngroupedProductGroupCode,
|
||||
Name = groupNameMap[UngroupedProductGroupCode]
|
||||
});
|
||||
}
|
||||
|
||||
foreach (var code in usedGroupCodes.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!consumed.Add(code)) continue;
|
||||
groupNameMap.TryGetValue(code, out var name);
|
||||
groups.Add(new SalesProductPortalGroupOutput
|
||||
{
|
||||
Code = code,
|
||||
Name = string.IsNullOrWhiteSpace(name) ? code : name
|
||||
});
|
||||
}
|
||||
|
||||
return new SalesProductPortalCatalogOutput
|
||||
{
|
||||
Groups = groups,
|
||||
Items = items
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -233,6 +233,10 @@ public class CustomerService : ApplicationService
|
||||
input.InviteCode,
|
||||
ReferralCodeRepository,
|
||||
CustomerRepository);
|
||||
await StaffAdvisorBinder.BindAdvisorOnCreateAsync(
|
||||
entity,
|
||||
input.AdvisorCode,
|
||||
RbacDbContext);
|
||||
await CustomerRepository.InsertAsync(entity);
|
||||
try
|
||||
{
|
||||
|
||||
@ -1,46 +1,49 @@
|
||||
using Fake.Application;
|
||||
using Fake.Users;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MiniExcelLibs;
|
||||
using Oms.Application.DataPerm;
|
||||
using Oms.Application.Dtos.Finance;
|
||||
using Oms.Application.Dtos.Shared;
|
||||
using Oms.Application.Services.Orders;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Domain.CustomersAggregate.Repositories;
|
||||
using Oms.Domain.OrdersAggregate;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
using Oms.Domain.SystemAggregate.Repositories;
|
||||
|
||||
namespace Oms.Application.Services.Finance;
|
||||
|
||||
/// <summary>
|
||||
/// 财务相关 API<EFBFBD>。
|
||||
/// </summary>
|
||||
/// 财务相关 API。
|
||||
/// </summary>
|
||||
[ApiExplorerSettings(GroupName = "OMS-Finance")]
|
||||
public class FinanceService : ApplicationService
|
||||
{
|
||||
private const string FeeItemDictType = "fee_item";
|
||||
private const int ExportMaxRows = 50000;
|
||||
|
||||
private IOrderRepository OrderRepository =>
|
||||
LazyServiceProvider.GetRequiredService<IOrderRepository>();
|
||||
|
||||
private ICustomerRepository CustomerRepository =>
|
||||
LazyServiceProvider.GetRequiredService<ICustomerRepository>();
|
||||
|
||||
private IDictRepository DictRepository =>
|
||||
LazyServiceProvider.GetRequiredService<IDictRepository>();
|
||||
|
||||
private EffectiveScopeBuilder DataScopeBuilder =>
|
||||
LazyServiceProvider.GetRequiredService<EffectiveScopeBuilder>();
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询应收应付(订单费用联查订单)<EFBFBD>。
|
||||
/// 分页查询应收应付(订单费用联查订单)。
|
||||
/// </summary>
|
||||
[HttpGet("/finance/receivable-payable/list")]
|
||||
public async Task<OmsPageResult<ReceivablePayablePaginatedOutput>> GetReceivablePayableListAsync(
|
||||
[FromQuery] ReceivablePayablePaginatedInput input)
|
||||
{
|
||||
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
||||
CustomerRepository,
|
||||
CurrentUser,
|
||||
input.CustomerId);
|
||||
var filter = BuildFilter(input, customerId);
|
||||
await ApplyInternalOrderDataScopeAsync(filter);
|
||||
|
||||
var filter = await BuildScopedFilterAsync(input);
|
||||
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
||||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||||
var (rows, total) = await OrderRepository.GetReceivablePayablePagedListAsync(
|
||||
@ -48,6 +51,102 @@ public class FinanceService : ApplicationService
|
||||
pageSize,
|
||||
filter);
|
||||
|
||||
var items = await MapReceivablePayableRowsAsync(rows);
|
||||
return new OmsPageResult<ReceivablePayablePaginatedOutput>
|
||||
{
|
||||
Items = items,
|
||||
Total = total
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应收应付汇总(当前筛选条件下全量费用明细)。
|
||||
/// </summary>
|
||||
[HttpGet("/finance/receivable-payable/summary")]
|
||||
public async Task<ReceivablePayableSummaryOutput> GetReceivablePayableSummaryAsync(
|
||||
[FromQuery] ReceivablePayablePaginatedInput input)
|
||||
{
|
||||
var filter = await BuildScopedFilterAsync(input);
|
||||
var rows = await OrderRepository.GetReceivablePayableSummaryAsync(filter);
|
||||
var items = rows
|
||||
.Select(row => new ReceivablePayableSummaryItemOutput
|
||||
{
|
||||
FeeType = row.FeeType,
|
||||
FeeTypeName = GetFeeTypeName(row.FeeType),
|
||||
Currency = row.Currency,
|
||||
TotalAmount = row.TotalAmount,
|
||||
LineCount = row.LineCount
|
||||
})
|
||||
.OrderBy(x => x.FeeType)
|
||||
.ThenBy(x => x.Currency)
|
||||
.ToList();
|
||||
|
||||
return new ReceivablePayableSummaryOutput
|
||||
{
|
||||
TotalLines = items.Sum(x => x.LineCount),
|
||||
Items = items
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导出应收应付 Excel。
|
||||
/// </summary>
|
||||
[HttpGet("/finance/receivable-payable/export")]
|
||||
public async Task<FileContentResult> ExportReceivablePayableAsync(
|
||||
[FromQuery] ReceivablePayablePaginatedInput input)
|
||||
{
|
||||
var filter = await BuildScopedFilterAsync(input);
|
||||
var rows = await OrderRepository.GetReceivablePayableExportListAsync(filter, ExportMaxRows);
|
||||
var items = await MapReceivablePayableRowsAsync(rows);
|
||||
var exportRows = items.Select(item => new
|
||||
{
|
||||
运单号 = item.ServiceNo,
|
||||
客户单号 = item.ClientNo,
|
||||
跟踪号 = item.TrackNo,
|
||||
客户编码 = item.CustomerCode,
|
||||
费用类型 = item.FeeTypeName,
|
||||
费用项 = item.CostItemName ?? item.CostItem,
|
||||
金额 = item.Amount,
|
||||
币种 = item.Currency,
|
||||
计费阶段 = item.ChargingStageName,
|
||||
订单状态 = item.OrderStatusName,
|
||||
支付状态 = item.IsPaid == 1 ? "已支付" : "未支付",
|
||||
产品编码 = item.ProductCode,
|
||||
渠道编码 = item.ChannelCode,
|
||||
目的国 = item.ConsigneeCountryName,
|
||||
下游服务商 = item.DownstreamProvider,
|
||||
价格编号 = item.PriceNo,
|
||||
价格生效时间 = item.PriceEffectiveTime,
|
||||
支付时间 = item.PayTime,
|
||||
收货时间 = item.ReceiveTime,
|
||||
发货时间 = item.ShipTime
|
||||
}).ToList();
|
||||
|
||||
await using var stream = new MemoryStream();
|
||||
await MiniExcel.SaveAsAsync(stream, exportRows);
|
||||
stream.Position = 0;
|
||||
return new FileContentResult(stream.ToArray(),
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
{
|
||||
FileDownloadName = $"receivable-payable-{DateTime.Now:yyyyMMddHHmmss}.xlsx"
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<OrderReceivablePayableFilter> BuildScopedFilterAsync(
|
||||
ReceivablePayablePaginatedInput input)
|
||||
{
|
||||
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
||||
CustomerRepository,
|
||||
CurrentUser,
|
||||
input.CustomerId);
|
||||
var filter = BuildFilter(input, customerId);
|
||||
await ApplyInternalOrderDataScopeAsync(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
private async Task<List<ReceivablePayablePaginatedOutput>> MapReceivablePayableRowsAsync(
|
||||
List<OrderReceivablePayableRow> rows)
|
||||
{
|
||||
var customerIds = rows
|
||||
.Select(x => x.Order.CustomerId)
|
||||
.Where(x => x.HasValue)
|
||||
@ -58,8 +157,9 @@ public class FinanceService : ApplicationService
|
||||
? []
|
||||
: await CustomerRepository.GetListAsync(x => customerIds.Contains(x.Id));
|
||||
var customerMap = customers.ToDictionary(x => x.Id);
|
||||
var feeItemNameMap = await BuildFeeItemNameMapAsync();
|
||||
|
||||
var items = rows.Select(row =>
|
||||
return rows.Select(row =>
|
||||
{
|
||||
string? customerCode = null;
|
||||
if (row.Order.CustomerId.HasValue
|
||||
@ -68,8 +168,12 @@ public class FinanceService : ApplicationService
|
||||
customerCode = customer.CustomerCode;
|
||||
}
|
||||
|
||||
var costItem = row.Fee.CostItem;
|
||||
feeItemNameMap.TryGetValue(costItem, out var costItemName);
|
||||
|
||||
return new ReceivablePayablePaginatedOutput
|
||||
{
|
||||
OrderId = row.Order.Id,
|
||||
ServiceNo = row.Order.ServiceNo,
|
||||
ClientNo = row.Order.ClientNo,
|
||||
TrackNo = row.Order.TrackNo,
|
||||
@ -85,7 +189,8 @@ public class FinanceService : ApplicationService
|
||||
ConsigneeCountryName = row.Order.ConsigneeCountryName,
|
||||
FeeType = row.Fee.Type,
|
||||
FeeTypeName = GetFeeTypeName(row.Fee.Type),
|
||||
CostItem = row.Fee.CostItem,
|
||||
CostItem = costItem,
|
||||
CostItemName = costItemName ?? ParcelBillingCalculator.ResolveFeeDisplayName(costItem, null),
|
||||
Amount = row.Fee.Amount,
|
||||
Currency = row.Fee.Currency,
|
||||
DownstreamProvider = row.Fee.DownstreamProvider,
|
||||
@ -95,27 +200,59 @@ public class FinanceService : ApplicationService
|
||||
ChargingStageName = GetChargingStageName(row.Fee.ChargingStage)
|
||||
};
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
return new OmsPageResult<ReceivablePayablePaginatedOutput>
|
||||
{
|
||||
Items = items,
|
||||
Total = total
|
||||
};
|
||||
private async Task<Dictionary<string, string>> BuildFeeItemNameMapAsync()
|
||||
{
|
||||
var dictRows = await DictRepository.GetListAsync(x => x.DictType == FeeItemDictType);
|
||||
return dictRows
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.DictCode))
|
||||
.GroupBy(x => x.DictCode.Trim(), StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => g.First().DictName?.Trim() ?? g.Key,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static OrderReceivablePayableFilter BuildFilter(
|
||||
ReceivablePayablePaginatedInput input,
|
||||
int? customerId) =>
|
||||
new()
|
||||
int? customerId)
|
||||
{
|
||||
var timeField = NormalizeTimeField(input.TimeField);
|
||||
var useCreateTime = timeField == "createTime";
|
||||
return new()
|
||||
{
|
||||
Keyword = input.Keyword,
|
||||
CustomerId = customerId,
|
||||
CreateTimeStart = input.StartTime,
|
||||
CreateTimeEnd = input.EndTime,
|
||||
CustomerCode = input.CustomerCode,
|
||||
CreateTimeStart = useCreateTime ? input.StartTime : null,
|
||||
CreateTimeEnd = useCreateTime ? input.EndTime : null,
|
||||
TimeField = timeField,
|
||||
TimeStart = input.StartTime,
|
||||
TimeEnd = input.EndTime,
|
||||
FeeType = input.FeeType,
|
||||
CostItem = input.CostItem,
|
||||
ChargingStage = input.ChargingStage
|
||||
ChargingStage = input.ChargingStage,
|
||||
EffectiveStageOnly = input.EffectiveStageOnly,
|
||||
IsPaid = NormalizeIsPaid(input.IsPaid)
|
||||
};
|
||||
}
|
||||
|
||||
private static int? NormalizeIsPaid(int? isPaid)
|
||||
{
|
||||
if (!isPaid.HasValue) return null;
|
||||
return isPaid.Value == 1 ? 1 : isPaid.Value == 0 ? 0 : null;
|
||||
}
|
||||
|
||||
private static string NormalizeTimeField(string? timeField)
|
||||
{
|
||||
var raw = timeField?.Trim();
|
||||
return raw switch
|
||||
{
|
||||
"receiveTime" or "shipTime" or "payTime" => raw,
|
||||
_ => "createTime"
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ApplyInternalOrderDataScopeAsync(OrderReceivablePayableFilter filter)
|
||||
{
|
||||
|
||||
1116
src/Oms.Application/Services/Orders/Customs9610OrderService.cs
Normal file
1116
src/Oms.Application/Services/Orders/Customs9610OrderService.cs
Normal file
File diff suppressed because it is too large
Load Diff
184
src/Oms.Application/Services/Orders/Customs9610OrderWriter.cs
Normal file
184
src/Oms.Application/Services/Orders/Customs9610OrderWriter.cs
Normal file
@ -0,0 +1,184 @@
|
||||
using Fake.DependencyInjection;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
|
||||
namespace Oms.Application.Services.Orders;
|
||||
|
||||
/// <summary>
|
||||
/// 运单创建时按 ExportCustomsMode=9610Summary 写入/刷新 t_9610_order。
|
||||
/// </summary>
|
||||
public class Customs9610OrderWriter : IScopedDependency
|
||||
{
|
||||
private readonly ICustoms9610OrderRepository _headerRepository;
|
||||
private readonly ICustoms9610OrderItemRepository _itemRepository;
|
||||
private readonly IOrderRepository _orderRepository;
|
||||
|
||||
public Customs9610OrderWriter(
|
||||
ICustoms9610OrderRepository headerRepository,
|
||||
ICustoms9610OrderItemRepository itemRepository,
|
||||
IOrderRepository orderRepository)
|
||||
{
|
||||
_headerRepository = headerRepository;
|
||||
_itemRepository = itemRepository;
|
||||
_orderRepository = orderRepository;
|
||||
}
|
||||
|
||||
public async Task UpsertFromOrderAsync(Order order, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (order.Id <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var isSummary = string.Equals(
|
||||
order.ExportCustomsMode,
|
||||
ExportCustomsModeCodes.Mode9610Summary,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var existing = await _headerRepository.GetByOrderIdAsync(order.Id, cancellationToken);
|
||||
|
||||
if (!isSummary)
|
||||
{
|
||||
if (existing != null
|
||||
&& string.Equals(existing.Status, Customs9610StatusCodes.Pending, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await _headerRepository.HardDeleteItemsByHeaderIdAsync(existing.Id, cancellationToken);
|
||||
await _headerRepository.HardDeleteAsync(existing, cancellationToken);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var declarations = await _orderRepository.GetDeclarationsByOrderIdsAsync([order.Id], cancellationToken);
|
||||
var weights = await _orderRepository.GetWeightsByOrderIdsAsync([order.Id], cancellationToken);
|
||||
var grossWeight = ResolveGrossWeight(weights);
|
||||
var serviceNo = order.ServiceNo?.Trim() ?? string.Empty;
|
||||
var clientNo = FirstNonEmpty(order.ClientNo, order.ServiceNo) ?? string.Empty;
|
||||
|
||||
if (existing != null
|
||||
&& string.Equals(existing.Status, Customs9610StatusCodes.Declared, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// 已申报:仅同步冗余检索字段,不覆盖申报内容
|
||||
existing.ServiceNo = string.IsNullOrWhiteSpace(existing.ServiceNo) ? serviceNo : existing.ServiceNo;
|
||||
existing.UpdateTime = DateTime.Now;
|
||||
await _headerRepository.UpdateAsync(existing);
|
||||
return;
|
||||
}
|
||||
|
||||
Customs9610Order header;
|
||||
if (existing == null)
|
||||
{
|
||||
header = new Customs9610Order
|
||||
{
|
||||
OrderId = order.Id,
|
||||
CustomerId = order.CustomerId ?? 0,
|
||||
Status = Customs9610StatusCodes.Pending,
|
||||
BusinessType = Customs9610FormTypeCodes.SummaryB,
|
||||
PreRecordFlag = "1",
|
||||
IsSummarySimple = 0,
|
||||
CreateTime = DateTime.Now,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
header = existing;
|
||||
header.IsDeleted = false;
|
||||
}
|
||||
|
||||
header.ServiceNo = serviceNo;
|
||||
header.CustomerId = order.CustomerId ?? header.CustomerId;
|
||||
header.ClientNo = clientNo;
|
||||
header.BusinessType = string.IsNullOrWhiteSpace(header.BusinessType)
|
||||
? Customs9610FormTypeCodes.SummaryB
|
||||
: header.BusinessType;
|
||||
if (string.IsNullOrWhiteSpace(header.PreRecordFlag)
|
||||
&& string.Equals(header.BusinessType, Customs9610FormTypeCodes.SummaryB, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
header.PreRecordFlag = "1";
|
||||
}
|
||||
|
||||
header.GrossWeight = grossWeight;
|
||||
header.Currency = FirstNonEmpty(order.DeclaredCurrency, header.Currency, "CNY")!;
|
||||
header.DestCountry = FirstNonEmpty(order.ConsigneeCountry, header.DestCountry) ?? string.Empty;
|
||||
header.UpdateTime = DateTime.Now;
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
header = await _headerRepository.InsertAsync(header);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _headerRepository.UpdateAsync(header);
|
||||
}
|
||||
|
||||
await _headerRepository.HardDeleteItemsByHeaderIdAsync(header.Id, cancellationToken);
|
||||
|
||||
var sort = 0;
|
||||
foreach (var d in declarations.OrderBy(x => x.Id))
|
||||
{
|
||||
var itemName = FirstNonEmpty(d.CnName, d.EnName) ?? string.Empty;
|
||||
var sku = FirstNonEmpty(d.Sku, $"ITEM{sort + 1}")!;
|
||||
var declareElement = JoinNonEmpty(" ", d.Model, d.Specifications, d.Attributes, d.Material);
|
||||
await _itemRepository.InsertAsync(new Customs9610OrderItem
|
||||
{
|
||||
Customs9610OrderId = header.Id,
|
||||
OrderDeclarationId = d.Id,
|
||||
Sku = Truncate(sku, 32) ?? string.Empty,
|
||||
CnName = Truncate(itemName, 250) ?? string.Empty,
|
||||
ExHsCode = Truncate(FirstNonEmpty(d.ExHsCode, d.HsCode, d.ImHsCode), 10),
|
||||
DeclareElement = Truncate(declareElement, 255),
|
||||
UnitPrice = d.UnitPrice ?? 0,
|
||||
Quantity = d.Quantity ?? 0,
|
||||
Amount = d.Amount,
|
||||
Unit = Truncate(d.Unit, 3),
|
||||
Freight = null,
|
||||
SortNo = sort++,
|
||||
CreateTime = DateTime.Now,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static decimal ResolveGrossWeight(IReadOnlyList<OrderWeight> weights)
|
||||
{
|
||||
var forecast = weights.FirstOrDefault(x =>
|
||||
string.Equals(x.Type, OrderWeightTypeCodes.Forecast, StringComparison.OrdinalIgnoreCase));
|
||||
var row = forecast ?? weights.FirstOrDefault();
|
||||
if (row == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var w = row.ActualWeight ?? row.ChargeableWeight ?? 0;
|
||||
return w > 0 ? w : 0;
|
||||
}
|
||||
|
||||
private static string? FirstNonEmpty(params string?[] values)
|
||||
{
|
||||
foreach (var v in values)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(v))
|
||||
{
|
||||
return v.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? JoinNonEmpty(string sep, params string?[] values)
|
||||
{
|
||||
var parts = values.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => x!.Trim()).ToList();
|
||||
return parts.Count == 0 ? null : string.Join(sep, parts);
|
||||
}
|
||||
|
||||
private static string? Truncate(string? value, int max)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return value.Length <= max ? value : value[..max];
|
||||
}
|
||||
}
|
||||
@ -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),
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -533,7 +543,7 @@ public partial class OrderService
|
||||
var template = await OrderTemplateRepository.FirstOrDefaultAsync(x => x.Id == input.TemplateId)
|
||||
?? throw new BusinessException("模板不存在");
|
||||
|
||||
var effectiveCustomer = await ResolveEffectiveCustomerAsync();
|
||||
var effectiveCustomer = await ResolveEffectiveCustomerAsync(input.CustomerId);
|
||||
EnsureTemplateAccessible(template, effectiveCustomer.Id);
|
||||
OrderTemplateImportMapping.EnsureTemplateReadyForImport(template);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -139,6 +139,7 @@ public partial class OrderService
|
||||
// 客户主动「先保存为草稿」:落库草稿后直接返回,不跑预报计费。
|
||||
if (input.SaveAsDraft)
|
||||
{
|
||||
await TryUpsertCustoms9610Async(order);
|
||||
return await BuildParcelCreateOutputAsync(order);
|
||||
}
|
||||
|
||||
@ -154,6 +155,7 @@ public partial class OrderService
|
||||
await ApplyChargeableWeightAsync(product, snapshotBinding.Rules, forecast);
|
||||
SyncWeightRows(weights, forecast);
|
||||
await OrderRepository.ReplaceWeightsAsync(order.Id, weights);
|
||||
await TryUpsertCustoms9610Async(order);
|
||||
|
||||
var customer = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == order.CustomerId);
|
||||
var priceTierKey = await ResolvePriceTierKeyAsync(order.CustomerId);
|
||||
@ -235,6 +237,20 @@ public partial class OrderService
|
||||
/// </summary>
|
||||
private async Task RollbackFailedParcelCreateAsync(Order order)
|
||||
{
|
||||
try
|
||||
{
|
||||
var writer = LazyServiceProvider.GetRequiredService<Customs9610OrderWriter>();
|
||||
// 强制清掉可能已写入的 pending 9610(ExportCustomsMode 临时置空触发删除逻辑)
|
||||
var mode = order.ExportCustomsMode;
|
||||
order.ExportCustomsMode = null;
|
||||
await writer.UpsertFromOrderAsync(order);
|
||||
order.ExportCustomsMode = mode;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
order.OrderStatus = OrderStatusCodes.Deleted;
|
||||
order.PredeclareFailReason = string.Empty;
|
||||
order.UpdateTime = DateTime.Now;
|
||||
@ -242,6 +258,19 @@ public partial class OrderService
|
||||
await OrderRepository.DeleteAsync(order);
|
||||
}
|
||||
|
||||
private async Task TryUpsertCustoms9610Async(Order order)
|
||||
{
|
||||
try
|
||||
{
|
||||
await LazyServiceProvider.GetRequiredService<Customs9610OrderWriter>()
|
||||
.UpsertFromOrderAsync(order);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 9610 同步失败不阻断运单创建
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 已有草稿重新预报失败时,在业务文案末尾附加机器可读标记,供前端区分。
|
||||
/// 格式:原因 [[omsDraft:{"draftSaved":true,"orderId":1,"serviceNo":"IDE..."}]]
|
||||
|
||||
@ -276,6 +276,9 @@ public partial class OrderService
|
||||
TradeTerms = string.IsNullOrWhiteSpace(product.TradeTerms) ? null : product.TradeTerms.Trim().ToUpperInvariant(),
|
||||
ProductGroup = groupCode,
|
||||
ProductGroupName = groupName,
|
||||
ProductDescription = string.IsNullOrWhiteSpace(product.ProductDescription)
|
||||
? null
|
||||
: product.ProductDescription.Trim(),
|
||||
VolumetricWeight = quote.VolumetricWeight,
|
||||
ChargeableWeight = quote.ChargeableWeight,
|
||||
CostFormula = summary.CostFormula,
|
||||
|
||||
@ -8,6 +8,7 @@ using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
using Oms.Domain.ChannelsAggregate.Entities;
|
||||
using Oms.Domain.WarehouseAggregate.Consts;
|
||||
using Oms.Domain.WarehouseAggregate.Entities;
|
||||
using Oms.Domain.WarehouseAggregate.Repositories;
|
||||
|
||||
@ -279,6 +280,12 @@ public partial class OrderService
|
||||
var order = await GetOrderOrThrowAsync(input.OrderId);
|
||||
var binds = await OrderStockBindRepository.GetListAsync(x =>
|
||||
x.OrderId == order.Id && x.Status == OrderStockBindStatusCodes.Bound);
|
||||
if (binds.Count == 0)
|
||||
{
|
||||
throw new BusinessException("当前订单无已绑定明细,无法确认扣减");
|
||||
}
|
||||
|
||||
await EnsureStockBindReadyForDeductAsync(binds);
|
||||
foreach (var bind in binds)
|
||||
{
|
||||
await DeductBoundLineAsync(bind);
|
||||
@ -906,4 +913,37 @@ public partial class OrderService
|
||||
order.UpdateTime = DateTime.Now;
|
||||
await OrderRepository.UpdateAsync(order);
|
||||
}
|
||||
|
||||
private async Task EnsureStockBindReadyForDeductAsync(IReadOnlyList<OrderStockBind> binds)
|
||||
{
|
||||
var parcelIds = binds
|
||||
.Where(x => x.ParcelId is > 0)
|
||||
.Select(x => x.ParcelId!.Value)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
if (parcelIds.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parcels = await StockBindParcelRepository.GetListAsync(x => parcelIds.Contains(x.Id));
|
||||
var inTransitNos = parcels
|
||||
.Where(x => string.Equals(
|
||||
x.LifecycleStatus,
|
||||
WarehouseParcelLifecycleCodes.InTransit,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
.Select(x => x.TrackingNo?.Trim())
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
if (inTransitNos.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sample = inTransitNos[0];
|
||||
throw new BusinessException(inTransitNos.Length == 1
|
||||
? $"包裹国内在途({sample}),请先到仓确认后再标记已打包"
|
||||
: $"存在 {inTransitNos.Length} 个国内在途包裹,请先到仓确认后再标记已打包");
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +65,25 @@ public partial class OrderService
|
||||
: await SalesStoreOrderRepository.CountAsync(x =>
|
||||
x.FulfillStatus == SalesStoreFulfillStatusCodes.Pending);
|
||||
|
||||
var fulfillFilter = new OrderListFilter
|
||||
{
|
||||
CustomerId = filter.CustomerId,
|
||||
FulfillmentType = FulfillmentTypeCodes.Warehouse,
|
||||
ScopeOrganizationIds = filter.ScopeOrganizationIds,
|
||||
ScopeSecretLevels = filter.ScopeSecretLevels,
|
||||
ScopeDataScope = filter.ScopeDataScope,
|
||||
ScopePersonalOwnerMapId = filter.ScopePersonalOwnerMapId,
|
||||
ScopePersonalOwnerAccount = filter.ScopePersonalOwnerAccount,
|
||||
ScopeOwnerMapIds = filter.ScopeOwnerMapIds,
|
||||
ScopeBizFilters = filter.ScopeBizFilters,
|
||||
ScopeCustomFilters = filter.ScopeCustomFilters,
|
||||
};
|
||||
var fulfillCounts = await OrderRepository.GetFulfillProgressCountsByFilterAsync(fulfillFilter);
|
||||
int FulfillCount(string key) =>
|
||||
fulfillCounts.TryGetValue(key, out var value) ? value : 0;
|
||||
|
||||
var pickupCounts = await PickupOrderRepository.GetWorkplaceCountsAsync(filter.CustomerId);
|
||||
|
||||
return new OrderWorkplaceStatsOutput
|
||||
{
|
||||
Total = counts.Values.Sum(),
|
||||
@ -79,6 +98,20 @@ public partial class OrderService
|
||||
Claimed = GetCount(OrderStatusCodes.Claimed),
|
||||
StoreCount = storeCount,
|
||||
StoreOrderPending = storeOrderPending,
|
||||
Fulfill = new FulfillWorkplaceStatsOutput
|
||||
{
|
||||
PendingBind = FulfillCount(FulfillmentProgressCodes.PendingBind),
|
||||
DomesticInTransit = FulfillCount(FulfillmentProgressCodes.DomesticInTransit),
|
||||
ArrivedPendingPick = FulfillCount(FulfillmentProgressCodes.ArrivedPendingPick),
|
||||
OutboundDone = FulfillCount(FulfillmentProgressCodes.OutboundDone),
|
||||
},
|
||||
Pickup = new PickupWorkplaceStatsOutput
|
||||
{
|
||||
PendingPayment = pickupCounts.PendingPayment,
|
||||
PendingSchedule = pickupCounts.PendingSchedule,
|
||||
PendingPickup = pickupCounts.PendingPickup,
|
||||
PendingInbound = pickupCounts.PendingInbound,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -198,6 +231,79 @@ public partial class OrderService
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 工作台业务线趋势(运单 / 代发 / 揽收新增量)。
|
||||
/// </summary>
|
||||
[HttpGet("/order/workplace-business-trend")]
|
||||
public async Task<OrderWorkplaceBusinessTrendOutput> GetWorkplaceBusinessTrendAsync(
|
||||
[FromQuery] string? dimension = "day",
|
||||
[FromQuery] int count = 7)
|
||||
{
|
||||
var dim = (dimension ?? "day").Trim().ToLowerInvariant();
|
||||
if (dim is not ("day" or "week" or "month"))
|
||||
{
|
||||
dim = "day";
|
||||
}
|
||||
|
||||
count = dim switch
|
||||
{
|
||||
"week" => Math.Clamp(count, 1, 12),
|
||||
"month" => Math.Clamp(count, 1, 12),
|
||||
_ => Math.Clamp(count, 1, 31),
|
||||
};
|
||||
|
||||
var filter = await BuildScopedListFilterAsync();
|
||||
var fulfillFilter = CloneScopedFilter(filter);
|
||||
fulfillFilter.FulfillmentType = FulfillmentTypeCodes.Warehouse;
|
||||
|
||||
var buckets = BuildTrendBuckets(dim, count);
|
||||
var points = new List<OrderWorkplaceBusinessTrendPointOutput>(buckets.Count);
|
||||
|
||||
foreach (var bucket in buckets)
|
||||
{
|
||||
var totalOrders = await OrderRepository.CountByFilterAndCreateTimeRangeAsync(
|
||||
filter,
|
||||
bucket.Start,
|
||||
bucket.EndExclusive);
|
||||
var fulfillOrders = await OrderRepository.CountByFilterAndCreateTimeRangeAsync(
|
||||
fulfillFilter,
|
||||
bucket.Start,
|
||||
bucket.EndExclusive);
|
||||
var pickupOrders = await PickupOrderRepository.CountByCreateTimeRangeAsync(
|
||||
filter.CustomerId,
|
||||
bucket.Start,
|
||||
bucket.EndExclusive);
|
||||
|
||||
points.Add(new OrderWorkplaceBusinessTrendPointOutput
|
||||
{
|
||||
Label = bucket.Label,
|
||||
WaybillCount = Math.Max(0, totalOrders - fulfillOrders),
|
||||
FulfillCount = fulfillOrders,
|
||||
PickupCount = pickupOrders,
|
||||
});
|
||||
}
|
||||
|
||||
return new OrderWorkplaceBusinessTrendOutput
|
||||
{
|
||||
Dimension = dim,
|
||||
Points = points,
|
||||
};
|
||||
}
|
||||
|
||||
private static OrderListFilter CloneScopedFilter(OrderListFilter filter) =>
|
||||
new()
|
||||
{
|
||||
CustomerId = filter.CustomerId,
|
||||
ScopeOrganizationIds = filter.ScopeOrganizationIds,
|
||||
ScopeSecretLevels = filter.ScopeSecretLevels,
|
||||
ScopeDataScope = filter.ScopeDataScope,
|
||||
ScopePersonalOwnerMapId = filter.ScopePersonalOwnerMapId,
|
||||
ScopePersonalOwnerAccount = filter.ScopePersonalOwnerAccount,
|
||||
ScopeOwnerMapIds = filter.ScopeOwnerMapIds,
|
||||
ScopeBizFilters = filter.ScopeBizFilters,
|
||||
ScopeCustomFilters = filter.ScopeCustomFilters,
|
||||
};
|
||||
|
||||
private async Task<OrderListFilter> BuildScopedListFilterAsync()
|
||||
{
|
||||
var customerId = await OmsSessionHelper.ResolveListCustomerIdAsync(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -413,8 +413,11 @@ public class PickupOrderService : ApplicationService
|
||||
(customerId == null || x.CustomerId == customerId.Value) &&
|
||||
(status == null || x.Status == status) &&
|
||||
(payStatus == null || x.PayStatus == payStatus) &&
|
||||
(input.WarehouseId == null || input.WarehouseId <= 0 || x.WarehouseId == input.WarehouseId.Value) &&
|
||||
(input.CreateTimeStart == null || x.CreateTime >= input.CreateTimeStart) &&
|
||||
(input.CreateTimeEnd == null || x.CreateTime <= input.CreateTimeEnd) &&
|
||||
(input.ExpectPickupTimeStart == null || x.ExpectPickupTime >= input.ExpectPickupTimeStart) &&
|
||||
(input.ExpectPickupTimeEnd == null || x.ExpectPickupTime <= input.ExpectPickupTimeEnd) &&
|
||||
(keyword == null || x.PickupNo.Contains(keyword) ||
|
||||
(x.CarrierWaybillNo != null && x.CarrierWaybillNo.Contains(keyword)) ||
|
||||
(x.SenderName != null && x.SenderName.Contains(keyword))));
|
||||
@ -431,6 +434,38 @@ public class PickupOrderService : ApplicationService
|
||||
return new OmsPageResult<PickupOrderOutput> { Items = outputs, Total = total };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 揽收单状态计数(Tab 徽章)。
|
||||
/// </summary>
|
||||
[HttpGet("/order/pickup-order/status-counts")]
|
||||
public async Task<PickupOrderStatusCountsOutput> GetStatusCountsAsync([FromQuery] int? customerId)
|
||||
{
|
||||
var resolvedCustomerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
|
||||
CustomerRepository,
|
||||
CurrentUser,
|
||||
customerId);
|
||||
|
||||
var all = await PickupOrderRepository.GetListAsync(x =>
|
||||
resolvedCustomerId == null || x.CustomerId == resolvedCustomerId.Value);
|
||||
|
||||
var terminal = new[] { PickupOrderStatusCodes.Cancelled, PickupOrderStatusCodes.Failed };
|
||||
var pendingPayment = all.Count(x =>
|
||||
!terminal.Contains(x.Status) &&
|
||||
x.PayStatus != PickupPayStatusCodes.Paid &&
|
||||
x.ReceivableAmount is > 0);
|
||||
|
||||
var counts = all
|
||||
.GroupBy(x => x.Status ?? string.Empty, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(g => g.Key, g => g.Count(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
return new PickupOrderStatusCountsOutput
|
||||
{
|
||||
Total = all.Count,
|
||||
PendingPayment = pendingPayment,
|
||||
Counts = counts
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 揽收单详情。
|
||||
/// </summary>
|
||||
@ -930,7 +965,8 @@ public class PickupOrderService : ApplicationService
|
||||
SettlementStatus = entity.SettlementStatus,
|
||||
SettlementBatchNo = entity.SettlementBatchNo,
|
||||
Remark = entity.Remark,
|
||||
CreateTime = entity.CreateTime
|
||||
CreateTime = entity.CreateTime,
|
||||
UpdateTime = entity.UpdateTime
|
||||
};
|
||||
|
||||
if (!includeOrders)
|
||||
|
||||
@ -109,6 +109,7 @@ public class OmsUserService(
|
||||
EmployeeNo = employeeNo,
|
||||
WechatId = input.WechatId,
|
||||
WechatQrCode = input.WechatQrCode,
|
||||
WecomKfUrl = input.WecomKfUrl,
|
||||
},
|
||||
cancellationToken);
|
||||
|
||||
@ -145,6 +146,7 @@ public class OmsUserService(
|
||||
Phone = input.Phone,
|
||||
WechatId = input.WechatId,
|
||||
WechatQrCode = input.WechatQrCode,
|
||||
WecomKfUrl = input.WecomKfUrl,
|
||||
},
|
||||
cancellationToken);
|
||||
|
||||
|
||||
@ -130,14 +130,15 @@ public class NoticeService : ApplicationService
|
||||
{
|
||||
var noticeType = NormalizeNoticeType(input.NoticeType);
|
||||
ValidateNoticeText(noticeType, input.Title, input.Content);
|
||||
var receiverIds = await NormalizeAndValidateReceiverIdsAsync(input.ReceiverType, input.ReceiverIds);
|
||||
var receiverType = ResolveReceiverType(noticeType, input.ReceiverType);
|
||||
var receiverIds = await NormalizeAndValidateReceiverIdsAsync(noticeType, receiverType, input.ReceiverIds);
|
||||
await NoticeRepository.InsertAsync(new Notice
|
||||
{
|
||||
NoticeType = noticeType,
|
||||
Title = NormalizeTitle(input.Title),
|
||||
Content = input.Content.Trim(),
|
||||
IsPopup = noticeType == NoticeTypeMessage ? (byte)0 : NormalizePopupValue(input.IsPopup),
|
||||
ReceiverType = NormalizeReceiverType(input.ReceiverType),
|
||||
ReceiverType = receiverType,
|
||||
ReceiverIds = OmsDocHelper.SerializeJson(receiverIds) ?? "[]"
|
||||
});
|
||||
}
|
||||
@ -151,20 +152,27 @@ public class NoticeService : ApplicationService
|
||||
var entity = await GetNoticeOrThrowAsync(input.Id);
|
||||
var noticeType = NormalizeNoticeType(input.NoticeType);
|
||||
ValidateNoticeText(noticeType, input.Title, input.Content);
|
||||
var receiverIds = await NormalizeAndValidateReceiverIdsAsync(input.ReceiverType, input.ReceiverIds);
|
||||
var receiverType = ResolveReceiverType(noticeType, input.ReceiverType);
|
||||
var receiverIds = await NormalizeAndValidateReceiverIdsAsync(noticeType, receiverType, input.ReceiverIds);
|
||||
entity.NoticeType = noticeType;
|
||||
entity.Title = NormalizeTitle(input.Title);
|
||||
entity.Content = input.Content.Trim();
|
||||
entity.IsPopup = noticeType == NoticeTypeMessage ? (byte)0 : NormalizePopupValue(input.IsPopup);
|
||||
entity.ReceiverType = NormalizeReceiverType(input.ReceiverType);
|
||||
entity.ReceiverType = receiverType;
|
||||
entity.ReceiverIds = OmsDocHelper.SerializeJson(receiverIds) ?? "[]";
|
||||
await NoticeRepository.UpdateAsync(entity);
|
||||
|
||||
var existsReads = await NoticeReadRepository.GetListAsync(x => x.NoticeId == entity.Id);
|
||||
var receiverIdSet = receiverIds.ToHashSet();
|
||||
foreach (var read in existsReads.Where(x => x.ReceiverType != ReceiverTypeUser || !receiverIdSet.Contains(x.ReceiverId)))
|
||||
// 全体广播不清理已读;定向接收时移除已不在名单内的已读记录
|
||||
if (receiverIds.Count > 0)
|
||||
{
|
||||
await NoticeReadRepository.DeleteAsync(read);
|
||||
var existsReads = await NoticeReadRepository.GetListAsync(x => x.NoticeId == entity.Id);
|
||||
var receiverIdSet = receiverIds.ToHashSet();
|
||||
foreach (var read in existsReads.Where(x =>
|
||||
!string.Equals(x.ReceiverType, receiverType, StringComparison.OrdinalIgnoreCase) ||
|
||||
!receiverIdSet.Contains(x.ReceiverId)))
|
||||
{
|
||||
await NoticeReadRepository.DeleteAsync(read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,14 +194,17 @@ public class NoticeService : ApplicationService
|
||||
{
|
||||
var notice = await GetNoticeOrThrowAsync(input.Id);
|
||||
var receiverIds = OmsDocHelper.DeserializeJson<List<int>>(notice.ReceiverIds) ?? [];
|
||||
var currentReceiverType = NormalizeReceiverType(notice.ReceiverType);
|
||||
var currentReceiverType = OmsSessionHelper.IsCustomerLogin(CurrentUser)
|
||||
? ReceiverTypeCustomer
|
||||
: ReceiverTypeUser;
|
||||
var currentReceiverId = CurrentUser.GetId<int>();
|
||||
if (currentReceiverId <= 0)
|
||||
{
|
||||
throw new BusinessException("当前登录主体无效");
|
||||
}
|
||||
|
||||
if (!receiverIds.Contains(currentReceiverId))
|
||||
// 空接收人 = 全体广播;否则校验当前主体在定向名单内
|
||||
if (receiverIds.Count > 0 && !receiverIds.Contains(currentReceiverId))
|
||||
{
|
||||
throw new BusinessException("当前主体不在通知接收范围内");
|
||||
}
|
||||
@ -355,11 +366,15 @@ public class NoticeService : ApplicationService
|
||||
(string.IsNullOrWhiteSpace(keyword) ||
|
||||
x.Title.Contains(keyword) ||
|
||||
x.Content.Contains(keyword)) &&
|
||||
x.ReceiverType == expectedReceiverType &&
|
||||
(x.ReceiverIds == $"[{receiverIdToken}]" ||
|
||||
x.ReceiverIds.StartsWith($"[{receiverIdToken},") ||
|
||||
x.ReceiverIds.Contains($",{receiverIdToken},") ||
|
||||
x.ReceiverIds.EndsWith($",{receiverIdToken}]")) &&
|
||||
(
|
||||
// 空接收人列表 = 全体广播(员工/客户均可见)
|
||||
x.ReceiverIds == "[]" ||
|
||||
(x.ReceiverType == expectedReceiverType &&
|
||||
(x.ReceiverIds == $"[{receiverIdToken}]" ||
|
||||
x.ReceiverIds.StartsWith($"[{receiverIdToken},") ||
|
||||
x.ReceiverIds.Contains($",{receiverIdToken},") ||
|
||||
x.ReceiverIds.EndsWith($",{receiverIdToken}]")))
|
||||
) &&
|
||||
(excluded.Count == 0 || !excluded.Contains(x.Id));
|
||||
}
|
||||
|
||||
@ -374,19 +389,29 @@ public class NoticeService : ApplicationService
|
||||
return entity;
|
||||
}
|
||||
|
||||
private async Task<List<int>> NormalizeAndValidateReceiverIdsAsync(string receiverType, IEnumerable<int>? receiverIds)
|
||||
private async Task<List<int>> NormalizeAndValidateReceiverIdsAsync(
|
||||
string noticeType,
|
||||
string receiverType,
|
||||
IEnumerable<int>? receiverIds)
|
||||
{
|
||||
var normalizedReceiverType = NormalizeReceiverType(receiverType);
|
||||
var selectionIds = (receiverIds ?? [])
|
||||
.Where(x => x > 0)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
// 公告默认全体生效,允许空接收人;站内信必须指定接收人
|
||||
if (selectionIds.Count == 0)
|
||||
{
|
||||
if (noticeType == NoticeTypeNotice)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
throw new BusinessException("接收人不能为空");
|
||||
}
|
||||
|
||||
var normalizedReceiverType = NormalizeReceiverType(receiverType);
|
||||
|
||||
if (normalizedReceiverType == ReceiverTypeCustomer)
|
||||
{
|
||||
var customers = await CustomerRepository.GetListAsync(x =>
|
||||
@ -420,6 +445,19 @@ public class NoticeService : ApplicationService
|
||||
return selectionIds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 公告全体广播时接收类型仅作占位;站内信必须明确 user/customer。
|
||||
/// </summary>
|
||||
private static string ResolveReceiverType(string noticeType, string? receiverType)
|
||||
{
|
||||
if (noticeType == NoticeTypeNotice && string.IsNullOrWhiteSpace(receiverType))
|
||||
{
|
||||
return ReceiverTypeUser;
|
||||
}
|
||||
|
||||
return NormalizeReceiverType(receiverType ?? string.Empty);
|
||||
}
|
||||
|
||||
private async Task<List<int>> QueryValidUserIdsAsync(IReadOnlyCollection<int> userIds)
|
||||
{
|
||||
if (userIds.Count == 0)
|
||||
@ -456,6 +494,7 @@ public class NoticeService : ApplicationService
|
||||
List<int> receiverSelectionIds)
|
||||
{
|
||||
var receiverIds = OmsDocHelper.DeserializeJson<List<int>>(entity.ReceiverIds) ?? [];
|
||||
var isBroadcast = receiverIds.Count == 0;
|
||||
return new NoticeOutput
|
||||
{
|
||||
Id = entity.Id,
|
||||
@ -465,12 +504,14 @@ public class NoticeService : ApplicationService
|
||||
Content = entity.Content,
|
||||
IsPopup = entity.IsPopup,
|
||||
ReceiverType = entity.ReceiverType,
|
||||
ReceiverTypeName = entity.ReceiverType == ReceiverTypeCustomer ? "客户" : "用户",
|
||||
ReceiverTypeName = isBroadcast
|
||||
? "全部"
|
||||
: entity.ReceiverType == ReceiverTypeCustomer ? "客户" : "用户",
|
||||
ReceiverIds = receiverIds,
|
||||
ReceiverSelectionIds = receiverSelectionIds,
|
||||
ReadCount = readCount,
|
||||
IsRead = isRead,
|
||||
ReceiverCount = receiverIds.Distinct().Count(),
|
||||
ReceiverCount = isBroadcast ? 0 : receiverIds.Distinct().Count(),
|
||||
CreateTime = entity.CreateTime,
|
||||
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
|
||||
};
|
||||
|
||||
@ -96,7 +96,10 @@ public partial class WarehouseService
|
||||
[HttpPost("/warehouse/parcel-arrival/create")]
|
||||
public async Task<WarehouseParcelArrivalOutput> CreateParcelArrivalAsync(WarehouseParcelArrivalCreateInput input)
|
||||
{
|
||||
var customerId = await ResolveMutationCustomerIdAsync(input.CustomerId);
|
||||
// 到仓按仓库 + 快递单号定位包裹;客户归属取自包裹本身,管理端无需先选客户。
|
||||
// 客户门户会话仍会限制只能到自己的包裹。
|
||||
var sessionCustomerId = await ResolveListCustomerIdAsync(null);
|
||||
var (hasCustomerFilter, customerIdValue) = CustomerFilter(sessionCustomerId);
|
||||
if (input.WarehouseId <= 0)
|
||||
{
|
||||
throw new BusinessException("仓库ID不能为空");
|
||||
@ -111,15 +114,21 @@ public partial class WarehouseService
|
||||
var warehouse = await WarehouseAddressRepository.FirstOrDefaultAsync(x => x.Id == input.WarehouseId)
|
||||
?? throw new BusinessException("仓库不存在");
|
||||
|
||||
var parcel = await ParcelRepository.FirstOrDefaultAsync(x =>
|
||||
x.CustomerId == customerId
|
||||
&& x.WarehouseId == input.WarehouseId
|
||||
&& x.TrackingNo == trackingNo);
|
||||
if (parcel == null)
|
||||
var parcels = await ParcelRepository.GetListAsync(x =>
|
||||
x.WarehouseId == input.WarehouseId
|
||||
&& x.TrackingNo == trackingNo
|
||||
&& (!hasCustomerFilter || x.CustomerId == customerIdValue));
|
||||
if (parcels.Count == 0)
|
||||
{
|
||||
throw new BusinessException("未找到对应包裹,请确认快递单号与仓库");
|
||||
}
|
||||
|
||||
if (parcels.Count > 1)
|
||||
{
|
||||
throw new BusinessException("同一仓库下存在多条相同快递单号的包裹,请先在「我的包裹」核对后再到仓");
|
||||
}
|
||||
|
||||
var parcel = parcels[0];
|
||||
if (parcel.LifecycleStatus == WarehouseParcelLifecycleCodes.Stored)
|
||||
{
|
||||
throw new BusinessException("包裹已上架,无法再到仓");
|
||||
@ -131,7 +140,7 @@ public partial class WarehouseService
|
||||
|
||||
var entity = new CParcelArrival
|
||||
{
|
||||
CustomerId = customerId,
|
||||
CustomerId = parcel.CustomerId,
|
||||
WarehouseId = input.WarehouseId,
|
||||
ParcelId = parcel.Id,
|
||||
TrackingNo = trackingNo
|
||||
|
||||
67
src/Oms.Application/Shared/StaffAdvisorBinder.cs
Normal file
67
src/Oms.Application/Shared/StaffAdvisorBinder.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using Fake.Rbac.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oms.Domain.CustomersAggregate.Entities;
|
||||
|
||||
namespace Oms.Application.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// 顾问邀请绑定:注册时把所属顾问(CustomerService)写成员工登录账号。
|
||||
/// </summary>
|
||||
public static class StaffAdvisorBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// 仅在 CustomerService 为空时绑定;advisorCode 为员工 Account(来自 ?advisor=)。
|
||||
/// 无效账号静默跳过,不阻断注册。
|
||||
/// </summary>
|
||||
public static async Task BindAdvisorOnCreateAsync(
|
||||
Customer entity,
|
||||
string? advisorCode,
|
||||
FakeRbacDbContext rbacDbContext,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(entity.CustomerService))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var key = advisorCode?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var account = await ResolveStaffAccountAsync(rbacDbContext, key, cancellationToken);
|
||||
if (string.IsNullOrWhiteSpace(account))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
entity.CustomerService = account;
|
||||
}
|
||||
|
||||
private static async Task<string?> ResolveStaffAccountAsync(
|
||||
FakeRbacDbContext rbacDbContext,
|
||||
string key,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
const string sql = """
|
||||
SELECT u.`Account` AS `Account`
|
||||
FROM `user` u
|
||||
WHERE u.`IsDeleted` = 0
|
||||
AND u.`Account` = {0}
|
||||
ORDER BY u.`CreateTime` ASC
|
||||
LIMIT 1
|
||||
""";
|
||||
|
||||
var row = await rbacDbContext.Database
|
||||
.SqlQueryRaw<AccountRow>(sql, key)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
return string.IsNullOrWhiteSpace(row?.Account) ? null : row.Account.Trim();
|
||||
}
|
||||
|
||||
private sealed class AccountRow
|
||||
{
|
||||
public string? Account { get; set; }
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,11 @@ public class SalesProduct : OmsAuditedAggregateRoot
|
||||
/// </summary>
|
||||
public string? TradeTerms { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 官网线路排序,数值越小越靠前。
|
||||
/// </summary>
|
||||
public int RouteSort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 可选附加服务字典 code JSON 数组,如 ["insurance","remoteFee"]。
|
||||
/// </summary>
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
namespace Oms.Domain.OrdersAggregate.Consts;
|
||||
|
||||
public static class Customs9610StatusCodes
|
||||
{
|
||||
public const string Pending = "pending";
|
||||
public const string Declared = "declared";
|
||||
|
||||
public static readonly string[] All = [Pending, Declared];
|
||||
}
|
||||
|
||||
public static class Customs9610FormTypeCodes
|
||||
{
|
||||
public const string SimpleA = "A";
|
||||
public const string SummaryB = "B";
|
||||
}
|
||||
164
src/Oms.Domain/OrdersAggregate/Entities/Customs9610Order.cs
Normal file
164
src/Oms.Domain/OrdersAggregate/Entities/Customs9610Order.cs
Normal file
@ -0,0 +1,164 @@
|
||||
using Oms.Domain.Shared;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Oms.Domain.OrdersAggregate.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 9610 汇总申报单(1 运单 : 0..1)。
|
||||
/// </summary>
|
||||
[SugarTable("t_9610_order")]
|
||||
public class Customs9610Order : OmsAuditedAggregateRoot
|
||||
{
|
||||
public int OrderId { get; set; }
|
||||
|
||||
[SugarColumn(Length = 64, IsNullable = true)]
|
||||
public string? ServiceNo { get; set; }
|
||||
|
||||
public int CustomerId { get; set; }
|
||||
|
||||
[SugarColumn(Length = 16)]
|
||||
public string Status { get; set; } = "pending";
|
||||
|
||||
/// <summary>电商平台。</summary>
|
||||
[SugarColumn(Length = 16)]
|
||||
public string PlatformCode { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>电商企业代码。</summary>
|
||||
[SugarColumn(Length = 18, IsNullable = true)]
|
||||
public string? EcomCorpCode { get; set; }
|
||||
|
||||
/// <summary>电商企业名称。</summary>
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? EcomCorpName { get; set; }
|
||||
|
||||
/// <summary>生产销售单位代码。</summary>
|
||||
[SugarColumn(Length = 18, IsNullable = true)]
|
||||
public string? ProducerCode { get; set; }
|
||||
|
||||
/// <summary>生产销售单位。</summary>
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? ProducerName { get; set; }
|
||||
|
||||
[SugarColumn(Length = 48)]
|
||||
public string ClientNo { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>业务类型:A 简化 / B 汇总。</summary>
|
||||
[SugarColumn(Length = 1)]
|
||||
public string BusinessType { get; set; } = "B";
|
||||
|
||||
/// <summary>区域 SZ/other(A 必填)。</summary>
|
||||
[SugarColumn(Length = 10, IsNullable = true)]
|
||||
public string? Region { get; set; }
|
||||
|
||||
/// <summary>是否代理申报:是/否(A 必填)。</summary>
|
||||
[SugarColumn(Length = 2, IsNullable = true)]
|
||||
public string? AgentDeclare { get; set; }
|
||||
|
||||
/// <summary>实际卖家信用代码(A 可填)。</summary>
|
||||
[SugarColumn(Length = 18, IsNullable = true)]
|
||||
public string? ActualSellerCode { get; set; }
|
||||
|
||||
/// <summary>汇总预录:0 已预录 / 1 未预录(B 必填)。</summary>
|
||||
[SugarColumn(Length = 1, IsNullable = true)]
|
||||
public string? PreRecordFlag { get; set; }
|
||||
|
||||
/// <summary>是否汇总简化申报。</summary>
|
||||
public byte IsSummarySimple { get; set; }
|
||||
|
||||
public decimal GrossWeight { get; set; }
|
||||
|
||||
[SugarColumn(Length = 4)]
|
||||
public string Currency { get; set; } = "CNY";
|
||||
|
||||
/// <summary>目的国。</summary>
|
||||
[SugarColumn(Length = 4)]
|
||||
public string DestCountry { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(DecimalDigits = 5, IsNullable = true)]
|
||||
public decimal? Freight { get; set; }
|
||||
|
||||
[SugarColumn(Length = 4, IsNullable = true)]
|
||||
public string? FreightCurrency { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public int? FreightMark { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public int? PackageType { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public int? PackageQty { get; set; }
|
||||
|
||||
/// <summary>出货单号。</summary>
|
||||
[SugarColumn(Length = 32, IsNullable = true)]
|
||||
public string? ShipmentNo { get; set; }
|
||||
|
||||
/// <summary>海关订单号(申报回写)。</summary>
|
||||
[SugarColumn(Length = 32, IsNullable = true)]
|
||||
public string? CustomsOrderNo { get; set; }
|
||||
|
||||
[SugarColumn(Length = 32, IsNullable = true)]
|
||||
public string? DownstreamRefNo { get; set; }
|
||||
|
||||
[SugarColumn(Length = 48, IsNullable = true)]
|
||||
public string? EnterpriseBizNo { get; set; }
|
||||
|
||||
[SugarColumn(Length = 48, IsNullable = true)]
|
||||
public string? ManifestNo { get; set; }
|
||||
|
||||
[SugarColumn(Length = 48, IsNullable = true)]
|
||||
public string? ClearanceTime { get; set; }
|
||||
|
||||
[SugarColumn(Length = 48, IsNullable = true)]
|
||||
public string? CustomsInboundTime { get; set; }
|
||||
|
||||
[SugarColumn(Length = 4, IsNullable = true)]
|
||||
public string? CustomsState { get; set; }
|
||||
|
||||
[SugarColumn(Length = 4, IsNullable = true)]
|
||||
public string? DeclareCustomsCode { get; set; }
|
||||
|
||||
/// <summary>电商平台代码(清单回写)。</summary>
|
||||
[SugarColumn(Length = 18, IsNullable = true)]
|
||||
public string? PlatformCorpCode { get; set; }
|
||||
|
||||
/// <summary>电商平台名称(清单回写)。</summary>
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? PlatformName { get; set; }
|
||||
|
||||
[SugarColumn(Length = 18, IsNullable = true)]
|
||||
public string? LogisticsCorpCode { get; set; }
|
||||
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? LogisticsCorpName { get; set; }
|
||||
|
||||
[SugarColumn(Length = 4, IsNullable = true)]
|
||||
public string? IeMark { get; set; }
|
||||
|
||||
[SugarColumn(Length = 4, IsNullable = true)]
|
||||
public string? ExportPortCode { get; set; }
|
||||
|
||||
[SugarColumn(Length = 32, IsNullable = true)]
|
||||
public string? ExportDate { get; set; }
|
||||
|
||||
[SugarColumn(Length = 18, IsNullable = true)]
|
||||
public string? DeclareAgentCode { get; set; }
|
||||
|
||||
[SugarColumn(Length = 100, IsNullable = true)]
|
||||
public string? DeclareAgentName { get; set; }
|
||||
|
||||
[SugarColumn(Length = 4, IsNullable = true)]
|
||||
public string? TradeModeCode { get; set; }
|
||||
|
||||
[SugarColumn(Length = 4, IsNullable = true)]
|
||||
public string? TransportMode { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? DeclareTime { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public DateTime? ManifestSyncTime { get; set; }
|
||||
|
||||
[SugarColumn(Length = 512, IsNullable = true)]
|
||||
public string? FailReason { get; set; }
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
using Oms.Domain.Shared;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Oms.Domain.OrdersAggregate.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 9610 申报商品明细(字段名对齐 t_order_declaration;海关专属另列)。
|
||||
/// </summary>
|
||||
[SugarTable("t_9610_order_item")]
|
||||
public class Customs9610OrderItem : OmsAuditedAggregateRoot
|
||||
{
|
||||
public int Customs9610OrderId { get; set; }
|
||||
|
||||
[SugarColumn(IsNullable = true)]
|
||||
public int? OrderDeclarationId { get; set; }
|
||||
|
||||
[SugarColumn(Length = 32)]
|
||||
public string Sku { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(Length = 250)]
|
||||
public string CnName { get; set; } = string.Empty;
|
||||
|
||||
[SugarColumn(Length = 10, IsNullable = true)]
|
||||
public string? ExHsCode { get; set; }
|
||||
|
||||
/// <summary>申报要素。</summary>
|
||||
[SugarColumn(Length = 255, IsNullable = true)]
|
||||
public string? DeclareElement { get; set; }
|
||||
|
||||
[SugarColumn(Length = 255, IsNullable = true)]
|
||||
public string? Barcode { get; set; }
|
||||
|
||||
[SugarColumn(Length = 255, IsNullable = true)]
|
||||
public string? DestCountry { get; set; }
|
||||
|
||||
[SugarColumn(Length = 3, IsNullable = true)]
|
||||
public string? ItemCurrency { get; set; }
|
||||
|
||||
public decimal UnitPrice { get; set; }
|
||||
|
||||
[SugarColumn(DecimalDigits = 5, IsNullable = true)]
|
||||
public decimal? Amount { get; set; }
|
||||
|
||||
public decimal Quantity { get; set; }
|
||||
|
||||
[SugarColumn(DecimalDigits = 5, IsNullable = true)]
|
||||
public decimal? LegalQty { get; set; }
|
||||
|
||||
[SugarColumn(Length = 3, IsNullable = true)]
|
||||
public string? Unit { get; set; }
|
||||
|
||||
[SugarColumn(Length = 3, IsNullable = true)]
|
||||
public string? LegalUnit { get; set; }
|
||||
|
||||
[SugarColumn(DecimalDigits = 4, IsNullable = true)]
|
||||
public decimal? Freight { get; set; }
|
||||
|
||||
public int SortNo { get; set; }
|
||||
}
|
||||
@ -19,4 +19,34 @@ public class OrderReceivablePayableFilter : OrderListFilter
|
||||
/// 计费阶段:forecast / inbound / outbound。
|
||||
/// </summary>
|
||||
public string? ChargingStage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 客户编码关键词(模糊匹配)。
|
||||
/// </summary>
|
||||
public string? CustomerCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 仅保留各运单费用项的当前生效阶段(出库 > 入库 > 预报)。
|
||||
/// </summary>
|
||||
public bool EffectiveStageOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间筛选字段:createTime / receiveTime / shipTime / payTime。
|
||||
/// </summary>
|
||||
public string? TimeField { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间区间起(与 TimeField 对应字段)。
|
||||
/// </summary>
|
||||
public DateTime? TimeStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 时间区间止(与 TimeField 对应字段)。
|
||||
/// </summary>
|
||||
public DateTime? TimeEnd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付状态:0 未支付,1 已支付;null 表示全部。
|
||||
/// </summary>
|
||||
public int? IsPaid { get; set; }
|
||||
}
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
namespace Oms.Domain.OrdersAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// 应收应付汇总行(按费用类型 + 币种)。
|
||||
/// </summary>
|
||||
public class OrderReceivablePayableSummaryRow
|
||||
{
|
||||
public string FeeType { get; set; } = string.Empty;
|
||||
|
||||
public string Currency { get; set; } = string.Empty;
|
||||
|
||||
public decimal TotalAmount { get; set; }
|
||||
|
||||
public int LineCount { get; set; }
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
using Fake.Domain.Repositories;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
|
||||
namespace Oms.Domain.OrdersAggregate.Repositories;
|
||||
|
||||
public interface ICustoms9610OrderRepository : IRepository<Customs9610Order>
|
||||
{
|
||||
Task<Customs9610Order?> GetByOrderIdAsync(int orderId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task HardDeleteItemsByHeaderIdAsync(int customs9610OrderId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task HardDeleteAsync(Customs9610Order entity, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface ICustoms9610OrderItemRepository : IRepository<Customs9610OrderItem>
|
||||
{
|
||||
Task<List<Customs9610OrderItem>> GetByHeaderIdAsync(
|
||||
int customs9610OrderId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<Customs9610OrderItem>> GetByHeaderIdsAsync(
|
||||
IReadOnlyCollection<int> headerIds,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -76,6 +76,15 @@ public interface IOrderRepository : IRepository<Order>
|
||||
OrderReceivablePayableFilter filter,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<OrderReceivablePayableSummaryRow>> GetReceivablePayableSummaryAsync(
|
||||
OrderReceivablePayableFilter filter,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<OrderReceivablePayableRow>> GetReceivablePayableExportListAsync(
|
||||
OrderReceivablePayableFilter filter,
|
||||
int maxRows,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<OrderStatusCountRow>> GetStatusCountsAsync(int? customerId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<List<OrderStatusCountRow>> GetStatusCountsByFilterAsync(
|
||||
@ -86,6 +95,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,
|
||||
|
||||
@ -8,6 +8,27 @@ public interface IPickupOrderRepository : IRepository<PickupOrder>
|
||||
Task<PickupOrder?> GetByPickupNoAsync(string pickupNo, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PickupOrder?> GetOwnedAsync(int id, int customerId, CancellationToken cancellationToken = default);
|
||||
|
||||
Task<PickupWorkplaceCountRow> GetWorkplaceCountsAsync(
|
||||
int? customerId,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
Task<int> CountByCreateTimeRangeAsync(
|
||||
int? customerId,
|
||||
DateTime startInclusive,
|
||||
DateTime endExclusive,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public sealed class PickupWorkplaceCountRow
|
||||
{
|
||||
public int PendingPayment { get; set; }
|
||||
|
||||
public int PendingSchedule { get; set; }
|
||||
|
||||
public int PendingPickup { get; set; }
|
||||
|
||||
public int PendingInbound { get; set; }
|
||||
}
|
||||
|
||||
public interface IPickupOrderLineRepository : IRepository<PickupOrderLine>
|
||||
|
||||
56
src/Oms.SqlSugarCore/Orders/Customs9610OrderRepository.cs
Normal file
56
src/Oms.SqlSugarCore/Orders/Customs9610OrderRepository.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using Fake.DependencyInjection;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
using Oms.SqlSugarCore.Repositories;
|
||||
|
||||
namespace Oms.SqlSugarCore.Orders;
|
||||
|
||||
public class Customs9610OrderRepository : OmsSqlSugarRepository<Customs9610Order>, ICustoms9610OrderRepository, IScopedDependency
|
||||
{
|
||||
public Task<Customs9610Order?> GetByOrderIdAsync(int orderId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return FirstOrDefaultAsync(x => x.OrderId == orderId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task HardDeleteItemsByHeaderIdAsync(int customs9610OrderId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var db = await GetDbContextAsync(cancellationToken);
|
||||
await db.Deleteable<Customs9610OrderItem>()
|
||||
.Where(x => x.Customs9610OrderId == customs9610OrderId)
|
||||
.ExecuteCommandAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task HardDeleteAsync(Customs9610Order entity, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var db = await GetDbContextAsync(cancellationToken);
|
||||
await db.Deleteable<Customs9610Order>()
|
||||
.Where(x => x.Id == entity.Id)
|
||||
.ExecuteCommandAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class Customs9610OrderItemRepository
|
||||
: OmsSqlSugarRepository<Customs9610OrderItem>,
|
||||
ICustoms9610OrderItemRepository,
|
||||
IScopedDependency
|
||||
{
|
||||
public Task<List<Customs9610OrderItem>> GetByHeaderIdAsync(
|
||||
int customs9610OrderId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return GetListAsync(x => x.Customs9610OrderId == customs9610OrderId, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<Customs9610OrderItem>> GetByHeaderIdsAsync(
|
||||
IReadOnlyCollection<int> headerIds,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (headerIds == null || headerIds.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var ids = headerIds.Distinct().ToList();
|
||||
return await GetListAsync(x => ids.Contains(x.Customs9610OrderId), cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using Fake.DependencyInjection;
|
||||
using Oms.Domain.CustomersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
@ -1088,8 +1089,81 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var db = await GetDbContextAsync(cancellationToken);
|
||||
var orderIdsQuery = ApplyListFilter(db.Queryable<Order>(), filter).Select(x => x.Id);
|
||||
var feeQuery = await BuildReceivablePayableFeeQueryAsync(db, filter, cancellationToken);
|
||||
|
||||
var total = await feeQuery.CountAsync(cancellationToken);
|
||||
var fees = await feeQuery
|
||||
.OrderBy(fee => fee.CreateTime, OrderByType.Desc)
|
||||
.OrderBy(fee => fee.Id, OrderByType.Desc)
|
||||
.ToPageListAsync(pageIndex, pageSize, cancellationToken);
|
||||
|
||||
return (await MapReceivablePayableRowsAsync(db, fees, cancellationToken), total);
|
||||
}
|
||||
|
||||
public async Task<List<OrderReceivablePayableSummaryRow>> GetReceivablePayableSummaryAsync(
|
||||
OrderReceivablePayableFilter filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var db = await GetDbContextAsync(cancellationToken);
|
||||
var feeQuery = await BuildReceivablePayableFeeQueryAsync(db, filter, cancellationToken);
|
||||
|
||||
return await feeQuery
|
||||
.GroupBy(fee => new { fee.Type, fee.Currency })
|
||||
.Select(g => new OrderReceivablePayableSummaryRow
|
||||
{
|
||||
FeeType = g.Type,
|
||||
Currency = g.Currency,
|
||||
TotalAmount = SqlFunc.AggregateSum(g.Amount),
|
||||
LineCount = SqlFunc.AggregateCount(g.Id)
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<OrderReceivablePayableRow>> GetReceivablePayableExportListAsync(
|
||||
OrderReceivablePayableFilter filter,
|
||||
int maxRows,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (maxRows <= 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var db = await GetDbContextAsync(cancellationToken);
|
||||
var feeQuery = await BuildReceivablePayableFeeQueryAsync(db, filter, cancellationToken);
|
||||
var fees = await feeQuery
|
||||
.OrderBy(fee => fee.CreateTime, OrderByType.Desc)
|
||||
.OrderBy(fee => fee.Id, OrderByType.Desc)
|
||||
.Take(maxRows)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return await MapReceivablePayableRowsAsync(db, fees, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<ISugarQueryable<OrderFee>> BuildReceivablePayableFeeQueryAsync(
|
||||
ISqlSugarClient db,
|
||||
OrderReceivablePayableFilter filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var orderQuery = ApplyListFilter(db.Queryable<Order>(), filter);
|
||||
var customerCode = filter.CustomerCode?.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(customerCode))
|
||||
{
|
||||
orderQuery = orderQuery.Where(o =>
|
||||
SqlFunc.Subqueryable<Customer>()
|
||||
.Where(c => c.Id == o.CustomerId && c.CustomerCode.Contains(customerCode))
|
||||
.Any());
|
||||
}
|
||||
|
||||
orderQuery = ApplyReceivablePayableTimeFilter(orderQuery, filter);
|
||||
|
||||
if (filter.IsPaid is 0 or 1)
|
||||
{
|
||||
var paidFlag = (byte)filter.IsPaid.Value;
|
||||
orderQuery = orderQuery.Where(o => o.IsPaid == paidFlag);
|
||||
}
|
||||
|
||||
var orderIdsQuery = orderQuery.Select(x => x.Id);
|
||||
var feeQuery = db.Queryable<OrderFee>()
|
||||
.Where(fee => !fee.IsDeleted)
|
||||
.In(fee => fee.OrderId, orderIdsQuery);
|
||||
@ -1104,7 +1178,7 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
var costItem = filter.CostItem?.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(costItem))
|
||||
{
|
||||
feeQuery = feeQuery.Where(fee => fee.CostItem.Contains(costItem));
|
||||
feeQuery = feeQuery.Where(fee => fee.CostItem == costItem || fee.CostItem.Contains(costItem));
|
||||
}
|
||||
|
||||
var chargingStage = filter.ChargingStage?.Trim();
|
||||
@ -1114,15 +1188,85 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
feeQuery = feeQuery.Where(fee => fee.ChargingStage == chargingStage);
|
||||
}
|
||||
|
||||
var total = await feeQuery.CountAsync(cancellationToken);
|
||||
var fees = await feeQuery
|
||||
.OrderBy(fee => fee.CreateTime, OrderByType.Desc)
|
||||
.OrderBy(fee => fee.Id, OrderByType.Desc)
|
||||
.ToPageListAsync(pageIndex, pageSize, cancellationToken);
|
||||
if (!filter.EffectiveStageOnly)
|
||||
{
|
||||
return feeQuery;
|
||||
}
|
||||
|
||||
var candidates = await feeQuery
|
||||
.Select(f => new { f.Id, f.OrderId, f.Type, f.CostItem, f.ChargingStage })
|
||||
.ToListAsync(cancellationToken);
|
||||
if (candidates.Count == 0)
|
||||
{
|
||||
return feeQuery.Where(_ => false);
|
||||
}
|
||||
|
||||
var winningIds = candidates
|
||||
.GroupBy(f => (f.OrderId, f.Type, f.CostItem))
|
||||
.Select(g => g
|
||||
.OrderByDescending(f => GetChargingStageRank(f.ChargingStage))
|
||||
.ThenByDescending(f => f.Id)
|
||||
.First()
|
||||
.Id)
|
||||
.ToArray();
|
||||
|
||||
return feeQuery.Where(f => winningIds.Contains(f.Id));
|
||||
}
|
||||
|
||||
private static int GetChargingStageRank(string? stage) =>
|
||||
stage switch
|
||||
{
|
||||
OrderFeeChargingStageCodes.Outbound => 3,
|
||||
OrderFeeChargingStageCodes.Inbound => 2,
|
||||
OrderFeeChargingStageCodes.Forecast => 1,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
private static ISugarQueryable<Order> ApplyReceivablePayableTimeFilter(
|
||||
ISugarQueryable<Order> query,
|
||||
OrderReceivablePayableFilter filter)
|
||||
{
|
||||
var timeField = filter.TimeField?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(timeField) || string.Equals(timeField, "createTime", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return query;
|
||||
}
|
||||
|
||||
if (filter.TimeStart.HasValue)
|
||||
{
|
||||
var start = filter.TimeStart.Value;
|
||||
query = timeField switch
|
||||
{
|
||||
"receiveTime" => query.Where(o => o.ReceiveTime != null && o.ReceiveTime >= start),
|
||||
"shipTime" => query.Where(o => o.ShipTime != null && o.ShipTime >= start),
|
||||
"payTime" => query.Where(o => o.PayTime != null && o.PayTime >= start),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.TimeEnd.HasValue)
|
||||
{
|
||||
var end = filter.TimeEnd.Value;
|
||||
query = timeField switch
|
||||
{
|
||||
"receiveTime" => query.Where(o => o.ReceiveTime != null && o.ReceiveTime <= end),
|
||||
"shipTime" => query.Where(o => o.ShipTime != null && o.ShipTime <= end),
|
||||
"payTime" => query.Where(o => o.PayTime != null && o.PayTime <= end),
|
||||
_ => query
|
||||
};
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private static async Task<List<OrderReceivablePayableRow>> MapReceivablePayableRowsAsync(
|
||||
ISqlSugarClient db,
|
||||
List<OrderFee> fees,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (fees.Count == 0)
|
||||
{
|
||||
return ([], total);
|
||||
return [];
|
||||
}
|
||||
|
||||
var orderIds = fees.Select(x => x.OrderId).Distinct().ToArray();
|
||||
@ -1131,7 +1275,7 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
.ToListAsync(cancellationToken);
|
||||
var orderMap = orders.ToDictionary(x => x.Id);
|
||||
|
||||
var items = fees
|
||||
return fees
|
||||
.Where(fee => orderMap.ContainsKey(fee.OrderId))
|
||||
.Select(fee => new OrderReceivablePayableRow
|
||||
{
|
||||
@ -1139,8 +1283,6 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
Order = orderMap[fee.OrderId]
|
||||
})
|
||||
.ToList();
|
||||
|
||||
return (items, total);
|
||||
}
|
||||
|
||||
public async Task<List<OrderStatusCountRow>> GetStatusCountsAsync(int? customerId, CancellationToken cancellationToken = default)
|
||||
@ -1212,6 +1354,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,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Fake.DependencyInjection;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.OrdersAggregate.Entities;
|
||||
using Oms.Domain.OrdersAggregate.Repositories;
|
||||
using Oms.SqlSugarCore.Repositories;
|
||||
@ -17,6 +18,63 @@ public class PickupOrderRepository : OmsSqlSugarRepository<PickupOrder>, IPickup
|
||||
{
|
||||
return FirstOrDefaultAsync(x => x.Id == id && x.CustomerId == customerId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<PickupWorkplaceCountRow> GetWorkplaceCountsAsync(
|
||||
int? customerId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var db = await GetDbContextAsync(cancellationToken);
|
||||
var query = db.Queryable<PickupOrder>();
|
||||
if (customerId is > 0)
|
||||
{
|
||||
query = query.Where(x => x.CustomerId == customerId.Value);
|
||||
}
|
||||
|
||||
var terminal = new[] { PickupOrderStatusCodes.Cancelled, PickupOrderStatusCodes.Failed };
|
||||
|
||||
var pendingPayment = await query
|
||||
.Where(x => !terminal.Contains(x.Status))
|
||||
.Where(x => x.PayStatus != PickupPayStatusCodes.Paid)
|
||||
.Where(x => x.ReceivableAmount != null && x.ReceivableAmount > 0)
|
||||
.CountAsync(cancellationToken);
|
||||
|
||||
var pendingSchedule = await query
|
||||
.Where(x => x.Status == PickupOrderStatusCodes.Draft)
|
||||
.CountAsync(cancellationToken);
|
||||
|
||||
var pendingPickup = await query
|
||||
.Where(x => x.Status == PickupOrderStatusCodes.Scheduled || x.Status == PickupOrderStatusCodes.Dispatched)
|
||||
.CountAsync(cancellationToken);
|
||||
|
||||
var pendingInbound = await query
|
||||
.Where(x => x.Status == PickupOrderStatusCodes.Picked)
|
||||
.CountAsync(cancellationToken);
|
||||
|
||||
return new PickupWorkplaceCountRow
|
||||
{
|
||||
PendingPayment = pendingPayment,
|
||||
PendingSchedule = pendingSchedule,
|
||||
PendingPickup = pendingPickup,
|
||||
PendingInbound = pendingInbound,
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<int> CountByCreateTimeRangeAsync(
|
||||
int? customerId,
|
||||
DateTime startInclusive,
|
||||
DateTime endExclusive,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var db = await GetDbContextAsync(cancellationToken);
|
||||
var query = db.Queryable<PickupOrder>()
|
||||
.Where(x => x.CreateTime >= startInclusive && x.CreateTime < endExclusive);
|
||||
if (customerId is > 0)
|
||||
{
|
||||
query = query.Where(x => x.CustomerId == customerId.Value);
|
||||
}
|
||||
|
||||
return await query.CountAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class PickupOrderLineRepository : OmsSqlSugarRepository<PickupOrderLine>, IPickupOrderLineRepository, IScopedDependency
|
||||
|
||||
@ -531,12 +531,32 @@ public partial class AuthService(
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
}
|
||||
|
||||
var permissions = roles
|
||||
.SelectMany(x => x.Permissions.Select(p => p.PermissionCode))
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
var menus = await BuildMenusByPermissionsAsync(permissions, cancellationToken);
|
||||
var isAdminRole = roles.Any(x =>
|
||||
string.Equals(x.Code, "Admin", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
List<string> permissions;
|
||||
List<MenuTreeDto> menus;
|
||||
if (isAdminRole)
|
||||
{
|
||||
// Admin:默认全菜单/全权限码,无需在角色管理里逐项勾选
|
||||
var allMenus = await GetAllActiveMenusCachedAsync(cancellationToken);
|
||||
permissions = allMenus
|
||||
.Select(x => x.PermissionCode)
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Select(x => x!)
|
||||
.ToList();
|
||||
menus = BuildMenuTree(allMenus);
|
||||
}
|
||||
else
|
||||
{
|
||||
permissions = roles
|
||||
.SelectMany(x => x.Permissions.Select(p => p.PermissionCode))
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
menus = await BuildMenusByPermissionsAsync(permissions, cancellationToken);
|
||||
}
|
||||
|
||||
var dto = new UserInfoDto
|
||||
{
|
||||
@ -784,8 +804,12 @@ public partial class AuthService(
|
||||
}
|
||||
}
|
||||
|
||||
var menuDtos = allMenus
|
||||
.Where(x => selectedMenuIds.Contains(x.Id))
|
||||
return BuildMenuTree(allMenus.Where(x => selectedMenuIds.Contains(x.Id)));
|
||||
}
|
||||
|
||||
private static List<MenuTreeDto> BuildMenuTree(IEnumerable<Fake.Rbac.Domain.MenuAggregate.Menu> menus)
|
||||
{
|
||||
var menuDtos = menus
|
||||
.OrderBy(x => x.Order)
|
||||
.ThenBy(x => x.CreateTime)
|
||||
.Select(x => new MenuTreeDto
|
||||
|
||||
@ -1,17 +1,28 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0.303 AS base
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
ARG NUGET_IDESHIP=https://baget.ideship.com/v3/index.json
|
||||
|
||||
COPY ["NuGet.Config", "Directory.Build.props", "./"]
|
||||
RUN sed -i \
|
||||
-e "s|https://baget.ideship.com/v3/index.json|${NUGET_IDESHIP}|g" \
|
||||
-e "s|http://baget.ideship.com/v3/index.json|${NUGET_IDESHIP}|g" \
|
||||
-e "s|https://baget.xiaolipro.cn/v3/index.json|${NUGET_IDESHIP}|g" \
|
||||
NuGet.Config \
|
||||
&& cat NuGet.Config
|
||||
|
||||
COPY ["src/Oms.Domain/Oms.Domain.csproj", "src/Oms.Domain/"]
|
||||
COPY ["src/Oms.SqlSugarCore/Oms.SqlSugarCore.csproj", "src/Oms.SqlSugarCore/"]
|
||||
COPY ["src/Oms.Application/Oms.Application.csproj", "src/Oms.Application/"]
|
||||
COPY ["src/SimpleAdmin.Api/SimpleAdmin.Api.csproj", "src/SimpleAdmin.Api/"]
|
||||
RUN dotnet restore "src/SimpleAdmin.Api/SimpleAdmin.Api.csproj"
|
||||
WORKDIR "/src/src/SimpleAdmin.Api"
|
||||
RUN dotnet build "SimpleAdmin.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
FROM base AS publish
|
||||
COPY . .
|
||||
WORKDIR /src/src/SimpleAdmin.Api
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "SimpleAdmin.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
RUN dotnet publish "SimpleAdmin.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false --no-restore
|
||||
|
||||
# 使用运行时镜像
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
COPY --from=build /app/publish .
|
||||
ENTRYPOINT ["dotnet", "SimpleAdmin.Api.dll"]
|
||||
|
||||
@ -7,8 +7,11 @@
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<EnableSdkContainerSupport>true</EnableSdkContainerSupport>
|
||||
<ContainerBaseImage>mcr.microsoft.com/dotnet/aspnet:8.0</ContainerBaseImage>
|
||||
<ContainerWorkingDirectory>/app</ContainerWorkingDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fake.Autofac" Version="$(FakePackageVersion)" />
|
||||
<PackageReference Include="Fake.Consul" Version="$(FakePackageVersion)" />
|
||||
|
||||
@ -96,6 +96,11 @@ public class WeChatCustomerRegisterInput
|
||||
/// </summary>
|
||||
public string? InviteCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 顾问邀请码(可选)。来自 ?advisor=,值为员工登录账号;注册时写入所属顾问 CustomerService。
|
||||
/// </summary>
|
||||
public string? AdvisorCode { get; set; }
|
||||
|
||||
[Required]
|
||||
public WeChatProfileInput Wechat { get; set; } = new();
|
||||
}
|
||||
|
||||
@ -164,6 +164,11 @@ public class WeChatCustomerService(
|
||||
{
|
||||
entity.ReferralChannel = "wechat";
|
||||
}
|
||||
await StaffAdvisorBinder.BindAdvisorOnCreateAsync(
|
||||
entity,
|
||||
input.AdvisorCode,
|
||||
rbacDbContext,
|
||||
cancellationToken);
|
||||
await customerRepository.InsertAsync(entity);
|
||||
|
||||
try
|
||||
|
||||
@ -64,7 +64,10 @@
|
||||
},
|
||||
"Itfx": {
|
||||
"BaseUrl": "http://api.nanafa.com/itfx",
|
||||
"TimeoutSeconds": 30
|
||||
"TimeoutSeconds": 30,
|
||||
"Customs9610StandardCode": "customs9610Declare",
|
||||
"Customs9610ManifestStandardCode": "customs9610Manifest",
|
||||
"Customs9610DeclarationFormStandardCode": "customs9610DeclarationForm"
|
||||
},
|
||||
"PlatformShop": {
|
||||
"FrontendBaseUrl": "http://localhost:5173",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user