diff --git a/.dockerignore b/.dockerignore index 68da880..864b7c0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,3 +11,9 @@ data coverage playwright-report test-results +deploy/*.tar +上传到服务器 +fixtures +docx +docs +.git diff --git a/.env.example b/.env.example index d70e143..3e81e64 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,12 @@ ENABLE_ISO_CHECK=true # 然后:pnpm exec tsx scripts/apply-demovip-cc.ts # 一期 PRD 默认仍是 test.saas + Saas:TEST(有正式 TEST 账号时改回) CC_MOCK=true +# 测试基础设施(与 CC_MOCK 正交):Mock IMAP + 可故障注入的 MockTargetAPI。生产勿开。 +# TEST_MODE=1 + +# 生产可观测:连续 IMAP 失败 / CC 鉴权过期 POST JSON(空则只写日志) +# ALERT_WEBHOOK_URL=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key= +# ALERT_WEBHOOK_TIMEOUT_MS=5000 IMAP_HOST=imap.qq.com IMAP_PORT=993 @@ -47,6 +53,10 @@ IMAP_READ_TIMEOUT_MS=60000 IMAP_LOOKBACK_DAYS=3 IMAP_MAX_FETCH_PER_TICK=100 IMAP_IDLE_ENABLED=true +# 国内访问 Gmail:填本机代理(Clash 混合端口多为 http://127.0.0.1:7890,SOCKS 为 socks5://127.0.0.1:7891) +# 默认只代理 Gmail/Outlook;QQ/163 仍直连。全部邮箱走代理则 IMAP_PROXY_HOSTS=* +# IMAP_PROXY=socks5://127.0.0.1:7891 +# IMAP_PROXY_HOSTS=gmail,google,outlook,office365,hotmail,live,msn # OCR:off | local | aliyun OCR_PROVIDER=local diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e28dc6e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +*.sh text eol=lf +*.yml text eol=lf +Dockerfile text eol=lf diff --git a/.gitignore b/.gitignore index defb4c2..28c6414 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,13 @@ coverage data/* !data/.gitkeep +# local pack / oversized artifacts (not for clone deploy) +上传到服务器/ +deploy/*.tar +deploy/*.tar.gz +.tmp-ingest* +.tmp-* + # local test records / smoke reports *.log data/logs/ diff --git a/Dockerfile b/Dockerfile index aff844f..f347fe7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,26 @@ FROM node:20-bookworm AS deps WORKDIR /app -RUN corepack enable && corepack prepare pnpm@9.15.0 --activate +RUN npm install -g pnpm@9.15.0 COPY package.json pnpm-lock.yaml* ./ COPY prisma ./prisma RUN pnpm install --frozen-lockfile || pnpm install FROM node:20-bookworm AS builder WORKDIR /app -RUN corepack enable && corepack prepare pnpm@9.15.0 --activate +RUN npm install -g pnpm@9.15.0 COPY --from=deps /app/node_modules ./node_modules COPY . . +# next build 预渲染会读取 env;构建期占位,运行时由 .env 覆盖 +ENV DATABASE_URL=mysql://build:build@127.0.0.1:3306/build +ENV SESSION_SECRET=build-time-placeholder-secret-at-least-32-chars +ENV APP_ADMIN_PASS=build-admin-pass-not-used-at-runtime +ENV APP_OPS_PASS=build-ops-pass-not-used-at-runtime RUN pnpm db:generate && pnpm build FROM node:20-bookworm AS runner WORKDIR /app ENV NODE_ENV=production -RUN corepack enable && corepack prepare pnpm@9.15.0 --activate +RUN npm install -g pnpm@9.15.0 COPY --from=builder /app/package.json ./ COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/.next ./.next @@ -26,6 +31,8 @@ COPY --from=builder /app/src ./src COPY --from=builder /app/scripts ./scripts COPY --from=builder /app/tsconfig.json ./tsconfig.json COPY --from=builder /app/tsconfig.worker.json ./tsconfig.worker.json -RUN mkdir -p /app/data && chmod +x /app/scripts/docker-entrypoint-web.sh -EXPOSE 3000 +RUN mkdir -p /app/data \ + && sed -i 's/\r$//' /app/scripts/*.sh \ + && chmod +x /app/scripts/docker-entrypoint-web.sh +EXPOSE 3100 CMD ["pnpm", "start"] diff --git a/README.md b/README.md index 124df28..2d93a9a 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,32 @@ pnpm worker # 常驻轮询 ## 测试 ```bash -pnpm test # vitest 单元/集成 -pnpm test:e2e # playwright(需先起 dev) +docker compose up -d mysql +pnpm exec prisma db push # 含 trace_id 列时需推一次 +pnpm test:all # 一条命令:vitest + 覆盖率(coverage/) +pnpm test # 仅 vitest(含 integration,需 MySQL) +pnpm test:e2e # playwright(需先起 dev) +pnpm test:e2e:test-mode # TEST_MODE Playwright(端口 3110) +``` + +覆盖率报告:`coverage/coverage-summary.json` 与终端 `text-summary`。 + +TEST_MODE=1 时可用 `/test-workbench` 注入样例、切故障、看 Mock 请求。 + +生产告警:配置 `ALERT_WEBHOOK_URL` 后,IMAP 连续失败 ≥3 或 CC 鉴权/登录失败会 POST JSON(15 分钟去重)。未配置时只写日志。 +### 已知问题与建议 + +| 项 | 现状 | 建议 | +|---|---|---| +| 关键字未命中 | `IGNORED` / `SKIP_FILTER`,不进业务列表 | 属过滤设计;已拉取但分类 `UNKNOWN` 会以 `PARSED` 出现在列表供人工处理 | +| IMAP SEARCH/FETCH | 瞬时超时会重试 3 次;鉴权失败不重试(走连续失败退避) | 保持 | +| `markSeen` | 处理失败且尚未落库时不再标已读,便于 lookback 重试 | 已按 UID 入库的仍标已读(DB 幂等) | +| 部分成功(CC 对象 data) | 仍作 `TIMEOUT_UNKNOWN` 进补偿 | 与「缺 external id」同路径,避免误标成功 | +| 工单/DO/转仓 5xx | 邮件 `FAILED`,未统一进预报补偿表 | 一键可从详情重试;若要对齐预报补偿可再抽公共队列 | +| 既有单测漂移 | `mail-intent` / `extract-header` / `four-business` / `work-order-dedupe` 4 项与当前解析文案/字段不一致 | 与本次可观测性无关;建议单独立项对齐 expected | + +```bash # CC pnpm cc:smoke # 跟随 .env(默认 mock 假成功,非 TEST 验收) pnpm cc:smoke -- --mode=mock # 强制 mock diff --git a/deploy/.env.bt-host-mysql.example b/deploy/.env.bt-host-mysql.example new file mode 100644 index 0000000..f8545f6 --- /dev/null +++ b/deploy/.env.bt-host-mysql.example @@ -0,0 +1,89 @@ +# ============================================================================= +# 宝塔部署模板 — 192.168.2.14 + MySQL(email_forecast) +# 首次部署:cp deploy/.env.bt-host-mysql.example .env 再 bash deploy/install-bt-host-mysql.sh +# 更新代码:勿覆盖服务器已有 .env(上传包不含根目录 .env) +# ============================================================================= + +APP_PORT=30327 +PUBLIC_HOST=192.168.2.14 + +MYSQL_USER=yubao +MYSQL_PASSWORD=CHANGE_ME_STRONG_PASSWORD +MYSQL_HOST=host.docker.internal +MYSQL_PORT=3306 +MYSQL_DATABASE=yubao + +DATABASE_URL=mysql://yubao:CHANGE_ME_STRONG_PASSWORD@host.docker.internal:3306/yubao + +# 生产必须替换;弱口令会导致 NODE_ENV=production 拒绝启动 +APP_ADMIN_USER=admin +APP_ADMIN_PASS=CHANGE_ME_admin_pass +APP_OPS_USER=ops +APP_OPS_PASS=CHANGE_ME_ops_pass + +# 内网 HTTP 访问必须 false;仅在全站 HTTPS 时改为 true +COOKIE_SECURE=false + +# CC(推荐上线后在 Admin「设置 → CarrierCentral」配置,DB 优先于 .env) +CC_MOCK=true +CC_API_BASE=https://test.saas.carriercentral.vip/api +CC_SAAS_HEADER=TEST +CC_LOGIN_MARK=11111111-2222-3333-4444-555555555555 +CC_USERNAME= +CC_PASSWORD_PLAIN= + +# IMAP 可选回退;生产推荐 Admin「设置 → 邮箱绑定」 +IMAP_HOST=imap.qq.com +IMAP_PORT=993 +IMAP_USER= +IMAP_PASS= + +POLL_INTERVAL_MS=1800000 +DATA_RETENTION_DAYS=30 +ENABLE_TYPE_OVERRIDE=true +ENABLE_FORCE_IMPORT=false +ENABLE_ISO_CHECK=true +CC_HTTP_TIMEOUT_MS=60000 +CC_READ_TIMEOUT_MS=8000 +IMAP_CONNECT_TIMEOUT_MS=30000 +IMAP_READ_TIMEOUT_MS=60000 +IMAP_LOOKBACK_DAYS=3 +IMAP_MAX_FETCH_PER_TICK=100 +IMAP_IDLE_ENABLED=true + +# OAuth 回调基址须改为实际访问地址 +OAUTH_PUBLIC_BASE_URL=http://192.168.2.14:30327 +OAUTH_GOOGLE_CLIENT_ID= +OAUTH_GOOGLE_CLIENT_SECRET= +OAUTH_MS_CLIENT_ID= +OAUTH_MS_CLIENT_SECRET= +OAUTH_MS_TENANT=common + +OCR_PROVIDER=local +OCR_MIN_CONFIDENCE=40 + +ENABLE_AUTO_EXEC_TRANSFER=true +ENABLE_AUTO_EXEC_HOLD_SPLIT=true +ENABLE_AUTO_EXEC_LABEL=true +AUTO_EXEC_REQUIRE_CONFIRM=false + +PARSING_STALE_MS=600000 +IMPORTING_TIMEOUT_MS=180000 +COMPENSATION_MAX_RETRY=3 +SHIPMENT_ROW_SOFT_LIMIT=1000 +SHIPMENT_ROW_HARD_LIMIT=5000 +ATTACHMENT_MAX_BYTES=20971520 + +# 首次启动自动 db push + seed 账号 +SEED_ON_START=true + +# 生产勿开 +# TEST_MODE=1 + +# 可选告警 +# ALERT_WEBHOOK_URL= +# ALERT_WEBHOOK_TIMEOUT_MS=5000 + +# 国内服务器访问 Gmail 时可配置代理(Worker 容器内) +# IMAP_PROXY=socks5://host.docker.internal:7891 +# IMAP_PROXY_HOSTS=gmail,google,outlook,office365,hotmail,live,msn diff --git a/deploy/build-image-local.ps1 b/deploy/build-image-local.ps1 new file mode 100644 index 0000000..5636475 --- /dev/null +++ b/deploy/build-image-local.ps1 @@ -0,0 +1,44 @@ +# Build Linux image on dev machine and export tar (Option C) +# Usage: powershell -ExecutionPolicy Bypass -File deploy/build-image-local.ps1 + +$ErrorActionPreference = "Stop" +$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) +$ImageTag = "yubao-app:latest" +$OutTar = Join-Path $Root "deploy\yubao-app-image.tar" +$Platform = "linux/amd64" + +Set-Location $Root + +if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { + throw "docker not found - start Docker Desktop first" +} + +docker info *> $null +if ($LASTEXITCODE -ne 0) { + throw "Docker is not running" +} + +Write-Host "build: platform=$Platform tag=$ImageTag" +Write-Host "build: Next.js compile may take 5-15 minutes..." + +$env:DOCKER_BUILDKIT = '1' +& docker build --platform $Platform -t $ImageTag -f Dockerfile . +if ($LASTEXITCODE -ne 0) { + throw "docker build failed" +} + +if (Test-Path $OutTar) { + Remove-Item -Force $OutTar +} + +Write-Host "build: export -> $OutTar" +& docker save $ImageTag -o $OutTar +if ($LASTEXITCODE -ne 0) { + throw "docker save failed" +} + +$sizeMb = [math]::Round((Get-Item $OutTar).Length / 1MB, 1) +Write-Host "" +Write-Host "build: done size=${sizeMb}MB" +Write-Host "upload deploy/yubao-app-image.tar to /home/project/yubao/deploy/" +Write-Host "then on server: bash deploy/install-prebuilt-image.sh" diff --git a/deploy/database/README.md b/deploy/database/README.md new file mode 100644 index 0000000..dc9a5b8 --- /dev/null +++ b/deploy/database/README.md @@ -0,0 +1,63 @@ +# 数据库导入说明(宝塔面板) + +## 文件 + +| 文件 | 说明 | +|------|------| +| `email_forecast.sql` | **推荐导入**:utf8mb4 / utf8mb4_unicode_ci,含本机完整数据 | +| `email_forecast.sql.gz` | 压缩版(二选一即可) | + +## 建库时必须与 SQL 一致 + +宝塔 → 数据库 → 添加数据库: + +| 项 | 必须选 | +|----|--------| +| 数据库名 | `email_forecast` | +| 字符集 | **utf8mb4** | +| 校对规则 / 排序规则 | **utf8mb4_unicode_ci**(不要选 general_ci / 0900_ai_ci) | + +若建库时选了别的校对规则,导入时会出现「字符集不一致可能导致恢复失败」。 + +### 已建错库时的修正 + +在宝塔 phpMyAdmin 或 MySQL 终端执行: + +```sql +ALTER DATABASE email_forecast + CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci; +``` + +然后再导入本 SQL。 + +## 导入步骤 + +1. 确认库为 **utf8mb4 + utf8mb4_unicode_ci** +2. 创建用户 **`email_forecast`**,授权主机 **`172.%`**(Docker 访问) +3. 进入该库 → **导入** → 上传 **`email_forecast.sql`** +4. 等待成功 + +SQL 文件开头已含: + +```sql +SET NAMES utf8mb4; +SET CHARACTER SET utf8mb4; +``` + +与查价项目导入方式一致。 + +## 导入后连接配置 + +```env +DATABASE_URL=mysql://email_forecast:密码@host.docker.internal:3306/email_forecast +``` + +## 与 Docker 内置 MySQL 二选一 + +| 方式 | 说明 | +|------|------| +| **宝塔 MySQL(生产)** | 导入本 SQL;使用 `deploy/docker-compose.bt-host-mysql.yml` | +| **Docker MySQL(本地)** | 根目录 `docker-compose.yml`,端口 7023 | + +不要同时连两个库。 diff --git a/deploy/database/email_forecast.sql b/deploy/database/email_forecast.sql new file mode 100644 index 0000000..91f4c93 --- /dev/null +++ b/deploy/database/email_forecast.sql @@ -0,0 +1,560 @@ +-- ============================================================================= +-- 邮件预报 email_forecast — 宝塔 / phpMyAdmin 导入用 +-- 字符集:utf8mb4 / utf8mb4_unicode_ci +-- 用法:在宝塔已创建空库 email_forecast(utf8mb4 + utf8mb4_unicode_ci)后导入本文件 +-- ============================================================================= + +SET NAMES utf8mb4; +SET CHARACTER SET utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- MySQL dump 10.13 Distrib 8.0.45, for Linux (x86_64) +-- +-- Host: localhost Database: email_forecast +-- ------------------------------------------------------ +-- Server version 8.0.45 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `app_user` +-- + +DROP TABLE IF EXISTS `app_user`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `app_user` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL, + `password_hash` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL, + `role` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'ops', + `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`id`), + UNIQUE KEY `app_user_username_key` (`username`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `app_user` +-- + +LOCK TABLES `app_user` WRITE; +/*!40000 ALTER TABLE `app_user` DISABLE KEYS */; +INSERT INTO `app_user` VALUES (1,'admin','240be518fabd2724ddb6f04eeb1da5967448d7e831c08c8fa822809f74c720a9','admin','2026-07-22 03:48:07.791'),(2,'ops','362e4f232c0f2fb110ac652338b3f20d2b99db1e7c012b7eb3382f27a3086251','ops','2026-07-22 03:48:07.811'); +/*!40000 ALTER TABLE `app_user` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `cc_settings` +-- + +DROP TABLE IF EXISTS `cc_settings`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `cc_settings` ( + `id` int NOT NULL DEFAULT '1', + `mock` tinyint(1) NOT NULL DEFAULT '1', + `api_base` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL, + `saas_header` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL, + `login_mark` char(36) COLLATE utf8mb4_unicode_ci NOT NULL, + `username` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `password_enc` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `cc_settings` +-- + +LOCK TABLES `cc_settings` WRITE; +/*!40000 ALTER TABLE `cc_settings` DISABLE KEYS */; +INSERT INTO `cc_settings` VALUES (1,0,'https://demovip.saas.carriercentral.vip/api','TEST','11111111-2222-3333-4444-555555555555','fj','LxlzY9tDL60tQUkd:EZv914wC07RD7fokmfb8mQ==:TET7frdse8Zk','2026-08-04 02:11:18.639'); +/*!40000 ALTER TABLE `cc_settings` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `cc_token_cache` +-- + +DROP TABLE IF EXISTS `cc_token_cache`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `cc_token_cache` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `login_mark` char(36) COLLATE utf8mb4_unicode_ci NOT NULL, + `token` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL, + `expire_at` datetime(3) NOT NULL, + `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`id`), + UNIQUE KEY `cc_token_cache_login_mark_key` (`login_mark`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `cc_token_cache` +-- + +LOCK TABLES `cc_token_cache` WRITE; +/*!40000 ALTER TABLE `cc_token_cache` DISABLE KEYS */; +INSERT INTO `cc_token_cache` VALUES (2,'11111111-2222-3333-4444-555555555555','e303ac0c-dd49-4d44-ab59-fda82c8a2f3c','2026-08-19 10:36:08.390','2026-08-19 08:36:09.951'); +/*!40000 ALTER TABLE `cc_token_cache` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `cc_write_capability` +-- + +DROP TABLE IF EXISTS `cc_write_capability`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `cc_write_capability` ( + `id` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL, + `mode` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'mock', + `endpoint` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `cc_write_capability` +-- + +LOCK TABLES `cc_write_capability` WRITE; +/*!40000 ALTER TABLE `cc_write_capability` DISABLE KEYS */; +INSERT INTO `cc_write_capability` VALUES ('batch_transfer','mock','/Container/SaveStockBatchTransfer','2026-08-03 07:15:35.834'),('customer_message','mock','/CustomerMessage/SaveForm','2026-08-03 07:15:35.882'),('do_upload','live','/Container/SaveFieldValue','2026-08-04 02:11:18.692'),('hold_split','mock','/Container/HoldSplitInstruction','2026-07-22 08:49:10.749'),('label','mock','/Container/ApplyLabelInstruction','2026-07-22 08:49:10.758'),('save_container','live','/Container/SaveContainer','2026-08-04 02:11:18.672'),('transfer','mock','/Container/TransferWarehouse','2026-07-22 08:49:10.737'); +/*!40000 ALTER TABLE `cc_write_capability` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `container_active_lock` +-- + +DROP TABLE IF EXISTS `container_active_lock`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `container_active_lock` ( + `container_no` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `mail_id` bigint unsigned NOT NULL, + `import_id` bigint unsigned DEFAULT NULL, + `locked_at` datetime(3) NOT NULL, + PRIMARY KEY (`container_no`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `container_active_lock` +-- + +LOCK TABLES `container_active_lock` WRITE; +/*!40000 ALTER TABLE `container_active_lock` DISABLE KEYS */; +INSERT INTO `container_active_lock` VALUES ('ZCSU6581068',99,42,'2026-08-17 07:26:14.673'); +/*!40000 ALTER TABLE `container_active_lock` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `container_import` +-- + +DROP TABLE IF EXISTS `container_import`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `container_import` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `mail_id` bigint unsigned NOT NULL, + `container_no` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + `external_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `request_body` mediumtext COLLATE utf8mb4_unicode_ci, + `response_body` mediumtext COLLATE utf8mb4_unicode_ci, + `status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL, + `last_error` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `shipments_hash` char(64) COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`id`), + UNIQUE KEY `uk_mail_container_shiphash` (`mail_id`,`container_no`,`shipments_hash`), + KEY `idx_mail` (`mail_id`), + KEY `idx_container` (`container_no`), + CONSTRAINT `container_import_mail_id_fkey` FOREIGN KEY (`mail_id`) REFERENCES `mail_message` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `container_import` +-- + +LOCK TABLES `container_import` WRITE; +/*!40000 ALTER TABLE `container_import` DISABLE KEYS */; +INSERT INTO `container_import` VALUES (42,99,'ZCSU6581068','66a83f5c-72cd-46f8-959c-655fad9191f0','{\"keyValue\":null,\"entity\":{\"F_TransMode\":0,\"F_OperationType\":0,\"F_ContainerNo\":\"ZCSU6581068\",\"F_BLCopyCode\":\"ZIMUSHH32195411\",\"F_ETD\":null,\"F_ETA\":\"2026-07-07\",\"F_LoadPort\":\"\",\"F_Dock\":\"\",\"F_ShippingLineId\":\"\",\"F_Classis\":0,\"F_MemoRemark\":\"\",\"F_Instruction\":\"\"},\"sR_Shipments\":[{\"F_FBACode\":\"ABQ2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":6,\"F_CBM\":0.1341,\"F_Weight\":114.06,\"F_PCS\":null,\"F_FBAID\":\"FBA19G6WBL97\",\"F_PO\":null,\"F_ReferenceId\":\"6Y9R7GZF\",\"F_ShipmentID\":\"BAZUS001796179\",\"F_Address\":\"6251 Pioneer Trail\\nLOS LUNAS NW 87031\\nABQ2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"ABQ2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":32,\"F_CBM\":3.15,\"F_Weight\":632.45,\"F_PCS\":null,\"F_FBAID\":\"FBA19GB3CZVK\",\"F_PO\":null,\"F_ReferenceId\":\"8TFEEDOK\",\"F_ShipmentID\":\"BAZUS001797203\",\"F_Address\":\"6251 Pioneer Trail\\nLOS LUNAS NW 87031\\nABQ2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"DEN8\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":11,\"F_CBM\":0.99,\"F_Weight\":209.6,\"F_PCS\":null,\"F_FBAID\":\"FBA19G28PJ8Q\",\"F_PO\":null,\"F_ReferenceId\":\"55HCL19V\",\"F_ShipmentID\":\"BAZUS001795001\",\"F_Address\":\"21000 E 13th Ave\\nAURORA CO 80018\\nDEN8\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FTW1\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0447,\"F_Weight\":14.89,\"F_PCS\":null,\"F_FBAID\":\"FBA19FQ4LX4D\",\"F_PO\":null,\"F_ReferenceId\":\"54WLBMNF\",\"F_ShipmentID\":\"BAZUS001797183\",\"F_Address\":\"33333 LBJ FWY \\nDallas TX 75241-7203 \\nFTW1\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FTW1\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0483,\"F_Weight\":20.52,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDJB5WW\",\"F_PO\":null,\"F_ReferenceId\":\"1B8U5QDK\",\"F_ShipmentID\":\"BAZUS001797758\",\"F_Address\":\"33333 LBJ FWY \\nDallas TX 75241-7203 \\nFTW1\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FWA4\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":4,\"F_CBM\":0.3477,\"F_Weight\":74.77,\"F_PCS\":null,\"F_FBAID\":\"FBA19G5J0DBG\",\"F_PO\":null,\"F_ReferenceId\":\"42VFA4DJ\",\"F_ShipmentID\":\"BAZUS001795819\",\"F_Address\":\"9798 Smith Road \\nFORT WAYNE IN 46809-9771\\nFWA4\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":\"转VGT2\",\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FWA4\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":3,\"F_CBM\":0.1207,\"F_Weight\":31.78,\"F_PCS\":null,\"F_FBAID\":\"FBA19G3W5YJW\",\"F_PO\":null,\"F_ReferenceId\":\"4NTVL4GD\",\"F_ShipmentID\":\"BAZUS001796327\",\"F_Address\":\"9798 Smith Road \\nFORT WAYNE IN 46809-9771\\nFWA4\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":\"转VGT2\",\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FWA4\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.1122,\"F_Weight\":16.01,\"F_PCS\":null,\"F_FBAID\":\"FBA19G874ZNL\",\"F_PO\":null,\"F_ReferenceId\":\"6FP3FXHD\",\"F_ShipmentID\":\"BAZUS001796576\",\"F_Address\":\"9798 Smith Road \\nFORT WAYNE IN 46809-9771\\nFWA4\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":\"转VGT2\",\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FWA4\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":8,\"F_CBM\":0.85,\"F_Weight\":164.64,\"F_PCS\":null,\"F_FBAID\":\"FBA19G7XSKR8\",\"F_PO\":null,\"F_ReferenceId\":\"6UG338LE\",\"F_ShipmentID\":\"BAZUS001797572\",\"F_Address\":\"9798 Smith Road \\nFORT WAYNE IN 46809-9771\\nFWA4\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":\"转VGT2\",\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FWA4\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0545,\"F_Weight\":10.02,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDYSN1C\",\"F_PO\":null,\"F_ReferenceId\":\"3W71FGOH\",\"F_ShipmentID\":\"BAZUS001797891\",\"F_Address\":\"9798 Smith Road \\nFORT WAYNE IN 46809-9771\\nFWA4\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":\"转VGT2\",\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GEG2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":30,\"F_CBM\":1.4,\"F_Weight\":473.3,\"F_PCS\":null,\"F_FBAID\":\"FBA19FMVN4Q7\",\"F_PO\":null,\"F_ReferenceId\":\"78EDUCOR\",\"F_ShipmentID\":\"BAZUS001793288\",\"F_Address\":\"18007 E Garland Ave\\nSPOKANE WA 99216-6102\\nGEG2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GEU2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":7,\"F_CBM\":0.88,\"F_Weight\":115.43,\"F_PCS\":null,\"F_FBAID\":\"FBA19G8JJCVZ\",\"F_PO\":null,\"F_ReferenceId\":\"3KO8UBSF\",\"F_ShipmentID\":\"BAZUS001796671\",\"F_Address\":\"15301 West Northern Avenue\\nGlendale AZ 85307\\nGEU2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GEU2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":33,\"F_CBM\":2.11,\"F_Weight\":517.76,\"F_PCS\":null,\"F_FBAID\":\"FBA19GB31YQD\",\"F_PO\":null,\"F_ReferenceId\":\"79P9QB8N\",\"F_ShipmentID\":\"BAZUS001796855\",\"F_Address\":\"15301 West Northern Avenue\\nGlendale AZ 85307\\nGEU2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GEU2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":6,\"F_CBM\":0.5159,\"F_Weight\":78.99,\"F_PCS\":null,\"F_FBAID\":\"FBA19GBQ0R84\",\"F_PO\":null,\"F_ReferenceId\":\"2VE64SDD\",\"F_ShipmentID\":\"BAZUS001797286\",\"F_Address\":\"15301 West Northern Avenue\\nGlendale AZ 85307\\nGEU2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GEU2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":3,\"F_CBM\":0.2354,\"F_Weight\":46.58,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDP9LNK\",\"F_PO\":null,\"F_ReferenceId\":\"1OEDNZ2J\",\"F_ShipmentID\":\"BAZUS001798238\",\"F_Address\":\"15301 West Northern Avenue\\nGlendale AZ 85307\\nGEU2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GEU3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":21,\"F_CBM\":2.23,\"F_Weight\":435.91,\"F_PCS\":null,\"F_FBAID\":\"FBA19G5HMMM0\",\"F_PO\":null,\"F_ReferenceId\":\"8N862NVX\",\"F_ShipmentID\":\"BAZUS001795738\",\"F_Address\":\"18900 W McDowell Road\\nBUCKEYE AZ 85396\\nGEU3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GEU3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":34,\"F_CBM\":2.11,\"F_Weight\":534.06,\"F_PCS\":null,\"F_FBAID\":\"FBA19GB086TX\",\"F_PO\":null,\"F_ReferenceId\":\"8OUWZO4J\",\"F_ShipmentID\":\"BAZUS001796863\",\"F_Address\":\"18900 W McDowell Road\\nBUCKEYE AZ 85396\\nGEU3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GEU3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":3,\"F_CBM\":0.2475,\"F_Weight\":54.1,\"F_PCS\":null,\"F_FBAID\":\"FBA19GF0Y7TR\",\"F_PO\":null,\"F_ReferenceId\":\"6JQGDRFL\",\"F_ShipmentID\":\"BAZUS001797907\",\"F_Address\":\"18900 W McDowell Road\\nBUCKEYE AZ 85396\\nGEU3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GYR2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":4,\"F_CBM\":0.36,\"F_Weight\":87.21,\"F_PCS\":null,\"F_FBAID\":\"FBA19G5W4ZH6\",\"F_PO\":null,\"F_ReferenceId\":\"4VEKC38D\",\"F_ShipmentID\":\"BAZUS001795871\",\"F_Address\":\"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GYR2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":3,\"F_CBM\":0.1659,\"F_Weight\":31.7,\"F_PCS\":null,\"F_FBAID\":\"FBA19G3ZN3R8\",\"F_PO\":null,\"F_ReferenceId\":\"2VIKOLFC\",\"F_ShipmentID\":\"BAZUS001796324\",\"F_Address\":\"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GYR2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":8,\"F_CBM\":1.02,\"F_Weight\":164.74,\"F_PCS\":null,\"F_FBAID\":\"FBA19G7WGW02\",\"F_PO\":null,\"F_ReferenceId\":\"5GLIYIJS\",\"F_ShipmentID\":\"BAZUS001797569\",\"F_Address\":\"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GYR2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":4,\"F_CBM\":0.1545,\"F_Weight\":87.22,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDJ9QKC\",\"F_PO\":null,\"F_ReferenceId\":\"6JF411TD\",\"F_ShipmentID\":\"BAZUS001797808\",\"F_Address\":\"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"GYR3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":15,\"F_CBM\":0.6367,\"F_Weight\":209.15,\"F_PCS\":null,\"F_FBAID\":\"FBA19GBFSDYY\",\"F_PO\":null,\"F_ReferenceId\":\"36BB9GCD\",\"F_ShipmentID\":\"BAZUS001798002\",\"F_Address\":\"8181 W ROOSEVELT ST\\nPHOENIX, AZ 85043-2356\\nGYR3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IAH3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0835,\"F_Weight\":13.55,\"F_PCS\":null,\"F_FBAID\":\"FBA19G8CZKPZ\",\"F_PO\":null,\"F_ReferenceId\":\"7LMVT2IN\",\"F_ShipmentID\":\"BAZUS001796597\",\"F_Address\":\"15525 Milner Road\\nHOUSTON, TX 77032\\nIAH3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IAH3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":2,\"F_CBM\":0.2163,\"F_Weight\":40.45,\"F_PCS\":null,\"F_FBAID\":\"FBA19G8D0PBJ\",\"F_PO\":null,\"F_ReferenceId\":\"7KPVQWHV\",\"F_ShipmentID\":\"BAZUS001796606\",\"F_Address\":\"15525 Milner Road\\nHOUSTON, TX 77032\\nIAH3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IAH3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":8,\"F_CBM\":0.86,\"F_Weight\":164.76,\"F_PCS\":null,\"F_FBAID\":\"FBA19G7STNH6\",\"F_PO\":null,\"F_ReferenceId\":\"1MU9CJCB\",\"F_ShipmentID\":\"BAZUS001797565\",\"F_Address\":\"15525 Milner Road\\nHOUSTON, TX 77032\\nIAH3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IAH3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":23,\"F_CBM\":1.14,\"F_Weight\":277.32,\"F_PCS\":null,\"F_FBAID\":\"FBA19GC2N3YQ\",\"F_PO\":null,\"F_ReferenceId\":\"56BGI6MF\",\"F_ShipmentID\":\"BAZUS001797862\",\"F_Address\":\"15525 Milner Road\\nHOUSTON, TX 77032\\nIAH3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IND9\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":8,\"F_CBM\":0.75,\"F_Weight\":150.1,\"F_PCS\":null,\"F_FBAID\":\"FBA19G5C4TKT\",\"F_PO\":null,\"F_ReferenceId\":\"11IWRYTZ\",\"F_ShipmentID\":\"BAZUS001795821\",\"F_Address\":\"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IND9\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":4,\"F_CBM\":0.26,\"F_Weight\":87.16,\"F_PCS\":null,\"F_FBAID\":\"FBA19G5WBF5W\",\"F_PO\":null,\"F_ReferenceId\":\"8XEKO8WL\",\"F_ShipmentID\":\"BAZUS001795872\",\"F_Address\":\"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IND9\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0843,\"F_Weight\":14.57,\"F_PCS\":null,\"F_FBAID\":\"FBA19GB4S42R\",\"F_PO\":null,\"F_ReferenceId\":\"78W76LVL\",\"F_ShipmentID\":\"BAZUS001797085\",\"F_Address\":\"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IND9\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":7,\"F_CBM\":0.68,\"F_Weight\":133.82,\"F_PCS\":null,\"F_FBAID\":\"FBA19G902RC2\",\"F_PO\":null,\"F_ReferenceId\":\"4RHPNDQY\",\"F_ShipmentID\":\"BAZUS001797677\",\"F_Address\":\"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IND9\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.68,\"F_Weight\":133.82,\"F_PCS\":null,\"F_FBAID\":\"FBA19G8XH0NV\",\"F_PO\":null,\"F_ReferenceId\":\"76LLHIYR\",\"F_ShipmentID\":\"BAZUS001797677\",\"F_Address\":\"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IND9\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":2,\"F_CBM\":0.68,\"F_Weight\":133.82,\"F_PCS\":null,\"F_FBAID\":\"FBA19G8R3H2V\",\"F_PO\":null,\"F_ReferenceId\":\"4UOSCCVM\",\"F_ShipmentID\":\"BAZUS001797677\",\"F_Address\":\"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IND9\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":5,\"F_CBM\":0.1892,\"F_Weight\":108.57,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDMQSNB\",\"F_PO\":null,\"F_ReferenceId\":\"7D2FTS5I\",\"F_ShipmentID\":\"BAZUS001797800\",\"F_Address\":\"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IND9\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0531,\"F_Weight\":10.01,\"F_PCS\":null,\"F_FBAID\":\"FBA19GF2GZNL\",\"F_PO\":null,\"F_ReferenceId\":\"2MSSGRBO\",\"F_ShipmentID\":\"BAZUS001797889\",\"F_Address\":\"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"IND9\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0322,\"F_Weight\":5.06,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDZY65T\",\"F_PO\":null,\"F_ReferenceId\":\"11N4OVXL\",\"F_ShipmentID\":\"BAZUS001797956\",\"F_Address\":\"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"LAS1\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":5,\"F_CBM\":0.1907,\"F_Weight\":108.87,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDMCVCX\",\"F_PO\":null,\"F_ReferenceId\":\"58P6VXUQ\",\"F_ShipmentID\":\"BAZUS001797802\",\"F_Address\":\"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"LAS1\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":21,\"F_CBM\":1.33,\"F_Weight\":258.06,\"F_PCS\":null,\"F_FBAID\":\"FBA19GC3ZCNQ\",\"F_PO\":null,\"F_ReferenceId\":\"6Z5VR6QA\",\"F_ShipmentID\":\"BAZUS001797863\",\"F_Address\":\"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"LGB8\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":16,\"F_CBM\":1.25,\"F_Weight\":267.25,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDBBCS0\",\"F_PO\":null,\"F_ReferenceId\":\"1E28K1YY\",\"F_ShipmentID\":\"BAZUS001797726\",\"F_Address\":\"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"MDW2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":13,\"F_CBM\":0.63,\"F_Weight\":188.65,\"F_PCS\":null,\"F_FBAID\":\"FBA19G5VDCT8\",\"F_PO\":null,\"F_ReferenceId\":\"7P4OCRJZ\",\"F_ShipmentID\":\"BAZUS001796794\",\"F_Address\":\"250 EMERALD DR\\nJoliet,IL 60433-3280\\nMDW2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"MDW2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0864,\"F_Weight\":14.45,\"F_PCS\":null,\"F_FBAID\":\"FBA19GB3Y64T\",\"F_PO\":null,\"F_ReferenceId\":\"8E6MVMNC\",\"F_ShipmentID\":\"BAZUS001797102\",\"F_Address\":\"250 EMERALD DR\\nJoliet,IL 60433-3280\\nMDW2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"MDW2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0322,\"F_Weight\":5.11,\"F_PCS\":null,\"F_FBAID\":\"FBA19GF16QNH\",\"F_PO\":null,\"F_ReferenceId\":\"37C6SQBP\",\"F_ShipmentID\":\"BAZUS001797953\",\"F_Address\":\"250 EMERALD DR\\nJoliet,IL 60433-3280\\nMDW2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"MQJ1\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.1112,\"F_Weight\":16.5,\"F_PCS\":null,\"F_FBAID\":\"FBA19G88BHGD\",\"F_PO\":null,\"F_ReferenceId\":\"3I7GJ3ZC\",\"F_ShipmentID\":\"BAZUS001796571\",\"F_Address\":\"4412 W 300 N \\nGREENFIELD, IN 46140-7099\\nMQJ1\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"MQJ1\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0864,\"F_Weight\":14.49,\"F_PCS\":null,\"F_FBAID\":\"FBA19GB46YP7\",\"F_PO\":null,\"F_ReferenceId\":\"3ILUAALL\",\"F_ShipmentID\":\"BAZUS001797101\",\"F_Address\":\"4412 W 300 N \\nGREENFIELD, IN 46140-7099\\nMQJ1\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"ONT8\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0808,\"F_Weight\":12.17,\"F_PCS\":null,\"F_FBAID\":\"FBA19GB1F8L7\",\"F_PO\":null,\"F_ReferenceId\":\"3JZEAIPX\",\"F_ShipmentID\":\"BAZUS001797110\",\"F_Address\":\"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"ONT8\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":6,\"F_CBM\":1.73,\"F_Weight\":247.22,\"F_PCS\":null,\"F_FBAID\":\"FBA19G7SNTHJ\",\"F_PO\":null,\"F_ReferenceId\":\"12MW1SQE\",\"F_ShipmentID\":\"BAZUS001797564\",\"F_Address\":\"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"ONT8\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":6,\"F_CBM\":1.73,\"F_Weight\":247.22,\"F_PCS\":null,\"F_FBAID\":\"FBA19G7SGKLK\",\"F_PO\":null,\"F_ReferenceId\":\"53D3BDRW\",\"F_ShipmentID\":\"BAZUS001797564\",\"F_Address\":\"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"PSC2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":5,\"F_CBM\":0.24,\"F_Weight\":67.93,\"F_PCS\":null,\"F_FBAID\":\"FBA19GBMQB06\",\"F_PO\":null,\"F_ReferenceId\":\"7OWR65GZ\",\"F_ShipmentID\":\"BAZUS001797539\",\"F_Address\":\"1351 S Road 40 E\\nPASCO WA 99301\\nPSC2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":\"转POC2\",\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"PSC2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":2,\"F_CBM\":0.1505,\"F_Weight\":38.32,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDQ1G8N\",\"F_PO\":null,\"F_ReferenceId\":\"7QKZXAOZ\",\"F_ShipmentID\":\"BAZUS001798165\",\"F_Address\":\"1351 S Road 40 E\\nPASCO WA 99301\\nPSC2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":\"转POC2\",\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"PSP3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.1179,\"F_Weight\":13.72,\"F_PCS\":null,\"F_FBAID\":\"FBA19G66FZ9R\",\"F_PO\":null,\"F_ReferenceId\":\"5X2YL64A\",\"F_ShipmentID\":\"BAZUS001795939\",\"F_Address\":\"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"PSP3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0808,\"F_Weight\":12.12,\"F_PCS\":null,\"F_FBAID\":\"FBA19GB3TCQV\",\"F_PO\":null,\"F_ReferenceId\":\"7JJUJLML\",\"F_ShipmentID\":\"BAZUS001797132\",\"F_Address\":\"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"PSP3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":21,\"F_CBM\":1.56,\"F_Weight\":353.73,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDF7LXM\",\"F_PO\":null,\"F_ReferenceId\":\"83YHES8B\",\"F_ShipmentID\":\"BAZUS001797727\",\"F_Address\":\"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"PSP3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0591,\"F_Weight\":15.88,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDK14N3\",\"F_PO\":null,\"F_ReferenceId\":\"2KNJR5RT\",\"F_ShipmentID\":\"BAZUS001797780\",\"F_Address\":\"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"PSP3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0523,\"F_Weight\":9.98,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDZHD3H\",\"F_PO\":null,\"F_ReferenceId\":\"5TJRHLMV\",\"F_ShipmentID\":\"BAZUS001797890\",\"F_Address\":\"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"PSP3\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":1,\"F_CBM\":0.0322,\"F_Weight\":5.08,\"F_PCS\":null,\"F_FBAID\":\"FBA19GF0B2ND\",\"F_PO\":null,\"F_ReferenceId\":\"5WRJ6X1L\",\"F_ShipmentID\":\"BAZUS001797957\",\"F_Address\":\"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"SCK4\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":5,\"F_CBM\":0.1919,\"F_Weight\":109.17,\"F_PCS\":null,\"F_FBAID\":\"FBA19GDJW516\",\"F_PO\":null,\"F_ReferenceId\":\"5LI2NMKR\",\"F_ShipmentID\":\"BAZUS001797810\",\"F_Address\":\"6001 S AUSTIN RD \\nSTOCKTON, CA 95215-8354\\nSCK4\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"SCK4\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":12,\"F_CBM\":0.5074,\"F_Weight\":176.41,\"F_PCS\":null,\"F_FBAID\":\"FBA19GBC9142\",\"F_PO\":null,\"F_ReferenceId\":\"8IJDFEKN\",\"F_ShipmentID\":\"BAZUS001798004\",\"F_Address\":\"6001 S AUSTIN RD \\nSTOCKTON, CA 95215-8354\\nSCK4\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"SCK8\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":20,\"F_CBM\":2.0978,\"F_Weight\":420,\"F_PCS\":null,\"F_FBAID\":\"FBA19G84B2GY\",\"F_PO\":null,\"F_ReferenceId\":\"6P7APYIY\",\"F_ShipmentID\":\"BAZUS001796585\",\"F_Address\":\"4700 Wilbur Ave\\nOAKLEY CA 94561-2677\\nSCK8\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"SLC2\",\"F_Transporter\":\"TRUCK\",\"F_CTNS\":30,\"F_CBM\":1.5,\"F_Weight\":472.4,\"F_PCS\":null,\"F_FBAID\":\"FBA19FMVM3BC\",\"F_PO\":null,\"F_ReferenceId\":\"81SO1PZK\",\"F_ShipmentID\":\"BAZUS001793289\",\"F_Address\":\"7148 W OLD BINGHAM HWY\\nWest Jordan UT 84081\\nSLC2\",\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798185\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797670\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797881\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797666\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":6,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001795761\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797929\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":4,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798193\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797326\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796954\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797843\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":4,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796956\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797330\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":6,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796966\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001795406\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798122\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797847\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":4,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001795987\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797879\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":9,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001795764\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796367\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797671\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":9,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796985\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797883\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797841\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797667\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797835\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797930\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796959\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798123\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797928\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797669\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796971\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796972\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797846\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797665\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":8,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798242\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":6,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001795763\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797322\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797931\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798121\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797324\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796978\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":3,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001791683\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":28,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798023\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798131\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798127\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797668\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798181\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796370\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796658\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":28,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798322\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":10,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001795762\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001795404\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"FBA19G2NWL9C\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":5,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796623\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":5,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796395\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798053\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797932\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":12,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798018\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798190\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":6,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797965\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797882\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797176\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798124\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001795405\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796962\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001795407\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798054\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797327\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796969\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001791687\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797167\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797663\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":4,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797003\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796970\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":3,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798188\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":3,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001791681\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798236\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797884\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798042\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797664\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797987\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":9,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001795765\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797672\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":6,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797798\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":4,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796729\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797541\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797777\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001798102\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":20,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797896\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":8,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797567\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":6,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797797\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":3,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"FBA19GFTMJV8\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797688\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":2,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796953\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":3,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001793333\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":5,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797796\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797756\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":1,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001797544\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"UPS\",\"F_Transporter\":\"UPS\",\"F_CTNS\":13,\"F_CBM\":21.62,\"F_Weight\":5300.44,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001795198\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":2,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"BAZUS001796391\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21795111-1859966-GWS\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21812939-1857404-AY6\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21798980-1860524-2Q0\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21803464-1851016-C4A\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21804617-1860684-64E\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21809991-1860619-IBL\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21810402-1860522-SVI\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21812564-1860651-VHN\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21812939-1857413-ZZU\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21812939-1857418-ZOH\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21812939-1857405-SFX\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21812939-1857408-A0V\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21812939-1857422-ASO\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21812939-1857406-QLF\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21813418-1857444-PKC\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21812939-1857415-LA2\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21812939-1857423-FV1\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21812939-1857420-2UO\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21813418-1857446-613\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21813418-1857451-KZQ\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21813418-1857449-GA6\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21813533-1857199-6FH\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21814201-1862425-LCR\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21814201-1862428-FWZ\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21816798-1861194-SIM\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21817183-1862441-KEX\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21817183-1862447-LSA\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21817594-1857678-DSY\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21817482-1859755-IMP\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21817836-1857534-Z1R\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21818563-1857392-WMF\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21818563-1857393-V46\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21820473-1862340-AEN\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21820473-1862346-DFZ\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21820874-1859963-PZ9\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21820677-1862363-ZWG\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21820677-1862397-B8Q\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21824718-1860925-WNJ\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21824387-1860821-UNI\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21824574-1862328-CFS\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21825020-1862319-RAD\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21824931-1860890-JNS\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21825020-1862321-GKP\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21825762-1862193-2KE\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21825934-1862176-NBC\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21825934-1862181-9XY\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21828078-1860831-KUB\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21828533-1860688-H7S\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21828681-1860796-TVZ\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21828991-1860892-JIU\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21829988-1860896-3VA\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21830306-1861199-6FP\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21830325-1862218-8CI\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21830401-1860830-TBY\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21830416-1860822-S55\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21831623-1860803-364\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21833355-1860762-EIA\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21832203-1862243-IND\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21832679-1862143-VRH\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21833355-1860758-BYH\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21833355-1860763-KIX\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21834079-1862148-BYN\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21834419-1862253-ROE\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21834896-1862224-ARH\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21836517-1861506-LH5\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21837058-1862540-HE5\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21837334-1862236-Y2J\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21837418-1862140-NAY\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21837624-1862107-JAW\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21838711-1862137-4A7\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"21838772-1862417-07W\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134081735-0619045627\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134081735-0619050016\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133375905-0620055447\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133542629-0618041348\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133793705-0620041202\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133909821-0620122845\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134117540-0620123347\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134117540-0620123606\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134314697-0620075314\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133551142-0620101145\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133551142-0620100909\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133912624-0620011557\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134258687-0620090927\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134258687-0620090559\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134258687-0620091149\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134044593-0619051059\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133387561-0617120522\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133743891-0618012911\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133532941-0620064403\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133777731-0619064125\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133868618-0619011347\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"132936951-0620112311\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133400801-0619100537\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134011003-0619065044\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133625456-0620100257\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133946666-0620075802\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133548663-0619112543\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133749460-0619012915\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133966081-0619081751\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133302274-0620013010\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133453227-0618101615\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133887190-0620055027\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133541475-0619015912\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133915428-0620075802\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134211551-0620043526\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133870275-0620074327\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133615729-0620045034\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134029576-0620071503\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133947543-0620094355\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134093720-0620114111\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133365581-0619064731\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134073376-0620082659\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133609950-0618073327\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133613332-0619052048\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133784563-0618062107\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133613332-0619052242\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"132246509-0618101425\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133417628-0618070117\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133572594-0618124747\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133821084-0620122107\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133902032-0619123321\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133910443-0620083037\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134229166-0620114955\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134614991-0620085101\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133678933-0618012324\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133742367-0620075703\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"134085239-0619064759\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133855069-0620064850\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null},{\"F_FBACode\":\"FEDEX\",\"F_Transporter\":\"FEDEX\",\"F_CTNS\":1,\"F_CBM\":12,\"F_Weight\":1976.32,\"F_PCS\":null,\"F_FBAID\":null,\"F_PO\":null,\"F_ReferenceId\":null,\"F_ShipmentID\":\"133959297-0620071222\",\"F_Address\":null,\"F_City\":null,\"F_State\":null,\"F_ZipCode\":null,\"F_Contact\":null,\"F_Mobile\":null,\"F_Email\":null,\"F_Expected_DeliveryDateB\":null,\"F_Expected_DeliveryDateE\":null,\"F_Remark\":null,\"F_SKU\":null,\"F_Description\":null,\"F_EnglishName\":null}]}','{\"code\":200,\"info\":\"OK\",\"data\":\"66a83f5c-72cd-46f8-959c-655fad9191f0\"}','SUCCESS',NULL,'70d9b087cb8fd54d4df69fea85d60385dbc6152efe3d29a86c7bc7cda93f0ab0','2026-08-17 03:53:31.590','2026-08-17 07:26:19.176'); +/*!40000 ALTER TABLE `container_import` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `imap_filter_rule` +-- + +DROP TABLE IF EXISTS `imap_filter_rule`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `imap_filter_rule` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `kind` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL, + `value` varchar(256) COLLATE utf8mb4_unicode_ci NOT NULL, + `enabled` tinyint(1) NOT NULL DEFAULT '1', + `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`id`), + UNIQUE KEY `uk_imap_filter_kind_value` (`kind`,`value`), + KEY `idx_imap_filter_kind` (`kind`,`enabled`) +) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `imap_filter_rule` +-- + +LOCK TABLES `imap_filter_rule` WRITE; +/*!40000 ALTER TABLE `imap_filter_rule` DISABLE KEYS */; +INSERT INTO `imap_filter_rule` VALUES (1,'keyword','派送',1,'2026-07-23 06:31:06.142'),(2,'keyword','预报',1,'2026-07-23 06:31:35.780'),(3,'keyword','货物',1,'2026-07-23 06:32:04.875'),(4,'keyword','转仓',1,'2026-08-03 03:00:45.096'),(5,'keyword','柜号',1,'2026-08-03 03:00:45.096'),(6,'keyword','贴标',1,'2026-08-03 03:00:45.096'),(7,'keyword','工单',1,'2026-08-03 03:00:45.096'),(9,'keyword','提单',1,'2026-08-03 03:00:45.096'),(10,'keyword','卡派',1,'2026-08-03 03:00:45.096'),(11,'keyword','拆柜',1,'2026-08-03 03:00:45.096'),(12,'keyword','自提',1,'2026-08-11 06:39:58.810'); +/*!40000 ALTER TABLE `imap_filter_rule` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `imap_pull_log` +-- + +DROP TABLE IF EXISTS `imap_pull_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `imap_pull_log` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `pulled_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `mailbox_account_id` bigint unsigned DEFAULT NULL, + `mailbox_name` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `mailbox_username` varchar(320) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `from_addr` varchar(320) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `subject` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `mail_type` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `mail_id` bigint unsigned DEFAULT NULL, + `result` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL, + `reason` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `idx_imap_pull_at` (`pulled_at`) +) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `imap_pull_log` +-- + +LOCK TABLES `imap_pull_log` WRITE; +/*!40000 ALTER TABLE `imap_pull_log` DISABLE KEYS */; +INSERT INTO `imap_pull_log` VALUES (19,'2026-08-03 08:22:33.456',1,'测试邮箱','3457189498@qq.com','account-security-noreply@accountprotection.microsoft.com','你的一次性代码',NULL,NULL,'skipped','SKIP_FILTER'),(20,'2026-08-03 09:48:33.480',3,'谷歌邮箱测试','niqijiti542@gmail.com','3457189498@qq.com','ces',NULL,NULL,'skipped','SKIP_FILTER'),(21,'2026-08-04 07:25:41.586',1,'测试邮箱','3457189498@qq.com','3457189498@qq.com','css',NULL,NULL,'skipped','SKIP_FILTER'),(22,'2026-08-06 02:08:05.761',1,'测试邮箱','3457189498@qq.com','noreply@email.openai.com','让 ChatGPT 帮你打造更轻松的生活','UNKNOWN',95,'pulled','KEYWORD'),(23,'2026-08-11 01:37:24.188',3,'谷歌邮箱测试','niqijiti542@gmail.com','googleplaypromo-noreply@google.com','Summer deals on top gift cards 🌟',NULL,NULL,'skipped','SKIP_FILTER'),(24,'2026-08-12 06:25:08.947',1,'测试邮箱','3457189498@qq.com','75440098@qq.com','Fw: Fw:回复:ZJYC COSU6504130784 FCIU7089474 7.10 派拆柜派送','WORK_ORDER',97,'pulled','KEYWORD'),(25,'2026-08-12 06:25:19.454',1,'测试邮箱','3457189498@qq.com','75440098@qq.com','Fw: Fw:贴标操作:派送指令:拍照指令:do请查收:TCNU4412465+ ONEYNB3BW9236300+船名航次:WAN HAI 511-V.E110+ETA:6/22+UPS-6件+亚马逊卡派-1275件+私人地址-30件+拆柜清单','WORK_ORDER',98,'pulled','KEYWORD'),(26,'2026-08-12 06:25:30.145',1,'测试邮箱','3457189498@qq.com','75440098@qq.com','Fw: Fw:回复: 回复: LINK EVER INC + ZIMUSHH32195411+ 柜号:ZCSU6581068+ ETA : 7/7','TRANSFER',99,'pulled','KEYWORD'),(27,'2026-08-12 06:25:40.203',1,'测试邮箱','3457189498@qq.com','75440098@qq.com','Fw: Fw:回复:柜号:TRHU8524770+提单号:EGLV143661466958+封号:EMCEJJ0635+YWZD+不带车架+整柜直送','DO_UPLOAD',100,'pulled','KEYWORD'),(28,'2026-08-13 01:32:15.048',3,'谷歌邮箱测试','niqijiti542@gmail.com','team@mail.cursor.com','Introducing Cursor Grok 4.6',NULL,NULL,'skipped','SKIP_FILTER'),(29,'2026-08-13 06:51:19.791',1,'测试邮箱','3457189498@qq.com','msa@communication.microsoft.com','使用条款更新',NULL,NULL,'skipped','SKIP_FILTER'),(30,'2026-08-19 08:33:39.791',1,'测试邮箱','3457189498@qq.com','noreply@github.com','[GitHub] You have used 90% of the Actions minutes included for the nanchuanbeizi account',NULL,NULL,'skipped','SKIP_FILTER'); +/*!40000 ALTER TABLE `imap_pull_log` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `imap_settings` +-- + +DROP TABLE IF EXISTS `imap_settings`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `imap_settings` ( + `id` int NOT NULL DEFAULT '1', + `poll_interval_ms` int NOT NULL DEFAULT '1800000', + `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `imap_settings` +-- + +LOCK TABLES `imap_settings` WRITE; +/*!40000 ALTER TABLE `imap_settings` DISABLE KEYS */; +INSERT INTO `imap_settings` VALUES (1,1800000,'2026-08-03 07:16:01.925'); +/*!40000 ALTER TABLE `imap_settings` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `import_compensation` +-- + +DROP TABLE IF EXISTS `import_compensation`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `import_compensation` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `import_id` bigint unsigned NOT NULL, + `retry_count` int unsigned NOT NULL DEFAULT '0', + `max_retry` int unsigned NOT NULL DEFAULT '3', + `next_retry_at` datetime(3) DEFAULT NULL, + `reason` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL, + `status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'OPEN', + `retry_token` char(36) COLLATE utf8mb4_unicode_ci NOT NULL, + `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`id`), + UNIQUE KEY `import_compensation_retry_token_key` (`retry_token`), + KEY `idx_open` (`status`,`next_retry_at`), + KEY `import_compensation_import_id_fkey` (`import_id`), + CONSTRAINT `import_compensation_import_id_fkey` FOREIGN KEY (`import_id`) REFERENCES `container_import` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `import_compensation` +-- + +LOCK TABLES `import_compensation` WRITE; +/*!40000 ALTER TABLE `import_compensation` DISABLE KEYS */; +/*!40000 ALTER TABLE `import_compensation` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `mail_attachment` +-- + +DROP TABLE IF EXISTS `mail_attachment`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `mail_attachment` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `mail_id` bigint unsigned NOT NULL, + `filename` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL, + `content_type` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `sha256` char(64) COLLATE utf8mb4_unicode_ci NOT NULL, + `path` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL, + `size` int unsigned NOT NULL, + `rejected` tinyint(1) NOT NULL DEFAULT '0', + `template_id` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`id`), + KEY `idx_mail` (`mail_id`), + CONSTRAINT `mail_attachment_mail_id_fkey` FOREIGN KEY (`mail_id`) REFERENCES `mail_message` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=446 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `mail_attachment` +-- + +LOCK TABLES `mail_attachment` WRITE; +/*!40000 ALTER TABLE `mail_attachment` DISABLE KEYS */; +INSERT INTO `mail_attachment` VALUES (265,98,'WL100244-37-XPB2(1).pdf','application/pdf','e607aecc3e3ce51c24a02985bc65979004b5b1b39543a7d0c7f48522e0798889','data/mails/98/attachments/WL100244-37-XPB2(1).pdf',748506,0,NULL,'2026-08-12 06:25:19.218'),(266,99,'5E30558D@529FEF40.E0117C6A00000000.jpg','image/jpeg','ecb5df2e63ccd3dbb052ea816eba9d6cad90d14f59e399d0bbf88eea32e41579','data/mails/99/attachments/5E30558D@529FEF40.E0117C6A00000000.jpg',24687,0,NULL,'2026-08-12 06:25:29.311'),(267,99,'630D332C@E5624726.E0117C6A00000000.jpg','image/jpeg','d3301093e2058e268edb600fe6cc46dec5a9ab3ef5ac4dabf7c55c3d3975aaf4','data/mails/99/attachments/630D332C@E5624726.E0117C6A00000000.jpg',22830,0,NULL,'2026-08-12 06:25:29.311'),(268,99,'柜号ZCSU6581068 卡派资料.xlsx','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','7a97804ab5c3aa4e4062dd0578fc36b1a70cfcb272c10255ea321d349cfe2370','data/mails/99/attachments/柜号ZCSU6581068 卡派资料.xlsx',81557,0,NULL,'2026-08-12 06:25:29.311'),(269,100,'TRHU8524770-DO-1.pdf','application/pdf','5d067be8c580154cd3fb2811f5dbb78ac1f5fba17e8d8aa7520a5e094c82542c','data/mails/100/attachments/TRHU8524770-DO-1.pdf',7473,0,NULL,'2026-08-12 06:25:39.989'),(435,253,'柜号:MATU2745683卡派资料.xlsx','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','2368b2b639862bed2db593e420f0ecc1448ac8285cc38ebf33962543a20bd306','data/mails/253/柜号:MATU2745683卡派资料.xlsx',35383,0,NULL,'2026-08-17 01:31:29.247'),(436,254,'WL103276-SBD1(1).pdf','application/pdf','042d04c72e4b5dc8102a327a2acdc6cd8250db279a3ac6fa78375a7e89da2950','data/mails/254/WL103276-SBD1(1).pdf',152020,0,NULL,'2026-08-17 01:31:30.329'),(437,254,'WL103944-WL103940.pdf','application/pdf','13bc23a99d698ac7175466eb029dbe654ffdcd2269c1b259900577d70c9a0e91','data/mails/254/WL103944-WL103940.pdf',349311,0,NULL,'2026-08-17 01:31:30.338'),(438,254,'WL98967-VGT2(1).pdf','application/pdf','b73578def7a4104fc2053e10c8c9753d5c02cd1ae1f4344b0a7000ce7ef5264d','data/mails/254/WL98967-VGT2(1).pdf',428726,0,NULL,'2026-08-17 01:31:30.349'),(439,254,'WL99583-LAX9.pdf','application/pdf','1ea0b1aa66dab26c517829fb4380e090f76abceed01950135da0f95466440432','data/mails/254/WL99583-LAX9.pdf',417580,0,NULL,'2026-08-17 01:31:30.360'),(440,255,'YT2604021091=76件换标走HIA1卡派出库操作指令.xlsx','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','3c990547378f36f04c760cda5c0746746eb9f7fc09e318fecb6665ae40394970','data/mails/255/YT2604021091=76件换标走HIA1卡派出库操作指令.xlsx',1033180,0,NULL,'2026-08-17 01:31:31.098'),(441,255,'数据模版-新辰泽-WHSU8127240.xlsx','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','849c7bfa1c93a5f12e07bf686bc476e29fa9a138b4a019a8e2029e424e53d9b6','data/mails/255/数据模版-新辰泽-WHSU8127240.xlsx',8906,0,NULL,'2026-08-17 01:31:31.109'),(442,255,'智鸿2+WHLC027G597465+WHSU8127240+洛杉矶+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派.xlsx','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','f04894b75ecac4f34e0234115beae8224031d6452723bd482e40703045940641','data/mails/255/智鸿2+WHLC027G597465+WHSU8127240+洛杉矶+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派.xlsx',8656,0,NULL,'2026-08-17 01:31:31.120'),(443,255,'FBA19HW52S0L-76CTN-HIA1-覆盖贴1箱贴2张.pdf','application/pdf','b5fd36029a6ca1cbda069d1adcc050c2f4f3b6f092765ae36063076f3d270abc','data/mails/255/FBA19HW52S0L-76CTN-HIA1-覆盖贴1箱贴2张.pdf',183876,0,NULL,'2026-08-17 01:31:31.132'),(444,255,'WHSU8127240-DO.pdf','application/pdf','ce8ce357f4b511303d5e1f04192bc1194dd4417fdc65906b8cf0db07e41441ed','data/mails/255/WHSU8127240-DO.pdf',2218220,0,NULL,'2026-08-17 01:31:31.148'),(445,255,'X003UHDF7B-304PCS-覆盖贴1箱贴1张.pdf','application/pdf','73b289dc597a4acabc696f06e3cbc45c50e48abee81a0c79feb12019f74f5b14','data/mails/255/X003UHDF7B-304PCS-覆盖贴1箱贴1张.pdf',21013,0,NULL,'2026-08-17 01:31:31.158'); +/*!40000 ALTER TABLE `mail_attachment` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `mail_message` +-- + +DROP TABLE IF EXISTS `mail_message`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `mail_message` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `message_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `imap_uid` bigint unsigned DEFAULT NULL, + `folder` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'INBOX', + `subject` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '(无主题)', + `from_addr` varchar(320) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `received_at` datetime(3) DEFAULT NULL, + `body_text` mediumtext COLLATE utf8mb4_unicode_ci, + `mail_type` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'UNKNOWN', + `status` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'FETCHED', + `type_evidence` json DEFAULT NULL, + `snapshot_path` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `raw_hash` char(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `last_error` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `version` int unsigned NOT NULL DEFAULT '1', + `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `mailbox_account_id` bigint unsigned DEFAULT NULL, + `in_reply_to` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `is_thread_root` tinyint(1) NOT NULL DEFAULT '1', + `ocr_text` mediumtext COLLATE utf8mb4_unicode_ci, + `references_header` text COLLATE utf8mb4_unicode_ci, + `thread_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `trace_id` varchar(36) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `mail_message_message_id_key` (`message_id`), + UNIQUE KEY `mail_message_raw_hash_key` (`raw_hash`), + UNIQUE KEY `uk_mailbox_folder_uid` (`mailbox_account_id`,`folder`,`imap_uid`), + KEY `idx_status_received` (`status`,`received_at`), + KEY `idx_mail_type` (`mail_type`), + KEY `idx_mailbox` (`mailbox_account_id`), + KEY `idx_thread` (`thread_id`), + KEY `idx_trace` (`trace_id`), + CONSTRAINT `mail_message_mailbox_account_id_fkey` FOREIGN KEY (`mailbox_account_id`) REFERENCES `mailbox_account` (`id`) ON DELETE SET NULL ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=257 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `mail_message` +-- + +LOCK TABLES `mail_message` WRITE; +/*!40000 ALTER TABLE `mail_message` DISABLE KEYS */; +INSERT INTO `mail_message` VALUES (89,'',1727,'INBOX','你的一次性代码','account-security-noreply@accountprotection.microsoft.com','2026-08-03 08:21:18.000',NULL,'UNKNOWN','IGNORED',NULL,NULL,'e34bb974a3bb65fff619698579f1696e222fbd9b1d976c36462530a38655a51f','SKIP_FILTER',1,'2026-08-03 08:22:33.390','2026-08-03 08:22:33.390',1,NULL,1,NULL,NULL,'LPG53MV8YTU4.2M9FKCU0IICS1@ph1pepf00017364',NULL),(91,'',97,'INBOX','ces','3457189498@qq.com','2026-08-03 09:48:26.000',NULL,'UNKNOWN','IGNORED',NULL,NULL,'26da40a1a3037b7d6915846567f65a9650cce01f263041bcffea245d294fb1f1','SKIP_FILTER',1,'2026-08-03 09:48:33.443','2026-08-03 09:48:33.443',3,NULL,1,NULL,NULL,'tencent_FE8FCFEB7B503ADBCBFD87DE2B1670DE9D05@qq.com',NULL),(94,'',1728,'INBOX','css','3457189498@qq.com','2026-08-04 07:25:27.000',NULL,'UNKNOWN','IGNORED',NULL,NULL,'56b091205455db549fbc90923e9887d8780c4ad74736aa4084003c5037aa3316','SKIP_FILTER',1,'2026-08-04 07:25:41.465','2026-08-04 07:25:41.465',1,NULL,1,NULL,NULL,'tencent_607B290F1E64A7B85BD3F3C44ACF3BF0410A@qq.com',NULL),(95,'',1729,'INBOX','让 ChatGPT 帮你打造更轻松的生活','noreply@email.openai.com','2026-08-05 13:49:11.000','几分钟内即可生成、优化并导出作品\n\n[https://persistent.oaistatic.com/chatgpt/icons/chatgpt_logo.png]\nhttps://r.openai.com/ls/click?upn=u001.I-2FaYIbqhsKngReUOKrmuweHw7Fh75qLKlFhai32cOruLSd4wBMnRBn-2B4DZAp3Y0Fa9V9GPmhMYOqa2OcA15BhKmi1y-2BiHOvfAyno332Uf-2FPz5entLHHM0NIQ7gHcJQgYq5ZOlDiFm9nvwU2873qZmv4KppmqBwk7ZQkNxPGgOf-2BoRBDCKEMoiwwomrWBkTd-2BZdsXUVNQHNQz-2BnRZJg3gYoGlTYUbMTyK4kuhzrlcfMybt5ARf2HUuEA0IHTkKbHZ8pTGbH-2FhPQK-2FtOsOSH42dkziJrbV6R-2F08FM8KeKxxsa9zHRnQOHsst1J6xiUBGwIYvHnxxJX1tv3-2FJKvk4lkG-2BRxkHRyvFA4dKFUkouzajBcIyCGvgZCl8yolZKUMz5ZtE3J9PneJBJbGwRE6lSqug-3D-3DQtVp_VDjbZ53GnxdNmglIpxB1qe8ze8EHfQ3-2F1ETAgNXqSqAyPKA1WbMU3JPX3q-2F3r-2F40ZS0n0CBFaibUcx0pd4DJEwrrHsf-2FMdaFfnbmTxn4HN4KtFQurgDSF5eKsvORIlpQ76j9QD4uTsypx-2B9qXdQ9MtHvb2QID3FlaF7RSkQPqGKF-2BmIHVak7fli7aF5dt2hHUK4W2Q0ZutgZ86B-2Fmq6p-2FT4MBn14EMjq38jw1Juc7cxHS8R-2F6AKI-2B9mD6PDBCYhjfbA9wcvumu4Tdpc-2BM6E4SoIKTvKErmUSeaAFAR5vE4aViTYr-2BXx0xDrk-2FI9yUXDyQYU5-2BkckS0wxncWKWmxK5nh0j8Vs242qKYsLNZqg1TstlcO42GvER7XB7ZtGCO8ZZ7Mo9PDdJuI4AL9548vrEGEv-2B-2FCyYdpOuHGxUOTTetqZy7-2B4DeL0NxfpPwKUBr2MGrX8cowttAQw7yv39wEdYA5DTxUROOZpQibGhsIDqOe3QBr3Astvyn-2BZAAlkKvQvSHj1elDPoAgkg1Qy-2Fdxx-2BkkobvUVFdqr0BhN9WltKzJmCb7JsPqnSwWIupZjrJ7AHCjJZnG-2BlpnBY3IFpxJHSi-2FERrKxL5LnJ6pcLgo8GKkZbpf1w9-2B83sTwM27y9MCk9wnkPOanmxYZ8Xf-2FMdXMkVrJ-2Bk2eUlvE7wuf32ECbZofCAq4W1hcyDAZ9Aym9-2BfOxFp5uMOGeW5z-2FjfD1uhYPy3lberU4igEGdOQNdQ6abnARwNElbHOFJtmuiUDDcPvV7CXfIClkhCnX10J8Odwmseh-2FTPUNHMA8TT9dYdqBx3Xt2O2CBMlLsQmmQkalHBAP7Uk3LHRfyP4937dXpmvsA-3D-3D\n\n\n轻触聊天示例,立即开始对话\n\n\n了解 CHATGPT 能为你代劳的事项。\n\n[http://cdn.mcauto-images-production.sendgrid.net/d4ef4a73d51fe844/73d38952-cd46-4a3f-9dec-66b504fb9ca6/196x196.png]\n\n\n朋友分享了美食照片,请据此生成我可尝试的食谱\n\n[http://cdn.mcauto-images-production.sendgrid.net/d4ef4a73d51fe844/53e215ea-a410-4b4f-b55d-10e00d42bd2e/139x139.png]\n\n[https://r.openai.com/uni/ls/click?upn=u001.I-2FaYIbqhsKngReUOKrmuwejRK4r-2FhohtLcN3t-2FloTfjz7yjLBCCa-2Fnn3Jw2B-2FulNsyog-2B0keUfeKNxqZ1afWybBtpkECFjuZCjZfWh508jIC1EUszTY2x-2F4hyZZbMfuD9xCs2kqL1XbdMajMNDAr8AG5ecHpuwZq5foN1YtC7OcU6z6AVqmy2rOzUH-2BgZPDngoZhgj5E-2B28y8zPM1o0oWXhDZTk4d0EYdX7KCzzOCGEumB4awJ777B0CWZZQRvAAuZpJ9WgFsMAYBFaYVYFerNTLMfdI-2Bbg3puIeXZI-2BxGQU888sSHTfD9QHQVxkvnvALo7WLHnLx3sO9kvXE-2FKK11uNFiZABGGU0icemSoPXFBgyys7Rz-2Bu0N26ismtTCwRRLt-2FBzfdGK4dqvTNgWTbKCM1pdAVU0V36HL-2Br5obaPd3WeiKCaaJN7ls6RoEBeU19LjRClv4eoZhlEj2194UXGH3n-2BmggMh5V1tsQs5ADI6OJkvebsaEX7hzq6yjO307zaEREXb4U5kf94mXW57qfdgo1BNnN-2BsMM8F-2BXHX53gQgueV0oZG21nZQNslSk9pcv6Z8hPQz-2BAXDOimUVgYKtTilf3tsoMXBZXJ9TFN3dGpUwJOhaZZmVJDef0JjcxtctD5TnpjJSv2k9YFJE56Rd-2FQdQM-2BoJUOcsmWhkZ6XwqbDUeuoSAR1x9JaycSWyeRx5s0XYzUycCN-2FPX-2FTyOkaMNhOx9bEwgz0BQ9o09NLmw-2BYcBlJdCulC9eDXkTmQPPgfcBADoAY6UBJfeChzsvBu4-2BMqQv5OeiPeYhjXcfTlNYM3o2pf2UkjFEHd-2Fz2wGTNhddmPkXyN14uvR2JoSAx2DeZakYuaAVM36Bow6kk6cBupw1jd3clz4Jk43lGGlXboi9fAuBfWw1u8BzwTwdhKw-3D-3Dv5V__VDjbZ53GnxdNmglIpxB1qe8ze8EHfQ3-2F1ETAgNXqSqAyPKA1WbMU3JPX3q-2F3r-2F40ZS0n0CBFaibUcx0pd4DJEwrrHsf-2FMdaFfnbmTxn4HN4KtFQurgDSF5eKsvORIlpQ76j9QD4uTsypx-2B9qXdQ9MtHvb2QID3FlaF7RSkQPqGKF-2BmIHVak7fli7aF5dt2hHUK4W2Q0ZutgZ86B-2Fmq6p-2FT4MBn14EMjq38jw1Juc7cxHS8R-2F6AKI-2B9mD6PDBCYhjfbA9wcvumu4Tdpc-2BM6E4SoIKTvKErmUSeaAFAR5vE4aViTYr-2BXx0xDrk-2FI9yUXDyQYU5-2BkckS0wxncWKWmxK5nh0j8Vs242qKYsLNZqg1TstlcO42GvER7XB7ZtGCO8ZZ7Mo9PDdJuI4AL9548vrEGEv-2B-2FCyYdpOuHGxUOTTetqZy7-2B4DeL0NxfpPwKUBr2MGrX8cowttAQw7yv39wEdYA5DTxUROOZpQibGhsIDqOe3QBr3Astvyn-2BZAAlkKvQvSHj1elDPoAgkg1Qy-2Fdxx-2BkkobvUVFdqr0BhN9WltKzJmCb7JsPqnSwWIupZjrJ7AHCjJZnG-2BlpnBY3IFpxJHSi-2FERrKxL5LnJ6pcLgo8GKkZbpf1w9-2B83sTwM27y9MCk9wnkPOanmxYZ8Xf-2FMdXMkVrJ-2Bk2eUlvE7wuf32ECbZraJih6yIVQuZ8JMxMVte5HRcU0DnvGASLbBL-2BNQn7MQNgcQys8BsuoPQFpTuhfLq9WICWFtRlgJmh3OD0aG4QKBmn5dOx9RRU4StFR5Tlh39fXt5X95Yjh8NoOqWn3F3yeDdWoS4-2BAj9Bo9f-2Fm9yNbV4lgrmrcXBhVqjFIlfc3xQ-3D-3D]\n\n[http://cdn.mcauto-images-production.sendgrid.net/d4ef4a73d51fe844/445b888f-8e09-441b-a01b-9f57adab0912/196x196.png]\n\n\n我无法参加会议,请起草礼貌的改期短信\n\n[http://cdn.mcauto-images-production.sendgrid.net/d4ef4a73d51fe844/53e215ea-a410-4b4f-b55d-10e00d42bd2e/139x139.png]\n\n[https://r.openai.com/uni/ls/click?upn=u001.I-2FaYIbqhsKngReUOKrmuwejRK4r-2FhohtLcN3t-2FloTfjZDpDnGsOAK51r-2BJAw-2FM5sGjma-2FKE2-2B9-2Blr5yT9GJna8L4KupV7M2O7NXklePbFYftuIit5x8dvnbUUNESXWJn9q0SC4KrrsQ3CVb6rm-2B1Mb-2FUAUfsPV-2F33w7u88DzpBK8Wg7KxskZOiIxNaQjn3mRb1Tmbqs88bfpHhgrcWX3eHW9a7mT5NpyQN-2FFLbYtbh7NSYwW2bkal1e3OmGRWkazc9i6jKn-2B8K0jbvhbU5Dr9LFnpTQh48qh7RbnrGuFwRao2h7WoUawcOWE4yANsgHqnALaldnrgL8KZLybyOq8Nttit7oWPflActGCzohp0e4e3Zg9W0JDHoIlRQ8w6BtrfYNjJ6-2FK4rccffWnys9KMupi1GTdVkx-2FphsT-2FpS4D-2B7h8gWn4naKMt7goqPUaFot0jsog4yyNBmDhGy1Y3kKn9iWMo1M-2FkbeZs7EWaReQRUUSCmDD5C5Bbsk9ooLvMqpNoEA92N0tunv6csDe2W4U0nr6Ta631MSZVosrvBqZhUxa9-2BBu4VaCOmXPa8wmMOqVTTON5KFn3TBUXqjoa1xWMbVwdLYpl2xOVZfeaDza5ym9dQPEP4dPIn5NfxmA4gUMLUtQBrFi17nmcFsblQV3VSD-2BHPYjc0cIQKxgtSQy5OktKkaB0EqSx2V2JauHjqFdyeSsgj-2F3ll5s9xAU7svQGcMhvE6Iz-2FK7SriT1P8PIyT9BaBh5f9XTFpXfXqExSa7uj2zqb8Z2uRy0sVFIiOMvUqQix5eKbIlbIGB6jg0qxNAv5SEwRljRxi8Zqb8TDUjOhmZVPBIWorPO8XQVcj3UnxLqt-2FQwIuSRytt-2B9t65mSrxrplPYh2-2B-2FskdcU2lYusdrMJCowHfPy4tz0j2AiKc0D9t1QPIV-2BmUHiiIsKRaV66w3Xi9XoCCXK5g6ft4KD-2B9YeU6tYpu7Hci2qXGuoC8h7nyebfo2PATLN2ufvZ-2BrpH0M4toboetd0K5mLlq5v11NtFxXmDnB2slVOV1lJKETkIl-2FwQ-2Fm49RpTILnRnBmsYPEdnOYCZ-2FSla-2FheXo9J-2Fv88OKrw0GaOaSfFfOe9RdcFeykvfpR-2Fm13MO2iMT0Jhx866OOXyz0s8DnYaYUClrqYeyivzaihNtTbPvzEh4S-2BU6-2BHjYrBW5nA1VVHwFRXnI-2B1vy5Lq0f9haEXIAEF7gNjauJDPxVuYyMAqGYeFGohQYqqe5oDoGibFUbwV5BV-2FAuBDCMACdQFBUY9YBe2WUr5pdtPEJLqwNrnTd0WQCg-3D-3DjlFC_VDjbZ53GnxdNmglIpxB1qe8ze8EHfQ3-2F1ETAgNXqSqAyPKA1WbMU3JPX3q-2F3r-2F40ZS0n0CBFaibUcx0pd4DJEwrrHsf-2FMdaFfnbmTxn4HN4KtFQurgDSF5eKsvORIlpQ76j9QD4uTsypx-2B9qXdQ9MtHvb2QID3FlaF7RSkQPqGKF-2BmIHVak7fli7aF5dt2hHUK4W2Q0ZutgZ86B-2Fmq6p-2FT4MBn14EMjq38jw1Juc7cxHS8R-2F6AKI-2B9mD6PDBCYhjfbA9wcvumu4Tdpc-2BM6E4SoIKTvKErmUSeaAFAR5vE4aViTYr-2BXx0xDrk-2FI9yUXDyQYU5-2BkckS0wxncWKWmxK5nh0j8Vs242qKYsLNZqg1TstlcO42GvER7XB7ZtGCO8ZZ7Mo9PDdJuI4AL9548vrEGEv-2B-2FCyYdpOuHGxUOTTetqZy7-2B4DeL0NxfpPwKUBr2MGrX8cowttAQw7yv39wEdYA5DTxUROOZpQibGhsIDqOe3QBr3Astvyn-2BZAAlkKvQvSHj1elDPoAgkg1Qy-2Fdxx-2BkkobvUVFdqr0BhN9WltKzJmCb7JsPqnSwWIupZjrJ7AHCjJZnG-2BlpnBY3IFpxJHSi-2FERrKxL5LnJ6pcLgo8GKkZbpf1w9-2B83sTwM27y9MCk9wnkPOanmxYZ8Xf-2FMdXMkVrJ-2Bk2eUlvE7wuf32ECbZpHW4w6YqL-2B7ADU1kNGcJzlGyccSnytDDdRZDijqDhXIsm0ms0xON5nGHLgPtBVAwodq0XubzveEE99WEb7y7gk-2BeC01klZFr7ruKm2i6kFC3-2BXq1PBDgTfD7osCryQhr1GXjN8oO6-2FaIBlZ5987rlicHwF00zOUWvZb7QUHWnDSw-3D-3D]\n\n[http://cdn.mcauto-images-production.sendgrid.net/d4ef4a73d51fe844/5683252c-1b08-476c-9dc9-98d2b1863e3d/196x196.png]\n\n\n我想学习冲泡一杯完美的咖啡,请通过图文教程提供指导\n\n[http://cdn.mcauto-images-production.sendgrid.net/d4ef4a73d51fe844/53e215ea-a410-4b4f-b55d-10e00d42bd2e/139x139.png]\n\n[https://r.openai.com/uni/ls/click?upn=u001.I-2FaYIbqhsKngReUOKrmuwejRK4r-2FhohtLcN3t-2FloTfgknvKOMP8shA-2FmVWPs3c1JozG0GPl4qAUn0XcJ-2BmUhtHIJOWQCBmYxH3xuOudxwkX5vQJzCrojkJnhgUxzkKNZLaMFyq5y50xBmmCkuhfIsTUqEyX1i1-2BUlDJFxtcQ-2FpmWwu3mtTlqR5h663dsRWSkKAd6NoqJKIhO4Cb2VWPKLW097LNsE1fOvL-2BQtZH66m-2B8yq3iPoxSukFryYuzSZeldIH63OWrp2VpzmiDdwHum3XMzVqR6bvdw-2BxDxiGS-2B6ONV-2BNz9lL04b3Cc5JfjNGjAmZvBtGXhkc1lc3SPMo5FwigcERiTzyN-2BtWjeCoRYqhQ8-2FEgWDpdw-2BA9uGh2bqwyvJTyKGg7-2FcOCQHl8RNfK2gQiE-2BC0Rztp6M6ZHFLYoj66McJ-2F3Nsz-2BtBoOJ54n1sq8N8YX9MzWsPcELyDmt-2BC2LTzsZxSNcpc9OGDZ7V1R9GhRDFH9vtVx6KhtdCDQS04YtPMvHRxNhce2aeXQdGJM9-2Btzdu2BmY6weZS-2F1smFDhpZqDzaR-2FVCioUa-2Ft40bvTHaohZmfTLhVft-2BtAfiGfsxvqVcE4NETwmq-2FoYTARmvfsf6-2Bcr3KlDf4nzin5SwZZ-2FSmNkWJdFwG-2BusrVkX5pQIpmPD-2F90xbZgyuERxGJ6EkeJywOQJQGn-2F1VF-2FOwL7b-2FTRwUD98tXI3-2FXLakzJqk4shAO9v4Av1VYP-2B7jYcLhjedci7qbLHg-2Bcx5X-2BCxV5hEntXlG1cE3P5eE-2BW4XHH5iO7wQyGy1KNpl6OIkct21EzH-2BnriakEECauUvsM53wCXrvRRU1OQRanoa45lpRwnaE0jYagTbmWOk3monh4ZK5weZMUiqEKxaiy2OJl3UTgZUml0MKynZcIU5xU46HjL59-2Fb4PvAy9MPR6llBlCDXWWSgwBWrb95ZEJlUqSoFYAR7XTmZsl29bJYCOKnAKoKkfGLEMKkKV3SU1hoyci0gr7-2FDgcedccHsnUGIEjvpX4mxiE3DZnJg8qcyKPM14t9W8hr5c5kysWF1iLMlR7w9ZKRv4V705hFMFv5cfufYAQLcJU4gyj2mcu6O9YTMolfhawXjyX7IWDMexI7-2B6dfwrU-3D9agE_VDjbZ53GnxdNmglIpxB1qe8ze8EHfQ3-2F1ETAgNXqSqAyPKA1WbMU3JPX3q-2F3r-2F40ZS0n0CBFaibUcx0pd4DJEwrrHsf-2FMdaFfnbmTxn4HN4KtFQurgDSF5eKsvORIlpQ76j9QD4uTsypx-2B9qXdQ9MtHvb2QID3FlaF7RSkQPqGKF-2BmIHVak7fli7aF5dt2hHUK4W2Q0ZutgZ86B-2Fmq6p-2FT4MBn14EMjq38jw1Juc7cxHS8R-2F6AKI-2B9mD6PDBCYhjfbA9wcvumu4Tdpc-2BM6E4SoIKTvKErmUSeaAFAR5vE4aViTYr-2BXx0xDrk-2FI9yUXDyQYU5-2BkckS0wxncWKWmxK5nh0j8Vs242qKYsLNZqg1TstlcO42GvER7XB7ZtGCO8ZZ7Mo9PDdJuI4AL9548vrEGEv-2B-2FCyYdpOuHGxUOTTetqZy7-2B4DeL0NxfpPwKUBr2MGrX8cowttAQw7yv39wEdYA5DTxUROOZpQibGhsIDqOe3QBr3Astvyn-2BZAAlkKvQvSHj1elDPoAgkg1Qy-2Fdxx-2BkkobvUVFdqr0BhN9WltKzJmCb7JsPqnSwWIupZjrJ7AHCjJZnG-2BlpnBY3IFpxJHSi-2FERrKxL5LnJ6pcLgo8GKkZbpf1w9-2B83sTwM27y9MCk9wnkPOanmxYZ8Xf-2FMdXMkVrJ-2Bk2eUlvE7wuf32ECbZoUyg91e2nbsqwISB4mNMNBxruHzXeFpGdcBGjTcZa3ZcNpYHE-2BoWW4k71yrcO5KGw8hzM5A2jeSI1PCi2tUiTha9Mc3qXpfqZaOymDdZIMeJGaJTMVWgLr39narUVfA3o5eM7J2H4t6aarf-2F9kUfaQrWEUhCN3g51VCAhEIUdYGw-3D-3D]\n\n[http://cdn.mcauto-images-production.sendgrid.net/d4ef4a73d51fe844/923d73ef-b57b-41cb-a37c-ce068932e174/196x196.png]\n\n\n我已经攒够钱,准备买一副耳机。帮我挑选合适的款式\n\n[http://cdn.mcauto-images-production.sendgrid.net/d4ef4a73d51fe844/53e215ea-a410-4b4f-b55d-10e00d42bd2e/139x139.png]\n\n[https://r.openai.com/uni/ls/click?upn=u001.I-2FaYIbqhsKngReUOKrmuwejRK4r-2FhohtLcN3t-2FloTfgpZKzxmHPQmdhTdj-2Bd8ZfknNIAyoitFPDWBcoIedgBt0OViDopXfcsEpx7tpTObnNcmgU-2Fzoby3b1EsHaVzhK1Bybw5WT63x1Pgm91yR3VA-2BjDNzwynez0RxUnMrNv5z2yXa1kjBKyFYsYHKBCdIx6fTGIs51lKNthl1P1dkvMkYstBbtYcFMTHt0FFzPEgDY-2F9p8Oc-2BvOTvYIJgbeb-2B556My96-2FaUVk-2FPhUB-2FGVd6yNC-2FLgx1Mltgbx55uQOFlcgopfeU7vbXolj8u16SBjUT2-2F8qUG-2BXTJaaSJn0vbKZkrSIF8APH1Q-2BkQLGYE6BmPO4maNRxt6e27vSBUITuY81-2BRQCRLslVDeynMeUO-2BB4yzAKmQRLKYlCJRcjgoefuCSOCh9a7r3hvM3F0PFT5PqfxFAk95jd0y-2FvDOHZ-2B9OS3Tr4h0xnzFjkCGTtuW5AiJI1gBg3aLiRmS6QmXdvX85sj7pgUECA37WbvdCQeJPnnYzdxyx-2FBEup1v79nh6Qbowm17GtjEmoCY2mLXMrlQWdtDYFcI3BTcgVU1lqcH6cjEVc-2FIIU3THsri-2BOUM4p-2F42jCYcwluv3-2Bo37aBl4xQ20-2FLJ3KUqqwDFeADqLLN8W1aMCRljPKg4pbXfc1FDwM29GV-2FBjJu2s-2FRbXdGSstsqDiVfJUdQ-2F99Doo8wFCuqVGY8x2xbxMJFAci-2FD7ZvUdha2AgNdk-2BInVL-2F9dJodJArWSWZhqTF-2Fj-2BttAH-2Fjzu3ZCKs9wEatuJciAlh-2BwnunXP1PBbEz9j4EAxl1WRK8ekI8tFjhsAYypmkgQyfdI0uYuNku-2FJ3JwnayA-2FZ2J8F6g4MO-2FOdwt2C3-2F-2FvHf13eyMITH60g6sxv4xcsOjXMLxEA-2BT955BpY9h9c3ij6Z2H4Ta4eHG3nzfmhzMZFxYEy4HWAJYXp-2BeLtkkJ5dq61WAC3HBdiVHCwMBs0t98DvdrUp-2B0iCWNb5EbsgyqpCOqLJcLOF8n9-2B82Eod6IFvfNSm87ffKtAd8ncPV7K6spRuswrkfM6aajcuJf7-2FVHIWT2wqjvM7N2L5lBP1c5pa1KN-2F-2BAe-2F9MqNkQvB7YXw3OmR9UfNiWEWKB3zBT0Ujpqk4BUf-2BK7N3kXFEfNHkfEmAIDhSsw3CnLN-2FvPX-2B87Ei7Ra7EOzUIbzR-2BX-2F7B9FX42YF9YXuPQsvgtJ2Wn-2BkctrtjR0FzJaXBgHWnovLq7KxRZGcO7fyWJy36DC3rilM4nC1nfYk-2Bp04P3fI0Gm-2FtfP03tWuSb4k2gsbHh8A4oR-2BLIZrWM-2BFwdAL9waFSDIsVM5wamlqaV3vPRJsNXfrTYPqHVmjkoeGT-2BL4JOl10lX-2BxZ84RbXKl8uJ6cLJe34qLiOihD1nskpAL-2FnNAOqf0E5O-2BnGjR7TT5hWo5iernt7nFLfP1UQ7teoBeCmUlRJhjUn3r2q8MhMmXjqmTUaewf9c1Rag2mQ-3D-3DVpog_VDjbZ53GnxdNmglIpxB1qe8ze8EHfQ3-2F1ETAgNXqSqAyPKA1WbMU3JPX3q-2F3r-2F40ZS0n0CBFaibUcx0pd4DJEwrrHsf-2FMdaFfnbmTxn4HN4KtFQurgDSF5eKsvORIlpQ76j9QD4uTsypx-2B9qXdQ9MtHvb2QID3FlaF7RSkQPqGKF-2BmIHVak7fli7aF5dt2hHUK4W2Q0ZutgZ86B-2Fmq6p-2FT4MBn14EMjq38jw1Juc7cxHS8R-2F6AKI-2B9mD6PDBCYhjfbA9wcvumu4Tdpc-2BM6E4SoIKTvKErmUSeaAFAR5vE4aViTYr-2BXx0xDrk-2FI9yUXDyQYU5-2BkckS0wxncWKWmxK5nh0j8Vs242qKYsLNZqg1TstlcO42GvER7XB7ZtGCO8ZZ7Mo9PDdJuI4AL9548vrEGEv-2B-2FCyYdpOuHGxUOTTetqZy7-2B4DeL0NxfpPwKUBr2MGrX8cowttAQw7yv39wEdYA5DTxUROOZpQibGhsIDqOe3QBr3Astvyn-2BZAAlkKvQvSHj1elDPoAgkg1Qy-2Fdxx-2BkkobvUVFdqr0BhN9WltKzJmCb7JsPqnSwWIupZjrJ7AHCjJZnG-2BlpnBY3IFpxJHSi-2FERrKxL5LnJ6pcLgo8GKkZbpf1w9-2B83sTwM27y9MCk9wnkPOanmxYZ8Xf-2FMdXMkVrJ-2Bk2eUlvE7wuf32ECbZq-2FLFd-2F1ATp6oz-2F4EmZYRBY6ZPcr2Hq7nCJszusTO9F74K2h9GhXM6NjyDH-2FjJl-2Bz6z4z0R3-2FAEJrFc6xABpOLnf0syCZQGJQih1fIZDtq-2Btg0T5qerQWVt59wgt2urcxHMprKz199zbKcvRpuPWg6shk7oEqUhABBaptISPX7W8A-3D-3D]\n\n\n向 CHATGPT 获取更多高效完成任务的方法\n\n开始聊天\n[https://r.openai.com/uni/ls/click?upn=u001.I-2FaYIbqhsKngReUOKrmuwZSO4NkXeh7XGoFSaLpi7cs3Rv7Oue6dj-2BVboH5Cnh6V8f04hQ-2F7FLziIew8EFjvKT9kFZef5n0NbqckevpOfLlNtx-2B90X6hMrJ0ACkAR9ufUvN-2BK82iiUCXayLlUxMn7hiathC-2BWxjlzthENQ6YCEQynZY8SuJ4fiYwfHV-2BXXz0ubrVe91ayilzHrlrWv65AFoMEWVyfpHhOHzYeN4u6djTs-2FXJvD3uPA4K23crGC74lSWANmWQjz0LzgMLLSN-2Fz7vuj4jPU5ldcTyexWXrOd3jRhzgXVmGBWYofQTiwTbVJIzhUwEQMJqE0NjTsw8fmoqrhNioOrtmhCdSCSBrsrYqJ9tGOG5n-2BjC-2Fd1G8ZgIMgNRS8SUgbyxnsOjOl95i9w-3D-3Da91i_VDjbZ53GnxdNmglIpxB1qe8ze8EHfQ3-2F1ETAgNXqSqAyPKA1WbMU3JPX3q-2F3r-2F40ZS0n0CBFaibUcx0pd4DJEwrrHsf-2FMdaFfnbmTxn4HN4KtFQurgDSF5eKsvORIlpQ76j9QD4uTsypx-2B9qXdQ9MtHvb2QID3FlaF7RSkQPqGKF-2BmIHVak7fli7aF5dt2hHUK4W2Q0ZutgZ86B-2Fmq6p-2FT4MBn14EMjq38jw1Juc7cxHS8R-2F6AKI-2B9mD6PDBCYhjfbA9wcvumu4Tdpc-2BM6E4SoIKTvKErmUSeaAFAR5vE4aViTYr-2BXx0xDrk-2FI9yUXDyQYU5-2BkckS0wxncWKWmxK5nh0j8Vs242qKYsLNZqg1TstlcO42GvER7XB7ZtGCO8ZZ7Mo9PDdJuI4AL9548vrEGEv-2B-2FCyYdpOuHGxUOTTetqZy7-2B4DeL0NxfpPwKUBr2MGrX8cowttAQw7yv39wEdYA5DTxUROOZpQibGhsIDqOe3QBr3Astvyn-2BZAAlkKvQvSHj1elDPoAgkg1Qy-2Fdxx-2BkkobvUVFdqr0BhN9WltKzJmCb7JsPqnSwWIupZjrJ7AHCjJZnG-2BlpnBY3IFpxJHSi-2FERrKxL5LnJ6pcLgo8GKkZbpf1w9-2B83sTwM27y9MCk9wnkPOanmxYZ8Xf-2FMdXMkVrJ-2Bk2eUlvE7wuf32ECbZqA-2BzGFbAPRrhhgzkqb49u-2FvN65FWIpbSVkqN-2BbHDFCzgEfxZRLxQtjSNfeK9dRO5QyxKZRQQYUAMmXjFLdynAmgIVyib-2BFdGvnkkWAJdkLQ6jK1t3Xk-2FqFPQ26u4ilUlWtgjIlRr7Ih7adq7INxyeAfp4END7F3K7vNoEroxcXNw-3D-3D]\n\n这封电子邮件有帮助吗?\n\n有帮助\n[https://chatgpt.com/backend-api/notifications/feedback?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X3VzZXJfaWQiOiJ1c2VyLURDNnQxTHpoNHh5UkgwSXNOWVkxUEdaSyIsInNvbGljaXRhdGlvbl9pZCI6IjZhNzJlOGIwNTdmODgxOTE5Mzc1ZTZjMDM5ZmM4OWZjIiwic3VibWlzc2lvbl9pZCI6IjY2MmM3Y2Q1LTlhOWUtNGEwYi05MGM4LTkwNGMzZjAwODQ1NyIsInByb2R1Y2VyX3N5c3RlbSI6ImNhbXBhaWducyIsInByb2R1Y2VyX3JlZmVyZW5jZV9pZCI6Imhvdy10by1hZHZpY2UtYnJvYWQtcHJvbXB0cy10by1jaHVybmVkLXVzZXJzIiwiY29udGVudF9yZWZlcmVuY2VfaWQiOiJwcm9tcHQtY2FyZHMiLCJjaGFubmVsIjoiZW1haWwiLCJzdXJmYWNlIjoiZW1haWwuZm9vdGVyIiwicXVlc3Rpb25faWQiOiJub3RpZmljYXRpb25fdXNlZnVsbmVzc192MCIsIm9wdGlvbl9pZCI6InVzZWZ1bCIsImFsbG93ZWRfb3B0aW9uX2lkcyI6WyJ1c2VmdWwiLCJub3RfdXNlZnVsIl0sImlhdCI6MTc4NTkzNzc1MCwiZXhwIjoxNzg3NzUyMTUwfQ.ZT9uQC2YX_3qevAsuK82kEqVl3zhrCUGpPdXyGw3kdM&entry_id=3dcb7ce8-3344-4184-bc03-40320a1ffe04&deep_link_id=bf66232a-020e-4f66-bf64-00cc0beac670&auth_modal=1&login_hint=3457189498%40qq.com&utm_campaign=EML-HowToAdviceBroadPrompts&utm_content=utm_content&utm_medium=email&utm_source=sendgrid&utm_term=utm_term]\n没帮助\n[https://chatgpt.com/backend-api/notifications/feedback?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50X3VzZXJfaWQiOiJ1c2VyLURDNnQxTHpoNHh5UkgwSXNOWVkxUEdaSyIsInNvbGljaXRhdGlvbl9pZCI6IjZhNzJlOGIwNTdmODgxOTE5Mzc1ZTZjMDM5ZmM4OWZjIiwic3VibWlzc2lvbl9pZCI6IjFjMDg1ZmZiLWUyNTYtNDc3Mi05YmVkLWUyMmY2MGUwOTkwNSIsInByb2R1Y2VyX3N5c3RlbSI6ImNhbXBhaWducyIsInByb2R1Y2VyX3JlZmVyZW5jZV9pZCI6Imhvdy10by1hZHZpY2UtYnJvYWQtcHJvbXB0cy10by1jaHVybmVkLXVzZXJzIiwiY29udGVudF9yZWZlcmVuY2VfaWQiOiJwcm9tcHQtY2FyZHMiLCJjaGFubmVsIjoiZW1haWwiLCJzdXJmYWNlIjoiZW1haWwuZm9vdGVyIiwicXVlc3Rpb25faWQiOiJub3RpZmljYXRpb25fdXNlZnVsbmVzc192MCIsIm9wdGlvbl9pZCI6Im5vdF91c2VmdWwiLCJhbGxvd2VkX29wdGlvbl9pZHMiOlsidXNlZnVsIiwibm90X3VzZWZ1bCJdLCJpYXQiOjE3ODU5Mzc3NTAsImV4cCI6MTc4Nzc1MjE1MH0.XBpsN-duDjyCFMXea_k10CYYqlGGNxNuIBAzN-nfPK8&entry_id=3dcb7ce8-3344-4184-bc03-40320a1ffe04&deep_link_id=0127645f-92be-4b97-93f6-a5b637e07bcb&auth_modal=1&login_hint=3457189498%40qq.com&utm_campaign=EML-HowToAdviceBroadPrompts&utm_content=utm_content&utm_medium=email&utm_source=sendgrid&utm_term=utm_term]\n\nChatGPT 徽标\n[https://cdn.mcauto-images-production.sendgrid.net/d4ef4a73d51fe844/23791e46-5717-4e4f-a33c-0cc5ba7cf5aa/176x45.png]\nhttps://r.openai.com/ls/click?upn=u001.I-2FaYIbqhsKngReUOKrmuweHw7Fh75qLKlFhai32cOruLSd4wBMnRBn-2B4DZAp3Y0Fa9V9GPmhMYOqa2OcA15BhKmi1y-2BiHOvfAyno332Uf-2FOLyYGke6WrWYgD4nMnSRdirtEcBR6XMWrjuRoMMdg-2BorAGq4t-2BBAf6jBF3JMyuM3zSwyf1ydk5doCNG7BEX-2F3ofCaTFbWroRMevwlFvr4fcTESbpKbEmtWIKeo85Y12w4uLCvJ7RjsSM8l2i4Kbw3H7GLmV86-2BmE6DZQcCLmByOuQ9h8DwpiOtMaY4313BLim7HbFR6nUpDINWQYPn4L7t9hbg0XNZUJ3DQ5vdR9pdXhtGwpNbAWZs0iTgIwI6pXA92IJ3aJMttLTGwAlaRwSxfJI84vsIjO3NZzpieZKE-2FA-3D-3DCIr9_VDjbZ53GnxdNmglIpxB1qe8ze8EHfQ3-2F1ETAgNXqSqAyPKA1WbMU3JPX3q-2F3r-2F40ZS0n0CBFaibUcx0pd4DJEwrrHsf-2FMdaFfnbmTxn4HN4KtFQurgDSF5eKsvORIlpQ76j9QD4uTsypx-2B9qXdQ9MtHvb2QID3FlaF7RSkQPqGKF-2BmIHVak7fli7aF5dt2hHUK4W2Q0ZutgZ86B-2Fmq6p-2FT4MBn14EMjq38jw1Juc7cxHS8R-2F6AKI-2B9mD6PDBCYhjfbA9wcvumu4Tdpc-2BM6E4SoIKTvKErmUSeaAFAR5vE4aViTYr-2BXx0xDrk-2FI9yUXDyQYU5-2BkckS0wxncWKWmxK5nh0j8Vs242qKYsLNZqg1TstlcO42GvER7XB7ZtGCO8ZZ7Mo9PDdJuI4AL9548vrEGEv-2B-2FCyYdpOuHGxUOTTetqZy7-2B4DeL0NxfpPwKUBr2MGrX8cowttAQw7yv39wEdYA5DTxUROOZpQibGhsIDqOe3QBr3Astvyn-2BZAAlkKvQvSHj1elDPoAgkg1Qy-2Fdxx-2BkkobvUVFdqr0BhN9WltKzJmCb7JsPqnSwWIupZjrJ7AHCjJZnG-2BlpnBY3IFpxJHSi-2FERrKxL5LnJ6pcLgo8GKkZbpf1w9-2B83sTwM27y9MCk9wnkPOanmxYZ8Xf-2FMdXMkVrJ-2Bk2eUlvE7wuf32ECbZqNiuoSmf-2FW3v5C-2BIAhTBIY7BHw9HFG-2FtOvK62z0s0fDBm-2BHMrJy-2BgESaAR4gJ5Um8whZ0JrqxwR6-2F7us00SKCLi-2FAuy8AOknbd8lthAiPBzcimyitIwYNL2sIS1tnS3GbcpcEDv9jxMjyDFYB-2BZQE77EqzK-2Bj-2BSfdJasYGr0EbVQ-3D-3D\n\nOpenAI\n1455 3rd Street\nSan Francisco, CA 94158\n取消订阅\n[https://r.openai.com/asm/unsubscribe/?user_id=108370056&data=qYEA2XeMTvm7x_y9VjHjADFFZ4lAv_uFSNKSEwXR1otoMDAwdTAwMPHZyAO-xw_IoHRoTEp-YbryMi5y1boUHncGAXkiI8O1lFMQ2mFVKWtj9prQjn-k3zZnSc8du6jAtdC7qANfu-AmSmHV6OAIhwPHtLmmgIs9hEiQhJG9fcNQcQagi5gpebJmnDNKt_mFl1QidRN8T7ip0hHBUzG4AdALcNvhCQN6OZzLnF1zv52zDT2J3AWLwtCfXldWiG9xAfRYEvGP67MDdlQO5k_LRqGXAMxwGVzKN5DwNVPi1-U9uPHVzz7qnxQL1Aid6obqXRf-vBgEfwJJrbl2BoRr-AQzowWXvaWQfq7y_1l5FHIZXGGU_n4H_uNBQNZh82n5UnjX5m0WS7npm3dGIXrTtOgf2a00bG7hb3lWDL-0BbQMaUM0sSsW6aAUQdylBmsl7TCYMxqcQpdAJe40OqTwFaMKcnD-TmtYj4fi5Nm3Typ8iBAbB6hFprr5tCaA-M12x2FyQ2StA7dGZnREiCQM2ur1Eve3Llzo5YyY4sIBlB4CyVDXWZ2fLBz_zSAjcDDcA3MY3yxDacIhIuazfaQ2i-5tPX6kPWtNczxxJGaSVeTZfPWWfxT87924KzkQdNakmr-li1mh6qkjOjjht2fotemDo6ZJrxZC5z4ok0I6oWMFEqu4EbAdi4HtQDAEsh2dZ2xDP0_7qoRROgQfRhfiSJ8fQCjgidaXqenjhxoEqvLzsXwGPC75ZvA1zHg-02aPoSAJZdwqbwWlztfjfGf0BkaqKlElxipfrSLpfuqwsMw9SB5NH96ruGAkCmVnGqJyylEosmX6O766o10un6_BP9lT9il1Bl7x4op-oW79fzxoa_yKiMXWN_FLNu6_SnAQJB3oCfDIYAuyEOUdxJPC0pK4v2WVR1pdpjpDSNxCPtBKAUkD6O8BsDRk_obIQvpYm0fpdMLcS9dk4jC9JqNom3sz2ROwNoZza9Nqf_55DqYW15PNj_INgSTUFJKoXz1IXh6tlfEskRgwLlU5NRM43X8xt28ZvA61JqN_zZ07s8GvYFrAh_TrYA==]\n隐私\n[https://r.openai.com/ls/click?upn=u001.I-2FaYIbqhsKngReUOKrmuwdFPmDaqS0juGoYmsKbfgYfwqExqW26D5AgK66Z85ve44Wnp0Hw3nl7gYerZVcyzc44kc1GDRfnrxnPXo2aTFjsqTw-2F3hiJuwQC-2BIKyQ6TA1rkFadrcFDnF2lvkmhNzE59NrnLdCudDTMqoHwcm5oOfUk90fMgoqnE655IuAN4-2FsGETmRCSmH-2BcEq7T7ZidczOFTVOj9IiWyo4rup6UEssI-3DOuxu_VDjbZ53GnxdNmglIpxB1qe8ze8EHfQ3-2F1ETAgNXqSqAyPKA1WbMU3JPX3q-2F3r-2F40ZS0n0CBFaibUcx0pd4DJEwrrHsf-2FMdaFfnbmTxn4HN4KtFQurgDSF5eKsvORIlpQ76j9QD4uTsypx-2B9qXdQ9MtHvb2QID3FlaF7RSkQPqGKF-2BmIHVak7fli7aF5dt2hHUK4W2Q0ZutgZ86B-2Fmq6p-2FT4MBn14EMjq38jw1Juc7cxHS8R-2F6AKI-2B9mD6PDBCYhjfbA9wcvumu4Tdpc-2BM6E4SoIKTvKErmUSeaAFAR5vE4aViTYr-2BXx0xDrk-2FI9yUXDyQYU5-2BkckS0wxncWKWmxK5nh0j8Vs242qKYsLNZqg1TstlcO42GvER7XB7ZtGCO8ZZ7Mo9PDdJuI4AL9548vrEGEv-2B-2FCyYdpOuHGxUOTTetqZy7-2B4DeL0NxfpPwKUBr2MGrX8cowttAQw7yv39wEdYA5DTxUROOZpQibGhsIDqOe3QBr3Astvyn-2BZAAlkKvQvSHj1elDPoAgkg1Qy-2Fdxx-2BkkobvUVFdqr0BhN9WltKzJmCb7JsPqnSwWIupZjrJ7AHCjJZnG-2BlpnBY3IFpxJHSi-2FERrKxL5LnJ6pcLgo8GKkZbpf1w9-2B83sTwM27y9MCk9wnkPOanmxYZ8Xf-2FMdXMkVrJ-2Bk2eUlvE7wuf32ECbZp6gHJkhD3u4HUZVhY7Ls6qMELTaC0qz4V352pl58NJ0yZpdKBCauBRMLMBiX0c5tsdwYchq2Xnu1I9sOXkxAgBqSStaMDDdMIDQxn-2FgJ3OGnt3fz0r34cbevZ0Rf-2Byg301poRDD6T2-2B4vo8-2FRv4b2KhXc7t-2B-2F6-2BYSqHcZo0Jn9bA-3D-3D]\n· 条款\n[https://r.openai.com/ls/click?upn=u001.I-2FaYIbqhsKngReUOKrmuwYaDqPngOSPu6GkRbe5tnRM9OIAhQskX37vJyi-2FSYhQONmgPRS1w0VrZ05LWC5P6-2FksFYnJmY94WloC6qX45tgdZVwEOx6-2BMhJuvy8SVRfvA4Kx5EEKwlTPrgjcxk4V8Yu2lJZuLCW5W1dvBZ7S0EJ-2BA1OyLUv66dgDgSbFRuO6fk32QgJXSdGdwWDcL50LzAQ-3D-3DHFI-_VDjbZ53GnxdNmglIpxB1qe8ze8EHfQ3-2F1ETAgNXqSqAyPKA1WbMU3JPX3q-2F3r-2F40ZS0n0CBFaibUcx0pd4DJEwrrHsf-2FMdaFfnbmTxn4HN4KtFQurgDSF5eKsvORIlpQ76j9QD4uTsypx-2B9qXdQ9MtHvb2QID3FlaF7RSkQPqGKF-2BmIHVak7fli7aF5dt2hHUK4W2Q0ZutgZ86B-2Fmq6p-2FT4MBn14EMjq38jw1Juc7cxHS8R-2F6AKI-2B9mD6PDBCYhjfbA9wcvumu4Tdpc-2BM6E4SoIKTvKErmUSeaAFAR5vE4aViTYr-2BXx0xDrk-2FI9yUXDyQYU5-2BkckS0wxncWKWmxK5nh0j8Vs242qKYsLNZqg1TstlcO42GvER7XB7ZtGCO8ZZ7Mo9PDdJuI4AL9548vrEGEv-2B-2FCyYdpOuHGxUOTTetqZy7-2B4DeL0NxfpPwKUBr2MGrX8cowttAQw7yv39wEdYA5DTxUROOZpQibGhsIDqOe3QBr3Astvyn-2BZAAlkKvQvSHj1elDPoAgkg1Qy-2Fdxx-2BkkobvUVFdqr0BhN9WltKzJmCb7JsPqnSwWIupZjrJ7AHCjJZnG-2BlpnBY3IFpxJHSi-2FERrKxL5LnJ6pcLgo8GKkZbpf1w9-2B83sTwM27y9MCk9wnkPOanmxYZ8Xf-2FMdXMkVrJ-2Bk2eUlvE7wuf32ECbZptK0OQNLgEFaNw1P68UVkQuRHFFvKiTRRI4xSngmAWwaFJ32FoSlPIppiWfg05osvV-2BOZac4V-2BG5xlPuqvQ5XqR0Pa7BMMpN2bY4Yv-2F5Q8abqk0ox1p8LSgUAhVao8kWKUFtnIkt4kvYAdDQhpp31tOlenlQNCDmRS-2FoTkcZquQA-3D-3D]\nOpenAI © 2015–2026\n\nGet it on Google Play\n[http://cdn.mcauto-images-production.sendgrid.net/d4ef4a73d51fe844/f0bb62f0-5199-4600-a341-38aa871bde57/240x80.png]\nhttps://r.openai.com/ls/click?upn=u001.I-2FaYIbqhsKngReUOKrmuwYBa-2BGFcXGeGbwcZIhpg-2FH1pc6X2duY0-2Fz3LF-2BpeGLeWmkTfxpouE7rs0nUGSMQpqUhry-2FFbVqtt-2F2iMOVVLfE-2BB8Rd-2FEm1jvc0cer3MMzSamrNKRkB2cBC4cNmdghgUm4-2B-2FylOBrUwdVEFvmnjv2V9Qa-2BM6sfHvJzetg4BnXTOGGapsUj7xle9n5gr8xlbCaIs0aEI9f6o5NDdqup6hZJXveACErSpGAtEDY45O8gZ2xCNKYW8rg4wnqKHJ5dcYnUnWVJQIEUsgT8bRdEVqiKSTTIjn9m05vAutMFg6EGEd098dLlGa4plBf-2FARqYQ-2F5IWaQC3VezdNM88YG8JySFA-3DXPEG_VDjbZ53GnxdNmglIpxB1qe8ze8EHfQ3-2F1ETAgNXqSqAyPKA1WbMU3JPX3q-2F3r-2F40ZS0n0CBFaibUcx0pd4DJEwrrHsf-2FMdaFfnbmTxn4HN4KtFQurgDSF5eKsvORIlpQ76j9QD4uTsypx-2B9qXdQ9MtHvb2QID3FlaF7RSkQPqGKF-2BmIHVak7fli7aF5dt2hHUK4W2Q0ZutgZ86B-2Fmq6p-2FT4MBn14EMjq38jw1Juc7cxHS8R-2F6AKI-2B9mD6PDBCYhjfbA9wcvumu4Tdpc-2BM6E4SoIKTvKErmUSeaAFAR5vE4aViTYr-2BXx0xDrk-2FI9yUXDyQYU5-2BkckS0wxncWKWmxK5nh0j8Vs242qKYsLNZqg1TstlcO42GvER7XB7ZtGCO8ZZ7Mo9PDdJuI4AL9548vrEGEv-2B-2FCyYdpOuHGxUOTTetqZy7-2B4DeL0NxfpPwKUBr2MGrX8cowttAQw7yv39wEdYA5DTxUROOZpQibGhsIDqOe3QBr3Astvyn-2BZAAlkKvQvSHj1elDPoAgkg1Qy-2Fdxx-2BkkobvUVFdqr0BhN9WltKzJmCb7JsPqnSwWIupZjrJ7AHCjJZnG-2BlpnBY3IFpxJHSi-2FERrKxL5LnJ6pcLgo8GKkZbpf1w9-2B83sTwM27y9MCk9wnkPOanmxYZ8Xf-2FMdXMkVrJ-2Bk2eUlvE7wuf32ECbZr-2BQI4eWe0dxzeV2NJxxxl1K40UMbotFd8R8Y11dAOy6HB4LJXMP4GCXzvoTCiOUOECJCKCH0xzWngbFBC2bG6HacrZWc0VSZUy7SceVwj77wRCPghrbNjDfVO3TJJ1P86ZBkiOdy0UNI7Q-2FsYJhlNiJ7MGwZ7wiPI-2Fq5peP6eSEg-3D-3D\nDownload on the App Store\n[http://cdn.mcauto-images-production.sendgrid.net/d4ef4a73d51fe844/7556e4e9-fd26-44ce-b5db-098b33274c20/240x80.png]\nhttps://r.openai.com/ls/click?upn=u001.I-2FaYIbqhsKngReUOKrmuwYWte-2BeK-2FyKj-2FOqgK2UtFoGAaO7eBUt8soQ5BGC2EfVtC95MFGma9I8358veyKD4koDR-2B9QvA-2BQ8-2FAIDiyTuN2d3Mq06RbUHeSwitr8-2Bt-2FKt5vlIoSRxDKzWzFMmJkDHSiGL-2BgAlPaDwgpaXsSOYs6dPrHVzGY0JvWBbcE-2B6Vsg2Aby54D-2FG8K8EAnt7HoQrhguzoBYNNx2zXOzqCTO1IM3tXlENJB-2BBMKZvF96YHkBqIVgu_VDjbZ53GnxdNmglIpxB1qe8ze8EHfQ3-2F1ETAgNXqSqAyPKA1WbMU3JPX3q-2F3r-2F40ZS0n0CBFaibUcx0pd4DJEwrrHsf-2FMdaFfnbmTxn4HN4KtFQurgDSF5eKsvORIlpQ76j9QD4uTsypx-2B9qXdQ9MtHvb2QID3FlaF7RSkQPqGKF-2BmIHVak7fli7aF5dt2hHUK4W2Q0ZutgZ86B-2Fmq6p-2FT4MBn14EMjq38jw1Juc7cxHS8R-2F6AKI-2B9mD6PDBCYhjfbA9wcvumu4Tdpc-2BM6E4SoIKTvKErmUSeaAFAR5vE4aViTYr-2BXx0xDrk-2FI9yUXDyQYU5-2BkckS0wxncWKWmxK5nh0j8Vs242qKYsLNZqg1TstlcO42GvER7XB7ZtGCO8ZZ7Mo9PDdJuI4AL9548vrEGEv-2B-2FCyYdpOuHGxUOTTetqZy7-2B4DeL0NxfpPwKUBr2MGrX8cowttAQw7yv39wEdYA5DTxUROOZpQibGhsIDqOe3QBr3Astvyn-2BZAAlkKvQvSHj1elDPoAgkg1Qy-2Fdxx-2BkkobvUVFdqr0BhN9WltKzJmCb7JsPqnSwWIupZjrJ7AHCjJZnG-2BlpnBY3IFpxJHSi-2FERrKxL5LnJ6pcLgo8GKkZbpf1w9-2B83sTwM27y9MCk9wnkPOanmxYZ8Xf-2FMdXMkVrJ-2Bk2eUlvE7wuf32ECbZp8LiiswtLwQal-2FBOEB49AincSZEoZoVzJV-2FJ4muwIs-2FjYq0gY7llGe-2B3MQjadivLeDruXLxsVbjcLOB1Q0T37NiLgprLJxOEd-2FOvNugpDtC-2BsyF-2B4ut73jP5lRC5IAcvDNBmuQAaxVTf7LxjytqDtw5CMBX5fCPrwtW1NmFnd2hw-3D-3D\n\n[https://r.openai.com/wf/open?upn=u001.nNYIxc2W-2Bct2wyDsGAx65Kau3Dw5Gyl6tfX69DcscDpy-2B0SAAsl-2BgfYwjg-2BDzvkPfQUj0vHEsAaOIWHWAq1gNZu1BhWtB4HB7vGTC-2Fe9Q86qUy94UfitHlXspDM-2Fq85l5lTMVVDQggy-2BNum4XTN1FbaLiU0o-2BKIfVxXiK1HMWxc0rw60qGojJUtrjsGxWVzxYZUHKyaNLNEFsXXMKdxM7iyMDzQyA4u0ODVYq3M1-2FPn0iQ-2BNF0-2BWv5FTDYyCrSEmrgigu5iourW4W7daNZWxJ-2FqNCkKF9i7m-2F0fpbw0Uz3u55R90BUqCkRT-2B5nEN8KcJjnphbnixLCzrxFS-2FwtLd64ZgjnNGSXvS1qbVpigiZNpk-2FTDp02Wxb7918t3Thpw6BvaeL1eAmFCG20rRiJmXKBKy985cXI4KzaqFL3FyGqYGz2pr4KcXCQqNqS5vNYY64nMBKreaV0-2B66foqJC7qzwVC8-2FWkzv0rA5oeGAA2n8ubY8nhSwuFhQmvxiPxH8a1AhhM57-2BtfgjLJOYhh4vKEdpKI6PEj1jbMHKSHdWf6X-2B2cEhjIuYZZt-2FGXA0byCrXkEa-2BvQgkf44zjhCm8EZ9TY87ZH5IjJAY0Qs1TKeX1DAPLheVv7U9QeaNj608ggldCqvQFc8nBjDFhLRLHqJpMgkr2LZdGW9i96dsop8QtUGriwESz-2FTyMfDzZ0OtuLgqqOw8QUBuCDlvnza-2BnuuKK0i2GA3OnUZyTI6uTAvhbUjMBZ83144U8biba3KPG-2FvbFNtVzOKjsqG-2F5epzlBzVc0VoNN7RxTsAI73MH3YPW0iQm580sLjlnoF2Fg8WbJVgg-2BldQOchTn6hbP4DZMC-2BEA-3D-3D]','UNKNOWN','IGNORED','{\"total\": 0, \"intent\": {\"facts\": [{\"label\": \"状态\", \"value\": \"未能确认业务动作\"}], \"action\": \"未知\", \"keywords\": [\"ETA/柜型\"], \"narrative\": \"判定为【未知】。类型未明:可由 Admin 改类型后重新解析\", \"next_step\": \"类型未明:可由 Admin 改类型后重新解析\", \"operation\": \"状态 未能确认业务动作\", \"action_code\": \"UNKNOWN\", \"content_hint\": \"几分钟内即可生成、优化并导出作品\", \"transfer_pairs\": [], \"attachment_role\": \"无附件\"}, \"scores\": {\"UNKNOWN\": 0, \"TRANSFER\": 0, \"DO_UPLOAD\": 0, \"WORK_ORDER\": 0, \"NEW_CONTAINER\": 0, \"INSTRUCTION_LABEL\": 0, \"INSTRUCTION_HOLD_SPLIT\": 0}, \"signals\": [{\"score\": 5, \"signal\": \"船期/柜型\", \"source\": \"body\", \"matched\": \"ETA/柜型\"}], \"mail_type\": \"UNKNOWN\"}','data/mails/95/raw.eml','b28da5657ab61921b6d853d3fa0917d2f004cb701d55c2d7da086f5f07e774fe',NULL,4,'2026-08-06 02:08:02.513','2026-08-06 03:09:59.999',1,NULL,1,NULL,NULL,'Ue2Sxl8xTV-qlcCrqOZJ-A@geopod-ismtpd-14',NULL),(96,'<3d390673882db38c1e47e415f5cd0c15bddf43eb-20405408-111882934@google.com>',98,'INBOX','Summer deals on top gift cards 🌟','googleplaypromo-noreply@google.com','2026-08-10 07:52:11.000',NULL,'UNKNOWN','IGNORED',NULL,NULL,'3c43c36d2ab77468a7b85fcbc5874014d0fc6c467469f3fde091a31abe7e3c1d','SKIP_FILTER',1,'2026-08-11 01:37:24.170','2026-08-11 01:37:24.170',3,NULL,1,NULL,NULL,'3d390673882db38c1e47e415f5cd0c15bddf43eb-20405408-111882934@google.com',NULL),(97,'',1732,'INBOX','Fw: Fw:回复:ZJYC COSU6504130784 FCIU7089474 7.10 派拆柜派送','75440098@qq.com','2026-08-12 06:24:51.000','发自我的iPhone\n\n\n------------------ 原始邮件 ------------------\n寄件人: luo ',1733,'INBOX','Fw: Fw:贴标操作:派送指令:拍照指令:do请查收:TCNU4412465+ ONEYNB3BW9236300+船名航次:WAN HAI 511-V.E110+ETA:6/22+UPS-6件+亚马逊卡派-1275件+私人地址-30件+拆柜清单','75440098@qq.com','2026-08-12 06:25:24.000','发自我的iPhone\n\n\n------------------ 原始邮件 ------------------\n寄件人: luo ',1734,'INBOX','Fw: Fw:回复: 回复: LINK EVER INC + ZIMUSHH32195411+ 柜号:ZCSU6581068+ ETA : 7/7','75440098@qq.com','2026-08-12 06:25:36.000','发自我的iPhone\n\n\n------------------ 原始邮件 ------------------\n寄件人: luo ',1735,'INBOX','Fw: Fw:回复:柜号:TRHU8524770+提单号:EGLV143661466958+封号:EMCEJJ0635+YWZD+不带车架+整柜直送','75440098@qq.com','2026-08-12 06:25:47.000','发自我的iPhone\n\n\n------------------ 原始邮件 ------------------\n寄件人: luo ',99,'INBOX','Introducing Cursor Grok 4.6','team@mail.cursor.com','2026-08-12 19:53:30.000',NULL,'UNKNOWN','IGNORED',NULL,NULL,'4ec89bab5d4d4725a0a0865852a28dfe0f315f450a44019c6db119989b467160','SKIP_FILTER',1,'2026-08-13 01:32:15.017','2026-08-13 01:32:15.017',3,NULL,1,NULL,NULL,'13014e6f-d5a1-41e9-82b0-0d1f907dc950@mtasv.net',NULL),(102,'<202608130651.7349b43074a0434a8a24e229f62dc7bc-OBZG6ZD4JFBVALSJKJEVGQ2PJVGVKTSJINAVISKPJZKE6VKFJVAUSTD4KNWXI4A=@microsoft.com>',1736,'INBOX','使用条款更新','msa@communication.microsoft.com','2026-08-13 06:51:28.000',NULL,'UNKNOWN','IGNORED',NULL,NULL,'9bed57490cb3bbd002997c0738588c508ec809eac9da4d28b94e1a6d0ad1fdb2','SKIP_FILTER',1,'2026-08-13 06:51:19.749','2026-08-13 06:51:19.749',1,NULL,1,NULL,NULL,'202608130651.7349b43074a0434a8a24e229f62dc7bc-OBZG6ZD4JFBVALSJKJEVGQ2PJVGVKTSJINAVISKPJZKE6VKFJVAUSTD4KNWXI4A=@microsoft.com',NULL),(252,'',NULL,'INBOX','Fw: 转发:TIIU8073522-90022','cs3@xinfenginc.com','2026-08-17 01:31:24.609','Fw: 转发:TIIU8073522-90022\n发件人75440098<75440098@qq.com>\n收件人3457189498<3457189498@qq.com>\n大小 12K\n时间 2026年7月14日 16:02\n发自我的iPhone\n------------------ 原始邮件 ------------------\n寄件人: date \n发送时间: 2026年7月10日 16:47\n收件人: 75440098@qq.com <75440098@qq.com>\n主旨: Fw: 转发:TIIU8073522-90022\ndate\ndate@usasinogroup.com\n---- 转发的邮件 ----\n发件人cs3\n日期 2026年07月09日 15:32\n收件人fancy、receiving、hui\n、luo、date、op10、cs1\n主题 TIIU8073522-90022\nBAZUS001787430-90022 4PX 预约码: ==CNF26070717 预约码\ncs3@xinfenginc.com','WORK_ORDER','PENDING_CONFIRM','{\"total\": 40, \"intent\": {\"facts\": [], \"action\": \"工单\", \"keywords\": [\"TIIU8073522\"], \"narrative\": \"判定为【工单】。核对工单字段后确认提交至 CC 客户留言\", \"next_step\": \"核对工单字段后确认提交至 CC 客户留言\", \"operation\": \"—\", \"action_code\": \"WORK_ORDER\", \"content_hint\": \"Fw: 转发:TIIU8073522-90022\", \"transfer_pairs\": [], \"attachment_role\": \"无附件\"}, \"scores\": {\"UNKNOWN\": 0, \"TRANSFER\": 0, \"DO_UPLOAD\": 0, \"WORK_ORDER\": 0, \"NEW_CONTAINER\": 0, \"INSTRUCTION_LABEL\": 0, \"INSTRUCTION_HOLD_SPLIT\": 0}, \"signals\": [{\"score\": 5, \"signal\": \"ISO柜号\", \"source\": \"subject\", \"matched\": \"TIIU8073522\"}, {\"score\": 40, \"signal\": \"无关键词兜底\", \"source\": \"body\", \"matched\": \"TIIU8073522\"}], \"mail_type\": \"WORK_ORDER\", \"sample_dir\": \"docx/邮件/邮件1\"}',NULL,'6d6aff4674643c96aff578aec858856d09037a5a08634b21e91437b963a57f6b',NULL,3,'2026-08-17 01:31:27.049','2026-08-17 01:31:28.550',NULL,NULL,1,NULL,NULL,NULL,'7a6f6103-59ce-4108-b057-c1a2f9e77378'),(253,'',NULL,'INBOX','Fw: 转发:LINK EVER INC + MATS4583030000+ 柜号:MATU2745683+ ETA : 7/13','op7@xinfenginc.com','2026-08-17 01:31:24.610','Fw: 转发:LINK EVER INC + MATS4583030000+ 柜号:\nMATU2745683+ ETA : 7/13\n发件人75440098<75440098@qq.com>\n收件人3457189498<3457189498@qq.com>\n大小 99K\n时间 2026年7月14日 16:02\n发自我的iPhone\n------------------ 原始邮件 ------------------\n寄件人: date \n发送时间: 2026年7月10日 16:46\n收件人: 75440098@qq.com <75440098@qq.com>\n主旨: Fw: 转发:LINK EVER INC + MATS4583030000+ 柜号:MATU2745683+ ETA : 7/13\ndate\ndate@usasinogroup.com\n---- 转发的邮件 ----\n发件人op7\n日期 2026年07月10日 16:44\n收件人fancy、receiving、hui\n、luo、date、cs3、cs1、op1\n0\n主题 回复: LINK EVER INC + MATS4583030000+ 柜号:MATU2745683+ ETA : 7/13\n新增转仓,请留意\n1个附件\n普通附件(34KB)\n柜号:MATU2745683卡派资料.xlsx(34K)\nop7@xinfenginc.com\n \n发件人: op7\n发送时间: 2026-07-02 18:09\n收件人: fancy; receiving; hui; luo; date; cs3; cs1; op10\n主题: LINK EVER INC + MATS4583030000+ 柜号: MATU2745683+ ETA : 7/13\n柜号:MATU2745683 更新派送单,请查收\nop7@xinfenginc.com','TRANSFER','PENDING_CONFIRM','{\"total\": 65, \"intent\": {\"facts\": [{\"label\": \"转仓对\", \"value\": \"2 组\"}, {\"label\": \"明细\", \"value\": \"ABQ2→POC2(1);FWA4→VGT2(1)\"}, {\"label\": \"货件行\", \"value\": \"327 行\"}], \"action\": \"批量转仓\", \"keywords\": [\"转仓\", \"更新派送单\", \"MATS4583030\", \"ETA/柜型\", \"卡派资料\", \"新增转仓\"], \"narrative\": \"判定为【批量转仓】。到仓前可确认批量转仓;到仓后将降级为工单\", \"next_step\": \"到仓前可确认批量转仓;到仓后将降级为工单\", \"operation\": \"转仓对 2 组 · 明细 ABQ2→POC2(1);FWA4→VGT2(1) · 货件行 327 行\", \"action_code\": \"TRANSFER\", \"content_hint\": \"新增转仓,请留意\", \"transfer_pairs\": [{\"to\": \"POC2\", \"from\": \"ABQ2\", \"rows\": 1}, {\"to\": \"VGT2\", \"from\": \"FWA4\", \"rows\": 1}], \"attachment_role\": \"卡派/货物明细(1 个)\"}, \"scores\": {\"UNKNOWN\": 0, \"TRANSFER\": 65, \"DO_UPLOAD\": 0, \"WORK_ORDER\": 45, \"NEW_CONTAINER\": 0, \"INSTRUCTION_LABEL\": 0, \"INSTRUCTION_HOLD_SPLIT\": 20}, \"signals\": [{\"score\": 50, \"signal\": \"转仓\", \"source\": \"body\", \"matched\": \"转仓\"}, {\"score\": 45, \"signal\": \"更新派送单\", \"source\": \"body\", \"matched\": \"更新派送单\"}, {\"score\": 5, \"signal\": \"ISO柜号\", \"source\": \"subject\", \"matched\": \"MATS4583030\"}, {\"score\": 5, \"signal\": \"船期/柜型\", \"source\": \"body\", \"matched\": \"ETA/柜型\"}, {\"score\": 15, \"signal\": \"卡派资料\", \"source\": \"attachment\", \"matched\": \"卡派资料\"}], \"mail_type\": \"TRANSFER\", \"sample_dir\": \"docx/邮件/邮件2\"}',NULL,'3b33e10ee6530a68408163952fb16528388ed6c1c7447d697db4c034e3e282ce',NULL,3,'2026-08-17 01:31:29.227','2026-08-17 01:31:29.592',NULL,NULL,1,NULL,NULL,NULL,'b4b17309-b1e2-4b58-a696-a23bd11fff93'),(254,'',NULL,'INBOX','Fw: 转发:派送要求更新 拆柜清单更新,DO请查收 拆柜清单更新: WHSU5574991+WHL063G550810+船名航次:OOCL SINGAPORE / 065W+ETA:6/28+FedEx-29件 UPS-102件 亚马逊卡派 289件 私人地址-166件 拦截-209件 拆柜清单','clx@cnwally.com.cn','2026-08-17 01:31:24.610','Fw: 转发:派送要求更新: 拆柜清单更新: DO请查收+拆柜清单更新:\nWHSU5574991+WHL063G550810+船名航次:OOCL SINGAPORE\n/ 065W+ETA:6/28+FedEx-29件+UPS-102件+亚马逊卡派-289件\n+私人地址-166件+拦截-209件+拆柜清单\n发件人75440098<75440098@qq.com>\n收件人3457189498<3457189498@qq.com>\n大小 1.4M\n时间 2026年7月14日 16:02\n发自我的iPhone\n------------------ 原始邮件 ------------------\n寄件人: date \n发送时间: 2026年7月10日 16:46\n收件人: 75440098@qq.com <75440098@qq.com>\n主旨: Fw: 转发:派送要求更新: 拆柜清单更新: DO请查收+拆柜清单更新:\nWHSU5574991+WHL063G550810+船名航次:OOCL SINGAPORE / 065W+ETA:\n6/28+FedEx-29件+UPS-102件+亚马逊卡派-289件+私人地址-166件+拦截-209件+拆柜清单\ndate\ndate@usasinogroup.com\n---- 转发的邮件 ----\n发件人clx@cnwally.com.cn\n日期 2026年07月10日 11:29\n收件人date、hui、luo、receiving\n抄送至马君利、曾丽、谢志冉、胡娜、樊苹梅、车欣\n雨、鲍悦纳、陈文静、蔡可欣、于雲雪、王\n小悦、杨慧元、朱甜甜\n主题 派送要求更新: 拆柜清单更新: DO请查收+拆柜清单更新: WHSU5574991+WHL063\nG550810+船名航次:OOCL SINGAPORE / 065W+ETA:6/28+FedEx-29件+UPS-\n102件+亚马逊卡派-289件+私人地址-166件+拦截-209件+拆柜清单\nWHSU5574991 \n1.WL104834-2 SCK8 2件\n这票2件每箱都需要开箱拍下里面的产品并回传照片 图片需要清晰,可以看清货物。\n2.\nWL103944-WL103940 2\nWL99583 LAX9 2\nWL103276 SBD1 1\nWL98967 VGT2 2\n以上货物需要卡转海 麻烦贴标交付,UPS默认一箱贴两张。面单请查收\n注意:FBA箱号需要对应快递子单号 ,例如:FBA0001对应第一张快递面单 \nclx@cnwally.com.cn\n \n发件人: clx@cnwally.com.cn\n发送时间: 2026-06-29 18:29\n收件人: date; hui; luo; receiving\n抄送: 马君利; 曾丽; 谢志冉; 胡娜; 樊苹梅; 车欣雨; 鲍悦纳; 陈文静; 蔡可欣; 于 雲 雪; 王小悦; 杨慧元; 朱甜\n甜\n主题: 拆柜清单更新: DO 请查收 + 拆柜清单更新 : WHSU5574991+WHL063G550810+ 船名航次:\nOOCL SINGAPORE / 065W+ETA : 6/28+FedEx-29 件 +UPS-102 件 + 亚马逊卡派 -289 件 + 私人地\n址 -166 件 + 拦截 -209 件 + 拆柜清单\n拆柜清单更新 以此为准\n注意:\n1.WL103516\nWL103616\nWL104246\nWL102053\nWL104827\nWL104563\nWL103544\nWL104135\nWL104139\n以上几票需要拦截 清单中已备注\n2.\nWL101167\nWL92334\n这2票改自提\nclx@cnwally.com.cn\n \n发件人: clx@cnwally.com.cn\n发送时间: 2026-06-29 18:22\n收件人: date; hui; luo; receiving\n抄送: 马君利; 曾丽; 谢志冉; 胡娜; 樊苹梅; 车欣雨; 鲍悦纳; 陈文静; 蔡可欣; 于 雲 雪; 王小悦; 杨慧\n元; 朱甜甜\n主题: DO 请查收 + 拆柜清单更新 : WHSU5574991+WHL063G550810+ 船名航次: OOCL\nSINGAPORE / 065W+ETA : 6/28+FedEx-29 件 +UPS-102 件 + 亚马逊卡派 -289 件 + 私人地\n址 -166 件 + 拦截 -209 件 + 拆柜清单\n拆柜清单更新 以此为准\n注意:\n1.WL103516\nWL103616\nWL104246\nWL102053\nWL104827\nWL104563\nWL103544\nWL104135\nWL104139\n以上几票需要拦截 清单中已备注\n2.\nWL101167\nWL92334\n这2票改自提\nclx@cnwally.com.cn\n \n发件人: clx@cnwally.com.cn\n发送时间: 2026-06-29 18:20\n收件人: date; hui; luo; receiving\n抄送: 马君利; 曾丽; 谢志冉; 胡娜; 樊苹梅; 车欣雨; 鲍悦纳; 陈文静; 蔡可欣; 于 雲 雪; 王小\n悦; 杨慧元; 朱甜甜\n主题: DO 请查收 + 拆柜清单更新 : WHSU5574991+WHL063G550810+ 船名航次:\nOOCL SINGAPORE / 065W+ETA : 6/28+FedEx-29 件 +UPS-102 件 + 亚马逊卡派 -289\n件 + 私人地址 -166 件 + 拦截 -209 件 + 拆柜清单\nDO请查收+拆柜清单更新: WHSU5574991+WHL063G550810+船名航次:\nOOCL SINGAPORE / 065W+ETA:6/28+FedEx-29件+UPS-102件+亚马逊卡\n派-289件+私人地址-166件+拦截-209件+拆柜清单\n拆柜清单更新 以此为准\n注意:\n1.WL103516\nWL103616\nWL104246\nWL102053\nWL104827\nWL104563\nWL103544\nWL104135\nWL104139\n以上几票需要拦截 清单中已备注\n2.\nWL101167\nWL92334\n这2票改自提\nclx@cnwally.com.cn\n \n发件人: clx@cnwally.com.cn\n发送时间: 2026-06-22 21:28\n收件人: date; hui; luo; receiving\n抄送: 马君利; 曾丽; 谢志冉; 胡娜; 樊苹梅; 车欣雨; 鲍悦纳; 陈文静; 蔡可欣; 于 雲 雪;\n王小悦; 杨慧元; 朱甜甜\n主题: WHSU5574991+WHL063G550810+ 船名航次: OOCL SINGAPORE /\n065W+ETA : 6/28+FedEx-29 件 +UPS-102 件 + 亚马逊卡派 -289 件 + 私人地\n址 -166 件 + 拦截 -209 件 + 拆柜清单\nWHSU5574991+WHL063G550810+船名航次:OOCL SINGAPORE /\n065W+ETA:6/28+FedEx-29件+UPS-102件+亚马逊卡派-289件+私人\n地址-166件+拦截-209件+拆柜清单\n注意:\n1.部分单号客户暂未提供PO 提供后补充 清单中已标黄 \n2.这个柜子还需要注意这些亚马逊卡派的是我们在国内换过标签 wl唛头上\n的仓点和FBA标签可能不一样需要注意下区分然后需要先根据wl唛头、拆柜\n清单还有FBA来分货 不能就根据wl唛头来打托\n3.WL102053\nWL104827\nWL104563\nWL103516\nWL103544\nWL104135\nWL102761\nWL104139\nWL101977\n以上单号需要拦截 清单中已备注\n1个附件\n普通附件(1MB)\nWHSU5574991 卡转海.zip(1M)\nclx@cnwally.com.cn','WORK_ORDER','PENDING_CONFIRM','{\"total\": 90, \"intent\": {\"facts\": [{\"label\": \"仓库线索\", \"value\": \"SCK8、LAX9、SBD1、VGT2\"}], \"action\": \"工单\", \"keywords\": [\"DO\", \"贴标,拦截\", \"拆柜清单\", \"WHSU5574991\", \"ETA/柜型\", \"贴标\", \"卡转海\"], \"narrative\": \"判定为【工单】。核对工单字段后确认提交至 CC 客户留言\", \"next_step\": \"核对工单字段后确认提交至 CC 客户留言\", \"operation\": \"仓库线索 SCK8、LAX9、SBD1、VGT2\", \"action_code\": \"WORK_ORDER\", \"content_hint\": \"卡转海\", \"transfer_pairs\": [], \"attachment_role\": \"业务附件 4 个\"}, \"scores\": {\"UNKNOWN\": 0, \"TRANSFER\": 0, \"DO_UPLOAD\": 25, \"WORK_ORDER\": 90, \"NEW_CONTAINER\": 0, \"INSTRUCTION_LABEL\": 0, \"INSTRUCTION_HOLD_SPLIT\": 20}, \"signals\": [{\"score\": 25, \"signal\": \"DO上传\", \"source\": \"body\", \"matched\": \"DO\"}, {\"score\": 45, \"signal\": \"工单\", \"source\": \"body\", \"matched\": \"贴标,拦截\"}, {\"score\": 45, \"signal\": \"拆柜清单\", \"source\": \"body\", \"matched\": \"拆柜清单\"}, {\"score\": 5, \"signal\": \"ISO柜号\", \"source\": \"subject\", \"matched\": \"WHSU5574991\"}, {\"score\": 5, \"signal\": \"船期/柜型\", \"source\": \"body\", \"matched\": \"ETA/柜型\"}], \"mail_type\": \"WORK_ORDER\", \"sample_dir\": \"docx/邮件/邮件3\"}',NULL,'346a55c6f1776c84277313cf3bfb8452847ab5aaaf9c349fc43ac19fbe47a873',NULL,3,'2026-08-17 01:31:30.314','2026-08-17 01:31:30.420',NULL,NULL,1,NULL,NULL,NULL,'909df990-57b0-41ac-b633-0d6549d1fc62'),(255,'',NULL,'INBOX','Fw: 转发:新增预报 36:新辰泽+WHLC027G597465+WHSU8127240+洛杉矶+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派组合柜-不带托架','xinchenze002@126.com','2026-08-17 01:31:24.610','Fw: 转发:新增预报136:新辰泽\n+WHLC027G597465+WHSU8127240+洛杉矶\n+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD\n013E+提拆派组合柜-不带托架\n发件人75440098<75440098@qq.com>\n收件人3457189498<3457189498@qq.com>\n大小 1.4M\n时间 2026年7月14日 16:00\n发自我的iPhone\n------------------ 原始邮件 ------------------\n寄件人: date \n发送时间: 2026年7月10日 16:43\n收件人: 75440098@qq.com <75440098@qq.com>\n主旨: Fw: 转发:新增预报136:新辰泽+WHLC027G597465+WHSU8127240+洛杉矶\n+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派组合柜-不带\n托架\ndate\ndate@usasinogroup.com\n---- 转发的邮件 ----\n发件人新辰泽-客服梁雪琴\n日期 2026年07月10日 15:41\n收件人luo\n抄送至date、hui、receiving、xinchenze\n主题 Re:Re:Re:Re:Re:Re:新增预报136:新辰泽+WHLC027G597465+WHSU8127240+洛\n杉矶+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆\n派组合柜-不带托架\nDear,\n原箱号 YT2604021091=FBA199R49LD6-MDW2=76 件操作指令\n换标后单号: FBA19HW52S0L-2G1MCB6X-HIA1=76 件\n1 :覆盖贴FBA 标签,一箱贴两张\n2 :覆盖贴SKU 标签,一箱 1 张( FNSKU : X003UHDF7B ( 304PCS ))\n3 :贴好拍照回传等国内客户确认后,再约仓卡派交付!\n注:回传过来的照片需要清晰拍照 SKU 上面的字迹,客户确认后再安排卡派交付,不确认不予安排!!!\n在 2026-05-25 13:45:03 , \" 新辰泽 - 客服梁雪琴 \" 写道:\nDear,\nWHSU8127240= 原箱号 YT2604021486- 剩余 540 件 +WHSU6227512= 原箱号\nYT2604021486=293 件 两个柜子共计 833 件操作指令 \n换标后单号:(以下可随机抽取对应件数换标)\nFBA19DS6CXV4-2G1XD81L-LGB8=87 件 FNSKU : X0054KUAT3-261PCS\nFBA19DSCJ1HD-79T3ESQP-VGT2=190 件 FNSKU : X0054KUAT3-570PCS\nFBA19DS9X2TC-8WR8HJSF-IAH3=115 件 FNSKU : X0054KUAT3-345PCS\nFBA19DS5Y39M-3U1C3ITA-CLT2=194 件 FNSKU : X0054KUAT3-582PCS\nFBA19DS9LZBL-8M3OUC5D-AVP1=247 件 FNSKU : X0054KUAT3-741PCS\n1 :覆盖贴FBA 标签,一箱贴两张\n2 :覆盖贴SKU 标签,一箱 1 张(五票 SKU 共用一份,需覆盖原透明计划标)\n3 :贴好拍照回传等国内客户确认后,再约仓卡派交付 \n在 2026-05-20 15:55:44 , \" 新辰泽 - 客服梁雪琴 \" 写道:\nDear,\n原箱号 YT2604021486=1041 件,换标 167 件操作指令如下 ,(剩余 540 件继续暂存等后续操作指\n令)\n换标后单号:(以下可随机抽取对应件数换标)\nFBA19DKB0P8R-6CZ4YAAH-SMF3=28 件 FNSKU : X0054GK7U9-84PCS\nFBA19DKGG68N-554KB4XH-QXY8=36 件 FNSKU : X0054GK7U9-108PCS\nFBA19DK88WL1-76244VXZ-IAH3=31 件 FNSKU : X0054GK7U9-93PCS\nFBA19DKDM6QF-8C9SYYJE-CLT2=35 件 FNSKU : X0054GK7U9-105PCS\nFBA19DKCBQQK-8L2EEN3Y-ABE8=37 件 FNSKU : X0054GK7U9-111PCS\n1 :覆盖贴FBA 标签,一箱贴两张\n2 :覆盖贴SKU 标签,一箱 1 张(五票 SKU 共用一份,需覆盖原透明计划标)\n3 :贴好拍照回传等国内客户确认后,再约仓卡派交付\n在 2026-05-18 17:17:17 , \" 新辰泽 - 客服梁雪琴 \" 写道:\nDear,\n原箱号 YT2604021486=1041 件,换标 334 件操作指令如下(剩余 707 件继续暂存等后续操作\n指令)\n换标后单号:(以下可随机抽取对应件数换标)\nFBA19DCCNYX0-8IOI1YYF-TCY2=167 件 FNSKU:X005462VRL-501PCS\n1 :覆盖贴FBA 标签,一箱贴两张\n2 :覆盖贴SKU 标签,一箱 1 张\n3 :贴好拍照回传等国内客户确认后,再约仓卡派交付\n============================\nFBA19DC7FMPX-48LST87U-SBD1=28 件 FNSKU : X005473QAL-84PCS\nFBA19DC4GCXC-8CRO9HJL-PSP3=31 件 FNSKU : X005473QAL-93PCS\nFBA19DC52QYN-635B7TMS-MQJ1=37 件 FNSKU : X005473QAL-111PCS\nFBA19DC40MDF-2WB1LB6N-FWA4=36 件 FNSKU : X005473QAL-108PCS\nFBA19DC459N0-31G5UIWW-IND9=35 件 FNSKU : X005473QAL-105PCS\n1 :覆盖贴FBA 标签,一箱贴两张\n2 :覆盖贴SKU 标签,一箱 1 张(五票 SKU 共用一份)\n3 :贴好拍照回传等国内客户确认后,再约仓卡派交付\n在 2026-05-09 18:00:16 , \"luo\" 写道:\n好的\nluo\nluo@usasinogroup.com\nOriginal:\nFrom:新辰泽-客服梁雪琴\nDate:2026-05-09 01:45:42(美国山区 (GMT-07:00))\nTo:luo \nCc:date , hui , receiving\n , xinchenze \nSubject:Re:Re:新增预报136:新辰泽+WHLC027G597465+WHSU8127240+洛杉矶\n+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派组合柜-不带托架\nDear,\nWHLC027G597465+WHSU8127240-- 此柜子预报已和你们老板沟通过 按照正常预\n报接受哈,帮忙安排派单,谢谢!!\n在 2026-05-09 10:52:25 , \"luo\" 写道:\n抱歉 仓库产能有限 该柜子我们目前无法接收 请您谅解\nluo\nluo@usasinogroup.com\nOriginal:\nFrom:新辰泽-客服梁雪琴\nDate:2026-05-08 19:15:53(美国山区 (GMT-07:00))\nTo:date , luo , hui\n , receiving \n1个附件\n普通附件(1MB)\n 箱 件换标操作指令\nCc:xinchenze \nSubject:新增预报136:新辰泽+WHLC027G597465+WHSU8127240+洛杉矶\n+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派组合柜-不带\n托架\nDear,\n请查收新增预报: 新辰泽 +WHLC027G597465+WHSU8127240+ 洛杉矶\n+40HQ+EDT2026.04-25 ETA2026.05-15 船名航次 HMM EMERALD 013E+ 提\n拆派组合柜 - 不带托架DO也同步上传至附件请注意查收!','WORK_ORDER','PENDING_CONFIRM','{\"total\": 75, \"intent\": {\"facts\": [{\"label\": \"仓库线索\", \"value\": \"MDW2、HIA1、LGB8、VGT2、IAH3、CLT2、AVP1、SMF3、QXY8、ABE8、TCY2、SBD1、PSP3、MQJ1、FWA4、IND9\"}, {\"label\": \"货件行\", \"value\": \"23 行\"}], \"action\": \"工单\", \"keywords\": [\"DO\", \"新增预报\", \"拍照,贴标\", \"WHSU8127240\", \"ETA/柜型\", \"卡派资料\", \"换标\", \"覆盖贴\"], \"narrative\": \"判定为【工单】。核对工单字段后确认提交至 CC 客户留言\", \"next_step\": \"核对工单字段后确认提交至 CC 客户留言\", \"operation\": \"仓库线索 MDW2、HIA1、LGB8、VGT2、IAH3、CLT2、AVP1、SMF3、QXY8、ABE8、TCY2、SBD1、PSP3、MQJ1、FWA4、IND9 · 货件行 23 行\", \"action_code\": \"WORK_ORDER\", \"content_hint\": \"换标操作指令\", \"transfer_pairs\": [], \"attachment_role\": \"贴标/换标作业附件(6 个)\"}, \"scores\": {\"UNKNOWN\": 0, \"TRANSFER\": 0, \"DO_UPLOAD\": 70, \"WORK_ORDER\": 75, \"NEW_CONTAINER\": 65, \"INSTRUCTION_LABEL\": 0, \"INSTRUCTION_HOLD_SPLIT\": 0}, \"signals\": [{\"score\": 55, \"signal\": \"DO上传\", \"source\": \"attachment\", \"matched\": \"DO\"}, {\"score\": 50, \"signal\": \"新增预报\", \"source\": \"subject\", \"matched\": \"新增预报\"}, {\"score\": 15, \"signal\": \"预报清单附件\", \"source\": \"attachment\", \"matched\": \"数据模版-新辰泽-WHSU8127240.xlsx\"}, {\"score\": 45, \"signal\": \"工单\", \"source\": \"body\", \"matched\": \"拍照,贴标\"}, {\"score\": 30, \"signal\": \"附件贴标指令\", \"source\": \"attachment\", \"matched\": \"YT2604021091=76件换标走HIA1卡派出库操作指令.xlsx 数据模版-新辰泽-WHSU8127240.xlsx 智鸿2+WHLC027G59746\"}, {\"score\": 5, \"signal\": \"ISO柜号\", \"source\": \"subject\", \"matched\": \"WHSU8127240\"}, {\"score\": 5, \"signal\": \"船期/柜型\", \"source\": \"body\", \"matched\": \"ETA/柜型\"}, {\"score\": 15, \"signal\": \"卡派资料\", \"source\": \"attachment\", \"matched\": \"卡派资料\"}], \"mail_type\": \"WORK_ORDER\", \"sample_dir\": \"docx/邮件/邮件4\"}',NULL,'aa290e414f50aef911b4032fbb20a11099551ecfcba4e94c628b217763661359',NULL,3,'2026-08-17 01:31:31.081','2026-08-17 01:31:31.250',NULL,NULL,1,NULL,NULL,NULL,'ddb3595f-c6b1-46e8-915f-6c73726f9aa3'),(256,'<6a84dce29c5f6_10a11a068815@sharedworker-85f4dd96d5-66wpc.mail>',1739,'INBOX','[GitHub] You have used 90% of the Actions minutes included for the nanchuanbeizi account','noreply@github.com','2026-08-18 22:29:54.000',NULL,'UNKNOWN','IGNORED',NULL,NULL,'5bee1bb942c97c69057fd8b73c4f98fc490691bacf98f21270969bd553310c3d','SKIP_FILTER',1,'2026-08-19 08:33:39.775','2026-08-19 08:33:39.775',1,NULL,1,NULL,NULL,'6a84dce29c5f6_10a11a068815@sharedworker-85f4dd96d5-66wpc.mail','fb48b5c6-1ef9-42ec-9d8e-8ef8434bd7a2'); +/*!40000 ALTER TABLE `mail_message` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `mailbox_account` +-- + +DROP TABLE IF EXISTS `mailbox_account`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `mailbox_account` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL, + `host` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'imap.qq.com', + `port` int unsigned NOT NULL DEFAULT '993', + `username` varchar(320) COLLATE utf8mb4_unicode_ci NOT NULL, + `password_enc` varchar(1024) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `folder` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'INBOX', + `enabled` tinyint(1) NOT NULL DEFAULT '1', + `last_test_at` datetime(3) DEFAULT NULL, + `last_test_ok` tinyint(1) DEFAULT NULL, + `last_test_error` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `auth_type` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'PASSWORD', + `idle_enabled` tinyint(1) NOT NULL DEFAULT '1', + `oauth_access_enc` varchar(2048) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `oauth_expires_at` datetime(3) DEFAULT NULL, + `oauth_provider` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `oauth_refresh_enc` varchar(2048) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_mailbox_username` (`username`), + KEY `idx_mailbox_enabled` (`enabled`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `mailbox_account` +-- + +LOCK TABLES `mailbox_account` WRITE; +/*!40000 ALTER TABLE `mailbox_account` DISABLE KEYS */; +INSERT INTO `mailbox_account` VALUES (1,'测试邮箱','imap.qq.com',993,'3457189498@qq.com','3soz5+zdqt2HkSvM:avirKWWDQK4zbZL6+D4//w==:JJmeda1RaLFM2XZNBv81eg==','INBOX',1,'2026-08-04 07:26:38.562',1,NULL,'2026-07-22 06:37:50.222','2026-08-04 07:26:38.565','PASSWORD',1,NULL,NULL,NULL,NULL),(3,'谷歌邮箱测试','imap.gmail.com',993,'niqijiti542@gmail.com','kwdZFNgc/tnReh97:Tcop1ZO11jTQZn7YZsmFgw==:zDEpeg8ibgHuqx913WYO2w==','INBOX',1,'2026-08-17 09:03:12.890',1,NULL,'2026-07-23 02:40:46.495','2026-08-17 09:03:12.892','PASSWORD',1,NULL,NULL,NULL,NULL); +/*!40000 ALTER TABLE `mailbox_account` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `oauth_settings` +-- + +DROP TABLE IF EXISTS `oauth_settings`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `oauth_settings` ( + `id` int NOT NULL DEFAULT '1', + `public_base_url` varchar(512) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'http://localhost:3000', + `google_client_id` varchar(256) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `google_client_secret_enc` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `ms_client_id` varchar(256) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', + `ms_client_secret_enc` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `ms_tenant` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'common', + `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `oauth_settings` +-- + +LOCK TABLES `oauth_settings` WRITE; +/*!40000 ALTER TABLE `oauth_settings` DISABLE KEYS */; +INSERT INTO `oauth_settings` VALUES (1,'http://localhost:3000','niqijiti542@gmail.com','N7eT2UvyNBE5trJj:LxFDMOLqVyxhm1tcoCeIDg==:H3A8qxYAmFzAjQHiWsVHye7pfQ==','',NULL,'common','2026-07-23 02:10:41.030'); +/*!40000 ALTER TABLE `oauth_settings` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `parse_result` +-- + +DROP TABLE IF EXISTS `parse_result`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `parse_result` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `mail_id` bigint unsigned NOT NULL, + `container_header` json NOT NULL, + `shipments` json NOT NULL, + `lineage` json DEFAULT NULL, + `created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + PRIMARY KEY (`id`), + UNIQUE KEY `parse_result_mail_id_key` (`mail_id`), + CONSTRAINT `parse_result_mail_id_fkey` FOREIGN KEY (`mail_id`) REFERENCES `mail_message` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=273 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `parse_result` +-- + +LOCK TABLES `parse_result` WRITE; +/*!40000 ALTER TABLE `parse_result` DISABLE KEYS */; +INSERT INTO `parse_result` VALUES (124,95,'{\"F_ETA\": null, \"F_ETD\": null, \"F_Dock\": \"\", \"F_LoadPort\": \"nf0syCZQGJQih1fIZDtq-2Btg0T5qerQWVt59wgt\", \"F_TransMode\": 0, \"F_BLCopyCode\": \"PNBY3IFPXJHSI-2FERRKXL5LNJ6PCLGO\", \"F_MemoRemark\": \"\", \"F_ContainerNo\": \"\", \"F_Instruction\": \"\", \"F_ShippingLineId\": \"\", \"container_no_valid\": false}','[]','{\"note\": \"no spreadsheet\", \"do_files\": [], \"mail_record\": {\"kind\": \"UNKNOWN\", \"table\": {\"rows\": [], \"columns\": [\"柜号\", \"动作\", \"原仓\", \"目标仓\", \"快递单号\", \"备注\"]}, \"lineage\": {\"source\": \"work_order\", \"heuristic_container\": null}, \"modules\": {}, \"summary\": \"未能确认业务类型;可 Admin 改类型后重解析。\"}, \"shipping_line_hint\": \"PNBY\", \"shipping_line_unmatched\": \"PNBY\"}','2026-08-06 02:08:05.728','2026-08-06 02:08:05.728'),(128,97,'{\"F_ETA\": \"2026-07-10\", \"F_ETD\": null, \"F_Dock\": \"\", \"F_LoadPort\": \"\", \"F_TransMode\": 0, \"F_BLCopyCode\": \"COSU6504130784\", \"F_MemoRemark\": \"ZJYC / 派拆柜派送\", \"F_CabinetType\": \"40HQ\", \"F_ContainerNo\": \"FCIU7089474\", \"F_Instruction\": \"\", \"F_OperationType\": 0, \"F_ShippingLineId\": \"\", \"container_no_valid\": true}','[]','{\"note\": \"no spreadsheet\", \"do_files\": [], \"mail_record\": {\"kind\": \"WORK_ORDER\", \"table\": {\"rows\": [{\"动作\": \"拆柜\", \"原仓\": \"\", \"备注\": \"Fw: Fw:回复:ZJYC COSU6504130784\\tFCIU7089474 7.10 派拆柜派送\", \"柜号\": \"FCIU7089474\", \"目标仓\": \"\", \"快递单号\": \"\"}], \"columns\": [\"柜号\", \"动作\", \"原仓\", \"目标仓\", \"快递单号\", \"备注\"]}, \"lineage\": {\"source\": \"work_order\", \"express_nos\": [], \"subject_only\": false, \"heuristic_container\": \"FCIU7089474\"}, \"modules\": {\"eta\": \"2026-07-10\", \"bl_no\": \"COSU6504130784\", \"service\": \"派拆柜派送\", \"ops_hints\": [\"拆柜\", \"派送\"], \"cabinet_type\": \"40HQ\", \"container_no\": \"FCIU7089474\", \"customer_name\": \"ZJYC\", \"shipping_line_hint\": \"COSU\"}, \"summary\": \"工单邮件(主题):拆柜、派送;提单 COSU6504130784 · 柜号 FCIU7089474 · ETA 2026-07-10。\", \"work_order_actions\": []}, \"shipping_line_hint\": \"COSU\", \"shipping_line_unmatched\": \"COSU\"}','2026-08-12 06:25:08.930','2026-08-13 03:57:45.240'),(129,98,'{\"F_ETA\": \"2026-06-22\", \"F_ETD\": null, \"F_Dock\": \"\", \"F_TransMode\": 0, \"F_BLCopyCode\": \"ONEYNB3BW9236300\", \"F_MemoRemark\": \"拆柜清单\", \"F_ContainerNo\": \"TCNU4412465\", \"F_Instruction\": \"船名航次 WAN HAI 511-V.E110\", \"container_no_valid\": true}','[]','{\"note\": \"ops_subject_only\", \"skip_body\": true, \"mail_record\": {\"kind\": \"WORK_ORDER\", \"table\": {\"rows\": [{\"动作\": \"贴标\", \"原仓\": \"\", \"备注\": \"UPS 6件\", \"柜号\": \"TCNU4412465\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"贴标\", \"原仓\": \"\", \"备注\": \"亚马逊卡派 1275件\", \"柜号\": \"TCNU4412465\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"贴标\", \"原仓\": \"\", \"备注\": \"私人地址 30件\", \"柜号\": \"TCNU4412465\", \"目标仓\": \"\", \"快递单号\": \"\"}], \"columns\": [\"柜号\", \"动作\", \"原仓\", \"目标仓\", \"快递单号\", \"备注\"]}, \"lineage\": {\"source\": \"work_order\", \"express_nos\": [], \"subject_only\": true, \"channel_summary\": [{\"ctns\": 6, \"channel\": \"UPS\"}, {\"ctns\": 1275, \"channel\": \"亚马逊卡派\"}, {\"ctns\": 30, \"channel\": \"私人地址\"}], \"heuristic_container\": \"TCNU4412465\"}, \"modules\": {\"eta\": \"2026-06-22\", \"bl_no\": \"ONEYNB3BW9236300\", \"service\": \"拆柜清单\", \"ops_hints\": [\"贴标\", \"拍照\"], \"container_no\": \"TCNU4412465\", \"vessel_voyage\": \"WAN HAI 511-V.E110\"}, \"summary\": \"工单邮件(主题):贴标、拍照;提单 ONEYNB3BW9236300 · 柜号 TCNU4412465 · 船名航次 WAN HAI 511-V.E110 · ETA 2026-06-22;渠道汇总 3 项。\", \"work_order_actions\": [\"贴标\", \"拍照\"]}, \"channel_summary\": [{\"ctns\": 6, \"channel\": \"UPS\"}, {\"ctns\": 1275, \"channel\": \"亚马逊卡派\"}, {\"ctns\": 30, \"channel\": \"私人地址\"}], \"skip_attachments\": true, \"ops_subject_payload\": \"TCNU4412465+ ONEYNB3BW9236300+船名航次:WAN HAI 511-V.E110+ETA:6/22+UPS-6件+亚马逊卡派-1275件+私人地址-30件+拆柜清单\"}','2026-08-12 06:25:19.441','2026-08-13 03:57:45.297'),(130,99,'{\"F_ETA\": \"2026-07-07\", \"F_ETD\": null, \"F_Dock\": \"\", \"F_Classis\": 0, \"F_LoadPort\": \"\", \"F_TransMode\": 0, \"F_BLCopyCode\": \"ZIMUSHH32195411\", \"F_MemoRemark\": \"\", \"F_ContainerNo\": \"ZCSU6581068\", \"F_Instruction\": \"\", \"F_OperationType\": 0, \"F_ShippingLineId\": \"\", \"container_no_valid\": true}','[{\"F_PO\": null, \"F_CBM\": 0.1341, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G6WBL97\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 114.06, \"F_Address\": \"6251 Pioneer Trail\\nLOS LUNAS NW 87031\\nABQ2\", \"F_Contact\": null, \"F_FBACode\": \"ABQ2\", \"F_ZipCode\": null, \"row_index\": 1, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796179\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6Y9R7GZF\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 3.15, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 32, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GB3CZVK\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 632.45, \"F_Address\": \"6251 Pioneer Trail\\nLOS LUNAS NW 87031\\nABQ2\", \"F_Contact\": null, \"F_FBACode\": \"ABQ2\", \"F_ZipCode\": null, \"row_index\": 2, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797203\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"8TFEEDOK\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.99, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 11, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G28PJ8Q\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 209.6, \"F_Address\": \"21000 E 13th Ave\\nAURORA CO 80018\\nDEN8\", \"F_Contact\": null, \"F_FBACode\": \"DEN8\", \"F_ZipCode\": null, \"row_index\": 3, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001795001\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"55HCL19V\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0447, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FQ4LX4D\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 14.89, \"F_Address\": \"33333 LBJ FWY \\nDallas TX 75241-7203 \\nFTW1\", \"F_Contact\": null, \"F_FBACode\": \"FTW1\", \"F_ZipCode\": null, \"row_index\": 4, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797183\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"54WLBMNF\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0483, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDJB5WW\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 20.52, \"F_Address\": \"33333 LBJ FWY \\nDallas TX 75241-7203 \\nFTW1\", \"F_Contact\": null, \"F_FBACode\": \"FTW1\", \"F_ZipCode\": null, \"row_index\": 5, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797758\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1B8U5QDK\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.3477, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G5J0DBG\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": \"转VGT2\", \"F_Weight\": 74.77, \"F_Address\": \"9798 Smith Road \\nFORT WAYNE IN 46809-9771\\nFWA4\", \"F_Contact\": null, \"F_FBACode\": \"FWA4\", \"F_ZipCode\": null, \"row_index\": 6, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001795819\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"42VFA4DJ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.1207, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G3W5YJW\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": \"转VGT2\", \"F_Weight\": 31.78, \"F_Address\": \"9798 Smith Road \\nFORT WAYNE IN 46809-9771\\nFWA4\", \"F_Contact\": null, \"F_FBACode\": \"FWA4\", \"F_ZipCode\": null, \"row_index\": 7, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796327\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4NTVL4GD\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.1122, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G874ZNL\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": \"转VGT2\", \"F_Weight\": 16.01, \"F_Address\": \"9798 Smith Road \\nFORT WAYNE IN 46809-9771\\nFWA4\", \"F_Contact\": null, \"F_FBACode\": \"FWA4\", \"F_ZipCode\": null, \"row_index\": 8, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796576\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6FP3FXHD\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.85, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 8, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G7XSKR8\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": \"转VGT2\", \"F_Weight\": 164.64, \"F_Address\": \"9798 Smith Road \\nFORT WAYNE IN 46809-9771\\nFWA4\", \"F_Contact\": null, \"F_FBACode\": \"FWA4\", \"F_ZipCode\": null, \"row_index\": 9, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797572\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6UG338LE\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0545, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDYSN1C\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": \"转VGT2\", \"F_Weight\": 10.02, \"F_Address\": \"9798 Smith Road \\nFORT WAYNE IN 46809-9771\\nFWA4\", \"F_Contact\": null, \"F_FBACode\": \"FWA4\", \"F_ZipCode\": null, \"row_index\": 10, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797891\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3W71FGOH\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.4, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 30, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FMVN4Q7\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 473.3, \"F_Address\": \"18007 E Garland Ave\\nSPOKANE WA 99216-6102\\nGEG2\", \"F_Contact\": null, \"F_FBACode\": \"GEG2\", \"F_ZipCode\": null, \"row_index\": 11, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001793288\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"78EDUCOR\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.88, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 7, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G8JJCVZ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 115.43, \"F_Address\": \"15301 West Northern Avenue\\nGlendale AZ 85307\\nGEU2\", \"F_Contact\": null, \"F_FBACode\": \"GEU2\", \"F_ZipCode\": null, \"row_index\": 12, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796671\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3KO8UBSF\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.11, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 33, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GB31YQD\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 517.76, \"F_Address\": \"15301 West Northern Avenue\\nGlendale AZ 85307\\nGEU2\", \"F_Contact\": null, \"F_FBACode\": \"GEU2\", \"F_ZipCode\": null, \"row_index\": 13, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796855\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"79P9QB8N\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.5159, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GBQ0R84\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 78.99, \"F_Address\": \"15301 West Northern Avenue\\nGlendale AZ 85307\\nGEU2\", \"F_Contact\": null, \"F_FBACode\": \"GEU2\", \"F_ZipCode\": null, \"row_index\": 14, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797286\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2VE64SDD\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.2354, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDP9LNK\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.58, \"F_Address\": \"15301 West Northern Avenue\\nGlendale AZ 85307\\nGEU2\", \"F_Contact\": null, \"F_FBACode\": \"GEU2\", \"F_ZipCode\": null, \"row_index\": 15, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798238\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1OEDNZ2J\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.23, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 21, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G5HMMM0\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 435.91, \"F_Address\": \"18900 W McDowell Road\\nBUCKEYE AZ 85396\\nGEU3\", \"F_Contact\": null, \"F_FBACode\": \"GEU3\", \"F_ZipCode\": null, \"row_index\": 16, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001795738\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"8N862NVX\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.11, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 34, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GB086TX\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 534.06, \"F_Address\": \"18900 W McDowell Road\\nBUCKEYE AZ 85396\\nGEU3\", \"F_Contact\": null, \"F_FBACode\": \"GEU3\", \"F_ZipCode\": null, \"row_index\": 17, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796863\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"8OUWZO4J\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.2475, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GF0Y7TR\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 54.1, \"F_Address\": \"18900 W McDowell Road\\nBUCKEYE AZ 85396\\nGEU3\", \"F_Contact\": null, \"F_FBACode\": \"GEU3\", \"F_ZipCode\": null, \"row_index\": 18, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797907\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6JQGDRFL\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.36, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G5W4ZH6\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 87.21, \"F_Address\": \"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\", \"F_Contact\": null, \"F_FBACode\": \"GYR2\", \"F_ZipCode\": null, \"row_index\": 19, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001795871\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4VEKC38D\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.1659, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G3ZN3R8\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 31.7, \"F_Address\": \"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\", \"F_Contact\": null, \"F_FBACode\": \"GYR2\", \"F_ZipCode\": null, \"row_index\": 20, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796324\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2VIKOLFC\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.02, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 8, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G7WGW02\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 164.74, \"F_Address\": \"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\", \"F_Contact\": null, \"F_FBACode\": \"GYR2\", \"F_ZipCode\": null, \"row_index\": 21, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797569\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5GLIYIJS\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.1545, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDJ9QKC\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 87.22, \"F_Address\": \"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\", \"F_Contact\": null, \"F_FBACode\": \"GYR2\", \"F_ZipCode\": null, \"row_index\": 22, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797808\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6JF411TD\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.6367, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 15, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GBFSDYY\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 209.15, \"F_Address\": \"8181 W ROOSEVELT ST\\nPHOENIX, AZ 85043-2356\\nGYR3\", \"F_Contact\": null, \"F_FBACode\": \"GYR3\", \"F_ZipCode\": null, \"row_index\": 23, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798002\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"36BB9GCD\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0835, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G8CZKPZ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 13.55, \"F_Address\": \"15525 Milner Road\\nHOUSTON, TX 77032\\nIAH3\", \"F_Contact\": null, \"F_FBACode\": \"IAH3\", \"F_ZipCode\": null, \"row_index\": 24, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796597\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7LMVT2IN\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.2163, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G8D0PBJ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 40.45, \"F_Address\": \"15525 Milner Road\\nHOUSTON, TX 77032\\nIAH3\", \"F_Contact\": null, \"F_FBACode\": \"IAH3\", \"F_ZipCode\": null, \"row_index\": 25, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796606\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7KPVQWHV\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.86, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 8, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G7STNH6\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 164.76, \"F_Address\": \"15525 Milner Road\\nHOUSTON, TX 77032\\nIAH3\", \"F_Contact\": null, \"F_FBACode\": \"IAH3\", \"F_ZipCode\": null, \"row_index\": 26, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797565\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1MU9CJCB\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.14, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 23, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GC2N3YQ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 277.32, \"F_Address\": \"15525 Milner Road\\nHOUSTON, TX 77032\\nIAH3\", \"F_Contact\": null, \"F_FBACode\": \"IAH3\", \"F_ZipCode\": null, \"row_index\": 27, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797862\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"56BGI6MF\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.75, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 8, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G5C4TKT\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 150.1, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 28, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001795821\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"11IWRYTZ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G5WBF5W\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 87.16, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 29, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001795872\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"8XEKO8WL\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0843, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GB4S42R\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 14.57, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 30, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797085\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"78W76LVL\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.68, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 7, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G902RC2\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 133.82, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 31, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797677\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4RHPNDQY\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.68, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G8XH0NV\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 133.82, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 32, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797677\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"76LLHIYR\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.68, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G8R3H2V\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 133.82, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 33, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797677\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4UOSCCVM\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.1892, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDMQSNB\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 108.57, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 34, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797800\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7D2FTS5I\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0531, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GF2GZNL\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 10.01, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 35, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797889\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2MSSGRBO\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0322, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDZY65T\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5.06, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 36, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797956\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"11N4OVXL\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.1907, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDMCVCX\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 108.87, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 37, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797802\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"58P6VXUQ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.33, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 21, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GC3ZCNQ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 258.06, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 38, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797863\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6Z5VR6QA\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.25, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 16, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDBBCS0\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 267.25, \"F_Address\": \"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\", \"F_Contact\": null, \"F_FBACode\": \"LGB8\", \"F_ZipCode\": null, \"row_index\": 39, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797726\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1E28K1YY\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.63, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 13, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G5VDCT8\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 188.65, \"F_Address\": \"250 EMERALD DR\\nJoliet,IL 60433-3280\\nMDW2\", \"F_Contact\": null, \"F_FBACode\": \"MDW2\", \"F_ZipCode\": null, \"row_index\": 40, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796794\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7P4OCRJZ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0864, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GB3Y64T\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 14.45, \"F_Address\": \"250 EMERALD DR\\nJoliet,IL 60433-3280\\nMDW2\", \"F_Contact\": null, \"F_FBACode\": \"MDW2\", \"F_ZipCode\": null, \"row_index\": 41, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797102\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"8E6MVMNC\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0322, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GF16QNH\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5.11, \"F_Address\": \"250 EMERALD DR\\nJoliet,IL 60433-3280\\nMDW2\", \"F_Contact\": null, \"F_FBACode\": \"MDW2\", \"F_ZipCode\": null, \"row_index\": 42, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797953\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"37C6SQBP\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.1112, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G88BHGD\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 16.5, \"F_Address\": \"4412 W 300 N \\nGREENFIELD, IN 46140-7099\\nMQJ1\", \"F_Contact\": null, \"F_FBACode\": \"MQJ1\", \"F_ZipCode\": null, \"row_index\": 43, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796571\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3I7GJ3ZC\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0864, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GB46YP7\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 14.49, \"F_Address\": \"4412 W 300 N \\nGREENFIELD, IN 46140-7099\\nMQJ1\", \"F_Contact\": null, \"F_FBACode\": \"MQJ1\", \"F_ZipCode\": null, \"row_index\": 44, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797101\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3ILUAALL\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0808, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GB1F8L7\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 12.17, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 45, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797110\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3JZEAIPX\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.73, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G7SNTHJ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 247.22, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 46, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797564\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"12MW1SQE\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.73, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G7SGKLK\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 247.22, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 47, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797564\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"53D3BDRW\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.24, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GBMQB06\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": \"转POC2\", \"F_Weight\": 67.93, \"F_Address\": \"1351 S Road 40 E\\nPASCO WA 99301\\nPSC2\", \"F_Contact\": null, \"F_FBACode\": \"PSC2\", \"F_ZipCode\": null, \"row_index\": 48, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797539\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7OWR65GZ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.1505, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDQ1G8N\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": \"转POC2\", \"F_Weight\": 38.32, \"F_Address\": \"1351 S Road 40 E\\nPASCO WA 99301\\nPSC2\", \"F_Contact\": null, \"F_FBACode\": \"PSC2\", \"F_ZipCode\": null, \"row_index\": 49, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798165\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7QKZXAOZ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.1179, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G66FZ9R\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 13.72, \"F_Address\": \"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 50, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001795939\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5X2YL64A\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0808, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GB3TCQV\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 12.12, \"F_Address\": \"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 51, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797132\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7JJUJLML\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.56, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 21, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDF7LXM\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 353.73, \"F_Address\": \"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 52, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797727\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"83YHES8B\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0591, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDK14N3\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 15.88, \"F_Address\": \"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 53, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797780\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2KNJR5RT\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0523, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDZHD3H\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 9.98, \"F_Address\": \"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 54, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797890\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5TJRHLMV\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.0322, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GF0B2ND\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5.08, \"F_Address\": \"64165 19th AVE,DESERT HOT SPRINGS CA 92240 PSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 55, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797957\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5WRJ6X1L\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.1919, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDJW516\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 109.17, \"F_Address\": \"6001 S AUSTIN RD \\nSTOCKTON, CA 95215-8354\\nSCK4\", \"F_Contact\": null, \"F_FBACode\": \"SCK4\", \"F_ZipCode\": null, \"row_index\": 56, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001797810\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5LI2NMKR\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.5074, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 12, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GBC9142\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 176.41, \"F_Address\": \"6001 S AUSTIN RD \\nSTOCKTON, CA 95215-8354\\nSCK4\", \"F_Contact\": null, \"F_FBACode\": \"SCK4\", \"F_ZipCode\": null, \"row_index\": 57, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798004\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"8IJDFEKN\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.0978, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 20, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G84B2GY\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 420, \"F_Address\": \"4700 Wilbur Ave\\nOAKLEY CA 94561-2677\\nSCK8\", \"F_Contact\": null, \"F_FBACode\": \"SCK8\", \"F_ZipCode\": null, \"row_index\": 58, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001796585\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6P7APYIY\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.5, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 30, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FMVM3BC\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 472.4, \"F_Address\": \"7148 W OLD BINGHAM HWY\\nWest Jordan UT 84081\\nSLC2\", \"F_Contact\": null, \"F_FBACode\": \"SLC2\", \"F_ZipCode\": null, \"row_index\": 59, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001793289\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"81SO1PZK\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 60, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798185\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 61, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797670\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 62, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797881\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 63, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797666\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 64, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001795761\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 65, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797929\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 66, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798193\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 67, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797326\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 68, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796954\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 69, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797843\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 70, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796956\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 71, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797330\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 72, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796966\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 73, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001795406\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 74, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798122\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 75, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797847\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 76, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001795987\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 77, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797879\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 9, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 78, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001795764\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 79, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796367\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 80, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797671\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 9, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 81, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796985\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 82, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797883\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 83, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797841\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 84, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797667\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 85, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797835\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 86, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797930\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 87, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796959\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 88, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798123\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 89, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797928\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 90, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797669\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 91, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796971\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 92, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796972\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 93, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797846\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 94, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797665\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 8, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 95, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798242\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 96, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001795763\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 97, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797322\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 98, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797931\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 99, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798121\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 100, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797324\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 101, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796978\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 102, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001791683\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 28, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 103, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798023\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 104, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798131\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 105, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798127\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 106, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797668\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 107, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798181\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 108, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796370\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 109, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796658\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 28, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 110, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798322\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 10, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 111, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001795762\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 112, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001795404\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 113, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"FBA19G2NWL9C\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 114, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796623\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 115, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796395\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 116, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798053\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 117, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797932\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 12, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 118, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798018\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 119, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798190\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 120, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797965\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 121, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797882\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 122, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797176\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 123, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798124\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 124, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001795405\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 125, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796962\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 126, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001795407\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 127, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798054\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 128, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797327\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 129, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796969\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 130, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001791687\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 131, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797167\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 132, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797663\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 133, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797003\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 134, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796970\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 135, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798188\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 136, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001791681\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 137, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798236\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 138, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797884\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 139, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798042\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 140, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797664\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 141, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797987\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 9, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 142, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001795765\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 143, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797672\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 144, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797798\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 145, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796729\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 146, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797541\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 147, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797777\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 148, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798102\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 20, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 149, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797896\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 8, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 150, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797567\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 151, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797797\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 152, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"FBA19GFTMJV8\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 153, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797688\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 154, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001796953\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 155, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001793333\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 156, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797796\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 157, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797756\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 158, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001797544\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 21.62, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 13, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 5300.44, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"UPS\", \"F_ZipCode\": null, \"row_index\": 159, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001795198\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 160, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"BAZUS001796391\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 161, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21795111-1859966-GWS\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 162, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21812939-1857404-AY6\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 163, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21798980-1860524-2Q0\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 164, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21803464-1851016-C4A\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 165, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21804617-1860684-64E\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 166, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21809991-1860619-IBL\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 167, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21810402-1860522-SVI\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 168, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21812564-1860651-VHN\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 169, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21812939-1857413-ZZU\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 170, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21812939-1857418-ZOH\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 171, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21812939-1857405-SFX\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 172, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21812939-1857408-A0V\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 173, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21812939-1857422-ASO\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 174, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21812939-1857406-QLF\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 175, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21813418-1857444-PKC\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 176, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21812939-1857415-LA2\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 177, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21812939-1857423-FV1\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 178, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21812939-1857420-2UO\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 179, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21813418-1857446-613\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 180, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21813418-1857451-KZQ\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 181, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21813418-1857449-GA6\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 182, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21813533-1857199-6FH\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 183, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21814201-1862425-LCR\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 184, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21814201-1862428-FWZ\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 185, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21816798-1861194-SIM\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 186, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21817183-1862441-KEX\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 187, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21817183-1862447-LSA\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 188, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21817594-1857678-DSY\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 189, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21817482-1859755-IMP\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 190, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21817836-1857534-Z1R\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 191, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21818563-1857392-WMF\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 192, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21818563-1857393-V46\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 193, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21820473-1862340-AEN\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 194, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21820473-1862346-DFZ\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 195, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21820874-1859963-PZ9\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 196, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21820677-1862363-ZWG\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 197, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21820677-1862397-B8Q\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 198, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21824718-1860925-WNJ\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 199, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21824387-1860821-UNI\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 200, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21824574-1862328-CFS\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 201, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21825020-1862319-RAD\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 202, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21824931-1860890-JNS\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 203, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21825020-1862321-GKP\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 204, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21825762-1862193-2KE\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 205, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21825934-1862176-NBC\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 206, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21825934-1862181-9XY\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 207, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21828078-1860831-KUB\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 208, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21828533-1860688-H7S\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 209, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21828681-1860796-TVZ\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 210, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21828991-1860892-JIU\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 211, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21829988-1860896-3VA\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 212, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21830306-1861199-6FP\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 213, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21830325-1862218-8CI\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 214, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21830401-1860830-TBY\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 215, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21830416-1860822-S55\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 216, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21831623-1860803-364\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 217, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21833355-1860762-EIA\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 218, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21832203-1862243-IND\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 219, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21832679-1862143-VRH\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 220, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21833355-1860758-BYH\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 221, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21833355-1860763-KIX\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 222, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21834079-1862148-BYN\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 223, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21834419-1862253-ROE\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 224, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21834896-1862224-ARH\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 225, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21836517-1861506-LH5\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 226, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21837058-1862540-HE5\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 227, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21837334-1862236-Y2J\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 228, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21837418-1862140-NAY\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 229, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21837624-1862107-JAW\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 230, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21838711-1862137-4A7\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 231, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21838772-1862417-07W\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 232, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134081735-0619045627\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 233, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134081735-0619050016\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 234, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133375905-0620055447\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 235, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133542629-0618041348\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 236, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133793705-0620041202\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 237, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133909821-0620122845\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 238, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134117540-0620123347\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 239, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134117540-0620123606\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 240, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134314697-0620075314\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 241, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133551142-0620101145\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 242, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133551142-0620100909\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 243, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133912624-0620011557\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 244, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134258687-0620090927\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 245, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134258687-0620090559\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 246, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134258687-0620091149\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 247, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134044593-0619051059\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 248, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133387561-0617120522\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 249, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133743891-0618012911\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 250, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133532941-0620064403\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 251, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133777731-0619064125\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 252, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133868618-0619011347\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 253, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132936951-0620112311\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 254, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133400801-0619100537\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 255, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134011003-0619065044\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 256, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133625456-0620100257\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 257, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133946666-0620075802\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 258, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133548663-0619112543\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 259, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133749460-0619012915\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 260, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133966081-0619081751\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 261, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133302274-0620013010\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 262, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133453227-0618101615\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 263, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133887190-0620055027\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 264, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133541475-0619015912\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 265, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133915428-0620075802\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 266, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134211551-0620043526\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 267, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133870275-0620074327\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 268, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133615729-0620045034\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 269, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134029576-0620071503\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 270, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133947543-0620094355\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 271, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134093720-0620114111\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 272, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133365581-0619064731\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 273, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134073376-0620082659\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 274, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133609950-0618073327\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 275, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133613332-0619052048\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 276, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133784563-0618062107\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 277, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133613332-0619052242\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 278, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132246509-0618101425\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 279, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133417628-0618070117\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 280, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133572594-0618124747\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 281, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133821084-0620122107\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 282, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133902032-0619123321\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 283, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133910443-0620083037\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 284, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134229166-0620114955\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 285, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134614991-0620085101\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 286, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133678933-0618012324\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 287, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133742367-0620075703\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 288, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134085239-0619064759\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 289, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133855069-0620064850\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 12, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 1976.32, \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"FEDEX\", \"F_ZipCode\": null, \"row_index\": 290, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133959297-0620071222\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": null, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 0, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": null, \"warnings\": [\"件数请填纯数值\", \"总体积请填纯数值\", \"毛重请填纯数值\"], \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 291, \"row_status\": \"INVALID\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"invalid_reasons\": [\"渠道缺失\", \"件数无效\", \"总体积缺失\", \"毛重缺失\"], \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}]','{\"sheet\": \"卡派资料\", \"row_count\": 291, \"mail_record\": {\"kind\": \"WORK_ORDER\", \"table\": {\"rows\": [{\"动作\": \"转仓\", \"原仓\": \"FWA4\", \"备注\": \"货件 5 行\", \"柜号\": \"ZCSU6581068\", \"目标仓\": \"VGT2\", \"快递单号\": \"\"}, {\"动作\": \"转仓\", \"原仓\": \"PSC2\", \"备注\": \"货件 2 行\", \"柜号\": \"ZCSU6581068\", \"目标仓\": \"POC2\", \"快递单号\": \"\"}], \"columns\": [\"柜号\", \"动作\", \"原仓\", \"目标仓\", \"快递单号\", \"备注\"]}, \"lineage\": {\"source\": \"work_order\", \"express_nos\": [], \"subject_only\": false, \"heuristic_container\": \"ZCSU6581068\"}, \"modules\": {\"ops_hints\": [\"转仓\"], \"container_no\": \"ZCSU6581068\"}, \"summary\": \"工单邮件(主题):转仓;柜号 ZCSU6581068。\", \"work_order_actions\": [\"转仓\"]}, \"usedFilename\": \"柜号ZCSU6581068 卡派资料.xlsx\", \"sheetFallback\": false, \"instruction_state\": {\"forecast-body_segment-5-派送单请查收\": {\"mode\": \"live\", \"status\": \"IMPORTED\", \"externalId\": \"66a83f5c-72cd-46f8-959c-655fad9191f0\", \"importedAt\": \"2026-08-17T07:26:19.303Z\", \"importedBy\": \"admin\"}}, \"shipping_line_hint\": \"ZIMU\", \"soft_limit_exceeded\": false, \"transfer_segment_ctx\": {\"transfer-body_segment-3-新增转仓_转仓\": {\"rows\": [{\"to\": \"POC2\", \"eta\": \"2026/7/7\", \"ctns\": 7, \"from\": \"PSC2\", \"containerNo\": \"ZCSU6581068\"}], \"pairs\": [{\"to\": \"POC2\", \"from\": \"PSC2\", \"rows\": 7}], \"ocrSource\": \"630D332C@E5624726.E0117C6A00000000.jpg\"}, \"transfer-body_segment-4-新增转仓_转仓\": {\"rows\": [{\"to\": \"VGT2\", \"ctns\": 17, \"from\": \"FWA4\", \"containerNo\": \"ZCSU6581068\"}], \"pairs\": [{\"to\": \"VGT2\", \"from\": \"FWA4\", \"rows\": 17}], \"ocrSource\": \"5E30558D@529FEF40.E0117C6A00000000.jpg\"}}, \"shipping_line_unmatched\": \"ZIMU\"}','2026-08-12 06:25:30.040','2026-08-17 07:26:19.318'),(131,100,'{\"F_ETA\": null, \"F_ETD\": null, \"F_Dock\": \"\", \"F_Classis\": 0, \"F_LoadPort\": \"\", \"F_TransMode\": 0, \"F_BLCopyCode\": \"EGLV143661466958\", \"F_MemoRemark\": \"封号:EMCEJJ0635\", \"F_ContainerNo\": \"TRHU8524770\", \"F_Instruction\": \"\", \"F_OperationType\": 2, \"F_ShippingLineId\": \"\", \"container_no_valid\": true}','[]','{\"note\": \"no spreadsheet\", \"do_files\": [\"TRHU8524770-DO-1.pdf\"], \"mail_record\": {\"kind\": \"BL_FORECAST\", \"table\": {\"rows\": [], \"columns\": [\"柜号\", \"外箱图片\", \"件数\", \"渠道\", \"总体积\", \"毛重\", \"派送地址\", \"FBA ID\", \"Amazon reference ID\", \"仓库ID\", \"最早送仓日期\", \"最晚送仓日期\", \"分货标识/箱唛\", \"备注\"]}, \"lineage\": {\"source\": \"plus_template\", \"plus_payload\": \"柜号:TRHU8524770+提单号:EGLV143661466958+封号:EMCEJJ0635+YWZD+不带车架+整柜直送\"}, \"modules\": {\"bl_no\": \"EGLV143661466958\", \"classis\": 0, \"service\": \"不带车架\", \"memo_bits\": [\"封号:EMCEJJ0635\"], \"ops_hints\": [\"直送\"], \"container_no\": \"TRHU8524770\", \"customer_name\": \"YWZD\", \"shipping_line_hint\": \"EGLV\"}, \"summary\": \"上传DO(含预报模板):YWZD · 提单 EGLV143661466958 · 柜号 TRHU8524770 · 不带车架;货件表待附件。\"}, \"shipping_line_hint\": \"EGLV\", \"shipping_line_unmatched\": \"EGLV\"}','2026-08-12 06:25:40.175','2026-08-17 02:48:02.508'),(269,252,'{\"F_ETA\": null, \"F_ETD\": null, \"F_Dock\": \"\", \"F_LoadPort\": \"\", \"F_TransMode\": 0, \"F_BLCopyCode\": \"BAZUS001787430\", \"F_MemoRemark\": \"\", \"F_ContainerNo\": \"TIIU8073522\", \"F_Instruction\": \"\", \"F_ShippingLineId\": \"\", \"container_no_valid\": true}','[]','{\"note\": \"no spreadsheet\", \"do_files\": [], \"mail_record\": {\"kind\": \"WORK_ORDER\", \"table\": {\"rows\": [{\"动作\": \"工单\", \"原仓\": \"\", \"备注\": \"Fw: 转发:TIIU8073522-90022\", \"柜号\": \"TIIU8073522\", \"目标仓\": \"\", \"快递单号\": \"\"}], \"columns\": [\"柜号\", \"动作\", \"原仓\", \"目标仓\", \"快递单号\", \"备注\"]}, \"lineage\": {\"source\": \"work_order\", \"express_nos\": [], \"subject_only\": false, \"heuristic_container\": \"TIIU8073522\"}, \"modules\": {\"container_no\": \"TIIU8073522\"}, \"summary\": \"工单邮件(主题):待识别动作;柜号 TIIU8073522。\", \"work_order_actions\": []}, \"shipping_line_hint\": \"BAZU\", \"shipping_line_unmatched\": \"BAZU\"}','2026-08-17 01:31:28.557','2026-08-17 01:31:28.557'),(270,253,'{\"F_ETA\": \"2026-07-13\", \"F_ETD\": null, \"F_Dock\": \"\", \"F_Classis\": 0, \"F_LoadPort\": \"\", \"F_TransMode\": 0, \"F_BLCopyCode\": \"MATS4583030000\", \"F_MemoRemark\": \"LINK EVER INC\", \"F_ContainerNo\": \"MATU2745683\", \"F_Instruction\": \"\", \"F_OperationType\": 0, \"F_ShippingLineId\": \"\", \"container_no_valid\": true}','[{\"F_PO\": null, \"F_CBM\": 0.09, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G5XJLV4\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": \"转POC2\", \"F_Weight\": 10.69, \"F_Address\": \"6251 Pioneer Trail\\nLOS LUNAS NW 87031\\nABQ2\", \"F_Contact\": null, \"F_FBACode\": \"ABQ2\", \"F_ZipCode\": null, \"row_index\": 1, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799251\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3NHUDF7E\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.21, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GLHTHQ2\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 85.08, \"F_Address\": \"33333 LBJ FWY \\nDallas TX 75241-7203 \\nFTW1\", \"F_Contact\": null, \"F_FBACode\": \"FTW1\", \"F_ZipCode\": null, \"row_index\": 2, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798986\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5ELVT8ED\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.41, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 7, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GLHN9Y6\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": \"转VGT2\", \"F_Weight\": 136.89, \"F_Address\": \"9798 Smith Road \\nFORT WAYNE IN 46809-9771\\nFWA4\", \"F_Contact\": null, \"F_FBACode\": \"FWA4\", \"F_ZipCode\": null, \"row_index\": 3, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798997\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"8BMWZVFJ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.17, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GDNX9Z8\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 64.6, \"F_Address\": \"15301 West Northern Avenue\\nGlendale AZ 85307\\nGEU2\", \"F_Contact\": null, \"F_FBACode\": \"GEU2\", \"F_ZipCode\": null, \"row_index\": 4, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798987\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2C9DKUZC\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.69, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FY3BJZZ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 60.89, \"F_Address\": \"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\", \"F_Contact\": null, \"F_FBACode\": \"GYR2\", \"F_ZipCode\": null, \"row_index\": 5, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798993\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4MSJ54NC\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.58, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G2KYS5B\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 70.06, \"F_Address\": \"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\", \"F_Contact\": null, \"F_FBACode\": \"GYR2\", \"F_ZipCode\": null, \"row_index\": 6, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798998\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5N6C5EWC\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.58, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1T2D2H\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 70.06, \"F_Address\": \"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\", \"F_Contact\": null, \"F_FBACode\": \"GYR2\", \"F_ZipCode\": null, \"row_index\": 7, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798998\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5LUCLWYC\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.58, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G8RHPBN\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 70.06, \"F_Address\": \"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\", \"F_Contact\": null, \"F_FBACode\": \"GYR2\", \"F_ZipCode\": null, \"row_index\": 8, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798998\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2T81OS4Z\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.58, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1RFNM1\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 70.06, \"F_Address\": \"17341 W MINNEZONA AVE \\nGOODYEAR, AZ 85395-7672\\nGYR2\", \"F_Contact\": null, \"F_FBACode\": \"GYR2\", \"F_ZipCode\": null, \"row_index\": 9, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798998\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5EWISDAX\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.22, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G29Q01R\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 7.02, \"F_Address\": \"8181 W ROOSEVELT ST\\nPHOENIX, AZ 85043-2356\\nGYR3\", \"F_Contact\": null, \"F_FBACode\": \"GYR3\", \"F_ZipCode\": null, \"row_index\": 10, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798982\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"883JZRQF\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.34, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1WHFDM\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 26.33, \"F_Address\": \"8181 W ROOSEVELT ST\\nPHOENIX, AZ 85043-2356\\nGYR3\", \"F_Contact\": null, \"F_FBACode\": \"GYR3\", \"F_ZipCode\": null, \"row_index\": 11, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798988\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6Z5X8QUO\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.85, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G2JRFPD\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 116.81, \"F_Address\": \"8181 W ROOSEVELT ST\\nPHOENIX, AZ 85043-2356\\nGYR3\", \"F_Contact\": null, \"F_FBACode\": \"GYR3\", \"F_ZipCode\": null, \"row_index\": 12, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799003\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1JHNCNCN\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.85, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1VL0QB\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 116.81, \"F_Address\": \"8181 W ROOSEVELT ST\\nPHOENIX, AZ 85043-2356\\nGYR3\", \"F_Contact\": null, \"F_FBACode\": \"GYR3\", \"F_ZipCode\": null, \"row_index\": 13, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799003\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7LG6QBSJ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.85, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FYXNB8R\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 116.81, \"F_Address\": \"8181 W ROOSEVELT ST\\nPHOENIX, AZ 85043-2356\\nGYR3\", \"F_Contact\": null, \"F_FBACode\": \"GYR3\", \"F_ZipCode\": null, \"row_index\": 14, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799003\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"29D62EDU\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.29, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G14DLY3\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 122.8, \"F_Address\": \"8181 W ROOSEVELT ST\\nPHOENIX, AZ 85043-2356\\nGYR3\", \"F_Contact\": null, \"F_FBACode\": \"GYR3\", \"F_ZipCode\": null, \"row_index\": 15, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799010\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"58Z8KBTV\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.29, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G13QGQH\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 122.8, \"F_Address\": \"8181 W ROOSEVELT ST\\nPHOENIX, AZ 85043-2356\\nGYR3\", \"F_Contact\": null, \"F_FBACode\": \"GYR3\", \"F_ZipCode\": null, \"row_index\": 16, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799010\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3C3P42MK\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.29, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 9, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G9P3YKL\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 122.8, \"F_Address\": \"8181 W ROOSEVELT ST\\nPHOENIX, AZ 85043-2356\\nGYR3\", \"F_Contact\": null, \"F_FBACode\": \"GYR3\", \"F_ZipCode\": null, \"row_index\": 17, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799010\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1LZ1KXIO\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.29, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FZ9ZF8L\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 122.8, \"F_Address\": \"8181 W ROOSEVELT ST\\nPHOENIX, AZ 85043-2356\\nGYR3\", \"F_Contact\": null, \"F_FBACode\": \"GYR3\", \"F_ZipCode\": null, \"row_index\": 18, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799010\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"48IIDD3B\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.45, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 8, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G7WHVS8\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 101.21, \"F_Address\": \"15525 Milner Road\\nHOUSTON, TX 77032\\nIAH3\", \"F_Contact\": null, \"F_FBACode\": \"IAH3\", \"F_ZipCode\": null, \"row_index\": 19, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799022\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"69TK2AFZ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.52, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G16PCF5\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 54.4, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 20, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798999\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3XO6AEVF\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.52, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G14JB60\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 54.4, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 21, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798999\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6FJ7BI4H\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.52, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G8NDVBC\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 54.4, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 22, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798999\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1CIFQ77S\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.64, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G2G8W6B\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 102.94, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 23, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799000\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1NSPTFYX\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.64, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FZ0QX7C\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 102.94, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 24, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799000\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1HQ5X13O\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.82, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1V0TTZ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 87.51, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 25, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799006\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3NF9SXTT\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.82, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FY5WZP6\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 87.51, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 26, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799006\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5DMRVMCM\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.29, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G284KD4\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 9.06, \"F_Address\": \"1151 S GRAHAM RD \\nGREENWOOD, IN 46143-7830 \\nIND9\", \"F_Contact\": null, \"F_FBACode\": \"IND9\", \"F_ZipCode\": null, \"row_index\": 27, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799017\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4F5I1TKH\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.43, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G28SWW9\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 13.08, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 28, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798995\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"699V15ZR\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.35, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G2GQL4W\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 189.9, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 29, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799011\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2LT65VBU\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.35, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G2DSQ7B\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 189.9, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 30, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799011\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6OJY8NYJ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.35, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G29T4G3\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 189.9, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 31, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799011\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"84TT9TBQ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.35, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G27WDCX\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 189.9, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 32, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799011\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"48CW4JXI\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.35, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1TYNQB\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 189.9, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 33, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799011\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1N4AKIXD\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.35, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FZ1Y6SX\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 189.9, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 34, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799011\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6Q7FGA7F\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 11, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G9Q89V3\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 35, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5QUXNLZN\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G2DQ29B\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 36, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7S1MYYVY\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G28M1SR\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 37, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"68Y3VDJO\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G238HXC\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 38, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5KXHN4MB\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1VGDFP\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 39, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"52Q7B9HC\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1V44BD\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 40, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1135PBIQ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1GFCX0\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 41, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2OXRZ4SI\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G16GJGV\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 42, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"25CMAT7G\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G143F79\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 43, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4EEQ35LD\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FZ76LC6\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 44, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1HXSD48A\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FVL6R32\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 45, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4XA7NTQK\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FVJLD51\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 46, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5DO2TAWV\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 2.7, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FTBFN91\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 275.62, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 47, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799012\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"43BGF7EN\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.76, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 10, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FY67M7B\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 152.26, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 48, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799013\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"15TZ1T1X\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.76, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1V4LQ6\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 152.26, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 49, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799013\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"34LVN2SW\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.76, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FY65GV8\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 152.26, \"F_Address\": \"12300 Bermuda Road\\nHENDERSON, NV 89044-8746\\nLAS1\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 50, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799013\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7TVRS88P\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.19, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G24X0MS\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 25.52, \"F_Address\": \"11263 Oleander Ave\\nFONTANA,CA,92337-7441\\nLAX9\", \"F_Contact\": null, \"F_FBACode\": \"LAX9\", \"F_ZipCode\": null, \"row_index\": 51, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798984\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7M3XX9JC\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.22, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1G8MR2\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 17.9, \"F_Address\": \"11263 Oleander Ave\\nFONTANA,CA,92337-7441\\nLAX9\", \"F_Contact\": null, \"F_FBACode\": \"LAX9\", \"F_ZipCode\": null, \"row_index\": 52, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799015\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2NK8YVOW\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.05, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FY3DF92\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 6.55, \"F_Address\": \"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\", \"F_Contact\": null, \"F_FBACode\": \"LGB8\", \"F_ZipCode\": null, \"row_index\": 53, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798979\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3VQEGDZY\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.74, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G2K978G\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 76.24, \"F_Address\": \"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\", \"F_Contact\": null, \"F_FBACode\": \"LGB8\", \"F_ZipCode\": null, \"row_index\": 54, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798992\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"7FSDEM1F\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.74, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G254ZZJ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 76.24, \"F_Address\": \"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\", \"F_Contact\": null, \"F_FBACode\": \"LGB8\", \"F_ZipCode\": null, \"row_index\": 55, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798992\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4MZFOXEB\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.91, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FVHRHHG\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 94.6, \"F_Address\": \"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\", \"F_Contact\": null, \"F_FBACode\": \"LGB8\", \"F_ZipCode\": null, \"row_index\": 56, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799009\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3XH83VDQ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.91, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G2DT8DL\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 94.6, \"F_Address\": \"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\", \"F_Contact\": null, \"F_FBACode\": \"LGB8\", \"F_ZipCode\": null, \"row_index\": 57, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799009\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3IVEY4YR\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.91, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G26NN7Q\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 94.6, \"F_Address\": \"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\", \"F_Contact\": null, \"F_FBACode\": \"LGB8\", \"F_ZipCode\": null, \"row_index\": 58, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799009\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"8VIRS8UP\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.91, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G24RCVC\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 94.6, \"F_Address\": \"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\", \"F_Contact\": null, \"F_FBACode\": \"LGB8\", \"F_ZipCode\": null, \"row_index\": 59, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799009\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6OTXKP2N\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.91, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FZ6HVJ5\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 94.6, \"F_Address\": \"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\", \"F_Contact\": null, \"F_FBACode\": \"LGB8\", \"F_ZipCode\": null, \"row_index\": 60, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799009\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1VYVGBVK\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.91, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FVJGF90\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 94.6, \"F_Address\": \"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\", \"F_Contact\": null, \"F_FBACode\": \"LGB8\", \"F_ZipCode\": null, \"row_index\": 61, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799009\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5IWGPFWO\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.91, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FTHJBZ2\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 94.6, \"F_Address\": \"1568 N Linden Ave\\nRialto CA 92376-8624\\nLGB8\", \"F_Contact\": null, \"F_FBACode\": \"LGB8\", \"F_ZipCode\": null, \"row_index\": 62, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799009\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3JALLX3W\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 4.88, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 59, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GLHP13Y\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 513.95, \"F_Address\": \"3200 Fulkerth Road\\nTURLOCK CA 95380-9684\\nMCE1\", \"F_Contact\": null, \"F_FBACode\": \"MCE1\", \"F_ZipCode\": null, \"row_index\": 63, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798498\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"33S3YS4B\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.42, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 9, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G7YQMSJ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 112.79, \"F_Address\": \"250 EMERALD DR\\nJoliet,IL 60433-3280\\nMDW2\", \"F_Contact\": null, \"F_FBACode\": \"MDW2\", \"F_ZipCode\": null, \"row_index\": 64, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799023\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"664HX25K\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.36, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FYS8ZBK\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 38.6, \"F_Address\": \"5408 Express Avenue\\nSHAFTER ca 93263\\nMIT2\", \"F_Contact\": null, \"F_FBACode\": \"MIT2\", \"F_ZipCode\": null, \"row_index\": 65, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798983\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5X61G1UR\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.36, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FYRDMKX\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 38.6, \"F_Address\": \"5408 Express Avenue\\nSHAFTER ca 93263\\nMIT2\", \"F_Contact\": null, \"F_FBACode\": \"MIT2\", \"F_ZipCode\": null, \"row_index\": 66, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798983\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"61LL4XVS\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.31, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G29H96J\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 68.9, \"F_Address\": \"5408 Express Avenue\\nSHAFTER ca 93263\\nMIT2\", \"F_Contact\": null, \"F_FBACode\": \"MIT2\", \"F_ZipCode\": null, \"row_index\": 67, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798989\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"47BZUVSU\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.31, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FYPNMM0\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 68.9, \"F_Address\": \"5408 Express Avenue\\nSHAFTER ca 93263\\nMIT2\", \"F_Contact\": null, \"F_FBACode\": \"MIT2\", \"F_ZipCode\": null, \"row_index\": 68, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798989\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"46UVFURW\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.09, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FYN9GKY\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 11.71, \"F_Address\": \"5408 Express Avenue\\nSHAFTER ca 93263\\nMIT2\", \"F_Contact\": null, \"F_FBACode\": \"MIT2\", \"F_ZipCode\": null, \"row_index\": 69, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799014\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"53K3D3DF\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.33, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GLHZ239\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 116.77, \"F_Address\": \"4412 W 300 N \\nGREENFIELD, IN 46140-7099\\nMQJ1\", \"F_Contact\": null, \"F_FBACode\": \"MQJ1\", \"F_ZipCode\": null, \"row_index\": 70, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798996\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1GA82U6G\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.15, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G2BXSRZ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 4.48, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 71, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798980\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3NWPC7EJ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.23, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1RNF3W\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 17.39, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 72, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798981\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"15OZBVLD\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.41, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G152S4P\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 41.38, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 73, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798990\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4166MLPK\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.41, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1VW210\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 41.38, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 74, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798990\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2FVYP8YP\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.41, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G1RR6P3\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 41.38, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 75, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798990\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6PW587KR\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.41, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G198QHD\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 41.38, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 76, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798990\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3NI1UI8Z\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.96, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G2J5G1Y\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 117.53, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 77, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799008\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1PNHFJTM\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.96, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FYWR9QL\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 117.53, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 78, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799008\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1XB8HK5K\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.96, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G7WQ1J4\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 117.53, \"F_Address\": \"24300 Nandina Ave\\nMoreno Valley CA 92551-9534\\nONT8\", \"F_Contact\": null, \"F_FBACode\": \"ONT8\", \"F_ZipCode\": null, \"row_index\": 79, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799008\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4RKVIM1Y\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.51, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FVL1FT5\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 62.46, \"F_Address\": \"64165 19th AVE\\nDESERT HOT SPRINGS,CA 92240\\nPSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 80, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799004\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2J5ODH7F\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.51, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G25ZZ55\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 62.46, \"F_Address\": \"64165 19th AVE\\nDESERT HOT SPRINGS,CA 92240\\nPSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 81, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799004\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"8IPW1K5O\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.51, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G26KDN0\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 62.46, \"F_Address\": \"64165 19th AVE\\nDESERT HOT SPRINGS,CA 92240\\nPSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 82, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799004\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"51TCCG5B\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.51, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FVJW8LR\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 62.46, \"F_Address\": \"64165 19th AVE\\nDESERT HOT SPRINGS,CA 92240\\nPSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 83, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799004\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3TBHARWJ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.51, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FTB79NK\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 62.46, \"F_Address\": \"64165 19th AVE\\nDESERT HOT SPRINGS,CA 92240\\nPSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 84, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799004\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"415WOB3V\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.96, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G5P7BSN\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 118.05, \"F_Address\": \"64165 19th AVE\\nDESERT HOT SPRINGS,CA 92240\\nPSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 85, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799005\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"8AFBLWQQ\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.96, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G2J49SD\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 118.05, \"F_Address\": \"64165 19th AVE\\nDESERT HOT SPRINGS,CA 92240\\nPSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 86, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799005\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3EJ9NB7L\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.96, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G25PWLS\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 118.05, \"F_Address\": \"64165 19th AVE\\nDESERT HOT SPRINGS,CA 92240\\nPSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 87, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799005\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"6Q2TK1NF\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.05, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FY4FTQH\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 6.55, \"F_Address\": \"64165 19th AVE\\nDESERT HOT SPRINGS,CA 92240\\nPSP3\", \"F_Contact\": null, \"F_FBACode\": \"PSP3\", \"F_ZipCode\": null, \"row_index\": 88, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799018\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1I1J3Q2M\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.44, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G5P61Y5\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 75.58, \"F_Address\": \"3388 S Cactus Ave \\nBLOOMINGTON, CA 92316-3819\\nSBD1\", \"F_Contact\": null, \"F_FBACode\": \"SBD1\", \"F_ZipCode\": null, \"row_index\": 89, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798991\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4OSVILWU\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.44, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GLL7LK9\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 75.58, \"F_Address\": \"3388 S Cactus Ave \\nBLOOMINGTON, CA 92316-3819\\nSBD1\", \"F_Contact\": null, \"F_FBACode\": \"SBD1\", \"F_ZipCode\": null, \"row_index\": 90, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798991\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1DCOLANI\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.67, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19FY4ZNLK\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 51.01, \"F_Address\": \"3388 S Cactus Ave \\nBLOOMINGTON, CA 92316-3819\\nSBD1\", \"F_Contact\": null, \"F_FBACode\": \"SBD1\", \"F_ZipCode\": null, \"row_index\": 91, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798994\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4HPGWNTM\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.16, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19GP3CYRZ\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 32, \"F_Address\": \"4700 Wilbur Ave\\nOAKLEY CA 94561-2677\\nSCK8\", \"F_Contact\": null, \"F_FBACode\": \"SCK8\", \"F_ZipCode\": null, \"row_index\": 92, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799252\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"689I7TQP\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.75, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G8N0CQ6\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 60.4, \"F_Address\": \"3923 S B ST \\nStockton, CA 95206-8202\\nSMF3\", \"F_Contact\": null, \"F_FBACode\": \"SMF3\", \"F_ZipCode\": null, \"row_index\": 93, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799001\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5CKNJXTR\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.75, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G9QGG8B\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 60.4, \"F_Address\": \"3923 S B ST \\nStockton, CA 95206-8202\\nSMF3\", \"F_Contact\": null, \"F_FBACode\": \"SMF3\", \"F_ZipCode\": null, \"row_index\": 94, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799001\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5AIVDEQL\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.31, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G8Q83JB\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 20.9, \"F_Address\": \"6401 Howdy Wells Ave\\nLAS VEGAS, NV 89115-2013\\nVGT2\", \"F_Contact\": null, \"F_FBACode\": \"VGT2\", \"F_ZipCode\": null, \"row_index\": 95, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001798985\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"2FN67G7S\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.11, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA19G5MMF8R\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 12.64, \"F_Address\": \"6401 Howdy Wells Ave\\nLAS VEGAS, NV 89115-2013\\nVGT2\", \"F_Contact\": null, \"F_FBACode\": \"VGT2\", \"F_ZipCode\": null, \"row_index\": 96, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"BAZUS001799016\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"1K2YB9GA\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 97, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798965\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 45, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 98, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798758\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 99, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798964\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 32, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 100, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001799096\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 101, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798956\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 7, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 102, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798967\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 103, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798963\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 34, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 104, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798509\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 105, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798958\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 106, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798975\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 16, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 107, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798972\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 108, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798839\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 109, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001799276\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 19, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 110, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798973\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 111, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798977\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 112, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798974\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 113, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001799021\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 114, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798960\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 115, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798907\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 8, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 116, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798969\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 8, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 117, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798968\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 16, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 118, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798971\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 119, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798959\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 120, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798957\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 121, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798961\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 122, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798978\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 123, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001799277\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 124, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001799250\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 125, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798966\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 10, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 126, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001798970\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 19.26, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 3738.01, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 127, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"BAZUS001784096\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 128, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"701-9431775-3316232\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 129, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"701-3500883-6431458-GX231\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 130, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"YWKJ08931781843847702\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 131, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21823919-1857665-CFB\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 132, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21816933-1860593-TCG\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 133, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21816741-1860546-GT2\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 134, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21837573-1860515-OHT\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 135, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21837573-1860514-XPB\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 136, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21819226-1857494-TDV\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 137, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21819226-1857493-FWK\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 138, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133088690-0618064749\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 139, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133949018-0618061639\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 140, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134205506-0620072947\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 141, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134205506-0620074545\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 142, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134044455-0620012150\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 143, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134085218-0619090720\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 144, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134053893-0620125147\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 145, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133456455-0618124341\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 146, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134129208-0619061927\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 147, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133944511-0619062748\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 148, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133964124-0619074745\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 149, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133894404-0619064717\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 150, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133943173-0618080526\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 151, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134250168-0619045033\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 152, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133745670-0620064727\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 153, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132211652-0620013043\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 154, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133366174-0620071430\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 155, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133745670-0620064507\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 156, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133366174-0620070748\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 157, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133366174-0620071556\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 158, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133917008-0620114532\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 159, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133745670-0620051514\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 160, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132869024-0617104338\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 161, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133960050-0619081150\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 162, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133341903-0620055304\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 163, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133862427-0619125737\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 164, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134126836-0618105142\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 165, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133681253-0620012744\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 166, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133880317-0619044627\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 167, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134245737-0619044943\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 168, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132865782-0619055816\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 169, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133226362-0619063425\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 170, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"701-6923063-9058635RC8759\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 171, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"2026061504333537\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 172, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134075864-0622094851\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 173, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134075864-0622094859\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 174, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134075864-0622094928\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 175, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134075864-0622094935\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 176, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134084730-0622095851\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 177, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"2026061306143160\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 178, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133561189-0622113416\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 179, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133914002-0622093637\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 180, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132693182-0622021121\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 181, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"131975629-0622110948\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 182, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132557484-0622115513\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 183, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"131975629-0622055733\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 184, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"131272618-0622094640\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 185, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"130686796-0622094707\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 186, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132049373-0622094730\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 187, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132017700-0622110901\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 188, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"131989745-0622115434\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 189, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132885490-0622120401\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 190, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"122898549-0622021146\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 191, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"131548505-0622060116\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 192, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132235578-0622060147\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 193, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133764457-0617105816\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 194, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133741541-0622055859\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 195, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134021023-0622055816\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 196, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132499196-0622055945\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 197, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"SYK69791781746180689\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 198, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"LSWL10881781504382180\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 199, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"QZWB58011781697394006\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 200, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"LYC13501781918960559\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 201, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"BAZCA001798787\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 202, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132669080-0623093739\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 203, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134118579-0625111631\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 204, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134118579-0625111642\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 205, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"114-6726539-7306645\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 206, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21816798-1861196-DP6\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 207, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21820473-1862347-HHB\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 208, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21824574-1862331-47X\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 209, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21825181-1860857-XRV\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 210, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21828600-1861507-9VX\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 211, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21829634-1861509-CRV\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 212, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134083845-0619055926\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 213, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133933664-0620070844\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 214, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134234620-0620061557\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 215, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21813418-1857448-WEH\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 216, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133640894-0619014659\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 217, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133909480-0619095248\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 218, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134063460-0619094839\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 219, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134272789-0620111140\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 220, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134314697-0620075155\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 221, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133248377-0620064013\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 222, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21816902-1857621-HA1\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 223, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134253188-0620080546\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 224, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133769755-0619052559\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 225, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133889830-0619101929\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 226, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21828507-1860650-ALQ\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 227, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21813185-1851111-N1X\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 228, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21795111-1859961-P1I\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 229, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133870275-0620084259\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 230, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133905652-0620121100\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 231, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133289771-0618054220\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 232, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"21809991-1860620-ZH7\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 233, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133417628-0618070935\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 234, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133570254-0620103308\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 235, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133980990-0619053951\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 236, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133743273-0620125459\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 237, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133759852-0620053342\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 238, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134614991-0620085121\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 239, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133759852-0620053659\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 240, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134125886-0619065540\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 241, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134084838-0619064907\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 242, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133933876-0620051627\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 243, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133078911-0620023811\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 244, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133755549-0618054106\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 245, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133792914-0618061951\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 246, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134046602-0620063913\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 247, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133248377-0620062901\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 248, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132773956-0617012158\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 249, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133743890-0617085434\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 250, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133294960-0615105114\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 251, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133221723-0620091453\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 252, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134216294-0620074449\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 253, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132936951-0620111834\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 254, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133788723-0620101943\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 255, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133078210-0620085658\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 256, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133392376-0620081703\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 257, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133946666-0620075450\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 258, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133716550-0618013200\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 259, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133302274-0620012745\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 260, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133452702-0620105401\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 261, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133392376-0620081834\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 262, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134048777-0619084015\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 263, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133606147-0618115057\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 264, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133947555-0620052914\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 265, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"131337098-0618064450\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 266, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133313107-0618083508\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 267, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133566609-0618055725\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 268, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134115897-0619013841\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 269, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134255176-0620011805\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 270, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133452702-0620110223\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 271, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133975736-0619014009\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 272, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133911122-0619123801\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 273, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134233310-0620091131\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 274, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133848419-0620092333\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 275, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133319090-0619010050\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 276, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133742367-0620075922\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 277, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133912624-0620012309\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 278, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"XMK75D026595\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 279, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"XMK75D026613\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 280, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133452816-0616063138\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 281, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133456456-0615013027\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 282, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133549587-0617104905\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 283, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133588109-0615013335\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 284, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134023821-0617105613\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 285, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"133894482-0616023722\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 286, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134054690-0617100645\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 287, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134054690-0617100706\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 288, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134054690-0617100718\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 289, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134067019-0617110310\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 290, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134117812-0617024334\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 291, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132365897-0622060036\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 292, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"132530670-0622115627\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 293, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"XM17BT5045172\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 294, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134218731-0622044311\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 295, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"YWKJ08931782038535616\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 296, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"MST37601782114583537\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 297, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"XM27NGE010289R1\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 298, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"111-5737992-8731430\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 299, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"114-1695672-3660245\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 300, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"113-9259578-7638640R36CC6\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 301, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"113-9259578-7638640R38267\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 302, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"113-9259578-7638640R6FD8A\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 303, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"113-1942766-0911457\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 304, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"XMX45S022242\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 305, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134468725-0621104419\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 306, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"134749412-0623023246\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 307, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"LYC13501781776045062\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 308, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"QZWB58011782106974941\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 309, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"QZWB58011781767978658\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 310, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"QZWB58011781922606005\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 311, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"XM2CERL010101\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 312, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"XM2CERL010128\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 313, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"QCJJ34861781749100045\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 15.54, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 2022.71, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 314, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"QCJJ34861781763827674\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 315, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"113-3408484-4384211\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 316, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"P29B6ZCC33166\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 317, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"P29D6ZCC33680\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 318, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"111-7143888-9005863\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 319, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"P29E6ZCC33770\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 320, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"P29F6ZCC34224\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 321, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"112-8254021-3837809-1\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 322, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"P29B6ZCC33153\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 323, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"111-7052958-1716203\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 324, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"P29D6ZCC33534\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 325, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"114-4860871-2073007\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.38, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 1, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 46.8, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 326, \"row_status\": \"VALID\", \"channel_raw\": \"USPS\", \"F_ShipmentID\": \"134428549-0619113726\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"USPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": null, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 0, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": null, \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": null, \"warnings\": [\"件数请填纯数值\", \"总体积请填纯数值\", \"毛重请填纯数值\"], \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 327, \"row_status\": \"INVALID\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"invalid_reasons\": [\"渠道缺失\", \"件数无效\", \"总体积缺失\", \"毛重缺失\"], \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}]','{\"sheet\": \"卡派资料\", \"row_count\": 327, \"mail_record\": {\"kind\": \"WORK_ORDER\", \"table\": {\"rows\": [{\"动作\": \"转仓\", \"原仓\": \"ABQ2\", \"备注\": \"货件 1 行\", \"柜号\": \"MATU2745683\", \"目标仓\": \"POC2\", \"快递单号\": \"\"}, {\"动作\": \"转仓\", \"原仓\": \"FWA4\", \"备注\": \"货件 1 行\", \"柜号\": \"MATU2745683\", \"目标仓\": \"VGT2\", \"快递单号\": \"\"}], \"columns\": [\"柜号\", \"动作\", \"原仓\", \"目标仓\", \"快递单号\", \"备注\"]}, \"lineage\": {\"source\": \"work_order\", \"express_nos\": [], \"subject_only\": false, \"heuristic_container\": \"MATU2745683\"}, \"modules\": {\"ops_hints\": [\"转仓\"], \"container_no\": \"MATU2745683\"}, \"summary\": \"工单邮件(主题):转仓;柜号 MATU2745683。\", \"work_order_actions\": [\"转仓\"]}, \"usedFilename\": \"柜号:MATU2745683卡派资料.xlsx\", \"sheetFallback\": false, \"shipping_line_hint\": \"MATS\", \"soft_limit_exceeded\": false, \"shipping_line_unmatched\": \"MATS\"}','2026-08-17 01:31:29.645','2026-08-17 01:31:29.645'),(271,254,'{\"F_ETA\": \"2026-06-28\", \"F_ETD\": null, \"F_Dock\": \"\", \"F_TransMode\": 0, \"F_BLCopyCode\": \"WHL063G550810\", \"F_MemoRemark\": \"FedEx-29件 UPS-102件 亚马逊卡派 289件 私人地址-166件 拦截-209件 拆柜清单 | Fw: 转发:派送要求更新: 拆柜清单更新: DO请查收\", \"F_ContainerNo\": \"WHSU5574991\", \"F_Instruction\": \"船名航次 OOCL SINGAPORE / 065W\", \"container_no_valid\": true}','[]','{\"note\": \"ops_subject_only\", \"skip_body\": true, \"mail_record\": {\"kind\": \"WORK_ORDER\", \"table\": {\"rows\": [{\"动作\": \"拆柜清单\", \"原仓\": \"\", \"备注\": \"Fw: 转发:派送要求更新 拆柜清单更新,DO请查收 拆柜清单更新: WHSU5574991+WHL063G550810+船名航次:OOCL SINGAPORE / 065W+ETA:6/28+FedEx-29件 UPS-102件 亚马逊卡\", \"柜号\": \"WHSU5574991\", \"目标仓\": \"\", \"快递单号\": \"\"}], \"columns\": [\"柜号\", \"动作\", \"原仓\", \"目标仓\", \"快递单号\", \"备注\"]}, \"lineage\": {\"source\": \"work_order\", \"express_nos\": [], \"subject_only\": true, \"channel_summary\": [], \"heuristic_container\": \"WHSU5574991\"}, \"modules\": {\"eta\": \"2026-06-28\", \"bl_no\": \"WHL063G550810\", \"service\": \"FedEx-29件 UPS-102件 亚马逊卡派 289件 私人地址-166件 拦截-209件 拆柜清单 | Fw: 转发:派送要求更新: 拆柜清单更新: DO请查收\", \"ops_hints\": [\"拆柜清单\", \"派送要求\"], \"container_no\": \"WHSU5574991\", \"vessel_voyage\": \"OOCL SINGAPORE / 065W\", \"shipping_line_hint\": \"WHL\"}, \"summary\": \"工单邮件(主题):拆柜清单、派送要求;提单 WHL063G550810 · 柜号 WHSU5574991 · 船名航次 OOCL SINGAPORE / 065W · ETA 2026-06-28。\", \"work_order_actions\": []}, \"channel_summary\": [], \"skip_attachments\": true, \"ops_subject_payload\": \"WHSU5574991+WHL063G550810+船名航次:OOCL SINGAPORE / 065W+ETA:6/28+FedEx-29件 UPS-102件 亚马逊卡派 289件 私人地址-166件 拦截-209件 拆柜清单 | Fw: 转发:派送要求更新: 拆柜清单更新: DO请查收+拆柜清单更新:\"}','2026-08-17 01:31:30.428','2026-08-17 01:31:30.428'),(272,255,'{\"F_ETA\": \"2026-05-15\", \"F_ETD\": \"2026-04-25\", \"F_Dock\": \"洛杉矶\", \"F_Classis\": 0, \"F_LoadPort\": \"\", \"F_TransMode\": 0, \"F_BLCopyCode\": \"WHLC027G597465\", \"F_MemoRemark\": \"新辰泽\", \"F_CabinetType\": \"40HQ\", \"F_ContainerNo\": \"WHSU8127240\", \"F_Instruction\": \"\", \"F_OperationType\": 0, \"F_ShippingLineId\": \"\", \"container_no_valid\": true}','[{\"F_PO\": null, \"F_CBM\": 5.14, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 44, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA16SQQ7PTF\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 567, \"F_Address\": \"仓库代码: LAS1\\n收件人: LAS1\\n公司名称: AMAZON COM SERVICES INC\\n联系电话: 0123456789\\n省/市/区: NV / HENDERSON /\\n邮政编码: 89044-8746\\n收货地址: 12300 Bermuda Road\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 1, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"6268357756\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"758SDXPH\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": \"2026-03-29\", \"F_Expected_DeliveryDateE\": \"2026-04-06\"}, {\"F_PO\": null, \"F_CBM\": 5.11, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 14, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA16SQTM0R1\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 562, \"F_Address\": \"仓库代码: LAS1\\n收件人: LAS1\\n公司名称: AMAZON COM SERVICES INC\\n联系电话: 0123456789\\n省/市/区: NV / HENDERSON /\\n邮政编码: 89044-8746\\n收货地址: 12300 Bermuda Road\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 2, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"6268357756\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"39MFHV4O\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": \"2026-03-29\", \"F_Expected_DeliveryDateE\": \"2026-04-06\"}, {\"F_PO\": null, \"F_CBM\": 0.45, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA16SRQFXY5\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 98, \"F_Address\": \"仓库代码: LAS1\\n收件人: LAS1\\n公司名称: AMAZON COM SERVICES INC\\n联系电话: 0123456789\\n省/市/区: NV / HENDERSON /\\n邮政编码: 89044-8746\\n收货地址: 12300 Bermuda Road\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 3, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"9926386665\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3ROVWIOM\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": \"2026-03-29\", \"F_Expected_DeliveryDateE\": \"2026-04-06\"}, {\"F_PO\": null, \"F_CBM\": 2.56, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA16SKZQCBB\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 756.32, \"F_Address\": \"仓库代码: LAS1\\n收件人: LAS1\\n公司名称: AMAZON COM SERVICES INC\\n联系电话: 0123456789\\n省/市/区: NV / HENDERSON /\\n邮政编码: 89044-8746\\n收货地址: 12300 Bermuda Road\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 4, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"20220729LAS139\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"4UWQROSH\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": \"2026-03-29\", \"F_Expected_DeliveryDateE\": \"2026-04-06\"}, {\"F_PO\": null, \"F_CBM\": 2.56, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 34, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA16SL031T3\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 756.32, \"F_Address\": \"仓库代码: LAS1\\n收件人: LAS1\\n公司名称: AMAZON COM SERVICES INC\\n联系电话: 0123456789\\n省/市/区: NV / HENDERSON /\\n邮政编码: 89044-8746\\n收货地址: 12300 Bermuda Road\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 5, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"20220729LAS139\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"5DQ3HYSM\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": \"2026-03-29\", \"F_Expected_DeliveryDateE\": \"2026-04-06\"}, {\"F_PO\": null, \"F_CBM\": 0.24, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 7, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA16SP4NBQR\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 128, \"F_Address\": \"仓库代码: LAS1\\n收件人: LAS1\\n公司名称: AMAZON COM SERVICES INC\\n联系电话: 0123456789\\n省/市/区: NV / HENDERSON /\\n邮政编码: 89044-8746\\n收货地址: 12300 Bermuda Road\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 6, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"822072867821\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"79VUM4FL\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": \"2026-03-29\", \"F_Expected_DeliveryDateE\": \"2026-04-06\"}, {\"F_PO\": null, \"F_CBM\": 0.48, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA16SRW4F6W\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 110.85, \"F_Address\": \"仓库代码: LAS1\\n收件人: LAS1\\n公司名称: AMAZON COM SERVICES INC\\n联系电话: 0123456789\\n省/市/区: NV / HENDERSON /\\n邮政编码: 89044-8746\\n收货地址: 12300 Bermuda Road\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 7, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"DGG803076\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"38JNRT2Q\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": \"2026-03-28\", \"F_Expected_DeliveryDateE\": \"2026-04-08\"}, {\"F_PO\": null, \"F_CBM\": 0.88, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 8, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA16SP4GFX0\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 166, \"F_Address\": \"仓库代码: LAS1\\n收件人: LAS1\\n公司名称: AMAZON COM SERVICES INC\\n联系电话: 0123456789\\n省/市/区: NV / HENDERSON /\\n邮政编码: 89044-8746\\n收货地址: 12300 Bermuda Road\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 8, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"JBHSF072803\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"8OSM83RO\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": \"2026-03-28\", \"F_Expected_DeliveryDateE\": \"2026-04-08\"}, {\"F_PO\": null, \"F_CBM\": 1.02, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA16SJZHL56\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 249, \"F_Address\": \"仓库代码: LAS1\\n收件人: LAS1\\n公司名称: AMAZON COM SERVICES INC\\n联系电话: 0123456789\\n省/市/区: NV / HENDERSON /\\n邮政编码: 89044-8746\\n收货地址: 12300 Bermuda Road\", \"F_Contact\": null, \"F_FBACode\": \"LAS1\", \"F_ZipCode\": null, \"row_index\": 9, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"YZ22080003\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": \"3ME4KJQV\", \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": \"2026-03-28\", \"F_Expected_DeliveryDateE\": \"2026-04-08\"}, {\"F_PO\": null, \"F_CBM\": 2.25, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 28, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"220729YT28NB\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": \"三票分开打托\", \"F_Weight\": 429, \"F_Address\": \"收件人: Erica Fabian\\n公司名称: Rapid Fulfillment\\n联系电话: 818-492-2760\\n省/市/区: CA / Pacoima /\\n邮政编码: 91331\\n收货地址: 12924 Pierce St\", \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 10, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.59, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 11, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"220730FK11\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 242, \"F_Address\": \"收件人: Erica Fabian\\n公司名称: Rapid Fulfillment\\n联系电话: 818-492-2760\\n省/市/区: CA / Pacoima /\\n邮政编码: 91331\\n收货地址: 12924 Pierce St\", \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 11, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.44, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 10, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"822072968180\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 136, \"F_Address\": \"收件人: Erica Fabian\\n公司名称: Rapid Fulfillment\\n联系电话: 818-492-2760\\n省/市/区: CA / Pacoima /\\n邮政编码: 91331\\n收货地址: 12924 Pierce St\", \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 12, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.76, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 16, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"FBA16SCKQQWC\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 144.31, \"F_Address\": \"收件人: Erica Fabian\\n公司名称: Rapid Fulfillment\\n联系电话: 818-492-2760\\n省/市/区: CA / Pacoima /\\n邮政编码: 91331\\n收货地址: 12924 Pierce St\", \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 13, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.4, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 6, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"XT0729JBSBD171\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 13, \"F_Address\": \"收件人: Erica Fabian\\n公司名称: Rapid Fulfillment\\n联系电话: 818-492-2760\\n省/市/区: CA / Pacoima /\\n邮政编码: 91331\\n收货地址: 12924 Pierce St\", \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 14, \"row_status\": \"VALID\", \"channel_raw\": \"卡派\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"TRUCK\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.23, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 3, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"1Z4X98060308009900\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 296, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 15, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.23, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 5, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"1Z4X98060321113067\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 296, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 16, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.23, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 8, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"1Z4X98060322191230\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 296, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 17, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.72, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 24, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"1Z4X98060306370433\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 425, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 18, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.5, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 7, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"1Z4X98060307290787\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 103, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 19, \"row_status\": \"VALID\", \"channel_raw\": \"UPS\", \"F_ShipmentID\": \"FBA16ST52H3N\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"UPS\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 3.43, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 40, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"889396190583\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 595.86, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 20, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"FBA16SV9CKD5\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 1.1, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 12, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"889396190584\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 210, \"F_Address\": null, \"F_Contact\": null, \"F_ZipCode\": null, \"row_index\": 21, \"row_status\": \"VALID\", \"channel_raw\": \"FEDEX\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"FEDEX\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.8, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 4, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"PICKUP-XCZ-001\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 120, \"warnings\": [\"存仓/留仓等无仓库ID,已用 HOLD 作为目的代码\"], \"F_Address\": \"收件人: Erica Fabian\\n公司名称: Rapid Fulfillment\\n联系电话: 818-492-2760\\n省/市/区: CA / Pacoima /\\n邮政编码: 91331\\n收货地址: 12924 Pierce St\", \"F_Contact\": null, \"F_FBACode\": \"HOLD\", \"F_ZipCode\": null, \"row_index\": 22, \"row_status\": \"VALID\", \"channel_raw\": \"自提\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"自提\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}, {\"F_PO\": null, \"F_CBM\": 0.3, \"F_Pcs\": null, \"F_SKU\": null, \"F_CTNS\": 2, \"F_City\": null, \"F_Email\": null, \"F_FBAID\": \"STORAGE-XCZ-001\", \"F_State\": null, \"F_Mobile\": null, \"F_Remark\": null, \"F_Weight\": 45, \"warnings\": [\"存仓/留仓等无仓库ID,已用 HOLD 作为目的代码\"], \"F_Address\": null, \"F_Contact\": null, \"F_FBACode\": \"HOLD\", \"F_ZipCode\": null, \"row_index\": 23, \"row_status\": \"VALID\", \"channel_raw\": \"存仓\", \"F_ShipmentID\": \"/\", \"F_Description\": null, \"F_EnglishName\": null, \"F_ReferenceId\": null, \"F_Transporter\": \"留仓\", \"F_Expected_DeliveryDateB\": null, \"F_Expected_DeliveryDateE\": null}]','{\"sheet\": \"Sheet1\", \"row_count\": 23, \"mail_record\": {\"kind\": \"WORK_ORDER\", \"table\": {\"rows\": [{\"动作\": \"拍照\", \"原仓\": \"LAS1\", \"备注\": \"6268357756\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"LAS1\", \"备注\": \"6268357756\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"LAS1\", \"备注\": \"9926386665\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"LAS1\", \"备注\": \"20220729LAS139\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"LAS1\", \"备注\": \"20220729LAS139\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"LAS1\", \"备注\": \"822072867821\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"LAS1\", \"备注\": \"DGG803076\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"LAS1\", \"备注\": \"JBHSF072803\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"LAS1\", \"备注\": \"YZ22080003\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"三票分开打托\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"/\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"/\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"/\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"/\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"/\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"/\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"/\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"/\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"FBA16ST52H3N\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"FBA16SV9CKD5\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"\", \"备注\": \"/\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"HOLD\", \"备注\": \"/\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}, {\"动作\": \"拍照\", \"原仓\": \"HOLD\", \"备注\": \"/\", \"柜号\": \"WHSU8127240\", \"目标仓\": \"\", \"快递单号\": \"\"}], \"columns\": [\"柜号\", \"动作\", \"原仓\", \"目标仓\", \"快递单号\", \"备注\"]}, \"lineage\": {\"source\": \"work_order\", \"express_nos\": [], \"subject_only\": false, \"heuristic_container\": \"WHSU8127240\"}, \"modules\": {\"port\": \"洛杉矶\", \"bl_no\": \"WHLC027G597465\", \"ops_hints\": [\"拍照\", \"贴标\"], \"container_no\": \"WHSU8127240\", \"customer_name\": \"新辰泽\", \"shipping_line_hint\": \"WHLC\"}, \"summary\": \"工单邮件(主题):拍照、贴标;提单 WHLC027G597465 · 柜号 WHSU8127240。\", \"work_order_actions\": [\"拍照\", \"贴标\"]}, \"usedFilename\": \"数据模版-新辰泽-WHSU8127240.xlsx\", \"sheetFallback\": true, \"shipping_line_hint\": \"WHLC\", \"soft_limit_exceeded\": false, \"shipping_line_unmatched\": \"WHLC\"}','2026-08-17 01:31:31.261','2026-08-17 01:31:31.261'); +/*!40000 ALTER TABLE `parse_result` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `sender_customer_map` +-- + +DROP TABLE IF EXISTS `sender_customer_map`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sender_customer_map` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `match_type` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL, + `match_value` varchar(320) COLLATE utf8mb4_unicode_ci NOT NULL, + `customer_code` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL, + `enabled` tinyint(1) NOT NULL DEFAULT '1', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_match` (`match_type`,`match_value`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sender_customer_map` +-- + +LOCK TABLES `sender_customer_map` WRITE; +/*!40000 ALTER TABLE `sender_customer_map` DISABLE KEYS */; +INSERT INTO `sender_customer_map` VALUES (1,'EMAIL','demo-customer@example.com','DEMO01',1),(2,'DOMAIN','partner.example.com','PARTNER01',1); +/*!40000 ALTER TABLE `sender_customer_map` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Dumping routines for database 'email_forecast' +-- +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2026-08-19 8:54:12 diff --git a/deploy/database/email_forecast.sql.gz b/deploy/database/email_forecast.sql.gz new file mode 100644 index 0000000..3b79124 Binary files /dev/null and b/deploy/database/email_forecast.sql.gz differ diff --git a/deploy/database/export-local.sh b/deploy/database/export-local.sh new file mode 100644 index 0000000..1463c45 --- /dev/null +++ b/deploy/database/export-local.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# 从本机 MySQL 导出完整数据 +# 用法:bash deploy/database/export-local.sh +# 依赖:mysqldump、项目根目录 .env(默认 localhost:7023) + +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +OUT_DIR="${ROOT}/deploy/database" +STAMP="$(date +%Y%m%d-%H%M%S)" +OUT_FILE="${OUT_DIR}/email_forecast-full-${STAMP}.sql" + +if [[ ! -f "${ROOT}/.env" ]]; then + echo "缺少 ${ROOT}/.env" + exit 1 +fi + +read_env() { + local key="$1" + local line + line="$(grep -E "^[[:space:]]*${key}=" "${ROOT}/.env" 2>/dev/null | tail -1 || true)" + [[ -z "$line" ]] && return + line="${line#*=}" + line="${line#"${line%%[![:space:]]*}"}" + line="${line%"${line##*[![:space:]]}"}" + echo "$line" +} + +DATABASE_URL="$(read_env DATABASE_URL)" +if [[ -z "$DATABASE_URL" ]]; then + echo ".env 中未设置 DATABASE_URL" + exit 1 +fi + +if ! command -v mysqldump >/dev/null 2>&1; then + echo "未找到 mysqldump,请安装 MySQL 客户端" + exit 1 +fi + +if [[ ! "$DATABASE_URL" =~ mysql://([^:]+):([^@]+)@([^:/]+):?([0-9]*)/([^?]+) ]]; then + echo "无法解析 DATABASE_URL: $DATABASE_URL" + exit 1 +fi + +DB_USER="${BASH_REMATCH[1]}" +DB_PASS="${BASH_REMATCH[2]}" +DB_HOST="${BASH_REMATCH[3]}" +DB_PORT="${BASH_REMATCH[4]:-3306}" +DB_NAME="${BASH_REMATCH[5]}" + +mkdir -p "$OUT_DIR" + +mysqldump \ + -h "$DB_HOST" \ + -P "$DB_PORT" \ + -u "$DB_USER" \ + -p"$DB_PASS" \ + --default-character-set=utf8mb4 \ + --single-transaction \ + --routines \ + --triggers \ + --set-gtid-purged=OFF \ + "$DB_NAME" > "$OUT_FILE" + +gzip -kf "$OUT_FILE" +echo "已导出: ${OUT_FILE}" +echo "已压缩: ${OUT_FILE}.gz" diff --git a/deploy/docker-compose.bt-host-mysql.yml b/deploy/docker-compose.bt-host-mysql.yml new file mode 100644 index 0000000..a4352af --- /dev/null +++ b/deploy/docker-compose.bt-host-mysql.yml @@ -0,0 +1,66 @@ +# 邮件预报 — 宝塔宿主机 MySQL + Docker 应用(无容器 MySQL) +# 服务器示例:192.168.2.14 代码目录:/home/project/yubao 对外端口:30327 +# +# 用法(在项目根目录 /home/project/yubao): +# cp deploy/.env.bt-host-mysql.example .env # 若根目录尚无 .env +# bash deploy/install-bt-host-mysql.sh + +name: yx-bt + +services: + web: + build: + context: .. + dockerfile: Dockerfile + image: yubao-app:latest + restart: unless-stopped + command: ["sh", "/app/scripts/docker-entrypoint-web.sh"] + env_file: + - ../.env + environment: + NODE_ENV: production + DATABASE_URL: ${DATABASE_URL} + SEED_ON_START: ${SEED_ON_START:-true} + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ../data:/app/data + ports: + - "0.0.0.0:${APP_PORT:-30327}:3100" + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "fetch('http://127.0.0.1:3100/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))", + ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 90s + networks: + - internal + + worker: + image: yubao-app:latest + restart: unless-stopped + command: ["pnpm", "exec", "tsx", "src/worker/index.ts"] + env_file: + - ../.env + environment: + NODE_ENV: production + DATABASE_URL: ${DATABASE_URL} + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ../data:/app/data + depends_on: + web: + condition: service_started + networks: + - internal + +networks: + internal: + driver: bridge diff --git a/deploy/docker-compose.prebuilt.yml b/deploy/docker-compose.prebuilt.yml new file mode 100644 index 0000000..b4db745 --- /dev/null +++ b/deploy/docker-compose.prebuilt.yml @@ -0,0 +1,62 @@ +# 邮件预报 — 预构建镜像部署(服务器不编译,省内存) +# 镜像须先在开发机 build 并 docker load +# 用法:bash deploy/install-prebuilt-image.sh [镜像tar路径] + +name: yx-bt-prebuilt + +services: + web: + image: yubao-app:latest + pull_policy: never + restart: unless-stopped + # 不走 entrypoint:db push 已由 install-prebuilt-image.sh 完成,避免 Windows CRLF 导致 set -e 失败 + command: ["pnpm", "start"] + env_file: + - ../.env + environment: + NODE_ENV: production + COOKIE_SECURE: ${COOKIE_SECURE:-false} + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ../data:/app/data + ports: + - "0.0.0.0:${APP_PORT:-30327}:3100" + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "fetch('http://127.0.0.1:3100/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))", + ] + interval: 30s + timeout: 10s + retries: 3 + start_period: 90s + networks: + - internal + + worker: + image: yubao-app:latest + pull_policy: never + restart: unless-stopped + command: ["pnpm", "exec", "tsx", "src/worker/index.ts"] + env_file: + - ../.env + environment: + NODE_ENV: production + DATABASE_URL: ${DATABASE_URL} + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ../data:/app/data + depends_on: + web: + condition: service_started + networks: + - internal + +networks: + internal: + driver: bridge diff --git a/deploy/install-bt-host-mysql-lowmem.sh b/deploy/install-bt-host-mysql-lowmem.sh new file mode 100644 index 0000000..1ae8199 --- /dev/null +++ b/deploy/install-bt-host-mysql-lowmem.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# 低内存首次部署:串行 build 1 次,web/worker 共用镜像 +# 用法:bash deploy/install-bt-host-mysql-lowmem.sh + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +COMPOSE_FILE="${ROOT_DIR}/deploy/docker-compose.bt-host-mysql.yml" +ENV_FILE="${ROOT_DIR}/.env" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "${GREEN}[deploy-lowmem]${NC} $*"; } +warn() { echo -e "${YELLOW}[deploy-lowmem]${NC} $*"; } +err() { echo -e "${RED}[deploy-lowmem]${NC} $*" >&2; } + +if [[ ! -f "$ENV_FILE" ]]; then + err "缺少 .env,请先 cp deploy/.env.bt-host-mysql.example .env 并填写" + exit 1 +fi + +if command -v free >/dev/null 2>&1; then + total_kb="$(free -k | awk '/^Mem:/{print $2}')" + avail_kb="$(free -k | awk '/^Mem:/{print $7}')" + log "内存:总计约 $((total_kb / 1024))MB,可用约 $((avail_kb / 1024))MB" + if [[ -n "${avail_kb:-}" && "$avail_kb" -lt 1500000 ]]; then + warn "可用内存偏低。建议先停查价等其它 Docker 项目,或加 2G Swap 后再部署。" + fi +fi + +mkdir -p "${ROOT_DIR}/data" +cd "$ROOT_DIR" +compose=(docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE") + +export DOCKER_BUILDKIT=1 +export BUILDKIT_MAX_PARALLELISM=1 + +log "停止可能半成品的容器…" +"${compose[@]}" down --remove-orphans 2>/dev/null || true + +log "串行构建镜像(仅 1 次,web/worker 共用 yubao-app:latest)…" +warn "Next.js 编译峰值约 2~4GB,期间 CPU/内存会飙高,服务器变慢属正常" +"${compose[@]}" build web + +log "启动 web + worker…" +"${compose[@]}" up -d + +log "同步数据库结构…" +"${compose[@]}" run --rm --no-deps web pnpm exec prisma db push --skip-generate + +log "同步默认账号…" +"${compose[@]}" run --rm --no-deps web pnpm db:seed || true + +"${compose[@]}" ps + +APP_PORT="$(grep -E '^APP_PORT=' "$ENV_FILE" | tail -1 | cut -d= -f2- | tr -d '\r' || echo 30327)" +PUBLIC_HOST="$(grep -E '^PUBLIC_HOST=' "$ENV_FILE" | tail -1 | cut -d= -f2- | tr -d '\r' || echo 127.0.0.1)" +log "完成 → http://${PUBLIC_HOST}:${APP_PORT}/login" diff --git a/deploy/install-bt-host-mysql.sh b/deploy/install-bt-host-mysql.sh new file mode 100644 index 0000000..120ceb1 --- /dev/null +++ b/deploy/install-bt-host-mysql.sh @@ -0,0 +1,187 @@ +#!/usr/bin/env bash +# 宝塔宿主机 MySQL + Docker 应用 — 192.168.2.14:30327 +# 用法(/home/project/yubao): +# bash deploy/install-bt-host-mysql.sh +# 若尚无 .env,会自动从 deploy/.env.bt-host-mysql.example 复制 + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +COMPOSE_FILE="${ROOT_DIR}/deploy/docker-compose.bt-host-mysql.yml" +ENV_FILE="${ROOT_DIR}/.env" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "${GREEN}[deploy-bt]${NC} $*"; } +warn() { echo -e "${YELLOW}[deploy-bt]${NC} $*"; } +err() { echo -e "${RED}[deploy-bt]${NC} $*" >&2; } + +read_env() { + local key="$1" + local line + line="$(grep -E "^[[:space:]]*${key}=" "$ENV_FILE" 2>/dev/null | tail -1 || true)" + if [[ -z "$line" ]]; then + echo "" + return + fi + line="${line#*=}" + line="${line#"${line%%[![:space:]]*}"}" + line="${line%"${line##*[![:space:]]}"}" + if [[ "$line" == \"*\" && "$line" == *\" ]]; then + line="${line:1:${#line}-2}" + elif [[ "$line" == \'*\' && "$line" == *\' ]]; then + line="${line:1:${#line}-2}" + fi + echo "$line" +} + +validate_env() { + local example="${ROOT_DIR}/deploy/.env.bt-host-mysql.example" + if [[ ! -f "$ENV_FILE" ]]; then + if [[ -f "$example" ]]; then + log "未找到 .env,从 deploy/.env.bt-host-mysql.example 自动复制" + cp "$example" "$ENV_FILE" + else + err "未找到 .env 且缺少 ${example}" + exit 1 + fi + fi + + DATABASE_URL="$(read_env DATABASE_URL)" + SESSION_SECRET="$(read_env SESSION_SECRET)" + APP_ADMIN_PASS="$(read_env APP_ADMIN_PASS)" + APP_OPS_PASS="$(read_env APP_OPS_PASS)" + APP_PORT="$(read_env APP_PORT)" + PUBLIC_HOST="$(read_env PUBLIC_HOST)" + MYSQL_USER="$(read_env MYSQL_USER)" + MYSQL_PASSWORD="$(read_env MYSQL_PASSWORD)" + + local missing=0 + for key in DATABASE_URL SESSION_SECRET APP_ADMIN_PASS APP_OPS_PASS MYSQL_USER MYSQL_PASSWORD; do + local val="${!key:-}" + if [[ -z "$val" ]] || [[ "$val" == CHANGE_ME* ]]; then + err "请在 .env 中填写: $key" + missing=1 + fi + done + + if [[ "$APP_ADMIN_PASS" == "$APP_OPS_PASS" ]]; then + err "APP_ADMIN_PASS 与 APP_OPS_PASS 不能相同" + missing=1 + fi + + for weak in admin123 ops123 password 123456; do + if [[ "$APP_ADMIN_PASS" == "$weak" || "$APP_OPS_PASS" == "$weak" ]]; then + err "生产环境禁止使用弱口令: $weak" + missing=1 + fi + done + + if [[ "$SESSION_SECRET" == *"change-me"* ]] || [[ ${#SESSION_SECRET} -lt 32 ]]; then + err "SESSION_SECRET 须至少 32 字符且非开发默认值" + missing=1 + fi + + if [[ $missing -ne 0 ]]; then + exit 1 + fi +} + +preflight_mysql() { + local db_user db_pass db_name + db_user="$(read_env MYSQL_USER)" + db_pass="$(read_env MYSQL_PASSWORD)" + db_name="$(read_env MYSQL_DATABASE)" + if [[ -z "$db_pass" ]]; then + return 0 + fi + log "测试宝塔 MySQL 连接..." + if docker run --rm --add-host=host.docker.internal:host-gateway mysql:8 \ + mysql -h host.docker.internal -u "${db_user}" -p"${db_pass}" -e "SELECT 1" "${db_name}" >/dev/null 2>&1; then + log "MySQL 连接正常" + return 0 + fi + err "MySQL 认证失败:请核对 .env 中 MYSQL_PASSWORD / DATABASE_URL 是否与宝塔一致" + err "常见原因:" + err " 1) 宝塔用户未授权 Docker 网段(172.%)" + err " 2) 重新上传代码覆盖了 .env" + err "修复示例:" + err " MYSQL_PASSWORD=宝塔里 ${db_user} 用户的真实密码" + err " DATABASE_URL=mysql://${db_user}:真实密码@host.docker.internal:3306/${db_name}" + exit 1 +} + +deploy_docker() { + if ! command -v docker >/dev/null 2>&1; then + err "未安装 Docker" + exit 1 + fi + if ! docker compose version >/dev/null 2>&1; then + err "需要 Docker Compose V2" + exit 1 + fi + + mkdir -p "${ROOT_DIR}/data" + + cd "$ROOT_DIR" + local compose=(docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE") + + log "构建并启动(无容器 MySQL,连接宝塔 MySQL)..." + export DOCKER_BUILDKIT=1 + export BUILDKIT_MAX_PARALLELISM=1 + "${compose[@]}" build web + "${compose[@]}" up -d + + log "同步数据库结构(prisma db push)..." + "${compose[@]}" run --rm --no-deps web pnpm exec prisma db push --skip-generate + + log "同步默认账号(admin / ops)..." + "${compose[@]}" run --rm --no-deps web pnpm db:seed + + log "容器状态:" + "${compose[@]}" ps +} + +smoke_check() { + local port="${APP_PORT:-30327}" + local url="http://127.0.0.1:${port}/api/health" + log "健康检查: ${url}" + local body + body="$(curl -sf "$url" 2>/dev/null || true)" + if [[ "$body" == \{* ]]; then + log "Web 服务正常" + else + warn "Web 暂未响应,请查看:docker compose -f deploy/docker-compose.bt-host-mysql.yml logs -f web" + fi +} + +print_summary() { + local port="${APP_PORT:-30327}" + local host="${PUBLIC_HOST:-<服务器内网IP>}" + echo "" + log "========== 部署完成(宝塔 MySQL 模式)==========" + echo " 内网访问: http://${host}:${port}" + echo " 登录页: http://${host}:${port}/login" + echo " 邮件列表: http://${host}:${port}/mails" + echo " 设置页: http://${host}:${port}/settings" + echo "" + echo " 常用命令:" + echo " docker compose --env-file .env -f deploy/docker-compose.bt-host-mysql.yml logs -f worker" + echo " docker compose --env-file .env -f deploy/docker-compose.bt-host-mysql.yml restart" + echo "" + warn "首次登录后请立即修改 admin/ops 密码(.env 中 APP_ADMIN_PASS / APP_OPS_PASS)" + warn "CC / IMAP 推荐在 Admin「设置」页配置,不必改代码" +} + +main() { + validate_env + preflight_mysql + deploy_docker + smoke_check + print_summary +} + +main "$@" diff --git a/deploy/install-prebuilt-image.sh b/deploy/install-prebuilt-image.sh new file mode 100644 index 0000000..c335820 --- /dev/null +++ b/deploy/install-prebuilt-image.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# 预构建镜像部署 — 服务器不编译,仅 load + 启动 +# 用法: +# bash deploy/install-prebuilt-image.sh +# bash deploy/install-prebuilt-image.sh /path/to/yubao-app-image.tar + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +COMPOSE_FILE="${ROOT_DIR}/deploy/docker-compose.prebuilt.yml" +ENV_FILE="${ROOT_DIR}/.env" +IMAGE_TAG="yubao-app:latest" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "${GREEN}[prebuilt]${NC} $*"; } +warn() { echo -e "${YELLOW}[prebuilt]${NC} $*"; } +err() { echo -e "${RED}[prebuilt]${NC} $*" >&2; } + +TAR_PATH="${1:-${ROOT_DIR}/deploy/yubao-app-image.tar}" + +if [[ ! -f "$ENV_FILE" ]]; then + err "缺少 .env,请先配置 DATABASE_URL 等" + exit 1 +fi + +if ! command -v docker >/dev/null 2>&1; then + err "未安装 Docker" + exit 1 +fi + +mkdir -p "${ROOT_DIR}/data" + +if docker image inspect "$IMAGE_TAG" >/dev/null 2>&1; then + log "镜像已存在: $IMAGE_TAG" +elif [[ -f "$TAR_PATH" ]]; then + log "加载镜像: $TAR_PATH" + docker load -i "$TAR_PATH" +else + err "未找到镜像 $IMAGE_TAG,也未找到 tar: $TAR_PATH" + err "请先从开发机上传 deploy/yubao-app-image.tar" + exit 1 +fi + +cd "$ROOT_DIR" +compose=(docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE") + +log "启动 web + worker(不编译)…" +"${compose[@]}" up -d + +log "同步数据库结构…" +"${compose[@]}" run --rm --no-deps web pnpm exec prisma db push --skip-generate + +log "同步默认账号…" +"${compose[@]}" run --rm --no-deps web pnpm db:seed || true + +"${compose[@]}" ps + +APP_PORT="$(grep -E '^APP_PORT=' "$ENV_FILE" | tail -1 | cut -d= -f2- | tr -d '\r' || echo 30327)" +PUBLIC_HOST="$(grep -E '^PUBLIC_HOST=' "$ENV_FILE" | tail -1 | cut -d= -f2- | tr -d '\r' || echo 127.0.0.1)" +log "完成 → http://${PUBLIC_HOST}:${APP_PORT}/login" +warn "更新版本:开发机重新 build-image-local.ps1 → 上传新 tar → bash deploy/update-prebuilt-image.sh" diff --git a/deploy/pack-prebuilt-readme.md b/deploy/pack-prebuilt-readme.md new file mode 100644 index 0000000..d98cd51 --- /dev/null +++ b/deploy/pack-prebuilt-readme.md @@ -0,0 +1,29 @@ +# 方案 C — 预构建镜像部署(服务器不编译) + +## 一、上传 + +1. 镜像/yubao-app-image.tar → 服务器 /home/project/yubao/deploy/yubao-app-image.tar +2. 部署/deploy/ 内全部 → 服务器 /home/project/yubao/deploy/(覆盖同名脚本) +3. 数据库/(若尚未导入)→ 宝塔导入 email_forecast.sql 到 yubao 库 + +## 二、服务器首次启动 + +```bash +cd /home/project/yubao +chmod +x deploy/install-prebuilt-image.sh +bash deploy/install-prebuilt-image.sh +``` + +## 三、后续更新(只换镜像) + +开发机重新 pack-prebuilt-upload.ps1,上传新 tar 后: + +```bash +bash deploy/update-prebuilt-image.sh deploy/yubao-app-image.tar +``` + +## 访问 + +http://192.168.2.14:30327/login + +服务器运行时内存约 1~1.5GB,无需在服务器上编译 Next.js。 diff --git a/deploy/pack-prebuilt-upload.ps1 b/deploy/pack-prebuilt-upload.ps1 new file mode 100644 index 0000000..d290f20 --- /dev/null +++ b/deploy/pack-prebuilt-upload.ps1 @@ -0,0 +1,89 @@ +# 方案 C:开发机构镜像 + 服务器 load(最省服务器内存) +# Usage: powershell -ExecutionPolicy Bypass -File deploy/pack-prebuilt-upload.ps1 +# +# 产出: +# 上传到服务器/ +# ├── 镜像/yubao-app-image.tar +# ├── 部署/deploy/ (仅脚本 + compose,不含 src) +# ├── 数据库/ (SQL,可选) +# └── README.md + +$ErrorActionPreference = "Stop" +$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) +$Out = Join-Path $Root ([char]0x4e0a + [char]0x4f20 + [char]0x5230 + [char]0x670d + [char]0x52a1 + [char]0x5668) +$ImageDir = Join-Path $Out ([char]0x955c + [char]0x50cf) +$DeployDir = Join-Path $Out ([char]0x90e8 + [char]0x7f72) +$DbDir = Join-Path $Out ([char]0x6570 + [char]0x636e + [char]0x5e93) + +Write-Host "prebuilt-pack: build image on dev machine…" +& (Join-Path $Root "deploy\build-image-local.ps1") + +$TarSrc = Join-Path $Root "deploy\yubao-app-image.tar" +if (-not (Test-Path $TarSrc)) { + throw "missing $TarSrc after build" +} + +Write-Host "prebuilt-pack: clean $Out (keep build tar)" +if (Test-Path $Out) { + Remove-Item -Recurse -Force $Out +} +New-Item -ItemType Directory -Path $ImageDir -Force | Out-Null +New-Item -ItemType Directory -Path (Join-Path $DeployDir "deploy") -Force | Out-Null +New-Item -ItemType Directory -Path $DbDir -Force | Out-Null + +Copy-Item $TarSrc (Join-Path $ImageDir "yubao-app-image.tar") -Force +Write-Host "prebuilt-pack: image copied" + +$DeployFiles = @( + "docker-compose.prebuilt.yml", + "docker-compose.bt-host-mysql.yml", + ".env.bt-host-mysql.example", + "install-prebuilt-image.sh", + "update-prebuilt-image.sh", + "install-bt-host-mysql.sh", + "install-bt-host-mysql-lowmem.sh", + "update-code-only-bt-host-mysql.sh" +) +$deployDst = Join-Path $DeployDir "deploy" +foreach ($f in $DeployFiles) { + $src = Join-Path $Root "deploy\$f" + if (Test-Path $src) { + Copy-Item $src (Join-Path $deployDst $f) -Force + Write-Host "prebuilt-pack: deploy/$f" + } +} + +# scripts/docker-entrypoint-web.sh referenced in compose lives in image; not needed on host + +$DbFiles = @("email_forecast.sql", "email_forecast.sql.gz", "README.md") +foreach ($f in $DbFiles) { + $src = Join-Path $Root "deploy\database\$f" + if (Test-Path $src) { + Copy-Item $src (Join-Path $DbDir $f) -Force + Write-Host "prebuilt-pack: database/$f" + } +} + +function Convert-ShFilesToLf { + param([string]$BaseDir) + Get-ChildItem -Path $BaseDir -Filter "*.sh" -Recurse -File | ForEach-Object { + $text = [System.IO.File]::ReadAllText($_.FullName) + $fixed = $text -replace "`r`n", "`n" -replace "`r", "`n" + if ($text -ne $fixed) { + [System.IO.File]::WriteAllText($_.FullName, $fixed, [System.Text.UTF8Encoding]::new($false)) + } + } +} +Convert-ShFilesToLf -BaseDir $DeployDir + +$readmePath = Join-Path $Root "deploy\pack-prebuilt-readme.md" +if (Test-Path $readmePath) { + Copy-Item $readmePath (Join-Path $Out "README.md") -Force +} else { + Set-Content -Path (Join-Path $Out "README.md") -Value "# 预构建镜像部署`n见 deploy/pack-prebuilt-readme.md" -Encoding UTF8 +} + +$imgMb = [math]::Round((Get-Item (Join-Path $ImageDir "yubao-app-image.tar")).Length / 1MB, 1) +Write-Host "" +Write-Host "prebuilt-pack: done -> $Out" +Write-Host ("prebuilt-pack: image=" + $imgMb + "MB") diff --git a/deploy/pack-readme-template.md b/deploy/pack-readme-template.md new file mode 100644 index 0000000..3d06938 --- /dev/null +++ b/deploy/pack-readme-template.md @@ -0,0 +1,110 @@ +# 上传到服务器 — 使用说明 + +本目录仅含**生产部署所需**文件,不含测试、文档、开发脚本、`node_modules` 等。 + +``` +上传到服务器/ +├── README.md ← 本说明 +├── RELEASE-NOTES.md ← 部署步骤摘要 +├── 代码/ ← 上传到服务器项目目录 /home/project/yubao +└── 数据库/ ← 宝塔 MySQL 导入用 + ├── email_forecast.sql + ├── email_forecast.sql.gz + └── README.md +``` + +--- + +## 第一步:导入数据库(宝塔) + +1. 宝塔创建数据库 **`email_forecast`** + - 字符集:**`utf8mb4`** + - 校对规则:**`utf8mb4_unicode_ci`**(必须一致,否则会提示「字符集不一致可能导致恢复失败」) +2. 创建用户 **`email_forecast`**,授权主机 **`172.%`**(Docker 容器访问) +3. 进入该库 → **导入** → 上传 **`数据库/email_forecast.sql`**(或 `.sql.gz`) + +若库已建错校对规则,先执行: + +```sql +ALTER DATABASE email_forecast CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +``` + +再导入。包内含本机完整业务数据;若只想空库,可跳过导入,首次 `install` 会 `db push` + seed。 + +--- + +## 第二步:上传代码 + +将 **`代码/`** 内全部内容上传到服务器 `/home/project/yubao/`。 + +**重要:不要覆盖服务器上已有的 `.env` 文件。** + +--- + +## 第三步:配置与启动 + +### 首次部署 + +```bash +cd /home/project/yubao +cp deploy/.env.bt-host-mysql.example .env +nano .env # 改密码、SESSION_SECRET、CC/IMAP 等 +chmod +x deploy/install-bt-host-mysql.sh +bash deploy/install-bt-host-mysql.sh +``` + +### 更新代码(已有 .env) + +```bash +cd /home/project/yubao +cp .env .env.backup.$(date +%Y%m%d) +# 用新「代码/」覆盖本目录(勿覆盖 .env) +chmod +x deploy/update-code-only-bt-host-mysql.sh +bash deploy/update-code-only-bt-host-mysql.sh +# schema 有变更时: +bash deploy/update-code-only-bt-host-mysql.sh --db-push +``` + +--- + +## 访问地址 + +| 用途 | 地址 | +|------|------| +| 登录 | http://192.168.2.14:30327/login | +| 邮件列表 | http://192.168.2.14:30327/mails | +| 设置(Admin) | http://192.168.2.14:30327/settings | + +默认账号由 `.env` 中 `APP_ADMIN_USER` / `APP_OPS_PASS` 决定;**生产勿用 admin123**。 + +--- + +## 重新打包(开发机 Windows) + +### 方案 A:服务器编译(需 4GB+ 可用内存) + +```powershell +cd X:\work\yx +powershell -ExecutionPolicy Bypass -File deploy\pack-server-upload.ps1 +``` + +### 方案 C:开发机构镜像(推荐,服务器不编译) + +```powershell +cd X:\work\yx +# 需 Docker Desktop 已启动;本机内存建议 ≥ 8GB +powershell -ExecutionPolicy Bypass -File deploy\pack-prebuilt-upload.ps1 +``` + +产出含 `镜像/yubao-app-image.tar`(约 800MB~1.5GB),服务器执行 `bash deploy/install-prebuilt-image.sh`。 + +--- + +## 与查价项目(chajia)同机部署 + +| 项目 | 端口 | 库名 | 目录 | +|------|------|------|------| +| chajia | 30325 | chajia | /home/project/chajia | +| 邮件预报(本项) | 30327 | email_forecast | /home/project/yubao | + +二者共用宝塔 MySQL,各自独立库与用户,互不影响。 diff --git a/deploy/pack-server-upload.ps1 b/deploy/pack-server-upload.ps1 new file mode 100644 index 0000000..a66cdd1 --- /dev/null +++ b/deploy/pack-server-upload.ps1 @@ -0,0 +1,193 @@ +# Pack production upload bundle (上传到服务器) +# Usage: powershell -ExecutionPolicy Bypass -File deploy/pack-server-upload.ps1 + +$ErrorActionPreference = "Stop" +$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) +$Out = Join-Path $Root ([char]0x4e0a + [char]0x4f20 + [char]0x5230 + [char]0x670d + [char]0x52a1 + [char]0x5668) +$Code = Join-Path $Out ([char]0x4ee3 + [char]0x7801) +$Db = Join-Path $Out ([char]0x6570 + [char]0x636e + [char]0x5e93) + +Write-Host "pack: clean $Out" +if (Test-Path $Out) { + Remove-Item -Recurse -Force $Out +} +New-Item -ItemType Directory -Path $Code -Force | Out-Null +New-Item -ItemType Directory -Path $Db -Force | Out-Null + +function Copy-TreeLiteral { + param( + [Parameter(Mandatory = $true)][string]$SourceDir, + [Parameter(Mandatory = $true)][string]$DestDir + ) + $stack = New-Object System.Collections.Stack + $stack.Push(@($SourceDir, $DestDir)) + while ($stack.Count -gt 0) { + $pair = $stack.Pop() + $currentSrc = $pair[0] + $currentDst = $pair[1] + if (-not (Test-Path -LiteralPath $currentDst)) { + New-Item -ItemType Directory -Path $currentDst -Force | Out-Null + } + Get-ChildItem -LiteralPath $currentSrc -Force | ForEach-Object { + if ($_.PSIsContainer) { + $stack.Push(@($_.FullName, (Join-Path $currentDst $_.Name))) + } elseif ($_.Extension -ne ".md") { + Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $currentDst $_.Name) -Force + } + } + } +} + +$RequiredRoutes = @( + "src\app\api\mails\[id]\route.ts", + "src\app\api\mails\[id]\import\route.ts", + "src\app\api\mails\[id]\do\confirm\route.ts", + "src\app\api\mails\[id]\transfer\confirm\route.ts", + "src\app\api\mails\[id]\work-order\confirm\route.ts", + "src\app\api\mails\[id]\attachments\[attId]\download\route.ts", + "src\app\api\compensations\[id]\retry\route.ts", + "src\app\api\imports\[importId]\retry\route.ts", + "src\app\api\settings\mailboxes\[id]\route.ts", + "src\app\api\settings\imap\filters\[id]\route.ts", + "src\app\api\oauth\callback\[provider]\route.ts" +) + +function Convert-ShFilesToLf { + param([Parameter(Mandatory = $true)][string]$BaseDir) + Get-ChildItem -Path $BaseDir -Filter "*.sh" -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object { + $text = [System.IO.File]::ReadAllText($_.FullName) + $fixed = $text -replace "`r`n", "`n" -replace "`r", "`n" + if ($text -ne $fixed) { + [System.IO.File]::WriteAllText($_.FullName, $fixed, [System.Text.UTF8Encoding]::new($false)) + Write-Host "pack: LF $($_.FullName.Substring($BaseDir.Length).TrimStart('\','/'))" + } + } +} + +function Test-RequiredRoutes { + param([string]$BaseDir) + $missing = @() + foreach ($rel in $RequiredRoutes) { + $path = Join-Path $BaseDir $rel + if (-not (Test-Path -LiteralPath $path)) { + $missing += $rel + } + } + if ($missing.Count -gt 0) { + throw "pack: missing dynamic API routes (bracket paths): $($missing -join ', ')" + } +} + +$CodeDirs = @( + "src", + "prisma", + "public", + "scripts" +) + +foreach ($dir in $CodeDirs) { + $src = Join-Path $Root $dir + if (-not (Test-Path $src)) { + throw "missing dir: $dir" + } + Write-Host "pack: copy $dir" + $dstDir = Join-Path $Code $dir + Copy-TreeLiteral -SourceDir $src -DestDir $dstDir +} + +$deploySrc = Join-Path $Root "deploy" +$deployDst = Join-Path $Code "deploy" +New-Item -ItemType Directory -Path $deployDst -Force | Out-Null +Get-ChildItem -Path $deploySrc -Force | Where-Object { + $_.Name -ne "database" -and + $_.Name -ne "pack-server-upload.ps1" -and + $_.Name -ne "pack-readme-template.md" +} | ForEach-Object { + Copy-Item -Path $_.FullName -Destination $deployDst -Recurse -Force +} + +$CodeFiles = @( + "package.json", + "pnpm-lock.yaml", + "next.config.ts", + "tsconfig.json", + "tsconfig.worker.json", + "next-env.d.ts", + "Dockerfile", + ".dockerignore" +) + +foreach ($file in $CodeFiles) { + $src = Join-Path $Root $file + if (Test-Path $src) { + Copy-Item -Path $src -Destination (Join-Path $Code $file) -Force + Write-Host "pack: copy $file" + } +} + +$envExample = Join-Path $Root "deploy\.env.bt-host-mysql.example" +if (Test-Path $envExample) { + Copy-Item -Path $envExample -Destination (Join-Path $deployDst ".env.bt-host-mysql.example") -Force + Write-Host "pack: copy deploy/.env.bt-host-mysql.example" +} + +$staleEnvPath = Join-Path $Code ".env" +if ((Test-Path -LiteralPath $Code) -and (Test-Path -LiteralPath $staleEnvPath)) { + Remove-Item -LiteralPath $staleEnvPath -Force + Write-Host "pack: removed stale 代码/.env" +} + +$DbFiles = @("email_forecast.sql", "email_forecast.sql.gz", "README.md") +foreach ($file in $DbFiles) { + $src = Join-Path $Root "deploy\database\$file" + if (Test-Path $src) { + Copy-Item -Path $src -Destination (Join-Path $Db $file) -Force + Write-Host "pack: database $file" + } +} + +function Get-DirSizeBytes($path) { + (Get-ChildItem -Path $path -Recurse -File -ErrorAction SilentlyContinue | + Measure-Object -Property Length -Sum).Sum +} + +$codeSize = [math]::Round((Get-DirSizeBytes $Code) / 1MB, 2) +$dbSize = [math]::Round((Get-DirSizeBytes $Db) / 1KB, 1) +$fileCount = (Get-ChildItem -Path $Out -Recurse -File).Count + +Write-Host "" +Write-Host "pack: normalize shell scripts (CRLF -> LF)" +Convert-ShFilesToLf -BaseDir $Code + +Write-Host "" +Write-Host "pack: verify dynamic API routes" +Test-RequiredRoutes -BaseDir $Code + +$ForbiddenInCode = @( + (Join-Path $Code ".env"), + (Join-Path $Code "node_modules"), + (Join-Path $Code ".next"), + (Join-Path $Code "data"), + (Join-Path $Code ".git") +) +foreach ($bad in $ForbiddenInCode) { + if (Test-Path -LiteralPath $bad) { + throw "pack: forbidden path present in 代码/: $bad" + } +} + +Write-Host "" +Write-Host "pack: done -> $Out" +Write-Host "pack: files=$fileCount codeMB=$codeSize dbKB=$dbSize" +Write-Host "pack: SAFETY ok (no .env/node_modules/.next/data/.git in 代码/)" + +$readmeSrc = Join-Path $Root "deploy\pack-readme-template.md" +if (Test-Path $readmeSrc) { + Copy-Item $readmeSrc (Join-Path $Out "README.md") -Force +} + +$releaseNotesSrc = Join-Path $Root "deploy\宝塔部署说明.md" +if (Test-Path $releaseNotesSrc) { + Copy-Item $releaseNotesSrc (Join-Path $Out "RELEASE-NOTES.md") -Force + Write-Host "pack: RELEASE-NOTES.md" +} diff --git a/deploy/update-code-only-bt-host-mysql.sh b/deploy/update-code-only-bt-host-mysql.sh new file mode 100644 index 0000000..828bb47 --- /dev/null +++ b/deploy/update-code-only-bt-host-mysql.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# 代码/镜像更新(服务器上可选同步库结构) +# 用法: +# bash deploy/update-code-only-bt-host-mysql.sh +# bash deploy/update-code-only-bt-host-mysql.sh --db-push # schema 有变更时 +# +# 特点:串行 build;默认不碰库;失败 fail-fast + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +COMPOSE_FILE="${ROOT_DIR}/deploy/docker-compose.bt-host-mysql.yml" +ENV_FILE="${ROOT_DIR}/.env" +DO_DB_PUSH=false + +for arg in "$@"; do + case "$arg" in + --db-push) DO_DB_PUSH=true ;; + -h|--help) + echo "用法: bash deploy/update-code-only-bt-host-mysql.sh [--db-push]" + exit 0 + ;; + esac +done + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "${GREEN}[update-code]${NC} $*"; } +warn() { echo -e "${YELLOW}[update-code]${NC} $*"; } +err() { echo -e "${RED}[update-code]${NC} $*" >&2; } + +CURRENT_STEP="init" +on_err() { + local code=$? + err "失败中断:步骤「${CURRENT_STEP}」退出码=${code}" + exit "$code" +} +trap on_err ERR + +require_file() { + local path="$1" + if [[ ! -f "$path" ]]; then + err "缺少文件: $path" + exit 1 + fi +} + +run_step() { + local name="$1" + shift + CURRENT_STEP="$name" + log "→ ${name}" + "$@" + log "✓ ${name} 完成" +} + +CURRENT_STEP="preflight" +log "前置校验..." + +if [[ ! -f "$ENV_FILE" ]]; then + err "缺少 .env,禁止继续" + exit 1 +fi + +if ! command -v docker >/dev/null 2>&1; then + err "未安装 docker" + exit 1 +fi + +require_file "${ROOT_DIR}/src/app/api/mails/[id]/route.ts" +require_file "${ROOT_DIR}/Dockerfile" +require_file "${ROOT_DIR}/package.json" +require_file "${ROOT_DIR}/pnpm-lock.yaml" + +log "前置校验通过" + +cd "$ROOT_DIR" +compose=(docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE") + +export DOCKER_BUILDKIT=1 +export BUILDKIT_MAX_PARALLELISM=1 + +run_step "停止 web/worker" \ + "${compose[@]}" stop web worker + +run_step "构建 web" \ + "${compose[@]}" build web + +run_step "构建 worker" \ + "${compose[@]}" build worker + +run_step "启动全部服务" \ + "${compose[@]}" up -d + +if [[ "$DO_DB_PUSH" == "true" ]]; then + run_step "同步数据库结构(prisma db push)" \ + "${compose[@]}" run --rm --no-deps web pnpm exec prisma db push --skip-generate +else + warn "跳过 db push(schema 无变更可忽略;有变更请加 --db-push)" +fi + +CURRENT_STEP="校验容器状态" +for svc in web worker; do + state="$("${compose[@]}" ps --status running --services 2>/dev/null | grep -x "$svc" || true)" + if [[ -z "$state" ]]; then + err "服务未处于 running: ${svc}" + exit 1 + fi + log "✓ ${svc} running" +done + +CURRENT_STEP="done" +trap - ERR +log "完成(代码-only 更新)" diff --git a/deploy/update-prebuilt-image.sh b/deploy/update-prebuilt-image.sh new file mode 100644 index 0000000..8d6d2a0 --- /dev/null +++ b/deploy/update-prebuilt-image.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# 预构建镜像更新 — 加载新 tar 并重建容器(不编译) +# 用法:bash deploy/update-prebuilt-image.sh [镜像tar路径] + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +COMPOSE_FILE="${ROOT_DIR}/deploy/docker-compose.prebuilt.yml" +ENV_FILE="${ROOT_DIR}/.env" +IMAGE_TAG="yubao-app:latest" +TAR_PATH="${1:-${ROOT_DIR}/deploy/yubao-app-image.tar}" + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +log() { echo -e "${GREEN}[prebuilt-update]${NC} $*"; } +err() { echo -e "${RED}[prebuilt-update]${NC} $*" >&2; } + +if [[ ! -f "$ENV_FILE" ]]; then + err "缺少 .env" + exit 1 +fi + +if [[ ! -f "$TAR_PATH" ]]; then + err "缺少镜像 tar: $TAR_PATH" + exit 1 +fi + +cd "$ROOT_DIR" +compose=(docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE") + +log "停止 web/worker…" +"${compose[@]}" stop web worker || true + +log "加载新镜像…" +docker load -i "$TAR_PATH" + +log "重建容器…" +"${compose[@]}" up -d --force-recreate + +"${compose[@]}" ps +log "完成" diff --git a/deploy/宝塔部署说明.md b/deploy/宝塔部署说明.md new file mode 100644 index 0000000..f08fbb5 --- /dev/null +++ b/deploy/宝塔部署说明.md @@ -0,0 +1,150 @@ +# 宝塔 + 宿主机 MySQL + Docker 应用(内网 30327) + +与查价项目同一套模式:**MySQL 在宝塔,应用在 Docker**。 + +适用于: + +- MySQL:**宝塔面板**,库名 `email_forecast` +- 应用:**Docker 容器**(web + worker) +- 代码目录:`/home/project/yubao` +- 访问:内网 `http://服务器IP:30327` + +--- + +## 一、宝塔 MySQL 准备 + +1. 创建数据库 **`email_forecast`** + - 字符集:**utf8mb4** + - 校对规则:**utf8mb4_unicode_ci**(与 SQL 一致;选错会出现「字符集不一致可能导致恢复失败」) +2. 创建用户 **`email_forecast`**,赋予该库全部权限 +3. 授权 Docker 网段(容器经 `host.docker.internal` 连宿主机): + +```sql +-- 在宝塔 phpMyAdmin 或终端执行(按实际密码改) +CREATE USER 'email_forecast'@'172.%' IDENTIFIED BY '你的强密码'; +GRANT ALL PRIVILEGES ON email_forecast.* TO 'email_forecast'@'172.%'; +FLUSH PRIVILEGES; +``` + +4. **推荐**:导入 `数据库/email_forecast.sql`(本机完整数据);也可不导入,由 install 脚本 `prisma db push` + seed + +### 测试(在服务器上) + +```bash +mysql -h 127.0.0.1 -u email_forecast -p email_forecast +``` + +--- + +## 二、开发机打包 + +```powershell +cd X:\work\yx +powershell -ExecutionPolicy Bypass -File deploy\pack-server-upload.ps1 +# 可选压缩便于上传 +Compress-Archive -Path "上传到服务器\代码\*" -DestinationPath "yubao-code.zip" -Force +``` + +将 **`上传到服务器/代码/`** 解压到服务器 `/home/project/yubao/`。 + +--- + +## 三、配置 .env + +```bash +cd /home/project/yubao +cp deploy/.env.bt-host-mysql.example .env +nano .env +``` + +必改项: + +| 变量 | 说明 | +|------|------| +| `APP_PORT` | 对外端口,默认 `30327`(勿与 chajia 30325 冲突) | +| `PUBLIC_HOST` | 服务器内网 IP | +| `MYSQL_PASSWORD` | 宝塔里 email_forecast 用户密码 | +| `DATABASE_URL` | `mysql://email_forecast:密码@host.docker.internal:3306/email_forecast` | +| `SESSION_SECRET` | ≥32 字符随机串 | +| `APP_ADMIN_PASS` / `APP_OPS_PASS` | **不能**用 admin123/ops123,且二者须不同 | +| `OAUTH_PUBLIC_BASE_URL` | `http://内网IP:30327` | +| `CC_MOCK` | 无 CC 账号时 `true`;有账号改 `false` 并在设置页填凭据 | + +密码含 `@`、`#` 等需 [URL 编码](https://www.urlencoder.org/)。 + +--- + +## 四、首次启动 + +```bash +chmod +x deploy/install-bt-host-mysql.sh +bash deploy/install-bt-host-mysql.sh +``` + +脚本会:校验 MySQL → 构建 web/worker → `prisma db push` → seed 账号 → 健康检查。 + +**不要**用根目录 `docker-compose.yml` 里的容器 MySQL(那是本地开发用的 7023 端口)。 + +--- + +## 五、更新代码 + +```bash +bash deploy/update-code-only-bt-host-mysql.sh +# Prisma schema 有变更时: +bash deploy/update-code-only-bt-host-mysql.sh --db-push +``` + +--- + +## 六、内网访问 + +| 用途 | 地址 | +|------|------| +| 登录 | `http://192.168.x.x:30327/login` | +| 邮件 | `http://192.168.x.x:30327/mails` | +| 健康检查 | `http://192.168.x.x:30327/api/health` | + +--- + +## 七、宝塔防火墙 + +- 放行 **30327**(内网即可) +- **不要**对公网开放 3306 + +--- + +## 八、架构示意 + +``` +内网浏览器 ──HTTP:30327──► [Docker] web (Next.js :3100) + │ + ├──► [Docker] worker (IMAP 拉取 + 解析) + │ + └──► host.docker.internal:3306 + │ + [宝塔] MySQL email_forecast +``` + +--- + +## 九、故障排查 + +| 现象 | 处理 | +|------|------| +| `Can't connect to MySQL` | 用户是否授权 `172.%`;`DATABASE_URL` 是否用 `host.docker.internal` | +| 生产启动报 SESSION_SECRET / 弱口令 | 改 `.env` 中 `SESSION_SECRET`、`APP_*_PASS` | +| Worker 不拉信 | `docker compose -f deploy/docker-compose.bt-host-mysql.yml logs -f worker`;检查设置页邮箱绑定 | +| 端口不通 | 宝塔防火墙、安全组是否放行 30327 | +| API 404(带 `[id]` 的路由) | Windows 打包方括号路径丢失;重新 `pack-server-upload.ps1` 并核对 RequiredRoutes | + +--- + +## 十、导出本机库 + +```bash +# 本机(需 MySQL 客户端 + 本地 docker mysql 在跑) +bash deploy/database/export-local.sh +# 或直接: +# docker exec yx-mysql-1 mysqldump -uapp -papp email_forecast > deploy/database/email_forecast.sql +``` diff --git a/docs/邮件指令提取规则.md b/docs/邮件指令提取规则.md new file mode 100644 index 0000000..d6e7405 --- /dev/null +++ b/docs/邮件指令提取规则.md @@ -0,0 +1,371 @@ +# 邮件指令提取规则(对照实现) + +> **用途**:给产品/运营对照「接下来没见过、格式很怪」的邮件,判断系统会不会拉进库、认成哪类指令、字段从哪来。 +> **约束**:解析**不走大模型**,全部是词表 + 正则 + 固定模板。新格式要么命中现有规则,要么漏/错,不会「猜一下」。 +> **实现真源**:`src/services/parse/`(词表 `instruction-lexicon.ts`、分类 `classify.ts`、拆段 `split-instructions.ts`、流水线 `pipeline.ts`)。 +> **产品口径**:`docx/产品规则-邮件指令识别与拆分.md`(冲突时以代码为准,并回写产品页)。 +> **日期**:2026-08-14 + +--- + +## 0. 一句话 + +一封信要变成可确认的业务卡,必须连过四道门: + +1. **拉取**:过滤规则 / 业务兜底认为「相关」才入库。 +2. **分类**:主题+正文+附件名打分,得到整封主类型。 +3. **拆指令**:按时间轮切开,每段映射到 UI 卡(预报 / 转仓 / 工单 / 上传 DO / 客户指令)。 +4. **抽字段**:柜头来自主题模板或正文标签;货件行几乎只来自 **xlsx/xls/csv**(卡派表或贴标指令单)。 + +任何一步没命中,后面再「看起来像业务」也不会自动补全。 + +```mermaid +flowchart TD + A[IMAP 新信] --> B{拉取过滤} + B -->|SKIP_FILTER / 黑名单| Z[不入库] + B -->|入库| C[classify 打分] + C --> D[split 拆时间轮] + D --> E{有表格附件?} + E -->|xlsx/xls/csv| F[卡派表 / 贴标指令单] + E -->|无表| G[仅柜头 + 空货件] + F --> H[详情指令卡] + G --> H + H --> I[人工确认写入 CC] +``` + +--- + +## 1. 拉取:什么样的信根本进不来 + +实现:`src/services/imap/pull-filter.ts` + `classify.ts` `isBusinessRelevantMail`。 + +| 顺序 | 条件 | 结果 | +|------|------|------| +| 1 | 发件人在黑名单,或主题/正文命中关键词黑名单 | **不拉** `SKIP_BLACKLIST` | +| 2 | 发件人在白名单 | **拉** | +| 3 | 主题/正文命中设置页关键词 | **拉** | +| 4 | 过滤全空时走业务兜底 | 见下表 | +| 5 | 已配置过滤但以上都不中 | **不拉** `SKIP_FILTER` | + +业务兜底(过滤全空才用)任一为真即拉: + +- 分类结果不是 `UNKNOWN` +- 主题/正文/附件名命中:`新增预报|转仓|换标|覆盖贴|贴标|拍照|拆柜清单|卡转海|拦截|改自提|留仓|卡派|装箱|货件清单|船名|航次|柜型|40HQ|20GP|ETA|预报资料|拆柜|柜号|快递单号|工单|DO|DO请查收` +- 文本或附件名里有 **ISO 柜号** `[A-Z]{4}` + 7 位数字(如 `WHSU8127240`) +- 附件名像卡派/装箱/packing/清单/预报/转仓/换标/贴标/货件/DO +- 有表格附件 **且** 正文弱物流词:`资料|清单|柜|仓|FBA|shipment|container` + +**未见过的信在这里就会丢:** + +- 纯英文、无柜号、无上表关键词(例如 “please book this container”) +- 只写「请安排」「帮忙处理」而无标准词 +- 柜号不是 ISO 四字母+七数字(短柜号、带校验位空格、中文「柜」无号) +- 设置了关键词过滤,但新客户用了别的叫法,又不在白名单 + +--- + +## 2. 整封分类(主类型) + +实现:`classifyMail`。优先级(同分取更靠前): + +`DO_UPLOAD` > `NEW_CONTAINER` > `TRANSFER` > `WORK_ORDER` + +总分 **< 40 → `UNKNOWN`**(列表里常变成忽略/无法确认)。 + +| 信号 | 加分 | 落到 | +|------|------|------| +| `DO请查收` / 请查收 DO / 上传 DO / 文件名独立 `DO` 的 PDF | 55(无真实 DO 且主题已是拆柜清单则降到 25) | DO_UPLOAD | +| `新增预报` / `请查收新增预报` | 50 | NEW_CONTAINER | +| 附件名 `数据模版` 或 `预报资料\|货件清单\|装箱清单` 的表格 | 45(已有预报分则 +15) | NEW_CONTAINER | +| `新增转仓` 或 `转仓`(排除 `不转仓`) | 50 | TRANSFER | +| 硬工单:贴标/拦截/拍照/快递单号,或换标/覆盖贴/贴好拍照 | 45 | WORK_ORDER | +| 软词:拆柜清单 / 卡转海 / 派送要求 / 改自提 / 更新派送单 / 留仓 | 45 | WORK_ORDER | +| 附件名 `换标\|贴标指令` | 30 | WORK_ORDER | +| 正文最新段已是硬工单 | 压过主题上的历史「新增预报」/DO | WORK_ORDER | +| 分类仍 UNKNOWN,但有 ISO 柜号或「预约码」 | 40 兜底 | WORK_ORDER(mail1 类) | + +**会错的典型:** + +- 主题还挂着「新增预报」,最新回复是换标 → 会压成工单(这是刻意的)。 +- 「拦截-209件」只是渠道汇总,**不当**整票拦截;真正拦截必须正文另有「拦截」且不是 `拦截-数字件`。 +- 文件名叫 `卡派资料.xlsx` **不会单独**打成预报,还要有「新增预报」或「数据模版/装箱清单」等。 +- 英文 “new booking / transload / relabel” **不加分**。 + +--- + +## 3. 多轮拆段 → 指令卡 + +实现:`split-instructions.ts` + `instruction-lexicon.ts`。 + +### 3.1 怎么切开 + +按边界切(保留分隔标签到后一段): + +- `---- 原始邮件 / 转发的邮件 / Original Message ----` +- `在 yyyy-mm-dd … 写道` / `在 yyyy年m月d日 … 写道` / `On … wrote:` +- `发件人` + `发送时间/日期` 邮件头 +- 行内嵌套 `> 在 … 写道`(QQ 引用) + +**不用**单独的 `Dear` 当切段点。 +弱切(独立成行才切):`新增预报|请查收新增预报|新增转仓|更新派送单|拆柜清单|派送要求|换标|覆盖贴|贴标指令|出库|留仓|拆分|DO请查收|请查收 DO` + +### 3.2 关键词 → UI kind + +| UI | 关键词(节选,见词表) | 确认动作 | +|----|------------------------|----------| +| `forecast` | 新增预报、请查收新增预报;正文 `柜号:XXXX1234567 派送单,请查收`;提柜+柜型+卡派/拆柜 | 预报表单 | +| `transfer` | 新增转仓、转仓(`不转仓` 除外) | 批量转仓;CC 到仓闸门失败则降级工单 | +| `work_order` | 贴标/换标/覆盖贴/操作指令、拍照、拦截(非渠道件数)、快递单号;软词拆柜清单/卡转海/派送要求/改自提/更新派送单/改地址派送/点前送达 | 客户留言 / 转渠道 / 拆分 / 出库 | +| `do_upload` | DO请查收、请查收 DO、文件名独立 DO 的 PDF | 上传 DO | +| `customer_instruction` | 按照正常预报接受、帮忙安排派单、继续暂存 | **只读**,不提交 | + +跳过(不出卡):QQ 转发壳、`发自我的iPhone`、空 Original、主题残片。 + +**可确认的一般是最新一轮有实质指令的段。** 历史预报/转仓可只读展示;历史转仓现在也允许单独执行。 + +同线程主题只变 Re:/Fw: 时,**不得**再用首封主题「新增预报」给最新轮再造一张预报卡。 + +### 3.3 附件挂到哪张卡 + +实现:`match-segment-attachments.ts`。顺序: + +1. 主键交叉:附件名与本段出现同一 `FBA…` / `YT…` / `X0…`(FNSKU)/ `WL…` 唛头。**不用 ISO 柜号**给工单分附件。 +2. 正文/主题出现该文件主名(去扩展名、去 `(1)`)。 +3. 仅**当前可确认段**角色兜底:文件名含卡转海 / 换标|覆盖贴|操作指令|贴标交付。 +4. `*DO*.pdf` 只进上传 DO 卡。 +5. 卡派/装箱 xlsx 是解析源,默认不进工单附件。 +6. 跳过 `*邮箱.pdf` / QQ 截图。 + +--- + +## 4. 预报(NEW_CONTAINER / forecast) + +### 4.1 主题柜头:两种互斥模板 + +**A. 客户+提单预报(`bl-plus-template.ts`)** + +剥掉 `Fw:/转发/新增预报N:` 后,用 **`+`(或全角+)** 分段。无 `+` 才按空格切,二者互斥。 + +常见形态: + +`新辰泽+WHLC027G597465+WHSU8127240+洛杉矶+40HQ+EDT2026.04-25+ETA2026.05-15+船名航次HMM EMERALD 013E+提拆派+不带托架` + +能抽出:客户名、提单、柜号、目的港(**美国港口白名单**)、柜型、ETD/ETA、船名航次、服务(提拆派/直送)、是否带车架。 + +客户名不得是柜号/提单/「贴标/转发/柜号」等。目的港未进白名单(洛杉矶/长滩/纽约/萨凡纳/奥克兰/西雅图/塔科马/休斯顿/迈阿密/查尔斯顿等)→ **不写港口**。 + +标签型(首段就是 `柜号:` / `提单号:`,客户不在提单前): + +`Fw: Fw:回复:柜号:TRHU8524770+提单号:EGLV143661466958+封号:EMCEJJ0635+YWZD+不带车架+整柜直送` + +| 段 | 落到 | +|----|------| +| `柜号:TRHU8524770` | 柜号 | +| `提单号:EGLV143661466958` | 提单 | +| `封号:EMCEJJ0635` | **备注**(预报 UI 无封号格) | +| `YWZD` | 客户(排除已知字段后仅剩 1 个独立缩写) | +| `不带车架` | 车架 = 不带 | +| `整柜直送` | 业务类型 = 直送 | + +**带「名称:值」前缀的段,不得去抢客户名。** 前缀已经说明这是有名字的字段;对不上预报 UI 的(封号/铅封/预约码等)一律进备注。无前缀的短码(如 YWZD)才参与客户竞争。 + +**B. 工单/拆柜清单主题(`ops-subject-template.ts`)** + +形态:`柜号+提单+船名航次:…+ETA:…+FedEx-29件+拦截-209件+…` + +一旦命中 B,**不再当 A 的客户+提单预报**。渠道 `Xxx-N件` 只作汇总,不是整票指令。 + +### 4.2 正文柜头补全(`extract-container-header.ts`) + +只认**带标签**的字段,不扫全文乱截: + +| 字段 | 规则 | +|------|------| +| 柜型 | `\b(45HC\|45HQ\|40HC\|40HQ\|40GP\|20GP\|20HQ\|45GP)\b` | +| ETA | `ETA\|预计抵港\|抵港日\|到港` + 日期 | +| ETD | `EDT\|ETD\|预计开船\|开船日\|离港` + 日期 | +| 提单 | `提单(号)\|B/L\|BLCopy` + 号;否则跳过 ISO 柜号取裸 `ABCD`+数字串 | +| 装货港 | `装货港\|起运港\|POL` | +| 目的港 | `目的港\|卸货港\|码头\|POD` + 白名单 | +| 船司 | 显式「船司」> 提单前 2–4 位前缀 > 正文别名 > 船名首词 | +| 车架 | `带车架/带托架` vs `不带车架/不带托架` | +| 操作 | `提拆派` → 0;`直送` → 2 | +| 运输方式 | 柜型/船名航次/海运提单前缀 → 海运;否则航司别名 → 空运 | +| 封号/铅封 | `封号\|铅封\|封条` + 号 → **备注**(预报 UI 无此格) | + +**正文没有这些标签时,柜头对应格就是空的**(例如只写 “arriving LA next week”)。 + +附件解析结果优先;邮件文本只补空字段。 + +### 4.3 货件行(几乎只认表格) + +选表优先级(文件名):`数据模版|提拆派|卡派资料|卡派清单|装箱清单|packing` > 其它表格 > `换标|覆盖贴|贴标|操作指令`。 + +**卡派表**(`packing-list.ts`): + +- 优先 sheet 名含「卡派」,否则第一张表。 +- 前 15 行找表头:同时有「渠道」+(件数或 CTNS)+(仓库或 FBACode)。 +- **必有列**:仓库ID/`FBACode`、渠道/`Transporter`、件数/`CTNS`。缺一则 `PARSE_FAILED`,再尝试贴标指令单。 +- 「注意:…」模板说明行不是货件。 +- 渠道映射:卡派→TRUCK,UPS/FEDEX/DHL/USPS/SF/4PX/YunExpress,自提,留仓/存仓,换标,扣货/拦截。超 40 字或「注意:」开头不当渠道。 + +列名别名见代码 `HEADER_ALIASES`(中英混排、`Custmer Ref No.` 错拼也认)。 + +**贴标指令单**(`label-instruction.ts`)表头含「贴标项目 / 详细操作指令 / 新FBA / 覆盖贴 / 换标」或同时有柜号+操作箱数。 +`lineage.template=label-instruction` 时:**预报货件列表必须空**,换标行不当 SaveContainer 货件。 + +无表格:预报可仅柜头进待确认;货件列表空。PDF/图片卡派表 **不会**当货件解析(转仓截图 OCR 是另一条路)。 + +### 4.4 柜头 Instruction / 备注(不是货件行备注) + +| 表单格 | CC 字段 | 实际来源 | +|--------|---------|----------| +| Instruction | `F_Instruction` 拆柜指引 | ① 预报正文段(含新增预报/提单/柜型/ETA/船名,且不含换标/贴标)截 500 字;② 否则 `船名航次 xxx`;③ 贴标单则用首行操作说明(预报卡会改回 ①②) | +| 备注 | `F_MemoRemark` | **优先**:主题/正文里带标签但对不上预报 UI 的字段(`封号:EMCEJJ0635`、`铅封:…`、`预约码:…` 等),多段用 ` / ` 拼接。**否则回退**:`客户名 / 提拆派或直送`。贴标 sheet 标题含贴标/换标/指令时预报卡会清掉。 | +| 货件「行备注」 | `F_Remark` | 卡派列「备注 / Remark / Instruction / 配送时段」 | + +**未映射标签 → 备注(硬规则)** + +- 段内已有 `名称:值`(或 `名称:值`)前缀,说明这是有名字的字段,**禁止**拿去填客户。 +- 名称能对上预报 UI(柜号/提单/柜型/ETA/ETD/船名航次/目的港/装货港/船司/客户/运输方式/业务类型)→ 填对应格。 +- 名称对不上(封号、铅封、预约码,以及其它未知标签)→ 整段原文进备注。 +- 无前缀的剩余短码:仅当排除已知字段后**恰好剩 1 个**才当客户;剩多个则全部进备注,不强猜客户。 +- 已占用的「柜号:」「提单号:」会从备注里剥掉,避免和柜号/提单格重复;**封号保留**。 + +去 QQ/Outlook/iPhone 转发壳后再填 Instruction。 + +--- + +## 5. 转仓(TRANSFER) + +- 关键词:`新增转仓` / `转仓`,排除 `不转仓`。 +- 货件:卡派表行备注「转 POC2」「转VGT2」等 → `extractTransferPairs`。 +- 截图/OCR 表(`transfer-instruction-table.ts`): + - 标准:`地址 PSC2 箱数 7 … 仓库重定向 POC2` + - 宽松:同行 `地址 X … 仓库重定向 Y` + - 仓码归一:FWA4 / PSC2 / POC2 / VGT2(含 OCR 错写 EWAD、WAAD4 等) +- 确认时查 CC 是否已到仓;不可转则自动改提交工单。 + +**会漏:** 英文 transload、没有「地址/仓库重定向」的自由句、目标仓不是上述几种代码、只在 PDF 里画了箭头。 + +--- + +## 6. 工单(WORK_ORDER) + +硬动作:`贴标` `拦截` `拍照` `快递单号`,同义 `换标` `覆盖贴` `贴好拍照`。 +软词(也走工单卡,不硬套某张 CC 工单界面):拆柜清单、卡转海、派送要求、改自提、更新派送单、改地址派送、点前送达。 + +mail1:主题只有柜号+预约码、无标准词 → 分类工单,详情用 `work_order_actions` 补卡。 + +留言取**当前段**,从 `Dear` / `原箱号` 起,禁止整封线程盖住本段。 + +面板分流(管理端/客户端):拦截/改渠道 → 转渠道;数量拆分 → Split;出库/贴标文件 → 出库;其余 → 客户留言。 + +--- + +## 7. 上传 DO + +文件名:`DO请查收` / `提货单` / `delivery order`,或 **PDF 且独立单词 DO**(`WHSU8127240-DO.pdf`、`xxx-DO.pdf`)。 +正文写「DO 也同步上传」但**没有**这类附件 → **不出**空 DO 卡。 +柜号:已有柜头 > 文件名 ISO 柜号 > 主题/正文第一个 ISO 柜号。 + +`.docx` / 图片扫描件不叫 DO、文件名是 `DeliveryOrder_final.pdf` 无独立 `DO` 词 → 可能认不出。 + +--- + +## 8. 金样覆盖(已按这些规则调过) + +| 样例 | 期望 | 规则依赖 | +|------|------|----------| +| 邮件1 仅柜号 TIIU8073522 | 工单兜底 | ISO 柜号、无关键词 | +| 邮件2 MATU + 卡派 xlsx | 转仓 | `新增转仓` + 卡派表 | +| 邮件3 拆柜清单+渠道件数 | 工单 | 工单主题模板 + 软词;渠道件数不是拦截指令 | +| 邮件4 预报+换标+DO | 工单为主,预报/DO 可并存 | 最新段硬工单压预报;卡派表优先于换标表;DO 文件名 | + +这四封能过,**不代表**换一套客户模板也能过。 + +--- + +## 9. 覆盖缺口清单(用来对照新邮件) + +把新邮件贴到下面问:会在哪一层失败? + +### 9.1 进不了库 + +- [ ] 没有 ISO 柜号,也没有第 1 节关键词 +- [ ] 纯英文 booking / sailing / transload / relabel / hold +- [ ] 已配关键词过滤,新叫法不在列表、发件人不在白名单 +- [ ] 主题乱码(GBK/UTF-8 错码)导致关键词对不上 + +### 9.2 能进库但类型错 / UNKNOWN + +- [ ] 同义说法:新增订舱、做预报、改仓、贴新标、hold cargo、stop shipment +- [ ] 「转」但没写「转仓」;「不要转仓」以外的否定 +- [ ] 预报附件不叫数据模版/装箱清单/卡派资料 +- [ ] 主题预报 + 正文最新段换标(会变成工单,可能符合产品,但预报卡不是当前) +- [ ] 预报与 DO 分数接近(close_call),以优先级表为准 + +### 9.3 类型对但字段空 + +- [ ] 主题不用 `+` 分隔(用空格/逗号/表格/HTML) +- [ ] 港口不在美国白名单(如西雅图别称未收录、欧洲港) +- [ ] 柜型写成 40'HQ / 40 尺柜 / HC 无数字 +- [ ] 日期无 ETA/ETD 标签(只写 5/15) +- [ ] 船名不写「船名航次」四字 +- [ ] 只写「备注:」标签、没有 `封号:` 这类字段名 — 当前**不**从「备注:」抽备注;未映射的 `名称:值` 才会进备注 +- [ ] 封号写在无 `+`、无冒号的自由句里(如 “seal EMCEJJ0635”) + +### 9.4 有附件但货件为空 / 错表 + +- [ ] 卡派是 PDF、图片、Word,不是 xlsx/xls/csv +- [ ] 表头不叫渠道+件数+仓库(例如全英文 Warehouse / Qty 但没有渠道) +- [ ] 缺仓库ID/渠道/件数三列之一 +- [ ] 只有换标指令单被当成预报行(文件名若不像卡派,可能先吃换标表;预报卡会清空货件) +- [ ] 表在 zip 内层路径/加密压缩包 +- [ ] 附件过大被拒 + +### 9.5 转仓 / 工单 / DO 细节 + +- [ ] 转仓目标仓不是 PSC2/POC2/FWA4/VGT2 +- [ ] 工单附件只靠柜号关联(规则故意不用柜号分发) +- [ ] DO 是 JPG/扫描件,或文件名无独立 DO + +### 9.6 编码与客户端壳 + +- [ ] QQ 转发壳、iPhone「发自我的」会剥;剥完若没剩指令词则跳过该段 +- [ ] 正文 HTML 未转纯文本时,标签可能打断正则 +- [ ] 繁体:新增預報、轉倉、貼標 — **词表是简体**,繁体会漏 + +--- + +## 10. 扩展新格式时改哪里 + +不要在详情页临场「理解」一封信。按层改: + +| 缺口 | 改 | +|------|----| +| 拉不进来 | 设置页关键词/白名单,或 `BUSINESS_TEXT_RE` / `isBusinessRelevantMail` | +| 类型错 | `classify.ts` 加分 + `instruction-lexicon.ts` 词表 + 单测 | +| 拆不出卡 | `KEYWORD_RULES`、切段边界、`split-instructions` | +| 主题柜头 | `bl-plus-template` 或 `ops-subject-template`(先判断像 A 还是 B);未映射标签进 `memo_bits` → 备注 | +| 正文柜头 | `extract-container-header.ts` 标签正则;港口 `us-ports.ts`;船司 `carrier-aliases.ts` | +| 卡派列名 | `packing-list.ts` `HEADER_ALIASES` | +| 贴标表 | `label-instruction.ts` `LABEL_HEADER_ALIASES` | +| 转仓 OCR | `transfer-instruction-table.ts` + 仓码归一 | +| DO 文件名 | `do-upload-extract.ts` | +| 附件挂段 | `match-segment-attachments.ts` | + +流程:**先在本页记下「哪一层缺口」→ 改对应文件 → 补 `tests/unit` 或 fixtures → `pnpm test` 相关文件。** + +--- + +## 11. 结论(给评审用) + +当前规则覆盖的是:**简体中文 + 固定口头禅 + ISO 柜号 + `+` 主题模板 + 中英列名接近金样的 xlsx**。 + +对「还没拿到的邮件」: + +- **同类客户、换柜号/换船名/换附件内容**:一般能覆盖。 +- **换一套邮件习惯**(英文、繁体、无 `+`、无「新增预报」、PDF 清单、新仓码、新列名):**不能默认覆盖**,会按第 9 节某一层失败,需要加词或加模板,而不是调模型。 + +建议每来一类新失败信:先标失败层(拉取 / 分类 / 拆段 / 柜头 / 表格),再决定是加关键词还是加模板,避免一条超级正则吃所有格式。 diff --git a/docs/邮箱项目.md b/docs/邮箱项目.md index 60f21fd..21d88ec 100644 --- a/docs/邮箱项目.md +++ b/docs/邮箱项目.md @@ -42,6 +42,7 @@ | 异常与韧性 | `docx/异常场景与系统韧性设计.md` | | 运营 SOP | `docx/运营SOP-邮件自动预报.md` | | 产品规则(指令拆分) | `docx/产品规则-邮件指令识别与拆分.md` | +| 指令提取规则(对照代码,含覆盖缺口) | `docs/邮件指令提取规则.md` | | CC 接口 V1.72 | `docx/接口/carriercentral客户端通用接口V1.72.md` | | 实施计划(Cursor) | `.cursor/rules/implementation-plan.mdc` | | 快速 README | `README.md` | diff --git a/docs/项目全貌与开发指南.md b/docs/项目全貌与开发指南.md new file mode 100644 index 0000000..89f869d --- /dev/null +++ b/docs/项目全貌与开发指南.md @@ -0,0 +1,784 @@ +# 项目全貌与开发指南 + +> **文档定位**:`X:\work\yx`(包名 `email-forecast`)后续开发的唯一权威参考(系统核心知识库)。 +> **产品名**:邮件自动预报系统 +> **形态**:Next.js 运营 Web + IMAP Worker + MySQL,**不是**桌面助手、不是客服渠道。 +> **代码根目录**:`X:\work\yx` +> **文档版本**:v1.0 +> **编写日期**:2026-08-18 +> **对照材料**:`README.md`、`docs/邮箱项目.md`、`docx/` PRD/技术设计、当前源码。 +> **密钥纪律**:只写配置**键名与路径**,不写 IMAP 授权码、CC 密码、OAuth secret 的具体值。 + +与 `carriercentral-ai-assistant` 的关系:yx 是**运营后台**(拉邮件 → 解析 → 人工确认 → 写 CarrierCentral SaaS);助手仓是**桌面查询 Demo**(读仓)。两边打同一套海外仓 HTTP,但进程、UI、状态机完全独立。把 yx 能力迁到助手仓时,以本文模块名为地图,**禁止**把 Worker 的 SaveContainer 禁令或 IMAP 轮询直接塞进 Tkinter。 + +--- + +## 0. 阅读路径与权威优先级 + +材料冲突时,优先级从高到低: + +1. **正在运行的代码**:`src/app/**`、`src/services/**`、`src/worker/index.ts`、`prisma/schema.prisma` +2. **行为契约**:`docx/需求规格-邮件自动预报-v0.2.md`、`docx/接口/carriercentral客户端通用接口V1.72.md` +3. **协作入口**:`docs/邮箱项目.md`(红线、排障、上市清单) +4. **指令规则**:`docx/产品规则-邮件指令识别与拆分.md`、`docs/邮件指令提取规则.md` +5. **速查**:`README.md` + +改识别规则先改 `docx/产品规则-*` 再改 `src/services/parse/`。改 CC 路径以 V1.72 为准;文档外 endpoint 默认 mock。 + +--- + +## 1. 技术栈与全局依赖 + +### 1.1 系统形态 + +三进程,无独立消息队列、无 Redis、无 GraphQL。 + +| 组件 | 本仓库状态 | +|------|------------| +| 前端 | **Next.js 15 App Router** + **React 18** + **Ant Design 5** | +| 后端 | 同进程:`src/app/api/**/route.ts`(Route Handlers) | +| Worker | 独立进程 `src/worker/index.ts`(`tsx` 跑源码) | +| ORM | **Prisma 5** + **mysql2** | +| 数据库 | **MySQL 8**(库名 `email_forecast`,宿主机端口 **7023**) | +| 缓存 | **无 Redis**。CC token 在表 `cc_token_cache`;会话在 Cookie | +| 消息队列 | **无**。补偿/自动执行靠 Worker 轮询 `import_compensation.next_retry_at` | +| 状态管理 | **无 Redux/Pinia**。客户端 `useState` + hooks(`useMails` / `useMailDetail`)+ `AuthSessionProvider` | +| 包管理 | **仅 pnpm**(不要 npm/yarn) | + +### 1.2 关键三方库(`package.json`) + +| 包 | 用途 | +|----|------| +| `next` ^15.1.3 | App Router、API、生产 `next start -p 3100` | +| `react` / `react-dom` 18 | UI | +| `antd` ^5.22 + `@ant-design/icons` + `cssinjs` + React19 patch | 运营后台组件库 | +| `@prisma/client` / `prisma` | schema → MySQL | +| `mysql2` | Prisma 驱动;`db-lock.ts` 独立连接做 `GET_LOCK` | +| `iron-session` | Cookie 会话 `email_forecast_session` | +| `imapflow` | IMAP 拉取 / IDLE | +| `mailparser` | 解析 eml | +| `exceljs` | 装箱清单 / 转仓表 xlsx | +| `adm-zip` | zip 解一层 | +| `tesseract.js` | `OCR_PROVIDER=local` 启发式 OCR | +| `undici` | CC HTTP(`src/services/cc/http.ts`) | +| `zod` | env 与 API 入参校验 | +| `pino` | 结构化日志 `src/lib/logger.ts` | +| `uuid` | 幂等 key、补偿 `retry_token`、trace | +| `@tanstack/react-virtual` | 确认页大表 `ShipmentVirtualTable` | + +开发:`vitest`、`@playwright/test`、`tsx`、`typescript`。Lint:`next lint`(无独立 ESLint/Prettier 配置文件)。 + +### 1.3 本仓库明确没有的技术 + +- Vue / 桌面 Tkinter / LangChain Agent +- Redis / Kafka / RabbitMQ +- 多租户 SaaS 账号体系(只有 admin/ops 两角色) +- SMTP 自动回信、真实大模型指令识别(词表/规则) + +--- + +## 2. 目录结构详解 + +组织逻辑:**按运行时分层 + 按业务域分子目录**。 + +``` +页面/API(src/app) + → hooks / components(UI) + → services(imap / parse / import / cc) + → lib(env / session / prisma 辅助) + → prisma + MySQL +Worker(src/worker)直接调同一套 services,禁止走 ConfirmImport 写新增预报。 +``` + +### 2.1 仓库根 + +| 路径 | 职责 | +|------|------| +| `package.json` | 脚本与依赖;`name: email-forecast` | +| `pnpm-lock.yaml` | 锁文件 | +| `docker-compose.yml` | `mysql` + `web` + `worker` | +| `Dockerfile` | 多阶段 Node 20 bookworm;web 入口 `docker-entrypoint-web.sh` | +| `next.config.ts` | Antd transpile、关 dev 指示标、client 禁 `async_hooks` | +| `tsconfig.json` | `@/*` → `./src/*`,strict | +| `tsconfig.worker.json` | Worker 编译(compose 实际用 tsx 跑源码) | +| `vitest.config.ts` | 单测 + coverage | +| `playwright.config.ts` | e2e,baseURL `3100` | +| `playwright.test-mode.config.ts` | TEST_MODE 端口 3110 | +| `.env.example` | 环境变量模板 | +| `prisma/schema.prisma` | 全表 | +| `prisma/seed.ts` | 仅账号 + 部分 write capability | +| `scripts/` | 启动栈、IMAP/CC 烟雾、retention、样例导入 | +| `fixtures/emails/` | 01–16 金样例(spec + expected) | +| `docx/` | PRD / 技术设计 / UI / 韧性 / 金样例 JSON / CC 接口 | +| `docs/` | 协作文档 + 本知识库 + Windows 一键脚本 | +| `data/` | 运行时 eml/附件/日志(gitignore);compose 挂载 `./data` | +| `tests/unit` `tests/integration` `tests/e2e` | 测试 | + +### 2.2 `src/app/` — 页面与 API + +| 路径 | 职责 | +|------|------| +| `layout.tsx` | 根:`lang=zh-CN`、`AntdProvider`、标题「邮件自动预报」 | +| `globals.css` | 全局样式 | +| `error.tsx` | 错误页 | +| `page.tsx` | `/` → `redirect("/mails")` | +| `(auth)/login/page.tsx` | 登录 | +| `(ops)/layout.tsx` | `AuthGuard` + `TopNav` + `main.ops-main` | +| `(ops)/mails/page.tsx` | 邮件列表 | +| `(ops)/mails/[id]/page.tsx` | 详情 | +| `(ops)/mails/[id]/confirm/page.tsx` | 确认导入(按 instruction 分发视图) | +| `(ops)/logs/page.tsx` | 导入日志 / 拉取记录 | +| `(ops)/settings/page.tsx` | Admin 设置 | +| `(ops)/test-workbench/page.tsx` | `TEST_MODE=1` 测试台 | +| `api/**/route.ts` | 42 个 Route Handler(见第 6 节) | + +**无** `middleware.ts`。鉴权在各 route 的 `requireSession` / `requireAdmin`,页面靠客户端 `AuthSessionProvider`。 + +### 2.3 `src/services/` — 业务核 + +| 目录 | 职责 | +|------|------| +| `imap/` | `poller.ts` 轮询;`idle-supervisor.ts` IDLE;`snapshot.ts` 落 eml;`pull-filter.ts` 过滤;`mailbox-config.ts` 多箱;`stale-reaper.ts` 超时回收;`runtime-status.ts` 连续失败 | +| `parse/` | `pipeline.ts` 主解析;`classify.ts` 类型;`mail-intent.ts` 意图文案;`packing-list.ts` 装箱表;`split-instructions.ts` 指令拆分;`extract-container-header.ts` 柜头 | +| `import/` | `confirm.ts` **ConfirmImport**(唯一新增预报 SaveContainer);`compensation.ts` 补偿;`auto-exec.ts` 指令自动写;`confirm-do-upload.ts` / `confirm-batch-transfer.ts` / `confirm-work-order.ts` | +| `cc/` | `http.ts` `auth.ts` `save-container.ts` `do-upload.ts` `conflict.ts` `shipping-line.ts` `mock-*` `write-capability.ts` `settings-config.ts` | +| `ocr/` | `index.ts` local/aliyun/off;`sanitize.ts` | +| `oauth/` | `providers.ts` Google/MS;state HMAC | +| `customer/` | `resolve-sender.ts` 发件人→客户编码 | +| `alert/` | `webhook.ts` 企微/飞书 JSON POST | +| `retention/` | `cleanup.ts` | +| `test/` | TEST_MODE 工作台、fixture 评测 | +| `db.ts` | Prisma 单例 | +| `db-lock.ts` | **独立 mysql2 连接** `GET_LOCK` / `RELEASE_LOCK` | +| `state-machine.ts` | `assertTransition` / `resolveAfterParse` | +| `audit.ts` | 审计日志 | + +### 2.4 `src/lib/` `src/components/` `src/hooks/` `src/constants/` `src/types/` + +| 路径 | 职责 | +|------|------| +| `lib/env.ts` | Zod 校验环境;生产弱密钥拒绝启动 | +| `lib/session.ts` | iron-session;`verifyCredentials` **读 .env 账号,不读 `app_user` 表** | +| `lib/api.ts` | `ok`/`fail`、`requireSession`、`requireAdmin`、BigInt JSON | +| `lib/safe-path.ts` | 附件/快照必须在 `data/` 下 | +| `lib/cc-api-base.ts` | CC `api_base` 主机白名单(防 SSRF) | +| `lib/rate-limit.ts` | 进程内滑动窗口 | +| `lib/secret-crypto.ts` | AES-GCM,密钥派生自 `SESSION_SECRET` | +| `lib/logger.ts` | pino | +| `lib/mail-trace.ts` | 单封 `traceId` | +| `lib/test-mode.ts` / `test-mode-guard.ts` | TEST_MODE;测试 API 非测试环境返回 404 | +| `components/TopNav.tsx` | 顶栏:邮件 / 日志 / 设置(admin) / 测试台 | +| `components/MailTable.tsx` | 列表 | +| `components/confirm/*` | 确认页:预报 / DO / 转仓 / 工单 | +| `components/Cc*.tsx` | 对照 CC 表单的运营控件 | +| `hooks/useMails.ts` `useMailDetail.ts` `useImport.ts` `useCcUiMode.ts` | 客户端数据 | +| `constants/ui-copy.ts` `error-copy.ts` | 中文文案 | +| `types/mail.ts` | `MailType` / `MailStatus` / 柜头货件字段 | + +### 2.5 `src/worker/index.ts` + +常驻循环 `scheduleLoop`: + +1. `ImapPoller.tick()` +2. `staleReaper.run()`(`PARSING`/`IMPORTING` 超时打回) +3. `drainFetchedMails(10)` 消化 `FETCHED` +4. `processDueAutoExec(10)` +5. `processDueCompensations(10)` + +启动:`ImapPoller.clearStaleLock()`、`ensureCcWriteCapabilities()`、`IdleSupervisor.start()`。 +吞掉 IMAP `ETIMEOUT` 的 `uncaughtException`,避免整进程退出。 + +--- + +## 3. 入口与启动流程 + +### 3.1 入口一览 + +| 入口 | 实质 | +|------|------| +| `pnpm dev` / `dev:stack` | `scripts/dev-stack.mjs`:同时 `next dev -p 3100` + `tsx src/worker/index.ts` | +| `pnpm dev:web` | 仅 Web,**不拉信** | +| `pnpm worker` | 仅 Worker | +| `pnpm start` | 生产 Next `:3100` | +| `.\docs\start-system.ps1` | Windows:mysql 容器 + 本机 web/worker(默认不拉 node 镜像) | +| `pnpm compose:up` | docker 三件套 | +| `prisma/seed.ts` | 账号种子 | + +无 FastAPI `app.include_router`。Next 按文件系统挂载路由。 + +### 3.2 Web 启动全过程 + +``` +pnpm dev:web + → Next 加载 next.config.ts + → 首次请求触发 getEnv()(读 process.env,Zod) + → prisma 单例连 DATABASE_URL + → App Router: + / → redirect /mails + (ops)/* → 客户端 AuthSessionProvider 调 GET /api/auth/me + 未登录 → /login + → API:各 route 内 requireSession / requireAdmin +``` + +**没有**全局中间件链。登录:`POST /api/auth/login` → `verifyCredentials`(env)→ `session.save()`。 + +Docker web:`scripts/docker-entrypoint-web.sh`(`SEED_ON_START` 时可 db push + seed)→ `pnpm start`。compose 把 `DATABASE_URL` 改成 `mysql://app:app@mysql:3306/email_forecast`。 + +注意:`Dockerfile` `EXPOSE 3000`,实际监听 **3100**(`package.json` `start`)。 + +### 3.3 Worker 启动全过程 + +``` +tsx src/worker/index.ts + → getEnv() + → clearStaleLock(imap_poll) + → ensureCcWriteCapabilities() upsert 写能力行 + → IdleSupervisor.start() 各箱 IDLE(IMAP_IDLE_ENABLED 且账号 idle_enabled) + → scheduleLoop: + tick → 按 imap_settings.pollIntervalMs(否则 POLL_INTERVAL_MS)+ 连续失败退避 +``` + +数据库:compose `mysql` healthcheck 通过后 web/worker 才起。本地需先 `docker compose up -d mysql` + `pnpm exec prisma db push`。 + +--- + +## 4. 核心模块与依赖关系 + +### 4.1 模块划分 + +| 模块 | 关键文件 | 职责 | +|------|----------|------| +| 认证 | `lib/session.ts` `api/auth/*` | admin/ops Cookie | +| 邮箱绑定 | `imap/mailbox-config.ts` `api/settings/mailboxes*` `oauth/*` | 多箱、授权码/OAuth | +| IMAP 拉取 | `imap/poller.ts` `pull-filter.ts` `snapshot.ts` | 入库 `mail_message` | +| 解析 | `parse/pipeline.ts` `classify.ts` | 类型 + 柜头 + 货件 + 指令 | +| 状态机 | `state-machine.ts` | 合法迁移 | +| 确认导入 | `import/confirm.ts` | 预报 SaveContainer(仅 web) | +| 指令写 CC | `confirm-do-upload` / `confirm-batch-transfer` / `confirm-work-order` / `auto-exec` | DO/转仓/工单 | +| CC HTTP | `cc/http.ts` `auth.ts` | customerLogin + 写接口 | +| 冲突锁 | `cc/conflict.ts` `container_active_lock` | 同柜互斥 | +| 补偿 | `import/compensation.ts` | TIMEOUT_UNKNOWN 重试 ≤3 | +| 设置 | `api/settings/*` | IMAP 间隔、过滤、CC、OAuth | +| 可观测 | `api/health` `alert/webhook.ts` `imap-pull-log` | 健康与告警 | +| TEST | `api/test/*` `TestWorkbench` | 仅 TEST_MODE | + +调用方向(禁止反向): + +``` +页面 → hooks/client-api → Route Handler → services.* → prisma / CC / IMAP +Worker → services.imap|parse|import.compensation|auto-exec +Worker ──X── ConfirmImport.execute(新增预报) +``` + +### 4.2 公共层 + +| 能力 | 文件 | +|------|------| +| API 信封 `{ok, data}` / `{ok:false, error}` | `lib/api.ts` | +| 限流 | `lib/rate-limit.ts` | +| 命名锁 | `db-lock.ts`(禁止 Prisma 池直接 GET_LOCK) | +| 路径安全 | `safe-path.ts` | +| 审计 | `audit.ts` | +| 中文错误 | `constants/error-copy.ts` `formatOperatorError` | + +### 4.3 页面层级与组件树 + +``` +RootLayout (AntdProvider) + /login 无 TopNav + OpsLayout (AuthGuard + TopNav) + /mails MailTable + 筛选 + /mails/[id] 详情:摘要、附件、重新解析、改类型(admin) + /mails/[id]/confirm 按 instruction_id: + ForecastConfirmView + DoUploadConfirmView + TransferConfirmView + WorkOrderConfirmView + InstructionConfirmPicker + /logs 导入 + ImapPullLogsPanel + /settings Admin:ImapPullSettingsCard、邮箱、CcSettingsCard、OauthSettingsCard + /test-workbench TestWorkbench(TEST_MODE) +``` + +顶栏 `TopNav`:`ImapStatusBanner` + 菜单 + 角色 Tag + 登出。 + +确认页 `confirm/page.tsx`:`extractInstructionsFromMail` → `resolveConfirmInstruction`;无 `instruction_id` 时 `router.replace(confirmHref(...))`。 + +--- + +## 5. 数据模型与数据流 + +### 5.1 表结构(`prisma/schema.prisma` → MySQL) + +``` +mailbox_account 1──* mail_message 1──* mail_attachment + │ + ├──1 parse_result(柜头/货件 JSON) + └──* container_import 1──* import_compensation + +container_active_lock(柜号主键,跨邮件互斥) +cc_token_cache / cc_settings / cc_write_capability / oauth_settings +imap_settings / imap_filter_rule / imap_pull_log +sender_customer_map +app_user ← seed 写入;登录当前不查此表 +``` + +**MailboxAccount** `mailbox_account`:IMAP 主机/端口/用户;`password_enc` AES-GCM;`auth_type` `PASSWORD|OAUTH`;OAuth 令牌密文与过期;`idle_enabled`;`username` 唯一。 + +**MailMessage** `mail_message`:幂等 `message_id`、`(mailbox, folder, imap_uid)`、`raw_hash`;`mail_type`;`status`;`type_evidence` JSON;`snapshot_path`;`trace_id`;`version`(乐观锁,导入用);正文/OCR/线程字段。 + +**MailAttachment**:`sha256`、`path`(相对 `data/`)、`rejected`、`template_id`。 + +**ParseResult**:`container_header` / `shipments` / `lineage` JSON,与 `types/mail.ts` 的 `ContainerHeader`、`ShipmentRow` 对齐。 + +**ContainerImport**:每次向 CC 提交一柜;`shipments_hash` 参与唯一键 `uk_mail_container_shiphash`;`external_id` 为 CC 返回 id。 + +**ImportCompensation**:`reason`、`retry_count`/`max_retry`、`next_retry_at`、`retry_token` UUID。 + +**CcSettings** id=1:mock、api_base、saas、login_mark、username、`password_enc`。优先于 `.env`。 + +**CcWriteCapability**:`save_container` / `do_upload` / `transfer` / `batch_transfer` / `hold_split` / `label` / `customer_message`,mode `live|mock|off`。 + +**ImapSettings** id=1:`poll_interval_ms` 默认 1800000(30min),合法 3min~7d。 + +**ImapFilterRule**:`kind` + `value`(关键词/白名单/黑名单等)。 + +**ImapPullLog**:每封拉取结果,最多约 1000 条。 + +**AppUser**:username + sha256 `password_hash` + role。**现行登录走 env,此表不参与鉴权**(隐式债务)。 + +### 5.2 邮件状态机(`state-machine.ts`) + +| from | 允许 to | +|------|---------| +| FETCHED | PARSING, IGNORED | +| PARSING | PENDING_CONFIRM, PARSED, PARSE_FAILED, REJECTED_VALIDATION, AUTO_EXECUTING, FETCHED | +| PARSED | PARSING, PENDING_CONFIRM, AUTO_EXECUTING, IGNORED | +| PENDING_CONFIRM | IMPORTING, PARSING, IGNORED, PENDING_CONFIRM | +| IMPORTING | SUCCESS, PARTIAL_SUCCESS, FAILED | +| AUTO_EXECUTING | SUCCESS, FAILED, PARSED | +| SUCCESS | (终态) | +| PARTIAL_SUCCESS | IMPORTING | +| FAILED | IMPORTING, PENDING_CONFIRM, PARSING, AUTO_EXECUTING, IGNORED | +| PARSE_FAILED | PARSING, IGNORED | +| REJECTED_VALIDATION | PARSING, IGNORED, PENDING_CONFIRM | +| IGNORED | (终态) | + +非法迁移抛 `IllegalTransition`。`IMPORTING`/`SUCCESS`/`PARTIAL_SUCCESS` 禁止重新解析。 + +`resolveAfterParse`:`NEW_CONTAINER` 且有效货件≥1 → `PENDING_CONFIRM`;`WORK_ORDER`/`DO_UPLOAD`/`TRANSFER` → `PENDING_CONFIRM`;`autoExecEligible` → `AUTO_EXECUTING`;否则 `PARSED`。 + +### 5.3 邮件类型 `MailType`(`types/mail.ts`) + +`NEW_CONTAINER` | `TRANSFER` | `DO_UPLOAD` | `INSTRUCTION_HOLD_SPLIT` | `INSTRUCTION_LABEL` | `WORK_ORDER` | `UNKNOWN`。 + +分类在 `classify.ts`(主题/正文/附件名打分),不是 LLM。 + +### 5.4 主数据流 + +``` +IMAP UID + → pull-filter(黑名单拒绝 → 白名单必拉 → 关键词;皆空则 isBusinessRelevantMail) + → snapshot 落 data/ + Prisma upsert(幂等) + → 成功后 IMAP \Seen(失败且未入库则不标已读) + → status FETCHED + → ParsePipeline(Worker drain 或拉取后立即 parse) + → parse_result + PENDING_CONFIRM / PARSED / ... + → 运营打开 /mails/[id]/confirm + → POST /api/mails/:id/import + ConfirmImport.execute + version 校验 → 柜冲突检查 → 状态 IMPORTING + → saveContainer(CC 或 mock) + → SUCCESS / 补偿 TIMEOUT_UNKNOWN + → 浏览器列表刷新 +``` + +前端:`useMails` → `GET /api/mails`;详情 `useMailDetail` → `GET /api/mails/:id`。无全局 store。 + +导入状态 `ImportStatus`:`PENDING|IMPORTING|SUCCESS|FAILED|CONFLICT|TIMEOUT_UNKNOWN`。 + +--- + +## 6. API 接口与页面路由 + +统一响应:`{ ok: true, data }` 或 `{ ok: false, error: { code, message, details? } }`。BigInt 转字符串。 + +权限:`public` / `session`(admin|ops)/ `admin`。`/api/test/*` 另需 `TEST_MODE` 否则 404。 + +### 6.1 认证与健康 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| POST | `/api/auth/login` | public | 限流 10/分/IP+用户名;写 Cookie | +| POST | `/api/auth/logout` | Cookie | 销毁会话 | +| GET | `/api/auth/me` | session | 当前用户 | +| GET | `/api/health` | **public** | mysql、IMAP 告警、`cc_mode`/`cc_live_ready`、`test_mode`;勿塞密钥 | + +### 6.2 邮件 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| GET | `/api/mails` | session | 分页筛选 `mail_type`/`status`/`content_kind`/`q`/`sort` | +| DELETE | `/api/mails` | session | `ids` 或 `oldest` 批量删 | +| GET | `/api/mails/[id]` | session | 详情+解析+附件 | +| POST | `/api/mails/[id]/import` | session | ConfirmImport;10/分/用户;`force_skip_conflict` 仅 admin | +| POST | `/api/mails/[id]/reparse` | session | 重新解析 | +| POST | `/api/mails/[id]/type` | admin | `ENABLE_TYPE_OVERRIDE`;改 `mail_type` | +| POST | `/api/mails/[id]/ignore` | admin | → IGNORED | +| GET | `/api/mails/[id]/attachments/[attId]/download` | session | `resolveDataFile` 读盘 | +| POST | `/api/mails/[id]/ocr-attachments` | session | 触发附件 OCR | +| POST | `/api/mails/[id]/do/confirm` | session | DO 确认上传 | +| POST | `/api/mails/[id]/transfer/confirm` | session | 转仓确认 | +| GET | `/api/mails/[id]/transfer/gate` | session | 转仓门禁 | +| POST | `/api/mails/[id]/work-order/confirm` | session | 工单确认 | + +### 6.3 导入 / 补偿 / 船司 / 拉取日志 + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| GET | `/api/imports` | session | 导入日志 | +| POST | `/api/imports/[importId]/retry` | session;部分 admin | 失败重试 | +| POST | `/api/compensations/[id]/retry` | session / admin 分支 | 补偿重试 | +| GET | `/api/shipping-lines` | session | CC `GetShippingLineList` | +| GET | `/api/imap-pull-logs` | session | 拉取记录 | +| POST | `/api/admin/imap/poll` | admin | 手动拉一轮,锁等待 20s | + +### 6.4 设置(均 admin) + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET/PUT | `/api/settings/imap` | 拉取间隔 | +| GET/POST | `/api/settings/imap/filters` | 过滤规则 | +| PATCH/DELETE | `/api/settings/imap/filters/[id]` | 单条规则 | +| GET/POST | `/api/settings/mailboxes` | 邮箱列表/新增 | +| PATCH/DELETE | `/api/settings/mailboxes/[id]` | 改/删 | +| POST | `/api/settings/mailboxes/[id]/test` | 测连通 | +| POST | `/api/settings/mailboxes/probe` | 探测 | +| GET/PUT | `/api/settings/cc` | CC 连接(api_base 白名单) | +| POST | `/api/settings/cc/test` | CC 测试 | +| GET/PUT | `/api/settings/oauth` | OAuth 应用凭证 | + +### 6.5 OAuth + +| 方法 | 路径 | 权限 | 说明 | +|------|------|------|------| +| GET | `/api/oauth/providers` | (见源码) | 提供商是否配置 | +| GET/POST | `/api/oauth/start` | admin | 发起绑定,带 HMAC state | +| GET | `/api/oauth/callback/[provider]` | **无登录 Cookie** | 靠 state.actor;写 mailbox 后重定向 `/settings?oauth=` | + +### 6.6 TEST_MODE(`requireTestMode`) + +`/api/test/faults` `inject` `pipeline` `fixtures` `reset` `run-all` `target-requests`:注入样例、切故障、跑流水线。生产必须 `TEST_MODE=false`。 + +### 6.7 页面路由 + +| 路径 | 文件 | 说明 | +|------|------|------| +| `/` | `app/page.tsx` | 跳转 `/mails` | +| `/login` | `(auth)/login/page.tsx` | 登录 | +| `/mails` | `(ops)/mails/page.tsx` | 列表 | +| `/mails/[id]` | `(ops)/mails/[id]/page.tsx` | 详情 | +| `/mails/[id]/confirm` | `confirm/page.tsx` | 确认;query `instruction_id` | +| `/logs` | `(ops)/logs/page.tsx` | 日志 | +| `/settings` | `(ops)/settings/page.tsx` | Admin | +| `/test-workbench` | `(ops)/test-workbench/page.tsx` | 测试台 | + +--- + +## 7. 关键业务流程 + +### 7.1 登录 + +1. `POST /api/auth/login` `{username, password}` +2. `checkSlidingWindow` → `verifyCredentials` 比对 `APP_ADMIN_*` / `APP_OPS_*` +3. iron-session 写入 `username`+`role` +4. 客户端进 `(ops)`,`AuthSessionProvider` 调 `/api/auth/me` + +状态:未登录只能 `/login`;Cookie `HttpOnly`,生产 `secure`。 + +### 7.2 IMAP 拉取 → 解析(Worker) + +``` +Idle 或 定时 tick + → withMysqlNamedLock("imap_poll") + → resolveEnabledMailboxes(DB 优先,否则 .env IMAP_*) + → SEARCH SINCE lookback(默认 3 天,已读+未读) + → 每 UID:decidePull → 跳过则 stub + pull_log + → FETCH + parseMailSource + saveMailSnapshot + → upsert mail_message(P2002 幂等) + → ParsePipeline / 或 FETCHED 待 drain + → markSeen +``` + +关键函数:`ImapPoller.tick`、`pollMailbox`、`ParsePipeline`(`pipeline.ts`)、`drainFetchedMails`。 + +### 7.3 新增预报确认导入(核心交易) + +``` +运营:有效货件 + PENDING_CONFIRM + → /mails/[id]/confirm → ForecastConfirmView + → POST /api/mails/:id/import + version + idempotency_key + F_TransMode + F_OperationType + + container_header 白名单 + selected_row_indexes + → ConfirmImport.execute(import/confirm.ts) + assertTransition → IMPORTING + checkContainerConflict / acquireContainerActiveLock + buildSaveContainerEntity → saveContainer + live: ccAuth + POST /Container/SaveContainer + mock: mockRegisterContainer + 成功 SUCCESS + external_id + 超时 TIMEOUT_UNKNOWN → import_compensation +``` + +Worker **禁止**走这条 SaveContainer。门禁:`forecast-confirm-gate.ts` / `canEnterForecastConfirm`。 + +渠道未映射须 `ack_unmapped_channels`。强制跳冲突:Admin + `ENABLE_FORCE_IMPORT` + 审计。 + +### 7.4 DO / 转仓 / 工单 + +确认页按 `mail_type` 与 `instruction_id` 分发: + +- DO → `confirm-do-upload.ts` → `cc/do-upload.ts`(`SaveFieldValue` + `/learun/adms/annexes/upload`) +- 转仓 → `confirm-batch-transfer.ts` / `batch-transfer.ts`(默认 capability **mock**) +- 工单 → `confirm-work-order.ts` + +自动执行:`auto-exec.ts` + `ENABLE_AUTO_EXEC_*`;新增预报仍以人工确认为准。 + +### 7.5 补偿与超时回收 + +- `IMPORTING` 超过 `IMPORTING_TIMEOUT_MS`(默认 180s)→ reaper 打回 +- `PARSING` 超过 `PARSING_STALE_MS`(默认 600s)→ 回 FETCHED +- 补偿 due:`processDueCompensations` 最多 `COMPENSATION_MAX_RETRY=3` +- IMAP 连续失败 ≥3 且配置了 `ALERT_WEBHOOK_URL` → POST JSON(15 分钟去重) + +--- + +## 8. 配置与环境管理 + +### 8.1 切换方式 + +无独立 `config/dev.yml`。环境 = `.env` + 设置页 DB(IMAP/CC/OAuth **DB 优先于 env**)。 + +| 场景 | 做法 | +|------|------| +| 本地 | `.env` 指向 `localhost:7023`;`CC_MOCK=true` | +| Docker | compose 覆盖 `DATABASE_URL=@mysql:3306` | +| 生产 | `NODE_ENV=production` + 强密钥;弱口令 **拒绝启动**(`env.ts` `enforceProductionSecrets`) | +| TEST | `TEST_MODE=1`;与 `CC_MOCK` 正交 | + +换 `SESSION_SECRET`:全员登出,且 **邮箱密码密文无法解密,须重新保存**。 + +### 8.2 环境变量(`getEnv` / `.env.example`) + +| 键 | 含义 | +|----|------| +| `DATABASE_URL` | Prisma MySQL | +| `POLL_INTERVAL_MS` | Worker 间隔回退;设置页优先 | +| `DATA_RETENTION_DAYS` | 保留天数;Worker 不再自动删,用 `pnpm retention:cleanup` | +| `ENABLE_TYPE_OVERRIDE` | Admin 改邮件类型 | +| `ENABLE_FORCE_IMPORT` | 强制跳过柜冲突 | +| `ENABLE_ISO_CHECK` | 柜号 ISO6346 | +| `IMAP_*` | 主机/端口/用户/超时/lookback/每轮上限/IDLE/代理 | +| `CC_API_BASE` `CC_SAAS_HEADER` `CC_LOGIN_MARK` | SaaS | +| `CC_USERNAME` `CC_PASSWORD_PLAIN` `CC_PASSWORD_MD5` | 回退凭证 | +| `CC_MOCK` | 全局写 mock | +| `CC_HTTP_TIMEOUT_MS` `CC_READ_TIMEOUT_MS` | 写/读超时 | +| `APP_ADMIN_*` `APP_OPS_*` | **实际登录账号** | +| `SESSION_SECRET` | ≥32;会话 + AES 派生 | +| `TEST_MODE` | Mock IMAP/CC 故障注入 | +| `ALERT_WEBHOOK_URL` | 告警 | +| `OCR_PROVIDER` | `off\|local\|aliyun` | +| `OAUTH_*` | Gmail/MS 应用 | +| `ENABLE_AUTO_EXEC_*` `AUTO_EXEC_REQUIRE_CONFIRM` | 自动执行开关 | +| `SHIPMENT_ROW_SOFT/HARD_LIMIT` `ATTACHMENT_MAX_BYTES` | 清单/附件上限 | + +### 8.3 端口约定(红线) + +Web **3100**;MySQL 对外 **7023**。不要改成 3306/3000 当本地默认。 + +--- + +## 9. 构建、测试与部署 + +### 9.1 脚本(`package.json`) + +| 脚本 | 作用 | +|------|------| +| `dev` / `dev:stack` | web+worker | +| `dev:web` / `dev:turbo` | 仅 Next | +| `build` | `next build` + `tsc -p tsconfig.worker.json` | +| `start` | `next start -p 3100` | +| `worker` | `tsx src/worker/index.ts` | +| `lint` | `next lint` | +| `test` / `test:coverage` / `test:all` | vitest | +| `test:e2e` / `test:e2e:test-mode` | Playwright | +| `db:*` | generate / migrate / seed / push | +| `cc:smoke` `imap:smoke` `imap:poll` | 联调 | +| `compose:*` | docker | +| `retention:cleanup` | 清旧数据 | +| `postinstall` | `prisma generate` | + +### 9.2 日常命令 + +```bash +pnpm install +cp .env.example .env +docker compose up -d mysql +pnpm exec prisma db push +pnpm db:seed +pnpm dev # http://localhost:3100 admin/admin123 +pnpm test +pnpm test:e2e # 需已起服务或交给 webServer 拉 pnpm dev +pnpm build && pnpm start +``` + +Windows:`.\docs\start-system.ps1`(`-Mode compose` 需 Docker Hub)。 + +### 9.3 部署要点 + +- 必须 **web + worker** 都活;worker 建议**单副本**(全局 `imap_poll` 锁) +- 持久化:MySQL 卷 + `./data`(eml、附件、`imap-runtime.json`) +- 生产:`pnpm db:migrate`(`migrate deploy`),不要用弱口令 seed 覆盖生产 +- 健康检查:`GET /api/health` +- compose worker 命令必须是 `tsx src/worker/index.ts`(dist 里 `@/` 别名会 MODULE_NOT_FOUND) + +测试目录:`src/**/*.test.ts`、`tests/unit`、`tests/integration/pipeline-fixtures.test.ts`、`tests/e2e`。覆盖率排除 `src/app` 与 `src/components`。 + +--- + +## 10. 代码规范与约定 + +来源:`docs/邮箱项目.md` §4–5、源码实践。 + +| 项 | 约定 | +|----|------| +| 语言 | TypeScript strict;API 入参 Zod | +| 命名 | 文件 kebab-case;函数 camelCase;Prisma model PascalCase;表 snake_case | +| 路径别名 | `@/` = `src/` | +| API | `ok`/`fail`;错误码大写下划线(`UNAUTHORIZED`、`VERSION_CONFLICT`) | +| 日志 | pino 对象字段 + 短消息(`worker.imap_tick`);禁止打完整密码/token | +| UI 文案 | `src/constants/ui-copy.ts`;中文 | +| CC 柜头覆盖 | 仅 `pickContainerHeaderPatch` 白名单 | +| 读盘 | 只走 `resolveDataFile` / `assertUnderRoot` | +| 锁 | 只走 `db-lock.ts` | +| 状态 | 只走 `assertTransition` | +| 包管理 | 仅 pnpm | +| 提交 | 用户明确要求才 git commit | +| Lint | `next lint`;无 Prettier/Ruff 工程配置 | +| 破坏性变更 | 先改 `docx` 再改代码 | + +角色:ops 列表/详情/确认/重解析/下载;admin 另含改类型、设置、立即拉取、忽略、FORCE 导入。 + +--- + +## 11. 现有问题与注意事项 + +源码几乎无 `TODO/FIXME/HACK` 标记。已知项来自 README、协作文档与代码对照。 + +| ID | 说明 | +|----|------| +| AUTH-TABLE | `app_user` 被 seed,登录却只认 env。改密码易误改库表无效 | +| MOCK≠TEST | `CC_MOCK` 或设置页 Mock 的成功 **不算** 上线验收 | +| CAP-MOCK | `transfer`/`hold_split`/`label`/`customer_message` 默认 mock,V1.72 无正式表 | +| OCR-LOCAL | `OCR_PROVIDER=local` 是启发式,不是商用 OCR;aliyun 路径未当正式 SDK 宣传 | +| RATE-LIMIT | 限流是进程内 Map,多副本不共享 | +| LOCK | Prisma 池 GET_LOCK 会泄漏 → 永久 `lock_busy`;用 `scripts/clear-imap-lock.ts` | +| UNIT-DRIFT | README:`mail-intent` / `extract-header` / `four-business` / `work-order-dedupe` 与 expected 不完全一致 | +| COMP-SCOPE | 工单/DO/转仓 5xx 未统一进预报补偿表,靠详情一键重试 | +| DOCKER-PORT | Dockerfile EXPOSE 3000 vs 应用 3100 | +| HEALTH-PUBLIC | `/api/health` 无鉴权(docker 依赖) | +| FILTER | 关键字未命中 `IGNORED`/`SKIP_FILTER` 不进列表;`UNKNOWN` 仍可能 `PARSED` 供人工处理 | +| PARTIAL-CC | CC 对象有 data 但仍可能 `TIMEOUT_UNKNOWN` 进补偿(缺 external id) | + +高耦合:`ParsePipeline` 体量大(分类+xlsx+zip+OCR+船司匹配+指令拆分);`ConfirmImport` 与 CC 字段、状态机、锁强绑定。 + +风险区:`SESSION_SECRET` 兼会话与邮箱密文;worker 多开抢锁;附件路径穿越;CC `api_base` SSRF(已有白名单)。 + +红线(事故级,见 `docs/邮箱项目.md` §5):Worker 禁止新增预报 SaveContainer;不绕过状态机;不用 Prisma 池 GET_LOCK;mock 不当验收;zip 只解一层;冲突默认阻断;先落库再 `\Seen`;端口 7023/3100;密钥不入库。 + +--- + +## 12. 外部集成 + +### 12.1 CarrierCentral SaaS(核心) + +| 项 | 位置 | +|----|------| +| 基址 | `CC_API_BASE` 或 `cc_settings.api_base`(默认 `https://test.saas.carriercentral.vip/api`) | +| 头 | `Saas`、登录 token(`cc/auth.ts` `customerLogin`) | +| 契约 | `docx/接口/carriercentral客户端通用接口V1.72.md` | +| 登录 | `customerLogin`;token 缓存 `cc_token_cache` | +| 预报写入 | `POST /Container/SaveContainer`(`save-container.ts`) | +| 船司 | `GET GetShippingLineList`(`shipping-line.ts`) | +| DO | `SaveFieldValue` + `/learun/adms/annexes/upload` | +| Mock | `cc/mock-target-api.ts` `mock-state.ts`;设置页或 `CC_MOCK` | +| 主机约束 | `lib/cc-api-base.ts`:`*.carriercentral.vip` / localhost / 与 env 同主机 | + +CC 密码协议为 MD5;设置页明文则服务端哈希。Token 可能出现在 GET query,日志须脱敏。 + +### 12.2 IMAP 邮箱 + +| 项 | 位置 | +|----|------| +| 协议 | IMAPS 993(预设 `lib/imap-presets.ts`:QQ/163/Gmail/Outlook) | +| 客户端 | `imapflow`(`imap/client.ts`) | +| 凭证 | 设置页 AES 密文,或 `.env IMAP_USER/PASS` | +| OAuth | Google / Microsoft(`oauth/providers.ts`);回调写 `mailbox_account` | +| 代理 | `IMAP_PROXY`;默认仅 Gmail/Outlook 主机走代理 | + +### 12.3 OCR + +`OCR_PROVIDER`:`off` | `local`(tesseract.js)| `aliyun`(endpoint/ak 环境变量)。解析辅助,不是主识别。 + +### 12.4 告警 Webhook + +`ALERT_WEBHOOK_URL`:IMAP 连续失败或 CC 鉴权失败 POST JSON。空则只打日志。实现 `services/alert/webhook.ts`。 + +### 12.5 未集成 + +支付、短信、邮件 SMTP 发送、对象存储 SDK、LangChain、微信客服。附件只落本地 `data/`。 + +--- + +## 附录 A. 限制常量 + +| 常量 | 默认 | 位置 | +|------|------|------| +| 拉取间隔 | 30min(设置页) | `imap_settings` / `POLL_INTERVAL_MS` | +| lookback | 3 天 | `IMAP_LOOKBACK_DAYS` | +| 每箱每轮最多新信 | 100 | `IMAP_MAX_FETCH_PER_TICK` | +| 解析过期 | 600s | `PARSING_STALE_MS` | +| 导入超时 | 180s | `IMPORTING_TIMEOUT_MS` | +| 补偿次数 | 3 | `COMPENSATION_MAX_RETRY` | +| 货件行软/硬上限 | 1000 / 5000 | env | +| 附件 | 20MiB | `ATTACHMENT_MAX_BYTES` | +| 登录/导入限流 | 10/分钟 | login / import route | +| pull_log | ~1000 | `imap/pull-log.ts` | +| 间隔合法范围 | 3min~7d | 设置页 IMAP | + +--- + +## 附录 B. 与助手仓对照(迁移功能时用) + +| 维度 | yx(本文) | carriercentral-ai-assistant | +|------|------------|------------------------------| +| 形态 | Web 运营后台 | Windows Tkinter Demo | +| 主路径 | IMAP → 解析 → 确认 → SaveContainer | 自然语言 → ReAct → GET intelligent | +| CC 写 | 有(确认后) | 查询为主,附件下载 | +| 状态机 | 邮件 12 态 | 无邮件态 | +| 鉴权 | admin/ops Cookie | 无登录 | +| 禁止 | Worker 写新增预报 | 改 kf-ai、伪装客服 | + +迁功能原则:复用 **CC 契约与字段名**(`F_ContainerNo`、`SaveContainer`),不要把 Next/Prisma/IMAP 整包塞进助手;UI 保持助手现有 `ui_main.py` 分层,除非用户确认换栈。 + +--- + +## 附录 C. 后续开发引用协议 + +之后任何需求(含「把 yx 功能加到助手仓」): + +1. 先引用本文的**模块路径与函数名**,再给修改方案。 +2. 遵循现有分层、Zod 入参、状态机、pnpm、中文 `ui-copy`。 +3. 未经明确允许,不引入 Redis/新 ORM/第二套前端框架。 +4. Worker 与 ConfirmImport 职责拆分不破。 +5. 行为变更同步 `docx` 与本文对应章节;协作入口 `docs/邮箱项目.md` 红线保持。 diff --git a/docx/产品规则-邮件指令识别与拆分.md b/docx/产品规则-邮件指令识别与拆分.md index b2c5211..9a57dc4 100644 --- a/docx/产品规则-邮件指令识别与拆分.md +++ b/docx/产品规则-邮件指令识别与拆分.md @@ -36,7 +36,9 @@ | UI kind | 典型关键词(见词表) | 确认动作 | |---------|----------------------|----------| | `forecast` | 新增预报、请查收新增预报 | 预报表单(人工确认) | -| `transfer` | 新增转仓、转仓(「不转仓」除外) | 批量转仓 / 到仓后降级工单 | +| `forecast` | 柜号+派送单,请查收(首票卡派清单) | 预报表单(只读历史) | +| `transfer` | 新增转仓、转仓(「不转仓」除外) | 详情先展示转仓表单;**确认时**再查 CC 到仓闸门,不可转则自动降级工单 | +| `transfer` 截图 | 正文仅「新增转仓请留意」+ 内嵌 JPG 表格 | 对每张截图 OCR → 解析「地址仓→仓库重定向」;按时间轮与附件顺序一一对应展示 | | `work_order` | 贴标/换标/覆盖贴/操作指令、拍照、**指令性**拦截、快递单号;mail1 仅柜号+预约码 | 客户留言 SaveForm(人工确认) | | `do_upload` | DO请查收;或真实 `*DO*.pdf` 等 | 上传 DO(人工确认) | @@ -112,8 +114,18 @@ - **提拆派**:预报时 `F_OperationType=0` 表示提拆派服务包,**不是**三个独立管理端「提柜/拆柜/派送」界面。 - **mail1 类**(仅柜号+预约码、无标准关键词):`classify` → `WORK_ORDER`;详情用 `work_order_actions` 补当前工单单元(见 `extractMailInstructions({ workOrderActions })`)。 - 模板表尾「注意:…」是填写说明,**不是**货件行。 +- **预报未映射标签进备注**:主题/正文 `+` 分段里,已有 `名称:值` 前缀但对不上预报 UI 的字段(如 `封号:EMCEJJ0635`)写入 `F_MemoRemark`,**不得**当作客户名与 `YWZD` 一类短码竞争。 - 卡派清单列:附件有则填;UI **隐藏整列皆空**的 FormOrder 列,避免满屏「-」。 - 工单留言内容:取当前指令段,并从 `Dear` / `原箱号` 起,去掉转发头;**禁止**用整封线程正文盖住分段留言。 +- **附件 ↔ 指令段(全邮件通用,禁止按单封特判)**:业务附件挂到**本段**表单(工单 `F_AttachFile` / 出库 `F_LabelFile`),按优先级: + 1. **主键交叉**:附件名与本段正文/主题抽出同一业务主键则挂本段。主键:`FBA…`、原箱号 `YT…`、FNSKU `X0…`、唛头 `WL…`。**不用 ISO 柜号**分发工单附件(避免历史换标/拦截卡误挂当前文件)。 + 2. **文件名原文**:正文/主题出现该附件主文件名(去扩展名、去 `(1)` 副本后缀)→ 挂本段。 + 3. **角色兜底(仅当前可确认段)**:附件名无主键,但含 `卡转海` / `换标|覆盖贴|操作指令|贴标交付`,且本段也有对应角色词 → 挂当前段。历史只读段不用角色兜底。 + 4. **DO 文件**只进上传 DO 卡,不进工单附件。 + 5. **卡派资料/装箱清单 xlsx**是预报/转仓解析源,不进工单附件,除非规则 2 正文点名该文件。 + 6. 跳过 `*邮箱.pdf` / QQ 截图。一封多轮时每段独立匹配,**不得**把整封附件列表复制到每一张卡。 +- **贴标指令单 ≠ 卡派预报行**:`lineage.template=label-instruction` 时,货件行只服务工单/出库核对;**新增预报**货件列表必须为空,不得把换标行(仓码当 Address、无体积毛重)当成 SaveContainer 预报展示。预报柜号优先主题/正文,不以贴标单内其它柜号覆盖。 +- **同封多表格**:同时有卡派/提拆派 xlsx 与换标指令 xlsx 时,解析**优先卡派预报表**生成 `shipments`;换标文件仍作附件挂工单。 --- @@ -128,6 +140,7 @@ | UID 幂等拉取 | `poller.ts` `knownUids` | 集成/烟雾 | | 空 DO 不出指令 | `split-instructions` + `do-upload-extract` | `mail4-time-rounds`、`do-upload-extract` | | 工单留言清洗 | `CcWorkOrderForm` `extractWorkOrderMessageBody` | `four-business.test.ts` | +| 附件↔指令段 | `match-segment-attachments.ts` | `match-segment-attachments.test.ts`、`four-business.test.ts` | 改口径流程:**改本页 → 改词表/拆分 → 改/补单测 → `pnpm test` 相关文件通过**。 diff --git a/docx/邮件/样例附件清单.md b/docx/邮件/样例附件清单.md index 953f317..8c8594b 100644 --- a/docx/邮件/样例附件清单.md +++ b/docx/邮件/样例附件清单.md @@ -9,7 +9,7 @@ | **邮件1** | `QQ邮箱.pdf` | 根目录截图;无业务附件 | 一致 | ingest:`none` | | **邮件2** | `2邮箱.pdf` + `柜号:MATU2745683卡派资料.xlsx` | 根目录 | 一致 | ingest:`packing_xlsx` | | **邮件3** | `3邮箱.pdf`;卡转海 PDF 在子目录 `WHSU5574991 卡转海/` | 业务 PDF **平铺到 `邮件3/` 根** | 嵌套子目录;ingest 原先不挂附件 | 平铺 + ingest:`business` | -| **邮件4** | `4邮箱.pdf`;换标 xlsx/PDF 在子目录 `原箱号YT…换标操作指令/` | 业务文件 **平铺到 `邮件4/` 根** | 嵌套子目录;ingest 原先不挂附件 | 平铺 + ingest:`label_instruction` | +| **邮件4** | 换标 xlsx/PDF;预报卡派与 DO 原在 `模板/` | 业务文件平铺到 `邮件4/`:卡派 `智鸿2…提拆派.xlsx` + `WHSU8127240-DO.pdf` + 换标文件 | 预报真实附件未入邮件4 | 拷贝模板预报附件 + ingest 优先卡派表 | | **模板** | 预报/DO 共享模板 | `docx/邮件/模板/`(不属于某一封邮件) | 正确 | `ingest-forecast-ui.ts` 单独用 | ## 修正后期望树 @@ -29,12 +29,16 @@ docx/邮件/ WL99583-LAX9.pdf 邮件4/ 4邮箱.pdf + 智鸿2+WHLC027G597465+WHSU8127240+洛杉矶+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派.xlsx + WHSU8127240-DO.pdf FBA19HW52S0L-76CTN-HIA1-覆盖贴1箱贴2张.pdf X003UHDF7B-304PCS-覆盖贴1箱贴1张.pdf YT2604021091=76件换标走HIA1卡派出库操作指令.xlsx 模板/ 数据模版.xlsx 数据模版-新辰泽-WHSU8127240.xlsx + WHSU8127240-DO.pdf + 智鸿2+…提拆派.xlsx … ``` @@ -45,7 +49,7 @@ docx/邮件/ | mail1 | `none` | 无 | | mail2 | `packing_xlsx` | MATU 卡派 xlsx | | mail3 | `business` | 全部非截图业务文件(卡转海 PDF);解析仍主题短路 | -| mail4 | `label_instruction` | 换标 xlsx + 指令 PDF;pipeline 先试卡派再试贴标指令单 | +| mail4 | `packing+do+label` | 卡派提拆派 xlsx(预报货件)+ DO.pdf + 换标 xlsx/PDF;pipeline **优先卡派表** | ## 维护命令 diff --git a/docx/邮件/邮件4/WHSU8127240-DO.pdf b/docx/邮件/邮件4/WHSU8127240-DO.pdf new file mode 100644 index 0000000..316c879 Binary files /dev/null and b/docx/邮件/邮件4/WHSU8127240-DO.pdf differ diff --git a/docx/邮件/邮件4/数据模版-新辰泽-WHSU8127240.xlsx b/docx/邮件/邮件4/数据模版-新辰泽-WHSU8127240.xlsx new file mode 100644 index 0000000..596c5dc Binary files /dev/null and b/docx/邮件/邮件4/数据模版-新辰泽-WHSU8127240.xlsx differ diff --git a/docx/邮件/邮件4/智鸿2+WHLC027G597465+WHSU8127240+洛杉矶+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派.xlsx b/docx/邮件/邮件4/智鸿2+WHLC027G597465+WHSU8127240+洛杉矶+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派.xlsx new file mode 100644 index 0000000..ffd7bdf Binary files /dev/null and b/docx/邮件/邮件4/智鸿2+WHLC027G597465+WHSU8127240+洛杉矶+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派.xlsx differ diff --git a/docx/邮件/邮件4/测试.xls b/docx/邮件/邮件4/测试.xls new file mode 100644 index 0000000..a983026 Binary files /dev/null and b/docx/邮件/邮件4/测试.xls differ diff --git a/fixtures/emails/01-normal-forecast/expected.json b/fixtures/emails/01-normal-forecast/expected.json new file mode 100644 index 0000000..5b94646 --- /dev/null +++ b/fixtures/emails/01-normal-forecast/expected.json @@ -0,0 +1,26 @@ +{ + "mail_type": "NEW_CONTAINER", + "status": "PENDING_CONFIRM", + "last_error": null, + "container_no": "WHSU8127240", + "container_no_valid": true, + "selected_attachment": "数据模版-WHSU8127240.xlsx", + "shipment_count": 2, + "valid_shipment_count": 2, + "shipments": [ + { + "F_FBACode": "LAS1", + "F_Transporter": "TRUCK", + "F_CTNS": 10, + "row_status": "VALID" + }, + { + "F_FBACode": "FTW1", + "F_Transporter": "TRUCK", + "F_CTNS": 4, + "row_status": "VALID" + } + ], + "idempotent_with": null, + "pull": true +} diff --git a/fixtures/emails/01-normal-forecast/spec.json b/fixtures/emails/01-normal-forecast/spec.json new file mode 100644 index 0000000..5e64227 --- /dev/null +++ b/fixtures/emails/01-normal-forecast/spec.json @@ -0,0 +1,47 @@ +{ + "id": "01-normal-forecast", + "category": "normal", + "from": "ops@example.com", + "subject": "请查收新增预报 智鸿2+WHLC027G597465+WHSU8127240+洛杉矶+40HQ", + "text": "请查收新增预报:WHSU8127240 40HQ EDT2026.04-25 ETA2026.05-15 船名航次HMM EMERALD 013E+提拆派", + "messageId": "<01-normal-forecast@fixtures.local>", + "date": "2026-04-14T00:00:00.000Z", + "attachments": [ + { + "filename": "数据模版-WHSU8127240.xlsx", + "kind": "packing_xlsx", + "rows": [ + { + "仓库ID": "LAS1", + "渠道": "卡派", + "件数": 10, + "总体积": 2.5, + "毛重": 100, + "FBA ID": "FBA16SQQ7PTF", + "Amazon reference ID": "REF-001", + "分货标识": "MK-1", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + }, + { + "仓库ID": "FTW1", + "渠道": "TRUCK", + "件数": 4, + "总体积": 1.2, + "毛重": 40, + "FBA ID": "FBA16AAAAAAA", + "Amazon reference ID": "REF-002", + "分货标识": "MK-2", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + } + ] + } + ] +} diff --git a/fixtures/emails/02-normal-transfer/expected.json b/fixtures/emails/02-normal-transfer/expected.json new file mode 100644 index 0000000..d350a9a --- /dev/null +++ b/fixtures/emails/02-normal-transfer/expected.json @@ -0,0 +1,11 @@ +{ + "mail_type": "TRANSFER", + "status": "PENDING_CONFIRM", + "last_error": null, + "container_no": "MATU2745683", + "container_no_valid": true, + "selected_attachment": "柜号:MATU2745683卡派资料.xlsx", + "shipment_count": 1, + "valid_shipment_count": 1, + "pull": true +} diff --git a/fixtures/emails/02-normal-transfer/spec.json b/fixtures/emails/02-normal-transfer/spec.json new file mode 100644 index 0000000..54273dc --- /dev/null +++ b/fixtures/emails/02-normal-transfer/spec.json @@ -0,0 +1,25 @@ +{ + "id": "02-normal-transfer", + "category": "normal", + "from": "ops@example.com", + "subject": "MATU2745683 转仓指令", + "text": "Dear, 请将 MATU2745683 新增转仓:原仓 ABQ2 转到 FTW1,谢谢。", + "messageId": "<02-normal-transfer@fixtures.local>", + "date": "2026-04-14T01:00:00.000Z", + "attachments": [ + { + "filename": "柜号:MATU2745683卡派资料.xlsx", + "kind": "packing_xlsx", + "rows": [ + { + "仓库ID": "FTW1", + "渠道": "卡派", + "件数": 8, + "总体积": 3, + "毛重": 80, + "柜号": "MATU2745683" + } + ] + } + ] +} diff --git a/fixtures/emails/03-normal-do-upload/expected.json b/fixtures/emails/03-normal-do-upload/expected.json new file mode 100644 index 0000000..3e07cbb --- /dev/null +++ b/fixtures/emails/03-normal-do-upload/expected.json @@ -0,0 +1,10 @@ +{ + "mail_type": "DO_UPLOAD", + "status": "PENDING_CONFIRM", + "last_error": null, + "container_no": "WHSU8127240", + "container_no_valid": true, + "selected_attachment": "WHSU8127240-DO.pdf", + "shipment_count": 0, + "pull": true +} diff --git a/fixtures/emails/03-normal-do-upload/spec.json b/fixtures/emails/03-normal-do-upload/spec.json new file mode 100644 index 0000000..1d86ee2 --- /dev/null +++ b/fixtures/emails/03-normal-do-upload/spec.json @@ -0,0 +1,16 @@ +{ + "id": "03-normal-do-upload", + "category": "normal", + "from": "ops@example.com", + "subject": "WHSU8127240 DO请查收", + "text": "DO也同步上传至附件请注意查收!", + "messageId": "<03-normal-do-upload@fixtures.local>", + "date": "2026-04-14T02:00:00.000Z", + "attachments": [ + { + "filename": "WHSU8127240-DO.pdf", + "kind": "pdf", + "text": "DELIVERY ORDER WHSU8127240" + } + ] +} diff --git a/fixtures/emails/04-keyword-subject/expected.json b/fixtures/emails/04-keyword-subject/expected.json new file mode 100644 index 0000000..10b8e5f --- /dev/null +++ b/fixtures/emails/04-keyword-subject/expected.json @@ -0,0 +1,10 @@ +{ + "mail_type": "NEW_CONTAINER", + "status": "PENDING_CONFIRM", + "last_error": null, + "container_no": "WHSU8127240", + "selected_attachment": "数据模版.xlsx", + "shipment_count": 2, + "valid_shipment_count": 2, + "pull": true +} diff --git a/fixtures/emails/04-keyword-subject/spec.json b/fixtures/emails/04-keyword-subject/spec.json new file mode 100644 index 0000000..914b531 --- /dev/null +++ b/fixtures/emails/04-keyword-subject/spec.json @@ -0,0 +1,47 @@ +{ + "id": "04-keyword-subject", + "category": "boundary", + "from": "ops@example.com", + "subject": "新增预报 WHSU8127240", + "text": "附件是数据模版,请处理。", + "messageId": "<04-keyword-subject@fixtures.local>", + "date": "2026-04-14T03:00:00.000Z", + "attachments": [ + { + "filename": "数据模版.xlsx", + "kind": "packing_xlsx", + "rows": [ + { + "仓库ID": "LAS1", + "渠道": "卡派", + "件数": 10, + "总体积": 2.5, + "毛重": 100, + "FBA ID": "FBA16SQQ7PTF", + "Amazon reference ID": "REF-001", + "分货标识": "MK-1", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + }, + { + "仓库ID": "FTW1", + "渠道": "TRUCK", + "件数": 4, + "总体积": 1.2, + "毛重": 40, + "FBA ID": "FBA16AAAAAAA", + "Amazon reference ID": "REF-002", + "分货标识": "MK-2", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + } + ] + } + ] +} diff --git a/fixtures/emails/05-keyword-body/expected.json b/fixtures/emails/05-keyword-body/expected.json new file mode 100644 index 0000000..33c0a87 --- /dev/null +++ b/fixtures/emails/05-keyword-body/expected.json @@ -0,0 +1,9 @@ +{ + "mail_type": "NEW_CONTAINER", + "status": "PENDING_CONFIRM", + "last_error": null, + "container_no": "WHSU8127240", + "selected_attachment": "数据模版.xlsx", + "shipment_count": 2, + "pull": true +} diff --git a/fixtures/emails/05-keyword-body/spec.json b/fixtures/emails/05-keyword-body/spec.json new file mode 100644 index 0000000..1121236 --- /dev/null +++ b/fixtures/emails/05-keyword-body/spec.json @@ -0,0 +1,47 @@ +{ + "id": "05-keyword-body", + "category": "boundary", + "from": "ops@example.com", + "subject": "WHSU8127240 资料", + "text": "请查收新增预报,柜号 WHSU8127240,40HQ。", + "messageId": "<05-keyword-body@fixtures.local>", + "date": "2026-04-14T04:00:00.000Z", + "attachments": [ + { + "filename": "数据模版.xlsx", + "kind": "packing_xlsx", + "rows": [ + { + "仓库ID": "LAS1", + "渠道": "卡派", + "件数": 10, + "总体积": 2.5, + "毛重": 100, + "FBA ID": "FBA16SQQ7PTF", + "Amazon reference ID": "REF-001", + "分货标识": "MK-1", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + }, + { + "仓库ID": "FTW1", + "渠道": "TRUCK", + "件数": 4, + "总体积": 1.2, + "毛重": 40, + "FBA ID": "FBA16AAAAAAA", + "Amazon reference ID": "REF-002", + "分货标识": "MK-2", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + } + ] + } + ] +} diff --git a/fixtures/emails/06-mixed-en-zh/expected.json b/fixtures/emails/06-mixed-en-zh/expected.json new file mode 100644 index 0000000..33c0a87 --- /dev/null +++ b/fixtures/emails/06-mixed-en-zh/expected.json @@ -0,0 +1,9 @@ +{ + "mail_type": "NEW_CONTAINER", + "status": "PENDING_CONFIRM", + "last_error": null, + "container_no": "WHSU8127240", + "selected_attachment": "数据模版.xlsx", + "shipment_count": 2, + "pull": true +} diff --git a/fixtures/emails/06-mixed-en-zh/spec.json b/fixtures/emails/06-mixed-en-zh/spec.json new file mode 100644 index 0000000..753f646 --- /dev/null +++ b/fixtures/emails/06-mixed-en-zh/spec.json @@ -0,0 +1,47 @@ +{ + "id": "06-mixed-en-zh", + "category": "boundary", + "from": "ops@example.com", + "subject": "Please check 新增预报 WHSU8127240 / 40HQ ETA 2026-05-15", + "text": "Hi team, 请查收新增预报 packing list attached. Vessel HMM EMERALD 013E.", + "messageId": "<06-mixed-en-zh@fixtures.local>", + "date": "2026-04-14T05:00:00.000Z", + "attachments": [ + { + "filename": "数据模版.xlsx", + "kind": "packing_xlsx", + "rows": [ + { + "仓库ID": "LAS1", + "渠道": "卡派", + "件数": 10, + "总体积": 2.5, + "毛重": 100, + "FBA ID": "FBA16SQQ7PTF", + "Amazon reference ID": "REF-001", + "分货标识": "MK-1", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + }, + { + "仓库ID": "FTW1", + "渠道": "TRUCK", + "件数": 4, + "总体积": 1.2, + "毛重": 40, + "FBA ID": "FBA16AAAAAAA", + "Amazon reference ID": "REF-002", + "分货标识": "MK-2", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + } + ] + } + ] +} diff --git a/fixtures/emails/07-gbk-body/expected.json b/fixtures/emails/07-gbk-body/expected.json new file mode 100644 index 0000000..33c0a87 --- /dev/null +++ b/fixtures/emails/07-gbk-body/expected.json @@ -0,0 +1,9 @@ +{ + "mail_type": "NEW_CONTAINER", + "status": "PENDING_CONFIRM", + "last_error": null, + "container_no": "WHSU8127240", + "selected_attachment": "数据模版.xlsx", + "shipment_count": 2, + "pull": true +} diff --git a/fixtures/emails/07-gbk-body/spec.json b/fixtures/emails/07-gbk-body/spec.json new file mode 100644 index 0000000..28354cf --- /dev/null +++ b/fixtures/emails/07-gbk-body/spec.json @@ -0,0 +1,48 @@ +{ + "id": "07-gbk-body", + "category": "boundary", + "from": "ops@example.com", + "subject": "Forecast WHSU8127240", + "text": "请查收新增预报 WHSU8127240", + "charset": "gbk", + "messageId": "<07-gbk-body@fixtures.local>", + "date": "2026-04-14T06:00:00.000Z", + "attachments": [ + { + "filename": "数据模版.xlsx", + "kind": "packing_xlsx", + "rows": [ + { + "仓库ID": "LAS1", + "渠道": "卡派", + "件数": 10, + "总体积": 2.5, + "毛重": 100, + "FBA ID": "FBA16SQQ7PTF", + "Amazon reference ID": "REF-001", + "分货标识": "MK-1", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + }, + { + "仓库ID": "FTW1", + "渠道": "TRUCK", + "件数": 4, + "总体积": 1.2, + "毛重": 40, + "FBA ID": "FBA16AAAAAAA", + "Amazon reference ID": "REF-002", + "分货标识": "MK-2", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + } + ] + } + ] +} diff --git a/fixtures/emails/08-oversized-attachment/expected.json b/fixtures/emails/08-oversized-attachment/expected.json new file mode 100644 index 0000000..58b872c --- /dev/null +++ b/fixtures/emails/08-oversized-attachment/expected.json @@ -0,0 +1,8 @@ +{ + "mail_type": "NEW_CONTAINER", + "status": "REJECTED_VALIDATION", + "last_error": "ATTACHMENT_TOO_LARGE", + "container_no": "WHSU8127240", + "selected_attachment": null, + "pull": true +} diff --git a/fixtures/emails/08-oversized-attachment/spec.json b/fixtures/emails/08-oversized-attachment/spec.json new file mode 100644 index 0000000..bb055e3 --- /dev/null +++ b/fixtures/emails/08-oversized-attachment/spec.json @@ -0,0 +1,16 @@ +{ + "id": "08-oversized-attachment", + "category": "boundary", + "from": "ops@example.com", + "subject": "请查收新增预报 WHSU8127240", + "text": "请查收新增预报,附件较大。", + "messageId": "<08-oversized-attachment@fixtures.local>", + "date": "2026-04-14T07:00:00.000Z", + "attachments": [ + { + "filename": "数据模版.xlsx", + "kind": "oversized", + "bytes": 20971521 + } + ] +} diff --git a/fixtures/emails/09-no-attachment/expected.json b/fixtures/emails/09-no-attachment/expected.json new file mode 100644 index 0000000..304f8bd --- /dev/null +++ b/fixtures/emails/09-no-attachment/expected.json @@ -0,0 +1,9 @@ +{ + "mail_type": "NEW_CONTAINER", + "status": "REJECTED_VALIDATION", + "last_error": "NO_SHIPMENT_ROWS", + "container_no": "WHSU8127240", + "selected_attachment": null, + "shipment_count": 0, + "pull": true +} diff --git a/fixtures/emails/09-no-attachment/spec.json b/fixtures/emails/09-no-attachment/spec.json new file mode 100644 index 0000000..adc3a72 --- /dev/null +++ b/fixtures/emails/09-no-attachment/spec.json @@ -0,0 +1,10 @@ +{ + "id": "09-no-attachment", + "category": "abnormal", + "from": "ops@example.com", + "subject": "请查收新增预报 WHSU8127240 40HQ", + "text": "请查收新增预报,稍后补附件。", + "messageId": "<09-no-attachment@fixtures.local>", + "date": "2026-04-14T08:00:00.000Z", + "attachments": [] +} diff --git a/fixtures/emails/10-corrupt-xlsx/expected.json b/fixtures/emails/10-corrupt-xlsx/expected.json new file mode 100644 index 0000000..772e981 --- /dev/null +++ b/fixtures/emails/10-corrupt-xlsx/expected.json @@ -0,0 +1,9 @@ +{ + "mail_type": "NEW_CONTAINER", + "pipeline_mail_type": "UNKNOWN", + "status": "PARSE_FAILED", + "last_error": "MIME_PARSE_ERROR", + "container_no": "WHSU8127240", + "selected_attachment": "数据模版.xlsx", + "pull": true +} diff --git a/fixtures/emails/10-corrupt-xlsx/spec.json b/fixtures/emails/10-corrupt-xlsx/spec.json new file mode 100644 index 0000000..c002acb --- /dev/null +++ b/fixtures/emails/10-corrupt-xlsx/spec.json @@ -0,0 +1,15 @@ +{ + "id": "10-corrupt-xlsx", + "category": "abnormal", + "from": "ops@example.com", + "subject": "请查收新增预报 WHSU8127240", + "text": "请查收新增预报,附件已损坏。", + "messageId": "<10-corrupt-xlsx@fixtures.local>", + "date": "2026-04-14T09:00:00.000Z", + "attachments": [ + { + "filename": "数据模版.xlsx", + "kind": "corrupt_xlsx" + } + ] +} diff --git a/fixtures/emails/11-wrong-format/expected.json b/fixtures/emails/11-wrong-format/expected.json new file mode 100644 index 0000000..b8c9e2d --- /dev/null +++ b/fixtures/emails/11-wrong-format/expected.json @@ -0,0 +1,8 @@ +{ + "mail_type": "NEW_CONTAINER", + "status": "PARSE_FAILED", + "last_error": "NO_SUPPORTED_SPREADSHEET", + "container_no": "WHSU8127240", + "selected_attachment": null, + "pull": true +} diff --git a/fixtures/emails/11-wrong-format/spec.json b/fixtures/emails/11-wrong-format/spec.json new file mode 100644 index 0000000..96f2634 --- /dev/null +++ b/fixtures/emails/11-wrong-format/spec.json @@ -0,0 +1,17 @@ +{ + "id": "11-wrong-format", + "category": "abnormal", + "from": "ops@example.com", + "subject": "请查收新增预报 WHSU8127240", + "text": "请查收新增预报,附件是说明文档。", + "messageId": "<11-wrong-format@fixtures.local>", + "date": "2026-04-14T10:00:00.000Z", + "attachments": [ + { + "filename": "说明.docx", + "kind": "raw", + "content": "this is not a spreadsheet", + "contentType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + } + ] +} diff --git a/fixtures/emails/12-missing-required/expected.json b/fixtures/emails/12-missing-required/expected.json new file mode 100644 index 0000000..bfed67d --- /dev/null +++ b/fixtures/emails/12-missing-required/expected.json @@ -0,0 +1,10 @@ +{ + "mail_type": "NEW_CONTAINER", + "status": "PARSE_FAILED", + "last_error": "missing_columns", + "container_no": "WHSU8127240", + "selected_attachment": "数据模版.xlsx", + "missing_columns": true, + "shipment_count": 0, + "pull": true +} diff --git a/fixtures/emails/12-missing-required/spec.json b/fixtures/emails/12-missing-required/spec.json new file mode 100644 index 0000000..0143a7c --- /dev/null +++ b/fixtures/emails/12-missing-required/spec.json @@ -0,0 +1,25 @@ +{ + "id": "12-missing-required", + "category": "abnormal", + "from": "ops@example.com", + "subject": "请查收新增预报 WHSU8127240", + "text": "请查收新增预报。", + "messageId": "<12-missing-required@fixtures.local>", + "date": "2026-04-14T11:00:00.000Z", + "attachments": [ + { + "filename": "数据模版.xlsx", + "kind": "packing_xlsx", + "headers": [ + "备注", + "柜号" + ], + "rows": [ + { + "备注": "只有备注没有必填列", + "柜号": "WHSU8127240" + } + ] + } + ] +} diff --git a/fixtures/emails/13-illegal-date/expected.json b/fixtures/emails/13-illegal-date/expected.json new file mode 100644 index 0000000..e3c76b5 --- /dev/null +++ b/fixtures/emails/13-illegal-date/expected.json @@ -0,0 +1,19 @@ +{ + "mail_type": "NEW_CONTAINER", + "status": "PENDING_CONFIRM", + "last_error": null, + "container_no": "WHSU8127240", + "selected_attachment": "数据模版.xlsx", + "shipment_count": 1, + "valid_shipment_count": 1, + "shipments": [ + { + "F_FBACode": "LAS1", + "F_Transporter": "TRUCK", + "F_CTNS": 2, + "row_status": "VALID", + "F_Expected_DeliveryDateB": null + } + ], + "pull": true +} diff --git a/fixtures/emails/13-illegal-date/spec.json b/fixtures/emails/13-illegal-date/spec.json new file mode 100644 index 0000000..c2d5931 --- /dev/null +++ b/fixtures/emails/13-illegal-date/spec.json @@ -0,0 +1,27 @@ +{ + "id": "13-illegal-date", + "category": "abnormal", + "from": "ops@example.com", + "subject": "请查收新增预报 WHSU8127240", + "text": "请查收新增预报。", + "messageId": "<13-illegal-date@fixtures.local>", + "date": "2026-04-14T12:00:00.000Z", + "attachments": [ + { + "filename": "数据模版.xlsx", + "kind": "packing_xlsx", + "rows": [ + { + "仓库ID": "LAS1", + "渠道": "卡派", + "件数": 2, + "总体积": 1, + "毛重": 20, + "柜号": "WHSU8127240", + "最早送仓日期": "not-a-date", + "最晚送仓日期": "99/99/99" + } + ] + } + ] +} diff --git a/fixtures/emails/14-duplicate-pull/expected.json b/fixtures/emails/14-duplicate-pull/expected.json new file mode 100644 index 0000000..225321c --- /dev/null +++ b/fixtures/emails/14-duplicate-pull/expected.json @@ -0,0 +1,9 @@ +{ + "mail_type": "NEW_CONTAINER", + "status": "PENDING_CONFIRM", + "last_error": null, + "container_no": "WHSU8127240", + "selected_attachment": "数据模版-WHSU8127240.xlsx", + "idempotent_with": "01-normal-forecast", + "pull": true +} diff --git a/fixtures/emails/14-duplicate-pull/spec.json b/fixtures/emails/14-duplicate-pull/spec.json new file mode 100644 index 0000000..e9017ab --- /dev/null +++ b/fixtures/emails/14-duplicate-pull/spec.json @@ -0,0 +1,47 @@ +{ + "id": "14-duplicate-pull", + "category": "duplicate", + "from": "ops@example.com", + "subject": "请查收新增预报 智鸿2+WHLC027G597465+WHSU8127240+洛杉矶+40HQ", + "text": "请查收新增预报:WHSU8127240 40HQ EDT2026.04-25 ETA2026.05-15 船名航次HMM EMERALD 013E+提拆派", + "messageId": "<01-normal-forecast@fixtures.local>", + "date": "2026-04-14T00:00:00.000Z", + "attachments": [ + { + "filename": "数据模版-WHSU8127240.xlsx", + "kind": "packing_xlsx", + "rows": [ + { + "仓库ID": "LAS1", + "渠道": "卡派", + "件数": 10, + "总体积": 2.5, + "毛重": 100, + "FBA ID": "FBA16SQQ7PTF", + "Amazon reference ID": "REF-001", + "分货标识": "MK-1", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + }, + { + "仓库ID": "FTW1", + "渠道": "TRUCK", + "件数": 4, + "总体积": 1.2, + "毛重": 40, + "FBA ID": "FBA16AAAAAAA", + "Amazon reference ID": "REF-002", + "分货标识": "MK-2", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + } + ] + } + ] +} diff --git a/fixtures/emails/15-multi-attachment/expected.json b/fixtures/emails/15-multi-attachment/expected.json new file mode 100644 index 0000000..2846d23 --- /dev/null +++ b/fixtures/emails/15-multi-attachment/expected.json @@ -0,0 +1,16 @@ +{ + "mail_type": "WORK_ORDER", + "status": "PENDING_CONFIRM", + "last_error": null, + "container_no": "WHSU8127240", + "selected_attachment": "数据模版-WHSU8127240.xlsx", + "selected_attachments": [ + "YT2604021091=76件换标走HIA1卡派出库操作指令.xlsx", + "WHSU8127240-DO.pdf", + "数据模版-WHSU8127240.xlsx", + "4邮箱.pdf" + ], + "shipment_count": 2, + "valid_shipment_count": 2, + "pull": true +} diff --git a/fixtures/emails/15-multi-attachment/spec.json b/fixtures/emails/15-multi-attachment/spec.json new file mode 100644 index 0000000..0a13b65 --- /dev/null +++ b/fixtures/emails/15-multi-attachment/spec.json @@ -0,0 +1,69 @@ +{ + "id": "15-multi-attachment", + "category": "multi_attachment", + "from": "ops@example.com", + "subject": "请查收新增预报 WHSU8127240,DO请查收", + "text": "请查收新增预报。DO也同步上传至附件。", + "messageId": "<15-multi-attachment@fixtures.local>", + "date": "2026-04-14T13:00:00.000Z", + "attachments": [ + { + "filename": "YT2604021091=76件换标走HIA1卡派出库操作指令.xlsx", + "kind": "packing_xlsx", + "rows": [ + { + "仓库ID": "HIA1", + "渠道": "换标", + "件数": 76, + "柜号": "XXXX0000000" + } + ] + }, + { + "filename": "WHSU8127240-DO.pdf", + "kind": "pdf", + "text": "DO" + }, + { + "filename": "数据模版-WHSU8127240.xlsx", + "kind": "packing_xlsx", + "rows": [ + { + "仓库ID": "LAS1", + "渠道": "卡派", + "件数": 10, + "总体积": 2.5, + "毛重": 100, + "FBA ID": "FBA16SQQ7PTF", + "Amazon reference ID": "REF-001", + "分货标识": "MK-1", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + }, + { + "仓库ID": "FTW1", + "渠道": "TRUCK", + "件数": 4, + "总体积": 1.2, + "毛重": 40, + "FBA ID": "FBA16AAAAAAA", + "Amazon reference ID": "REF-002", + "分货标识": "MK-2", + "派送地址": "/", + "备注": "/", + "柜号": "WHSU8127240", + "最早送仓日期": "2026-05-20", + "最晚送仓日期": "2026-05-25" + } + ] + }, + { + "filename": "4邮箱.pdf", + "kind": "pdf", + "text": "screenshot" + } + ] +} diff --git a/fixtures/emails/16-unknown-format/expected.json b/fixtures/emails/16-unknown-format/expected.json new file mode 100644 index 0000000..ab0cf30 --- /dev/null +++ b/fixtures/emails/16-unknown-format/expected.json @@ -0,0 +1,7 @@ +{ + "mail_type": "UNKNOWN", + "status": "IGNORED", + "last_error": "SKIP_FILTER", + "selected_attachment": null, + "pull": false +} diff --git a/fixtures/emails/16-unknown-format/spec.json b/fixtures/emails/16-unknown-format/spec.json new file mode 100644 index 0000000..829151c --- /dev/null +++ b/fixtures/emails/16-unknown-format/spec.json @@ -0,0 +1,10 @@ +{ + "id": "16-unknown-format", + "category": "abnormal", + "from": "random@example.com", + "subject": "Weekly newsletter", + "text": "Hello, this is a marketing newsletter about office snacks.", + "messageId": "<16-unknown-format@fixtures.local>", + "date": "2026-04-14T14:00:00.000Z", + "attachments": [] +} diff --git a/next.config.ts b/next.config.ts index 15f6080..eb2d5bf 100644 --- a/next.config.ts +++ b/next.config.ts @@ -14,6 +14,15 @@ const nextConfig: NextConfig = { experimental: { optimizePackageImports: ["antd", "@ant-design/icons"], }, + webpack: (config, { isServer }) => { + if (!isServer) { + config.resolve.fallback = { + ...(config.resolve.fallback || {}), + async_hooks: false, + }; + } + return config; + }, }; export default nextConfig; diff --git a/package.json b/package.json index 206ad0a..b0c54fc 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,10 @@ "lint": "next lint", "test": "vitest run", "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "test:all": "vitest run --coverage", "test:e2e": "playwright test", + "test:e2e:test-mode": "playwright test -c playwright.test-mode.config.ts", "db:generate": "prisma generate", "db:migrate": "prisma migrate deploy", "db:migrate:dev": "prisma migrate dev", @@ -53,6 +56,7 @@ "pino": "^9.6.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "tesseract.js": "^7.0.0", "undici": "^6.21.0", "uuid": "^11.0.3", "zod": "^3.24.1" @@ -66,6 +70,7 @@ "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", "@types/uuid": "^10.0.0", + "@vitest/coverage-v8": "^2.1.9", "prisma": "^5.22.0", "tsx": "^4.19.2", "typescript": "^5.7.2", diff --git a/playwright.config.ts b/playwright.config.ts index 065c46d..fb773f8 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -2,6 +2,7 @@ import { defineConfig, devices } from "@playwright/test"; export default defineConfig({ testDir: "./tests/e2e", + testIgnore: "test-mode-pipeline.spec.ts", timeout: 60_000, use: { baseURL: "http://127.0.0.1:3100", diff --git a/playwright.test-mode.config.ts b/playwright.test-mode.config.ts new file mode 100644 index 0000000..f7c91b7 --- /dev/null +++ b/playwright.test-mode.config.ts @@ -0,0 +1,31 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * TEST_MODE=1 专用 E2E:注入 fixture → UI → 提交 MockTargetAPI。 + * 需要本机 MySQL(docker compose up -d mysql)。 + */ +export default defineConfig({ + testDir: "./tests/e2e", + testMatch: "test-mode-pipeline.spec.ts", + timeout: 90_000, + use: { + baseURL: "http://127.0.0.1:3110", + trace: "on-first-retry", + }, + projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }], + webServer: { + command: "pnpm exec next dev -p 3110", + url: "http://127.0.0.1:3110", + reuseExistingServer: false, + timeout: 120_000, + env: { + ...process.env, + TEST_MODE: "1", + OCR_PROVIDER: "off", + ENABLE_FORCE_IMPORT: "1", + DATABASE_URL: + process.env.DATABASE_URL || + "mysql://app:app@localhost:7023/email_forecast", + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b648848..81ba18e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + tesseract.js: + specifier: ^7.0.0 + version: 7.0.0 undici: specifier: ^6.21.0 version: 6.27.0 @@ -90,6 +93,9 @@ importers: '@types/uuid': specifier: ^10.0.0 version: 10.0.0 + '@vitest/coverage-v8': + specifier: ^2.1.9 + version: 2.1.9(vitest@2.1.9(@types/node@20.19.43)) prisma: specifier: ^5.22.0 version: 5.22.0 @@ -105,6 +111,10 @@ importers: packages: + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@ant-design/colors@7.2.1': resolution: {integrity: sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==} @@ -147,10 +157,30 @@ packages: react: '>=19.0.0' react-dom: '>=19.0.0' + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/runtime@7.29.7': resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@emnapi/runtime@1.11.2': resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} @@ -597,9 +627,27 @@ packages: cpu: [x64] os: [win32] + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@next/env@15.5.21': resolution: {integrity: sha512-hjJI/GfrjWHgNguRIBzItjRRu0m3Nrz17GhxsjuHfjIvg9hyg3239REd2dpI+bpMTFuVrVprHzEQ19m++cDtbw==} @@ -654,6 +702,10 @@ packages: '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@playwright/test@1.61.1': resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==} engines: {node: '>=18'} @@ -912,6 +964,15 @@ packages: '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@vitest/coverage-v8@2.1.9': + resolution: {integrity: sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==} + peerDependencies: + '@vitest/browser': 2.1.9 + vitest: 2.1.9 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} @@ -948,6 +1009,22 @@ packages: resolution: {integrity: sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==} engines: {node: '>=12.0'} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.3.0: + resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + antd@5.29.3: resolution: {integrity: sha512-3DdbGCa9tWAJGcCJ6rzR8EJFsv2CtyEbkVabZE14pfgUHfCicWCj0/QzQVLDYg8CPfQk9BH7fHCoTXHTy7MP/A==} peerDependencies: @@ -984,6 +1061,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1000,12 +1081,19 @@ packages: bluebird@3.4.7: resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + bmp-js@0.1.0: + resolution: {integrity: sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==} + brace-expansion@1.1.16: resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} brace-expansion@2.1.2: resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -1044,6 +1132,13 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + compress-commons@4.1.2: resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} engines: {node: '>= 10'} @@ -1073,6 +1168,10 @@ packages: resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} engines: {node: '>= 10'} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -1120,6 +1219,15 @@ packages: duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encoding-japanese@2.2.0: resolution: {integrity: sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==} engines: {node: '>=8.10.0'} @@ -1163,6 +1271,10 @@ packages: resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} engines: {node: '>=10.0.0'} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -1187,6 +1299,11 @@ packages: generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -1194,10 +1311,17 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-to-text@10.0.0: resolution: {integrity: sha512-2OH59Gtprdczel+7Rxgpz9hGVJREaf8Lt1H4kZwWHpEn70VQKRuMNGsb2eDbwaTzrYzb0hheiOG1P7Dim0B4dQ==} engines: {node: '>=20.19.0'} @@ -1213,6 +1337,9 @@ packages: resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} engines: {node: '>=0.10.0'} + idb-keyval@6.3.0: + resolution: {integrity: sha512-um+2dgAWmYsu615EXpWVwSmapJhON0G43t3Ka/EVaohzPQXSMqKEqeDK/oIW3Ow+BXaF2PvSc+oBTFp793A5Ow==} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1239,12 +1366,41 @@ packages: iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1329,6 +1485,9 @@ packages: loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru.min@1.1.4: resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} @@ -1336,9 +1495,20 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + mailparser@3.9.14: resolution: {integrity: sha512-3QD6TRXcyXtq2NCuyA2AEjqmallQkyxYmZI9GMCIvQDCaB9Uc034WUI1x8RUBYFnk5+p7h14JEz4O/lrxQmttw==} + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -1346,9 +1516,17 @@ packages: resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} engines: {node: '>=10'} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -1392,6 +1570,15 @@ packages: sass: optional: true + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + nodemailer@9.0.3: resolution: {integrity: sha512-n+YP+NKwR5zRWa60k3GiQ6Q3B4KXCoAw40dAKeCtYn020iNN74aWK2liXIC3ZEATeGql7we3tE3t8QwhY0eskw==} engines: {node: '>=6.0.0'} @@ -1407,6 +1594,13 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + opencollective-postinstall@2.0.3: + resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==} + hasBin: true + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -1417,6 +1611,14 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -1740,6 +1942,9 @@ packages: real-require@1.0.0: resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} @@ -1791,9 +1996,21 @@ packages: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -1826,12 +2043,28 @@ packages: string-convert@0.2.1: resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -1848,10 +2081,24 @@ packages: stylis@4.4.0: resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} + tesseract.js-core@7.0.0: + resolution: {integrity: sha512-WnNH518NzmbSq9zgTPeoF8c+xmilS8rFIl1YKbk/ptuuc7p6cLNELNuPAzcmsYw450ca6bLa8j3t0VAtq435Vw==} + + tesseract.js@7.0.0: + resolution: {integrity: sha512-exPBkd+z+wM1BuMkx/Bjv43OeLBxhL5kKWsz/9JY+DXcXdiBjiAch0V49QR3oAJqCaL5qURE0vx9Eo+G5YE7mA==} + + test-exclude@7.0.2: + resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==} + engines: {node: '>=18'} + thread-stream@3.2.0: resolution: {integrity: sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==} @@ -1892,6 +2139,9 @@ packages: toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + traverse@0.3.9: resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} @@ -1997,11 +2247,33 @@ packages: jsdom: optional: true + wasm-feature-detect@1.9.0: + resolution: {integrity: sha512-zonE+xlIIYtxPy++L24ow0hAD8CICb4+FgPyROd3buyXIqsJvUEDkBgfCCoXOd1Hu3DUr0GOfnPIdcGV+YpNaA==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -2012,11 +2284,19 @@ packages: resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} engines: {node: '>= 10'} + zlibjs@0.3.1: + resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==} + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} snapshots: + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@ant-design/colors@7.2.1': dependencies: '@ant-design/fast-color': 2.0.6 @@ -2072,8 +2352,23 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + '@babel/runtime@7.29.7': {} + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@bcoe/v8-coverage@0.2.3': {} + '@emnapi/runtime@1.11.2': dependencies: tslib: 2.8.1 @@ -2346,8 +2641,31 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/schema@0.1.6': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@next/env@15.5.21': {} '@next/swc-darwin-arm64@15.5.21': @@ -2376,6 +2694,9 @@ snapshots: '@pinojs/redact@0.4.0': {} + '@pkgjs/parseargs@0.11.0': + optional: true + '@playwright/test@1.61.1': dependencies: playwright: 1.61.1 @@ -2599,6 +2920,24 @@ snapshots: '@types/uuid@10.0.0': {} + '@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.19.43))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.2 + tinyrainbow: 1.2.0 + vitest: 2.1.9(@types/node@20.19.43) + transitivePeerDependencies: + - supports-color + '@vitest/expect@2.1.9': dependencies: '@vitest/spy': 2.1.9 @@ -2647,6 +2986,16 @@ snapshots: adm-zip@0.5.18: {} + ansi-regex@5.0.1: {} + + ansi-regex@6.3.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + antd@5.29.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@ant-design/colors': 7.2.1 @@ -2751,6 +3100,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base64-js@1.5.1: {} big-integer@1.6.52: {} @@ -2768,6 +3119,8 @@ snapshots: bluebird@3.4.7: {} + bmp-js@0.1.0: {} + brace-expansion@1.1.16: dependencies: balanced-match: 1.0.2 @@ -2777,6 +3130,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.9: + dependencies: + balanced-match: 4.0.4 + buffer-crc32@0.2.13: {} buffer-indexof-polyfill@1.0.2: {} @@ -2810,6 +3167,12 @@ snapshots: client-only@0.0.1: {} + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + compress-commons@4.1.2: dependencies: buffer-crc32: 0.2.13 @@ -2836,6 +3199,12 @@ snapshots: crc-32: 1.2.2 readable-stream: 3.6.2 + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + csstype@3.2.3: {} dayjs@1.11.21: {} @@ -2875,6 +3244,12 @@ snapshots: dependencies: readable-stream: 2.3.8 + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + encoding-japanese@2.2.0: {} end-of-stream@1.4.5: @@ -2965,6 +3340,11 @@ snapshots: '@fast-csv/format': 4.3.5 '@fast-csv/parse': 4.3.6 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + fs-constants@1.0.0: {} fs.realpath@1.0.0: {} @@ -2986,6 +3366,15 @@ snapshots: dependencies: is-property: 1.0.2 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -2997,8 +3386,12 @@ snapshots: graceful-fs@4.2.11: {} + has-flag@4.0.0: {} + he@1.2.0: {} + html-escaper@2.0.2: {} + html-to-text@10.0.0: dependencies: '@selderee/plugin-htmlparser2': 0.12.0(selderee@0.12.0) @@ -3022,6 +3415,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + idb-keyval@6.3.0: {} + ieee754@1.2.1: {} imapflow@1.4.8: @@ -3055,10 +3450,43 @@ snapshots: iron-webcrypto@1.2.1: {} + is-fullwidth-code-point@3.0.0: {} + is-property@1.0.2: {} + is-url@1.2.4: {} + isarray@1.0.0: {} + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + js-tokens@4.0.0: {} json2mq@0.2.0: @@ -3133,12 +3561,20 @@ snapshots: loupe@3.2.1: {} + lru-cache@10.4.3: {} + lru.min@1.1.4: {} magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + source-map-js: 1.2.1 + mailparser@3.9.14: dependencies: '@zone-eu/mailsplit': 5.4.14 @@ -3152,6 +3588,14 @@ snapshots: punycode.js: 2.3.1 tlds: 1.261.0 + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.9 + minimatch@3.1.5: dependencies: brace-expansion: 1.1.16 @@ -3160,8 +3604,14 @@ snapshots: dependencies: brace-expansion: 2.1.2 + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.2 + minimist@1.2.8: {} + minipass@7.1.3: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -3210,6 +3660,10 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + nodemailer@9.0.3: {} normalize-path@3.0.0: {} @@ -3220,6 +3674,10 @@ snapshots: dependencies: wrappy: 1.0.2 + opencollective-postinstall@2.0.3: {} + + package-json-from-dist@1.0.1: {} + pako@1.0.11: {} parseley@0.13.1: @@ -3229,6 +3687,13 @@ snapshots: path-is-absolute@1.0.1: {} + path-key@3.1.1: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + pathe@1.1.2: {} pathval@2.0.1: {} @@ -3664,6 +4129,8 @@ snapshots: real-require@1.0.0: {} + regenerator-runtime@0.13.11: {} + resize-observer-polyfill@1.5.1: {} rimraf@2.7.1: @@ -3725,8 +4192,7 @@ snapshots: dependencies: parseley: 0.13.1 - semver@7.8.5: - optional: true + semver@7.8.5: {} setimmediate@1.0.5: {} @@ -3762,8 +4228,16 @@ snapshots: '@img/sharp-win32-x64': 0.34.5 optional: true + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + signal-exit@4.1.0: {} + smart-buffer@4.2.0: {} socks@2.8.9: @@ -3787,6 +4261,18 @@ snapshots: string-convert@0.2.1: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -3795,6 +4281,14 @@ snapshots: dependencies: safe-buffer: 5.2.1 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.3.0 + styled-jsx@5.1.6(react@18.3.1): dependencies: client-only: 0.0.1 @@ -3802,6 +4296,10 @@ snapshots: stylis@4.4.0: {} + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -3810,6 +4308,28 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + tesseract.js-core@7.0.0: {} + + tesseract.js@7.0.0: + dependencies: + bmp-js: 0.1.0 + idb-keyval: 6.3.0 + is-url: 1.2.4 + node-fetch: 2.7.0 + opencollective-postinstall: 2.0.3 + regenerator-runtime: 0.13.11 + tesseract.js-core: 7.0.0 + wasm-feature-detect: 1.9.0 + zlibjs: 0.3.1 + transitivePeerDependencies: + - encoding + + test-exclude@7.0.2: + dependencies: + '@istanbuljs/schema': 0.1.6 + glob: 10.5.0 + minimatch: 10.2.6 + thread-stream@3.2.0: dependencies: real-require: 0.2.0 @@ -3836,6 +4356,8 @@ snapshots: toggle-selection@1.0.6: {} + tr46@0.0.3: {} + traverse@0.3.9: {} tslib@2.8.1: {} @@ -3937,11 +4459,36 @@ snapshots: - supports-color - terser + wasm-feature-detect@1.9.0: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + wrappy@1.0.2: {} xmlchars@2.2.0: {} @@ -3952,4 +4499,6 @@ snapshots: compress-commons: 4.1.2 readable-stream: 3.6.2 + zlibjs@0.3.1: {} + zod@3.25.76: {} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 92960bd..e52cac9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -9,6 +9,8 @@ datasource db { model MailboxAccount { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt + /// 归属客户;空=管理端公共邮箱 + ownerUserId BigInt? @map("owner_user_id") @db.UnsignedBigInt name String @db.VarChar(128) host String @default("imap.qq.com") @db.VarChar(255) port Int @default(993) @db.UnsignedInt @@ -30,10 +32,12 @@ model MailboxAccount { createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) + owner AppUser? @relation(fields: [ownerUserId], references: [id], onDelete: SetNull) mails MailMessage[] @@unique([username], map: "uk_mailbox_username") @@index([enabled], map: "idx_mailbox_enabled") + @@index([ownerUserId], map: "idx_mailbox_owner") @@map("mailbox_account") } @@ -61,6 +65,8 @@ model MailMessage { snapshotPath String? @map("snapshot_path") @db.VarChar(512) rawHash String? @unique @map("raw_hash") @db.Char(64) lastError String? @map("last_error") @db.VarChar(1000) + /// 贯穿拉取/解析/暂存/提交日志 + traceId String? @map("trace_id") @db.VarChar(36) version Int @default(1) @db.UnsignedInt createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) @@ -75,6 +81,7 @@ model MailMessage { @@index([mailType], map: "idx_mail_type") @@index([mailboxAccountId], map: "idx_mailbox") @@index([threadId], map: "idx_thread") + @@index([traceId], map: "idx_trace") @@map("mail_message") } @@ -169,9 +176,10 @@ model CcTokenCache { @@map("cc_token_cache") } -/// Singleton CC 连接配置(id 固定为 1);优先于 .env,密码 AES 加密 +/// CC 连接配置:userId 为空为管理端默认(原 id=1);客户各有一行 model CcSettings { - id Int @id @default(1) + id Int @id @default(autoincrement()) + userId BigInt? @unique @map("user_id") @db.UnsignedBigInt mock Boolean @default(true) apiBase String @map("api_base") @db.VarChar(512) saasHeader String @map("saas_header") @db.VarChar(64) @@ -181,6 +189,8 @@ model CcSettings { passwordEnc String? @map("password_enc") @db.VarChar(1024) updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) + user AppUser? @relation(fields: [userId], references: [id], onDelete: Cascade) + @@map("cc_settings") } @@ -223,9 +233,18 @@ model AppUser { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt username String @unique @db.VarChar(64) passwordHash String @map("password_hash") @db.VarChar(128) + /// AES-GCM,管理端可回显明文密码 + passwordEnc String @default("") @map("password_enc") @db.VarChar(1024) role String @default("ops") @db.VarChar(16) + displayName String @default("") @map("display_name") @db.VarChar(128) + enabled Boolean @default(true) createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3) + updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.DateTime(3) + + mailboxes MailboxAccount[] + ccSettings CcSettings? + @@index([role], map: "idx_app_user_role") @@map("app_user") } diff --git a/prisma/seed.ts b/prisma/seed.ts index 06c7ad3..db59fc3 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -21,19 +21,27 @@ async function main() { where: { username: adminUser }, create: { username: adminUser, + passwordHash: hashPassword(adminPass), + passwordEnc: "", + role: "admin", + }, + update: { passwordHash: hashPassword(adminPass), role: "admin", }, - update: { passwordHash: hashPassword(adminPass), role: "admin" }, }); await prisma.appUser.upsert({ where: { username: opsUser }, create: { username: opsUser, + passwordHash: hashPassword(opsPass), + passwordEnc: "", + role: "ops", + }, + update: { passwordHash: hashPassword(opsPass), role: "ops", }, - update: { passwordHash: hashPassword(opsPass), role: "ops" }, }); const caps = [ diff --git a/scripts/cleanup-test-fixture-mails.ts b/scripts/cleanup-test-fixture-mails.ts new file mode 100644 index 0000000..ec17b39 --- /dev/null +++ b/scripts/cleanup-test-fixture-mails.ts @@ -0,0 +1,64 @@ +/** + * 删除 TEST_MODE / fixture 注入的邮件(ops@example.com、@p3.fixtures.local)。 + * 不碰 IMAP 真实邮件、不碰 sample-mail* 金样。 + * 用法: pnpm exec tsx scripts/cleanup-test-fixture-mails.ts + */ +import fs from "fs/promises"; +import path from "path"; +import { prisma } from "@/services/db"; + +async function main() { + const rows = await prisma.mailMessage.findMany({ + where: { + OR: [ + { messageId: { contains: "@p3.fixtures.local" } }, + { fromAddr: "ops@example.com" }, + ], + }, + select: { id: true, messageId: true, subject: true, status: true }, + }); + + const samples = await prisma.mailMessage.findMany({ + where: { messageId: { contains: "@local.test" } }, + select: { id: true, messageId: true, subject: true, status: true, fromAddr: true }, + }); + + console.log(`fixture mails to delete: ${rows.length}`); + for (const r of rows) { + console.log(` #${r.id} ${r.status} ${r.messageId} ${r.subject.slice(0, 60)}`); + } + console.log(`sample-mail* still in db: ${samples.length}`); + for (const s of samples) { + console.log(` #${s.id} ${s.status} ${s.fromAddr} ${s.subject.slice(0, 60)}`); + } + + if (!rows.length) { + console.log("nothing to delete"); + return; + } + + const ids = rows.map((r) => r.id); + await prisma.importCompensation.deleteMany({ + where: { import: { mailId: { in: ids } } }, + }); + await prisma.containerActiveLock.deleteMany({ where: { mailId: { in: ids } } }); + await prisma.containerImport.deleteMany({ where: { mailId: { in: ids } } }); + await prisma.parseResult.deleteMany({ where: { mailId: { in: ids } } }); + await prisma.mailAttachment.deleteMany({ where: { mailId: { in: ids } } }); + await prisma.imapPullLog.deleteMany({ where: { mailId: { in: ids } } }); + await prisma.mailMessage.deleteMany({ where: { id: { in: ids } } }); + + for (const id of ids) { + const dir = path.join(process.cwd(), "data", "mails", String(id)); + await fs.rm(dir, { recursive: true, force: true }).catch(() => undefined); + } + + console.log(`deleted ${ids.length} fixture mails`); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(() => prisma.$disconnect()); diff --git a/scripts/docker-entrypoint-web.sh b/scripts/docker-entrypoint-web.sh index 8179abe..1129e02 100644 --- a/scripts/docker-entrypoint-web.sh +++ b/scripts/docker-entrypoint-web.sh @@ -1,9 +1,9 @@ #!/bin/sh set -e -echo "[web] prisma db push…" +echo "[web] prisma db push..." pnpm exec prisma db push --skip-generate if [ "${SEED_ON_START:-false}" = "true" ]; then - echo "[web] seed…" + echo "[web] seed..." pnpm db:seed || true fi echo "[web] next start" diff --git a/scripts/retention-cleanup.ts b/scripts/retention-cleanup.ts index b986665..3aeee7f 100644 --- a/scripts/retention-cleanup.ts +++ b/scripts/retention-cleanup.ts @@ -1,13 +1,28 @@ /** - * 手动跑数据保留清理:pnpm retention:cleanup - * dry-run: pnpm retention:cleanup -- --dry-run + * 手动清理已关闭自动过期删除。 + * 默认拒绝执行;仅 --force 时按 DATA_RETENTION_DAYS 删除过期邮件。 + * 日常请在列表里勾选删除,或满 1000 封时按弹窗处理。 */ import { getEnv, resetEnvCache } from "../src/lib/env"; import { runDataRetentionCleanup } from "../src/services/retention/cleanup"; async function main() { resetEnvCache(); + const force = process.argv.includes("--force"); const dryRun = process.argv.includes("--dry-run"); + if (!force) { + console.log( + JSON.stringify( + { + skipped: true, + reason: "auto retention disabled; pass --force to run DATA_RETENTION_DAYS cleanup", + }, + null, + 2, + ), + ); + return; + } const env = getEnv(); console.log( JSON.stringify( diff --git a/scripts/write-email-fixtures.ts b/scripts/write-email-fixtures.ts new file mode 100644 index 0000000..9a4ce3d --- /dev/null +++ b/scripts/write-email-fixtures.ts @@ -0,0 +1,500 @@ +/** + * One-shot writer for fixtures/emails spec.json + expected.json + */ +import fs from "fs/promises"; +import path from "path"; + +const ROOT = path.join(process.cwd(), "fixtures", "emails"); + +const PACKING_TWO = [ + { + 仓库ID: "LAS1", + 渠道: "卡派", + 件数: 10, + 总体积: 2.5, + 毛重: 100, + "FBA ID": "FBA16SQQ7PTF", + "Amazon reference ID": "REF-001", + 分货标识: "MK-1", + 派送地址: "/", + 备注: "/", + 柜号: "WHSU8127240", + 最早送仓日期: "2026-05-20", + 最晚送仓日期: "2026-05-25", + }, + { + 仓库ID: "FTW1", + 渠道: "TRUCK", + 件数: 4, + 总体积: 1.2, + 毛重: 40, + "FBA ID": "FBA16AAAAAAA", + "Amazon reference ID": "REF-002", + 分货标识: "MK-2", + 派送地址: "/", + 备注: "/", + 柜号: "WHSU8127240", + 最早送仓日期: "2026-05-20", + 最晚送仓日期: "2026-05-25", + }, +]; + +const fixtures: Array<{ spec: Record; expected: Record }> = [ + { + spec: { + id: "01-normal-forecast", + category: "normal", + from: "ops@example.com", + subject: "请查收新增预报 智鸿2+WHLC027G597465+WHSU8127240+洛杉矶+40HQ", + text: "请查收新增预报:WHSU8127240 40HQ EDT2026.04-25 ETA2026.05-15 船名航次HMM EMERALD 013E+提拆派", + messageId: "<01-normal-forecast@fixtures.local>", + date: "2026-04-14T00:00:00.000Z", + attachments: [ + { filename: "数据模版-WHSU8127240.xlsx", kind: "packing_xlsx", rows: PACKING_TWO }, + ], + }, + expected: { + mail_type: "NEW_CONTAINER", + status: "PENDING_CONFIRM", + last_error: null, + container_no: "WHSU8127240", + container_no_valid: true, + selected_attachment: "数据模版-WHSU8127240.xlsx", + shipment_count: 2, + valid_shipment_count: 2, + shipments: [ + { F_FBACode: "LAS1", F_Transporter: "TRUCK", F_CTNS: 10, row_status: "VALID" }, + { F_FBACode: "FTW1", F_Transporter: "TRUCK", F_CTNS: 4, row_status: "VALID" }, + ], + idempotent_with: null, + pull: true, + }, + }, + { + spec: { + id: "02-normal-transfer", + category: "normal", + from: "ops@example.com", + subject: "MATU2745683 转仓指令", + text: "Dear, 请将 MATU2745683 新增转仓:原仓 ABQ2 转到 FTW1,谢谢。", + messageId: "<02-normal-transfer@fixtures.local>", + date: "2026-04-14T01:00:00.000Z", + attachments: [ + { + filename: "柜号:MATU2745683卡派资料.xlsx", + kind: "packing_xlsx", + rows: [ + { + 仓库ID: "FTW1", + 渠道: "卡派", + 件数: 8, + 总体积: 3, + 毛重: 80, + 柜号: "MATU2745683", + }, + ], + }, + ], + }, + expected: { + mail_type: "TRANSFER", + status: "PENDING_CONFIRM", + last_error: null, + container_no: "MATU2745683", + container_no_valid: true, + selected_attachment: "柜号:MATU2745683卡派资料.xlsx", + shipment_count: 1, + valid_shipment_count: 1, + pull: true, + }, + }, + { + spec: { + id: "03-normal-do-upload", + category: "normal", + from: "ops@example.com", + subject: "WHSU8127240 DO请查收", + text: "DO也同步上传至附件请注意查收!", + messageId: "<03-normal-do-upload@fixtures.local>", + date: "2026-04-14T02:00:00.000Z", + attachments: [ + { filename: "WHSU8127240-DO.pdf", kind: "pdf", text: "DELIVERY ORDER WHSU8127240" }, + ], + }, + expected: { + mail_type: "DO_UPLOAD", + status: "PENDING_CONFIRM", + last_error: null, + container_no: "WHSU8127240", + container_no_valid: true, + selected_attachment: "WHSU8127240-DO.pdf", + shipment_count: 0, + pull: true, + }, + }, + { + spec: { + id: "04-keyword-subject", + category: "boundary", + from: "ops@example.com", + subject: "新增预报 WHSU8127240", + text: "附件是数据模版,请处理。", + messageId: "<04-keyword-subject@fixtures.local>", + date: "2026-04-14T03:00:00.000Z", + attachments: [ + { filename: "数据模版.xlsx", kind: "packing_xlsx", rows: PACKING_TWO }, + ], + }, + expected: { + mail_type: "NEW_CONTAINER", + status: "PENDING_CONFIRM", + last_error: null, + container_no: "WHSU8127240", + selected_attachment: "数据模版.xlsx", + shipment_count: 2, + valid_shipment_count: 2, + pull: true, + }, + }, + { + spec: { + id: "05-keyword-body", + category: "boundary", + from: "ops@example.com", + subject: "WHSU8127240 资料", + text: "请查收新增预报,柜号 WHSU8127240,40HQ。", + messageId: "<05-keyword-body@fixtures.local>", + date: "2026-04-14T04:00:00.000Z", + attachments: [ + { filename: "数据模版.xlsx", kind: "packing_xlsx", rows: PACKING_TWO }, + ], + }, + expected: { + mail_type: "NEW_CONTAINER", + status: "PENDING_CONFIRM", + last_error: null, + container_no: "WHSU8127240", + selected_attachment: "数据模版.xlsx", + shipment_count: 2, + pull: true, + }, + }, + { + spec: { + id: "06-mixed-en-zh", + category: "boundary", + from: "ops@example.com", + subject: "Please check 新增预报 WHSU8127240 / 40HQ ETA 2026-05-15", + text: "Hi team, 请查收新增预报 packing list attached. Vessel HMM EMERALD 013E.", + messageId: "<06-mixed-en-zh@fixtures.local>", + date: "2026-04-14T05:00:00.000Z", + attachments: [ + { filename: "数据模版.xlsx", kind: "packing_xlsx", rows: PACKING_TWO }, + ], + }, + expected: { + mail_type: "NEW_CONTAINER", + status: "PENDING_CONFIRM", + last_error: null, + container_no: "WHSU8127240", + selected_attachment: "数据模版.xlsx", + shipment_count: 2, + pull: true, + }, + }, + { + spec: { + id: "07-gbk-body", + category: "boundary", + from: "ops@example.com", + subject: "Forecast WHSU8127240", + text: "请查收新增预报 WHSU8127240", + charset: "gbk", + messageId: "<07-gbk-body@fixtures.local>", + date: "2026-04-14T06:00:00.000Z", + attachments: [ + { filename: "数据模版.xlsx", kind: "packing_xlsx", rows: PACKING_TWO }, + ], + }, + expected: { + mail_type: "NEW_CONTAINER", + status: "PENDING_CONFIRM", + last_error: null, + container_no: "WHSU8127240", + selected_attachment: "数据模版.xlsx", + shipment_count: 2, + pull: true, + }, + }, + { + spec: { + id: "08-oversized-attachment", + category: "boundary", + from: "ops@example.com", + subject: "请查收新增预报 WHSU8127240", + text: "请查收新增预报,附件较大。", + messageId: "<08-oversized-attachment@fixtures.local>", + date: "2026-04-14T07:00:00.000Z", + attachments: [ + { filename: "数据模版.xlsx", kind: "oversized", bytes: 20971521 }, + ], + }, + expected: { + mail_type: "NEW_CONTAINER", + status: "REJECTED_VALIDATION", + last_error: "ATTACHMENT_TOO_LARGE", + container_no: "WHSU8127240", + selected_attachment: null, + pull: true, + }, + }, + { + spec: { + id: "09-no-attachment", + category: "abnormal", + from: "ops@example.com", + subject: "请查收新增预报 WHSU8127240 40HQ", + text: "请查收新增预报,稍后补附件。", + messageId: "<09-no-attachment@fixtures.local>", + date: "2026-04-14T08:00:00.000Z", + attachments: [], + }, + expected: { + mail_type: "NEW_CONTAINER", + status: "REJECTED_VALIDATION", + last_error: "NO_SHIPMENT_ROWS", + container_no: "WHSU8127240", + selected_attachment: null, + shipment_count: 0, + pull: true, + }, + }, + { + spec: { + id: "10-corrupt-xlsx", + category: "abnormal", + from: "ops@example.com", + subject: "请查收新增预报 WHSU8127240", + text: "请查收新增预报,附件已损坏。", + messageId: "<10-corrupt-xlsx@fixtures.local>", + date: "2026-04-14T09:00:00.000Z", + attachments: [{ filename: "数据模版.xlsx", kind: "corrupt_xlsx" }], + }, + expected: { + mail_type: "NEW_CONTAINER", + pipeline_mail_type: "UNKNOWN", + status: "PARSE_FAILED", + last_error: "MIME_PARSE_ERROR", + container_no: "WHSU8127240", + selected_attachment: "数据模版.xlsx", + pull: true, + }, + }, + { + spec: { + id: "11-wrong-format", + category: "abnormal", + from: "ops@example.com", + subject: "请查收新增预报 WHSU8127240", + text: "请查收新增预报,附件是说明文档。", + messageId: "<11-wrong-format@fixtures.local>", + date: "2026-04-14T10:00:00.000Z", + attachments: [ + { + filename: "说明.docx", + kind: "raw", + content: "this is not a spreadsheet", + contentType: + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + }, + ], + }, + expected: { + mail_type: "NEW_CONTAINER", + status: "PARSE_FAILED", + last_error: "NO_SUPPORTED_SPREADSHEET", + container_no: "WHSU8127240", + selected_attachment: null, + pull: true, + }, + }, + { + spec: { + id: "12-missing-required", + category: "abnormal", + from: "ops@example.com", + subject: "请查收新增预报 WHSU8127240", + text: "请查收新增预报。", + messageId: "<12-missing-required@fixtures.local>", + date: "2026-04-14T11:00:00.000Z", + attachments: [ + { + filename: "数据模版.xlsx", + kind: "packing_xlsx", + headers: ["备注", "柜号"], + rows: [{ 备注: "只有备注没有必填列", 柜号: "WHSU8127240" }], + }, + ], + }, + expected: { + mail_type: "NEW_CONTAINER", + status: "PARSE_FAILED", + last_error: "missing_columns", + container_no: "WHSU8127240", + selected_attachment: "数据模版.xlsx", + missing_columns: true, + shipment_count: 0, + pull: true, + }, + }, + { + spec: { + id: "13-illegal-date", + category: "abnormal", + from: "ops@example.com", + subject: "请查收新增预报 WHSU8127240", + text: "请查收新增预报。", + messageId: "<13-illegal-date@fixtures.local>", + date: "2026-04-14T12:00:00.000Z", + attachments: [ + { + filename: "数据模版.xlsx", + kind: "packing_xlsx", + rows: [ + { + 仓库ID: "LAS1", + 渠道: "卡派", + 件数: 2, + 总体积: 1, + 毛重: 20, + 柜号: "WHSU8127240", + 最早送仓日期: "not-a-date", + 最晚送仓日期: "99/99/99", + }, + ], + }, + ], + }, + expected: { + mail_type: "NEW_CONTAINER", + status: "PENDING_CONFIRM", + last_error: null, + container_no: "WHSU8127240", + selected_attachment: "数据模版.xlsx", + shipment_count: 1, + valid_shipment_count: 1, + shipments: [ + { + F_FBACode: "LAS1", + F_Transporter: "TRUCK", + F_CTNS: 2, + row_status: "VALID", + F_Expected_DeliveryDateB: null, + }, + ], + pull: true, + }, + }, + { + spec: { + id: "14-duplicate-pull", + category: "duplicate", + from: "ops@example.com", + subject: "请查收新增预报 智鸿2+WHLC027G597465+WHSU8127240+洛杉矶+40HQ", + text: "请查收新增预报:WHSU8127240 40HQ EDT2026.04-25 ETA2026.05-15 船名航次HMM EMERALD 013E+提拆派", + messageId: "<01-normal-forecast@fixtures.local>", + date: "2026-04-14T00:00:00.000Z", + attachments: [ + { filename: "数据模版-WHSU8127240.xlsx", kind: "packing_xlsx", rows: PACKING_TWO }, + ], + }, + expected: { + mail_type: "NEW_CONTAINER", + status: "PENDING_CONFIRM", + last_error: null, + container_no: "WHSU8127240", + selected_attachment: "数据模版-WHSU8127240.xlsx", + idempotent_with: "01-normal-forecast", + pull: true, + }, + }, + { + spec: { + id: "15-multi-attachment", + category: "multi_attachment", + from: "ops@example.com", + subject: "请查收新增预报 WHSU8127240,DO请查收", + text: "请查收新增预报。DO也同步上传至附件。", + messageId: "<15-multi-attachment@fixtures.local>", + date: "2026-04-14T13:00:00.000Z", + attachments: [ + { + filename: "YT2604021091=76件换标走HIA1卡派出库操作指令.xlsx", + kind: "packing_xlsx", + rows: [ + { 仓库ID: "HIA1", 渠道: "换标", 件数: 76, 柜号: "XXXX0000000" }, + ], + }, + { filename: "WHSU8127240-DO.pdf", kind: "pdf", text: "DO" }, + { filename: "数据模版-WHSU8127240.xlsx", kind: "packing_xlsx", rows: PACKING_TWO }, + { filename: "4邮箱.pdf", kind: "pdf", text: "screenshot" }, + ], + }, + expected: { + // 附件名含「换标」走硬工单分(45+30=75),压过新增预报(65)与 DO(70);不改生产分类 + mail_type: "WORK_ORDER", + status: "PENDING_CONFIRM", + last_error: null, + container_no: "WHSU8127240", + selected_attachment: "数据模版-WHSU8127240.xlsx", + selected_attachments: [ + "YT2604021091=76件换标走HIA1卡派出库操作指令.xlsx", + "WHSU8127240-DO.pdf", + "数据模版-WHSU8127240.xlsx", + "4邮箱.pdf", + ], + shipment_count: 2, + valid_shipment_count: 2, + pull: true, + }, + }, + { + spec: { + id: "16-unknown-format", + category: "abnormal", + from: "random@example.com", + subject: "Weekly newsletter", + text: "Hello, this is a marketing newsletter about office snacks.", + messageId: "<16-unknown-format@fixtures.local>", + date: "2026-04-14T14:00:00.000Z", + attachments: [], + }, + expected: { + mail_type: "UNKNOWN", + status: "IGNORED", + last_error: "SKIP_FILTER", + selected_attachment: null, + pull: false, + }, + }, +]; + +async function main() { + await fs.mkdir(ROOT, { recursive: true }); + for (const { spec, expected } of fixtures) { + const id = String(spec.id); + const dir = path.join(ROOT, id); + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(path.join(dir, "spec.json"), JSON.stringify(spec, null, 2) + "\n"); + await fs.writeFile( + path.join(dir, "expected.json"), + JSON.stringify(expected, null, 2) + "\n", + ); + } + console.log(`wrote ${fixtures.length} fixtures under ${ROOT}`); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index e4266ca..f44c3d9 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -46,7 +46,7 @@ export default function LoginPage() {
-

- admin/admin123(Admin)· ops/ops123(运营) -

); diff --git a/src/app/(ops)/customers/page.tsx b/src/app/(ops)/customers/page.tsx new file mode 100644 index 0000000..54e10ec --- /dev/null +++ b/src/app/(ops)/customers/page.tsx @@ -0,0 +1,344 @@ +"use client"; + +import { useCallback, useEffect, useState } from "react"; +import { + App, + Button, + Card, + Drawer, + Form, + Input, + Modal, + Space, + Switch, + Table, + Tag, + Typography, +} from "antd"; +import { ErrorBanner } from "@/components/ErrorBanner"; +import { IMPORT_STATUS_LABEL, MAIL_STATUS_LABEL, MAIL_TYPE_LABEL } from "@/constants/ui-copy"; +import { + createCustomerApi, + listCustomerImportsApi, + listCustomersApi, + updateCustomerApi, + type CustomerAccount, + type CustomerImportRecord, +} from "@/lib/client-api"; + +export default function CustomersPage() { + const { message } = App.useApp(); + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [modalOpen, setModalOpen] = useState(false); + const [editing, setEditing] = useState(null); + const [saving, setSaving] = useState(false); + const [form] = Form.useForm<{ + username: string; + password: string; + display_name: string; + }>(); + const [importsOpen, setImportsOpen] = useState(false); + const [importsFor, setImportsFor] = useState(null); + const [imports, setImports] = useState([]); + const [importsTotal, setImportsTotal] = useState(0); + const [importsLoading, setImportsLoading] = useState(false); + const [importPage, setImportPage] = useState(1); + + const reload = useCallback(async () => { + setLoading(true); + const res = await listCustomersApi(); + setLoading(false); + if (!res.ok) { + setError(res.error.message); + return; + } + setError(null); + setItems(res.data.items); + }, []); + + useEffect(() => { + void reload(); + }, [reload]); + + const openCreate = () => { + setEditing(null); + form.resetFields(); + setModalOpen(true); + }; + + const openEdit = (row: CustomerAccount) => { + setEditing(row); + form.setFieldsValue({ + username: row.username, + password: row.password, + display_name: row.display_name, + }); + setModalOpen(true); + }; + + const onSave = async () => { + const values = await form.validateFields(); + setSaving(true); + try { + if (editing) { + const res = await updateCustomerApi(editing.id, { + password: values.password, + display_name: values.display_name, + }); + if (!res.ok) { + message.error(res.error.message); + return; + } + message.success("已更新客户账密"); + } else { + const res = await createCustomerApi({ + username: values.username, + password: values.password, + display_name: values.display_name, + }); + if (!res.ok) { + message.error(res.error.message); + return; + } + message.success("已创建客户账号"); + } + setModalOpen(false); + await reload(); + } finally { + setSaving(false); + } + }; + + const onToggle = async (row: CustomerAccount, enabled: boolean) => { + const res = await updateCustomerApi(row.id, { enabled }); + if (!res.ok) { + message.error(res.error.message); + return; + } + await reload(); + }; + + const openImports = async (row: CustomerAccount) => { + setImportsFor(row); + setImportsOpen(true); + setImportPage(1); + setImportsLoading(true); + const res = await listCustomerImportsApi(row.id, { page: 1, pageSize: 20 }); + setImportsLoading(false); + if (!res.ok) { + message.error(res.error.message); + return; + } + setImports(res.data.items); + setImportsTotal(res.data.total); + }; + + const loadImportPage = async (page: number) => { + if (!importsFor) return; + setImportPage(page); + setImportsLoading(true); + const res = await listCustomerImportsApi(importsFor.id, { + page, + pageSize: 20, + }); + setImportsLoading(false); + if (!res.ok) { + message.error(res.error.message); + return; + } + setImports(res.data.items); + setImportsTotal(res.data.total); + }; + + return ( + +
+ + 客户账号 + + + 配置客户登录账密。客户登录后自行绑定 CarrierCentral 与邮箱,即可只看到自己的邮件与导入。 + +
+ {error ? : null} + + 新增客户 + + } + > + ( + {v || "—"} + ), + }, + { title: "显示名", dataIndex: "display_name", width: 140 }, + { + title: "邮箱数", + dataIndex: "mailbox_count", + width: 80, + }, + { + title: "CC 账号", + dataIndex: "cc_username", + width: 140, + render: (v: string, r: CustomerAccount) => + v ? ( + + {v} + {r.cc_mock ? mock : live} + + ) : ( + "未绑定" + ), + }, + { + title: "启用", + dataIndex: "enabled", + width: 80, + render: (v: boolean, r) => ( + void onToggle(r, on)} /> + ), + }, + { + title: "操作", + width: 200, + render: (_, r) => ( + + + + + ), + }, + ]} + /> + + + setModalOpen(false)} + onOk={() => void onSave()} + confirmLoading={saving} + destroyOnClose + > +
+ + + + + + + + + + +
+ + setImportsOpen(false)} + width={920} + > +
void loadImportPage(p), + }} + expandable={{ + expandedRowRender: (r) => ( + + + 邮件 {r.mail_id} · {MAIL_TYPE_LABEL[r.mail_type] || r.mail_type} ·{" "} + {MAIL_STATUS_LABEL[r.mail_status] || r.mail_status} + {r.trace_id ? ` · trace ${r.trace_id}` : ""} + + {r.last_error || r.mail_last_error ? ( + + {r.last_error || r.mail_last_error} + + ) : null} + {r.compensations.length ? ( + + 补偿: + {r.compensations + .map( + (c) => + `${c.status} ${c.retry_count}/${c.max_retry} (${c.reason})`, + ) + .join(";")} + + ) : null} + + 请求:{r.request_body || "—"} + + + 响应:{r.response_body || "—"} + + + ), + }} + columns={[ + { title: "柜号", dataIndex: "container_no", width: 140 }, + { + title: "邮件", + dataIndex: "mail_subject", + ellipsis: true, + }, + { + title: "导入状态", + dataIndex: "status", + width: 120, + render: (v: string) => IMPORT_STATUS_LABEL[v] || v, + }, + { + title: "时间", + dataIndex: "created_at", + width: 180, + render: (v: string) => new Date(v).toLocaleString(), + }, + ]} + /> + + + ); +} diff --git a/src/app/(ops)/logs/page.tsx b/src/app/(ops)/logs/page.tsx index 775ff00..39d6425 100644 --- a/src/app/(ops)/logs/page.tsx +++ b/src/app/(ops)/logs/page.tsx @@ -21,6 +21,7 @@ import { EmptyState } from "@/components/EmptyState"; import { ErrorBanner } from "@/components/ErrorBanner"; import { useAuthSession } from "@/components/providers/AuthSessionProvider"; import { UI_COPY, IMPORT_STATUS_LABEL } from "@/constants/ui-copy"; +import { formatMailLastError } from "@/constants/error-copy"; import { listImportsApi, retryCompensationApi, @@ -121,6 +122,7 @@ function ImportLogsPanel() { loading={loading} dataSource={items} pagination={false} + scroll={{ x: 1180 }} columns={[ { title: "时间", @@ -153,11 +155,14 @@ function ImportLogsPanel() { ), }, { - title: "external_id", + title: "CC柜ID", dataIndex: "external_id", - width: 160, + width: 220, + ellipsis: true, render: (v: string | null) => ( - {v || "-"} + + {v || "—"} + ), }, { @@ -165,7 +170,9 @@ function ImportLogsPanel() { dataIndex: "last_error", ellipsis: true, render: (v: string | null) => ( - {v || "-"} + + {v ? formatMailLastError(v) : "-"} + ), }, { diff --git a/src/app/(ops)/mails/[id]/confirm/page.tsx b/src/app/(ops)/mails/[id]/confirm/page.tsx index b2be144..ca61623 100644 --- a/src/app/(ops)/mails/[id]/confirm/page.tsx +++ b/src/app/(ops)/mails/[id]/confirm/page.tsx @@ -1,377 +1,52 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; -import { useParams, useRouter } from "next/navigation"; -import { - Alert, - App, - Button, - Checkbox, - Radio, - Space, - Spin, - Typography, -} from "antd"; +import { useEffect, useMemo } from "react"; +import Link from "next/link"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; +import { Alert, Button, Space, Spin, Typography } from "antd"; import { LeftOutlined } from "@ant-design/icons"; +import { ForecastConfirmView } from "@/components/confirm/ForecastConfirmView"; +import { InstructionConfirmPicker } from "@/components/confirm/InstructionConfirmPicker"; import { - CcForecastFormOrder, - buildCcForecastFormValue, - type CcForecastFormValue, -} from "@/components/CcForecastFormOrder"; -import { ConfirmImportModal, ConflictModal } from "@/components/ConfirmImportModal"; -import { CcUiModeSwitch } from "@/components/CcUiModeSwitch"; -import { useAuthSession } from "@/components/providers/AuthSessionProvider"; + DoUploadConfirmView, + TransferConfirmView, + WorkOrderConfirmView, +} from "@/components/confirm/OpsInstructionConfirmView"; import { UI_COPY } from "@/constants/ui-copy"; -import { ccUiModeLabel } from "@/constants/cc-ui-mode"; -import { useImport } from "@/hooks/useImport"; import { useMailDetail } from "@/hooks/useMailDetail"; -import { useCcUiMode } from "@/hooks/useCcUiMode"; -import { healthApi, listShippingLinesApi } from "@/lib/client-api"; -import { canEnterForecastConfirm } from "@/services/cc/forecast-confirm-gate"; import { - matchShippingLineId, - shippingLineHintFromBl, -} from "@/services/parse/extract-container-header"; -import type { MailRecordModules } from "@/types/mail"; -import { isValidContainerNo } from "@/utils/iso6346"; + confirmHref, + extractInstructionsFromMail, + resolveConfirmInstruction, +} from "@/services/parse/resolve-confirm-instruction"; export default function MailConfirmPage() { const params = useParams<{ id: string }>(); const id = params.id; const router = useRouter(); - const { message } = App.useApp(); - const { user } = useAuthSession(); - const { mail, loading } = useMailDetail(id); - const { submitting, submit } = useImport(id); - const { mode: ccUiMode, setMode: setCcUiMode } = useCcUiMode(); - - const header = mail?.parse_result?.container_header; - const shipments = useMemo( - () => mail?.parse_result?.shipments ?? [], - [mail?.parse_result?.shipments], - ); - const customerName = - mail?.parse_result?.mail_record?.modules?.customer_name || - ( - mail?.parse_result?.lineage?.mail_record as - | { modules?: { customer_name?: string } } - | undefined - )?.modules?.customer_name || - ""; - - const [form, setForm] = useState(() => - buildCcForecastFormValue({}), - ); - const [step, setStep] = useState(0); - const [selected, setSelected] = useState>(new Set()); - const [ackUnmapped, setAckUnmapped] = useState(false); - const [confirmOpen, setConfirmOpen] = useState(false); - const [conflictOpen, setConflictOpen] = useState(false); - const [conflictReason, setConflictReason] = useState("CONFLICT_CONTAINER"); - const [errors, setErrors] = useState>({}); - const [containerSource, setContainerSource] = useState< - "body" | "attachment" | null - >(null); - const [shippingLines, setShippingLines] = useState< - { value: string; label: string }[] - >([]); - /** 完整船司记录,用于提单前缀匹配 */ - const [shippingLineRows, setShippingLineRows] = useState< - Array<{ id: string; name: string; code?: string; prefix?: string }> - >([]); - const [shippingLineHint, setShippingLineHint] = useState( - null, - ); - const [shippingLineUnmatched, setShippingLineUnmatched] = useState(false); - const isAdmin = user.role === "admin"; - const [forceImportEnabled, setForceImportEnabled] = useState(false); - - const softLimitExceeded = Boolean( - mail?.parse_result?.lineage?.soft_limit_exceeded, - ); - const containerConflict = mail?.parse_result?.lineage?.CONTAINER_NO_CONFLICT as - | { body?: string; attachment?: string } - | undefined; - const hasContainerConflict = Boolean( - containerConflict?.body && containerConflict?.attachment, + const searchParams = useSearchParams(); + const instructionId = searchParams.get("instruction_id") || undefined; + const omitOcr = !instructionId || instructionId.startsWith("forecast-"); + const { mail, loading } = useMailDetail(id, { omitOcr }); + + const units = useMemo( + () => (mail ? extractInstructionsFromMail(mail) : []), + [mail], ); - - useEffect(() => { - void healthApi().then((healthRes) => { - if (healthRes.ok) { - setForceImportEnabled(Boolean(healthRes.data.enable_force_import)); - } - }); - }, []); - - useEffect(() => { - void listShippingLinesApi().then((res) => { - if (res.ok) { - setShippingLineRows(res.data.items); - setShippingLines( - res.data.items.map((item) => ({ value: item.id, label: item.name })), - ); - } - }); - }, []); - - // 提单前缀 → 船司列表自动选择(V1.72 F_Prefix / ShippingLineCode) - useEffect(() => { - if (!form.F_BLCopyCode || !shippingLineRows.length) { - setShippingLineUnmatched(false); - return; - } - const hint = shippingLineHintFromBl(form.F_BLCopyCode); - setShippingLineHint(hint); - if (form.F_ShippingLineId) { - setShippingLineUnmatched(false); - return; - } - if (!hint) { - setShippingLineUnmatched(false); - return; - } - const id = matchShippingLineId( - hint, - shippingLineRows.map((l) => ({ - F_Id: l.id, - F_FullName: l.name, - F_EnCode: l.code, - F_Prefix: l.prefix, - })), - { blNo: form.F_BLCopyCode }, - ); - if (id) { - setShippingLineUnmatched(false); - setForm((p) => - p.F_ShippingLineId === id ? p : { ...p, F_ShippingLineId: id }, - ); - } else { - setShippingLineUnmatched(true); - } - }, [form.F_BLCopyCode, form.F_ShippingLineId, shippingLineRows]); - - useEffect(() => { - if (!mail) return; - const record = - mail.parse_result?.mail_record || - ( - mail.parse_result?.lineage as - | { mail_record?: { modules?: MailRecordModules } } - | undefined - )?.mail_record; - setForm( - buildCcForecastFormValue({ - customerName, - header: mail.parse_result?.container_header, - modules: record?.modules, + const resolved = useMemo( + () => + resolveConfirmInstruction({ + units, + instructionId, + lineage: mail?.parse_result?.lineage, }), - ); - }, [mail, customerName]); - - const validRows = useMemo( - () => shipments.filter((s) => s.row_status === "VALID"), - [shipments], + [units, instructionId, mail?.parse_result?.lineage], ); - const validRowKey = useMemo( - () => validRows.map((r) => r.row_index).join(","), - [validRows], - ); - - useEffect(() => { - if (softLimitExceeded) { - setSelected((prev) => (prev.size === 0 ? prev : new Set())); - return; - } - setSelected((prev) => { - const nextIds = validRowKey - ? validRowKey.split(",").map((x) => Number(x)) - : []; - if ( - prev.size === nextIds.length && - nextIds.every((id) => prev.has(id)) - ) { - return prev; - } - return new Set(nextIds); - }); - }, [mail?.id, validRowKey, softLimitExceeded]); - - useEffect(() => { - setContainerSource(null); - setStep(0); - }, [mail?.id]); useEffect(() => { - if (!hasContainerConflict || !containerConflict || !containerSource) return; - const next = - containerSource === "body" - ? containerConflict.body || "" - : containerConflict.attachment || ""; - const upper = next.toUpperCase(); - setForm((p) => - p.F_ContainerNo === upper ? p : { ...p, F_ContainerNo: upper }, - ); - }, [ - containerSource, - hasContainerConflict, - containerConflict?.body, - containerConflict?.attachment, - ]); - - useEffect(() => { - if (!mail || loading) return; - const valid = - mail.parse_result?.shipments.filter((s) => s.row_status === "VALID") - .length || 0; - const allowed = canEnterForecastConfirm({ - mailType: mail.mail_type, - status: mail.status, - validShipmentCount: valid, - allowReadonlyStatuses: true, - }); - - if (!allowed) { - message.warning(UI_COPY.confirmNotAllowed); - router.replace(`/mails/${id}`); - } - }, [mail, loading, id, router, message]); - - const selectedValid = Array.from(selected).filter((idx) => - validRows.some((r) => r.row_index === idx), - ); - - const hasUnmappedSelected = selectedValid.some((idx) => { - const row = shipments.find((s) => s.row_index === idx); - return row?.warnings?.includes("CHANNEL_UNMAPPED"); - }); - - const pageReadonly = - mail?.status === "IMPORTING" || mail?.status === "SUCCESS"; - - const validateStep1 = () => { - const next: Record = {}; - const { F_TransMode, F_OperationType, F_ContainerNo, F_CabinetType } = form; - if (F_TransMode !== 0 && F_TransMode !== 1 && F_TransMode !== 3) { - next.transMode = "请选择运输方式"; - } - if ( - F_OperationType !== 0 && - F_OperationType !== 2 && - F_OperationType !== 4 - ) { - next.operationType = "请选择业务类型"; - } - if (hasContainerConflict && !containerSource) { - next.containerSource = "请选定正文或附件柜号"; - } - if (!F_ContainerNo.trim()) next.containerNo = "柜号必填"; - else if (!isValidContainerNo(F_ContainerNo)) - next.containerNo = "柜号不符合 ISO 6346 格式"; - if (!F_CabinetType.trim()) next.cabinetType = "请选择柜型"; - setErrors(next); - return Object.keys(next).length === 0; - }; - - const validateStep2 = () => { - const next: Record = {}; - if (selectedValid.length === 0) next.rows = "请至少勾选一行有效货件"; - if (hasUnmappedSelected && !ackUnmapped) - next.ack = "请勾选已知晓未映射渠道"; - setErrors((p) => ({ ...p, ...next })); - return Object.keys(next).length === 0; - }; - - const onStepChange = (s: number) => { - if (pageReadonly) { - setStep(s); - return; - } - if (s === 1 && !validateStep1()) { - message.error("请先修正基础信息"); - return; - } - setStep(s); - }; - - const onPrimary = () => { - if (pageReadonly || !mail) return; - if (!validateStep1() || !validateStep2()) { - message.error("请先修正表单校验错误"); - return; - } - setConfirmOpen(true); - }; - - const onConfirmSubmit = async (forceSkip = false) => { - if (!mail) return; - const containerNo = form.F_ContainerNo.toUpperCase(); - const basePayload = { - // 服务端会以库内 version 对齐;此处仍传当前页 version - version: mail.version, - idempotency_key: `${mail.id}:${containerNo}:${selectedValid.join(",")}${forceSkip ? ":force" : ""}`, - F_TransMode: form.F_TransMode, - F_OperationType: form.F_OperationType, - container_header: { - F_ContainerNo: containerNo, - F_CabinetType: form.F_CabinetType, - F_Classis: form.F_Classis, - F_ETA: form.F_ETA || null, - F_ETD: form.F_ETD || null, - F_LoadPort: form.F_LoadPort, - F_Dock: form.F_Dock, - F_BLCopyCode: form.F_BLCopyCode, - F_MemoRemark: form.F_MemoRemark, - F_Instruction: form.F_Instruction, - F_ShippingLineId: form.F_ShippingLineId, - }, - selected_row_indexes: selectedValid, - ack_unmapped_channels: ackUnmapped, - ...(forceSkip ? { force_skip_conflict: true } : {}), - }; - - const res = await submit(basePayload); - - if (!res.ok) { - if ( - res.error.code === "CONFLICT_CONTAINER" || - res.error.code === "CONFLICT_CHECK_FAILED" - ) { - setConfirmOpen(false); - setConflictReason(res.error.code); - setConflictOpen(true); - return; - } - message.error(res.error.message); - setConfirmOpen(false); - setConflictOpen(false); - return; - } - - setConfirmOpen(false); - setConflictOpen(false); - if (res.data.mail_status === "SUCCESS") { - message.success("导入成功"); - router.push(`/logs?mail_id=${mail.id}`); - } else if (res.data.mail_status === "PARTIAL_SUCCESS") { - message.warning("部分成功,请查看导入日志"); - router.push(`/logs?mail_id=${mail.id}`); - } else { - // 业务失败(如 CC 校验)— 展示后端写入的具体原因 - const importErr = - res.data.imports?.[0]?.last_error || - mail.last_error || - "导入失败,可修改后重试"; - message.error(importErr); - router.refresh(); - } - }; - - const canForceSkip = - isAdmin && - forceImportEnabled && - (conflictReason === "CONFLICT_CONTAINER" || - conflictReason === "CONFLICT_CHECK_FAILED"); + if (!mail || loading || instructionId || !resolved.unit) return; + router.replace(confirmHref(mail.id, resolved.unit.id)); + }, [mail, loading, instructionId, resolved.unit, router]); if (loading || !mail) { return ( @@ -383,180 +58,71 @@ export default function MailConfirmPage() { ); } - if ( - mail.mail_type !== "NEW_CONTAINER" && - mail.mail_type !== "DO_UPLOAD" - ) { - return null; + if (!instructionId) { + if (resolved.unit) { + return ( +
+ +
+ +
+ ); + } + return ( + + ); } - return ( - - - - - - {mail.subject} - - - {form.F_ContainerNo || "-"} - - - - - - {ccUiModeLabel(ccUiMode)} · SaveContainer - - - - - {mail.status === "IMPORTING" ? ( - - ) : null} - - {shippingLineUnmatched && shippingLineHint ? ( + - ) : null} - - {softLimitExceeded ? ( - - ) : null} - - {hasContainerConflict ? ( - setContainerSource(e.target.value)} - style={{ marginTop: 8 }} - > - - - 正文柜号: - {containerConflict?.body} - - - 附件柜号: - {containerConflict?.attachment} - - - - } + message="该指令无需确认页" + description="客户指令只在详情展示,请返回邮件详情查看。" /> - ) : null} - {errors.containerSource ? ( - - {errors.containerSource} - - ) : null} - - setForm((p) => ({ ...p, ...patch }))} - errors={errors} - step={step} - onStepChange={onStepChange} - softLimitExceeded={softLimitExceeded} - containerNoLocked={hasContainerConflict} - onFinish={onPrimary} - finishLoading={submitting} - finishDisabled={ - selectedValid.length === 0 || - (hasContainerConflict && !containerSource) - } - /> - - {hasUnmappedSelected ? ( -
- setAckUnmapped(e.target.checked)} - > - {UI_COPY.ackUnmapped} - - {errors.ack ? ( -
- {errors.ack} -
- ) : null} -
- ) : null} - {errors.rows ? ( -
- {errors.rows} -
- ) : null} +
+ ); + } -
- - 已选 {selectedValid.length} / 有效 {validRows.length} - - -
+ if (resolved.unit.uiKind === "forecast") { + return ; + } + if (resolved.unit.uiKind === "transfer") { + return ; + } + if (resolved.unit.uiKind === "do_upload") { + return ; + } + if (resolved.unit.uiKind === "work_order") { + return ; + } - setConfirmOpen(false)} - onConfirm={() => void onConfirmSubmit(false)} - /> - setConflictOpen(false)} - onForceSkip={() => void onConfirmSubmit(true)} - /> + return ( + + 未知指令类型 + 返回详情 ); } diff --git a/src/app/(ops)/mails/[id]/page.tsx b/src/app/(ops)/mails/[id]/page.tsx index 20acbe0..3e95b4f 100644 --- a/src/app/(ops)/mails/[id]/page.tsx +++ b/src/app/(ops)/mails/[id]/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useMemo, useState } from "react"; +import Link from "next/link"; import { useParams, useRouter } from "next/navigation"; import { Alert, @@ -27,13 +28,16 @@ import { useCcUiMode } from "@/hooks/useCcUiMode"; import { healthApi, ignoreMailApi, + deleteMailsApi, overrideTypeApi, reparseMailApi, } from "@/lib/client-api"; import { buildMailIntent } from "@/services/parse/mail-intent"; -import { sanitizeOcrDisplayText } from "@/services/ocr"; +import { sanitizeOcrDisplayText } from "@/services/ocr/sanitize"; import { canTransition } from "@/services/state-machine"; import { canEnterForecastConfirm } from "@/services/cc/forecast-confirm-gate"; +import { extractMailInstructions } from "@/services/parse/split-instructions"; +import { confirmHref } from "@/services/parse/resolve-confirm-instruction"; import type { MailType } from "@/types/mail"; export default function MailDetailPage() { @@ -47,6 +51,7 @@ export default function MailDetailPage() { const [reparseLoading, setReparseLoading] = useState(false); const [ignoreLoading, setIgnoreLoading] = useState(false); + const [deleteLoading, setDeleteLoading] = useState(false); const [overrideOpen, setOverrideOpen] = useState(false); const [overrideLoading, setOverrideLoading] = useState(false); const [enableOverride, setEnableOverride] = useState(false); @@ -58,39 +63,51 @@ export default function MailDetailPage() { }); }, []); + useEffect(() => { + if (!id) return; + router.prefetch(`/mails/${id}/confirm`); + }, [id, router]); + const validCount = mail?.parse_result?.shipments.filter((s) => s.row_status === "VALID") .length || 0; + const hasContainerHeader = Boolean( + ( + mail?.parse_result?.container_header?.F_ContainerNo || + mail?.container_no || + "" + ).trim(), + ); + + const forecastUnit = useMemo(() => { + if (!mail) return null; + return extractMailInstructions({ + subject: mail.subject, + body: mail.body_text, + filenames: mail.attachments.map((a) => a.filename), + }).find((u) => u.uiKind === "forecast") || null; + }, [mail]); - const canImport = mail + const canImport = mail && forecastUnit ? canEnterForecastConfirm({ - mailType: mail.mail_type, status: mail.status, validShipmentCount: validCount, + hasContainerHeader, }) : false; const disableReason = useMemo(() => { - if (!mail) return ""; - if (canImport) return ""; - if (mail.mail_type === "WORK_ORDER" || mail.mail_type === "TRANSFER") { - return ""; - } - if (mail.mail_type === "DO_UPLOAD" && validCount < 1) { - return "本封无有效货件行,仅可确认上传 DO"; - } + if (!mail || canImport || !forecastUnit) return ""; if ( - mail.mail_type !== "NEW_CONTAINER" && - mail.mail_type !== "DO_UPLOAD" + mail.status !== "PENDING_CONFIRM" && + mail.status !== "FAILED" && + mail.status !== "PARTIAL_SUCCESS" ) { - return UI_COPY.disableNotNew(mail.mail_type); - } - if (mail.status !== "PENDING_CONFIRM" && mail.status !== "FAILED") { return UI_COPY.disableBadStatus(mail.status); } - if (validCount < 1) return UI_COPY.disableNoRows; + if (validCount < 1 && !hasContainerHeader) return UI_COPY.disableNoRows; return ""; - }, [mail, validCount, canImport]); + }, [mail, validCount, canImport, hasContainerHeader, forecastUnit]); const showReparse = mail ? [ @@ -204,6 +221,16 @@ export default function MailDetailPage() { ) : null} {new Date(mail.received_at).toLocaleString("zh-CN")} #{mail.id} + {mail.trace_id ? ( + + trace {mail.trace_id.slice(0, 8)} + + ) : null}
@@ -264,12 +291,43 @@ export default function MailDetailPage() { ) : null} + {forecastUnit ? ( + canImport ? ( + + + + ) : ( + + ) + ) : null} {!canImport && disableReason ? (
{disableReason}
@@ -346,17 +404,12 @@ export default function MailDetailPage() { extra={ 共 {mail.parse_result?.shipments.length || 0} 行 - {canImport ? ( + {canImport && forecastUnit ? ( <> {" · "} - + ) : null} @@ -377,7 +430,11 @@ export default function MailDetailPage() { title: "仓库", dataIndex: "F_FBACode", width: 88, - render: (v) => v || "—", + render: (v, row) => + v || + (/^(UPS|FEDEX|DHL|USPS)$/i.test(row.F_Transporter || "") + ? String(row.F_Transporter).toUpperCase() + : "—"), }, { title: "渠道", diff --git a/src/app/(ops)/mails/page.tsx b/src/app/(ops)/mails/page.tsx index 20d0382..e1279e1 100644 --- a/src/app/(ops)/mails/page.tsx +++ b/src/app/(ops)/mails/page.tsx @@ -1,21 +1,40 @@ "use client"; import { useEffect, useState } from "react"; -import { App, Button, Card, Input, Select, Space, Typography } from "antd"; +import { App, Button, Card, Input, Modal, Select, Space, Typography } from "antd"; import { ErrorBanner } from "@/components/ErrorBanner"; import { EmptyState } from "@/components/EmptyState"; import { MailTable } from "@/components/MailTable"; -import { UI_COPY, MAIL_STATUS_LABEL, MAIL_TYPE_LABEL } from "@/constants/ui-copy"; +import { + UI_COPY, + MAIL_STATUS_LABEL, + MAIL_TYPE_LABEL, +} from "@/constants/ui-copy"; +import { MAIL_CONTENT_KIND_LABEL } from "@/utils/mailContent"; +import { + MAIL_PRUNE_OLDEST_BATCH, + MAIL_STORAGE_SOFT_LIMIT, +} from "@/constants/mail-storage"; import { useMails } from "@/hooks/useMails"; +import { deleteMailsApi } from "@/lib/client-api"; + +const STORAGE_PROMPT_KEY = "yx.mail-storage-prompt-dismissed"; export default function MailsPage() { const { message } = App.useApp(); const [mailType, setMailType] = useState(""); const [status, setStatus] = useState(""); + const [contentKind, setContentKind] = useState(""); + const [sort, setSort] = useState<"pulled_at" | "imported_at" | "received_at">( + "pulled_at", + ); const [q, setQ] = useState(""); const [qDebounced, setQDebounced] = useState(""); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(20); + const [selectedIds, setSelectedIds] = useState([]); + const [deleting, setDeleting] = useState(false); + const [storagePromptOpen, setStoragePromptOpen] = useState(false); useEffect(() => { const id = window.setTimeout(() => setQDebounced(q), 300); @@ -24,12 +43,23 @@ export default function MailsPage() { useEffect(() => { setPage(1); - }, [mailType, status, qDebounced]); + setSelectedIds([]); + }, [mailType, status, contentKind, sort, qDebounced]); - const { items, total, loading, error, reload } = useMails({ + const { + items, + total, + storageTotal, + storageLimit, + loading, + error, + reload, + } = useMails({ mail_type: mailType || undefined, status: status || undefined, + content_kind: contentKind || undefined, q: qDebounced || undefined, + sort, page, pageSize, }); @@ -38,6 +68,41 @@ export default function MailsPage() { if (error) message.error(error); }, [error, message]); + useEffect(() => { + if (loading) return; + const limit = storageLimit || MAIL_STORAGE_SOFT_LIMIT; + if (storageTotal < limit) return; + try { + if (sessionStorage.getItem(STORAGE_PROMPT_KEY) === "1") return; + } catch { + /* ignore */ + } + setStoragePromptOpen(true); + }, [loading, storageTotal, storageLimit]); + + const dismissStoragePrompt = () => { + try { + sessionStorage.setItem(STORAGE_PROMPT_KEY, "1"); + } catch { + /* ignore */ + } + setStoragePromptOpen(false); + }; + + const runDelete = async (payload: { ids?: string[]; oldest?: number }) => { + setDeleting(true); + const res = await deleteMailsApi(payload); + setDeleting(false); + if (!res.ok) { + message.error(res.error.message); + return false; + } + message.success(`已删除 ${res.data.deleted} 封`); + setSelectedIds([]); + await reload(); + return true; + }; + return (
邮件预报 - + + +
@@ -69,6 +136,16 @@ export default function MailsPage() { label, }))} /> + + {UI_COPY.btnQuery} + {selectedIds.length > 0 ? ( + + ) : null}
{error ? : null} @@ -112,6 +218,10 @@ export default function MailsPage() { total={total} page={page} pageSize={pageSize} + selectedIds={selectedIds} + deleting={deleting} + onSelectedChange={setSelectedIds} + onDelete={(ids) => void runDelete({ ids })} onPageChange={(p, ps) => { setPage(p); setPageSize(ps); @@ -119,6 +229,33 @@ export default function MailsPage() { /> )} + + + 暂不删除 + , + , + ]} + > +

+ 存储达到 {storageLimit || MAIL_STORAGE_SOFT_LIMIT}{" "} + 封后建议清理较早拉取的邮件,以免磁盘占用过大。也可以先不删,之后在列表里勾选批量删除。 +

+
); } diff --git a/src/app/(ops)/settings/page.tsx b/src/app/(ops)/settings/page.tsx index e8d2459..57c1dc6 100644 --- a/src/app/(ops)/settings/page.tsx +++ b/src/app/(ops)/settings/page.tsx @@ -2,7 +2,6 @@ import { useCallback, useEffect, useState } from "react"; import { - Alert, App, Button, Card, @@ -19,11 +18,13 @@ import { } from "antd"; import { ErrorBanner } from "@/components/ErrorBanner"; import { CcSettingsCard } from "@/components/CcSettingsCard"; +import { ChangePasswordCard } from "@/components/ChangePasswordCard"; import { ImapPullSettingsCard } from "@/components/ImapPullSettingsCard"; import { MailboxBindHelpIcon } from "@/components/MailboxBindHelp"; import { OauthSettingsButton } from "@/components/OauthSettingsCard"; import { useAuthSession } from "@/components/providers/AuthSessionProvider"; import { UI_COPY } from "@/constants/ui-copy"; +import { formatOAuthCallbackError } from "@/constants/error-copy"; import { createMailboxApi, deleteMailboxApi, @@ -90,10 +91,10 @@ export default function SettingsPage() { const oauth = new URLSearchParams(window.location.search).get("oauth"); if (oauth?.startsWith("ok:")) message.success("OAuth 邮箱已绑定"); else if (oauth?.startsWith("error:")) - message.error(`OAuth 失败:${decodeURIComponent(oauth.slice(6))}`); + message.error(formatOAuthCallbackError(decodeURIComponent(oauth.slice(6)))); }, [message]); useEffect(() => { - if (user.role !== "admin") { + if (user.role !== "admin" && user.role !== "customer") { setAllowed(false); router.replace("/mails"); return; @@ -313,13 +314,16 @@ export default function SettingsPage() { {error ? : null} - + {user.role === "customer" ? : null} + {user.role === "admin" ? : null} - void reload()} /> + {user.role === "admin" ? ( + void reload()} /> + ) : null} {oauthProviders.map((p) => ( - - - - 对齐管理端货件列表操作条(本页预填;提交仍走 SaveContainer) - - - ) : ( + {!isAdmin ? ( { if (readonly || s === 0 || s === 1) setStep(s); }} /> - )} + ) : null}
{(isAdmin || step === 0) ? ( @@ -297,19 +310,19 @@ export function CcForecastFormOrder({ patch({ F_TransMode: v as TransMode })} /> @@ -319,24 +332,13 @@ export function CcForecastFormOrder({ disabled={readonly} style={{ width: "100%" }} placeholder={ph} - value={value.F_OperationType} + value={operationTypeValue} options={OPERATION_TYPE_OPTIONS} onChange={(v) => patch({ F_OperationType: v as OperationType }) } /> - - - patch({ F_ContainerNo: e.target.value.toUpperCase() }) - } - /> -
)} @@ -347,7 +349,7 @@ export function CcForecastFormOrder({ disabled={readonly} style={{ width: "100%" }} placeholder={ph} - value={value.F_OperationType} + value={operationTypeValue} options={OPERATION_TYPE_OPTIONS} onChange={(v) => patch({ F_OperationType: v as OperationType }) @@ -365,87 +367,23 @@ export function CcForecastFormOrder({ } /> - - patch({ F_CabinetType: v || "" })} + /> + + ) : null} ) : ( -
- - - - - - patch({ F_BLCopyCode: e.target.value.toUpperCase() }) - } - /> - -
- )} - -
- - patch({ F_ETD: e.target.value })} - /> - - - patch({ F_ETA: e.target.value })} - /> - - - patch({ F_LoadPort: e.target.value })} - /> - -
- -
- - patch({ F_Dock: e.target.value })} - /> - - {isAdmin ? ( +
- ) : ( - - ) : ( +
+ )} + + {isAdmin ? ( + <> + {opt(value.F_ETD) || opt(value.F_ETA) || opt(value.F_LoadPort) ? ( +
+ {opt(value.F_ETD) ? ( + + patch({ F_ETD: e.target.value })} + /> + + ) : null} + {opt(value.F_ETA) ? ( + + patch({ F_ETA: e.target.value })} + /> + + ) : null} + {opt(value.F_LoadPort) ? ( + + patch({ F_LoadPort: e.target.value })} + /> + + ) : null} +
+ ) : null} + + {opt(value.F_Dock) || + opt(value.F_BLCopyCode) || + opt(value.F_ContainerId) ? ( +
+ {opt(value.F_Dock) ? ( + + patch({ F_Dock: e.target.value })} + /> + + ) : null} + {opt(value.F_BLCopyCode) ? ( + + + patch({ F_BLCopyCode: e.target.value.toUpperCase() }) + } + /> + + ) : null} + {opt(value.F_ContainerId) ? ( + + + + ) : null} +
+ ) : null} + + {opt(value.F_ShippingLineId) || + opt(value.F_CustomerService) || + opt(value.F_Classis) ? ( +
+ {opt(value.F_ShippingLineId) ? ( + + + patch({ F_CustomerService: e.target.value }) + } + /> + + ) : null} + {opt(value.F_Classis) ? ( +
+ + patch({ F_Classis: e.target.checked ? 1 : 0 }) + } + > + 自带车架 + +
+ ) : null} +
+ ) : null} + + ) : ( +
- )} -
+
+ )} -
- {isAdmin ? ( - - + - patch({ F_CustomerService: e.target.value }) - } + autoSize={{ minRows: 3, maxRows: 8 }} + value={value.F_MemoRemark} + onChange={(e) => patch({ F_MemoRemark: e.target.value })} /> - ) : null} -
- {!isAdmin ? ( - - patch({ F_OverWeight: e.target.checked }) - } - > - 是否超重柜 - - ) : null} - - patch({ F_Classis: e.target.checked ? 1 : 0 }) - } - > - 自带车架 -
-
- -
- - patch({ F_Instruction: e.target.value })} - /> - - - patch({ F_MemoRemark: e.target.value })} - /> - -
+ )} ) : null} @@ -570,14 +602,6 @@ export function CcForecastFormOrder({ {isAdmin ? ( 货件列表 ) : null} -
-
货件填写须知
-
    - {PACKING_FILL_TIPS.map((t) => ( -
  1. {t}
  2. - ))} -
-
-
- {!isAdmin ? ( - <> - - - - ) : ( - 管理端单页:柜头 + 货件一次展示 - )} - {onFinish && !readonly ? ( - - ) : ( - - )} -
+ {!readonly && onFinish ? ( +
+ {finishBlockers.length ? ( +
+ 无法确认导入 +
    + {finishBlockers.map((item) => ( +
  • {item}
  • + ))} +
+
+ ) : finishNotes.length ? ( +
+ {finishNotes.map((item) => ( +
{item}
+ ))} +
+ ) : null} +
+ {!isAdmin ? ( + <> + + + + ) : null} + + + + + +
+
+ ) : null}
); } @@ -728,28 +770,84 @@ export function buildCcForecastFormValue(input: { const h = input.header || {}; const m = input.modules || {}; const vesselInst = m.vessel_voyage ? `船名航次 ${m.vessel_voyage}` : ""; + const containerNo = h.F_ContainerNo || m.container_no || ""; + const blNo = h.F_BLCopyCode || m.bl_no || ""; + const customer = sanitizeCustomerName( + input.customerName || m.customer_name || "", + { containerNo, blNo }, + ); + const memoRaw = h.F_MemoRemark || ""; + // 备注若残留「回复:柜号:…」等脏客户前缀,剥掉已占用柜号包装 + const memo = sanitizeMemoRemark(memoRaw, { containerNo, blNo, customer }); + const transMode = h.F_TransMode; + const operationType = h.F_OperationType; + const classis = + h.F_Classis === 0 || h.F_Classis === 1 + ? h.F_Classis + : m.classis === 0 || m.classis === 1 + ? m.classis + : undefined; return { - customerName: input.customerName || m.customer_name || "", - F_TransMode: (h.F_TransMode ?? 0) as TransMode, - F_OperationType: (h.F_OperationType ?? 0) as OperationType, - F_ContainerNo: h.F_ContainerNo || m.container_no || "", - F_CabinetType: h.F_CabinetType || m.cabinet_type || "40HQ", + customerName: customer, + F_TransMode: + transMode === 0 || transMode === 1 || transMode === 3 + ? transMode + : undefined, + F_OperationType: + operationType === 0 || operationType === 2 || operationType === 4 + ? operationType + : undefined, + F_ContainerNo: containerNo, + F_CabinetType: h.F_CabinetType || m.cabinet_type || "", F_ETD: h.F_ETD || m.etd || "", F_ETA: h.F_ETA || m.eta || "", F_LoadPort: h.F_LoadPort || "", - F_Dock: h.F_Dock || m.port || "", - F_BLCopyCode: h.F_BLCopyCode || m.bl_no || "", + // 初值:仅白名单港口;未识别则空。确认页用户可任意手改,提交不再二次白名单过滤 + F_Dock: normalizeDestinationPort(h.F_Dock) || normalizeDestinationPort(m.port) || "", + F_BLCopyCode: blNo, F_ContainerId: "", F_ShippingLineId: h.F_ShippingLineId || "", F_CustomerService: "", F_OverWeight: false, - F_Classis: (h.F_Classis ?? m.classis ?? 0) as 0 | 1, + F_Classis: classis, F_Instruction: stripMobileMailChrome( h.F_Instruction || vesselInst || "", ), - F_MemoRemark: - h.F_MemoRemark || - [m.customer_name, m.service].filter(Boolean).join(" / ") || - "", + F_MemoRemark: memo, }; } + +function sanitizeMemoRemark( + raw: string, + reserved: { containerNo?: string; blNo?: string; customer?: string }, +): string { + let t = (raw || "").trim(); + if (!t) return ""; + // 去掉「回复:柜号:XXX」整段若柜号已占用 + if (reserved.containerNo) { + t = t.replace( + new RegExp( + String.raw`(?:回复\s*[::]\s*)?柜号\s*[::]?\s*${reserved.containerNo}`, + "ig", + ), + "", + ); + } + if (reserved.blNo) { + t = t.replace( + new RegExp(String.raw`提单(?:号)?\s*[::]?\s*${reserved.blNo}`, "ig"), + "", + ); + } + t = t.replace(/^[\s/|·]+|[\s/|·]+$/g, "").replace(/\s*\/\s*$/g, "").trim(); + // 若整段只剩假客户,清空 + if (!sanitizeCustomerName(t, reserved) && /柜号|提单|回复|Fw:/i.test(t)) { + // 保留服务说明片段 + const service = t + .split(/\s*\/\s*/) + .map((x) => x.trim()) + .filter((x) => x && !/柜号|提单|回复|Fw:/i.test(x)); + return service.join(" / "); + } + return t; +} diff --git a/src/components/CcOutStockPanel.tsx b/src/components/CcOutStockPanel.tsx index 85fabe2..47f4484 100644 --- a/src/components/CcOutStockPanel.tsx +++ b/src/components/CcOutStockPanel.tsx @@ -10,6 +10,7 @@ import { type CcOutStockShipmentRow, } from "@/types/cc-ops"; import type { MailRecordModules, ShipmentRow } from "@/types/mail"; +import { matchSegmentAttachments } from "@/services/parse/match-segment-attachments"; const fieldRow: CSSProperties = { display: "grid", @@ -312,11 +313,13 @@ export function buildCcOutStockFormValue(input: { modules?: MailRecordModules | null; shipments?: ShipmentRow[]; subject?: string; + segmentText?: string; filenames?: string[]; }): CcOutStockFormValue { const containerNo = input.modules?.container_no || input.containerNo || ""; const hintText = [ + input.segmentText || "", input.subject || "", ...(input.filenames || []), input.modules?.service || "", @@ -369,7 +372,12 @@ export function buildCcOutStockFormValue(input: { return { header: { - F_LabelFile: "", + F_LabelFile: matchSegmentAttachments({ + segmentText: input.segmentText || "", + segmentSubject: input.subject || "", + filenames: input.filenames || [], + isCurrent: true, + }).join("; "), F_OutStockType: outType, F_Transporter: express, F_FBACode: diff --git a/src/components/CcTransferInstructionTable.tsx b/src/components/CcTransferInstructionTable.tsx new file mode 100644 index 0000000..7a18c0a --- /dev/null +++ b/src/components/CcTransferInstructionTable.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { Descriptions, Table } from "antd"; +import type { TransferInstructionRow } from "@/services/parse/transfer-instruction-table"; +import type { TransferPair } from "@/services/parse/mail-intent"; + +export function CcTransferInstructionTable({ + rows, + pairs, + ocrSource, + missingHint, +}: { + rows: TransferInstructionRow[]; + pairs: TransferPair[]; + ocrSource?: string; + missingHint?: string; +}) { + if (!rows.length) { + return ( + + ); + } + + return ( +
+ {ocrSource ? ( +
+ 来自截图 OCR:{ocrSource} +
+ ) : null} +
`${r.from}-${r.to}-${r.ctns}`} + columns={[ + { title: "地址仓", dataIndex: "from", width: 100 }, + { title: "箱数", dataIndex: "ctns", width: 72 }, + { title: "仓库重定向", dataIndex: "to", width: 120 }, + { title: "柜号", dataIndex: "containerNo", ellipsis: true }, + { title: "ETA", dataIndex: "eta", width: 110 }, + ]} + dataSource={rows} + /> + {pairs.length ? ( +
+ 本段转仓对: + {pairs.map((p) => ( + + {p.from} → {p.to} + {p.rows > 1 ? `(${p.rows} 箱)` : ""} + + ))} +
+ ) : null} + + ); +} diff --git a/src/components/CcWorkOrderForm.tsx b/src/components/CcWorkOrderForm.tsx index 37e0143..b096967 100644 --- a/src/components/CcWorkOrderForm.tsx +++ b/src/components/CcWorkOrderForm.tsx @@ -12,6 +12,7 @@ import { collectWorkOrderActions, extractContainerFromFirstLines, extractWorkOrderMessageBody, + matchWorkOrderAttachments, } from "@/services/parse/work-order-record"; const fieldRow: CSSProperties = { @@ -237,9 +238,6 @@ export function CcWorkOrderForm({ ) : ( /* —— 客户端 customerMessage 新增 —— */
-
- 对齐客户端:无「客户 / 紧急程度」字段;留言类型取字典 CustomerMessageType。 -
(); + const [saving, setSaving] = useState(false); + + const onSave = async () => { + const values = await form.validateFields(); + if (values.new_password !== values.confirm) { + message.error("两次输入的新密码不一致"); + return; + } + setSaving(true); + const res = await changePasswordApi({ + current_password: values.current_password, + new_password: values.new_password, + }); + setSaving(false); + if (!res.ok) { + message.error(res.error.message); + return; + } + message.success("密码已更新,管理端可看到新密码"); + form.resetFields(); + }; + + return ( + + + + + + + + + + + + + + + ); +} diff --git a/src/components/ConfirmImportModal.tsx b/src/components/ConfirmImportModal.tsx index 3f4e4cc..c829531 100644 --- a/src/components/ConfirmImportModal.tsx +++ b/src/components/ConfirmImportModal.tsx @@ -8,8 +8,6 @@ export function ConfirmImportModal({ loading, containerNo, selectedCount, - transMode, - operationType, onCancel, onConfirm, }: { @@ -17,8 +15,6 @@ export function ConfirmImportModal({ loading: boolean; containerNo: string; selectedCount: number; - transMode: number; - operationType: number; onCancel: () => void; onConfirm: () => void; }) { @@ -34,9 +30,6 @@ export function ConfirmImportModal({ onOk={onConfirm} >

{UI_COPY.modalImportBody(containerNo.toUpperCase(), selectedCount)}

-

- TransMode={transMode} · OperationType={operationType} -

); } diff --git a/src/components/ImapPullLogsPanel.tsx b/src/components/ImapPullLogsPanel.tsx index 3b23dfa..d906612 100644 --- a/src/components/ImapPullLogsPanel.tsx +++ b/src/components/ImapPullLogsPanel.tsx @@ -6,6 +6,7 @@ import { App, Button, Select, Space, Table, Tag, Typography } from "antd"; import { EmptyState } from "@/components/EmptyState"; import { ErrorBanner } from "@/components/ErrorBanner"; import { MAIL_TYPE_LABEL, UI_COPY } from "@/constants/ui-copy"; +import { formatImapSkipReason } from "@/constants/error-copy"; import { listImapPullLogsApi } from "@/lib/client-api"; import type { ImapPullLogItem } from "@/types"; @@ -166,7 +167,7 @@ export function ImapPullLogsPanel() { dataIndex: "reason", width: 120, render: (r: string | null) => - r ? REASON_LABEL[r] || r : "-", + r ? REASON_LABEL[r] || formatImapSkipReason(r) : "-", }, ]} /> diff --git a/src/components/ImapStatusBanner.tsx b/src/components/ImapStatusBanner.tsx index 668ed9c..ee9e0eb 100644 --- a/src/components/ImapStatusBanner.tsx +++ b/src/components/ImapStatusBanner.tsx @@ -5,22 +5,22 @@ import Link from "next/link"; import { Alert, App, Button, Space, Tag } from "antd"; import { useAuthSession } from "@/components/providers/AuthSessionProvider"; import { healthApi, imapPollApi } from "@/lib/client-api"; +import { + formatImapSkipReason, + formatImapSyncError, +} from "@/constants/error-copy"; import { requestMailsReload } from "@/lib/mails-events"; export function ImapStatusBanner() { const { message } = App.useApp(); const { user } = useAuthSession(); const isAdmin = user.role === "admin"; - const [mysqlOk, setMysqlOk] = useState(null); const [imapLock, setImapLock] = useState(); const [configured, setConfigured] = useState(false); const [syncAlert, setSyncAlert] = useState(false); const [failCount, setFailCount] = useState(0); const [lastError, setLastError] = useState(null); const [polling, setPolling] = useState(false); - const [ccMode, setCcMode] = useState<"mock" | "live" | null>(null); - const [ccLiveReady, setCcLiveReady] = useState(false); - const [ccReason, setCcReason] = useState(null); useEffect(() => { let cancelled = false; @@ -29,17 +29,11 @@ export function ImapStatusBanner() { const healthRes = await healthApi(); if (cancelled) return; if (healthRes.ok) { - setMysqlOk(healthRes.data.mysql === "ok"); setImapLock(healthRes.data.imap_lock); setConfigured(Boolean(healthRes.data.imap_configured)); setSyncAlert(Boolean(healthRes.data.imap_sync_alert)); setFailCount(healthRes.data.imap_consecutive_fail ?? 0); setLastError(healthRes.data.imap_last_error ?? null); - setCcMode(healthRes.data.cc_mode ?? null); - setCcLiveReady(Boolean(healthRes.data.cc_live_ready)); - setCcReason(healthRes.data.cc_reason ?? null); - } else { - setMysqlOk(false); } }; @@ -61,7 +55,7 @@ export function ImapStatusBanner() { if (reason === "lock_busy") { message.warning("拉取锁占用中(worker 可能正在同步),请稍后再试"); } else { - message.info(`跳过拉取:${reason}`); + message.info(`跳过拉取:${formatImapSkipReason(reason)}`); } } else { const n = res.data.processed ?? 0; @@ -89,27 +83,8 @@ export function ImapStatusBanner() { } }; - if (mysqlOk === null) { - return 健康检查…; - } - return ( - - MySQL {mysqlOk ? "OK" : "异常"} - - {ccMode === "mock" ? ( - - CC mock - - ) : ccMode === "live" ? ( - - {ccLiveReady ? "CC live" : "CC live 缺凭证"} - - ) : null} {!configured ? ( @@ -120,15 +95,13 @@ export function ImapStatusBanner() { ) : imapLock && imapLock !== "free" ? ( IMAP 拉取中 - ) : ( - IMAP 正常 - )} + ) : null} {isAdmin ? ( + + + ); } } else if (unit.uiKind === "transfer") { - const blocked = gate && !gate.canTransfer; + const unitImported = + instructionStateMap[unit.id]?.status === "IMPORTED"; + const segCtx = transferSegmentCtx.get(unit.id); + const unitPairs = + segCtx?.pairs?.length ? segCtx.pairs : transferPairs; + const unitShipments = filterTransferShipmentsByPairs( + transferShipments, + unitPairs, + ); + const unitTransferValue = buildCcTransferFormValue({ + customerName: record?.modules.customer_name, + containerNo, + modules: record?.modules, + shipments: unitShipments, + transferPairs: unitPairs, + }); + const roundBody = (unit.segmentText || "") + .replace(/ /gi, " ") + .replace(/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/gi, "") + .replace(/\s+/g, " ") + .trim(); body = ( <> - {gate ? ( + + + {unitImported ? ( ) : null} - {blocked ? ( - - setWorkOrderValue((v) => ({ ...v, ...p })) - } - /> - ) : ( - - )} + ); - if (canConfirmOps && unit.isCurrent) { - actions = blocked ? ( - - ) : ( - + if (canConfirmOps && !unitImported) { + actions = ( + + + ); } } else if (unit.uiKind === "work_order") { + const unitImported = + instructionStateMap[unit.id]?.status === "IMPORTED"; // 仅用本指令段的关键词 + 段正文分流,禁止整封 body 污染(如主题链里的「拦截-209件」) const unitActions = Array.from( new Set([ ...(unit.keywords || []), - ...(unit.isCurrent - ? workOrderValue.mailActions || [] - : []), + ...(workOrderDrafts[unit.id]?.mailActions || []), ]), ); const panelKind = resolveWorkOrderPanelKind( @@ -678,38 +640,27 @@ export function MailBusinessSummary({ .filter(Boolean) .join("\n"), ); - const woForUnit = unit.isCurrent - ? workOrderValue - : buildCcWorkOrderFormValue({ - subject: unit.segmentSubject || "", - body: unit.segmentText, - filenames: [], - containerNo, - actions: unit.keywords, - customerName: record?.modules.customer_name, - }); - const editMode = - canConfirmOps && unit.isCurrent ? "edit" : "readonly"; + const woForUnit = + workOrderDrafts[unit.id] || + buildWorkOrderDraft({ + unit, + mail, + containerNo, + filenames, + customerName: record?.modules.customer_name, + }); + const editMode = "readonly"; body = ( <> - + {unitImported ? ( + + ) : null} {panelKind === "hold" ? ( - setWorkOrderValue((v) => ({ ...v, ...p })) + setWorkOrderDrafts((v) => ({ + ...v, + [unit.id]: { ...woForUnit, ...p }, + })) } /> )} ); - if (canConfirmOps && unit.isCurrent && panelKind === "message") { + if (canConfirmOps && !unitImported) { actions = ( - - ); - } else if ( - canConfirmOps && - unit.isCurrent && - panelKind !== "message" - ) { - actions = ( - + + + ); } } else if (unit.uiKind === "do_upload") { + const unitImported = + instructionStateMap[unit.id]?.status === "IMPORTED"; const doForUnit = buildCcDoUploadFormValue({ subject: unit.segmentSubject || mail.subject, body: unit.segmentText || bodyText, @@ -778,36 +720,17 @@ export function MailBusinessSummary({ containerTaskId: successImportExternalId, }); body = ( - <> - {mail.status === "SUCCESS" ? ( - - ) : null} setDoValue((v) => ({ ...v, ...p }))} /> - ); - if (canDoConfirm && unit.isCurrent) { + if (canDoConfirm && !unitImported) { actions = ( - + + + ); } } else if (unit.uiKind === "customer_instruction") { @@ -829,6 +752,7 @@ export function MailBusinessSummary({ unit={unit} index={index} total={instructions.length} + imported={instructionStateMap[unit.id]?.status === "IMPORTED"} > {body} {actions ? ( diff --git a/src/components/MailInstructionUnitShell.tsx b/src/components/MailInstructionUnitShell.tsx index b6d75c3..98ef53f 100644 --- a/src/components/MailInstructionUnitShell.tsx +++ b/src/components/MailInstructionUnitShell.tsx @@ -9,11 +9,13 @@ export function InstructionUnitShell({ unit, index, total, + imported = false, children, }: { unit: MailInstructionUnit; index: number; total: number; + imported?: boolean; children: ReactNode; }) { const color = @@ -32,6 +34,14 @@ export function InstructionUnitShell({ {index + 1}/{total} {unit.label} + {imported ? ( + 已导入 + ) : null} + {unit.isCurrent ? ( + 当前 + ) : ( + 历史 + )}
{children}
diff --git a/src/components/MailTable.tsx b/src/components/MailTable.tsx index 08bb9e9..6ad59d3 100644 --- a/src/components/MailTable.tsx +++ b/src/components/MailTable.tsx @@ -2,13 +2,13 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; -import { Button, Space, Table, Tooltip, Typography } from "antd"; +import { Button, Popconfirm, Space, Table, Tooltip, Typography } from "antd"; import type { ColumnsType, TablePaginationConfig } from "antd/es/table"; import { StatusPill } from "@/components/StatusPill"; import { TypeTag } from "@/components/TypeTag"; -import { canEnterForecastConfirm } from "@/services/cc/forecast-confirm-gate"; import type { MailListItem } from "@/types"; import type { MailStatus, MailType } from "@/types/mail"; +import { formatMailLastError } from "@/constants/error-copy"; function formatTs(v: string | null | undefined) { if (!v) return "-"; @@ -21,14 +21,22 @@ export function MailTable({ total, page, pageSize, + selectedIds, + deleting, onPageChange, + onSelectedChange, + onDelete, }: { items: MailListItem[]; loading?: boolean; total: number; page: number; pageSize: number; + selectedIds?: string[]; + deleting?: boolean; onPageChange?: (page: number, pageSize: number) => void; + onSelectedChange?: (ids: string[]) => void; + onDelete?: (ids: string[]) => void; }) { const router = useRouter(); @@ -102,55 +110,67 @@ export function MailTable({ title: "导入CC状态", dataIndex: "status", width: 140, - render: (status: MailStatus) => , + render: (status: MailStatus, row) => ( + + + + + + ), }, { - title: "起止时间", + title: "时间", key: "time_range", - width: 200, + width: 220, render: (_, row) => (
-
起:{formatTs(row.received_at)}
-
止:{formatTs(row.ended_at)}
+
拉取:{formatTs(row.pulled_at || row.received_at)}
+
导入:{formatTs(row.imported_at)}
), }, { title: "操作", key: "actions", - width: 168, + width: 200, fixed: "right", render: (_, row) => { - const canConfirm = canEnterForecastConfirm({ - mailType: row.mail_type, - status: row.status, - // 列表无货件统计;门槛在详情/确认页与 import API - validShipmentCount: 1, - }); + const canOpenConfirm = [ + "PENDING_CONFIRM", + "FAILED", + "PARTIAL_SUCCESS", + ].includes(row.status); return ( - - {canConfirm ? ( - + 确认 + ) : ( - + )} + onDelete?.([row.id])} + > + + ); }, @@ -175,7 +195,15 @@ export function MailTable({ dataSource={items} pagination={pagination} size="middle" - scroll={{ x: 1400 }} + scroll={{ x: 1480 }} + rowSelection={ + onSelectedChange + ? { + selectedRowKeys: selectedIds, + onChange: (keys) => onSelectedChange(keys.map(String)), + } + : undefined + } onRow={(row) => ({ onDoubleClick: () => router.push(`/mails/${row.id}`), style: { cursor: "pointer" }, diff --git a/src/components/ShipmentVirtualTable.tsx b/src/components/ShipmentVirtualTable.tsx index fe2afb3..abb6ea8 100644 --- a/src/components/ShipmentVirtualTable.tsx +++ b/src/components/ShipmentVirtualTable.tsx @@ -4,9 +4,12 @@ import { useCallback, useMemo, useRef, type ReactNode, type UIEvent } from "reac import { useVirtualizer } from "@tanstack/react-virtual"; import { Checkbox } from "antd"; import type { ShipmentRow } from "@/types/mail"; +import { formatShipmentIssue } from "@/constants/error-copy"; +import { normalizeFbaCodeForCc } from "@/services/parse/normalize-fba-code"; const ROW_HEIGHT = 48; -const TABLE_HEIGHT = 480; +const MAX_TABLE_HEIGHT = 480; +const EMPTY_TABLE_HEIGHT = 96; export interface ShipmentColumn { key: string; @@ -15,14 +18,15 @@ export interface ShipmentColumn { render: (row: ShipmentRow) => ReactNode; } -const ALWAYS_VISIBLE_COL_KEYS = new Set(["idx", "ck", "st"]); +const ALWAYS_VISIBLE_COL_KEYS = new Set(["idx", "ck", "st", "ch", "fba"]); const COL_VALUE_GETTERS: Record unknown> = { ch: (r) => r.F_Transporter, sid: (r) => r.F_ShipmentID, fbaid: (r) => r.F_FBAID, po: (r) => r.F_PO, - fba: (r) => r.F_FBACode, + fba: (r) => + normalizeFbaCodeForCc(r.F_FBACode, r.F_Transporter || "") || r.F_FBACode, addr: (r) => r.F_Address, ctns: (r) => r.F_CTNS, cbm: (r) => r.F_CBM, @@ -50,18 +54,15 @@ function isMeaningfulValue(v: unknown): boolean { return s !== "" && s !== "-" && s !== "/" && s !== "—" && s !== "/"; } -/** 附件没有的列不展示,避免满屏「-」 */ +/** + * 无附件解析行时展示全部列头; + * 有提取数据时只保留附件里实际有值的列(序号/勾选/状态始终保留)。 + */ export function filterShipmentColumns( columns: ShipmentColumn[], rows: ShipmentRow[], ): ShipmentColumn[] { - if (!rows.length) { - return columns.filter( - (c) => - ALWAYS_VISIBLE_COL_KEYS.has(c.key) || - ["ch", "fba", "ctns", "fbaid", "sid", "addr"].includes(c.key), - ); - } + if (!rows.length) return columns; return columns.filter((col) => { if (ALWAYS_VISIBLE_COL_KEYS.has(col.key)) return true; const getter = COL_VALUE_GETTERS[col.key]; @@ -97,6 +98,10 @@ export function ShipmentVirtualTable({ }); const totalWidth = visibleColumns.reduce((sum, col) => sum + col.width, 0); + const bodyHeight = + rows.length === 0 + ? EMPTY_TABLE_HEIGHT + : Math.min(MAX_TABLE_HEIGHT, rows.length * ROW_HEIGHT); const syncHeaderScroll = useCallback((e: UIEvent) => { if (headerRef.current) { @@ -147,7 +152,7 @@ export function ShipmentVirtualTable({
{row.F_ShipmentID || "-"}, }, - { key: "st", title: "行状态", width: 80, render: (row) => row.row_status }, + { key: "st", title: "行状态", width: 80, render: (row) => formatShipmentIssue(row.row_status) || row.row_status }, { key: "warn", title: "警告", width: 140, - render: (row) => - row.warnings?.join(",") || row.invalid_reasons?.join(",") || "-", + render: (row) => { + const parts = [ + ...(row.warnings || []), + ...(row.invalid_reasons || []), + ].map((x) => formatShipmentIssue(x) || x); + return parts.length ? parts.join(";") : "-"; + }, }, ]; /** FormOrder 货件列表列(对齐 ccnew FormOrder.js) */ +function displayFbaCode(row: ShipmentRow): string { + return normalizeFbaCodeForCc(row.F_FBACode, row.F_Transporter || ""); +} + function cellText(v: unknown, fallback = "-"): string { const s = v == null || v === "" ? "" : String(v).replace(/\s+/g, " ").trim(); return s || fallback; @@ -286,6 +300,12 @@ export const CC_SHIPMENT_COLUMNS: ShipmentColumn[] = [ width: 100, render: (row) => cellText(row.F_Transporter), }, + { + key: "fba", + title: "FBACode", + width: 88, + render: (row) => cellText(displayFbaCode(row)), + }, { key: "sid", title: "Custmer Ref No.", @@ -304,12 +324,6 @@ export const CC_SHIPMENT_COLUMNS: ShipmentColumn[] = [ width: 100, render: (row) => monoCell(row.F_PO), }, - { - key: "fba", - title: "FBACode", - width: 88, - render: (row) => cellText(row.F_FBACode), - }, { key: "addr", title: "Address", @@ -443,13 +457,13 @@ export const CC_TRANSFER_SHIPMENT_COLUMNS: ShipmentColumn[] = [ key: "fba", title: "FBACode", width: 88, - render: (row) => cellText(row.F_FBACode), + render: (row) => cellText(displayFbaCode(row)), }, { key: "prevFc", title: "转仓前FC", width: 88, - render: (row) => cellText(row.F_FBACode), + render: (row) => cellText(displayFbaCode(row)), }, { key: "targetFc", diff --git a/src/components/TestWorkbench.tsx b/src/components/TestWorkbench.tsx new file mode 100644 index 0000000..41d3f40 --- /dev/null +++ b/src/components/TestWorkbench.tsx @@ -0,0 +1,444 @@ +"use client"; + +import { useCallback, useEffect, useMemo, useState } from "react"; +import Link from "next/link"; +import { + App, + Button, + Card, + Select, + Space, + Table, + Tag, + Typography, +} from "antd"; +import type { ColumnsType } from "antd/es/table"; +import { ErrorBanner } from "@/components/ErrorBanner"; +import { StatusPill } from "@/components/StatusPill"; +import { TypeTag } from "@/components/TypeTag"; +import { formatMailLastError } from "@/constants/ui-copy"; +import { + testFaultsGetApi, + testFaultsSetApi, + testFixturesApi, + testInjectApi, + imapPollApi, + testPipelineApi, + testResetApi, + testRunAllApi, + testTargetRequestsApi, + type TestFixtureItem, + type TestPipelineItem, + type TestRecordedRequest, + type TestRunCase, +} from "@/lib/client-api"; +import type { MailStatus, MailType } from "@/types/mail"; + +const CAT_LABEL: Record = { + normal: "正常", + boundary: "边界", + abnormal: "异常", + duplicate: "重复", + multi_attachment: "多附件", +}; + +function stageLabel(status: string): string { + if (status === "FETCHED" || status === "PARSING") return "已拉取"; + if ( + status === "PARSED" || + status === "PENDING_CONFIRM" || + status === "REJECTED_VALIDATION" || + status === "PARSE_FAILED" + ) { + return status === "PENDING_CONFIRM" ? "已暂存" : "已解析"; + } + if (status === "SUCCESS" || status === "PARTIAL_SUCCESS") return "已提交"; + if (status === "FAILED" || status === "IMPORTING") return "提交中/失败"; + if (status === "IGNORED") return "已忽略"; + return status; +} + +export function TestWorkbench() { + const { message } = App.useApp(); + const [error, setError] = useState(null); + const [fixtures, setFixtures] = useState([]); + const [pipeline, setPipeline] = useState([]); + const [requests, setRequests] = useState([]); + const [imapFault, setImapFault] = useState("none"); + const [targetFault, setTargetFault] = useState("ok"); + const [targetPath, setTargetPath] = useState("/Container/SaveContainer"); + const [injecting, setInjecting] = useState(null); + const [polling, setPolling] = useState(false); + const [running, setRunning] = useState(false); + const [report, setReport] = useState<{ + passed: number; + failed: number; + cases: TestRunCase[]; + } | null>(null); + const [loading, setLoading] = useState(true); + + const reload = useCallback(async () => { + setLoading(true); + const [fx, pipe, rec, faults] = await Promise.all([ + testFixturesApi(), + testPipelineApi(), + testTargetRequestsApi(), + testFaultsGetApi(), + ]); + setLoading(false); + if (!fx.ok) { + setError(fx.error.message); + return; + } + setError(null); + setFixtures(fx.data.items); + if (pipe.ok) setPipeline(pipe.data.items); + if (rec.ok) setRequests(rec.data.requests.slice().reverse()); + if (faults.ok) { + setImapFault(faults.data.imap || "none"); + const pathHit = faults.data.target?.pathFaults?.[0]; + setTargetFault(pathHit?.[1] || faults.data.target?.defaultFault || "ok"); + if (pathHit?.[0]) setTargetPath(pathHit[0]); + } + }, []); + + useEffect(() => { + void reload(); + }, [reload]); + + const onInject = async (id: string) => { + setInjecting(id); + const res = await testInjectApi({ fixture_id: id, run_id: "wb" }); + setInjecting(null); + if (!res.ok) { + message.error(res.error.message); + return; + } + message.success(`已注入 ${id} → ${res.data.status}`); + void reload(); + }; + + const onFaultImap = async (v: string) => { + setImapFault(v); + const res = await testFaultsSetApi({ imap: v }); + if (!res.ok) message.error(res.error.message); + }; + + const onFaultTarget = async (v: string) => { + setTargetFault(v); + const res = await testFaultsSetApi({ + target: v, + target_path: targetPath, + }); + if (!res.ok) message.error(res.error.message); + }; + + const onReset = async () => { + const res = await testResetApi(); + if (!res.ok) { + message.error(res.error.message); + return; + } + setReport(null); + message.success("已清空 Mock 请求与故障开关"); + void reload(); + }; + + const onPoll = async () => { + setPolling(true); + const res = await imapPollApi(); + setPolling(false); + if (!res.ok) { + message.error(res.error.message); + return; + } + if (res.data.skipped) { + message.warning(`拉取跳过:${res.data.reason || "busy"}`); + } else { + message.info( + `拉取完成 processed=${res.data.processed} last_error=${res.data.last_error || "无"}`, + ); + } + void reload(); + }; + + const onRunAll = async () => { + setRunning(true); + setReport(null); + const res = await testRunAllApi({ pipeline: true, run_id: "wb" }); + setRunning(false); + if (!res.ok) { + message.error(res.error.message); + return; + } + setReport(res.data); + if (res.data.failed === 0) message.success(`全量通过 ${res.data.passed} 项`); + else message.warning(`通过 ${res.data.passed} / 失败 ${res.data.failed}`); + void reload(); + }; + + const fixtureCols: ColumnsType = useMemo( + () => [ + { title: "样例", dataIndex: "id", width: 200, ellipsis: true }, + { + title: "类别", + dataIndex: "category", + width: 88, + render: (c: string) => CAT_LABEL[c] || c, + }, + { + title: "期望", + key: "exp", + width: 220, + render: (_, row) => ( + + {row.expected.mail_type} + · {row.expected.status} + + ), + }, + { + title: "主题", + dataIndex: "subject", + ellipsis: true, + }, + { + title: "", + key: "act", + width: 88, + render: (_, row) => ( + + ), + }, + ], + [injecting], + ); + + const pipeCols: ColumnsType = [ + { + title: "阶段", + dataIndex: "status", + width: 100, + render: (s: string) => stageLabel(s), + }, + { + title: "状态", + dataIndex: "status", + width: 120, + render: (s: string) => , + }, + { + title: "类型", + dataIndex: "mail_type", + width: 100, + render: (t: string) => , + }, + { + title: "主题", + dataIndex: "subject", + ellipsis: true, + render: (s: string, row) => ( + {s || row.mail_id} + ), + }, + { + title: "失败原因", + dataIndex: "last_error", + ellipsis: true, + render: (e: string | null) => + e ? ( + {formatMailLastError(e)} + ) : ( + "—" + ), + }, + ]; + + const reqCols: ColumnsType = [ + { title: "时间", dataIndex: "at", width: 180, render: (t: string) => t.slice(11, 19) }, + { title: "方法", dataIndex: "method", width: 64 }, + { title: "路径", dataIndex: "path", width: 240, ellipsis: true }, + { + title: "Body", + dataIndex: "body", + ellipsis: true, + render: (b: unknown) => ( + + {b == null ? "—" : JSON.stringify(b).slice(0, 180)} + + ), + }, + ]; + + const reportCols: ColumnsType = [ + { + title: "", + dataIndex: "ok", + width: 72, + render: (ok: boolean) => + ok ? 通过 : 失败, + }, + { title: "分组", dataIndex: "group", width: 96 }, + { title: "用例", dataIndex: "id", width: 260, ellipsis: true }, + { title: "说明", dataIndex: "detail", ellipsis: true }, + ]; + + return ( + +
+ + + 测试工作台 + + TEST_MODE + + + + + + + +
+ + {error ? : null} + + + + + IMAP + void onFaultTarget(v)} + options={[ + { value: "ok", label: "成功" }, + { value: "401", label: "鉴权失败 401" }, + { value: "500", label: "服务器 500" }, + { value: "timeout", label: "超时" }, + { value: "400", label: "400" }, + { value: "partial", label: "部分成功" }, + ]} + /> + + + 路径 +
+ + +
`${r.at}-${r.path}-${r.method}`} + pagination={false} + columns={reqCols} + dataSource={requests.slice(0, 80)} + scroll={{ y: 360 }} + /> + + + + +
+ + + {report ? ( + +
(row.ok ? "" : "wb-row-fail")} + /> + + ) : null} + + ); +} diff --git a/src/components/TopNav.tsx b/src/components/TopNav.tsx index e5bb320..8aa2bbf 100644 --- a/src/components/TopNav.tsx +++ b/src/components/TopNav.tsx @@ -10,24 +10,35 @@ import { useAuthSession, } from "@/components/providers/AuthSessionProvider"; import { UI_COPY } from "@/constants/ui-copy"; -import { logoutApi } from "@/lib/client-api"; +import { logoutApi, healthApi } from "@/lib/client-api"; const { Header } = Layout; -const OPS_ROUTES = ["/mails", "/logs", "/settings"] as const; +const OPS_ROUTES = ["/mails", "/logs", "/settings", "/customers", "/test-workbench"] as const; export function TopNav() { const pathname = usePathname(); const router = useRouter(); const { user } = useAuthSession(); const [loggingOut, setLoggingOut] = useState(false); + const [testMode, setTestMode] = useState(false); + + useEffect(() => { + void healthApi().then((res) => { + if (res.ok) setTestMode(Boolean(res.data.test_mode)); + }); + }, []); useEffect(() => { for (const path of OPS_ROUTES) { - if (path === "/settings" && user.role !== "admin") continue; + if (path === "/settings" && user.role !== "admin" && user.role !== "customer") { + continue; + } + if (path === "/customers" && user.role !== "admin") continue; + if (path === "/test-workbench" && !testMode) continue; router.prefetch(path); } - }, [router, user.role]); + }, [router, user.role, testMode]); const onLogout = async () => { setLoggingOut(true); @@ -39,15 +50,25 @@ export function TopNav() { const selectedKey = pathname.startsWith("/settings") ? "/settings" - : pathname.startsWith("/logs") + : pathname.startsWith("/customers") + ? "/customers" + : pathname.startsWith("/logs") ? "/logs" - : "/mails"; + : pathname.startsWith("/test-workbench") + ? "/test-workbench" + : "/mails"; const menuItems = [ - { key: "/mails", label: UI_COPY.navMails }, - { key: "/logs", label: UI_COPY.navLogs }, + { key: "/mails", label: {UI_COPY.navMails} }, + { key: "/logs", label: {UI_COPY.navLogs} }, + ...(user.role === "admin" || user.role === "customer" + ? [{ key: "/settings", label: {UI_COPY.navSettings} }] + : []), ...(user.role === "admin" - ? [{ key: "/settings", label: UI_COPY.navSettings }] + ? [{ key: "/customers", label: {UI_COPY.navCustomers} }] + : []), + ...(testMode + ? [{ key: "/test-workbench", label: 测试工作台 }] : []), ]; @@ -78,10 +99,7 @@ export function TopNav() { mode="horizontal" selectedKeys={[selectedKey]} items={menuItems} - onClick={({ key }) => { - if (key && key !== pathname) router.push(key); - }} - style={{ flex: 1, minWidth: 320, border: "none", lineHeight: "54px" }} + style={{ flex: 1, minWidth: 520, border: "none", lineHeight: "54px" }} /> @@ -91,6 +109,8 @@ export function TopNav() { {user.role === "admin" ? ( Admin + ) : user.role === "customer" ? ( + 客户 ) : null} + + + {unit.label} + + {mail.subject} + + {form.F_ContainerNo || "-"} + + + + + + {ccUiModeLabel(ccUiMode)} · SaveContainer + + + + + {mail.status === "IMPORTING" ? ( + + ) : null} + + {unitImported ? ( + + ) : null} + + {softLimitExceeded ? ( + + ) : null} + + {!pageReadonly && importIssues.blockers.length ? ( + + {importIssues.blockers.map((item) => ( +
  • {item}
  • + ))} + + } + /> + ) : null} + + {!pageReadonly && + !importIssues.blockers.length && + importIssues.notes.length ? ( + + ) : null} + + {hasContainerConflict ? ( + setContainerSource(e.target.value)} + style={{ marginTop: 8 }} + > + + + 正文柜号: + {containerConflict?.body} + + + 附件柜号: + {containerConflict?.attachment} + + + + } + /> + ) : null} + {errors.containerSource ? ( + + {errors.containerSource} + + ) : null} + + {hasUnmappedSelected ? ( +
    + setAckUnmapped(e.target.checked)} + > + {UI_COPY.ackUnmapped} + + {errors.ack ? ( +
    + {errors.ack} +
    + ) : null} +
    + ) : null} + + setForm((p) => ({ ...p, ...patch }))} + errors={errors} + step={step} + onStepChange={onStepChange} + softLimitExceeded={softLimitExceeded} + containerNoLocked={hasContainerConflict} + onFinish={onPrimary} + finishLoading={submitting} + finishDisabled={importIssues.blockers.length > 0} + finishBlockers={pageReadonly ? [] : importIssues.blockers} + finishNotes={pageReadonly ? [] : importIssues.notes} + /> + +
    + + 已选 {selectedValid.length} / 有效 {validRows.length} + {!pageReadonly && importIssues.blockers.length ? ( + + {importIssues.blockers[0]} + + ) : null} + + + + +
    + + setConfirmOpen(false)} + onConfirm={() => void onConfirmSubmit(false)} + /> + setConflictOpen(false)} + onForceSkip={() => void onConfirmSubmit(true)} + /> + + ); +} diff --git a/src/components/confirm/InstructionConfirmPicker.tsx b/src/components/confirm/InstructionConfirmPicker.tsx new file mode 100644 index 0000000..d291d1c --- /dev/null +++ b/src/components/confirm/InstructionConfirmPicker.tsx @@ -0,0 +1,80 @@ +"use client"; + +import Link from "next/link"; +import { Alert, Button, Space, Typography } from "antd"; +import { LeftOutlined } from "@ant-design/icons"; +import { InstructionUnitShell } from "@/components/MailInstructionUnitShell"; +import { UI_COPY } from "@/constants/ui-copy"; +import { confirmHref } from "@/services/parse/resolve-confirm-instruction"; +import { instructionUiKindLabel } from "@/services/parse/split-instructions"; +import type { MailInstructionUnit } from "@/services/parse/split-instructions"; + +export function InstructionConfirmPicker({ + mailId, + subject, + units, + missingId, +}: { + mailId: string; + subject: string; + units: MailInstructionUnit[]; + missingId?: boolean; +}) { + return ( + + + + + + + {subject} + + + + + + {units.length === 0 ? ( + + ) : ( +
    + {units.map((unit, index) => ( + + + {unit.summary || instructionUiKindLabel(unit.uiKind)} + + + + + + ))} +
    + )} +
    + ); +} diff --git a/src/components/confirm/OpsInstructionConfirmView.tsx b/src/components/confirm/OpsInstructionConfirmView.tsx new file mode 100644 index 0000000..c5e4492 --- /dev/null +++ b/src/components/confirm/OpsInstructionConfirmView.tsx @@ -0,0 +1,636 @@ +"use client"; + +import { useEffect, useMemo, useState, type ReactNode } from "react"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { Alert, App, Button, Space, Typography } from "antd"; +import { LeftOutlined } from "@ant-design/icons"; +import { + CcDoUploadPanel, + buildCcDoUploadFormValue, +} from "@/components/CcDoUploadPanel"; +import { CcCustomerInstructionPanel } from "@/components/CcCustomerInstructionPanel"; +import { CcHoldMoveTypePanel, buildCcHoldFormValue } from "@/components/CcHoldMoveTypePanel"; +import { CcOutStockPanel, buildCcOutStockFormValue } from "@/components/CcOutStockPanel"; +import { CcSplitForm, buildCcSplitFormValue } from "@/components/CcSplitForm"; +import { CcTransferInstructionTable } from "@/components/CcTransferInstructionTable"; +import { + CcTransferIndexTruck, + buildCcTransferFormValue, +} from "@/components/CcTransferIndexTruck"; +import { + CcWorkOrderForm, + buildCcWorkOrderFormValue, + type CcWorkOrderFormValue, +} from "@/components/CcWorkOrderForm"; +import { CcUiModeSwitch } from "@/components/CcUiModeSwitch"; +import { UI_COPY } from "@/constants/ui-copy"; +import { ccUiModeLabel } from "@/constants/cc-ui-mode"; +import { useCcUiMode } from "@/hooks/useCcUiMode"; +import { + confirmDoUploadApi, + confirmTransferApi, + confirmWorkOrderApi, + getTransferGateApi, +} from "@/lib/client-api"; +import { toCcCustomerMessageEntity } from "@/services/cc/customer-message-entity"; +import { resolveWorkOrderPanelKind } from "@/services/cc/work-order-panel"; +import { isInstructionImported } from "@/services/parse/instruction-execution-state"; +import { transferSegmentContextsFromLineage } from "@/services/parse/match-transfer-segment-ocr"; +import { + buildMailIntent, + extractTransferPairs, + filterTransferShipments, + filterTransferShipmentsByPairs, +} from "@/services/parse/mail-intent"; +import { instructionUiKindLabel } from "@/services/parse/split-instructions"; +import type { MailInstructionUnit } from "@/services/parse/split-instructions"; +import type { MailMessage } from "@/types"; +import type { ContainerHeader, MailRecord, MailType, TypeEvidence } from "@/types/mail"; +import { withVersionConflictRetry } from "@/utils/mail-submit-version"; + +function useSubmitVersion(mailVersion: number) { + const [version, setVersion] = useState(mailVersion); + useEffect(() => { + setVersion(mailVersion); + }, [mailVersion]); + return { version, setVersion }; +} + +function asHeader(mail: MailMessage): ContainerHeader | null { + return mail.parse_result?.container_header ?? null; +} + +function asMailRecord(mail: MailMessage): MailRecord | null { + const direct = mail.parse_result?.mail_record; + if (direct) return direct; + const lineage = mail.parse_result?.lineage as + | { mail_record?: MailRecord } + | undefined; + return lineage?.mail_record ?? null; +} + +function ConfirmChrome({ + mail, + unit, + ccUiMode, + setCcUiMode, + children, +}: { + mail: MailMessage; + unit: MailInstructionUnit; + ccUiMode: ReturnType["mode"]; + setCcUiMode: ReturnType["setMode"]; + children: ReactNode; +}) { + return ( + + + + + + + + {unit.label} + + {mail.subject} + + + + + {ccUiModeLabel(ccUiMode)} · {instructionUiKindLabel(unit.uiKind)} + + + + {children} + + ); +} + +function afterConfirm(router: ReturnType, mailId: string) { + router.push(`/mails/${mailId}`); +} + +export function TransferConfirmView({ + mail, + unit, +}: { + mail: MailMessage; + unit: MailInstructionUnit; +}) { + const router = useRouter(); + const { message } = App.useApp(); + const { mode: ccUiMode, setMode: setCcUiMode } = useCcUiMode(); + const { version: submitVersion, setVersion: setSubmitVersion } = + useSubmitVersion(mail.version); + const header = asHeader(mail); + const record = asMailRecord(mail); + const shipments = mail.parse_result?.shipments ?? []; + const filenames = mail.attachments.map((a) => a.filename); + const imageFilenames = mail.attachments + .filter((a) => /\.(jpe?g|png|webp|bmp)$/i.test(a.filename)) + .map((a) => a.filename); + const containerNo = header?.F_ContainerNo || mail.container_no || ""; + const bodyText = mail.body_text || ""; + const unitImported = isInstructionImported( + mail.parse_result?.lineage, + unit.id, + ); + const canSubmit = + !unitImported && + (mail.status === "PENDING_CONFIRM" || + mail.status === "FAILED" || + mail.status === "PARTIAL_SUCCESS"); + + const intent = + mail.type_evidence?.intent || + buildMailIntent({ + mailType: mail.mail_type as MailType, + subject: mail.subject, + body: bodyText, + filenames, + shipments, + evidence: mail.type_evidence as TypeEvidence, + containerNo, + }); + const transferPairs = intent.transfer_pairs?.length + ? intent.transfer_pairs + : extractTransferPairs(shipments); + const transferShipments = filterTransferShipments(shipments); + const transferSegmentCtx = transferSegmentContextsFromLineage( + mail.parse_result?.lineage, + { + transferUnits: [unit], + imageFilenames, + ocrText: mail.ocr_text, + containerNo, + }, + ); + const segCtx = transferSegmentCtx.get(unit.id); + const unitPairs = segCtx?.pairs?.length ? segCtx.pairs : transferPairs; + const unitShipments = filterTransferShipmentsByPairs( + transferShipments, + unitPairs, + ); + const [form, setForm] = useState(() => + buildCcTransferFormValue({ + customerName: record?.modules.customer_name, + containerNo, + modules: record?.modules, + shipments: unitShipments, + transferPairs: unitPairs, + }), + ); + const [submitting, setSubmitting] = useState(false); + + const roundBody = (unit.segmentText || "") + .replace(/ /gi, " ") + .replace(/[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/gi, "") + .replace(/\s+/g, " ") + .trim(); + + const submitAsWorkOrder = async (gateReason: string) => { + const woDraft = buildCcWorkOrderFormValue({ + subject: unit.segmentSubject || mail.subject, + body: unit.segmentText || bodyText, + filenames, + containerNo, + actions: ["转仓"], + customerName: record?.modules.customer_name, + isCurrent: true, + }); + const entity = toCcCustomerMessageEntity({ + ...woDraft, + F_MessageType: woDraft.F_MessageType || "拆柜时自动转仓", + F_Remark: [woDraft.F_Remark, `闸门:${gateReason}`] + .filter(Boolean) + .join(";"), + }); + if (!entity.F_MessageContent) { + message.error("请填写留言内容(邮件指令正文)"); + return; + } + const res = await withVersionConflictRetry({ + version: submitVersion, + onVersion: setSubmitVersion, + submit: (version) => + confirmWorkOrderApi(mail.id, { + version, + instruction_id: unit.id, + entity, + }), + }); + if (!res.ok) { + message.error(res.error.message); + return; + } + message.success( + res.data.mode === "mock" + ? "CC 不可批量转仓,已改为提交工单(mock)" + : "CC 不可批量转仓,已改为提交工单", + ); + afterConfirm(router, mail.id); + }; + + const onSubmit = async () => { + const target = form.targetFBACode || form.F_ConvertFBACode || ""; + if (!target) { + message.error("请填写目标 FBACode"); + return; + } + const rows = unitShipments.length ? unitShipments : shipments.slice(0, 1); + setSubmitting(true); + const gateRes = await getTransferGateApi(mail.id); + if (!gateRes.ok) { + setSubmitting(false); + message.error(gateRes.error.message); + return; + } + const gate = gateRes.data.gate; + if (!gate.canTransfer) { + message.warning(`${gate.reason},已改为提交工单`); + await submitAsWorkOrder(gate.reason); + setSubmitting(false); + return; + } + const res = await withVersionConflictRetry({ + version: submitVersion, + onVersion: setSubmitVersion, + submit: (version) => + confirmTransferApi(mail.id, { + version, + instruction_id: unit.id, + container_no: + form.F_ContainerNo || + header?.F_ContainerNo || + mail.container_no || + "", + payload: { + keyValue: + "containerTaskId" in gate ? gate.containerTaskId || "" : "", + strDetialList: rows.map((r) => ({ + F_FBACode: r.F_FBACode || "", + F_ConvertFBACode: target, + })), + F_ConvertChargeMode: form.F_ConvertChargeMode || "2", + F_RemovePlanDetail: form.F_RemovePlanDetail ? "1" : "0", + }, + }), + }); + setSubmitting(false); + if (!res.ok) { + if (res.error.code === "GATE_BLOCKED") { + message.warning(`${res.error.message},已改为提交工单`); + setSubmitting(true); + await submitAsWorkOrder(res.error.message); + setSubmitting(false); + return; + } + message.error(res.error.message); + return; + } + message.success( + res.data.mode === "mock" ? "批量转仓已提交(mock)" : "批量转仓已提交", + ); + afterConfirm(router, mail.id); + }; + + return ( + + {unitImported ? ( + + ) : null} + + + setForm((v) => ({ ...v, ...patch }))} + /> +
    + + + + + + + +
    +
    + ); +} + +export function DoUploadConfirmView({ + mail, + unit, +}: { + mail: MailMessage; + unit: MailInstructionUnit; +}) { + const router = useRouter(); + const { message } = App.useApp(); + const { mode: ccUiMode, setMode: setCcUiMode } = useCcUiMode(); + const { version: submitVersion, setVersion: setSubmitVersion } = + useSubmitVersion(mail.version); + const header = asHeader(mail); + const containerNo = header?.F_ContainerNo || mail.container_no || ""; + const filenames = mail.attachments.map((a) => a.filename); + const successImportExternalId = + mail.imports?.find((i) => i.status === "SUCCESS" && i.external_id) + ?.external_id || ""; + const unitImported = isInstructionImported( + mail.parse_result?.lineage, + unit.id, + ); + const canSubmit = + !unitImported && + (mail.status === "PENDING_CONFIRM" || + mail.status === "FAILED" || + mail.status === "PARTIAL_SUCCESS" || + mail.status === "SUCCESS"); + const [value, setValue] = useState(() => + buildCcDoUploadFormValue({ + subject: unit.segmentSubject || mail.subject, + body: unit.segmentText || mail.body_text || "", + containerNo, + filenames: + unit.source === "attachment" ? [unit.segmentText] : filenames, + containerTaskId: successImportExternalId, + }), + ); + const [submitting, setSubmitting] = useState(false); + + const onSubmit = async () => { + setSubmitting(true); + const res = await withVersionConflictRetry({ + version: submitVersion, + onVersion: setSubmitVersion, + submit: (version) => + confirmDoUploadApi(mail.id, { + version, + instruction_id: unit.id, + container_no: value.F_ContainerNo, + container_task_id: value.F_ContainerTaskId || undefined, + filenames: value.filenames, + }), + }); + setSubmitting(false); + if (!res.ok) { + message.error(res.error.message); + return; + } + message.success( + res.data.mode === "mock" ? "DO 已登记(mock)" : "DO 已上传", + ); + afterConfirm(router, mail.id); + }; + + return ( + + {unitImported ? ( + + ) : null} + setValue((v) => ({ ...v, ...p }))} + /> +
    + + + + + + + +
    +
    + ); +} + +export function WorkOrderConfirmView({ + mail, + unit, +}: { + mail: MailMessage; + unit: MailInstructionUnit; +}) { + const router = useRouter(); + const { message } = App.useApp(); + const { mode: ccUiMode, setMode: setCcUiMode } = useCcUiMode(); + const { version: submitVersion, setVersion: setSubmitVersion } = + useSubmitVersion(mail.version); + const header = asHeader(mail); + const record = asMailRecord(mail); + const shipments = mail.parse_result?.shipments ?? []; + const filenames = mail.attachments.map((a) => a.filename); + const containerNo = header?.F_ContainerNo || mail.container_no || ""; + const unitImported = isInstructionImported( + mail.parse_result?.lineage, + unit.id, + ); + const canSubmit = + !unitImported && + (mail.status === "PENDING_CONFIRM" || + mail.status === "FAILED" || + mail.status === "PARTIAL_SUCCESS"); + + const [draft, setDraft] = useState(() => + buildCcWorkOrderFormValue({ + subject: unit.segmentSubject || mail.subject, + body: unit.segmentText || mail.body_text || "", + filenames, + containerNo, + actions: unit.keywords, + customerName: record?.modules.customer_name, + isCurrent: true, + }), + ); + const holdBundle = useMemo( + () => + buildCcHoldFormValue({ + containerNo, + modules: record?.modules, + shipments, + }), + [containerNo, record?.modules, shipments], + ); + const [holdValue, setHoldValue] = useState(holdBundle.value); + const [splitValue, setSplitValue] = useState(() => + buildCcSplitFormValue({ + containerNo, + modules: record?.modules, + shipments, + }), + ); + const [outStockValue, setOutStockValue] = useState(() => + buildCcOutStockFormValue({ + containerNo, + modules: record?.modules, + shipments, + subject: unit.segmentSubject || mail.subject, + segmentText: unit.segmentText || "", + filenames, + }), + ); + const [submitting, setSubmitting] = useState(false); + + const panelKind = resolveWorkOrderPanelKind( + ccUiMode, + Array.from(new Set([...(unit.keywords || []), ...(draft.mailActions || [])])), + [unit.segmentSubject, unit.segmentText, unit.summary] + .filter(Boolean) + .join("\n"), + ); + const editMode = canSubmit && panelKind === "message" ? "edit" : "readonly"; + + const onSubmit = async () => { + const entity = toCcCustomerMessageEntity(draft); + if (!entity.F_MessageType) { + message.error("请选择留言类型(管理端必填,对应 CC 工单分类)"); + return; + } + if (!entity.F_MessageContent) { + message.error("请填写留言内容(邮件指令正文)"); + return; + } + setSubmitting(true); + const res = await withVersionConflictRetry({ + version: submitVersion, + onVersion: setSubmitVersion, + submit: (version) => + confirmWorkOrderApi(mail.id, { + version, + instruction_id: unit.id, + entity, + }), + }); + setSubmitting(false); + if (!res.ok) { + message.error(res.error.message); + return; + } + message.success( + res.data.mode === "mock" ? "工单已提交(mock)" : "工单已提交", + ); + afterConfirm(router, mail.id); + }; + + return ( + + {unitImported ? ( + + ) : null} + {panelKind === "hold" ? ( + setHoldValue((v) => ({ ...v, ...p }))} + /> + ) : panelKind === "split" ? ( + setSplitValue((v) => ({ ...v, ...p }))} + /> + ) : panelKind === "outstock" ? ( + + ) : ( + setDraft((v) => ({ ...v, ...p }))} + /> + )} +
    + + + + + + {panelKind === "message" ? ( + + ) : ( + + )} + +
    +
    + ); +} diff --git a/src/components/providers/AuthSessionProvider.tsx b/src/components/providers/AuthSessionProvider.tsx index 687fbf3..127414f 100644 --- a/src/components/providers/AuthSessionProvider.tsx +++ b/src/components/providers/AuthSessionProvider.tsx @@ -75,7 +75,10 @@ export function AuthSessionProvider({ setReady(true); router.prefetch("/mails"); router.prefetch("/logs"); - if (res.data.role === "admin") router.prefetch("/settings"); + if (res.data.role === "admin" || res.data.role === "customer") { + router.prefetch("/settings"); + } + if (res.data.role === "admin") router.prefetch("/customers"); }); return () => { cancelled = true; diff --git a/src/constants/error-copy.ts b/src/constants/error-copy.ts new file mode 100644 index 0000000..f4e5476 --- /dev/null +++ b/src/constants/error-copy.ts @@ -0,0 +1,362 @@ +/** + * 操作员可见报错。禁止把英文短句、机器码、CarrierCentral 原文直接展示。 + * 内部仍可存机器码;展示与 API message 必须走这里。 + */ + +const CJK = /[\u4e00-\u9fff]/; +const CJK_ALL = /[\u4e00-\u9fff]/g; + +const API_CODE_MESSAGE: Record = { + UNAUTHORIZED: "未登录或登录已过期,请重新登录", + FORBIDDEN: "没有权限执行此操作", + NOT_FOUND: "未找到对应记录", + VALIDATION: "填写内容不符合要求,请检查后重试", + RATE_LIMITED: "操作过于频繁,请稍后再试", + INTERNAL: "系统处理失败,请稍后重试", + DB_ERROR: "数据库暂时不可用,请稍后刷新", + VERSION_CONFLICT: "数据已被更新,请刷新页面后重试", + ALREADY_IMPORTING: "正在导入中,请稍候再试", + ILLEGAL_TRANSITION: "当前状态不允许此操作", + IMMUTABLE_STATUS: "当前状态已锁定,不能再改类型", + MAIL_BUSY: "邮件正在处理中,请稍后再试", + CONFLICT_CONTAINER: "该柜已在 CarrierCentral 中存在,请确认", + CONFLICT_CHECK_FAILED: "无法校验柜号是否已存在,请稍后重试", + CONFLICT: "与已有数据冲突,请检查后重试", + INVALID_ROWS: "货件行不符合导入要求,请检查勾选或重新解析", + ACK_UNMAPPED_REQUIRED: "存在未映射渠道,请确认后提交", + IMAP_NOT_CONFIGURED: "尚未配置邮箱,请先到「设置 → 邮箱绑定」", + IMAP_POLL_FAILED: "拉取邮件失败,请检查邮箱配置后重试", + IMAP_TEST_FAILED: "邮箱连通失败,请检查主机、端口和授权码", + CC_TEST_FAILED: "CarrierCentral 登录测试失败,请检查用户名和密码", + CC_FAILED: "写入 CarrierCentral 失败,请稍后重试", + CC_LOOKUP: "查询 CarrierCentral 柜号失败,请稍后重试", + OCR_EMPTY: "未能从图片或 PDF 识别到文字", + OAUTH_NOT_CONFIGURED: "尚未配置邮箱授权,请先填写 OAuth 参数", + INVALID_CREDENTIALS: "用户名或密码不正确", + HTTP_ERROR: "请求失败,请刷新后重试", + PARSE_ERROR: "服务器响应异常,请确认系统已启动", + EXHAUSTED: "自动重试次数已用完,请到日志页手动重试", + TIMEOUT_UNKNOWN: "提交超时,结果不确定,请到 CarrierCentral 核对后重试", + RETRY_FAILED: "重试失败,请查看错误说明后处理", + INVALID_STATE: "缺少可重试的请求数据,无法自动重试", + BAD_STATUS: "当前邮件状态不允许确认", + ALREADY_IMPORTED: "该指令已经导入,无需重复提交", + GATE_BLOCKED: "当前柜状态不允许批量转仓,请改走工单", + NO_CONTAINER: "CarrierCentral 中还没有该柜,请先确认导入预报", + NO_DO_FILE: "本地没有可上传的 DO 附件", + CC_WRITE_OFF: "当前未开启向 CarrierCentral 写入,请到设置检查 CC 模式", +}; + +const LAST_ERROR_EXACT: Record = { + NO_SHIPMENT_ROWS: + "未解析到货件行:新增预报需要卡派清单等表格附件,当前邮件没有可用货件表", + NO_SUPPORTED_SPREADSHEET: "未找到可解析的表格附件(xlsx/xls/csv)", + NO_BODY_NO_ATTACHMENT: "邮件没有正文也没有附件,无法识别指令", + ATTACHMENT_TOO_LARGE: "附件过大,已跳过解析。请让发件人压缩后重发,或拆分附件", + ZIP_EXTRACT_FAILED: "压缩包解压失败,请检查附件是否损坏", + ROW_LIMIT: "货件行数超过系统上限,请拆分表格后重发", + MIME_PARSE_ERROR: "邮件格式损坏,无法解析。请让发件人重新发送", + PARSING_STALE: "解析超时未完成,已自动重置。请点「重新解析」", + IMPORTING_TIMEOUT: + "导入超时,结果不确定。请到 CarrierCentral 核对该柜是否已创建,再到日志页处理", + IMAP_PROCESS_ERROR: "拉取到邮件后处理失败,请点「重新解析」或查看日志", + NON_BUSINESS_SKIP: "判定为非业务邮件,已跳过导入", + SKIP_BLACKLIST: "发件人在黑名单中,未拉取该邮件", + SKIP_FILTER: "未匹配拉取规则,已跳过该邮件", + AUTO_EXEC_DISABLED: "自动执行已关闭,需人工确认", + AUTO_EXEC_FAILED: "自动执行失败,请打开邮件详情查看并手动确认", + AUTO_EXEC_MOCK_OK: "自动执行已在模拟模式完成(未写入真实 CarrierCentral)", + DO_FAILED: "上传 DO 到 CarrierCentral 失败", + XFER_FAILED: "批量转仓失败", + WO_FAILED: "工单提交失败", + CC_WRITE_OFF: "当前未开启向 CarrierCentral 写入,请到设置检查 CC 模式", + CC_XFER_FAILED: "批量转仓时 CarrierCentral 返回失败", + CC_XFER_ERROR: "批量转仓时无法连接 CarrierCentral", + CC_WO_ERROR: "提交工单时无法连接 CarrierCentral", + CC_WRITE_ERROR: "写入 CarrierCentral 失败", + CC_DO_ERROR: "上传 DO 时无法连接 CarrierCentral", + stale_importing_recovered: "上次导入中断已自动恢复,可以重新确认导入", + unique_violation: "该邮件已经入库(重复邮件)", + no_credentials: "尚未配置邮箱账号或授权码", + no_mailboxes: "没有已启用的邮箱,请到设置中绑定", + lock_busy: "正在同步邮件,请稍后再试", + busy: "正在同步邮件,请稍后再试", + CHANNEL_UNMAPPED: "渠道未映射,将按原值提交", + INVALID: "该行未通过校验", + VALID: "有效", +}; + +/** 英文 / 机器短语 → 操作员说明(按长词优先) */ +const PHRASE_REPLACEMENTS: Array<[RegExp, string]> = [ + [/CC response missing external id/gi, "CarrierCentral 未返回柜任务编号,请到 CC 核对是否已创建"], + [/SaveContainer request failed/gi, "保存柜信息时无法连接 CarrierCentral"], + [/CC save failed with code\s*401/gi, "CarrierCentral 登录失败或账号无权限,请到「设置」检查用户名和密码"], + [/CC save failed with code\s*400/gi, "CarrierCentral 拒绝保存,请核对柜号、运输方式和货件必填项"], + [/\bBad Request\b/gi, "CarrierCentral 拒绝了请求,请核对填写内容"], + [/CC save failed with code\s*(\d+)/gi, "CarrierCentral 保存失败(状态 $1),请稍后重试或到 CC 核对"], + [/CC invalid JSON:[^\n]*/gi, "CarrierCentral 返回了无法解析的数据"], + [/CC upload invalid JSON:[^\n]*/gi, "CarrierCentral 上传接口返回异常"], + [/SaveFieldValue failed:\s*(\d+)/gi, "绑定 DO 文件夹失败(状态 $1)"], + [/SaveFieldValue failed/gi, "绑定 DO 文件夹失败"], + [/annexes upload failed:\s*(\d+)/gi, "上传 DO 附件失败(状态 $1)"], + [/annexes upload failed/gi, "上传 DO 附件失败"], + [/annexes upload error/gi, "上传 DO 附件时无法连接 CarrierCentral"], + [/annex list error/gi, "读取 DO 附件列表失败"], + [/annex list invalid JSON/gi, "CarrierCentral 附件列表返回异常"], + [/CC request timeout/gi, "连接 CarrierCentral 超时,请稍后重试"], + [/CC request failed/gi, "无法连接 CarrierCentral,请检查网络或 CC 地址"], + [/GetContainerList timeout/gi, "查询柜号超时,请稍后重试"], + [/GetContainerList failed:\s*(\d+)/gi, "查询柜号失败(状态 $1)"], + [/GetContainerList failed/gi, "查询柜号失败"], + [/AUTHENTICATIONFAILED[:\s]*Invalid credentials/gi, "邮箱登录失败:账号或授权码不正确"], + [/AUTHENTICATIONFAILED[^\n]*/gi, "邮箱登录失败,请检查账号或授权码"], + [/Invalid credentials/gi, "账号或密码不正确"], + [/Invalid username or password/gi, "用户名或密码不正确"], + [/username and password required/gi, "请填写用户名和密码"], + [/Too many login attempts[^\n]*/gi, "登录尝试过多,请稍后再试"], + [/Internal Server Error/gi, "CarrierCentral 服务器错误,请稍后重试"], + [/\bUnauthorized\b/gi, "CarrierCentral 登录失败或账号无权限,请到「设置」检查用户名和密码"], + [/Type override disabled/gi, "改类型功能未开启"], + [/Version mismatch/gi, "数据已被更新,请刷新页面后重试"], + [/Status\s+(\w+)\s+is immutable/gi, "当前状态已锁定,不能再改类型"], + [/Mail is\s+(\w+)/gi, "邮件正在处理中,请稍后再试"], + [/Cannot reparse from status\s+(\w+)/gi, "当前状态不能重新解析"], + [/Cannot ignore from status\s+(\w+)/gi, "当前状态不能忽略"], + [/Admin required for force_skip_conflict/gi, "强制跳过冲突需要管理员权限"], + [/Admin required/gi, "此操作需要管理员权限"], + [/Max 10 imports per minute/gi, "导入过于频繁,每分钟最多 10 次,请稍后再试"], + [/Import failed/gi, "导入失败,请查看邮件详情中的错误说明"], + [/IMAP poll failed/gi, "拉取邮件失败,请检查邮箱配置"], + [/OAuth not configured/gi, "尚未配置邮箱授权,请先填写 OAuth 参数"], + [/Unknown OAuth provider/gi, "不支持的邮箱服务商"], + [/Mailbox not found/gi, "未找到该邮箱配置"], + [/Mail not found/gi, "未找到该邮件"], + [/Attachment file missing/gi, "附件文件已丢失,无法下载"], + [/Attachment not found/gi, "未找到该附件"], + [/Invalid attachment path/gi, "附件路径无效,已拒绝下载"], + [/Invalid compensation id/gi, "补偿记录编号无效"], + [/Invalid import id/gi, "导入记录编号无效"], + [/Invalid filter id/gi, "过滤规则编号无效"], + [/Invalid mailbox_id/gi, "邮箱编号无效"], + [/Invalid mail_id/gi, "邮件编号无效"], + [/Invalid mail id/gi, "邮件编号无效"], + [/Invalid delete payload/gi, "删除请求无效"], + [/Invalid query parameters/gi, "查询条件无效"], + [/Invalid JSON body/gi, "请求内容不是有效 JSON"], + [/Invalid JSON/gi, "请求内容不是有效 JSON"], + [/Invalid body/gi, "提交内容不符合要求,请检查必填项后重试"], + [/Invalid query/gi, "查询条件无效"], + [/Invalid id/gi, "编号无效"], + [/\bInvalid\b/gi, "填写内容无效"], + [/fixture_id required/gi, "请选择要注入的测试邮件"], + [/Unknown fixture/gi, "未知的测试邮件"], + [/Not found/gi, "未找到对应记录"], + [/\bETIMEOUT\b/gi, "连接超时"], + [/\bETIMEDOUT\b/gi, "连接超时"], + [/\bECONNRESET\b/gi, "连接被中断"], + [/\bECONNREFUSED\b/gi, "连接被拒绝,请检查主机和端口"], + [/\bENOTFOUND\b/gi, "找不到服务器地址"], + [/mailbox_fail:/gi, "邮箱同步失败:"], + [/certificate/gi, "证书校验失败"], + [/\bcreate failed\b/gi, "创建失败"], + [/\bupdate failed\b/gi, "更新失败"], + [/\boauth_failed\b/gi, "邮箱授权失败"], + [/\bmissing_code\b/gi, "授权未完成(缺少授权码),请重新绑定"], + [/\bbad_state\b/gi, "授权校验失败,请重新发起绑定"], + [/\bunknown_provider\b/gi, "不支持的邮箱服务商"], + [/\bno_refresh_token\b/gi, "未获得长期授权。请在授权页勾选允许离线访问后重试"], + [/\baccess_denied\b/gi, "你取消了邮箱授权"], + [/\block_busy\b/gi, "正在同步邮件,请稍后再试"], + [/\bno_credentials\b/gi, "尚未配置邮箱账号或授权码"], + [/\bno_mailboxes\b/gi, "没有已启用的邮箱"], + [/\bbusy\b/gi, "正在同步邮件,请稍后再试"], +]; + +const OAUTH_CALLBACK: Record = { + missing_code: "授权未完成(缺少授权码),请重新绑定", + bad_state: "授权校验失败,请重新发起绑定", + unknown_provider: "不支持的邮箱服务商", + no_refresh_token: "未获得长期授权。请在授权页勾选允许离线访问后重试", + access_denied: "你取消了邮箱授权", +}; + +function hasCjk(s: string): boolean { + return CJK.test(s); +} + +function applyPhraseReplacements(s: string): string { + let out = s; + for (const [re, repl] of PHRASE_REPLACEMENTS) { + out = out.replace(re, repl); + } + return out.replace(/\s{2,}/g, " ").trim(); +} + +function looksLikeMachineCode(s: string): boolean { + return /^[A-Z][A-Z0-9_]{2,}$/.test(s); +} + +function leftoverLatin(s: string): boolean { + if (!s) return false; + const allowed = + /CarrierCentral|OAuth|JSON|PDF|\bDO\b|\bCC\b|ISO|IMAP|FBA|xlsx|xls|csv|GUID/gi; + const rest = s + .replace(CJK_ALL, "") + .replace(allowed, "") + .replace(/[0-9\s.,::()「」《》\-_/]/g, ""); + return /[A-Za-z]{3,}/.test(rest); +} + +/** + * 把任意错误字符串变成操作员能看懂的中文。 + * @param apiCode 接口 error.code,用于区分「未登录」和「CC 返回 Unauthorized」 + */ +export function formatOperatorError( + raw: string | null | undefined, + apiCode?: string, +): string { + const s = (raw || "").trim(); + if (!s && apiCode && API_CODE_MESSAGE[apiCode]) { + return API_CODE_MESSAGE[apiCode]; + } + if (!s) return ""; + + if (LAST_ERROR_EXACT[s]) return LAST_ERROR_EXACT[s]; + + if (s.startsWith("INVALID_CONTAINER_NO:")) { + const no = s.slice("INVALID_CONTAINER_NO:".length).trim(); + return no + ? `柜号「${no}」不符合 ISO 6346 校验位,请核对后改正` + : "柜号不符合 ISO 6346 校验位,请核对后改正"; + } + if (s.startsWith("missing_columns:")) { + const cols = s.slice("missing_columns:".length).trim(); + return cols + ? `表格缺列:${cols}。请按模板补全后重新发送或重新解析` + : "表格缺少必要列,请按模板补全后重新解析"; + } + if (s.startsWith("DO 失败:") || s.startsWith("DO 失败:")) { + const rest = s.replace(/^DO 失败[::]\s*/, ""); + return `上传 DO 失败:${formatCcCause(rest)}`; + } + + if (apiCode === "UNAUTHORIZED" && /^unauthorized$/i.test(s)) { + return API_CODE_MESSAGE.UNAUTHORIZED; + } + if (apiCode === "INVALID_CREDENTIALS") { + return API_CODE_MESSAGE.INVALID_CREDENTIALS; + } + + const rewritten = applyPhraseReplacements(s); + if (LAST_ERROR_EXACT[rewritten]) return LAST_ERROR_EXACT[rewritten]; + if (hasCjk(rewritten)) { + if (leftoverLatin(rewritten)) { + const again = applyPhraseReplacements(rewritten); + if (hasCjk(again) && !leftoverLatin(again)) return again; + } + if (!leftoverLatin(rewritten)) return rewritten; + } + + if (apiCode && API_CODE_MESSAGE[apiCode] && leftoverLatin(rewritten)) { + return API_CODE_MESSAGE[apiCode]; + } + + if (looksLikeMachineCode(rewritten) && LAST_ERROR_EXACT[rewritten]) { + return LAST_ERROR_EXACT[rewritten]; + } + + return "操作失败,请刷新后重试。若仍出现,请到日志页查看该邮件的导入记录"; +} + +/** 邮件 last_error / 导入错误列 */ +export function formatMailLastError(code: string | null | undefined): string { + return formatOperatorError(code); +} + +/** CarrierCentral 失败原因(不含「操作失败」万能句,便于拼到具体动作前) */ +export function formatCcCause(raw: string | null | undefined): string { + const s = (raw || "").trim(); + if (!s) return "CarrierCentral 未返回具体原因,请稍后重试或到 CC 核对"; + if (LAST_ERROR_EXACT[s]) return LAST_ERROR_EXACT[s]; + const rewritten = polishCcChinese(applyPhraseReplacements(s)); + if (LAST_ERROR_EXACT[rewritten]) return LAST_ERROR_EXACT[rewritten]; + // V1.72:失败看 code + info。info 常是中文并夹着 FBACode 等字段名,必须原文给操作员。 + if (hasCjk(rewritten)) return rewritten; + if (looksLikeMachineCode(s) && LAST_ERROR_EXACT[s]) return LAST_ERROR_EXACT[s]; + if (leftoverLatin(rewritten) || leftoverLatin(s)) { + return "CarrierCentral 处理失败,请稍后重试或到 CC 核对该柜状态"; + } + return "CarrierCentral 处理失败,请稍后重试或到 CC 核对该柜状态"; +} + +function polishCcChinese(s: string): string { + return s + .replace(/\[[^\]]{8,}\]/g, "英文字母、数字、空格、下划线和横杠") + .replace(/正常英文字符(?=英文字母)/g, "") + .replace(/\b(?:F_)?FBACode\b/gi, "目的代码") + .replace(/\b(?:F_)?ContainerNo\b/gi, "柜号") + .replace(/\b(?:F_)?Transporter\b/gi, "派送方式") + .replace(/\b(?:F_)?CabinetType\b/gi, "柜型") + .replace(/\b(?:F_)?BLCopyCode\b/gi, "提单号") + .replace(/\b(?:F_)?CTNS\b/gi, "件数") + .replace(/\s{2,}/g, " ") + .trim(); +} + +export function formatCcFailure(action: string, raw?: string | null): string { + return `向 CarrierCentral ${action}失败:${formatCcCause(raw)}`; +} + +export function formatOAuthCallbackError(token: string): string { + const key = token.trim(); + if (OAUTH_CALLBACK[key]) return `邮箱授权失败:${OAUTH_CALLBACK[key]}`; + const decoded = decodeURIComponent(key); + return `邮箱授权失败:${formatOperatorError(decoded)}`; +} + +export function formatImapSkipReason(reason: string | null | undefined): string { + const s = (reason || "").trim(); + if (!s) return "已跳过本次拉取"; + if (LAST_ERROR_EXACT[s]) return LAST_ERROR_EXACT[s]; + return formatOperatorError(s); +} + +/** 顶栏 IMAP 同步告警:勿落到「请到日志页查看导入记录」 */ +export function formatImapSyncError(raw: string | null | undefined): string { + const s = (raw || "").trim(); + if (!s) return "邮件服务器连接异常,请检查网络或邮箱设置后重试"; + if (/无法连接|鉴权失败|应用专用密码|邮箱「/.test(s)) { + const polished = applyPhraseReplacements(s); + return polished || s; + } + const rewritten = formatOperatorError(s); + if ( + !rewritten || + rewritten.includes("请到日志页查看该邮件的导入记录") + ) { + if (/timeout|ETIMEDOUT|ECONNRESET|ENOTFOUND|无法连接/i.test(s)) { + return "无法连接邮件服务器,请检查网络、主机和端口后重试"; + } + return "邮件同步失败,请到「设置 → 邮箱绑定」测试连通,或查看 worker 日志"; + } + return rewritten; +} + +export function formatShipmentIssue(raw: string | null | undefined): string { + const s = (raw || "").trim(); + if (!s) return ""; + if (LAST_ERROR_EXACT[s]) return LAST_ERROR_EXACT[s]; + return formatOperatorError(s); +} + +export function formatHttpFallback(status: number, isParseError: boolean): string { + if (status === 401) return API_CODE_MESSAGE.UNAUTHORIZED; + if (status === 403) return API_CODE_MESSAGE.FORBIDDEN; + if (status === 404) return API_CODE_MESSAGE.NOT_FOUND; + if (status === 429) return API_CODE_MESSAGE.RATE_LIMITED; + if (status >= 500) { + return "服务异常,请确认系统与数据库已启动后刷新"; + } + if (isParseError) return "服务器响应异常,请刷新后重试"; + return `请求失败,请刷新后重试`; +} diff --git a/src/constants/mail-storage.ts b/src/constants/mail-storage.ts new file mode 100644 index 0000000..fa4583e --- /dev/null +++ b/src/constants/mail-storage.ts @@ -0,0 +1,8 @@ +/** 达到该数量后列表弹窗提示用户自行删除较早邮件(不自动删)。 */ +export const MAIL_STORAGE_SOFT_LIMIT = 1000; + +/** 弹窗「删除最早」一次最多删多少封 */ +export const MAIL_PRUNE_OLDEST_BATCH = 100; + +/** 单次 API 删除上限 */ +export const MAIL_DELETE_MAX = 200; diff --git a/src/constants/mailbox-bind-guides.ts b/src/constants/mailbox-bind-guides.ts index 47650e9..6fba553 100644 --- a/src/constants/mailbox-bind-guides.ts +++ b/src/constants/mailbox-bind-guides.ts @@ -88,6 +88,7 @@ export const MAILBOX_BIND_GUIDES: Record = "打开 myaccount.google.com/apppasswords(或在安全性中搜索「应用专用密码」)。", "输入应用名称(如:邮件自动预报)→「创建」,复制 16 位密码(显示时空格可忽略,本系统会自动去掉)。", "回到本系统「授权码绑定」:邮箱填 @gmail.com,密码栏粘贴应用专用密码。", + "国内网络通常无法直连 imap.gmail.com。请让跑本系统的电脑走外网,或在 .env 设置 IMAP_PROXY(Clash SOCKS 如 socks5://127.0.0.1:7891)。QQ/163 不必走代理。", "或:管理员先在「配置 OAuth 应用」填好 Google Web 客户端,再点「OAuth · Gmail」一键授权(推荐)。", "「测试连接」通过后保存。", ], diff --git a/src/constants/ui-copy.ts b/src/constants/ui-copy.ts index 7691b4c..6452aba 100644 --- a/src/constants/ui-copy.ts +++ b/src/constants/ui-copy.ts @@ -1,14 +1,41 @@ +export const MAIL_TYPE_LABEL: Record = { + NEW_CONTAINER: "新增预报", + TRANSFER: "批量转仓", + DO_UPLOAD: "上传DO", + INSTRUCTION_HOLD_SPLIT: "留仓/拆分/出库", + INSTRUCTION_LABEL: "贴标指令", + WORK_ORDER: "工单", + UNKNOWN: "未知", +}; + +export const MAIL_STATUS_LABEL: Record = { + FETCHED: "已拉取", + PARSING: "解析中", + PARSED: "已解析(非NEW)", + PENDING_CONFIRM: "待确认", + IMPORTING: "导入中", + AUTO_EXECUTING: "自动执行中", + SUCCESS: "导入成功", + PARTIAL_SUCCESS: "部分成功", + FAILED: "导入失败", + PARSE_FAILED: "解析失败", + REJECTED_VALIDATION: "校验拒绝", + IGNORED: "已忽略", +}; + export const UI_COPY = { appTitle: "邮件自动预报", navMails: "邮件列表", navLogs: "日志", navSettings: "设置", + navCustomers: "客户账号", emptyMails: "暂无邮件,等待 IMAP 拉取", emptyImports: "暂无导入记录", btnConfirmImport: "确认导入", btnReparse: "重新解析", btnOverrideType: "改类型", btnIgnore: "忽略邮件", + btnDelete: "删除邮件", btnRetry: "重试", btnResetRetry: "重置并重试", btnRefresh: "刷新", @@ -20,11 +47,16 @@ export const UI_COPY = { containerConflictAlert: "正文与附件柜号不一致,须选定其一后方可提交", ignoreConfirm: "确认忽略该邮件?忽略后不再进入导入流程。", + deleteConfirm: "确认删除该邮件?删除后不可恢复。", modalImportTitle: "确认导入至 CarrierCentral", modalImportBody: (no: string, n: number) => - `将向 CC 导入柜 ${no},货件 ${n} 行`, - disableNotNew: (type: string) => `类型为 ${type},一期不可导入`, - disableBadStatus: (status: string) => `当前状态为 ${status},不可导入`, + n > 0 + ? `将向 CC 导入柜 ${no},货件 ${n} 行` + : `将向 CC 导入柜 ${no}(仅柜头,无货件行)`, + disableNotNew: (type: string) => + `当前邮件类型为「${MAIL_TYPE_LABEL[type] ?? type}」,不能走预报导入`, + disableBadStatus: (status: string) => + `当前状态为「${MAIL_STATUS_LABEL[status] ?? status}」,不能导入`, disableNoRows: "无有效货件行", alertImporting: "导入进行中,请稍后刷新查看结果", ackUnmapped: "已知晓存在未映射渠道,仍提交原值", @@ -34,50 +66,14 @@ export const UI_COPY = { mailNotFound: "邮件不存在", backToList: "返回邮件列表", backToDetail: "返回邮件详情", - confirmNotAllowed: "当前邮件不可进入确认导入页", + confirmNotAllowed: "当前指令不可进入确认页", noShipmentRows: - "未解析到货件行(NO_SHIPMENT_ROWS):新增预报需卡派清单等表格附件,当前邮件无可用货件表", + "未解析到货件行:新增预报需要卡派清单等表格附件,当前邮件没有可用货件表", noSupportedSpreadsheet: "未找到可解析的表格附件", } as const; /** 将机器码 last_error 转成操作员可读文案 */ -export function formatMailLastError(code: string | null | undefined): string { - if (!code) return ""; - if (code === "NO_SHIPMENT_ROWS") return UI_COPY.noShipmentRows; - if (code === "NO_SUPPORTED_SPREADSHEET") return UI_COPY.noSupportedSpreadsheet; - if (code.startsWith("INVALID_CONTAINER_NO:")) { - return `柜号不符合 ISO 6346:${code.slice("INVALID_CONTAINER_NO:".length)}`; - } - if (code.startsWith("missing_columns:")) { - return `表格缺列:${code.slice("missing_columns:".length)}`; - } - return code; -} - -export const MAIL_TYPE_LABEL: Record = { - NEW_CONTAINER: "新增预报", - TRANSFER: "批量转仓", - DO_UPLOAD: "上传DO", - INSTRUCTION_HOLD_SPLIT: "留仓/拆分/出库", - INSTRUCTION_LABEL: "贴标指令", - WORK_ORDER: "工单", - UNKNOWN: "未知", -}; - -export const MAIL_STATUS_LABEL: Record = { - FETCHED: "已拉取", - PARSING: "解析中", - PARSED: "已解析(非NEW)", - PENDING_CONFIRM: "待确认", - IMPORTING: "导入中", - AUTO_EXECUTING: "自动执行中", - SUCCESS: "导入成功", - PARTIAL_SUCCESS: "部分成功", - FAILED: "导入失败", - PARSE_FAILED: "解析失败", - REJECTED_VALIDATION: "校验拒绝", - IGNORED: "已忽略", -}; +export { formatMailLastError } from "@/constants/error-copy"; export const IMPORT_STATUS_LABEL: Record = { PENDING: "待处理", diff --git a/src/hooks/useMailDetail.ts b/src/hooks/useMailDetail.ts index ecf44a2..b08be4c 100644 --- a/src/hooks/useMailDetail.ts +++ b/src/hooks/useMailDetail.ts @@ -1,26 +1,41 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { getMailApi } from "@/lib/client-api"; import type { MailMessage } from "@/types"; +import type { ShipmentRow } from "@/types/mail"; +import { fillShipmentFbaCodes } from "@/services/parse/normalize-fba-code"; -export function useMailDetail(id: string) { +export function useMailDetail( + id: string, + opts?: { omitOcr?: boolean }, +) { const [mail, setMail] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const loadedId = useRef(null); const reload = useCallback(async () => { - setLoading(true); - const res = await getMailApi(id); + const silent = loadedId.current === id; + if (!silent) setLoading(true); + const res = await getMailApi(id, { omitOcr: opts?.omitOcr }); if (res.ok) { - setMail(res.data); + loadedId.current = id; + const mail = res.data; + if (mail.parse_result) { + mail.parse_result.shipments = fillShipmentFbaCodes( + mail.parse_result.shipments, + ) as ShipmentRow[]; + } + setMail(mail); setError(null); } else { + loadedId.current = null; setMail(null); setError(res.error.message); } setLoading(false); - }, [id]); + }, [id, opts?.omitOcr]); useEffect(() => { void reload(); diff --git a/src/hooks/useMails.ts b/src/hooks/useMails.ts index 21621d1..1007b94 100644 --- a/src/hooks/useMails.ts +++ b/src/hooks/useMails.ts @@ -8,12 +8,16 @@ import type { MailListItem } from "@/types"; export function useMails(filters: { mail_type?: string; status?: string; + content_kind?: string; q?: string; + sort?: "pulled_at" | "imported_at" | "received_at"; page?: number; pageSize?: number; }) { const [items, setItems] = useState([]); const [total, setTotal] = useState(0); + const [storageTotal, setStorageTotal] = useState(0); + const [storageLimit, setStorageLimit] = useState(1000); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const reloadSeq = useRef(0); @@ -25,7 +29,9 @@ export function useMails(filters: { const res = await listMailsApi({ mail_type: filters.mail_type, status: filters.status, + content_kind: filters.content_kind, q: filters.q, + sort: filters.sort, page: filters.page, pageSize: filters.pageSize, }); @@ -33,6 +39,8 @@ export function useMails(filters: { if (res.ok) { setItems(res.data.items); setTotal(res.data.total); + setStorageTotal(res.data.storage_total ?? 0); + setStorageLimit(res.data.storage_limit ?? 1000); setError(null); } else { setError(res.error.message); @@ -46,7 +54,9 @@ export function useMails(filters: { }, [ filters.mail_type, filters.status, + filters.content_kind, filters.q, + filters.sort, filters.page, filters.pageSize, ]); @@ -63,5 +73,13 @@ export function useMails(filters: { return () => window.removeEventListener(MAILS_RELOAD_EVENT, onReload); }, [reload]); - return { items, total, loading, error, reload }; + return { + items, + total, + storageTotal, + storageLimit, + loading, + error, + reload, + }; } diff --git a/src/lib/api.ts b/src/lib/api.ts index 8f2918b..31f0f4d 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,4 +1,5 @@ import { NextResponse } from "next/server"; +import { formatOperatorError } from "@/constants/error-copy"; import { getSession, type SessionData, @@ -37,7 +38,11 @@ export function fail( return NextResponse.json( jsonSafe({ ok: false, - error: { code, message, details }, + error: { + code, + message: formatOperatorError(message, code) || "操作失败,请稍后重试", + details, + }, } satisfies ApiFailure), { status }, ); @@ -73,6 +78,15 @@ export function isAdmin(session: SessionData): boolean { return session.role === "admin"; } +export async function requireAdminOrCustomer(): Promise { + const guard = await requireSession(); + if (guard.response) return guard; + if (guard.session.role !== "admin" && guard.session.role !== "customer") { + return { response: fail("FORBIDDEN", "Admin or customer required", 403) }; + } + return guard; +} + export function parseBigIntId(id: string): bigint | null { if (!/^\d+$/.test(id)) return null; try { diff --git a/src/lib/cc-owner.ts b/src/lib/cc-owner.ts new file mode 100644 index 0000000..f5b4559 --- /dev/null +++ b/src/lib/cc-owner.ts @@ -0,0 +1,26 @@ +import { AsyncLocalStorage } from "async_hooks"; +import { prisma } from "@/services/db"; + +const als = new AsyncLocalStorage<{ ownerUserId: bigint | null }>(); + +export function getCcOwnerUserId(): bigint | null | undefined { + return als.getStore()?.ownerUserId; +} + +export function runWithCcOwner( + ownerUserId: bigint | null, + fn: () => T, +): T { + return als.run({ ownerUserId }, fn); +} + +export async function runWithMailCcOwner( + mailId: bigint, + fn: () => Promise, +): Promise { + const mail = await prisma.mailMessage.findUnique({ + where: { id: mailId }, + select: { mailboxAccount: { select: { ownerUserId: true } } }, + }); + return runWithCcOwner(mail?.mailboxAccount?.ownerUserId ?? null, fn); +} diff --git a/src/lib/client-api.ts b/src/lib/client-api.ts index f7c6d3c..8414924 100644 --- a/src/lib/client-api.ts +++ b/src/lib/client-api.ts @@ -15,12 +15,31 @@ import type { ShippingLine, } from "@/types"; import type { MailType } from "@/types/mail"; +import { + formatHttpFallback, + formatOperatorError, +} from "@/constants/error-copy"; async function parseJson(res: Response): Promise> { const text = await res.text(); try { const body = text ? (JSON.parse(text) as ApiResponse) : null; if (body && typeof body === "object" && "ok" in body) { + if ( + body && + typeof body === "object" && + "ok" in body && + (body as ApiResponse).ok === false + ) { + const err = (body as Extract, { ok: false }>).error; + return { + ok: false, + error: { + ...err, + message: formatOperatorError(err.message, err.code), + }, + }; + } return body; } if (!res.ok) { @@ -28,7 +47,7 @@ async function parseJson(res: Response): Promise> { ok: false, error: { code: "HTTP_ERROR", - message: `请求失败 (${res.status})`, + message: formatHttpFallback(res.status, false), details: text.slice(0, 200), }, }; @@ -39,10 +58,7 @@ async function parseJson(res: Response): Promise> { ok: false, error: { code: "PARSE_ERROR", - message: - res.status >= 500 - ? `服务异常 (${res.status}),请确认数据库已启动` - : "响应解析失败", + message: formatHttpFallback(res.status, true), details: text.slice(0, 200), }, }; @@ -124,7 +140,9 @@ export async function meApi(): Promise> { export async function listMailsApi(params?: { mail_type?: string; status?: string; + content_kind?: string; q?: string; + sort?: "pulled_at" | "imported_at" | "received_at"; page?: number; pageSize?: number; }): Promise< @@ -133,12 +151,16 @@ export async function listMailsApi(params?: { total: number; page: number; pageSize: number; + storage_total: number; + storage_limit: number; }> > { const qs = new URLSearchParams(); if (params?.mail_type) qs.set("mail_type", params.mail_type); if (params?.status) qs.set("status", params.status); + if (params?.content_kind) qs.set("content_kind", params.content_kind); if (params?.q) qs.set("q", params.q); + if (params?.sort) qs.set("sort", params.sort); if (params?.page) qs.set("page", String(params.page)); if (params?.pageSize) qs.set("pageSize", String(params.pageSize)); const suffix = qs.toString() ? `?${qs.toString()}` : ""; @@ -147,13 +169,35 @@ export async function listMailsApi(params?: { total: number; page: number; pageSize: number; + storage_total: number; + storage_limit: number; }>(`/api/mails${suffix}`); } +export async function deleteMailsApi(input: { + ids?: string[]; + oldest?: number; +}): Promise< + ApiResponse<{ + requested: number; + deleted: number; + errors: number; + storage_total: number; + storage_limit: number; + }> +> { + return request(`/api/mails`, { + method: "DELETE", + body: JSON.stringify(input), + }); +} + export async function getMailApi( id: string, + opts?: { omitOcr?: boolean }, ): Promise> { - return request(`/api/mails/${id}`); + const qs = opts?.omitOcr ? "?omit_ocr=1" : ""; + return request(`/api/mails/${id}${qs}`); } export async function reparseMailApi( @@ -192,6 +236,7 @@ export async function importMailApi( mail_status: MailMessage["status"]; imports: ContainerImport[]; accepted_count: number; + version: number; }> > { return request(`/api/mails/${id}/import`, { @@ -204,6 +249,7 @@ export async function confirmWorkOrderApi( id: string, payload: { version: number; + instruction_id?: string; entity: { F_ReferCode?: string; F_MessageType: string; @@ -226,6 +272,7 @@ export async function confirmDoUploadApi( id: string, payload: { version: number; + instruction_id?: string; container_no: string; container_task_id?: string; filenames?: string[]; @@ -247,6 +294,7 @@ export async function confirmTransferApi( id: string, payload: { version: number; + instruction_id?: string; container_no: string; payload: { keyValue: string; @@ -277,6 +325,19 @@ export async function getTransferGateApi( return request(`/api/mails/${id}/transfer/gate`); } +export async function ocrMailAttachmentsApi( + id: string, +): Promise< + ApiResponse<{ + ocr_text: string; + provider: string; + confidence: number; + pages: number; + }> +> { + return request(`/api/mails/${id}/ocr-attachments`, { method: "POST" }); +} + export async function listImportsApi(params?: { status?: string; mail_id?: string; @@ -540,3 +601,202 @@ export async function updateOAuthSettingsApi(body: { body: JSON.stringify(body), }); } + +export type TestFixtureItem = { + id: string; + category: string; + from: string; + subject: string; + charset: string; + attachments: string[]; + expected: { + mail_type: string; + pipeline_mail_type: string | null; + status: string; + last_error: string | null; + pull: boolean; + }; +}; + +export type TestPipelineItem = { + mail_id: string; + message_id: string | null; + subject: string; + from_addr: string; + mail_type: string; + status: string; + last_error: string | null; + snapshot_path: string | null; + created_at: string; + updated_at: string; +}; + +export type TestRecordedRequest = { + method: "GET" | "POST"; + path: string; + body: unknown; + at: string; +}; + +export type TestRunCase = { + id: string; + group: "extract" | "pipeline" | "fault"; + ok: boolean; + detail: string; +}; + +export async function testFixturesApi() { + return request<{ items: TestFixtureItem[] }>("/api/test/fixtures"); +} + +export async function testInjectApi(body: { fixture_id: string; run_id?: string }) { + return request<{ + mail_id: string; + run_id: string; + status: string; + mail_type: string; + last_error: string | null; + subject: string; + version: number; + }>("/api/test/inject", { method: "POST", body: JSON.stringify(body) }); +} + +export async function testFaultsGetApi() { + return request<{ + imap: string; + target: { + defaultFault: string; + pathFaults: Array<[string, string]>; + }; + }>("/api/test/faults"); +} + +export async function testFaultsSetApi(body: { + imap?: string; + target?: string; + target_path?: string; +}) { + return request<{ imap: string; target: string; target_path: string | null }>( + "/api/test/faults", + { method: "POST", body: JSON.stringify(body) }, + ); +} + +export async function changePasswordApi(body: { + current_password: string; + new_password: string; +}): Promise> { + return request("/api/auth/password", { + method: "PUT", + body: JSON.stringify(body), + }); +} + +export type CustomerAccount = { + id: string; + username: string; + password: string; + display_name: string; + enabled: boolean; + mailbox_count?: number; + cc_username?: string; + cc_mock?: boolean; + created_at?: string; + updated_at?: string; +}; + +export async function listCustomersApi(): Promise< + ApiResponse<{ items: CustomerAccount[] }> +> { + return request("/api/admin/customers"); +} + +export async function createCustomerApi(body: { + username: string; + password: string; + display_name?: string; +}): Promise> { + return request("/api/admin/customers", { + method: "POST", + body: JSON.stringify(body), + }); +} + +export async function updateCustomerApi( + id: string, + body: { password?: string; display_name?: string; enabled?: boolean }, +): Promise> { + return request(`/api/admin/customers/${id}`, { + method: "PATCH", + body: JSON.stringify(body), + }); +} + +export type CustomerImportRecord = { + id: string; + mail_id: string; + mail_subject: string; + mail_status: string; + mail_type: string; + mail_last_error: string | null; + trace_id: string | null; + container_no: string; + external_id: string | null; + status: string; + last_error: string | null; + request_body: string | null; + response_body: string | null; + created_at: string; + updated_at: string; + compensations: Array<{ + id: string; + status: string; + retry_count: number; + max_retry: number; + reason: string; + created_at: string; + updated_at: string; + }>; +}; + +export async function listCustomerImportsApi( + customerId: string, + params?: { page?: number; pageSize?: number }, +): Promise< + ApiResponse<{ + customer: { id: string; username: string; display_name: string }; + items: CustomerImportRecord[]; + total: number; + page: number; + pageSize: number; + }> +> { + const qs = new URLSearchParams(); + if (params?.page) qs.set("page", String(params.page)); + if (params?.pageSize) qs.set("pageSize", String(params.pageSize)); + const suffix = qs.toString() ? `?${qs.toString()}` : ""; + return request(`/api/admin/customers/${customerId}/imports${suffix}`); +} + +export async function testTargetRequestsApi() { + return request<{ requests: TestRecordedRequest[] }>("/api/test/target-requests"); +} + +export async function testResetApi() { + return request<{ reset: true }>("/api/test/reset", { method: "POST" }); +} + +export async function testPipelineApi() { + return request<{ items: TestPipelineItem[] }>("/api/test/pipeline"); +} + +export async function testRunAllApi(body?: { pipeline?: boolean; run_id?: string }) { + return request<{ + passed: number; + failed: number; + cases: TestRunCase[]; + }>("/api/test/run-all", { + method: "POST", + body: JSON.stringify(body ?? { pipeline: true }), + }); +} diff --git a/src/lib/env.ts b/src/lib/env.ts index 8d1a95f..4ac724d 100644 --- a/src/lib/env.ts +++ b/src/lib/env.ts @@ -11,7 +11,7 @@ export const WEAK_APP_PASSWORDS = new Set(["admin123", "ops123", "password", "12 export const envSchema = z.object({ DATABASE_URL: z.string().min(1), POLL_INTERVAL_MS: z.coerce.number().default(60000), - DATA_RETENTION_DAYS: z.coerce.number().default(30), + DATA_RETENTION_DAYS: z.coerce.number().default(30), // 仅手动 --force 脚本;Worker 不再自动删邮件 ENABLE_TYPE_OVERRIDE: z.preprocess(bool, z.boolean()).default(true), ENABLE_FORCE_IMPORT: z.preprocess(bool, z.boolean()).default(false), ENABLE_ISO_CHECK: z.preprocess(bool, z.boolean()).default(true), @@ -36,12 +36,30 @@ export const envSchema = z.object({ .min(32) .default(DEV_DEFAULT_SESSION_SECRET), CC_MOCK: z.preprocess(bool, z.boolean()).default(false), + /** + * 测试基础设施开关(与 CC_MOCK 正交)。 + * TEST_MODE=1:IMAP / CC 走 Mock 实现,可注入故障;生产默认关闭。 + */ + TEST_MODE: z.preprocess(bool, z.boolean()).default(false), + /** 连续失败 / 鉴权过期告警(企微/飞书等兼容 JSON POST);空则只打日志 */ + ALERT_WEBHOOK_URL: z.string().optional().default(""), + ALERT_WEBHOOK_TIMEOUT_MS: z.coerce.number().default(5000), /** 写接口(SaveContainer / 转仓等)超时 */ CC_HTTP_TIMEOUT_MS: z.coerce.number().default(60000), /** 读接口(查柜 / 船司列表)超时;过长会把整页拖死 */ CC_READ_TIMEOUT_MS: z.coerce.number().default(8000), IMAP_CONNECT_TIMEOUT_MS: z.coerce.number().default(30000), IMAP_READ_TIMEOUT_MS: z.coerce.number().default(60000), + /** + * IMAP 代理(http:// 或 socks5://)。国内直连 Gmail 不通时配置本机 Clash 等端口。 + * 默认只对 Gmail/Outlook 主机生效,QQ/163 仍直连。 + */ + IMAP_PROXY: z.string().optional().default(""), + /** 走代理的主机关键字,逗号分隔;* 表示全部邮箱 */ + IMAP_PROXY_HOSTS: z + .string() + .optional() + .default("gmail,google,outlook,office365,hotmail,live,msn"), /** 同步近 N 天(已读+未读);默认 3 */ IMAP_LOOKBACK_DAYS: z.coerce.number().int().min(1).max(30).default(3), /** 每邮箱每轮最多新拉取封数,防止一次打爆 */ diff --git a/src/lib/imap-presets.ts b/src/lib/imap-presets.ts index 2df5e3d..3f5a082 100644 --- a/src/lib/imap-presets.ts +++ b/src/lib/imap-presets.ts @@ -14,7 +14,7 @@ const PRESETS: Array<{ match: RegExp; preset: ImapPreset }> = [ host: "imap.gmail.com", port: 993, label: "Gmail", - hint: "Gmail 须用应用专用密码", + hint: "Gmail 须用应用专用密码;国内网络常无法直连,需外网或停用该邮箱", }, }, { @@ -97,11 +97,33 @@ export function normalizeMailboxSecret(secret: string): string { return secret.replace(/\s+/g, ""); } +/** AggregateError / ETIMEDOUT 常为空 message,需挖 code 与子错误 */ +function imapErrDetail(err: unknown): string { + if (!(err instanceof Error)) return String(err || "").trim(); + const parts: string[] = []; + if (err.message.trim()) parts.push(err.message.trim()); + const code = (err as NodeJS.ErrnoException).code; + if (code) parts.push(String(code)); + const nested = (err as { errors?: unknown[] }).errors; + if (Array.isArray(nested)) { + for (const child of nested) { + const d = imapErrDetail(child); + if (d) parts.push(d); + } + } + const cause = (err as Error & { cause?: unknown }).cause; + if (cause) { + const d = imapErrDetail(cause); + if (d) parts.push(d); + } + return [...new Set(parts)].join(" | "); +} + export function formatImapConnectError( err: unknown, opts: { host: string; user: string }, ): string { - const raw = err instanceof Error ? err.message : String(err); + const raw = imapErrDetail(err) || "连接失败"; const host = opts.host.toLowerCase(); const domain = (opts.user.split("@")[1] || "").toLowerCase(); const looksGmail = /gmail|googlemail/.test(domain); @@ -116,8 +138,19 @@ export function formatImapConnectError( } return `鉴权失败:请核对授权码/应用专用密码(${raw})`; } - if (/timeout|ECONNREFUSED|ENOTFOUND|certificate/i.test(raw)) { + if ( + /timeout|ETIMEDOUT|ECONNREFUSED|ECONNRESET|ENOTFOUND|certificate/i.test( + raw, + ) + ) { + if (looksGmail || hostIsGmail) { + return `无法连接 Gmail(${opts.host}):国内网络通常无法直连。请在运行 worker 的机器上开外网,或在 .env 配置 IMAP_PROXY(如 socks5://127.0.0.1:7891),或到设置里停用该邮箱。详情:${raw}`; + } return `无法连接 ${opts.host}:检查主机/端口/网络(${raw})`; } - return raw; + return raw === "连接失败" + ? looksGmail || hostIsGmail + ? `无法连接 Gmail:国内网络通常无法直连,请用外网或停用该邮箱` + : `无法连接 ${opts.host}:检查主机/端口/网络` + : raw; } diff --git a/src/lib/imap-proxy.ts b/src/lib/imap-proxy.ts new file mode 100644 index 0000000..0cc5d83 --- /dev/null +++ b/src/lib/imap-proxy.ts @@ -0,0 +1,52 @@ +/** IMAP 代理:国内直连 Gmail 不通时,经 SOCKS/HTTP 走外网。QQ/163 默认仍直连。 */ + +const PROXY_SCHEMES = /^(socks5?|socks4a?|https?):$/i; +const DEFAULT_PROXY_HOSTS = + "gmail,google,outlook,office365,hotmail,live,msn"; + +export function redactProxyUrl(raw: string): string { + try { + const u = new URL(raw); + if (u.password) u.password = "****"; + if (u.username) u.username = u.username ? "****" : ""; + return u.toString(); + } catch { + return "(invalid-proxy-url)"; + } +} + +export function normalizeImapProxyUrl(raw: string): string | undefined { + const s = raw.trim(); + if (!s) return undefined; + try { + const u = new URL(s); + if (!PROXY_SCHEMES.test(u.protocol)) return undefined; + if (!u.hostname) return undefined; + return s; + } catch { + return undefined; + } +} + +/** + * @param hostFilter 逗号分隔关键字;`*` / `all` 表示所有 IMAP 主机都走代理 + */ +export function imapProxyForHost( + imapHost: string, + proxyUrl: string, + hostFilter = DEFAULT_PROXY_HOSTS, +): string | undefined { + const proxy = normalizeImapProxyUrl(proxyUrl); + if (!proxy) return undefined; + const host = (imapHost || "").toLowerCase(); + const filter = hostFilter.trim().toLowerCase(); + if (!filter || filter === "*" || filter === "all") return proxy; + const keys = filter + .split(/[,;\s]+/) + .map((k) => k.trim()) + .filter(Boolean); + if (keys.some((k) => host.includes(k))) return proxy; + return undefined; +} + +export { DEFAULT_PROXY_HOSTS }; diff --git a/src/lib/log-context.ts b/src/lib/log-context.ts new file mode 100644 index 0000000..27b434a --- /dev/null +++ b/src/lib/log-context.ts @@ -0,0 +1,65 @@ +export type LogStage = "pull" | "parse" | "stage" | "submit"; + +export type LogBindings = { + traceId?: string; + mailId?: string; + stage?: LogStage; +}; + +type AlsLike = { + getStore(): LogBindings | undefined; + run(store: LogBindings, fn: () => T): T; +}; + +function fallbackStorage(): AlsLike { + let store: LogBindings | undefined; + return { + getStore: () => store, + run(next: LogBindings, fn: () => T) { + const prev = store; + store = next; + try { + return fn(); + } finally { + store = prev; + } + }, + }; +} + +function createStorage(): AlsLike { + try { + // 动态加载,避免客户端 webpack 解析 Node 内置模块 + const { AsyncLocalStorage } = + // eslint-disable-next-line @typescript-eslint/no-require-imports + require("async_hooks") as typeof import("async_hooks"); + if (typeof AsyncLocalStorage !== "function") return fallbackStorage(); + return new AsyncLocalStorage(); + } catch { + return fallbackStorage(); + } +} + +const storage = createStorage(); + +export function newTraceId(): string { + const c = globalThis.crypto as { randomUUID?: () => string } | undefined; + if (c?.randomUUID) return c.randomUUID(); + return `t-${Date.now().toString(36)}-${Math.random().toString(16).slice(2, 10)}`; +} + +export function currentLogBindings(): LogBindings { + return storage.getStore() ?? {}; +} + +export function getTraceId(): string | undefined { + return storage.getStore()?.traceId; +} + +export function withLogContext( + bindings: LogBindings, + fn: () => T, +): T { + const parent = storage.getStore() ?? {}; + return storage.run({ ...parent, ...bindings }, fn); +} diff --git a/src/lib/logger.ts b/src/lib/logger.ts index 946a659..40cc6e1 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -1,6 +1,10 @@ import pino from "pino"; +import { currentLogBindings } from "@/lib/log-context"; export const logger = pino({ level: process.env.LOG_LEVEL || "info", base: { service: "email-forecast" }, + mixin() { + return currentLogBindings(); + }, }); diff --git a/src/lib/mail-trace.ts b/src/lib/mail-trace.ts new file mode 100644 index 0000000..a8dd5e6 --- /dev/null +++ b/src/lib/mail-trace.ts @@ -0,0 +1,34 @@ +import { prisma } from "@/services/db"; +import { + newTraceId, + withLogContext, + type LogStage, +} from "@/lib/log-context"; + +export async function ensureMailTraceId( + mailId: bigint, + existing?: string | null, +): Promise { + if (existing) return existing; + const row = await prisma.mailMessage.findUnique({ + where: { id: mailId }, + select: { traceId: true }, + }); + if (row?.traceId) return row.traceId; + const traceId = newTraceId(); + await prisma.mailMessage.update({ + where: { id: mailId }, + data: { traceId }, + }); + return traceId; +} + +export async function runWithMailTrace( + mailId: bigint, + stage: LogStage, + fn: () => Promise, + existing?: string | null, +): Promise { + const traceId = await ensureMailTraceId(mailId, existing); + return withLogContext({ traceId, mailId: String(mailId), stage }, fn); +} diff --git a/src/lib/resilience-metrics.ts b/src/lib/resilience-metrics.ts index df61f1f..764f355 100644 --- a/src/lib/resilience-metrics.ts +++ b/src/lib/resilience-metrics.ts @@ -9,6 +9,8 @@ export type ResilienceLevel = "info" | "warn" | "error"; /** 文档 §11 及文中命名事件 */ export type ResilienceMetric = | "imap.poll_fail" + | "imap.poll_partial_fail" + | "imap.poll_mailbox_isolated_fail" | "imap.seen_fail" | "imap.lock_stuck" | "cc.login_fail" @@ -40,7 +42,8 @@ export type ResilienceMetric = | "import.count_mismatch" | "security.rate_limit_import" | "security.type_override_denied" - | "type.close_call"; + | "type.close_call" + | "alert.webhook_fail"; export function emitResilience( metric: ResilienceMetric, diff --git a/src/lib/session.ts b/src/lib/session.ts index 70c3b72..37f7fd2 100644 --- a/src/lib/session.ts +++ b/src/lib/session.ts @@ -1,12 +1,15 @@ import { getIronSession, type SessionOptions } from "iron-session"; import { cookies } from "next/headers"; import { getEnv } from "@/lib/env"; +import { prisma } from "@/services/db"; +import { passwordMatches } from "@/services/auth/passwords"; -export type UserRole = "admin" | "ops"; +export type UserRole = "admin" | "ops" | "customer"; export interface SessionData { username?: string; role?: UserRole; + userId?: string; } export const SESSION_COOKIE = "email_forecast_session"; @@ -17,7 +20,8 @@ export function getSessionOptions(): SessionOptions { password: env.SESSION_SECRET, cookieName: SESSION_COOKIE, cookieOptions: { - secure: process.env.NODE_ENV === "production", + // 内网 http://IP:端口 部署时必须为 false,否则浏览器不存 Cookie,登录后无法跳转 + secure: process.env.COOKIE_SECURE === "true", httpOnly: true, sameSite: "lax", path: "/", @@ -29,16 +33,27 @@ export async function getSession() { return getIronSession(await cookies(), getSessionOptions()); } -export function verifyCredentials( +export async function verifyCredentials( username: string, password: string, -): { username: string; role: UserRole } | null { +): Promise<{ username: string; role: UserRole; userId?: string } | null> { const env = getEnv(); - if (username === env.APP_ADMIN_USER && password === env.APP_ADMIN_PASS) { - return { username, role: "admin" }; + const name = username.trim(); + if (name === env.APP_ADMIN_USER && password === env.APP_ADMIN_PASS) { + return { username: name, role: "admin" }; } - if (username === env.APP_OPS_USER && password === env.APP_OPS_PASS) { - return { username, role: "ops" }; + if (name === env.APP_OPS_USER && password === env.APP_OPS_PASS) { + return { username: name, role: "ops" }; } - return null; + + const user = await prisma.appUser.findUnique({ where: { username: name } }); + if (!user || !user.enabled || user.role !== "customer") return null; + if (!passwordMatches(password, user.passwordHash, user.passwordEnc)) { + return null; + } + return { + username: user.username, + role: "customer", + userId: user.id.toString(), + }; } diff --git a/src/lib/test-mode-guard.ts b/src/lib/test-mode-guard.ts new file mode 100644 index 0000000..6bd9d4e --- /dev/null +++ b/src/lib/test-mode-guard.ts @@ -0,0 +1,10 @@ +import { fail } from "@/lib/api"; +import { isTestMode } from "@/lib/test-mode"; +import type { NextResponse } from "next/server"; + +export function requireTestMode(): NextResponse | null { + if (!isTestMode()) { + return fail("NOT_FOUND", "Not found", 404); + } + return null; +} diff --git a/src/lib/test-mode.ts b/src/lib/test-mode.ts new file mode 100644 index 0000000..cbd7700 --- /dev/null +++ b/src/lib/test-mode.ts @@ -0,0 +1,6 @@ +import { getEnv } from "@/lib/env"; + +/** TEST_MODE=1 时走 Mock IMAP / MockTargetAPI;与 CC_MOCK 正交。 */ +export function isTestMode(): boolean { + return getEnv().TEST_MODE; +} diff --git a/src/services/alert/webhook.ts b/src/services/alert/webhook.ts new file mode 100644 index 0000000..2fe2862 --- /dev/null +++ b/src/services/alert/webhook.ts @@ -0,0 +1,107 @@ +import { getEnv } from "@/lib/env"; +import { logger } from "@/lib/logger"; +import { emitResilience } from "@/lib/resilience-metrics"; + +export type AlertSeverity = "warning" | "error"; + +export type AlertEvent = { + /** 去重键:同类告警 15 分钟内只发一次 */ + key: string; + title: string; + severity: AlertSeverity; + fields?: Record; +}; + +const DEDUP_MS = 15 * 60_000; +const MAX_BUFFER = 50; + +const recent: AlertEvent[] = []; +const lastSent = new Map(); + +export function getEmittedAlerts(): AlertEvent[] { + return [...recent]; +} + +export function resetAlertState(): void { + recent.length = 0; + lastSent.clear(); +} + +function remember(event: AlertEvent): void { + recent.push({ ...event, fields: { ...event.fields } }); + if (recent.length > MAX_BUFFER) recent.splice(0, recent.length - MAX_BUFFER); +} + +export async function emitAlert(event: AlertEvent): Promise { + const now = Date.now(); + const prev = lastSent.get(event.key) ?? 0; + if (now - prev < DEDUP_MS) { + logger.info({ alertKey: event.key }, "alert.deduped"); + return false; + } + + remember(event); + lastSent.set(event.key, now); + + logger.warn( + { + metric: "alert.emit", + alertKey: event.key, + title: event.title, + severity: event.severity, + ...(event.fields || {}), + }, + "alert.emit", + ); + + let url = ""; + try { + url = getEnv().ALERT_WEBHOOK_URL?.trim() || ""; + } catch { + url = ""; + } + if (!url) return true; + + const timeoutMs = (() => { + try { + return getEnv().ALERT_WEBHOOK_TIMEOUT_MS; + } catch { + return 5000; + } + })(); + + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + try { + const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + msgtype: "text", + text: { + content: `[email-forecast] ${event.title}\n${JSON.stringify(event.fields || {}, null, 2)}`, + }, + title: event.title, + severity: event.severity, + key: event.key, + fields: event.fields || {}, + at: new Date().toISOString(), + }), + signal: controller.signal, + }); + if (!res.ok) { + emitResilience("alert.webhook_fail", "warn", { + status: res.status, + key: event.key, + }); + } + } catch (err) { + emitResilience("alert.webhook_fail", "warn", { + key: event.key, + err: err instanceof Error ? err.message : String(err), + }); + } finally { + clearTimeout(timer); + } + return true; +} diff --git a/src/services/auth/customers.ts b/src/services/auth/customers.ts new file mode 100644 index 0000000..4abb9e6 --- /dev/null +++ b/src/services/auth/customers.ts @@ -0,0 +1,168 @@ +import { prisma } from "@/services/db"; +import { + encodeLoginPassword, + passwordMatches, + revealPassword, +} from "@/services/auth/passwords"; + +const USERNAME_RE = /^[a-zA-Z0-9._-]{3,64}$/; + +export class CustomerAccountError extends Error { + constructor( + public readonly code: string, + message: string, + public readonly status = 400, + ) { + super(message); + this.name = "CustomerAccountError"; + } +} + +function reservedUsernames(): Set { + return new Set( + [ + process.env.APP_ADMIN_USER || "admin", + process.env.APP_OPS_USER || "ops", + ].map((s) => s.toLowerCase()), + ); +} + +export function assertCustomerUsername(username: string): string { + const name = username.trim(); + if (!USERNAME_RE.test(name)) { + throw new CustomerAccountError( + "VALIDATION", + "用户名须为 3–64 位字母、数字、点、下划线或短横线", + ); + } + if (reservedUsernames().has(name.toLowerCase())) { + throw new CustomerAccountError("CONFLICT", "该用户名已被系统保留", 409); + } + return name; +} + +export function assertPassword(password: string): string { + const p = password.trim(); + if (p.length < 6 || p.length > 128) { + throw new CustomerAccountError("VALIDATION", "密码长度须为 6–128 位"); + } + return p; +} + +export async function listCustomers() { + const rows = await prisma.appUser.findMany({ + where: { role: "customer" }, + orderBy: { id: "asc" }, + include: { + _count: { select: { mailboxes: true } }, + ccSettings: { select: { username: true, mock: true, updatedAt: true } }, + }, + }); + return rows.map((r) => ({ + id: r.id.toString(), + username: r.username, + password: revealPassword(r.passwordEnc), + display_name: r.displayName, + enabled: r.enabled, + mailbox_count: r._count.mailboxes, + cc_username: r.ccSettings?.username || "", + cc_mock: r.ccSettings?.mock ?? true, + created_at: r.createdAt.toISOString(), + updated_at: r.updatedAt.toISOString(), + })); +} + +export async function createCustomer(input: { + username: string; + password: string; + displayName?: string; +}) { + const username = assertCustomerUsername(input.username); + const password = assertPassword(input.password); + const existing = await prisma.appUser.findUnique({ where: { username } }); + if (existing) { + throw new CustomerAccountError("CONFLICT", "用户名已存在", 409); + } + const cred = encodeLoginPassword(password); + const row = await prisma.appUser.create({ + data: { + username, + passwordHash: cred.passwordHash, + passwordEnc: cred.passwordEnc, + role: "customer", + displayName: (input.displayName || "").trim(), + enabled: true, + }, + }); + return { + id: row.id.toString(), + username: row.username, + password, + display_name: row.displayName, + enabled: row.enabled, + }; +} + +export async function updateCustomer( + id: bigint, + input: { + password?: string; + displayName?: string; + enabled?: boolean; + }, +) { + const row = await prisma.appUser.findFirst({ + where: { id, role: "customer" }, + }); + if (!row) { + throw new CustomerAccountError("NOT_FOUND", "客户不存在", 404); + } + const data: { + passwordHash?: string; + passwordEnc?: string; + displayName?: string; + enabled?: boolean; + } = {}; + let revealed = revealPassword(row.passwordEnc); + if (input.password != null && input.password.length > 0) { + const password = assertPassword(input.password); + const cred = encodeLoginPassword(password); + data.passwordHash = cred.passwordHash; + data.passwordEnc = cred.passwordEnc; + revealed = password; + } + if (input.displayName != null) data.displayName = input.displayName.trim(); + if (input.enabled != null) data.enabled = input.enabled; + const updated = await prisma.appUser.update({ where: { id }, data }); + return { + id: updated.id.toString(), + username: updated.username, + password: revealed, + display_name: updated.displayName, + enabled: updated.enabled, + }; +} + +export async function changeOwnPassword(input: { + userId: bigint; + currentPassword: string; + newPassword: string; +}) { + const row = await prisma.appUser.findFirst({ + where: { id: input.userId, role: "customer" }, + }); + if (!row) { + throw new CustomerAccountError("NOT_FOUND", "账号不存在", 404); + } + if ( + !passwordMatches(input.currentPassword, row.passwordHash, row.passwordEnc) + ) { + throw new CustomerAccountError("INVALID_CREDENTIALS", "当前密码不正确", 401); + } + const password = assertPassword(input.newPassword); + const cred = encodeLoginPassword(password); + await prisma.appUser.update({ + where: { id: row.id }, + data: { passwordHash: cred.passwordHash, passwordEnc: cred.passwordEnc }, + }); +} diff --git a/src/services/auth/passwords.ts b/src/services/auth/passwords.ts new file mode 100644 index 0000000..0a576bc --- /dev/null +++ b/src/services/auth/passwords.ts @@ -0,0 +1,39 @@ +import { createHash } from "crypto"; +import { decryptSecret, encryptSecret } from "@/lib/secret-crypto"; + +export function hashPassword(plain: string): string { + return createHash("sha256").update(plain).digest("hex"); +} + +export function encodeLoginPassword(plain: string): { + passwordHash: string; + passwordEnc: string; +} { + return { + passwordHash: hashPassword(plain), + passwordEnc: encryptSecret(plain), + }; +} + +export function revealPassword(passwordEnc: string): string { + if (!passwordEnc) return ""; + try { + return decryptSecret(passwordEnc); + } catch { + return ""; + } +} + +export function passwordMatches( + plain: string, + passwordHash: string, + passwordEnc: string, +): boolean { + if (passwordHash && hashPassword(plain) === passwordHash) return true; + if (!passwordEnc) return false; + try { + return decryptSecret(passwordEnc) === plain; + } catch { + return false; + } +} diff --git a/src/services/cc/auth.ts b/src/services/cc/auth.ts index 2bbcf0b..da59e45 100644 --- a/src/services/cc/auth.ts +++ b/src/services/cc/auth.ts @@ -1,5 +1,7 @@ import { getEnv } from "@/lib/env"; +import { isTestMode } from "@/lib/test-mode"; import { logger } from "@/lib/logger"; +import { emitAlert } from "@/services/alert/webhook"; import { emitResilience } from "@/lib/resilience-metrics"; import { prisma } from "@/services/db"; import { withMysqlNamedLock } from "@/services/db-lock"; @@ -60,6 +62,10 @@ export function shouldReuseCcMemoryToken( export class CcAuth { async getToken(): Promise<{ token: string; loginMark: string }> { + if (isTestMode()) { + return this.getMockToken(getEnv().CC_LOGIN_MARK); + } + const rt = await resolveCcRuntime(); if (rt.mock) { return this.getMockToken(rt.loginMark); @@ -95,6 +101,7 @@ export class CcAuth { async invalidateToken(): Promise { memoryToken = null; mockToken = null; + if (isTestMode()) return; const rt = await resolveCcRuntime(); if (rt.mock) return; await prisma.ccTokenCache.deleteMany({ @@ -198,6 +205,12 @@ export class CcAuth { return this.customerLogin(retries + 1); } emitResilience("cc.login_fail", "error", { code, retries }); + void emitAlert({ + key: "cc.login_fail", + title: "CC 登录失败", + severity: "error", + fields: { code, retries }, + }); throw new Error(`CC_LOGIN_FAILED:${code}`); } @@ -206,6 +219,12 @@ export class CcAuth { code, info: body.info, }); + void emitAlert({ + key: "cc.auth_expired", + title: "CC 鉴权失效", + severity: "error", + fields: { code, info: body.info }, + }); throw new Error(`CC_LOGIN_FAILED:${body.info || code}`); } diff --git a/src/services/cc/batch-transfer.ts b/src/services/cc/batch-transfer.ts index 53ff475..06bd869 100644 --- a/src/services/cc/batch-transfer.ts +++ b/src/services/cc/batch-transfer.ts @@ -6,6 +6,7 @@ import { ccAuth } from "@/services/cc/auth"; import { CcHttpClient } from "@/services/cc/http"; import { normalizeCcCode } from "@/services/cc/code"; import { getCcWriteCapability, resolveWriteMode } from "@/services/cc/write-capability"; +import { formatCcCause } from "@/constants/error-copy"; const ccHttp = new CcHttpClient(ccAuth); @@ -37,7 +38,13 @@ export async function saveStockBatchTransfer( const request = { ...payload, mail_id: opts?.mailId }; if (mode === "off") { - return { ok: false, mode, request, response: null, error: "CC_WRITE_OFF" }; + return { + ok: false, + mode, + request, + response: null, + error: formatCcCause("CC_WRITE_OFF"), + }; } if (mode === "mock") { @@ -66,7 +73,13 @@ export async function saveStockBatchTransfer( response: res, error: ok ? undefined - : String((res as { message?: string }).message || "CC_XFER_FAILED"), + : formatCcCause( + String( + (res as { info?: string; message?: string }).info || + (res as { message?: string }).message || + "CC_XFER_FAILED", + ), + ), }; } catch (err) { return { @@ -74,7 +87,9 @@ export async function saveStockBatchTransfer( mode: "live", request, response: null, - error: err instanceof Error ? err.message : "CC_XFER_ERROR", + error: formatCcCause( + err instanceof Error ? err.message : "CC_XFER_ERROR", + ), }; } } diff --git a/src/services/cc/conflict.ts b/src/services/cc/conflict.ts index d1c022a..a8cf640 100644 --- a/src/services/cc/conflict.ts +++ b/src/services/cc/conflict.ts @@ -10,6 +10,7 @@ import { } from "@/services/cc/mock-state"; import { isCcMock } from "@/services/cc/settings-config"; import { toShanghaiDateString } from "@/utils/dates"; +import { formatCcCause } from "@/constants/error-copy"; const LIST_PATH = "/Container/GetContainerList"; const ccHttp = new CcHttpClient(ccAuth); @@ -128,7 +129,7 @@ export async function getContainerListFromCc( if (isCcRateLimitedMessage(error) && cached?.result.ok) { return cached.result; } - return { ok: false, error }; + return { ok: false, error: formatCcCause(error) }; } const items = parseContainerListRows(response.data); const okResult = { ok: true as const, items }; @@ -145,7 +146,7 @@ export async function getContainerListFromCc( if (isCcRateLimitedMessage(msg) && cached?.result.ok) { return cached.result; } - return { ok: false, error: msg }; + return { ok: false, error: formatCcCause(msg) }; } }; diff --git a/src/services/cc/customer-message.ts b/src/services/cc/customer-message.ts index c488b18..b8286ff 100644 --- a/src/services/cc/customer-message.ts +++ b/src/services/cc/customer-message.ts @@ -7,6 +7,7 @@ import { ccAuth } from "@/services/cc/auth"; import { CcHttpClient } from "@/services/cc/http"; import { normalizeCcCode } from "@/services/cc/code"; import { resolveWriteMode } from "@/services/cc/write-capability"; +import { formatCcCause } from "@/constants/error-copy"; import type { CustomerMessageEntity } from "@/services/cc/customer-message-entity"; export type { CustomerMessageEntity } from "@/services/cc/customer-message-entity"; @@ -41,7 +42,7 @@ export async function saveCustomerMessage( mode, request, response: null, - error: "CC_WRITE_OFF", + error: formatCcCause("CC_WRITE_OFF"), }; } @@ -72,7 +73,13 @@ export async function saveCustomerMessage( response: res, error: ok ? undefined - : String((res as { message?: string }).message || "CC_WO_FAILED"), + : formatCcCause( + String( + (res as { info?: string; message?: string }).info || + (res as { message?: string }).message || + "CC_WO_FAILED", + ), + ), }; } catch (err) { return { @@ -80,7 +87,9 @@ export async function saveCustomerMessage( mode: "live", request, response: null, - error: err instanceof Error ? err.message : "CC_WO_ERROR", + error: formatCcCause( + err instanceof Error ? err.message : "CC_WO_ERROR", + ), }; } } diff --git a/src/services/cc/do-upload.ts b/src/services/cc/do-upload.ts index aa44dbb..75047ba 100644 --- a/src/services/cc/do-upload.ts +++ b/src/services/cc/do-upload.ts @@ -12,13 +12,16 @@ import fs from "fs/promises"; import { File, FormData, fetch } from "undici"; import { logger } from "@/lib/logger"; import { getEnv } from "@/lib/env"; +import { isTestMode } from "@/lib/test-mode"; import { resolveDataFile } from "@/lib/safe-path"; import { ccAuth } from "@/services/cc/auth"; import { CcHttpClient } from "@/services/cc/http"; import { normalizeCcCode } from "@/services/cc/code"; import { resolveWriteMode } from "@/services/cc/write-capability"; import { getContainerListFromCc } from "@/services/cc/conflict"; +import { getMockTargetAPI } from "@/services/cc/mock-target-api"; import { resolveCcRuntime } from "@/services/cc/settings-config"; +import { formatCcCause } from "@/constants/error-copy"; import type { CcApiResponse } from "@/types/cc"; const SAVE_FIELD_PATH = "/Container/SaveFieldValue"; @@ -89,11 +92,34 @@ async function listAnnexFolder( response?: unknown; error?: string; }> { + if (isTestMode()) { + try { + const body = await getMockTargetAPI().requestGet( + `${ANNEX_LIST_PATH}?data=${encodeURIComponent(folderId)}`, + ); + const code = normalizeCcCode(body.code); + const raw = body.data; + const files = Array.isArray(raw) + ? (raw as Array<{ F_Id?: string; F_FileName?: string; F_FolderId?: string }>) + : []; + return { ok: code === 200, files, response: body }; + } catch (err) { + return { + ok: false, + files: [], + error: formatCcCause( + err instanceof Error ? err.message : "annex list error", + ), + }; + } + } + const rt = await resolveCcRuntime(); const { token, loginMark } = await ccAuth.getToken(); const base = rt.apiBase.replace(/\/$/, ""); const qs = new URLSearchParams({ loginMark, token, data: folderId }); const url = `${base}${ANNEX_LIST_PATH}?${qs}`; + try { const res = await fetch(url, { method: "GET", @@ -108,7 +134,7 @@ async function listAnnexFolder( ok: false, files: [], response: text.slice(0, 300), - error: "annex list invalid JSON", + error: formatCcCause("annex list invalid JSON"), }; } const code = normalizeCcCode(body.code); @@ -125,7 +151,9 @@ async function listAnnexFolder( return { ok: false, files: [], - error: err instanceof Error ? err.message : "annex list error", + error: formatCcCause( + err instanceof Error ? err.message : "annex list error", + ), }; } } @@ -139,6 +167,40 @@ async function uploadAnnexFile( }, retried410 = false, ): Promise<{ ok: boolean; fileId?: string; response: unknown; error?: string }> { + if (isTestMode()) { + try { + const body = await getMockTargetAPI().request(UPLOAD_PATH, { + folderId: input.folderId, + filename: input.filename, + size: input.buffer.length, + }); + const code = normalizeCcCode(body.code); + const fileId = + typeof body.data === "string" + ? body.data + : body.data != null + ? String(body.data) + : ""; + return { + ok: code === 200, + fileId: code === 200 ? fileId : undefined, + response: body, + error: + code === 200 + ? undefined + : formatCcCause(body.info || `annexes upload failed: ${code}`), + }; + } catch (err) { + return { + ok: false, + response: null, + error: formatCcCause( + err instanceof Error ? err.message : "annexes upload error", + ), + }; + } + } + const rt = await resolveCcRuntime(); const { token, loginMark } = await ccAuth.getToken(); const base = rt.apiBase.replace(/\/$/, ""); @@ -181,7 +243,7 @@ async function uploadAnnexFile( return { ok: false, response: text.slice(0, 300), - error: `CC upload invalid JSON: ${text.slice(0, 120)}`, + error: formatCcCause(`CC upload invalid JSON: ${text.slice(0, 120)}`), }; } const code = normalizeCcCode(body.code); @@ -202,13 +264,15 @@ async function uploadAnnexFile( error: code === 200 ? undefined - : body.info || `annexes upload failed: ${code}`, + : formatCcCause(body.info || `annexes upload failed: ${code}`), }; } catch (err) { return { ok: false, response: null, - error: err instanceof Error ? err.message : "annexes upload error", + error: formatCcCause( + err instanceof Error ? err.message : "annexes upload error", + ), }; } finally { clearTimeout(timer); @@ -247,7 +311,7 @@ export async function saveContainerDoFile(input: { mode, request: requestBase, response: null, - error: "CC_WRITE_OFF", + error: formatCcCause("CC_WRITE_OFF"), }; } @@ -292,13 +356,13 @@ export async function saveContainerDoFile(input: { } } - if (!taskId) { + if (!taskId || /^mock-task-/i.test(taskId)) { return { ok: false, mode: "live", request: requestBase, response: null, - error: "缺少集装箱任务 id,无法挂 DO", + error: "CC 中还没有该柜,无法挂 DO。请先确认导入预报。", }; } @@ -320,7 +384,7 @@ export async function saveContainerDoFile(input: { folderId: null, request: { ...requestBase, fieldValue: folderId }, response: bind, - error: bind.info || `SaveFieldValue failed: ${bindCode}`, + error: formatCcCause(bind.info || `SaveFieldValue failed: ${bindCode}`), }; } @@ -356,7 +420,7 @@ export async function saveContainerDoFile(input: { uploadedFileIds, request: { ...requestBase, fieldValue: folderId }, response: { bind: folderId, uploads: uploadResponses }, - error: up.error || "附件上传失败", + error: formatCcCause(up.error || "附件上传失败"), }; } if (up.fileId) uploadedFileIds.push(up.fileId); @@ -372,9 +436,10 @@ export async function saveContainerDoFile(input: { uploadedFileIds, request: { ...requestBase, fieldValue: folderId }, response: { uploads: uploadResponses, list: listed.response }, - error: + error: formatCcCause( listed.error || - "上传接口返回成功,但附件列表为空(F_DOFile 必须为文件夹 id)", + "上传接口返回成功,但附件列表为空(F_DOFile 必须为文件夹 id)", + ), }; } @@ -416,7 +481,9 @@ export async function saveContainerDoFile(input: { mode: "live", request: requestBase, response: null, - error: err instanceof Error ? err.message : "CC_DO_ERROR", + error: formatCcCause( + err instanceof Error ? err.message : "CC_DO_ERROR", + ), }; } } diff --git a/src/services/cc/errors.ts b/src/services/cc/errors.ts new file mode 100644 index 0000000..cbce1d1 --- /dev/null +++ b/src/services/cc/errors.ts @@ -0,0 +1,10 @@ +export class CcHttpError extends Error { + constructor( + message: string, + public readonly cause?: unknown, + public readonly timeout = false, + ) { + super(message); + this.name = "CcHttpError"; + } +} diff --git a/src/services/cc/forecast-confirm-gate.ts b/src/services/cc/forecast-confirm-gate.ts index 7d2165e..7dd905e 100644 --- a/src/services/cc/forecast-confirm-gate.ts +++ b/src/services/cc/forecast-confirm-gate.ts @@ -1,21 +1,22 @@ +import { isValidContainerNo } from "@/utils/iso6346"; + /** - * 是否可进入预报确认导入(SaveContainer)。 - * 同封「新增预报 + 上传 DO」时主类型可能是 DO_UPLOAD,但仍有有效货件可预报。 + * 这一条「预报」指令是否具备进入确认页的数据与状态。 + * 不看整封邮件主类型:同封还可以有转仓/工单/DO,互不拦截。 + * 仅柜头预报(有柜号、暂无货件行)也可进入确认页补录/空货件导入。 */ export function canEnterForecastConfirm(input: { - mailType: string; status: string; validShipmentCount: number; - /** 详情页仅 PENDING/FAILED 可点导入;确认页还允许多种导入后回看状态 */ + /** 已解析出可用柜号(ISO 柜头),允许无货件行进入确认 */ + hasContainerHeader?: boolean; + /** 详情页仅待确认类状态可点导入;确认页还允许多种导入后回看状态 */ allowReadonlyStatuses?: boolean; }): boolean { - const { mailType, status, validShipmentCount } = input; - if (validShipmentCount < 1) return false; - - const typeOk = - mailType === "NEW_CONTAINER" || - mailType === "DO_UPLOAD"; - if (!typeOk) return false; + const { status, validShipmentCount } = input; + const hasRows = validShipmentCount >= 1; + const headerOnly = Boolean(input.hasContainerHeader) && !hasRows; + if (!hasRows && !headerOnly) return false; if (input.allowReadonlyStatuses) { return [ @@ -27,5 +28,119 @@ export function canEnterForecastConfirm(input: { ].includes(status); } - return status === "PENDING_CONFIRM" || status === "FAILED"; + return ( + status === "PENDING_CONFIRM" || + status === "FAILED" || + status === "PARTIAL_SUCCESS" + ); +} + +export type ForecastImportIssues = { + /** 字段级错误,挂到对应表单项 */ + errors: Record; + /** 阻断「确认导入」的原因(按钮灰掉时必须展示) */ + blockers: string[]; + /** 不阻断提交的提示(如仅柜头、货件全无效) */ + notes: string[]; +}; + +function summarizeInvalidRows( + rows: Array<{ row_index: number; invalid_reasons?: string[] }>, +): string | null { + if (!rows.length) return null; + const counts = new Map(); + for (const row of rows) { + const reasons = row.invalid_reasons?.length + ? row.invalid_reasons + : ["未标明原因"]; + for (const reason of reasons) { + counts.set(reason, (counts.get(reason) || 0) + 1); + } + } + const parts = [...counts.entries()].map(([reason, n]) => + n > 1 ? `${reason}(${n}行)` : reason, + ); + return `货件有 ${rows.length} 行未通过校验:${parts.join("、")}。请在附件中补全后重新解析,或仅导入柜头。`; +} + +/** + * 实时收集确认导入页的缺项 / 错项,供灰按钮旁提示与字段红字共用。 + * 无有效货件但柜号可用时允许仅柜头导入,不把「未选货件」当作阻断。 + */ +export function collectForecastImportIssues(input: { + transMode: number | null | undefined; + operationType: number | null | undefined; + containerNo: string; + hasContainerConflict: boolean; + containerSource: "body" | "attachment" | null; + validRowCount: number; + selectedValidCount: number; + invalidRows?: Array<{ row_index: number; invalid_reasons?: string[] }>; + shipmentCount?: number; + hasUnmappedSelected: boolean; + ackUnmapped: boolean; + softLimitExceeded?: boolean; + portal?: "admin" | "client"; + step?: number; +}): ForecastImportIssues { + const errors: Record = {}; + const blockers: string[] = []; + const notes: string[] = []; + const invalidRows = input.invalidRows ?? []; + const shipmentCount = input.shipmentCount ?? 0; + + if (input.transMode !== 0 && input.transMode !== 1 && input.transMode !== 3) { + const msg = "请选择运输方式(海运 / 空运 / 自送柜)"; + errors.transMode = msg; + blockers.push(msg); + } + if ( + input.operationType !== 0 && + input.operationType !== 2 && + input.operationType !== 4 + ) { + const msg = "请选择业务类型(提拆派 / 直送 / 只清关)"; + errors.operationType = msg; + blockers.push(msg); + } + + const containerNo = (input.containerNo || "").trim(); + if (!containerNo) { + const msg = "柜号为空,请填写 4 位字母 + 7 位数字(ISO 6346)"; + errors.containerNo = msg; + blockers.push(msg); + } else if (!isValidContainerNo(containerNo)) { + const msg = `柜号「${containerNo.toUpperCase()}」不符合 ISO 6346 校验位,请核对后改正`; + errors.containerNo = msg; + blockers.push(msg); + } + + if (input.hasContainerConflict && !input.containerSource) { + const msg = "正文与附件柜号不一致,请先选定使用哪一个"; + errors.containerSource = msg; + blockers.push(msg); + } + + if (input.validRowCount > 0 && input.selectedValidCount === 0) { + const msg = input.softLimitExceeded + ? "货件超过软限,默认未全选;请筛选后至少勾选一行有效货件" + : "请至少勾选一行有效货件"; + errors.rows = msg; + blockers.push(msg); + } + + if (input.hasUnmappedSelected && !input.ackUnmapped) { + const msg = "所选货件含未映射渠道,请勾选「已知晓存在未映射渠道,仍提交原值」"; + errors.ack = msg; + blockers.push(msg); + } + + const invalidSummary = summarizeInvalidRows(invalidRows); + if (input.validRowCount === 0 && invalidSummary) { + notes.push(invalidSummary); + } else if (input.validRowCount === 0 && shipmentCount === 0) { + notes.push("未解析到货件行,确认导入将只提交柜头信息。"); + } + + return { errors, blockers, notes }; } diff --git a/src/services/cc/http.ts b/src/services/cc/http.ts index 24c1f9e..ff4e206 100644 --- a/src/services/cc/http.ts +++ b/src/services/cc/http.ts @@ -1,24 +1,25 @@ import { Agent, fetch } from "undici"; import { getEnv } from "@/lib/env"; +import { isTestMode } from "@/lib/test-mode"; import { logger } from "@/lib/logger"; import { emitResilience } from "@/lib/resilience-metrics"; import type { CcApiResponse, CcEnvelope } from "@/types/cc"; import type { CcAuth } from "@/services/cc/auth"; +import { ccAuth } from "@/services/cc/auth"; import { normalizeCcCode } from "@/services/cc/code"; import { resolveCcRuntime } from "@/services/cc/settings-config"; - -export class CcHttpError extends Error { - constructor( - message: string, - public readonly cause?: unknown, - public readonly timeout = false, - ) { - super(message); - this.name = "CcHttpError"; - } -} - +import { CcHttpError } from "@/services/cc/errors"; +import { getMockTargetAPI } from "@/services/cc/mock-target-api"; +import { + getTargetSystemClientOverride, + registerLiveTargetSystemClient, + type CcHttpRequestOptions, + type TargetSystemClient, +} from "@/services/cc/target-system-client"; + +export { CcHttpError } from "@/services/cc/errors"; export { normalizeCcCode } from "@/services/cc/code"; +export type { CcHttpRequestOptions, TargetSystemClient }; /** 复用到同一 CC 主机的 TLS 连接,减少每次握手 */ const ccDispatcher = new Agent({ @@ -28,12 +29,14 @@ const ccDispatcher = new Agent({ pipelining: 0, }); -export type CcHttpRequestOptions = { - retried410?: boolean; - timeoutMs?: number; -}; +function injectedClient(): TargetSystemClient | null { + const override = getTargetSystemClientOverride(); + if (override) return override; + if (isTestMode()) return getMockTargetAPI(); + return null; +} -export class CcHttpClient { +export class CcHttpClient implements TargetSystemClient { constructor(private readonly auth: CcAuth) {} /** V1.72 部分接口(如船司列表)为 GET:loginMark + token 放 query。 */ @@ -41,6 +44,9 @@ export class CcHttpClient { path: string, options?: CcHttpRequestOptions, ): Promise { + const injected = injectedClient(); + if (injected) return injected.requestGet(path, options); + const env = getEnv(); const rt = await resolveCcRuntime(); const { token, loginMark } = await this.auth.getToken(); @@ -96,6 +102,9 @@ export class CcHttpClient { dataPayload: unknown, options?: CcHttpRequestOptions, ): Promise { + const injected = injectedClient(); + if (injected) return injected.request(path, dataPayload, options); + const env = getEnv(); const rt = await resolveCcRuntime(); const { token, loginMark } = await this.auth.getToken(); @@ -165,3 +174,5 @@ export class CcHttpClient { } } } + +registerLiveTargetSystemClient(new CcHttpClient(ccAuth)); diff --git a/src/services/cc/instruction-write.ts b/src/services/cc/instruction-write.ts index 9f2ed4e..9833c70 100644 --- a/src/services/cc/instruction-write.ts +++ b/src/services/cc/instruction-write.ts @@ -3,6 +3,7 @@ import { ccAuth } from "@/services/cc/auth"; import { CcHttpClient } from "@/services/cc/http"; import { normalizeCcCode } from "@/services/cc/code"; import { getCcWriteCapability, resolveWriteMode } from "./write-capability"; +import { formatCcCause } from "@/constants/error-copy"; export type InstructionWriteKind = "transfer" | "hold_split" | "label"; @@ -46,7 +47,7 @@ export async function executeInstructionWrite( mode, request, response: null, - error: "CC_WRITE_OFF", + error: formatCcCause("CC_WRITE_OFF"), }; } @@ -76,9 +77,12 @@ export async function executeInstructionWrite( response: res, error: ok ? undefined - : String( - (res as { message?: string }).message || - `CC_${kind.toUpperCase()}_FAILED`, + : formatCcCause( + String( + (res as { info?: string; message?: string }).info || + (res as { message?: string }).message || + `CC_${kind.toUpperCase()}_FAILED`, + ), ), }; } catch (err) { @@ -87,7 +91,9 @@ export async function executeInstructionWrite( mode: "live", request, response: null, - error: err instanceof Error ? err.message : "CC_WRITE_ERROR", + error: formatCcCause( + err instanceof Error ? err.message : "CC_WRITE_ERROR", + ), }; } } diff --git a/src/services/cc/mock-target-api.ts b/src/services/cc/mock-target-api.ts new file mode 100644 index 0000000..ed4ac87 --- /dev/null +++ b/src/services/cc/mock-target-api.ts @@ -0,0 +1,211 @@ +import fs from "fs"; +import path from "path"; +import type { CcApiResponse } from "@/types/cc"; +import { CcHttpError } from "@/services/cc/errors"; +import { + registerMockTargetSystemClient, + type CcHttpRequestOptions, + type TargetSystemClient, +} from "@/services/cc/target-system-client"; + +export type MockTargetFault = + | "ok" + | "400" + | "401" + | "500" + | "timeout" + | "partial"; + +export type RecordedTargetRequest = { + method: "GET" | "POST"; + path: string; + body: unknown; + at: string; +}; + +type PersistShape = { + defaultFault: MockTargetFault; + pathFaults: Array<[string, MockTargetFault]>; + requests: RecordedTargetRequest[]; +}; + +const STATE_PATH = path.join(process.cwd(), "data", "test-mode-target.json"); + +function normalizePath(pathName: string): string { + const raw = pathName.startsWith("/") ? pathName : `/${pathName}`; + return raw.split("?")[0] || "/"; +} + +function readState(): PersistShape { + try { + const raw = fs.readFileSync(STATE_PATH, "utf8"); + const parsed = JSON.parse(raw) as PersistShape; + return { + defaultFault: parsed.defaultFault || "ok", + pathFaults: Array.isArray(parsed.pathFaults) ? parsed.pathFaults : [], + requests: Array.isArray(parsed.requests) ? parsed.requests : [], + }; + } catch { + return { defaultFault: "ok", pathFaults: [], requests: [] }; + } +} + +function writeState(state: PersistShape): void { + fs.mkdirSync(path.dirname(STATE_PATH), { recursive: true }); + fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2), "utf8"); +} + +function defaultOk(reqPath: string): CcApiResponse { + const p = normalizePath(reqPath); + if (p.includes("GetContainerList")) { + return { code: 200, data: [] }; + } + if (p.includes("GetShippingLineList")) { + return { + code: 200, + data: [ + { + F_Id: "mock-whlc", + F_FullName: "万海航运(WHLC)", + F_EnCode: "WHLC", + F_Prefix: "WHLC", + }, + ], + }; + } + if (p.includes("annexes/list")) { + return { code: 200, data: [] }; + } + if (p.includes("annexes/upload")) { + return { code: 200, data: `mock-file-${Date.now()}` }; + } + if (p.includes("customerLogin")) { + return { code: 200, data: { token: "test-mode-cc-token" } }; + } + return { code: 200, data: `mock-${Date.now()}` }; +} + +/** + * 可配置故障的目标 API Mock;记录每次请求供断言。 + * 状态落盘,避免 Next.js 多 isolate 丢内存单例。 + */ +export class MockTargetAPI implements TargetSystemClient { + private customResponses = new Map(); + + setFault(fault: MockTargetFault, pathName?: string): void { + const state = readState(); + if (!pathName) { + state.defaultFault = fault; + } else { + const p = normalizePath(pathName); + state.pathFaults = state.pathFaults.filter(([k]) => k !== p); + state.pathFaults.push([p, fault]); + } + writeState(state); + } + + setResponse(pathName: string, response: CcApiResponse): void { + this.customResponses.set(normalizePath(pathName), response); + } + + reset(): void { + this.customResponses.clear(); + writeState({ defaultFault: "ok", pathFaults: [], requests: [] }); + } + + getRequests(): RecordedTargetRequest[] { + return [...readState().requests]; + } + + getFaultState(): { + defaultFault: MockTargetFault; + pathFaults: Array<[string, MockTargetFault]>; + } { + const state = readState(); + return { + defaultFault: state.defaultFault, + pathFaults: state.pathFaults, + }; + } + + async request( + reqPath: string, + dataPayload: unknown, + _options?: CcHttpRequestOptions, + ): Promise { + this.record("POST", reqPath, dataPayload); + return this.resolve(reqPath); + } + + async requestGet( + reqPath: string, + _options?: CcHttpRequestOptions, + ): Promise { + this.record("GET", reqPath, null); + return this.resolve(reqPath); + } + + private record( + method: "GET" | "POST", + reqPath: string, + body: unknown, + ): void { + const state = readState(); + state.requests.push({ + method, + path: normalizePath(reqPath), + body, + at: new Date().toISOString(), + }); + writeState(state); + } + + private resolve(reqPath: string): CcApiResponse { + const p = normalizePath(reqPath); + const custom = this.customResponses.get(p); + if (custom) return { ...custom }; + + const state = readState(); + const pathFault = state.pathFaults.find(([k]) => k === p)?.[1]; + const fault = pathFault ?? state.defaultFault; + switch (fault) { + case "400": + return { code: 400, info: "Bad Request" }; + case "401": + return { code: 401, info: "Unauthorized" }; + case "500": + return { code: 500, info: "Internal Server Error" }; + case "timeout": + throw new CcHttpError("CC request timeout", undefined, true); + case "partial": + return { + code: 200, + info: "PARTIAL_SUCCESS", + data: { + ok_count: 1, + fail_count: 1, + succeeded: [{ id: "mock-ok" }], + failed: [{ id: "mock-fail", error: "partial" }], + }, + }; + default: + return defaultOk(p); + } + } +} + +let singleton: MockTargetAPI | null = null; + +export function getMockTargetAPI(): MockTargetAPI { + if (!singleton) singleton = new MockTargetAPI(); + return singleton; +} + +export function resetMockTargetAPI(): MockTargetAPI { + singleton = new MockTargetAPI(); + singleton.reset(); + registerMockTargetSystemClient(singleton); + return singleton; +} + +registerMockTargetSystemClient(getMockTargetAPI()); diff --git a/src/services/cc/save-container.ts b/src/services/cc/save-container.ts index 04a9e79..ab0e128 100644 --- a/src/services/cc/save-container.ts +++ b/src/services/cc/save-container.ts @@ -13,6 +13,10 @@ import type { CcShipment, } from "@/types/cc"; import type { ContainerHeader, ShipmentRow } from "@/types/mail"; +import { formatCcCause } from "@/constants/error-copy"; +import { normalizeFbaCodeForCc } from "@/services/parse/normalize-fba-code"; + +export { normalizeFbaCodeForCc } from "@/services/parse/normalize-fba-code"; const SAVE_PATH = "/Container/SaveContainer"; @@ -48,12 +52,14 @@ export function buildSaveContainerEntity( }, ): CcSaveContainerEntity { const merged = { ...header, ...overrides }; + const cabinet = (merged.F_CabinetType || "").trim(); + const bl = (merged.F_BLCopyCode || "").trim().slice(0, 15); return { F_TransMode: merged.F_TransMode ?? overrides.F_TransMode, F_OperationType: merged.F_OperationType ?? overrides.F_OperationType, F_ContainerNo: merged.F_ContainerNo.toUpperCase(), - F_CabinetType: merged.F_CabinetType, - F_BLCopyCode: merged.F_BLCopyCode ?? "", + F_CabinetType: cabinet || undefined, + F_BLCopyCode: bl, F_ETD: merged.F_ETD ?? null, F_ETA: merged.F_ETA ?? null, F_LoadPort: merged.F_LoadPort ?? "", @@ -68,15 +74,8 @@ export function buildSaveContainerEntity( export function mapShipmentRows(rows: ShipmentRow[]): CcShipment[] { return rows.map((row) => { const transporter = normalizeTransporterForCc(row.F_Transporter || ""); - let fba = (row.F_FBACode || "").trim(); - // CC 校验:非空 + 仅允许字母数字与 _- 空格 - if (!fba || !/^[A-Za-z0-9_\- ]+$/.test(fba)) { - if (/^(留仓|存仓|扣货|自提)$/.test(transporter)) { - fba = "HOLD"; - } - } return { - F_FBACode: fba, + F_FBACode: normalizeFbaCodeForCc(row.F_FBACode, transporter), F_Transporter: transporter, F_CTNS: row.F_CTNS ?? 0, F_CBM: row.F_CBM ?? null, @@ -159,7 +158,7 @@ export async function saveContainer( return { ok: false, status: "TIMEOUT_UNKNOWN", - error: "CC response missing external id", + error: formatCcCause("CC response missing external id"), requestBody, responseBody, response, @@ -181,10 +180,17 @@ export async function saveContainer( }); } + if (code === 401) { + emitResilience("cc.login_fail", "error", { + containerNo: input.entity.F_ContainerNo, + info: response.info, + }); + } + return { ok: false, status: "FAILED", - error: response.info || `CC save failed with code ${code}`, + error: formatCcCause(ccApiErrorText(response, code)), requestBody, responseBody, response, @@ -195,18 +201,56 @@ export async function saveContainer( return { ok: false, status: timeout ? "TIMEOUT_UNKNOWN" : "FAILED", - error: + error: formatCcCause( err instanceof Error ? err.message : "SaveContainer request failed", + ), requestBody, }; } } +function ccApiErrorText( + response: CcApiResponse, + code = normalizeCcCode(response.code), +): string { + const info = String(response.info || "").trim(); + if (info) return info; + const message = String( + (response as { message?: string }).message || "", + ).trim(); + if (message) return message; + if (typeof response.data === "string" && response.data.trim()) { + return response.data.trim(); + } + return `CC save failed with code ${code}`; +} + function extractExternalId(data: unknown): string | null { if (typeof data === "string" && data.trim()) return data.trim(); - if (typeof data === "object" && data && "F_Id" in data) { - const id = (data as { F_Id?: unknown }).F_Id; + if (typeof data === "object" && data) { + const rec = data as { F_Id?: unknown; F_ContainerTaskId?: unknown }; + const id = rec.F_ContainerTaskId ?? rec.F_Id; if (id != null && String(id).trim()) return String(id).trim(); } return null; } + +/** 超时或 5xx 入补偿队列;401/400 不自动重试 */ +export function shouldEnqueueCompensation( + result: Extract, +): string | null { + if (result.status === "TIMEOUT_UNKNOWN") return "TIMEOUT_UNKNOWN"; + const code = normalizeCcCode(result.response?.code); + if (code === 401 || code === 400) return null; + if (/Unauthorized|\b401\b|登录失败|无权限/.test(result.error)) return null; + if (code >= 500) return "CC_5XX"; + if (!result.response && result.status === "FAILED") return "CC_5XX"; + if ( + /Internal Server Error|CC request failed|无法连接|服务器错误/i.test( + result.error, + ) + ) { + return "CC_5XX"; + } + return null; +} diff --git a/src/services/cc/settings-config.ts b/src/services/cc/settings-config.ts index ef1cdd5..688b2a4 100644 --- a/src/services/cc/settings-config.ts +++ b/src/services/cc/settings-config.ts @@ -1,10 +1,13 @@ import { getEnv } from "@/lib/env"; +import { getCcOwnerUserId } from "@/lib/cc-owner"; +import { isTestMode } from "@/lib/test-mode"; import { decryptSecret, encryptSecret, maskUsername } from "@/lib/secret-crypto"; import { prisma } from "@/services/db"; import { md5 } from "@/utils/hash"; import { normalizeCcCode } from "@/services/cc/code"; import type { CcApiResponse } from "@/types/cc"; import { fetch } from "undici"; +import { getMockTargetAPI } from "@/services/cc/mock-target-api"; const SINGLETON_ID = 1; const LOGIN_PATH = "/learun/adms/user/customerLogin"; @@ -36,10 +39,14 @@ export type CcSettingsPublic = { type CacheEntry = { at: number; value: CcRuntimeConfig }; -let cache: CacheEntry | null = null; +const cacheByOwner = new Map(); + +function cacheKey(ownerUserId: bigint | null | undefined): string { + return ownerUserId != null ? String(ownerUserId) : "global"; +} export function invalidateCcSettingsCache(): void { - cache = null; + cacheByOwner.clear(); } function passwordMd5FromParts( @@ -69,17 +76,45 @@ function fromEnv(): CcRuntimeConfig { }; } -async function loadRuntime(): Promise { +function unconfiguredCustomer(): CcRuntimeConfig { const env = getEnv(); - let row: Awaited> = null; + return { + mock: true, + apiBase: env.CC_API_BASE, + saasHeader: env.CC_SAAS_HEADER, + loginMark: env.CC_LOGIN_MARK, + username: "", + passwordMd5: "", + source: "db", + hasPassword: false, + }; +} + +async function findGlobalCcRow() { + return ( + (await prisma.ccSettings.findFirst({ where: { userId: null } })) ?? + (await prisma.ccSettings.findUnique({ where: { id: SINGLETON_ID } })) + ); +} + +async function loadRuntime( + ownerUserId: bigint | null | undefined, +): Promise { + const env = getEnv(); + let row: Awaited> = null; try { - row = await prisma.ccSettings.findUnique({ - where: { id: SINGLETON_ID }, - }); + if (ownerUserId != null) { + row = await prisma.ccSettings.findUnique({ + where: { userId: ownerUserId }, + }); + if (!row) return unconfiguredCustomer(); + } else { + row = await findGlobalCcRow(); + } } catch { - return fromEnv(); + return ownerUserId != null ? unconfiguredCustomer() : fromEnv(); } - if (!row) return fromEnv(); + if (!row) return ownerUserId != null ? unconfiguredCustomer() : fromEnv(); let passwordMd5 = ""; let hasPassword = false; @@ -92,7 +127,7 @@ async function loadRuntime(): Promise { hasPassword = false; } } - if (!hasPassword) { + if (!hasPassword && row.userId == null) { passwordMd5 = passwordMd5FromParts( env.CC_PASSWORD_PLAIN ?? "", env.CC_PASSWORD_MD5 ?? "", @@ -100,7 +135,10 @@ async function loadRuntime(): Promise { hasPassword = Boolean(passwordMd5); } - const username = row.username.trim() || (env.CC_USERNAME ?? "").trim(); + const username = + row.userId != null + ? row.username.trim() + : row.username.trim() || (env.CC_USERNAME ?? "").trim(); return { mock: row.mock, @@ -115,28 +153,51 @@ async function loadRuntime(): Promise { } export async function resolveCcRuntime(): Promise { + const ownerUserId = getCcOwnerUserId(); + const key = cacheKey(ownerUserId); const now = Date.now(); - if (cache && now - cache.at < CACHE_TTL_MS) return cache.value; - const value = await loadRuntime(); - cache = { at: now, value }; + const hit = cacheByOwner.get(key); + if (hit && now - hit.at < CACHE_TTL_MS) return hit.value; + const value = await loadRuntime(ownerUserId); + cacheByOwner.set(key, { at: now, value }); return value; } export async function isCcMock(): Promise { + if (isTestMode()) return false; return (await resolveCcRuntime()).mock; } -export async function getCcSettingsPublic(): Promise { +export async function getCcSettingsPublic( + ownerUserId?: bigint | null, +): Promise { const env = getEnv(); - let row: Awaited> = null; + let row: Awaited> = null; try { - row = await prisma.ccSettings.findUnique({ - where: { id: SINGLETON_ID }, - }); + if (ownerUserId != null) { + row = await prisma.ccSettings.findUnique({ + where: { userId: ownerUserId }, + }); + } else { + row = await findGlobalCcRow(); + } } catch { row = null; } if (!row) { + if (ownerUserId != null) { + return { + mock: true, + api_base: env.CC_API_BASE, + saas_header: env.CC_SAAS_HEADER, + login_mark: env.CC_LOGIN_MARK, + username: "", + username_masked: "", + has_password: false, + source: "db", + updated_at: null, + }; + } const rt = fromEnv(); return { mock: rt.mock, @@ -158,8 +219,10 @@ export async function getCcSettingsPublic(): Promise { username: row.username, username_masked: row.username ? maskUsername(row.username) : "", has_password: - Boolean(row.passwordEnc) || - Boolean(env.CC_PASSWORD_PLAIN || env.CC_PASSWORD_MD5), + row.userId != null + ? Boolean(row.passwordEnc) + : Boolean(row.passwordEnc) || + Boolean(env.CC_PASSWORD_PLAIN || env.CC_PASSWORD_MD5), source: "db", updated_at: row.updatedAt.toISOString(), }; @@ -173,10 +236,13 @@ export async function upsertCcSettings(input: { username: string; /** 留空则不改密码 */ password?: string; + ownerUserId?: bigint | null; }): Promise { - const existing = await prisma.ccSettings.findUnique({ - where: { id: SINGLETON_ID }, - }); + const ownerUserId = input.ownerUserId ?? null; + const existing = + ownerUserId != null + ? await prisma.ccSettings.findUnique({ where: { userId: ownerUserId } }) + : await findGlobalCcRow(); const env = getEnv(); let passwordEnc: string | null | undefined = undefined; @@ -197,24 +263,21 @@ export async function upsertCcSettings(input: { if (existing) { await prisma.ccSettings.update({ - where: { id: SINGLETON_ID }, + where: { id: existing.id }, data, }); } else { await prisma.ccSettings.create({ data: { - id: SINGLETON_ID, + ...(ownerUserId == null ? { id: SINGLETON_ID } : { userId: ownerUserId }), ...data, - passwordEnc: - passwordEnc === undefined - ? null - : passwordEnc, + passwordEnc: passwordEnc === undefined ? null : passwordEnc, }, }); } invalidateCcSettingsCache(); - return getCcSettingsPublic(); + return getCcSettingsPublic(ownerUserId); } /** 用当前配置(或覆盖密码)试 customerLogin;不写 token cache。 */ @@ -223,7 +286,7 @@ export async function testCcLogin(overridePassword?: string): Promise<{ message: string; }> { const rt = await resolveCcRuntime(); - if (rt.mock) { + if (rt.mock && !isTestMode()) { return { ok: true, message: "当前为 mock 模式,跳过真网登录" }; } @@ -234,6 +297,28 @@ export async function testCcLogin(overridePassword?: string): Promise<{ return { ok: false, message: "缺少用户名或密码" }; } + if (isTestMode()) { + try { + const body = await getMockTargetAPI().request(LOGIN_PATH, { + username: rt.username, + password: passwordMd5, + }); + const code = normalizeCcCode(body.code); + if (code === 200) { + return { ok: true, message: "登录成功" }; + } + return { + ok: false, + message: body.info || `CC_LOGIN_FAILED:${code}`, + }; + } catch (err) { + return { + ok: false, + message: err instanceof Error ? err.message : "登录请求失败", + }; + } + } + const base = rt.apiBase.replace(/\/$/, ""); const url = `${base}${LOGIN_PATH}`; const env = getEnv(); diff --git a/src/services/cc/target-system-client.ts b/src/services/cc/target-system-client.ts new file mode 100644 index 0000000..370d5b0 --- /dev/null +++ b/src/services/cc/target-system-client.ts @@ -0,0 +1,62 @@ +import type { CcApiResponse } from "@/types/cc"; +import { isTestMode } from "@/lib/test-mode"; + +export type CcHttpRequestOptions = { + retried410?: boolean; + timeoutMs?: number; +}; + +/** + * 目标系统(CarrierCentral)传输层。 + * 生产:CcHttpClient;TEST_MODE:MockTargetAPI。 + */ +export interface TargetSystemClient { + request( + path: string, + dataPayload: unknown, + options?: CcHttpRequestOptions, + ): Promise; + requestGet( + path: string, + options?: CcHttpRequestOptions, + ): Promise; +} + +let override: TargetSystemClient | null = null; +let liveClient: TargetSystemClient | null = null; +let mockClient: TargetSystemClient | null = null; + +/** 测试/工作台可替换实现;传 null 恢复默认。 */ +export function setTargetSystemClientOverride( + client: TargetSystemClient | null, +): void { + override = client; +} + +export function getTargetSystemClientOverride(): TargetSystemClient | null { + return override; +} + +/** 由 CcHttpClient 模块注册生产实现(避免循环依赖)。 */ +export function registerLiveTargetSystemClient(client: TargetSystemClient): void { + liveClient = client; +} + +/** 由 MockTargetAPI 模块注册测试实现。 */ +export function registerMockTargetSystemClient(client: TargetSystemClient): void { + mockClient = client; +} + +export function getTargetSystemClient(): TargetSystemClient { + if (override) return override; + if (isTestMode()) { + if (!mockClient) { + throw new Error("MockTargetAPI is not registered"); + } + return mockClient; + } + if (!liveClient) { + throw new Error("TargetSystemClient live implementation is not registered"); + } + return liveClient; +} diff --git a/src/services/cc/write-capability.ts b/src/services/cc/write-capability.ts index 84a470a..0736faf 100644 --- a/src/services/cc/write-capability.ts +++ b/src/services/cc/write-capability.ts @@ -1,5 +1,6 @@ import { prisma } from "@/services/db"; import { isCcMock } from "@/services/cc/settings-config"; +import { isTestMode } from "@/lib/test-mode"; export type CcWriteCapabilityId = | "transfer" @@ -79,6 +80,7 @@ export async function listCcWriteCapabilities() { export async function resolveWriteMode( id: CcWriteCapabilityId, ): Promise { + if (isTestMode()) return "live"; if (await isCcMock()) return "mock"; const cap = await getCcWriteCapability(id); return cap.mode; diff --git a/src/services/imap/client.ts b/src/services/imap/client.ts index 1ae0586..5473970 100644 --- a/src/services/imap/client.ts +++ b/src/services/imap/client.ts @@ -1,8 +1,11 @@ -import { ImapFlow, type FetchMessageObject } from "imapflow"; +import { ImapFlow, type FetchMessageObject, type ImapFlowOptions } from "imapflow"; import { getEnv } from "@/lib/env"; +import { isTestMode } from "@/lib/test-mode"; import { formatImapConnectError } from "@/lib/imap-presets"; +import { imapProxyForHost, redactProxyUrl } from "@/lib/imap-proxy"; import { logger } from "@/lib/logger"; import { hasConfiguredMailbox } from "@/services/imap/mailbox-config"; +import { getMockMailServer } from "@/services/imap/mock-mail-server"; export interface FetchedImapMessage { uid: number; @@ -43,25 +46,51 @@ export function startOfLookbackDay(lookbackDays: number, now = new Date()): Date return d; } +export function buildImapFlowOptions( + config: ImapClientConfig, + extra?: Partial, +): ImapFlowOptions { + const env = getEnv(); + const proxy = imapProxyForHost( + config.host, + env.IMAP_PROXY || "", + env.IMAP_PROXY_HOSTS, + ); + const auth = config.accessToken + ? { user: config.user, accessToken: config.accessToken } + : { user: config.user, pass: config.pass || "" }; + return { + host: config.host, + port: config.port, + secure: config.port === 993, + auth, + logger: false, + connectionTimeout: config.connectTimeoutMs, + greetingTimeout: config.connectTimeoutMs, + socketTimeout: config.readTimeoutMs, + ...(proxy ? { proxy } : {}), + ...extra, + }; +} + export class ImapClient implements ImapClientLike { private client: ImapFlow; private readonly folder: string; constructor(private readonly config: ImapClientConfig) { this.folder = config.folder ?? "INBOX"; - const auth = config.accessToken - ? { user: config.user, accessToken: config.accessToken } - : { user: config.user, pass: config.pass || "" }; - this.client = new ImapFlow({ - host: config.host, - port: config.port, - secure: config.port === 993, - auth, - logger: false, - connectionTimeout: config.connectTimeoutMs, - greetingTimeout: config.connectTimeoutMs, - socketTimeout: config.readTimeoutMs, - }); + const opts = buildImapFlowOptions(config); + if (opts.proxy) { + logger.info( + { + host: config.host, + user: config.user, + proxy: redactProxyUrl(opts.proxy), + }, + "imap.connect_via_proxy", + ); + } + this.client = new ImapFlow(opts); this.client.on("error", (err) => { logger.warn({ err, host: config.host, user: config.user }, "imap.client_socket_error"); }); @@ -164,13 +193,26 @@ export function hasImapCredentials(): boolean { } export function createImapClientFromConfig(config: ImapClientConfig): ImapClientLike { + if (isTestMode()) { + return getMockMailServer().createClient(config); + } return new ImapClient(config); } export function createImapClient(): ImapClientLike | null { - if (!hasImapCredentials()) return null; const env = getEnv(); - return new ImapClient({ + if (isTestMode()) { + return createImapClientFromConfig({ + host: env.IMAP_HOST, + port: env.IMAP_PORT, + user: env.IMAP_USER || "test@mock.local", + pass: env.IMAP_PASS || "test", + connectTimeoutMs: env.IMAP_CONNECT_TIMEOUT_MS, + readTimeoutMs: env.IMAP_READ_TIMEOUT_MS, + }); + } + if (!hasImapCredentials()) return null; + return createImapClientFromConfig({ host: env.IMAP_HOST, port: env.IMAP_PORT, user: env.IMAP_USER, diff --git a/src/services/imap/idle-supervisor.ts b/src/services/imap/idle-supervisor.ts index 60dc681..d9f0dc0 100644 --- a/src/services/imap/idle-supervisor.ts +++ b/src/services/imap/idle-supervisor.ts @@ -1,11 +1,13 @@ import { ImapFlow } from "imapflow"; import { getEnv } from "@/lib/env"; +import { isTestMode } from "@/lib/test-mode"; import { logger } from "@/lib/logger"; import { ImapPoller } from "@/services/imap/poller"; import { resolveEnabledMailboxes, type ResolvedMailbox, } from "@/services/imap/mailbox-config"; +import { createImapClientFromConfig, buildImapFlowOptions } from "@/services/imap/client"; type IdleHandle = { stop: () => Promise; @@ -25,6 +27,22 @@ export class IdleSupervisor { async start(): Promise { const env = getEnv(); + if (isTestMode()) { + // 与拉取共用 createImapClientFromConfig(Mock),避免 IDLE 打真箱。 + // 新信由工作台/测试注入后主动 tick,不挂真实 EXISTS 循环。 + try { + const boxes = await resolveEnabledMailboxes(); + for (const box of boxes) { + const client = createImapClientFromConfig(box.config); + await client.connect(); + await client.disconnect(); + } + } catch (err) { + logger.warn({ err }, "imap.idle_test_mode_connect_skipped"); + } + logger.info("imap.idle_skipped_test_mode"); + return; + } if (!env.IMAP_IDLE_ENABLED) { logger.info("imap.idle_disabled_global"); return; @@ -51,17 +69,11 @@ export class IdleSupervisor { const loop = async () => { while (!this.stopped && !aborted) { try { - client = new ImapFlow({ - host: box.config.host, - port: box.config.port, - secure: box.config.port === 993, - auth: box.config.accessToken - ? { user: box.config.user, accessToken: box.config.accessToken } - : { user: box.config.user, pass: box.config.pass || "" }, - logger: false, - connectionTimeout: box.config.connectTimeoutMs, - socketTimeout: Math.max(box.config.readTimeoutMs, 300_000), - }); + client = new ImapFlow( + buildImapFlowOptions(box.config, { + socketTimeout: Math.max(box.config.readTimeoutMs, 300_000), + }), + ); // imapflow Socket timeout 会 emit('error');未监听会直接打崩 worker client.on("error", (err) => { logger.warn( diff --git a/src/services/imap/mailbox-config.ts b/src/services/imap/mailbox-config.ts index b33a357..b023050 100644 --- a/src/services/imap/mailbox-config.ts +++ b/src/services/imap/mailbox-config.ts @@ -1,5 +1,6 @@ import { prisma } from "@/services/db"; import { getEnv } from "@/lib/env"; +import { isTestMode } from "@/lib/test-mode"; import { normalizeMailboxSecret, resolveMailboxEndpoint, @@ -141,8 +142,9 @@ function rowToConfig( }; } -export async function listMailboxes(): Promise { +export async function listMailboxes(ownerUserId?: bigint | null): Promise { const rows = await prisma.mailboxAccount.findMany({ + where: ownerUserId != null ? { ownerUserId } : undefined, orderBy: [{ enabled: "desc" }, { id: "asc" }], }); return rows.map(toPublic); @@ -163,6 +165,7 @@ export async function createMailbox(input: { enabled?: boolean; idleEnabled?: boolean; authType?: string; + ownerUserId?: bigint | null; }): Promise { const endpoint = resolveMailboxEndpoint(input.username, { host: input.host, @@ -181,6 +184,7 @@ export async function createMailbox(input: { folder: (input.folder ?? "INBOX").trim() || "INBOX", enabled: input.enabled ?? true, idleEnabled: input.idleEnabled ?? true, + ownerUserId: input.ownerUserId ?? null, }, }); return toPublic(row); @@ -231,7 +235,29 @@ export async function deleteMailbox(id: bigint): Promise { await prisma.mailboxAccount.delete({ where: { id } }); } +function syntheticTestMailbox(): ResolvedMailbox { + const env = getEnv(); + return { + id: null, + name: "test-mode-mock", + idleEnabled: false, + config: { + host: "mock.local", + port: 993, + user: env.IMAP_USER || "test@mock.local", + pass: env.IMAP_PASS || "test", + connectTimeoutMs: env.IMAP_CONNECT_TIMEOUT_MS, + readTimeoutMs: env.IMAP_READ_TIMEOUT_MS, + folder: "INBOX", + }, + }; +} + export async function resolveEnabledMailboxes(): Promise { + if (isTestMode()) { + return [syntheticTestMailbox()]; + } + const rows = await prisma.mailboxAccount.findMany({ where: { enabled: true }, orderBy: { id: "asc" }, @@ -250,12 +276,20 @@ export async function resolveEnabledMailboxes(): Promise { } if (row.authType === "OAUTH" && !accessToken) continue; if (row.authType !== "OAUTH" && !row.passwordEnc) continue; - out.push({ - id: row.id, - name: row.name, - idleEnabled: row.idleEnabled !== false, - config: rowToConfig(row, accessToken), - }); + try { + out.push({ + id: row.id, + name: row.name, + idleEnabled: row.idleEnabled !== false, + config: rowToConfig(row, accessToken), + }); + } catch (err) { + console.warn( + "mailbox.decrypt_failed — SESSION_SECRET 与加密时不一致,请在设置页重新保存该邮箱授权码", + row.username, + err, + ); + } } return out; } @@ -284,6 +318,7 @@ export async function resolveEnabledMailboxes(): Promise { } export async function hasConfiguredMailbox(): Promise { + if (isTestMode()) return true; const count = await prisma.mailboxAccount.count({ where: { enabled: true } }); if (count > 0) return true; const env = getEnv(); @@ -299,12 +334,18 @@ export async function resolveMailboxForTest( if (row.authType === "OAUTH") { accessToken = await resolveAccessToken(row); } - return { - id: row.id, - name: row.name, - idleEnabled: row.idleEnabled !== false, - config: rowToConfig(row, accessToken), - }; + try { + return { + id: row.id, + name: row.name, + idleEnabled: row.idleEnabled !== false, + config: rowToConfig(row, accessToken), + }; + } catch { + throw new Error( + "邮箱授权码无法解密。请打开该邮箱「编辑」,重新填写授权码并保存(不必去邮箱重新申请,除非授权码已失效)", + ); + } } export async function markMailboxTestResult( @@ -409,6 +450,7 @@ export async function upsertOAuthMailbox(input: { accessToken: string; refreshToken: string; expiresIn: number; + ownerUserId?: bigint | null; }): Promise { const existing = await prisma.mailboxAccount.findUnique({ where: { username: input.username.trim().toLowerCase() }, @@ -427,7 +469,15 @@ export async function upsertOAuthMailbox(input: { folder: "INBOX", enabled: true, idleEnabled: true, + ...(input.ownerUserId != null ? { ownerUserId: input.ownerUserId } : {}), }; + if ( + existing?.ownerUserId && + input.ownerUserId != null && + existing.ownerUserId !== input.ownerUserId + ) { + throw new Error("该邮箱已绑定其他客户"); + } const row = existing ? await prisma.mailboxAccount.update({ where: { id: existing.id }, data }) : await prisma.mailboxAccount.create({ data }); diff --git a/src/services/imap/mock-mail-server.ts b/src/services/imap/mock-mail-server.ts new file mode 100644 index 0000000..5c79d71 --- /dev/null +++ b/src/services/imap/mock-mail-server.ts @@ -0,0 +1,308 @@ +import fs from "fs"; +import path from "path"; +import type { + FetchedImapMessage, + ImapClientConfig, + ImapClientLike, +} from "@/services/imap/client"; + +export type MockImapFault = "none" | "auth_fail" | "timeout" | "empty"; + +const IMAP_FAULT_PATH = path.join(process.cwd(), "data", "test-mode-imap.json"); + +function readImapFault(): MockImapFault { + try { + const raw = JSON.parse(fs.readFileSync(IMAP_FAULT_PATH, "utf8")) as { + fault?: MockImapFault; + }; + return raw.fault || "none"; + } catch { + return "none"; + } +} + +function writeImapFault(fault: MockImapFault): void { + fs.mkdirSync(path.dirname(IMAP_FAULT_PATH), { recursive: true }); + fs.writeFileSync(IMAP_FAULT_PATH, JSON.stringify({ fault }, null, 2), "utf8"); +} + +export type MockMailAttachmentFixture = { + filename: string; + contentType?: string; + content: Buffer; +}; + +export type MockMailFixture = { + uid?: number; + /** 原始 EML;缺省时由 from/subject/text/attachments 生成 */ + source?: Buffer; + from?: string; + subject?: string; + text?: string; + date?: Date; + messageId?: string; + inReplyTo?: string; + references?: string[]; + attachments?: MockMailAttachmentFixture[]; + seen?: boolean; +}; + +type StoredMessage = FetchedImapMessage & { + seen: boolean; + date: Date; +}; + +function encodeHeaderUtf8(value: string): string { + if (/^[\x20-\x7E]*$/.test(value)) return value; + return `=?UTF-8?B?${Buffer.from(value, "utf8").toString("base64")}?=`; +} + +/** 构造可被 mailparser 解析的简单 MIME 邮件(含附件)。 */ +export function buildRawEml(input: { + from?: string; + subject?: string; + text?: string; + date?: Date; + messageId?: string; + inReplyTo?: string; + references?: string[]; + attachments?: MockMailAttachmentFixture[]; +}): Buffer { + const from = input.from || "sender@example.com"; + const subject = input.subject || "(no subject)"; + const date = (input.date || new Date()).toUTCString(); + const messageId = input.messageId || ``; + const boundary = `mock-${Math.random().toString(16).slice(2)}`; + const lines = [ + `From: ${from}`, + `To: inbox@test.local`, + `Subject: ${encodeHeaderUtf8(subject)}`, + `Date: ${date}`, + `Message-ID: ${messageId}`, + ]; + if (input.inReplyTo) lines.push(`In-Reply-To: ${input.inReplyTo}`); + if (input.references?.length) { + lines.push(`References: ${input.references.join(" ")}`); + } + lines.push("MIME-Version: 1.0"); + + const attachments = input.attachments || []; + if (attachments.length === 0) { + lines.push("Content-Type: text/plain; charset=utf-8"); + lines.push(""); + lines.push(input.text || ""); + return Buffer.from(lines.join("\r\n"), "utf8"); + } + + lines.push(`Content-Type: multipart/mixed; boundary="${boundary}"`); + lines.push(""); + lines.push(`--${boundary}`); + lines.push("Content-Type: text/plain; charset=utf-8"); + lines.push("Content-Transfer-Encoding: 8bit"); + lines.push(""); + lines.push(input.text || ""); + for (const att of attachments) { + const filename = att.filename.replace(/"/g, ""); + const ctype = att.contentType || "application/octet-stream"; + lines.push(`--${boundary}`); + lines.push(`Content-Type: ${ctype}; name="${filename}"`); + lines.push(`Content-Disposition: attachment; filename="${filename}"`); + lines.push("Content-Transfer-Encoding: base64"); + lines.push(""); + lines.push(att.content.toString("base64")); + } + lines.push(`--${boundary}--`); + lines.push(""); + return Buffer.from(lines.join("\r\n"), "utf8"); +} + +function toEnvelope(input: MockMailFixture, source: Buffer): FetchedImapMessage["envelope"] { + const date = input.date || new Date(); + const fromAddr = input.from || "sender@example.com"; + return { + date, + subject: input.subject, + messageId: input.messageId, + inReplyTo: input.inReplyTo, + from: [{ address: fromAddr, name: "" }], + to: [{ address: "inbox@test.local", name: "" }], + sourceSize: source.length, + } as FetchedImapMessage["envelope"]; +} + +export class MockImapAuthError extends Error { + constructor(message = "AUTHENTICATIONFAILED: Invalid credentials") { + super(message); + this.name = "MockImapAuthError"; + } +} + +export class MockImapTimeoutError extends Error { + code = "ETIMEOUT"; + constructor(message = "Socket timeout") { + super(message); + this.name = "Error"; + } +} + +class MockImapClient implements ImapClientLike { + private connected = false; + + constructor( + private readonly server: MockMailServer, + private readonly config: ImapClientConfig, + ) {} + + async connect(): Promise { + this.server.assertConnect(this.config); + this.connected = true; + } + + async fetchUnseen(): Promise { + this.assertConnected(); + const uids = this.server.searchUids({ unseenOnly: true }); + return this.server.fetchByUids(uids); + } + + async searchSince(since: Date): Promise { + this.assertConnected(); + return this.server.searchUids({ since }); + } + + async fetchByUids(uids: number[]): Promise { + this.assertConnected(); + return this.server.fetchByUids(uids); + } + + async countUnseen(): Promise { + this.assertConnected(); + return this.server.searchUids({ unseenOnly: true }).length; + } + + async markSeen(uid: number): Promise { + this.assertConnected(); + this.server.markSeen(uid); + } + + async disconnect(): Promise { + this.connected = false; + } + + private assertConnected(): void { + if (!this.connected) { + throw new Error("IMAP not connected"); + } + } +} + +/** + * 进程内 Mock 邮箱:可注入 EML/附件,可模拟鉴权失败、超时、空信箱。 + * 仅 TEST_MODE 下由 createImapClientFromConfig 使用。 + */ +export class MockMailServer { + private messages: StoredMessage[] = []; + private nextUid = 1; + + setFault(fault: MockImapFault): void { + writeImapFault(fault); + } + + getFault(): MockImapFault { + return readImapFault(); + } + + inject(fixture: MockMailFixture): FetchedImapMessage { + const uid = fixture.uid ?? this.nextUid; + this.nextUid = Math.max(this.nextUid, uid + 1); + const source = + fixture.source ?? + buildRawEml({ + from: fixture.from, + subject: fixture.subject, + text: fixture.text, + date: fixture.date, + messageId: fixture.messageId, + inReplyTo: fixture.inReplyTo, + references: fixture.references, + attachments: fixture.attachments, + }); + const stored: StoredMessage = { + uid, + source, + envelope: toEnvelope(fixture, source), + seen: Boolean(fixture.seen), + date: fixture.date || new Date(), + }; + this.messages.push(stored); + return { uid, source, envelope: stored.envelope }; + } + + injectMany(fixtures: MockMailFixture[]): FetchedImapMessage[] { + return fixtures.map((f) => this.inject(f)); + } + + clear(): void { + this.messages = []; + this.nextUid = 1; + writeImapFault("none"); + } + + listMessages(): FetchedImapMessage[] { + return this.messages.map(({ uid, source, envelope }) => ({ + uid, + source, + envelope, + })); + } + + assertConnect(_config: ImapClientConfig): void { + const fault = readImapFault(); + if (fault === "timeout") { + throw new MockImapTimeoutError(); + } + if (fault === "auth_fail") { + throw new MockImapAuthError(); + } + } + + searchUids(opts: { since?: Date; unseenOnly?: boolean }): number[] { + if (readImapFault() === "empty") return []; + return this.messages + .filter((m) => { + if (opts.unseenOnly && m.seen) return false; + if (opts.since && m.date < opts.since) return false; + return true; + }) + .map((m) => m.uid); + } + + fetchByUids(uids: number[]): FetchedImapMessage[] { + if (readImapFault() === "empty") return []; + const want = new Set(uids); + return this.messages + .filter((m) => want.has(m.uid)) + .map(({ uid, source, envelope }) => ({ uid, source, envelope })); + } + + markSeen(uid: number): void { + const row = this.messages.find((m) => m.uid === uid); + if (row) row.seen = true; + } + + createClient(config: ImapClientConfig): ImapClientLike { + return new MockImapClient(this, config); + } +} + +let singleton: MockMailServer | null = null; + +export function getMockMailServer(): MockMailServer { + if (!singleton) singleton = new MockMailServer(); + return singleton; +} + +export function resetMockMailServer(): MockMailServer { + writeImapFault("none"); + singleton = new MockMailServer(); + return singleton; +} diff --git a/src/services/imap/poller.ts b/src/services/imap/poller.ts index 5a80829..81cf558 100644 --- a/src/services/imap/poller.ts +++ b/src/services/imap/poller.ts @@ -7,6 +7,7 @@ import { } from "@/services/db-lock"; import { getEnv } from "@/lib/env"; import { logger } from "@/lib/logger"; +import { newTraceId, withLogContext } from "@/lib/log-context"; import { resolveThreadMeta } from "@/services/parse/thread"; import { ParsePipeline } from "@/services/parse/pipeline"; import { computeRawHash, sha256 } from "@/utils/hash"; @@ -82,12 +83,14 @@ export class ImapPoller { let processed = 0; let candidates = 0; + const mailboxErrors: Array<{ name: string; err: unknown }> = []; for (const box of mailboxes) { try { const r = await this.pollMailbox(box); processed += r.processed; candidates += r.candidates; } catch (err) { + mailboxErrors.push({ name: box.name, err }); logger.error( { err, mailbox: box.name }, "imap.poll_mailbox_isolated_fail", @@ -95,7 +98,40 @@ export class ImapPoller { } } - await recordImapPollSuccess(processed); + const okCount = mailboxes.length - mailboxErrors.length; + if (mailboxErrors.length && okCount === 0) { + const first = mailboxErrors[0]!; + const msg = + first.err instanceof Error + ? first.err.message || String(first.err) + : String(first.err); + await recordImapPollFailure( + new Error( + `邮箱「${mailboxErrors.map((e) => e.name).join("、")}」同步失败:${msg || "连接异常"}`, + ), + ); + emitResilience("imap.poll_fail", "error", { + err: msg, + failedMailboxes: mailboxErrors.map((e) => e.name), + okMailboxes: 0, + }); + } else { + // 有任一邮箱成功则清连续失败;个别失败只记日志,避免拖垮整轮同步告警 + if (mailboxErrors.length) { + emitResilience("imap.poll_partial_fail", "warn", { + failedMailboxes: mailboxErrors.map((e) => e.name), + okMailboxes: okCount, + err: mailboxErrors + .map((e) => + e.err instanceof Error + ? e.err.message || String(e.err) + : String(e.err), + ) + .join("; "), + }); + } + await recordImapPollSuccess(processed); + } return { processed, skipped: false as const, @@ -143,7 +179,9 @@ export class ImapPoller { await client.connect(); const since = startOfLookbackDay(env.IMAP_LOOKBACK_DAYS); const folder = box.config.folder ?? "INBOX"; - const allUids = await client.searchSince(since); + const allUids = await this.withTransientRetry("searchSince", () => + client.searchSince(since), + ); const known = await this.knownUids(box.id, folder, allUids); const pending = allUids @@ -166,7 +204,9 @@ export class ImapPoller { "imap.lookback_search", ); - const messages = await client.fetchByUids(toFetch); + const messages = await this.withTransientRetry("fetchByUids", () => + client.fetchByUids(toFetch), + ); const filterRules = await listEnabledImapFilterRules(); for (const msg of messages) { const handled = await this.processMessage( @@ -241,6 +281,8 @@ export class ImapPoller { references?: string; }, ): Promise { + const traceId = newTraceId(); + return withLogContext({ traceId, stage: "pull" }, async () => { let mailId: bigint | null = null; const mailboxAccountId = box.id; const mailboxName = box.name; @@ -346,6 +388,7 @@ export class ImapPoller { mailType: "UNKNOWN", rawHash, lastError: decision.reason || NON_BUSINESS_SKIP, + traceId, threadId: thread.threadId, inReplyTo: thread.inReplyTo, referencesHeader: thread.referencesHeader, @@ -385,6 +428,7 @@ export class ImapPoller { receivedAt: meta.receivedAt, status: "FETCHED", rawHash, + traceId, threadId: thread.threadId, inReplyTo: thread.inReplyTo, referencesHeader: thread.referencesHeader, @@ -393,6 +437,10 @@ export class ImapPoller { }); const newMailId = created.id; mailId = newMailId; + logger.info( + { mailId: String(newMailId), imapUid, traceId }, + "imap.mail_fetched", + ); const snapshot = await saveMailSnapshot(newMailId, source, parsed); @@ -474,10 +522,36 @@ export class ImapPoller { version: { increment: 1 }, }, }).catch(() => undefined); + await this.markSeenSafe(client, imapUid); + } else { + logger.warn({ imapUid }, "imap.process_failed_leave_unseen"); } - await this.markSeenSafe(client, imapUid); return false; } + }); + } + + private async withTransientRetry( + label: string, + fn: () => Promise, + ): Promise { + let last: unknown; + for (let attempt = 0; attempt < 3; attempt++) { + try { + return await fn(); + } catch (err) { + last = err; + const code = (err as { code?: string } | null)?.code; + const msg = err instanceof Error ? err.message : String(err); + const retry = + code === "ETIMEOUT" || + /ETIMEOUT|ETIMEDOUT|ECONNRESET|socket/i.test(msg); + if (!retry || attempt === 2) throw err; + logger.warn({ err, attempt: attempt + 1, label }, "imap.op_retry"); + await new Promise((r) => setTimeout(r, 400 * 2 ** attempt)); + } + } + throw last; } private async markSeenSafe(client: ImapClientLike, uid: number): Promise { diff --git a/src/services/imap/runtime-status.ts b/src/services/imap/runtime-status.ts index db9a88e..80478f3 100644 --- a/src/services/imap/runtime-status.ts +++ b/src/services/imap/runtime-status.ts @@ -1,5 +1,6 @@ import fs from "fs/promises"; import path from "path"; +import { emitAlert } from "@/services/alert/webhook"; const STATUS_PATH = path.join(process.cwd(), "data", "imap-runtime.json"); @@ -88,4 +89,13 @@ export async function recordImapPollFailure(err: unknown): Promise { lastProcessed: prev.lastProcessed, updatedAt: now, }); + const consecutiveFail = prev.consecutiveFail + 1; + if (consecutiveFail >= FAIL_ALERT_THRESHOLD) { + void emitAlert({ + key: "imap.consecutive_fail", + title: "IMAP 连续拉取失败", + severity: "error", + fields: { consecutiveFail, lastError: message }, + }); + } } diff --git a/src/services/import/auto-exec.ts b/src/services/import/auto-exec.ts index 5f24532..470514c 100644 --- a/src/services/import/auto-exec.ts +++ b/src/services/import/auto-exec.ts @@ -1,5 +1,6 @@ import { prisma } from "@/services/db"; import { getEnv } from "@/lib/env"; +import { runWithMailCcOwner } from "@/lib/cc-owner"; import { logger } from "@/lib/logger"; import { writeAudit } from "@/services/audit"; import { assertTransition } from "@/services/state-machine"; @@ -42,6 +43,14 @@ export async function runAutoExec(mailId: bigint): Promise<{ ok: boolean; status: string; error?: string; +}> { + return runWithMailCcOwner(mailId, () => runAutoExecInner(mailId)); +} + +async function runAutoExecInner(mailId: bigint): Promise<{ + ok: boolean; + status: string; + error?: string; }> { const mail = await prisma.mailMessage.findUnique({ where: { id: mailId }, @@ -72,7 +81,7 @@ export async function runAutoExec(mailId: bigint): Promise<{ const header = mail.parseResult?.containerHeader as | { F_ContainerNo?: string; F_Instruction?: string } | undefined; - const shipments = (mail.parseResult?.shipments || []) as ShipmentRow[]; + const shipments = ((mail.parseResult?.shipments || []) as unknown) as ShipmentRow[]; const result = await executeInstructionWrite(kind, { mailId: mailId.toString(), diff --git a/src/services/import/compensation.ts b/src/services/import/compensation.ts index d2b297a..dff8978 100644 --- a/src/services/import/compensation.ts +++ b/src/services/import/compensation.ts @@ -1,6 +1,8 @@ import { writeAudit } from "@/services/audit"; import { getEnv } from "@/lib/env"; +import { runWithMailCcOwner } from "@/lib/cc-owner"; import { logger } from "@/lib/logger"; +import { emitAlert } from "@/services/alert/webhook"; import { emitResilience } from "@/lib/resilience-metrics"; import { prisma } from "@/services/db"; import { getContainerListFromCc } from "@/services/cc/conflict"; @@ -139,6 +141,21 @@ export interface CompensationRetryResult { export async function executeCompensationRetry( input: CompensationRetryInput, +): Promise { + const preview = await prisma.importCompensation.findUnique({ + where: { id: input.compensationId }, + select: { import: { select: { mailId: true } } }, + }); + if (!preview) { + throw new ImportRetryError("NOT_FOUND", "补偿记录不存在"); + } + return runWithMailCcOwner(preview.import.mailId, () => + executeCompensationRetryInner(input), + ); +} + +async function executeCompensationRetryInner( + input: CompensationRetryInput, ): Promise { const compensation = await prisma.importCompensation.findUnique({ where: { id: input.compensationId }, @@ -205,6 +222,16 @@ export async function executeCompensationRetry( mailId: String(importRec.mailId), retryCount: compensation.retryCount, }); + void emitAlert({ + key: `compensation.exhausted.${importRec.id}`, + title: "导入补偿重试已耗尽", + severity: "error", + fields: { + importId: String(importRec.id), + mailId: String(importRec.mailId), + retryCount: compensation.retryCount, + }, + }); throw new ImportRetryError("EXHAUSTED", "补偿重试已耗尽"); } @@ -344,6 +371,16 @@ export async function executeCompensationRetry( retryCount: nextRetryCount, error: saveResult.error, }); + void emitAlert({ + key: `compensation.exhausted.${importRec.id}`, + title: "导入补偿重试已耗尽", + severity: "error", + fields: { + importId: String(importRec.id), + mailId: String(importRec.mailId), + error: saveResult.error, + }, + }); throw new ImportRetryError("EXHAUSTED", "补偿重试已耗尽"); } diff --git a/src/services/import/confirm-batch-transfer.ts b/src/services/import/confirm-batch-transfer.ts index eaad8af..fb04563 100644 --- a/src/services/import/confirm-batch-transfer.ts +++ b/src/services/import/confirm-batch-transfer.ts @@ -1,5 +1,7 @@ import { prisma } from "@/services/db"; import { writeAudit } from "@/services/audit"; +import { runWithMailCcOwner } from "@/lib/cc-owner"; +import { runWithMailTrace } from "@/lib/mail-trace"; import { assertTransition } from "@/services/state-machine"; import { evaluateTransferGate } from "@/services/cc/container-status"; import { @@ -7,12 +9,17 @@ import { type BatchTransferPayload, } from "@/services/cc/batch-transfer"; import type { MailStatus } from "@/types/mail"; +import { + markInstructionImportedAndResolveMail, + validateInstructionImportable, +} from "@/services/import/instruction-import-state"; export class TransferConfirmError extends Error { constructor( public code: string, message: string, public httpStatus = 400, + public details?: unknown, ) { super(message); this.name = "TransferConfirmError"; @@ -23,15 +30,35 @@ export async function confirmBatchTransfer(input: { mailId: bigint; version: number; actor: string; + instructionId?: string; containerNo: string; payload: BatchTransferPayload; }): Promise<{ externalId: string | null; mode: string }> { - const mail = await prisma.mailMessage.findUnique({ - where: { id: input.mailId }, + return runWithMailCcOwner(input.mailId, () => + runWithMailTrace(input.mailId, "submit", async () => { + const checked = await validateInstructionImportable({ + mailId: input.mailId, + instructionId: input.instructionId, + allowedKinds: ["transfer"], + allowedStatuses: ["PENDING_CONFIRM", "FAILED", "PARTIAL_SUCCESS"], }); - if (!mail) throw new TransferConfirmError("NOT_FOUND", "邮件不存在", 404); + if (checked.error === "NOT_FOUND") { + throw new TransferConfirmError("NOT_FOUND", "邮件不存在", 404); + } + const mail = checked.mail; if (mail.version !== input.version) { - throw new TransferConfirmError("VERSION_CONFLICT", "版本冲突", 409); + throw new TransferConfirmError("VERSION_CONFLICT", "版本冲突", 409, { + server_version: mail.version, + }); + } + if (checked.error === "BAD_STATUS") { + throw new TransferConfirmError("BAD_STATUS", `状态 ${mail.status} 不可确认`, 409); + } + if (checked.error === "ALREADY_IMPORTED") { + throw new TransferConfirmError("ALREADY_IMPORTED", "该转仓指令已导入", 409); + } + if (checked.error === "INSTRUCTION_NOT_FOUND" || checked.error === "FORBIDDEN_KIND") { + throw new TransferConfirmError("FORBIDDEN", "未找到可执行的转仓指令", 403); } if (mail.mailType !== "TRANSFER") { throw new TransferConfirmError( @@ -40,13 +67,6 @@ export async function confirmBatchTransfer(input: { 403, ); } - if (mail.status !== "PENDING_CONFIRM" && mail.status !== "FAILED") { - throw new TransferConfirmError( - "BAD_STATUS", - `状态 ${mail.status} 不可确认`, - 409, - ); - } const gate = await evaluateTransferGate(input.containerNo); if (!gate.canTransfer) { @@ -85,30 +105,33 @@ export async function confirmBatchTransfer(input: { }); if (result.ok) { - assertTransition("IMPORTING", "SUCCESS"); - await prisma.mailMessage.update({ - where: { id: input.mailId }, - data: { - status: "SUCCESS", - lastError: result.mode === "mock" ? "XFER_MOCK_OK" : null, - version: { increment: 1 }, - }, + await markInstructionImportedAndResolveMail({ + mailId: input.mailId, + instructionId: checked.unit.id, + actor: input.actor, + mode: result.mode, + externalId: result.externalId || null, + successLastError: result.mode === "mock" ? "XFER_MOCK_OK" : null, }); return { externalId: result.externalId || null, mode: result.mode }; } assertTransition("IMPORTING", "FAILED"); - await prisma.mailMessage.update({ + const failed = await prisma.mailMessage.update({ where: { id: input.mailId }, data: { status: "FAILED", lastError: (result.error || "XFER_FAILED").slice(0, 1000), version: { increment: 1 }, }, + select: { version: true }, }); throw new TransferConfirmError( "CC_FAILED", result.error || "批量转仓失败", 502, + { server_version: failed.version }, + ); + }), ); } diff --git a/src/services/import/confirm-do-upload.ts b/src/services/import/confirm-do-upload.ts index 5d5f2cb..d5d45a7 100644 --- a/src/services/import/confirm-do-upload.ts +++ b/src/services/import/confirm-do-upload.ts @@ -2,6 +2,8 @@ import fs from "fs/promises"; import { prisma } from "@/services/db"; import { writeAudit } from "@/services/audit"; import { resolveDataFile } from "@/lib/safe-path"; +import { runWithMailCcOwner } from "@/lib/cc-owner"; +import { runWithMailTrace } from "@/lib/mail-trace"; import { assertTransition } from "@/services/state-machine"; import { saveContainerDoFile, @@ -10,12 +12,17 @@ import { import { getContainerListFromCc } from "@/services/cc/conflict"; import { isDoAttachmentFilename } from "@/services/parse/do-upload-extract"; import type { MailStatus } from "@/types/mail"; +import { + markInstructionImportedAndResolveMail, + validateInstructionImportable, +} from "@/services/import/instruction-import-state"; export class DoConfirmError extends Error { constructor( public code: string, message: string, public httpStatus = 400, + public details?: unknown, ) { super(message); this.name = "DoConfirmError"; @@ -26,10 +33,58 @@ function canDoUpload(status: string): boolean { return ( status === "PENDING_CONFIRM" || status === "FAILED" || + status === "PARTIAL_SUCCESS" || status === "SUCCESS" ); } +function isUsableCcTaskId(id: string | null | undefined): boolean { + const t = (id || "").trim(); + if (!t) return false; + if (/^mock-task-/i.test(t)) return false; + return true; +} + +function pickTaskIdFromRows( + items: Array<{ + F_Id?: string; + F_ContainerTaskId?: string; + F_ContainerNo?: string; + }>, + containerNo: string, +): string { + const cn = containerNo.trim().toUpperCase(); + const exact = items.filter( + (row) => String(row.F_ContainerNo || "").trim().toUpperCase() === cn, + ); + for (const row of exact) { + const id = (row.F_ContainerTaskId || row.F_Id || "").trim(); + if (isUsableCcTaskId(id)) return id; + } + return ""; +} + +/** + * DO 必须挂到 CC 里已存在的集装箱。 + * 以 GetContainerList 命中为准;查不到柜时不得编造 mock-task 继续上传。 + */ +export function resolveDoUploadTaskId(input: { + importExternalId?: string | null; + listItems: Array<{ + F_Id?: string; + F_ContainerTaskId?: string; + F_ContainerNo?: string; + }>; + containerNo: string; +}): { ok: true; taskId: string } | { ok: false } { + const fromList = pickTaskIdFromRows(input.listItems, input.containerNo); + if (fromList) return { ok: true, taskId: fromList }; + if (isUsableCcTaskId(input.importExternalId)) { + return { ok: true, taskId: input.importExternalId!.trim() }; + } + return { ok: false }; +} + /** 从本地 mail 附件表加载 DO 二进制 */ async function loadDoFileParts( mailId: bigint, @@ -77,6 +132,7 @@ export async function confirmDoUpload(input: { mailId: bigint; version: number; actor: string; + instructionId?: string; containerNo: string; containerTaskId?: string; filenames?: string[]; @@ -86,6 +142,17 @@ export async function confirmDoUpload(input: { containerTaskId: string; uploadedFileIds?: string[]; }> { + return runWithMailCcOwner(input.mailId, () => + runWithMailTrace(input.mailId, "submit", async () => { + const checked = await validateInstructionImportable({ + mailId: input.mailId, + instructionId: input.instructionId, + allowedKinds: ["do_upload"], + allowedStatuses: ["PENDING_CONFIRM", "FAILED", "SUCCESS", "PARTIAL_SUCCESS"], + }); + if (checked.error === "NOT_FOUND") { + throw new DoConfirmError("NOT_FOUND", "邮件不存在", 404); + } const mail = await prisma.mailMessage.findUnique({ where: { id: input.mailId }, include: { @@ -97,6 +164,15 @@ export async function confirmDoUpload(input: { }, }); if (!mail) throw new DoConfirmError("NOT_FOUND", "邮件不存在", 404); + if (checked.error === "BAD_STATUS") { + throw new DoConfirmError("BAD_STATUS", `状态 ${mail.status} 不可确认上传 DO`, 409); + } + if (checked.error === "ALREADY_IMPORTED") { + throw new DoConfirmError("ALREADY_IMPORTED", "该 DO 指令已导入", 409); + } + if (checked.error === "INSTRUCTION_NOT_FOUND" || checked.error === "FORBIDDEN_KIND") { + throw new DoConfirmError("FORBIDDEN", "未找到可执行的 DO 指令", 403); + } if (mail.mailType !== "DO_UPLOAD" && mail.mailType !== "NEW_CONTAINER") { throw new DoConfirmError( @@ -116,31 +192,23 @@ export async function confirmDoUpload(input: { const cn = input.containerNo.trim().toUpperCase(); if (!cn) throw new DoConfirmError("VALIDATION", "柜号必填"); - let taskId = (input.containerTaskId || "").trim(); - if (!taskId && mail.imports[0]?.externalId) { - taskId = mail.imports[0].externalId; + const list = await getContainerListFromCc(cn); + if (!list.ok) { + throw new DoConfirmError("CC_LOOKUP", list.error, 502); } - if (!taskId) { - const list = await getContainerListFromCc(cn); - if (!list.ok) { - throw new DoConfirmError("CC_LOOKUP", list.error, 502); - } - const hit = list.items[0] as { - F_Id?: string; - F_ContainerTaskId?: string; - }; - taskId = hit?.F_ContainerTaskId || hit?.F_Id || ""; - if (!taskId && list.items.length === 0) { - taskId = `mock-task-${cn}`; - } - if (!taskId) { - throw new DoConfirmError( - "NO_CONTAINER", - "CC 未找到该柜,无法挂 DO(请先完成预报导入)", - 404, - ); - } + const resolved = resolveDoUploadTaskId({ + importExternalId: mail.imports[0]?.externalId, + listItems: list.items, + containerNo: cn, + }); + if (!resolved.ok) { + throw new DoConfirmError( + "NO_CONTAINER", + "CC 中还没有该柜,无法上传 DO。请先确认导入预报、创建集装箱后再传 DO。", + 409, + ); } + const taskId = resolved.taskId; const files = await loadDoFileParts(input.mailId, input.filenames); if (!files.length) { @@ -193,19 +261,16 @@ export async function confirmDoUpload(input: { }); if (result.ok) { - if (!wasSuccess) { - assertTransition("IMPORTING", "SUCCESS"); - } - await prisma.mailMessage.update({ - where: { id: input.mailId }, - data: { - status: "SUCCESS", - lastError: - result.mode === "mock" - ? "DO_MOCK_OK" - : `DO 已上传: ${(result.uploadedFileIds || []).join(",") || result.folderId || "ok"}`, - version: { increment: 1 }, - }, + await markInstructionImportedAndResolveMail({ + mailId: input.mailId, + instructionId: checked.unit.id, + actor: input.actor, + mode: result.mode, + externalId: taskId, + successLastError: + result.mode === "mock" + ? "DO_MOCK_OK" + : `DO 已上传: ${(result.uploadedFileIds || []).join(",") || result.folderId || "ok"}`, }); return { folderId: result.folderId || null, @@ -216,24 +281,32 @@ export async function confirmDoUpload(input: { } if (wasSuccess) { - await prisma.mailMessage.update({ + const failed = await prisma.mailMessage.update({ where: { id: input.mailId }, data: { lastError: `DO 失败: ${(result.error || "DO_FAILED").slice(0, 900)}`, version: { increment: 1 }, }, + select: { version: true }, + }); + throw new DoConfirmError("CC_FAILED", result.error || "DO 上传失败", 502, { + server_version: failed.version, }); - throw new DoConfirmError("CC_FAILED", result.error || "DO 上传失败", 502); } assertTransition("IMPORTING", "FAILED"); - await prisma.mailMessage.update({ + const failed = await prisma.mailMessage.update({ where: { id: input.mailId }, data: { status: "FAILED", lastError: (result.error || "DO_FAILED").slice(0, 1000), version: { increment: 1 }, }, + select: { version: true }, }); - throw new DoConfirmError("CC_FAILED", result.error || "DO 上传失败", 502); + throw new DoConfirmError("CC_FAILED", result.error || "DO 上传失败", 502, { + server_version: failed.version, + }); + }), + ); } diff --git a/src/services/import/confirm-work-order.ts b/src/services/import/confirm-work-order.ts index 03cd0db..50184c7 100644 --- a/src/services/import/confirm-work-order.ts +++ b/src/services/import/confirm-work-order.ts @@ -1,17 +1,24 @@ import { prisma } from "@/services/db"; import { writeAudit } from "@/services/audit"; +import { runWithMailCcOwner } from "@/lib/cc-owner"; +import { runWithMailTrace } from "@/lib/mail-trace"; import { assertTransition } from "@/services/state-machine"; import { saveCustomerMessage, type CustomerMessageEntity, } from "@/services/cc/customer-message"; import type { MailStatus } from "@/types/mail"; +import { + markInstructionImportedAndResolveMail, + validateInstructionImportable, +} from "@/services/import/instruction-import-state"; export class WorkOrderConfirmError extends Error { constructor( public code: string, message: string, public httpStatus = 400, + public details?: unknown, ) { super(message); this.name = "WorkOrderConfirmError"; @@ -22,14 +29,34 @@ export async function confirmWorkOrder(input: { mailId: bigint; version: number; actor: string; + instructionId?: string; entity: CustomerMessageEntity; }): Promise<{ externalId: string | null; mode: string }> { - const mail = await prisma.mailMessage.findUnique({ - where: { id: input.mailId }, + return runWithMailCcOwner(input.mailId, () => + runWithMailTrace(input.mailId, "submit", async () => { + const checked = await validateInstructionImportable({ + mailId: input.mailId, + instructionId: input.instructionId, + allowedKinds: ["work_order", "transfer"], + allowedStatuses: ["PENDING_CONFIRM", "FAILED", "PARTIAL_SUCCESS"], }); - if (!mail) throw new WorkOrderConfirmError("NOT_FOUND", "邮件不存在", 404); + if (checked.error === "NOT_FOUND") { + throw new WorkOrderConfirmError("NOT_FOUND", "邮件不存在", 404); + } + const mail = checked.mail; if (mail.version !== input.version) { - throw new WorkOrderConfirmError("VERSION_CONFLICT", "版本冲突", 409); + throw new WorkOrderConfirmError("VERSION_CONFLICT", "版本冲突", 409, { + server_version: mail.version, + }); + } + if (checked.error === "BAD_STATUS") { + throw new WorkOrderConfirmError("BAD_STATUS", `状态 ${mail.status} 不可确认`, 409); + } + if (checked.error === "ALREADY_IMPORTED") { + throw new WorkOrderConfirmError("ALREADY_IMPORTED", "该指令已导入", 409); + } + if (checked.error === "INSTRUCTION_NOT_FOUND" || checked.error === "FORBIDDEN_KIND") { + throw new WorkOrderConfirmError("FORBIDDEN", "未找到可执行的工单指令", 403); } if ( mail.mailType !== "WORK_ORDER" && @@ -43,13 +70,6 @@ export async function confirmWorkOrder(input: { 403, ); } - if (mail.status !== "PENDING_CONFIRM" && mail.status !== "FAILED") { - throw new WorkOrderConfirmError( - "BAD_STATUS", - `状态 ${mail.status} 不可确认`, - 409, - ); - } if (!input.entity.F_MessageType?.trim()) { throw new WorkOrderConfirmError("VALIDATION", "留言类型必填"); } @@ -80,30 +100,33 @@ export async function confirmWorkOrder(input: { }); if (result.ok) { - assertTransition("IMPORTING", "SUCCESS"); - await prisma.mailMessage.update({ - where: { id: input.mailId }, - data: { - status: "SUCCESS", - lastError: result.mode === "mock" ? "WO_MOCK_OK" : null, - version: { increment: 1 }, - }, + await markInstructionImportedAndResolveMail({ + mailId: input.mailId, + instructionId: checked.unit.id, + actor: input.actor, + mode: result.mode, + externalId: result.externalId || null, + successLastError: result.mode === "mock" ? "WO_MOCK_OK" : null, }); return { externalId: result.externalId || null, mode: result.mode }; } assertTransition("IMPORTING", "FAILED"); - await prisma.mailMessage.update({ + const failed = await prisma.mailMessage.update({ where: { id: input.mailId }, data: { status: "FAILED", lastError: (result.error || "WO_FAILED").slice(0, 1000), version: { increment: 1 }, }, + select: { version: true }, }); throw new WorkOrderConfirmError( "CC_FAILED", result.error || "工单写入失败", 502, + { server_version: failed.version }, + ); + }), ); } diff --git a/src/services/import/confirm.ts b/src/services/import/confirm.ts index 18b1f7e..f21ff25 100644 --- a/src/services/import/confirm.ts +++ b/src/services/import/confirm.ts @@ -1,6 +1,8 @@ import { writeAudit } from "@/services/audit"; import { getEnv } from "@/lib/env"; -import { canEnterForecastConfirm } from "@/services/cc/forecast-confirm-gate"; +import { logger } from "@/lib/logger"; +import { runWithMailCcOwner } from "@/lib/cc-owner"; +import { runWithMailTrace } from "@/lib/mail-trace"; import { prisma } from "@/services/db"; import { acquireContainerActiveLock, @@ -10,7 +12,9 @@ import { import { buildSaveContainerEntity, saveContainer, + shouldEnqueueCompensation, } from "@/services/cc/save-container"; +import { emitAlert } from "@/services/alert/webhook"; import type { ContainerImport } from "@prisma/client"; import type { ContainerHeader, @@ -25,6 +29,11 @@ import { noteImportContention, } from "@/lib/resilience-metrics"; import { v4 as uuidv4 } from "uuid"; +import { + markInstructionImportedAndResolveMail, + validateInstructionImportable, +} from "@/services/import/instruction-import-state"; +import { fillShipmentFbaCodes } from "@/services/parse/normalize-fba-code"; /** 仅允许白名单柜头字段从客户端覆盖,防止 mass assignment 窜改 CC payload */ function pickContainerHeaderPatch( @@ -108,6 +117,7 @@ export interface ConfirmImportInput { mailId: bigint; actor: string; version: number; + instructionId?: string; idempotencyKey: string; F_TransMode: TransMode; F_OperationType: OperationType; @@ -121,6 +131,19 @@ export interface ConfirmImportResult { mailStatus: MailStatus; imports: ContainerImport[]; acceptedCount: number; + version: number; +} + +async function attachMailVersion( + mailId: bigint, + result: Omit, + fallbackVersion?: number, +): Promise { + const row = await prisma.mailMessage.findUnique({ + where: { id: mailId }, + select: { version: true }, + }); + return { ...result, version: row?.version ?? fallbackVersion ?? 0 }; } async function createCompensation( @@ -146,9 +169,19 @@ async function createCompensation( export class ConfirmImport { static async execute(input: ConfirmImportInput): Promise { + return runWithMailCcOwner(input.mailId, () => + runWithMailTrace(input.mailId, "submit", () => + ConfirmImport.executeUntraced(input), + ), + ); + } + + private static async executeUntraced( + input: ConfirmImportInput, + ): Promise { let mail = await prisma.mailMessage.findUnique({ where: { id: input.mailId }, - include: { parseResult: true }, + include: { parseResult: true, attachments: true }, }); if (!mail) { @@ -162,11 +195,11 @@ export class ConfirmImport { orderBy: { id: "desc" }, }); if (done) { - return { + return attachMailVersion(input.mailId, { mailStatus: "SUCCESS", imports: [done], acceptedCount: 0, - }; + }, mail.version); } } @@ -183,35 +216,63 @@ export class ConfirmImport { }); mail = await prisma.mailMessage.findUniqueOrThrow({ where: { id: input.mailId }, - include: { parseResult: true }, + include: { parseResult: true, attachments: true }, }); } else { throw new ConfirmImportError("ALREADY_IMPORTING", "正在导入中,请稍候再试"); } } + if (!mail) { + throw new ConfirmImportError("NOT_FOUND", "邮件不存在"); + } + const shipmentRows = mail.parseResult - ? (mail.parseResult.shipments as unknown as ShipmentRow[]) + ? (fillShipmentFbaCodes( + mail.parseResult.shipments, + ) as unknown as ShipmentRow[]) : []; const validShipmentCount = shipmentRows.filter( (s) => s.row_status === "VALID", ).length; + const checked = await validateInstructionImportable({ + mailId: input.mailId, + instructionId: input.instructionId, + allowedKinds: ["forecast"], + allowedStatuses: ["PENDING_CONFIRM", "FAILED", "PARTIAL_SUCCESS"], + }); + if (checked?.error === "ALREADY_IMPORTED") { + throw new ConfirmImportError("FORBIDDEN", "该预报指令已导入"); + } if ( - !canEnterForecastConfirm({ - mailType: mail.mailType, - status: mail.status, - validShipmentCount, - }) + checked?.error === "INSTRUCTION_NOT_FOUND" || + checked?.error === "FORBIDDEN_KIND" ) { + throw new ConfirmImportError("FORBIDDEN", "未找到可执行的预报指令"); + } + if (checked?.error === "BAD_STATUS") { + throw new ConfirmImportError( + "ILLEGAL_TRANSITION", + `状态 ${mail.status} 不可导入`, + ); + } + + const containerNoForGate = ( + mail.parseResult?.containerHeader as { F_ContainerNo?: string } | null + )?.F_ContainerNo || ""; + + if (validShipmentCount < 1 && !containerNoForGate.trim()) { throw new ConfirmImportError( "FORBIDDEN", - mail.mailType === "DO_UPLOAD" && validShipmentCount < 1 - ? "本封无有效货件,不可确认预报导入" - : "当前邮件不可确认预报导入", + "本封无有效货件,不可确认预报导入", ); } + const forecastInstructionId = + input.instructionId || + ("unit" in checked && checked.unit ? checked.unit.id : undefined); + // 韧性 §2.5/8.3:客户端 version 必须与库一致,避免用旧页覆盖并发结果 if (input.version !== mail.version) { noteImportContention(input.mailId); @@ -228,7 +289,11 @@ export class ConfirmImport { ); } - if (mail.status !== "PENDING_CONFIRM" && mail.status !== "FAILED") { + if ( + mail.status !== "PENDING_CONFIRM" && + mail.status !== "FAILED" && + mail.status !== "PARTIAL_SUCCESS" + ) { throw new ConfirmImportError( "ILLEGAL_TRANSITION", `状态 ${mail.status} 不可导入`, @@ -239,7 +304,9 @@ export class ConfirmImport { ? { container_header: mail.parseResult .containerHeader as unknown as ContainerHeader, - shipments: mail.parseResult.shipments as unknown as ShipmentRow[], + shipments: fillShipmentFbaCodes( + mail.parseResult.shipments, + ) as unknown as ShipmentRow[], } : null; @@ -255,7 +322,14 @@ export class ConfirmImport { throw new ConfirmImportError("INVALID_ROWS", "勾选含 INVALID 行"); } - if (selected.length === 0) { + const containerNoPreview = ( + parseResult.container_header.F_ContainerNo || "" + ).toUpperCase(); + // 仅柜头预报:允许空货件;若解析结果里有有效行则必须至少勾选一行 + if (selected.length === 0 && validShipmentCount > 0) { + throw new ConfirmImportError("INVALID_ROWS", "请至少勾选一行有效货件"); + } + if (selected.length === 0 && !containerNoPreview) { throw new ConfirmImportError("INVALID_ROWS", "请至少勾选一行有效货件"); } @@ -297,16 +371,28 @@ export class ConfirmImport { }, }); if (existingImport?.status === "SUCCESS") { - // mail 已收窄为可导入态,对齐幂等成功后的邮件状态 - await prisma.mailMessage.update({ - where: { id: input.mailId }, - data: { status: "SUCCESS", lastError: null }, - }); - return { - mailStatus: "SUCCESS", + let mailStatus: MailStatus = "SUCCESS"; + if (forecastInstructionId) { + mailStatus = + (await markInstructionImportedAndResolveMail({ + mailId: input.mailId, + instructionId: forecastInstructionId, + actor: input.actor, + mode: "idempotent", + externalId: existingImport.externalId, + successLastError: null, + })) || "SUCCESS"; + } else { + await prisma.mailMessage.update({ + where: { id: input.mailId }, + data: { status: "SUCCESS", lastError: null }, + }); + } + return attachMailVersion(input.mailId, { + mailStatus, imports: [existingImport], acceptedCount: selected.length, - }; + }); } const conflict = await checkContainerConflict({ @@ -346,7 +432,7 @@ export class ConfirmImport { const cas = await prisma.mailMessage.updateMany({ where: { id: input.mailId, - status: { in: ["PENDING_CONFIRM", "FAILED"] }, + status: { in: ["PENDING_CONFIRM", "FAILED", "PARTIAL_SUCCESS"] }, version: input.version, }, data: { @@ -372,11 +458,11 @@ export class ConfirmImport { }, }); if (done) { - return { + return attachMailVersion(input.mailId, { mailStatus: "SUCCESS", imports: [done], acceptedCount: selected.length, - }; + }, current.version); } } if (current.status === "IMPORTING") { @@ -450,10 +536,23 @@ export class ConfirmImport { }, }); - await prisma.mailMessage.update({ - where: { id: input.mailId }, - data: { status: "SUCCESS", lastError: null }, - }); + let mailStatus: MailStatus = "SUCCESS"; + if (forecastInstructionId) { + mailStatus = + (await markInstructionImportedAndResolveMail({ + mailId: input.mailId, + instructionId: forecastInstructionId, + actor: input.actor, + mode: "live", + externalId: saveResult.externalId, + successLastError: null, + })) || "SUCCESS"; + } else { + await prisma.mailMessage.update({ + where: { id: input.mailId }, + data: { status: "SUCCESS", lastError: null }, + }); + } await writeAudit({ actor: input.actor, @@ -467,11 +566,11 @@ export class ConfirmImport { }, }); - return { - mailStatus: "SUCCESS", + return attachMailVersion(input.mailId, { + mailStatus, imports: [importRecord], acceptedCount: selected.length, - }; + }); } const importStatus = saveResult.status; @@ -487,6 +586,24 @@ export class ConfirmImport { if (importStatus === "TIMEOUT_UNKNOWN") { await createCompensation(importRecord.id, "TIMEOUT_UNKNOWN"); + } else { + const reason = shouldEnqueueCompensation(saveResult); + if (reason) { + await createCompensation(importRecord.id, reason); + } + } + + if (/Unauthorized|401/i.test(saveResult.error || "")) { + void emitAlert({ + key: "cc.auth_expired", + title: "CC 鉴权失效", + severity: "error", + fields: { + mailId: String(input.mailId), + containerNo, + error: saveResult.error, + }, + }); } await releaseContainerActiveLock(containerNo); @@ -512,11 +629,21 @@ export class ConfirmImport { }, }); - return { + logger.info( + { + mailId: String(input.mailId), + containerNo, + importStatus, + error: saveResult.error, + }, + "import.submit_failed", + ); + + return attachMailVersion(input.mailId, { mailStatus, imports: [importRecord], acceptedCount: selected.length, - }; + }); } } diff --git a/src/services/import/instruction-import-state.ts b/src/services/import/instruction-import-state.ts new file mode 100644 index 0000000..4e108f2 --- /dev/null +++ b/src/services/import/instruction-import-state.ts @@ -0,0 +1,94 @@ +import { prisma } from "@/services/db"; +import type { Prisma } from "@prisma/client"; +import { assertTransition } from "@/services/state-machine"; +import { + listActionableInstructionUnits, + markInstructionImported, + resolveMailStatusAfterInstructionImport, +} from "@/services/parse/instruction-execution-state"; +import type { MailStatus } from "@/types/mail"; + +export async function validateInstructionImportable(input: { + mailId: bigint; + instructionId?: string; + allowedKinds: string[]; + allowedStatuses: MailStatus[]; +}) { + const mail = await prisma.mailMessage.findUnique({ + where: { id: input.mailId }, + include: { attachments: true, parseResult: true }, + }); + if (!mail) return { error: "NOT_FOUND" as const }; + if (!input.allowedStatuses.includes(mail.status as MailStatus)) { + return { error: "BAD_STATUS" as const, mail }; + } + const lineage = mail.parseResult?.lineage || {}; + const units = listActionableInstructionUnits({ + subject: mail.subject, + body: mail.bodyText, + filenames: mail.attachments.map((a) => a.filename), + lineage, + }); + const unit = input.instructionId + ? units.find((u) => u.id === input.instructionId) + : units.find((u) => input.allowedKinds.includes(u.uiKind)); + if (!unit) { + return { error: "INSTRUCTION_NOT_FOUND" as const, mail }; + } + if (!input.allowedKinds.includes(unit.uiKind)) { + return { error: "FORBIDDEN_KIND" as const, mail, unit }; + } + const state = ( + (lineage as { instruction_state?: Record }) + ?.instruction_state || {} + )[unit.id]; + if (state?.status === "IMPORTED") { + return { error: "ALREADY_IMPORTED" as const, mail, unit }; + } + return { mail, unit, lineage }; +} + +export async function markInstructionImportedAndResolveMail(input: { + mailId: bigint; + instructionId: string; + actor: string; + mode: string; + externalId?: string | null; + successLastError?: string | null; +}): Promise { + const mail = await prisma.mailMessage.findUnique({ + where: { id: input.mailId }, + include: { attachments: true, parseResult: true }, + }); + if (!mail || !mail.parseResult) return null; + const lineage = markInstructionImported(mail.parseResult.lineage, input.instructionId, { + status: "IMPORTED", + importedAt: new Date().toISOString(), + importedBy: input.actor, + mode: input.mode, + externalId: input.externalId || null, + }); + const status = resolveMailStatusAfterInstructionImport({ + currentStatus: mail.status as MailStatus, + subject: mail.subject, + body: mail.bodyText, + filenames: mail.attachments.map((a) => a.filename), + lineage, + }); + assertTransition("IMPORTING", status); + await prisma.$transaction(async (tx) => { + await tx.parseResult.update({ + where: { mailId: input.mailId }, + data: { lineage: lineage as unknown as Prisma.InputJsonValue }, + }); + await tx.mailMessage.update({ + where: { id: input.mailId }, + data: { + status, + lastError: input.successLastError ?? null, + version: { increment: 1 }, + }, + }); + }); + return status; +} diff --git a/src/services/mail/delete-mails.ts b/src/services/mail/delete-mails.ts new file mode 100644 index 0000000..c951302 --- /dev/null +++ b/src/services/mail/delete-mails.ts @@ -0,0 +1,72 @@ +import { promises as fs } from "fs"; +import path from "path"; +import { prisma } from "@/services/db"; +import { logger } from "@/lib/logger"; + +export type DeleteMailsResult = { + requested: number; + deleted: number; + errors: number; +}; + +async function removeMailDir(id: bigint): Promise { + const dir = path.join(process.cwd(), "data", "mails", String(id)); + await fs.rm(dir, { recursive: true, force: true }).catch(() => undefined); +} + +async function deleteOneMail(id: bigint): Promise { + const mail = await prisma.mailMessage.findUnique({ + where: { id }, + select: { id: true, snapshotPath: true }, + }); + if (!mail) return; + + await prisma.importCompensation.deleteMany({ + where: { import: { mailId: id } }, + }); + await prisma.containerActiveLock.deleteMany({ where: { mailId: id } }); + await prisma.containerImport.deleteMany({ where: { mailId: id } }); + await prisma.parseResult.deleteMany({ where: { mailId: id } }); + await prisma.mailAttachment.deleteMany({ where: { mailId: id } }); + await prisma.imapPullLog.deleteMany({ where: { mailId: id } }); + await prisma.mailMessage.delete({ where: { id } }); + await removeMailDir(id); + + if (mail.snapshotPath) { + const abs = path.isAbsolute(mail.snapshotPath) + ? mail.snapshotPath + : path.join(process.cwd(), mail.snapshotPath); + await fs.rm(abs, { recursive: true, force: true }).catch(() => undefined); + } +} + +export async function deleteMailsByIds(ids: bigint[]): Promise { + const unique = [...new Set(ids)]; + const stats: DeleteMailsResult = { + requested: unique.length, + deleted: 0, + errors: 0, + }; + for (const id of unique) { + try { + await deleteOneMail(id); + stats.deleted += 1; + } catch (err) { + stats.errors += 1; + logger.error({ err, mailId: id.toString() }, "mail.delete_failed"); + } + } + return stats; +} + +/** 按拉取入库时间(createdAt)删除最早的 N 封。 */ +export async function deleteOldestMails(limit: number): Promise { + const take = Math.max(0, limit); + if (!take) return { requested: 0, deleted: 0, errors: 0 }; + const rows = await prisma.mailMessage.findMany({ + select: { id: true }, + orderBy: [{ createdAt: "asc" }, { id: "asc" }], + take, + }); + return deleteMailsByIds(rows.map((r) => r.id)); +} diff --git a/src/services/ocr/index.ts b/src/services/ocr/index.ts index 1218053..70ed706 100644 --- a/src/services/ocr/index.ts +++ b/src/services/ocr/index.ts @@ -1,5 +1,8 @@ import { getEnv } from "@/lib/env"; import { logger } from "@/lib/logger"; +import { isPlausibleOcrChunk, sanitizeOcrDisplayText } from "./sanitize"; + +export { isPlausibleOcrChunk, sanitizeOcrDisplayText } from "./sanitize"; export interface OcrResult { text: string; @@ -13,41 +16,29 @@ export interface OcrProvider { recognize(buffer: Buffer, mimeHint?: string | null): Promise; } -/** 物流邮件里常见的汉字;二进制误解码出的生僻字不会命中 */ -const BUSINESS_CJK_RE = - /[柜箱件仓標标请查收转预报贴换拦截派送清单附提单货客户码号船航次重量体积卡派出库留仓拆分覆盖指令预约]/; - function isPdfBuffer(buffer: Buffer, mimeHint?: string | null): boolean { if (/pdf/i.test(mimeHint || "")) return true; return buffer.slice(0, 5).toString("latin1") === "%PDF-"; } -export function isPlausibleOcrChunk(raw: string): boolean { - const t = raw.replace(/\s+/g, "").trim(); - if (!t) return false; - if (/[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}/.test(t)) return true; - if (/[A-Z]{4}\d{7}/.test(t)) return true; - if (/\.(pdf|png|jpe?g|webp|bmp)$/i.test(t) && !/[\u4e00-\u9fff]/.test(t)) { - return true; - } - const cjk = t.match(/[\u4e00-\u9fff]/g)?.length ?? 0; - if (cjk === 0) return t.length >= 3; - if (BUSINESS_CJK_RE.test(t)) return true; - return cjk >= 8; -} +type TesseractWorker = Awaited< + ReturnType<(typeof import("tesseract.js"))["createWorker"]> +>; + +let sharedEngWorkerPromise: Promise | null = null; -/** 展示/落库前清洗:去掉【OCR:文件名】,丢掉 PDF 二进制误识别的乱码行 */ -export function sanitizeOcrDisplayText(raw: string): string { - if (!raw?.trim()) return ""; - const lines = raw - .replace(/^【OCR:[^】]*】\s*/gm, "") - .split(/\r?\n/) - .map((l) => l.trim()) - .filter((l) => l && isPlausibleOcrChunk(l)); - return [...new Set(lines)].join("\n").trim(); +/** 转仓截图专用:复用 eng worker,避免每张图 create/terminate */ +async function getSharedEngTesseractWorker(): Promise { + if (!sharedEngWorkerPromise) { + sharedEngWorkerPromise = (async () => { + const { createWorker } = await import("tesseract.js"); + return createWorker("eng", 1, { logger: () => {} }); + })(); + } + return sharedEngWorkerPromise; } -/** 本地占位:从 PDF 文本对象 / 图片可打印字节抽片段(无 tesseract) */ +/** 本地:启发式抽取(PDF/图片二进制),不在通用路径跑 tesseract */ export class LocalHeuristicOcrProvider implements OcrProvider { name = "local"; @@ -88,6 +79,7 @@ export class LocalHeuristicOcrProvider implements OcrProvider { 50_000, ); const confidence = text.length > 20 ? 55 : text.length > 0 ? 35 : 0; + return { text, confidence, provider: this.name, pages: 1 }; } } @@ -124,6 +116,54 @@ export function getOcrProvider(): OcrProvider | null { return new LocalHeuristicOcrProvider(); } +/** 转仓内嵌截图:并行 eng OCR,仓库码为 ASCII,比 chi_sim+逐张 worker 快一个数量级 */ +export async function ocrTransferScreenshotBuffers( + files: Array<{ buffer: Buffer; filename: string; contentType?: string | null }>, +): Promise { + const imgs = files.filter((f) => + /\.(jpe?g|png|webp|bmp)$/i.test(f.filename), + ); + if (!imgs.length) return null; + + const worker = await getSharedEngTesseractWorker(); + const results = await Promise.all( + imgs.map(async (f) => { + try { + const { + data: { text, confidence }, + } = await worker.recognize(f.buffer); + const cleaned = sanitizeOcrDisplayText(text); + if (!cleaned) return null; + return { + filename: f.filename, + text: cleaned, + confidence: Math.round(confidence || 55), + }; + } catch (err) { + logger.warn({ err, file: f.filename }, "ocr.transfer_screenshot_failed"); + return null; + } + }), + ); + + const texts: string[] = []; + let confSum = 0; + let pages = 0; + for (const r of results) { + if (!r) continue; + texts.push(`【OCR:${r.filename}】\n${r.text}`); + confSum += r.confidence; + pages += 1; + } + if (!texts.length) return null; + return { + text: texts.join("\n\n"), + confidence: Math.round(confSum / Math.max(pages, 1)), + provider: "local-tesseract-eng-fast", + pages, + }; +} + export async function ocrAttachmentBuffers( files: Array<{ buffer: Buffer; filename: string; contentType?: string | null }>, ): Promise { @@ -138,7 +178,7 @@ export async function ocrAttachmentBuffers( const r = await provider.recognize(f.buffer, f.contentType); const cleaned = sanitizeOcrDisplayText(r.text); if (cleaned) { - texts.push(cleaned); + texts.push(`【OCR:${f.filename}】\n${cleaned}`); confSum += r.confidence; pages += r.pages; } diff --git a/src/services/ocr/sanitize.ts b/src/services/ocr/sanitize.ts new file mode 100644 index 0000000..84bc028 --- /dev/null +++ b/src/services/ocr/sanitize.ts @@ -0,0 +1,28 @@ +/** 物流邮件里常见的汉字;二进制误解码出的生僻字不会命中 */ +const BUSINESS_CJK_RE = + /[柜箱件仓標标请查收转预报贴换拦截派送清单附提单货客户码号船航次重量体积卡派出库留仓拆分覆盖指令预约]/; + +export function isPlausibleOcrChunk(raw: string): boolean { + const t = raw.replace(/\s+/g, "").trim(); + if (!t) return false; + if (/[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}/.test(t)) return true; + if (/[A-Z]{4}\d{7}/.test(t)) return true; + if (/\.(pdf|png|jpe?g|webp|bmp)$/i.test(t) && !/[\u4e00-\u9fff]/.test(t)) { + return true; + } + const cjk = t.match(/[\u4e00-\u9fff]/g)?.length ?? 0; + if (cjk === 0) return t.length >= 3; + if (BUSINESS_CJK_RE.test(t)) return true; + return cjk >= 8; +} + +/** 展示/落库前清洗:去掉【OCR:文件名】,丢掉 PDF 二进制误识别的乱码行 */ +export function sanitizeOcrDisplayText(raw: string): string { + if (!raw?.trim()) return ""; + const lines = raw + .replace(/^【OCR:[^】]*】\s*/gm, "") + .split(/\r?\n/) + .map((l) => l.trim()) + .filter((l) => l && isPlausibleOcrChunk(l)); + return [...new Set(lines)].join("\n").trim(); +} diff --git a/src/services/parse/address-block.ts b/src/services/parse/address-block.ts new file mode 100644 index 0000000..f26b22d --- /dev/null +++ b/src/services/parse/address-block.ts @@ -0,0 +1,222 @@ +import { flattenExcelCellValue } from "./excel-cell-text"; + +/** 键值块拆出的货件字段(与装箱单列名对齐) */ +export type LabeledShipmentFields = Partial>; + +export type LabeledField = + | "F_FBACode" + | "F_Transporter" + | "F_CTNS" + | "F_CBM" + | "F_Weight" + | "F_Pcs" + | "F_FBAID" + | "F_PO" + | "F_ReferenceId" + | "F_ShipmentID" + | "F_Address" + | "F_City" + | "F_State" + | "F_ZipCode" + | "F_Contact" + | "F_Mobile" + | "F_Email" + | "F_Company" + | "F_Remark" + | "F_SKU" + | "F_Description" + | "F_EnglishName" + | "F_ContainerNo" + | "F_Expected_DeliveryDateB" + | "F_Expected_DeliveryDateE"; + +const LABELS: Array<{ keys: string[]; field: LabeledField }> = [ + { keys: ["仓库代码", "仓库ID", "仓库id", "目的代码", "FBACode", "FBA Code"], field: "F_FBACode" }, + { keys: ["渠道", "Move Type", "Transporter", "运输方式"], field: "F_Transporter" }, + { keys: ["件数", "箱数", "Qty.(CTNS)", "CTNS"], field: "F_CTNS" }, + { keys: ["总体积", "体积(m³)", "Vol(CBM)", "CBM"], field: "F_CBM" }, + { keys: ["毛重", "G.W.(KGS)", "Weight"], field: "F_Weight" }, + { keys: ["件数(PCS)", "PCS", "Pcs"], field: "F_Pcs" }, + { keys: ["FBA ID", "FBAID", "FBAId(Amazon)", "Shipment ID(FBA)"], field: "F_FBAID" }, + { keys: ["PO(Amazon)", "Amazon PO", "FBA PO"], field: "F_PO" }, + { keys: ["Amazon reference ID", "ReferenceId", "Reference ID"], field: "F_ReferenceId" }, + { + keys: ["分货标识", "箱唛", "Customer Ref No.", "Customer Ref No", "Custmer Ref No.", "ShipmentID"], + field: "F_ShipmentID", + }, + { keys: ["收货地址", "派送地址", "详细地址", "Address"], field: "F_Address" }, + { keys: ["城市", "City"], field: "F_City" }, + { keys: ["州省", "State"], field: "F_State" }, + { keys: ["省/市/区", "省市/区", "省市区"], field: "F_State" }, + { keys: ["邮政编码", "邮编", "ZipCode", "Zip Code", "ZIP"], field: "F_ZipCode" }, + { keys: ["收件人", "收货人", "联系人", "Contact"], field: "F_Contact" }, + { keys: ["联系电话", "手机号", "电话", "Mobile"], field: "F_Mobile" }, + { keys: ["邮箱", "Email"], field: "F_Email" }, + { keys: ["公司名称", "公司名", "Company Name", "Company"], field: "F_Company" }, + { keys: ["行备注", "备注", "Remark"], field: "F_Remark" }, + { keys: ["SKU"], field: "F_SKU" }, + { keys: ["品名", "货物描述", "Description"], field: "F_Description" }, + { keys: ["英文名称", "English Name", "EnglishName"], field: "F_EnglishName" }, + { keys: ["柜号", "集装箱号", "ContainerNo", "Container No"], field: "F_ContainerNo" }, + { + keys: ["最早送仓日期", "最早送仓", "Expected Delivery Date B"], + field: "F_Expected_DeliveryDateB", + }, + { + keys: ["最晚送仓日期", "最晚送仓", "Expected Delivery Date E"], + field: "F_Expected_DeliveryDateE", + }, +]; + +function escapeRe(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +const LABEL_ALT = [...new Set(LABELS.flatMap((x) => x.keys))] + .sort((a, b) => b.length - a.length) + .map(escapeRe) + .join("|"); + +const LABEL_RE = new RegExp(`(${LABEL_ALT})\\s*[::]`, "gi"); + +function fieldForLabel(label: string): LabeledField | null { + const n = label.replace(/\s+/g, "").toLowerCase(); + for (const row of LABELS) { + if (row.keys.some((k) => k.replace(/\s+/g, "").toLowerCase() === n)) { + return row.field; + } + } + return null; +} + +function placeholderEmpty(t: string): boolean { + return !t || t === "/" || t === "/" || t === "-" || t === "—"; +} + +/** 「NV / HENDERSON /」或「NV/Henderson」→ 州 + 城市 */ +export function parseRegionComposite(raw: unknown): { + state: string | null; + city: string | null; +} { + const t = flattenExcelCellValue(raw).trim(); + if (placeholderEmpty(t)) return { state: null, city: null }; + const parts = t + .split(/[//|]+/) + .map((p) => p.trim()) + .filter(Boolean); + if (parts.length >= 2) { + return { state: parts[0].slice(0, 80), city: parts[1].slice(0, 80) }; + } + if (parts.length === 1 && /^[A-Z]{2}$/i.test(parts[0])) { + return { state: parts[0].toUpperCase(), city: null }; + } + return { state: null, city: null }; +} + +function normalizeCellText(text: string): string { + return (text || "") + .replace(//gi, "\n") + .replace(/\u2028|\u2029/g, "\n") + .replace(/\r\n/g, "\n") + .replace(/\r/g, "\n") + .trim(); +} + +function labelHitCount(src: string): number { + let n = 0; + const re = new RegExp(LABEL_RE.source, "gi"); + while (re.exec(src)) n += 1; + return n; +} + +export function looksLikeLabeledBlock(text: string): boolean { + return labelHitCount(normalizeCellText(text)) >= 1; +} + +function emptyLabeled(): LabeledShipmentFields { + return {}; +} + +/** + * 任意单元格:「标签:值」可多项。一项归本列,其余归对应货件字段。 + */ +export function parseLabeledCell(text: string): LabeledShipmentFields | null { + const src = normalizeCellText(text); + if (!src || labelHitCount(src) < 1) return null; + + const hits: Array<{ field: LabeledField; start: number; valueStart: number }> = []; + const re = new RegExp(LABEL_RE.source, "gi"); + let m: RegExpExecArray | null; + while ((m = re.exec(src))) { + const field = fieldForLabel(m[1]); + if (!field) continue; + hits.push({ field, start: m.index, valueStart: m.index + m[0].length }); + } + if (!hits.length) return null; + + const out = emptyLabeled(); + for (let i = 0; i < hits.length; i++) { + const cur = hits[i]; + const end = i + 1 < hits.length ? hits[i + 1].start : src.length; + const value = src.slice(cur.valueStart, end).replace(/^[\s;;]+|[\s;;]+$/g, ""); + if (placeholderEmpty(value)) continue; + if (cur.field === "F_State" && /[//]/.test(value)) { + const region = parseRegionComposite(value); + if (region.state && !out.F_State) out.F_State = region.state; + if (region.city && !out.F_City) out.F_City = region.city; + continue; + } + if (!out[cur.field]) out[cur.field] = value; + } + return Object.keys(out).length ? out : null; +} + +/** 本列只取「属于该列」的那一项;整段键值块不得原样当本列值。 */ +export function columnValueFromCell( + columnField: string, + cellText: string, +): string { + const plain = normalizeCellText(cellText); + const block = parseLabeledCell(plain); + if (!block) return plain; + const home = block[columnField as LabeledField]; + return (home || "").trim(); +} + +export function mergeLabeledHarvest( + target: LabeledShipmentFields, + block: LabeledShipmentFields, + homeField?: string, +): void { + for (const [key, value] of Object.entries(block) as Array<[LabeledField, string | undefined]>) { + if (!value) continue; + if (!target[key] || key === homeField) target[key] = value; + } +} + +export function firstFilled(...vals: Array): string | null { + for (const v of vals) { + const t = (v || "").trim(); + if (t) return t; + } + return null; +} + +/** @deprecated 兼容旧单测;请用 parseLabeledCell */ +export function parseLabeledAddressBlock(text: string) { + const p = parseLabeledCell(text); + if (!p) return null; + return { + warehouseCode: p.F_FBACode ?? null, + contact: p.F_Contact ?? null, + company: p.F_Company ?? null, + mobile: p.F_Mobile ?? null, + state: p.F_State ?? null, + city: p.F_City ?? null, + zip: p.F_ZipCode ?? null, + street: p.F_Address ?? null, + remark: p.F_Remark ?? null, + }; +} + +export const looksLikeLabeledAddressBlock = looksLikeLabeledBlock; diff --git a/src/services/parse/bl-plus-template.ts b/src/services/parse/bl-plus-template.ts index 9590f21..d393bb4 100644 --- a/src/services/parse/bl-plus-template.ts +++ b/src/services/parse/bl-plus-template.ts @@ -1,19 +1,60 @@ -import { toShanghaiDateString } from "@/utils/dates"; +import { toLooseShanghaiDateString } from "@/utils/dates"; import type { ContainerHeader, MailRecordModules } from "@/types/mail"; import { isValidContainerNo } from "@/utils/iso6346"; +import { SHIPPING_LINE_ALIASES } from "@/services/parse/carrier-aliases"; +import { extractOpsSubjectFromMail } from "@/services/parse/ops-subject-template"; +import { + isKnownUsDestinationPort, + matchUsDestinationPort, +} from "@/services/parse/us-ports"; const ISO_CN = /\b([A-Z]{4}\d{7})\b/; +const ISO_CN_EXACT = /^[A-Z]{4}\d{7}$/; const CABINET = /\b(45HC|45HQ|40HC|40HQ|40GP|20GP|20HQ|45GP)\b/i; -const DATE_TOKEN = /(\d{4}[-/.]\d{1,2}[-/.]\d{1,2})/; +const DATE_TOKEN = /(\d{4}[-/.]\d{1,2}[-/.]\d{1,2}|\d{1,2}[-/.]\d{1,2})/; +const LOOSE_DATE = String.raw`(?:\d{4}[-/.]\d{1,2}[-/.]\d{1,2}|\d{1,2}[-/.]\d{1,2})`; +const BARE_BL = /^[A-Z]{4}[A-Z0-9]{6,28}$/i; +/** 字段分隔:+ / 全角+;命中则不再用空格切字段 */ +const PLUS_FIELD_DELIM = /[++]/; + +/** + * 是否用「+」类特殊字符做字段分隔。 + * 规则:有 + 只按 + 切;无 + 才按空格切。二者互斥。 + */ +export function usesPlusFieldDelimiter(text: string): boolean { + const t = (text || "").trim(); + if (!PLUS_FIELD_DELIM.test(t)) return false; + const parts = t.split(PLUS_FIELD_DELIM).map((p) => p.trim()).filter(Boolean); + return parts.length >= 2; +} + +function splitPlusFields(text: string): string[] { + return text + .split(PLUS_FIELD_DELIM) + .map((p) => p.trim()) + .filter(Boolean); +} /** 去掉 Fw/转发/新增预报N 等邮件行为前缀,露出「客户+提单+…」 */ export function stripForecastSubjectNoise(raw: string): string { - let t = raw.replace(/\s+/g, " ").trim(); + let t = raw + .replace(/ /gi, " ") + .replace(/\s+/g, " ") + .trim(); for (let i = 0; i < 8; i++) { const next = t - .replace(/^(?:(?:Fw|FW|Fwd|FWD|Re|RE)\s*:\s*)+/i, "") + .replace(/^(?:(?:Fw|FW|Fwd|FWD|Re|RE|回复)\s*[::]\s*)+/i, "") .replace(/^转发\s*[::]\s*/u, "") + .replace(/^(?:主旨|主题|Subject)\s*[::]\s*/i, "") + // QQ/转发壳:「主题 回复: …」无冒号紧跟主题 + .replace(/^(?:主旨|主题|Subject)\s+(?:回复|Re)\s*[::]\s*/i, "") + .replace(/^(?:主旨|主题|Subject)\s+/i, "") .replace(/^新增预报\d*\s*[::]\s*/u, "") + // 工单主题行为前缀:贴标操作:派送指令:拍照指令:do请查收: + .replace( + /^(?:(?:贴标(?:操作)?|派送指令|拍照指令|do请查收|拆柜清单(?:更新)?|卡转海|派送要求)\s*[::]\s*)+/iu, + "", + ) .trim(); if (next === t) break; t = next; @@ -21,65 +62,354 @@ export function stripForecastSubjectNoise(raw: string): string { return t; } -/** 从主题/正文抽出「客户+提单+柜号+…」标准串 */ +/** + * 客户名:中英文名称;不得是指令前缀、柜号/提单号包装、或已占用字段值。 + */ +export function looksLikeCustomerName(raw: string): boolean { + const t = (raw || "").trim(); + if (!t || t.length > 60) return false; + if (ISO_CN_EXACT.test(t)) return false; + if (BARE_BL.test(t) && /[0-9]/.test(t)) return false; + if (CABINET.test(t)) return false; + if ( + /贴标|派送指令|拍照|do请查收|拆柜清单|卡转海|转发|回复|柜号|提单|封号|船名|航次|直送|提拆|不带车架|带车架/i.test( + t, + ) + ) { + return false; + } + if (/[+::|]/.test(t)) return false; + if (ISO_CN.test(t)) return false; + if (/(?:ETA|ETD|EDT)\b/i.test(t) || DATE_TOKEN.test(t)) return false; + // 纯短大写码(如 YWZD)可作客户代码;含中文或空格英文名 OK + if (/^[\u4e00-\u9fffA-Za-z0-9][\u4e00-\u9fffA-Za-z0-9 .&_-]{0,40}$/.test(t)) { + return true; + } + return false; +} + +/** 若文本只是「柜号/提单包装」或含已占用值,则不是客户 */ +export function sanitizeCustomerName( + raw: string | null | undefined, + reserved?: { containerNo?: string | null; blNo?: string | null }, +): string { + const t = (raw || "").trim(); + if (!t || !looksLikeCustomerName(t)) return ""; + const cn = (reserved?.containerNo || "").toUpperCase(); + const bl = (reserved?.blNo || "").toUpperCase(); + const upper = t.toUpperCase(); + if (cn && upper.includes(cn)) return ""; + if (bl && bl.length >= 8 && upper.includes(bl)) return ""; + return t; +} + +/** 预报 UI 已有对应格的标签,不进备注 */ +const FORECAST_MAPPED_LABEL_RE = + /^(?:柜号|提单(?:号)?|B\/?L|柜型|ETA|ETD|EDT|预计抵港|预计开船|开船日|抵港日|到港|船名航次|船名|目的港|卸货港|码头|POD|装货港|起运港|POL|船司|船公司|客户(?:名|代码)?|运输方式|业务类型)\s*[::]/i; + +const LABELED_FIELD_RE = + /^([\u4e00-\u9fffA-Za-z][\u4e00-\u9fffA-Za-z0-9\/]{0,15})\s*[::]\s*(.+)$/; + +const MAIL_CHROME_LABEL_RE = + /^(?:主题|主旨|Subject|发件人|收件人|抄送|日期|From|To|Cc|Date)$/i; + +/** + * 带「名称:值」前缀、且对不上预报 UI 参数的字段 → 备注原文。 + * 例:封号:EMCEJJ0635。柜号/提单等已映射标签返回 null。 + */ +export function labeledFieldToMemo(raw: string): string | null { + const t = (raw || "").trim(); + if (!t) return null; + const seal = t.match(/^(封号|铅封(?:号)?|封条号?)\s*[::]?\s*(.+)$/i); + if (seal?.[2]?.trim()) { + return `${seal[1]}:${seal[2].trim()}`; + } + if (FORECAST_MAPPED_LABEL_RE.test(t)) return null; + const m = t.match(LABELED_FIELD_RE); + if (!m) return null; + const label = m[1].trim(); + const value = m[2].trim(); + if (!value || MAIL_CHROME_LABEL_RE.test(label)) return null; + return `${label}:${value}`; +} + +function pushMemoBit(modules: MailRecordModules, bit: string | null | undefined) { + const t = (bit || "").trim(); + if (!t) return; + const cur = modules.memo_bits || []; + if (cur.some((x) => x.toUpperCase() === t.toUpperCase())) return; + modules.memo_bits = [...cur, t]; +} + +function tryConsumeUnmappedLabeled( + p: string, + modules: MailRecordModules, +): boolean { + const bit = labeledFieldToMemo(p); + if (!bit) return false; + pushMemoBit(modules, bit); + return true; +} + +/** 一值一列:已归属柜号/提单/港口的串不得再进客户/备注客户位 */ +export function exclusiveFinalizeModules( + modules: MailRecordModules, +): MailRecordModules { + const out: MailRecordModules = { ...modules }; + out.customer_name = sanitizeCustomerName(out.customer_name, { + containerNo: out.container_no, + blNo: out.bl_no, + }) || undefined; + if (out.port) { + const port = matchUsDestinationPort(out.port); + out.port = port || undefined; + } + // 备注用服务说明,不要把假客户拼进去 + if (out.customer_name && out.container_no) { + const up = out.customer_name.toUpperCase(); + if (up.includes(out.container_no.toUpperCase())) { + out.customer_name = undefined; + } + } + if (out.memo_bits?.length) { + const cn = (out.container_no || "").toUpperCase(); + const bl = (out.bl_no || "").toUpperCase(); + out.memo_bits = out.memo_bits.filter((bit) => { + const up = bit.toUpperCase(); + if (cn && /柜号/.test(bit) && up.includes(cn)) return false; + if (bl && /提单/.test(bit) && up.includes(bl)) return false; + return true; + }); + if (!out.memo_bits.length) delete out.memo_bits; + } + return out; +} + +/** + * 标签型 + 模板:柜号:…+提单号:…+封号:…+YWZD+不带车架+整柜直送 + * 客户一般不在提单前;排除已知字段后若**仅剩 1 个**独立英文缩写,才认定为客户。 + */ +export function parseLabeledPlusTemplate( + payload: string, + opts?: { defaultYear?: number }, +): MailRecordModules | null { + const year = opts?.defaultYear ?? new Date().getFullYear(); + const cleaned = cleanJoinedPayload(stripForecastSubjectNoise(payload)); + if (!/柜号\s*[::]|提单(?:号)?\s*[::]/i.test(cleaned)) return null; + const parts = cleaned + .split(PLUS_FIELD_DELIM) + .map((p) => p.trim()) + .filter(Boolean); + if (parts.length < 2) return null; + // 仅当首段就是「柜号:/提单号:」才走标签模板; + // 「客户 + … + 柜号:XXX」仍走客户+提单模板 + if ( + !/^柜号\s*[::]/i.test(parts[0]) && + !/^提单(?:号)?\s*[::]/i.test(parts[0]) + ) { + return null; + } + + const modules: MailRecordModules = {}; + const leftovers: string[] = []; + + for (const p of parts) { + if (/柜号/i.test(p) || (!modules.container_no && ISO_CN_EXACT.test(p.trim()))) { + const m = p.match(ISO_CN); + if (m) { + modules.container_no = m[1].toUpperCase(); + continue; + } + } + if (/提单/.test(p)) { + const m = + p.match(/提单(?:号)?\s*[::]?\s*([A-Z0-9][A-Z0-9\-_/]{5,31})/i) || + p.match(/\b([A-Z]{4}[A-Z0-9]{6,28})\b/); + if (m?.[1] && !ISO_CN_EXACT.test(m[1])) { + modules.bl_no = m[1].toUpperCase(); + continue; + } + } + // 封号:… 等带标签但对不上预报 UI 的字段 → 备注,不得当客户 + if (tryConsumeUnmappedLabeled(p, modules)) { + continue; + } + if (!modules.cabinet_type && CABINET.test(p)) { + modules.cabinet_type = p.match(CABINET)![1].toUpperCase(); + continue; + } + if (/(?:EDT|ETD|ETA)/i.test(p) || /船名航次/.test(p)) { + Object.assign(modules, parseScheduleSegment(p, year)); + continue; + } + if (/直送/.test(p)) { + modules.ops_hints = [...(modules.ops_hints || []), "直送"]; + modules.service = modules.service || p; + continue; + } + if (/提|拆|派|托架|车架/.test(p)) { + Object.assign(modules, parseServiceSegment(p)); + continue; + } + if (!modules.port && looksLikePort(p)) { + modules.port = matchUsDestinationPort(p) || undefined; + continue; + } + // 已知船司/航司简称 → 船司线索,不当客户 + if (findShipAliasToken(p) && !/[0-9]/.test(p) && p.trim().length <= 12) { + modules.shipping_line_hint = + modules.shipping_line_hint || findShipAliasToken(p) || undefined; + continue; + } + leftovers.push(p); + } + + if (modules.bl_no) { + const prefix = modules.bl_no.match(/^([A-Z]{2,4})(?=\d)/); + if (prefix) modules.shipping_line_hint = modules.shipping_line_hint || prefix[1]; + } + + // 仅当排除全部已知参数后,剩余恰好 1 个独立缩写/名称 → 客户 + const residualCustomers = leftovers.filter((x) => + isResidualCustomerCandidate(x, modules), + ); + if (residualCustomers.length === 1 && leftovers.length === 1) { + modules.customer_name = residualCustomers[0].trim(); + } else { + delete modules.customer_name; + for (const extra of leftovers) { + const labeled = labeledFieldToMemo(extra); + pushMemoBit(modules, labeled || extra.trim()); + } + } + + return modules.container_no || modules.bl_no + ? exclusiveFinalizeModules(modules) + : null; +} + +/** 标签模板剩余段:短英文缩写或中文名,且未占用柜号/提单 */ +function isResidualCustomerCandidate( + raw: string, + reserved: MailRecordModules, +): boolean { + const t = raw.trim(); + if (!t) return false; + if (!looksLikeCustomerName(t)) return false; + if ( + !sanitizeCustomerName(t, { + containerNo: reserved.container_no, + blNo: reserved.bl_no, + }) + ) { + return false; + } + // 独立英文缩写(如 YWZD)或短中文名;拒绝长句 + if (t.length > 32) return false; + if (/^[A-Za-z][A-Za-z0-9_-]{1,11}$/.test(t)) return true; + if (/^[\u4e00-\u9fff]{2,20}$/.test(t)) return true; + if (/^[A-Za-z]+(?:\s+[A-Za-z]+){0,3}$/.test(t) && t.length <= 32) return true; + return false; +} + +/** subjectForParse 用「 | 」拼接时,只保留首段可用的 + 模板 */ +function cleanJoinedPayload(payload: string): string { + if (!payload.includes(" | ")) return payload; + const head = payload.split(" | ")[0].trim(); + if (splitPlusFields(head).length >= 4) return head; + return payload; +} + +/** 从主题/正文抽出「客户+提单+柜号+…」标准串(仅 + 分隔模板) */ export function findBlPlusPayload(subject: string, body: string): string | null { - const candidates: string[] = []; + const subjectCandidates: string[] = []; + const bodyCandidates: string[] = []; const sub = stripForecastSubjectNoise( subject.replace(/^[^::]*[::]/, "").trim(), ); // 整主题再剥一轮(Fw: 转发:新增预报136:新辰泽+…) const subFull = stripForecastSubjectNoise(subject); - if (sub.includes("+")) candidates.push(sub); - if (subFull.includes("+")) candidates.push(subFull); + if (usesPlusFieldDelimiter(sub)) subjectCandidates.push(sub); + if (usesPlusFieldDelimiter(subFull)) subjectCandidates.push(subFull); for (const line of body.split(/\r?\n/)) { const t = line.trim(); - if (!t.includes("+")) continue; + if (!usesPlusFieldDelimiter(t)) continue; const after = t.replace(/^.*?请查收新增预报[::]\s*/i, "").trim(); const cleaned = after .replace(/DO也同步上传.*$/i, "") .replace(/请注意查收[!!]?.*$/i, "") .trim(); - if (cleaned.includes("+")) candidates.push(cleaned); - else if (t.includes("+")) candidates.push(t); - } - - const scored = candidates - .map((c) => stripForecastSubjectNoise(c.replace(/\s+/g, " ").trim())) - .filter((c) => { - const parts = c - .split("+") - .map((p) => p.trim()) - .filter(Boolean); - if (parts.length >= 5) return true; - // 转仓/工单主题:客户+提单+柜号+ETA(4 段) - if ( - parts.length >= 4 && - parts.some((p) => ISO_CN.test(p) || /柜号/.test(p)) - ) { - return true; - } - return false; - }) + if (usesPlusFieldDelimiter(cleaned)) bodyCandidates.push(cleaned); + else if (usesPlusFieldDelimiter(t)) bodyCandidates.push(t); + } + + const normalizeCand = (c: string) => + cleanJoinedPayload( + stripForecastSubjectNoise(c.replace(/\s+/g, " ").trim()), + ); + + const isViable = (c: string) => { + if (!usesPlusFieldDelimiter(c)) return false; + const parts = splitPlusFields(c); + if (parts.length >= 5) return true; + if ( + parts.length >= 4 && + parts.some((p) => ISO_CN.test(p) || /柜号/.test(p)) + ) { + return true; + } + if ( + parts.length >= 2 && + parts.some( + (p) => /^柜号\s*[::]/i.test(p) || /^提单(?:号)?\s*[::]/i.test(p), + ) + ) { + return true; + } + return false; + }; + + // 优先主题候选;同级再取可成功 parse 的最长串 + const ranked = [...subjectCandidates, ...bodyCandidates] + .map(normalizeCand) + .filter(isViable); + + for (const c of ranked) { + if (parseBlPlusTemplate(c)) return c; + } + // 回退:主题侧最长可行串(即使暂未 parse 出,也优于脏正文) + const subjectOnly = subjectCandidates + .map(normalizeCand) + .filter(isViable) .sort((a, b) => b.length - a.length); - return scored[0] || null; + return subjectOnly[0] || ranked.sort((a, b) => b.length - a.length)[0] || null; } -function normDateToken(raw: string | undefined): string | null { - if (!raw) return null; - return toShanghaiDateString(raw.replace(/\./g, "-").replace(/\//g, "-")); +function normDateToken( + raw: string | undefined, + defaultYear = new Date().getFullYear(), +): string | null { + return toLooseShanghaiDateString(raw, defaultYear); } -function parseScheduleSegment(seg: string): { +function parseScheduleSegment( + seg: string, + defaultYear = new Date().getFullYear(), +): { etd?: string; eta?: string; vessel_voyage?: string; } { const out: { etd?: string; eta?: string; vessel_voyage?: string } = {}; - const etdM = seg.match(/(?:EDT|ETD)\s*[::]?\s*(\d{4}[-/.]\d{1,2}[-/.]\d{1,2})/i); - const etaM = seg.match(/ETA\s*[::]?\s*(\d{4}[-/.]\d{1,2}[-/.]\d{1,2})/i); - const etd = normDateToken(etdM?.[1]); - const eta = normDateToken(etaM?.[1]); + const etdM = seg.match( + new RegExp(String.raw`(?:EDT|ETD)\s*[::]?\s*(${LOOSE_DATE})`, "i"), + ); + const etaM = seg.match( + new RegExp(String.raw`ETA\s*[::]?\s*(${LOOSE_DATE})`, "i"), + ); + const etd = normDateToken(etdM?.[1], defaultYear); + const eta = normDateToken(etaM?.[1], defaultYear); if (etd) out.etd = etd; if (eta) out.eta = eta; @@ -88,8 +418,11 @@ function parseScheduleSegment(seg: string): { out.vessel_voyage = vesselM[1].trim().replace(/\s+/g, " "); } else { let rest = seg - .replace(/(?:EDT|ETD)\s*[::]?\s*\d{4}[-/.]\d{1,2}[-/.]\d{1,2}/gi, " ") - .replace(/ETA\s*[::]?\s*\d{4}[-/.]\d{1,2}[-/.]\d{1,2}/gi, " ") + .replace( + new RegExp(String.raw`(?:EDT|ETD)\s*[::]?\s*${LOOSE_DATE}`, "gi"), + " ", + ) + .replace(new RegExp(String.raw`ETA\s*[::]?\s*${LOOSE_DATE}`, "gi"), " ") .trim(); const segHead = seg.trim().slice(0, 12); if ( @@ -129,42 +462,99 @@ function parseServiceSegment(seg: string): { }; } +function looksLikePort(p: string): boolean { + return isKnownUsDestinationPort(p); +} + +/** 目的港:仅美国港口白名单(洛杉矶/长滩/纽约…);禁止随便截取 */ +export function isPlausibleDestinationPort( + raw: string | null | undefined, +): boolean { + return isKnownUsDestinationPort(raw); +} + +/** 规范目的港名;未命中返回空串 */ +export function normalizeDestinationPort( + raw: string | null | undefined, +): string { + return matchUsDestinationPort(raw) || ""; +} + /** * 解析提单标准「+」模板。 * 例:新辰泽+WHLC027G597465+WHSU8127240+洛杉矶+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派组合柜-不带托架 */ -export function parseBlPlusTemplate(payload: string): MailRecordModules | null { - const parts = payload - .split("+") - .map((p) => p.trim()) - .filter(Boolean); +export function parseBlPlusTemplate( + payload: string, + opts?: { defaultYear?: number }, +): MailRecordModules | null { + const labeled = parseLabeledPlusTemplate(payload, opts); + if (labeled) return labeled; + + const year = opts?.defaultYear ?? new Date().getFullYear(); + const parts = splitPlusFields(cleanJoinedPayload(payload)); const hasContainer = parts.some((p) => ISO_CN.test(p)); if (parts.length < 5 && !(parts.length >= 4 && hasContainer)) return null; const modules: MailRecordModules = {}; // 首段偶发残留「新增预报136:新辰泽」 - modules.customer_name = stripForecastSubjectNoise(parts[0]).replace( + const customerRaw = stripForecastSubjectNoise(parts[0]).replace( /^新增预报\d*\s*[::]\s*/u, "", ); - // 首段已是 ISO 柜号 → 工单主题模板,非「客户+提单」预报 - if (/^[A-Z]{4}\d{7}$/.test(parts[0].trim())) { + // 剥工单前缀后首段已是 ISO 柜号 → 工单主题模板,非「客户+提单」预报 + if (ISO_CN_EXACT.test(customerRaw.trim()) || ISO_CN_EXACT.test(parts[0].trim())) { + return null; + } + if (!looksLikeCustomerName(customerRaw)) { return null; } + modules.customer_name = customerRaw; - let cnIdx = parts.findIndex( - (p) => ISO_CN.test(p) && /^[A-Z]{4}\d{7}$/.test(p.trim()), - ); + let cnIdx = parts.findIndex((p) => { + const bare = p.replace(/^柜号\s*[::]?\s*/i, "").trim(); + return ISO_CN.test(p) && ISO_CN_EXACT.test(bare); + }); if (cnIdx < 0) { cnIdx = parts.findIndex((p) => ISO_CN.test(p)); } if (cnIdx >= 0) { const cn = parts[cnIdx].match(ISO_CN)?.[1]; if (cn) modules.container_no = cn; - if (cnIdx >= 1) modules.bl_no = parts[cnIdx - 1].trim().toUpperCase(); + // 仅当前一段像提单号时才取;「柜号:」标签段前不是客户提单 + if (cnIdx >= 1) { + const prev = parts[cnIdx - 1].replace(/^提单(?:号)?\s*[::]?\s*/i, "").trim(); + if ( + looksLikeCustomerName(modules.customer_name || "") && + BARE_BL.test(prev) && + !ISO_CN_EXACT.test(prev) && + !/柜号|封号|船名/i.test(parts[cnIdx - 1]) + ) { + modules.bl_no = prev.toUpperCase(); + } else if ( + BARE_BL.test(prev) && + !ISO_CN_EXACT.test(prev) && + !/[\u4e00-\u9fff]/.test(prev) + ) { + modules.bl_no = prev.toUpperCase(); + } + } } else if (parts[1]) { - modules.bl_no = parts[1].trim().toUpperCase(); + const blCand = parts[1].replace(/^提单(?:号)?\s*[::]?\s*/i, "").trim(); + if (BARE_BL.test(blCand) && !ISO_CN_EXACT.test(blCand)) { + modules.bl_no = blCand.toUpperCase(); + } + } + + // 客户必须在提单前;且不得占用柜号 + modules.customer_name = sanitizeCustomerName(modules.customer_name, { + containerNo: modules.container_no, + blNo: modules.bl_no, + }); + if (!modules.customer_name) { + // 无合法客户时仍可解析柜头(标签/工单混排) + delete modules.customer_name; } if (modules.bl_no) { @@ -174,14 +564,18 @@ export function parseBlPlusTemplate(payload: string): MailRecordModules | null { const afterCn = cnIdx >= 0 ? parts.slice(cnIdx + 1) : parts.slice(2); for (const p of afterCn) { - if ( - !modules.port && - /[\u4e00-\u9fff]/.test(p) && - !CABINET.test(p) && - !DATE_TOKEN.test(p) && - !/船名|提拆|派/.test(p) - ) { - modules.port = p; + if (/提单/.test(p) && !modules.bl_no) { + const m = + p.match(/提单(?:号)?\s*[::]?\s*([A-Z0-9][A-Z0-9\-_/]{5,31})/i) || + p.match(/\b([A-Z]{4}[A-Z0-9]{6,28})\b/); + if (m?.[1] && !ISO_CN_EXACT.test(m[1])) { + modules.bl_no = m[1].toUpperCase(); + const prefix = modules.bl_no.match(/^([A-Z]{2,4})(?=\d)/); + if (prefix) modules.shipping_line_hint = prefix[1]; + continue; + } + } + if (tryConsumeUnmappedLabeled(p, modules)) { continue; } if (!modules.cabinet_type && CABINET.test(p)) { @@ -189,11 +583,21 @@ export function parseBlPlusTemplate(payload: string): MailRecordModules | null { continue; } if (/(?:EDT|ETD|ETA)/i.test(p) || /船名航次/.test(p)) { - Object.assign(modules, parseScheduleSegment(p)); + Object.assign(modules, parseScheduleSegment(p, year)); + continue; + } + if (/直送/.test(p)) { + modules.ops_hints = [...new Set([...(modules.ops_hints || []), "直送"])]; + modules.service = modules.service || p; continue; } if (/提|拆|派|组合柜|托架|车架/.test(p)) { Object.assign(modules, parseServiceSegment(p)); + continue; + } + if (!modules.port && looksLikePort(p)) { + modules.port = matchUsDestinationPort(p) || undefined; + continue; } } @@ -204,14 +608,229 @@ export function parseBlPlusTemplate(payload: string): MailRecordModules | null { if (!modules.etd || !modules.eta || !modules.vessel_voyage) { const scheduleSeg = parts.find( (p) => - /(?:EDT|ETD|ETA)\s*[::]?\s*\d{4}/i.test(p) || /船名航次/.test(p), + new RegExp(String.raw`(?:EDT|ETD|ETA)\s*[::]?\s*${LOOSE_DATE}`, "i").test( + p, + ) || /船名航次/.test(p), ); if (scheduleSeg) { - Object.assign(modules, parseScheduleSegment(scheduleSeg)); + Object.assign(modules, parseScheduleSegment(scheduleSeg, year)); } } - return modules.container_no || modules.bl_no ? modules : null; + return modules.container_no || modules.bl_no + ? exclusiveFinalizeModules(modules) + : null; +} + +const SHIP_ALIAS_TOKENS = Object.keys(SHIPPING_LINE_ALIASES) + .filter((k) => k.length >= 3) + .sort((a, b) => b.length - a.length); + +function normalizeMailText(raw: string): string { + return raw + .replace(/ /gi, " ") + .replace(/\t/g, " ") + .replace(/\s+/g, " ") + .trim(); +} + +function findShipAliasToken(text: string): string | null { + const upper = text.toUpperCase(); + for (const token of SHIP_ALIAS_TOKENS) { + const safe = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + if (new RegExp(`(?:^|[^A-Z0-9])${safe}(?:[^A-Z0-9]|$)`).test(upper)) { + return token; + } + } + return null; +} + +/** + * 空格分隔预报模板(非 + 号): + * 主题:ZJYC COSU6504130784 FCIU7089474 7.10 派拆柜派送 + * 正文:FCIU7089474 提柜 COSCO 40HQ ETA:7.10 卡派拆柜派送 + */ +export function parseSpaceForecastTemplate( + text: string, + opts?: { defaultYear?: number }, +): MailRecordModules | null { + const year = opts?.defaultYear ?? new Date().getFullYear(); + // subjectForParse 用「 | 」拼接多段主题时,只取首段业务串 + const cleaned = normalizeMailText(text).split(/\s\|\s/)[0]?.trim() || ""; + if (!cleaned) return null; + // 互斥:已用 + 分隔字段时,禁止再按空格切字段 + if (usesPlusFieldDelimiter(cleaned)) return null; + + const modules: MailRecordModules = {}; + + // 客户 + 提单 + 柜号 + [日期] + [业务] + const subjectM = cleaned.match( + new RegExp( + String.raw`(?:^|[::]\s*)([A-Za-z0-9\u4e00-\u9fff][A-Za-z0-9\u4e00-\u9fff ._/-]{0,40}?)\s+([A-Z]{4}[A-Z0-9]{6,28})\s+([A-Z]{4}\d{7})(?:\s+(${LOOSE_DATE}))?(?:\s+(.+))?$`, + "i", + ), + ); + if (subjectM) { + const customer = stripForecastSubjectNoise(subjectM[1]).trim(); + const bl = subjectM[2].toUpperCase(); + const cn = subjectM[3].toUpperCase(); + if ( + customer && + !ISO_CN_EXACT.test(customer) && + !BARE_BL.test(customer) && + !ISO_CN_EXACT.test(bl) + ) { + modules.customer_name = customer; + modules.bl_no = bl; + modules.container_no = cn; + const eta = normDateToken(subjectM[4], year); + if (eta) modules.eta = eta; + const service = (subjectM[5] || "").split(/\s\|\s/)[0].trim(); + if (service && /提|拆|派/.test(service)) { + Object.assign(modules, parseServiceSegment(service)); + } + const prefix = bl.match(/^([A-Z]{2,4})(?=\d)/); + if (prefix) modules.shipping_line_hint = prefix[1]; + } + } + + // 正文行:柜号 提柜 船司 柜型 ETA:日期 业务 + const bodyM = cleaned.match( + new RegExp( + String.raw`\b([A-Z]{4}\d{7})\b(?:\s+提柜)?\s+([A-Z][A-Z0-9 .-]{1,30}?)\s+(45HC|45HQ|40HC|40HQ|40GP|20GP|20HQ|45GP)\s+(?:ETA\s*[::]?\s*)?(${LOOSE_DATE})\s*(.*)`, + "i", + ), + ); + if (bodyM) { + modules.container_no = modules.container_no || bodyM[1].toUpperCase(); + const ship = bodyM[2].trim().toUpperCase(); + if (ship && !/^(?:提柜|拆柜|派送)$/i.test(ship)) { + modules.shipping_line_hint = modules.shipping_line_hint || ship.split(/\s+/)[0]; + } + modules.cabinet_type = + modules.cabinet_type || bodyM[3].toUpperCase(); + const eta = normDateToken(bodyM[4], year); + if (eta) modules.eta = modules.eta || eta; + const service = (bodyM[5] || "").trim(); + if (service && /提|拆|派/.test(service)) { + Object.assign(modules, parseServiceSegment(service)); + } + } else { + // 宽松补字段 + if (!modules.container_no) { + const cn = cleaned.match(ISO_CN)?.[1]; + if (cn) modules.container_no = cn; + } + if (!modules.cabinet_type) { + const cab = cleaned.match(CABINET)?.[1]; + if (cab) modules.cabinet_type = cab.toUpperCase(); + } + if (!modules.eta) { + const etaM = cleaned.match( + new RegExp(String.raw`(?:ETA\s*[::]?\s*)?(${LOOSE_DATE})`, "i"), + ); + // 仅在明确 ETA 或主题已抽到客户时采信裸日期 + const labeled = cleaned.match( + new RegExp(String.raw`ETA\s*[::]?\s*(${LOOSE_DATE})`, "i"), + ); + const eta = normDateToken(labeled?.[1] || (modules.customer_name ? etaM?.[1] : undefined), year); + if (eta) modules.eta = eta; + } + if (!modules.shipping_line_hint) { + const alias = findShipAliasToken(cleaned); + if (alias) modules.shipping_line_hint = alias; + } + } + + // 目的港未给出时保持空,绝不从主题噪声回填 + delete modules.port; + + return modules.container_no || modules.bl_no || modules.customer_name + ? modules + : null; +} + +function mergeModules( + base: MailRecordModules, + extra: MailRecordModules | null, +): MailRecordModules { + if (!extra) return base; + return { + ...base, + customer_name: base.customer_name || extra.customer_name, + bl_no: base.bl_no || extra.bl_no, + container_no: base.container_no || extra.container_no, + port: base.port || extra.port, + cabinet_type: base.cabinet_type || extra.cabinet_type, + eta: base.eta || extra.eta, + etd: base.etd || extra.etd, + vessel_voyage: base.vessel_voyage || extra.vessel_voyage, + service: base.service || extra.service, + classis: base.classis ?? extra.classis, + shipping_line_hint: base.shipping_line_hint || extra.shipping_line_hint, + ops_hints: base.ops_hints?.length + ? base.ops_hints + : extra.ops_hints, + memo_bits: (() => { + const bits = [...(base.memo_bits || []), ...(extra.memo_bits || [])].filter( + (bit, i, arr) => + arr.findIndex((x) => x.toUpperCase() === bit.toUpperCase()) === i, + ); + return bits.length ? bits : undefined; + })(), + }; +} + +export function extractSpaceForecastFromMail( + subject: string, + body: string, + opts?: { defaultYear?: number }, +): { payload: string; modules: MailRecordModules } | null { + const year = opts?.defaultYear ?? new Date().getFullYear(); + const sub = stripForecastSubjectNoise(subject); + // 主题已是 + 模板 → 整封走 + 解析,空格路径不碰 + let modules: MailRecordModules = {}; + if (!usesPlusFieldDelimiter(sub)) { + modules = parseSpaceForecastTemplate(sub, { defaultYear: year }) || {}; + } + + for (const line of body.split(/\r?\n/)) { + const t = normalizeMailText(line); + if (!t || usesPlusFieldDelimiter(t)) continue; + if ( + !ISO_CN.test(t) && + !/ETA/i.test(t) && + !CABINET.test(t) && + !/提柜|派拆|卡派/.test(t) + ) { + continue; + } + const m = parseSpaceForecastTemplate(t, { defaultYear: year }); + if (m) modules = mergeModules(modules, m); + } + + // 整段正文再扫一遍(仅无 + 时) + const bodyNorm = normalizeMailText(body).slice(0, 2000); + if (!usesPlusFieldDelimiter(bodyNorm)) { + const bodyAll = parseSpaceForecastTemplate(bodyNorm, { defaultYear: year }); + modules = mergeModules(modules, bodyAll); + } + + if (!modules.container_no && !modules.bl_no) return null; + if (!modules.customer_name && !modules.eta && !modules.cabinet_type) { + return null; + } + + const payload = [ + modules.customer_name, + modules.bl_no, + modules.container_no, + modules.eta, + modules.service, + ] + .filter(Boolean) + .join(" "); + return { payload, modules: exclusiveFinalizeModules(modules) }; } /** 将 + 模板 modules 合并进柜头(空字段才填) */ @@ -222,11 +841,13 @@ export function mapBlModulesToHeader( ): ContainerHeader { const enableIso = opts?.enableIsoCheck ?? true; const opFromService = - modules.ops_hints?.includes("拆柜") || - modules.ops_hints?.includes("提柜") || - modules.ops_hints?.includes("派送") - ? (0 as const) - : undefined; + modules.ops_hints?.includes("直送") + ? (2 as const) + : modules.ops_hints?.includes("拆柜") || + modules.ops_hints?.includes("提柜") || + modules.ops_hints?.includes("派送") + ? (0 as const) + : undefined; return { ...header, F_ContainerNo: header.F_ContainerNo || modules.container_no || "", @@ -239,7 +860,10 @@ export function mapBlModulesToHeader( F_ETA: header.F_ETA || modules.eta || null, F_ETD: header.F_ETD || modules.etd || null, F_BLCopyCode: header.F_BLCopyCode || modules.bl_no || "", - F_Dock: header.F_Dock || modules.port || "", + F_Dock: + normalizeDestinationPort(header.F_Dock) || + normalizeDestinationPort(modules.port) || + "", F_Classis: header.F_Classis ?? (modules.classis !== undefined ? modules.classis : undefined), @@ -256,18 +880,27 @@ export function mapBlModulesToHeader( : undefined), F_MemoRemark: header.F_MemoRemark || - [modules.customer_name, modules.service].filter(Boolean).join(" / ") || - undefined, + (modules.memo_bits?.length ? modules.memo_bits.join(" / ") : undefined), }; } export function extractBlPlusFromMail( subject: string, body: string, + opts?: { defaultYear?: number }, ): { payload: string; modules: MailRecordModules } | null { + const year = opts?.defaultYear ?? new Date().getFullYear(); + // 柜号+提单+船名/ETA/渠道件数 → 工单主题,不作「客户+提单」预报 + if (extractOpsSubjectFromMail(subject, { defaultYear: year })) { + return null; + } const payload = findBlPlusPayload(subject, body); - if (!payload) return null; - const modules = parseBlPlusTemplate(payload); - if (!modules) return null; - return { payload, modules }; + if (payload) { + // 已确认是 + 分隔模板:只按 + 解析,失败也不回落到空格切分 + const modules = parseBlPlusTemplate(payload, { defaultYear: year }); + if (modules) return { payload: cleanJoinedPayload(payload), modules }; + return null; + } + // 无 + 字段分隔时,才按空格切分 + return extractSpaceForecastFromMail(subject, body, { defaultYear: year }); } diff --git a/src/services/parse/carrier-aliases.ts b/src/services/parse/carrier-aliases.ts new file mode 100644 index 0000000..cdf54bd --- /dev/null +++ b/src/services/parse/carrier-aliases.ts @@ -0,0 +1,115 @@ +/** + * 常见承运人简称库。 + * - shipping lines: 直接参与船司匹配 + * - express / airlines: 先统一沉淀,后续渠道/空运识别可复用 + */ + +export const SHIPPING_LINE_ALIASES: Record = { + COSCO: ["中国远洋海运集团有限公司", "中远海运", "中远海运集团"], + COSU: ["中国远洋海运集团有限公司", "中远海运", "中远海运集团", "COSCO"], + MSK: ["马士基", "A.P. Moller - Maersk", "MAERSK"], + MAERSK: ["马士基", "A.P. Moller - Maersk", "MSK"], + MSKU: ["马士基", "A.P. Moller - Maersk", "MSK", "MAERSK"], + /** Master B/L 前缀 */ + MAEU: ["马士基", "A.P. Moller - Maersk", "MSK", "MAERSK", "MSKU"], + MSC: ["地中海航运", "地中海"], + MSCU: ["地中海航运", "地中海", "MSC"], + MEDU: ["地中海航运", "地中海", "MSC"], + CMA: ["达飞轮船", "达飞海运集团", "达飞"], + "CMA CGM": ["达飞轮船", "达飞海运集团", "达飞"], + CMACGM: ["达飞轮船", "达飞海运集团", "达飞"], + CMDU: ["达飞轮船", "达飞海运集团", "达飞", "CMA"], + OOCL: ["东方海外", "东方海外货柜航运公司"], + OOLU: ["东方海外", "东方海外货柜航运公司", "OOCL"], + EMC: ["长荣海运", "长荣", "EVERGREEN"], + EVERGREEN: ["长荣海运", "长荣", "EMC"], + EMCU: ["长荣海运", "长荣", "EMC", "EVERGREEN"], + /** 长荣 Master B/L 标准前缀(如 EGLV143661466958) */ + EGLV: ["长荣海运", "长荣", "EMC", "EVERGREEN", "EMCU"], + HPL: ["赫伯罗特", "赫伯罗特船务", "HAPAG-LLOYD"], + HAPAG: ["赫伯罗特", "赫伯罗特船务", "HAPAG-LLOYD"], + HLCU: ["赫伯罗特", "赫伯罗特船务", "HPL", "HAPAG-LLOYD"], + ONE: ["海洋网联船务", "海洋网联", "OCEAN NETWORK EXPRESS"], + ONEY: ["海洋网联船务", "海洋网联", "ONE", "OCEAN NETWORK EXPRESS"], + YML: ["阳明海运", "阳明", "YANG MING MARINE TRANSPORT"], + YANGMING: ["阳明海运", "阳明", "YANG MING MARINE TRANSPORT"], + YMLU: ["阳明海运", "阳明", "YML", "YANG MING MARINE TRANSPORT"], + ZIM: ["以星综合航运", "以星航运", "以星"], + ZIMU: ["以星综合航运", "以星航运", "以星", "ZIM"], + HMM: ["现代商船", "韩新海运", "现代", "HYUNDAI MERCHANT MARINE"], + HDMU: ["现代商船", "韩新海运", "HMM", "HYUNDAI MERCHANT MARINE"], + PIL: ["太平船务", "太平", "PACIFIC INTERNATIONAL LINES"], + WHL: ["万海航运", "万海航运股份有限公司", "万海"], + WHLC: ["万海航运", "万海航运股份有限公司", "万海"], + "WAN HAI": ["万海航运", "万海航运股份有限公司", "万海"], + WANNHAI: ["万海航运", "万海航运股份有限公司", "万海"], + MATSON: ["美森轮船", "美森"], + MATS: ["美森轮船", "美森", "MATSON"], + APL: ["美国总统轮船", "总统轮船"], + APLU: ["美国总统轮船", "总统轮船", "APL"], + SML: ["森罗商船", "森罗", "SM LINE"], + "SM LINE": ["森罗商船", "森罗"], + SMLINE: ["森罗商船", "森罗"], +}; + +/** + * 提单前缀对应的可读船司名(用于提示;未命中返回 null) + */ +export function shippingLineDisplayNameFromHint( + hint: string | null | undefined, +): string | null { + const raw = (hint || "").trim().toUpperCase(); + if (!raw) return null; + const aliases = SHIPPING_LINE_ALIASES[raw] || SHIPPING_LINE_ALIASES[raw.slice(0, 4)]; + if (!aliases?.length) return null; + // 优先中文全名 + const cn = aliases.find((a) => /[\u4e00-\u9fff]/.test(a)); + return cn || aliases[0] || null; +} + +/** 已知海运提单前缀(4 位字母)→ 视为海运运输方式 */ +export function isSeaBlPrefix(prefix: string | null | undefined): boolean { + const p = (prefix || "").trim().toUpperCase(); + if (!p || p.length < 2) return false; + if (SHIPPING_LINE_ALIASES[p]) return true; + if (p.length >= 4 && SHIPPING_LINE_ALIASES[p.slice(0, 4)]) return true; + return false; +} + +export const EXPRESS_ALIASES: Record = { + DHL: ["DHL EXPRESS", "DHL GLOBAL FORWARDING"], + UPS: ["UNITED PARCEL SERVICE"], + FEDEX: ["FEDERAL EXPRESS", "FEDEX EXPRESS"], + TNT: ["TNT EXPRESS"], + SF: ["SF EXPRESS", "顺丰速运"], + "4PX": ["递四方"], + YUNEXPRESS: ["云途物流", "YUN EXPRESS"], +}; + +export const AIRLINE_ALIASES: Record = { + CA: ["AIR CHINA", "中国国际航空", "CCA"], + CCA: ["AIR CHINA", "中国国际航空", "CA"], + CZ: ["CHINA SOUTHERN AIRLINES", "中国南方航空", "CSN"], + CSN: ["CHINA SOUTHERN AIRLINES", "中国南方航空", "CZ"], + MU: ["CHINA EASTERN AIRLINES", "中国东方航空", "CES"], + CES: ["CHINA EASTERN AIRLINES", "中国东方航空", "MU"], + CX: ["CATHAY PACIFIC", "国泰航空", "CPA"], + CPA: ["CATHAY PACIFIC", "国泰航空", "CX"], + CI: ["CHINA AIRLINES", "中华航空", "CAL"], + CAL: ["CHINA AIRLINES", "中华航空", "CI"], + BR: ["EVA AIR", "长荣航空", "EVA"], + EVA: ["EVA AIR", "长荣航空", "BR"], + FX: ["FEDEX EXPRESS", "联邦快递", "FDX"], + FDX: ["FEDEX EXPRESS", "联邦快递", "FX"], + QR: ["QATAR AIRWAYS CARGO", "卡塔尔航空货运", "QTR"], + QTR: ["QATAR AIRWAYS CARGO", "卡塔尔航空货运", "QR"], + EK: ["EMIRATES SKYCARGO", "阿联酋航空货运", "UAE"], + UAE: ["EMIRATES SKYCARGO", "阿联酋航空货运", "EK"], + TK: ["TURKISH CARGO", "土耳其航空货运", "THY"], + THY: ["TURKISH CARGO", "土耳其航空货运", "TK"], + LH: ["LUFTHANSA CARGO", "汉莎货运", "DLH"], + DLH: ["LUFTHANSA CARGO", "汉莎货运", "LH"], + KE: ["KOREAN AIR CARGO", "大韩航空货运", "KAL"], + KAL: ["KOREAN AIR CARGO", "大韩航空货运", "KE"], + "5X": ["UNITED PARCEL SERVICE", "UPS", "联合包裹"], +}; diff --git a/src/services/parse/channel-map.ts b/src/services/parse/channel-map.ts index b1c2f94..dd00d71 100644 --- a/src/services/parse/channel-map.ts +++ b/src/services/parse/channel-map.ts @@ -1,9 +1,14 @@ +import { EXPRESS_ALIASES } from "@/services/parse/carrier-aliases"; + const CHANNEL_MAP: Array<{ keys: string[]; value: string }> = [ { keys: ["卡派", "TRUCK", "truck", "Truck"], value: "TRUCK" }, - { keys: ["UPS"], value: "UPS" }, - { keys: ["FEDEX", "FedEx", "fedex"], value: "FEDEX" }, - { keys: ["DHL"], value: "DHL" }, + { keys: ["UPS", ...EXPRESS_ALIASES.UPS], value: "UPS" }, + { keys: ["FEDEX", "FedEx", "fedex", ...EXPRESS_ALIASES.FEDEX, ...EXPRESS_ALIASES.TNT], value: "FEDEX" }, + { keys: ["DHL", ...EXPRESS_ALIASES.DHL], value: "DHL" }, { keys: ["USPS"], value: "USPS" }, + { keys: ["SF", ...EXPRESS_ALIASES.SF], value: "SF" }, + { keys: ["4PX", ...EXPRESS_ALIASES["4PX"]], value: "4PX" }, + { keys: ["YUNEXPRESS", "YunExpress", ...EXPRESS_ALIASES.YUNEXPRESS], value: "YUNEXPRESS" }, { keys: ["自提", "PickUp", "PICKUP", "Pickup"], value: "自提" }, // V1.72 SaveContainer:派送方式为「留仓」;客户表常写「存仓」 { keys: ["存仓", "Storage", "STORAGE", "留仓"], value: "留仓" }, diff --git a/src/services/parse/excel-cell-text.ts b/src/services/parse/excel-cell-text.ts new file mode 100644 index 0000000..8aece03 --- /dev/null +++ b/src/services/parse/excel-cell-text.ts @@ -0,0 +1,22 @@ +/** 把 ExcelJS 单元格值收成可读字符串(含换行、富文本)。 */ +export function flattenExcelCellValue(raw: unknown): string { + if (raw == null) return ""; + if (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean") { + return String(raw); + } + if (raw instanceof Date) return raw.toISOString(); + if (typeof raw !== "object") return ""; + + const o = raw as { + richText?: Array<{ text?: string }>; + text?: unknown; + result?: unknown; + hyperlink?: unknown; + }; + if (Array.isArray(o.richText)) { + return o.richText.map((p) => p.text || "").join(""); + } + if (o.text != null && o.text !== "") return flattenExcelCellValue(o.text); + if ("result" in o) return flattenExcelCellValue(o.result); + return ""; +} diff --git a/src/services/parse/extract-container-header.ts b/src/services/parse/extract-container-header.ts index a1b04d0..4a52fad 100644 --- a/src/services/parse/extract-container-header.ts +++ b/src/services/parse/extract-container-header.ts @@ -1,32 +1,45 @@ import type { ContainerHeader, OperationType, TransMode } from "@/types/mail"; -import { toShanghaiDateString } from "@/utils/dates"; +import { toLooseShanghaiDateString } from "@/utils/dates"; +import { + AIRLINE_ALIASES, + SHIPPING_LINE_ALIASES, + isSeaBlPrefix, +} from "@/services/parse/carrier-aliases"; +import { normalizeDestinationPort } from "@/services/parse/bl-plus-template"; +import { matchUsDestinationPort } from "@/services/parse/us-ports"; const CABINET_RE = /\b(45HC|45HQ|40HC|40HQ|40GP|20GP|20HQ|45GP)\b/i; -const ETA_RE = - /(?:ETA|预计抵港|抵港日|到港)\s*[::\s]*([0-9]{4}[-/.][0-9]{1,2}[-/.][0-9]{1,2})/i; -const ETD_RE = - /(?:EDT|ETD|预计开船|开船日|离港)\s*[::\s]*([0-9]{4}[-/.][0-9]{1,2}[-/.][0-9]{1,2})/i; +const LOOSE_DATE = String.raw`(?:\d{4}[-/.]\d{1,2}[-/.]\d{1,2}|\d{1,2}[-/.]\d{1,2})`; +const ETA_RE = new RegExp( + String.raw`(?:ETA|预计抵港|抵港日|到港)\s*[::\s]*(${LOOSE_DATE})`, + "i", +); +const ETD_RE = new RegExp( + String.raw`(?:EDT|ETD|预计开船|开船日|离港)\s*[::\s]*(${LOOSE_DATE})`, + "i", +); const BL_RE = /(?:提单(?:号)?|B\/?L(?:\s*(?:No\.?|号))?|BLCopy)\s*[::#\s]*([A-Z0-9][A-Z0-9\-_/]{3,31})/i; /** 无标签提单号(常见船司前缀+数字,如 WHLC027G597465) */ const BARE_BL_RE = /\b([A-Z]{4}[A-Z0-9]{6,28})\b/; const LOAD_PORT_RE = /(?:装货港|起运港|POL)\s*[::\s]*([^\n\r,,;;]{1,40})/i; -const DOCK_RE = /(?:码头|卸货码头|POD\s*码头)\s*[::\s]*([^\n\r,,;;]{1,40})/i; +const DOCK_RE = + /(?:目的港|卸货港|码头|卸货码头|POD(?:\s*码头)?)\s*[::\s]*([^\n\r,,;;+]{1,40})/i; const SHIP_LINE_RE = /(?:船司|船公司|Shipping\s*Line|Carrier)\s*[::\s]*([^\n\r,,;;]{1,60})/i; const VESSEL_RE = /船名航次\s*([^\n\r+]{2,60})/i; const CLASSIS_RE = /(?:带车架|带托架)/; const NO_CLASSIS_RE = /(?:不带车架|不带托架|无车架)/; +const SEAL_NO_RE = + /(?:封号|铅封(?:号)?|封条号?)\s*[::]\s*([A-Z0-9][A-Z0-9\-_/]{3,31})/gi; const ISO_CN_RE = /^[A-Z]{4}\d{7}$/; - export type MailHeaderExtract = Partial & { /** 船司名称原文,供后续模糊匹配 F_ShippingLineId */ shipping_line_hint?: string; }; function normDate(raw: string | undefined): string | null { - if (!raw) return null; - return toShanghaiDateString(raw.replace(/\./g, "-").replace(/\//g, "-")); + return toLooseShanghaiDateString(raw); } /** 提单号前缀字母 → 船司代码线索(WHLC→WHLC;WHL063→WHL) */ @@ -39,6 +52,44 @@ export function shippingLineHintFromBl( return m ? m[1] : null; } +const AIRLINE_HINTS = (() => { + const out = new Set(); + for (const [code, aliases] of Object.entries(AIRLINE_ALIASES)) { + if (code.length >= 3) out.add(code); + for (const alias of aliases) { + if (alias.length >= 3) out.add(alias); + } + } + return [...out]; +})(); + +function containsAirlineHint(text: string): boolean { + const upper = text.toUpperCase(); + return AIRLINE_HINTS.some((token) => { + const t = token.toUpperCase(); + if (/^[A-Z0-9\s-]+$/.test(t)) { + const safe = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + return new RegExp(`(?:^|[^A-Z0-9])${safe}(?:[^A-Z0-9]|$)`).test(upper); + } + return upper.includes(t); + }); +} + +const SHIPPING_LINE_HINT_TOKENS = Object.keys(SHIPPING_LINE_ALIASES) + .filter((k) => k.length >= 3) + .sort((a, b) => b.length - a.length); + +function findShippingLineAliasInText(text: string): string | null { + const upper = text.toUpperCase(); + for (const token of SHIPPING_LINE_HINT_TOKENS) { + const safe = token.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + if (new RegExp(`(?:^|[^A-Z0-9])${safe}(?:[^A-Z0-9]|$)`).test(upper)) { + return token; + } + } + return null; +} + /** * 从主题+正文抽取柜头字段(卡派表通常无这些列)。 * 不覆盖已有非空字段(由 mergeContainerHeader 控制)。 @@ -78,9 +129,13 @@ export function extractContainerHeaderFromText( if (port?.[1]) out.F_LoadPort = port[1].trim(); const dock = text.match(DOCK_RE); - if (dock?.[1]) out.F_Dock = dock[1].trim(); + const dockMatched = dock?.[1] ? matchUsDestinationPort(dock[1]) : null; + // 仅「目的港/卸货港/POD」显式标签 + 白名单;不扫全文乱截 + if (dockMatched) { + out.F_Dock = dockMatched; + } - // 船司线索优先级:显式「船司」> 提单前缀 > 船名首词 + // 船司线索优先级:显式「船司」> 提单前缀 > 正文已知船司简称 > 船名首词 const line = text.match(SHIP_LINE_RE); if (line?.[1]) { out.shipping_line_hint = line[1].trim(); @@ -89,9 +144,14 @@ export function extractContainerHeaderFromText( if (blHint) { out.shipping_line_hint = blHint; } else { - const vessel = text.match(VESSEL_RE); - if (vessel?.[1]) { - out.shipping_line_hint = vessel[1].trim().split(/\s+/)[0]; + const aliasHint = findShippingLineAliasInText(text); + if (aliasHint) { + out.shipping_line_hint = aliasHint; + } else { + const vessel = text.match(VESSEL_RE); + if (vessel?.[1]) { + out.shipping_line_hint = vessel[1].trim().split(/\s+/)[0]; + } } } } @@ -99,6 +159,16 @@ export function extractContainerHeaderFromText( if (NO_CLASSIS_RE.test(text)) out.F_Classis = 0; else if (CLASSIS_RE.test(text)) out.F_Classis = 1; + const sealBits: string[] = []; + for (const m of text.matchAll(SEAL_NO_RE)) { + const label = (m[0].match(/^(封号|铅封(?:号)?|封条号?)/i)?.[1] || "封号").trim(); + const value = (m[1] || "").trim().toUpperCase(); + if (value) sealBits.push(`${label}:${value}`); + } + if (sealBits.length) { + out.F_MemoRemark = [...new Set(sealBits)].join(" / "); + } + // 提拆派优先于直送(二者可同时出现在说明文案中) if (/提拆派/.test(text)) { out.F_OperationType = 0 as OperationType; @@ -106,14 +176,22 @@ export function extractContainerHeaderFromText( out.F_OperationType = 2 as OperationType; } - // 有柜型/提单/船名航次 → 默认海运 + const hasAirHint = containsAirlineHint(text); + const blPrefix = shippingLineHintFromBl(out.F_BLCopyCode); + const seaFromBlPrefix = isSeaBlPrefix(blPrefix) || isSeaBlPrefix(out.shipping_line_hint); + + // 有柜型/船名航次/已知海运提单前缀(EGLV/COSU…)→ 海运 + // 仅裸号时不要压过已识别出的空运航司线索 if ( out.F_CabinetType || - out.F_BLCopyCode || VESSEL_RE.test(text) || - /船名航次/.test(text) + /船名航次/.test(text) || + seaFromBlPrefix || + (out.F_BLCopyCode && !hasAirHint) ) { out.F_TransMode = 0 as TransMode; + } else if (hasAirHint) { + out.F_TransMode = 1 as TransMode; } return out; @@ -140,7 +218,7 @@ export function mergeContainerHeader( F_ETD: pick("F_ETD", fromMail.F_ETD ?? null) ?? null, F_BLCopyCode: pick("F_BLCopyCode", fromMail.F_BLCopyCode) ?? "", F_LoadPort: pick("F_LoadPort", fromMail.F_LoadPort) ?? "", - F_Dock: pick("F_Dock", fromMail.F_Dock) ?? "", + F_Dock: normalizeDestinationPort(pick("F_Dock", fromMail.F_Dock)) || "", F_Classis: (pick("F_Classis", fromMail.F_Classis) ?? base.F_Classis) as | 0 | 1 @@ -175,6 +253,27 @@ function prefixTokens(raw: string | undefined): string[] { .filter(Boolean); } +function normalizeShippingLineHints(hint: string | undefined | null): string[] { + const raw = (hint || "").trim(); + if (!raw) return []; + const upper = raw.toUpperCase(); + const out = new Set([raw, raw.toLowerCase(), upper, upper.toLowerCase()]); + const seed = [upper, upper.slice(0, 4)].filter((x) => x.length >= 2); + for (const key of seed) { + for (const alias of SHIPPING_LINE_ALIASES[key] || []) { + out.add(alias); + out.add(alias.toLowerCase()); + // 二级展开:EGLV → EMC → 长荣海运 + const aliasKey = alias.toUpperCase(); + for (const nested of SHIPPING_LINE_ALIASES[aliasKey] || []) { + out.add(nested); + out.add(nested.toLowerCase()); + } + } + } + return [...out]; +} + /** * 船司匹配:提单前缀 hint(如 WHLC)→ F_Prefix / F_ShippingLineCode / 名称。 * 优先最长 prefix/code,避免 WHL 抢先于 WHLC。 @@ -184,7 +283,8 @@ export function matchShippingLineId( lines: ShippingLineMatchInput[], options?: { blNo?: string | null }, ): string | null { - const q = (hint || "").trim().toLowerCase(); + const hints = normalizeShippingLineHints(hint); + const q = hints[0]?.toLowerCase() || ""; const bl = (options?.blNo || "").trim().toUpperCase(); if ((!q && !bl) || lines.length === 0) return null; @@ -209,8 +309,11 @@ export function matchShippingLineId( const exact = lines.find( (l) => - (l.F_FullName || "").toLowerCase() === q || - (l.F_EnCode || "").toLowerCase() === q, + hints.some( + (h) => + (l.F_FullName || "").toLowerCase() === h.toLowerCase() || + (l.F_EnCode || "").toLowerCase() === h.toLowerCase(), + ), ); if (exact) return exact.F_Id; @@ -230,10 +333,13 @@ export function matchShippingLineId( const partial = lines.find((l) => { const name = (l.F_FullName || "").toLowerCase(); const code = (l.F_EnCode || "").toLowerCase(); - return ( - (name && (name.includes(q) || q.includes(name))) || - (code && (code.includes(q) || q.includes(code))) - ); + return hints.some((h) => { + const x = h.toLowerCase(); + return ( + (name && (name.includes(x) || x.includes(name))) || + (code && (code.includes(x) || x.includes(code))) + ); + }); }); return partial?.F_Id ?? null; } diff --git a/src/services/parse/instruction-execution-state.ts b/src/services/parse/instruction-execution-state.ts new file mode 100644 index 0000000..a6e6908 --- /dev/null +++ b/src/services/parse/instruction-execution-state.ts @@ -0,0 +1,98 @@ +import { extractMailInstructions, type MailInstructionUnit } from "@/services/parse/split-instructions"; +import type { MailStatus } from "@/types/mail"; + +export type InstructionExecutionStateEntry = { + status: "IMPORTED"; + importedAt: string; + importedBy?: string; + mode?: string; + externalId?: string | null; +}; + +export type InstructionExecutionStateMap = Record< + string, + InstructionExecutionStateEntry +>; + +type LineageWithInstructionState = { + instruction_state?: InstructionExecutionStateMap; + mail_record?: { work_order_actions?: string[] } | null; +}; + +export function getInstructionExecutionStateMap( + lineage: unknown, +): InstructionExecutionStateMap { + return ( + (lineage as LineageWithInstructionState | null | undefined)?.instruction_state || + {} + ); +} + +export function getInstructionExecutionState( + lineage: unknown, + unitId: string, +): InstructionExecutionStateEntry | null { + return getInstructionExecutionStateMap(lineage)[unitId] || null; +} + +export function isInstructionImported( + lineage: unknown, + unitId: string, +): boolean { + return getInstructionExecutionState(lineage, unitId)?.status === "IMPORTED"; +} + +export function markInstructionImported( + lineage: unknown, + unitId: string, + entry: InstructionExecutionStateEntry, +): Record { + const base = (lineage && typeof lineage === "object" ? lineage : {}) as Record< + string, + unknown + >; + const current = getInstructionExecutionStateMap(base); + return { + ...base, + instruction_state: { + ...current, + [unitId]: entry, + }, + }; +} + +export function listActionableInstructionUnits(input: { + subject: string; + body?: string | null; + filenames?: string[]; + lineage?: unknown; +}): MailInstructionUnit[] { + const workOrderActions = + (input.lineage as LineageWithInstructionState | null | undefined)?.mail_record + ?.work_order_actions || []; + return extractMailInstructions({ + subject: input.subject, + body: input.body, + filenames: input.filenames, + workOrderActions, + }).filter((u) => + ["forecast", "transfer", "work_order", "do_upload"].includes(u.uiKind), + ); +} + +export function resolveMailStatusAfterInstructionImport(input: { + currentStatus: MailStatus; + subject: string; + body?: string | null; + filenames?: string[]; + lineage?: unknown; +}): MailStatus { + const units = listActionableInstructionUnits(input); + if (!units.length) return input.currentStatus; + const importedCount = units.filter((u) => + isInstructionImported(input.lineage, u.id), + ).length; + if (importedCount <= 0) return "PENDING_CONFIRM"; + if (importedCount >= units.length) return "SUCCESS"; + return "PARTIAL_SUCCESS"; +} diff --git a/src/services/parse/instruction-lexicon.ts b/src/services/parse/instruction-lexicon.ts index 7b61eaa..8057c5f 100644 --- a/src/services/parse/instruction-lexicon.ts +++ b/src/services/parse/instruction-lexicon.ts @@ -6,8 +6,12 @@ * * 拆分原则: * 1. 按「在 yyyy-mm-dd … 写道」时间轮、以及「原始邮件 / 转发的邮件 / Original」标签切开 - * 2. 标准格式 → 四类业务 UI;无法归类或非标准 → 客户指令 + * - 对应用户在邮件界面里看到的一圈圈红框:每个「xx 在 xx 写道」就是一轮独立指令 + * - 同行嵌套引用(`name 在 yyyy… 写道`)也必须切开,不能并到上一轮 + * 2. 标准格式 → 四类业务 UI;除「新增预报 / 批量转仓 / 上传DO」外,其他可执行业务默认走工单 * 3. 仓库 ACK /「无法接收」/ QQ 转发壳 → 跳过 + * 4. 邮件标题 / 主旨通常是线程最早一轮的摘要,用来补齐预报底稿;不可覆盖后续日期更晚的动作指令 + * 5. 纯信息性内容(如:好的 / 无法接收 / 按正常预报接受 / 继续暂存)保留为 customer_instruction,不进入新建工单 */ export type InstructionUiKind = @@ -21,11 +25,14 @@ export type InstructionUiKind = export const SEGMENT_BOUNDARY_PATTERNS: RegExp[] = [ /-{2,}\s*原始电子邮件\s*-{2,}/i, /-{2,}\s*原始邮件\s*-{2,}/i, + /-{2,}\s*回复的原邮件\s*-{2,}/i, /-{2,}\s*转发的邮件\s*-{2,}/i, /-{0,2}\s*转发的邮件\s*-{0,2}/i, /-{2,}\s*Forwarded message\s*-{2,}/i, + /Forwarded message\s*:/i, /Original\s*Message/i, /Original\s*:/i, + /From[::][^\n]+(?:\n\s*)+Date[::]/i, // 在 2026-05-25 13:45:03 , "xxx" 写道: /在\s*\d{4}[-/.]\d{1,2}[-/.]\d{1,2}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?[^\n]{0,160}写道[::]/i, /在\s*\d{4}年\d{1,2}月\d{1,2}日[^\n]{0,160}写道[::]/i, @@ -35,6 +42,14 @@ export const SEGMENT_BOUNDARY_PATTERNS: RegExp[] = [ /From[::]\s*[^\n]+\n(?:Sent|Date|日期)[::]/i, ]; +/** + * 同行嵌套引用:`name|>)(?:\s| )*在\s*\d{4}[-/.]\d{1,2}[-/.]\d{1,2}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?[^\n]{0,160}写道[::]/i, + /(?:>|>)(?:\s| )*在\s*\d{4}年\d{1,2}月\d{1,2}日[^\n]{0,160}写道[::]/i, +]; + /** 弱切:独立成行的指令起句(不再用 Dear 切,避免同轮内误切) */ export const SEGMENT_START_LINE_RE = /^(新增预报|请查收新增预报|新增转仓|更新派送单|拆柜清单|派送要求|换标|覆盖贴|贴标指令|出库|留仓|拆分|DO请查收|请查收\s*DO|请查收附件\s*DO)/i; @@ -48,7 +63,7 @@ export type KeywordRule = { /** * 关键词 → UI - * 标准四类优先;软词 / 非标表述 → customer_instruction + * 标准四类优先;软词业务默认并入 work_order;仅纯信息性内容保留 customer_instruction */ export const KEYWORD_RULES: KeywordRule[] = [ { @@ -76,6 +91,18 @@ export const KEYWORD_RULES: KeywordRule[] = [ re: /新增预报|请查收新增预报/i, where: ["subject", "body"], }, + { + uiKind: "forecast", + keyword: "派送单请查收", + re: /柜号[::\s]*[A-Z]{4}\d{7}\s+派送单[,,]\s*请查收/i, + where: ["body"], + }, + { + uiKind: "forecast", + keyword: "提柜预报", + re: /提柜[^\n]{0,80}(?:40HQ|20GP|40GP|45HQ|ETA|ETD)[^\n]{0,80}(?:卡派|拆柜|派送)/i, + where: ["body"], + }, { uiKind: "transfer", @@ -100,7 +127,7 @@ export const KEYWORD_RULES: KeywordRule[] = [ { uiKind: "work_order", keyword: "拍照", - re: /拍照|贴好拍照|开箱拍/i, + re: /拍照|贴好拍照|开箱拍|拍一下照片|拍.{0,4}照片/i, where: ["subject", "body"], }, { @@ -119,23 +146,35 @@ export const KEYWORD_RULES: KeywordRule[] = [ // 非标准 / 软词 → 客户指令(不套工单表单) { - uiKind: "customer_instruction", + uiKind: "work_order", keyword: "更新派送单", re: /更新派送单/i, where: ["subject", "body"], }, { - uiKind: "customer_instruction", + uiKind: "work_order", keyword: "改自提", re: /改自提|改为仓库自提|仓库自提/i, where: ["subject", "body"], }, { - uiKind: "customer_instruction", + uiKind: "work_order", + keyword: "改地址派送", + re: /改地址派送|改地址|派送到\s*\d|送到\s*\d|地址派送/i, + where: ["subject", "body"], + }, + { + uiKind: "work_order", keyword: "拆柜清单", re: /拆柜清单|卡转海|派送要求/i, where: ["subject", "body", "attachment"], }, + { + uiKind: "work_order", + keyword: "派送时效", + re: /点前送达|下午\s*\d+\s*点前|美时.*送达/i, + where: ["body"], + }, { uiKind: "customer_instruction", keyword: "接受预报", @@ -155,7 +194,7 @@ export const ATTACHMENT_HINT_RE = /** 正文里的「硬动作词」——有则忽略 Re: 链路上的历史「新增预报」主题 */ export const BODY_ACTION_RE = - /换标|覆盖贴|贴标|拍照|拦截|新增转仓|更新派送单|改自提|DO请查收|拆柜清单|卡转海|开箱拍|操作指令|请查收新增预报|安排派单|按照正常预报接受/i; + /换标|覆盖贴|贴标|拍照|拍一下照片|拍.{0,4}照片|拦截|新增转仓|更新派送单|改自提|改地址派送|改地址|地址派送|派送到\s*\d|送到\s*\d|DO请查收|拆柜清单|卡转海|开箱拍|操作指令|请查收新增预报|柜号[::\s]*[A-Z]{4}\d{7}\s+派送单[,,]\s*请查收|安排派单|按照正常预报接受|点前送达/i; export function isNegatedTransfer(text: string): boolean { return /不转仓/.test(text); @@ -182,7 +221,8 @@ function stripSubjectLines(text: string): string { if ( BODY_ACTION_RE.test(line) || /Dear[,,]/.test(t) || - /(?:新增转仓|新增预报|请查收|更新派送单)/.test(t) || + /(?:新增转仓|新增预报|请查收|更新派送单|提柜)/.test(t) || + /^[A-Z]{4}\d{7}\b/.test(t) || /^\d+[\.\u3001\uff1a:]/.test(t) ) { inSubject = false; @@ -228,7 +268,8 @@ export function isForwardShellSegment(text: string): boolean { if ( /date@usasinogroup\.com/i.test(compact) && /寄件人|主旨/.test(compact) && - !/Dear[,,]/.test(compact) + !/Dear[,,]/.test(compact) && + !BODY_ACTION_RE.test(bodyOnly) ) { return true; } @@ -245,11 +286,14 @@ export function isForwardShellSegment(text: string): boolean { /寄件人/.test(compact) && /date@usasinogroup\.com/i.test(compact) && bodyCompact.length < 120 && - !/Dear[,,]/.test(bodyOnly) + !/Dear[,,]/.test(bodyOnly) && + !BODY_ACTION_RE.test(bodyOnly) ) { return true; } + if (BODY_ACTION_RE.test(bodyOnly)) return false; + return false; } @@ -264,7 +308,7 @@ export function isSubjectEchoSegment(text: string): boolean { // 有真实业务起句 → 不是主题回声 if ( - /Dear[,,]|请查收新增预报|请查收\s*DO|DO请查收|新增转仓|更新派送单|操作指令|换标后单号|覆盖贴|贴好拍照|原箱号/.test( + /Dear[,,]|请查收新增预报|请查收\s*DO|DO请查收|新增转仓|更新派送单|改地址派送|改地址|地址派送|派送到\s*\d|送到\s*\d|提柜[^\n]{0,80}(?:40HQ|20GP|40GP|45HQ|ETA|ETD)[^\n]{0,80}(?:卡派|拆柜|派送)|操作指令|换标后单号|覆盖贴|贴好拍照|原箱号|柜号[::\s]*[A-Z]{4}\d{7}\s+派送单[,,]\s*请查收/.test( raw, ) ) { @@ -303,7 +347,7 @@ export function segmentBodyOnly(text: string): string { return stripSubjectLines(text) .replace(/\\n/g, "\n") .replace( - /^在\s*\d{4}[-/.年][^\n]{0,160}写道[::]?\s*/i, + /^(?:>|>)?\s*在\s*\d{4}[-/.年][^\n]{0,160}写道[::]?\s*/i, "", ) .replace(/^Original\s*:\s*/i, "") @@ -365,7 +409,7 @@ export function extractRoundMeta(segment: string): { /在\s*(\d{4}[-/.年]\d{1,2}[-/.月]\d{1,2}日?(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?)/.exec( segment, )?.[1] || - /(?:发送时间|日期|Date|Sent)\s*[::]\s*([^\n(]+)/i.exec(segment)?.[1]?.trim() || + /(?:发送时间|发送日期|日期|Date|Sent)\s*[::]\s*([^\n(]+)/i.exec(segment)?.[1]?.trim() || /(\d{4}年\d{1,2}月\d{1,2}日\s*\d{1,2}:\d{2})/.exec(segment)?.[1]; const from = chrome.from || @@ -454,7 +498,7 @@ export function extractForwardChromeMeta(text: string): ForwardChromeMeta { // 时间/日期 const atM = - /^(?:日期|时间|发送时间)\s*[::]?\s*(.+)$/i.exec(t) || + /^(?:日期|时间|发送时间|发送日期)\s*[::]?\s*(.+)$/i.exec(t) || /^Date\s*[::]\s*(.+)$/i.exec(t) || /^Sent\s*[::]\s*(.+)$/i.exec(t); if (atM?.[1]) { @@ -483,9 +527,10 @@ export function extractForwardChromeMeta(text: string): ForwardChromeMeta { // 主题续行:短行、无业务动作、未到正文起句 if (collectingSubject) { if ( - /Dear[,,]|请查收|新增预报|新增转仓|换标|贴标|转仓|柜号|操作|ETA|ETD/i.test( + /Dear[,,]|请查收|新增预报|新增转仓|换标|贴标|转仓|柜号|提柜|操作/i.test( t, ) || + /^[A-Z]{4}\d{7}\b/.test(t) || /^(?:发件人|收件人|日期|时间)/.test(t) ) { flushSubject(); @@ -572,7 +617,7 @@ export function stripMobileMailChrome(text: string): string { // 头部字段行(可无冒号:发件人cs3<…> / 主题 TIIU…) if ( - /^(?:发件人|收件人|抄送|寄件人|日期|发送时间|主题|主旨)(?:\s*[::]\s*|\s+)/.test( + /^(?:发件人|收件人|抄送|寄件人|日期|发送时间|发送日期|主题|主旨)(?:\s*[::]\s*|\s+)/.test( t, ) || /^(?:发件人|收件人|抄送|寄件人)(?:[0-9a-zA-Z_<<])/.test(t) || @@ -584,7 +629,7 @@ export function stripMobileMailChrome(text: string): string { // 仅「日期/时间 + 日历」整行 if ( - /^(?:日期|时间|发送时间)\s*[::]?\s*\d{4}/.test(t) || + /^(?:日期|时间|发送时间|发送日期)\s*[::]?\s*\d{4}/.test(t) || (/^\d{4}年\d{1,2}月\d{1,2}日\s*\d{1,2}:\d{2}/.test(t) && t.length < 40) ) { inForwardChrome = true; @@ -609,6 +654,16 @@ export function stripMobileMailChrome(text: string): string { continue; } + // Inline nested quote: `name|>)\s*在\s*\d{4}[-/.年][^\n]{0,160}写道[::]?\s*/i; + if (inlineQuote.test(t)) { + const rest = t.replace(inlineQuote, "").trim(); + inForwardChrome = false; + if (rest) kept.push(rest.replace(/\s+$/g, "")); + continue; + } + // 转发头收件人断行续行:只剩邮箱/顿号/尖括号,无业务动作词 if (inForwardChrome && isForwardHeaderContinuation(t)) { continue; @@ -623,6 +678,7 @@ export function stripMobileMailChrome(text: string): string { .join("\n") .replace(/[ \t]{2,}/g, " ") .replace(/\n{3,}/g, "\n\n") + .replace(/\n?\s*[^\n]{0,48}<[^>\n]{0,80}\s*$/i, "") .trim(); } diff --git a/src/services/parse/label-instruction.ts b/src/services/parse/label-instruction.ts index 6650479..ddc3a2b 100644 --- a/src/services/parse/label-instruction.ts +++ b/src/services/parse/label-instruction.ts @@ -149,6 +149,8 @@ export async function parseLabelInstructionBuffer( return t; }; const wh = clean(raw.address).toUpperCase(); + const warehouseOnly = + /^(HOLD|STORAGE|留仓)$/i.test(wh) || /^[A-Z]{3}\d$/i.test(wh); const channelRaw = clean(raw.transporter); const mapped = mapChannel(channelRaw || "卡派"); const ctns = @@ -182,7 +184,7 @@ export async function parseLabelInstructionBuffer( F_ReferenceId: po || null, F_FBAID: pickFbaId(raw), F_ShipmentID: markId || clean(raw.oldFba) || null, - F_Address: clean(raw.address) || null, + F_Address: warehouseOnly ? null : clean(raw.address) || null, F_Remark: remarkParts.join(";") || null, channel_raw: channelRaw || "卡派", }); diff --git a/src/services/parse/mail-intent.test.ts b/src/services/parse/mail-intent.test.ts index 62a31c5..7108340 100644 --- a/src/services/parse/mail-intent.test.ts +++ b/src/services/parse/mail-intent.test.ts @@ -3,6 +3,7 @@ import { buildMailIntent, extractTransferPairs, filterTransferShipments, + filterTransferShipmentsByPairs, } from "@/services/parse/mail-intent"; import type { ShipmentRow } from "@/types/mail"; @@ -34,6 +35,33 @@ describe("mail-intent", () => { ]); }); + it("filterTransferShipmentsByPairs keeps only rows for the segment pair", () => { + const rows: ShipmentRow[] = [ + { + row_index: 1, + row_status: "VALID", + F_FBACode: "PSC2", + F_Remark: "转POC2", + }, + { + row_index: 2, + row_status: "VALID", + F_FBACode: "PSC2", + F_Remark: "转POC2", + }, + { + row_index: 3, + row_status: "VALID", + F_FBACode: "FWA4", + F_Remark: "转VGT2", + }, + ]; + const filtered = filterTransferShipmentsByPairs(rows, [ + { from: "PSC2", to: "POC2", rows: 2 }, + ]); + expect(filtered.map((r) => r.row_index)).toEqual([1, 2]); + }); + it("extracts ABQ2→POC2 and FWA4→VGT2 from remarks", () => { const rows: ShipmentRow[] = [ { diff --git a/src/services/parse/mail-intent.ts b/src/services/parse/mail-intent.ts index 0a57a2c..806f67b 100644 --- a/src/services/parse/mail-intent.ts +++ b/src/services/parse/mail-intent.ts @@ -75,6 +75,26 @@ export function filterTransferShipments( return shipments.filter(isTransferShipmentRow); } +/** 按本段转仓对过滤货件行(FBACode + 备注「转目标仓」) */ +export function filterTransferShipmentsByPairs( + shipments: ShipmentRow[], + pairs: TransferPair[], +): ShipmentRow[] { + const transferRows = filterTransferShipments(shipments); + if (!pairs.length) return transferRows; + const pairKeys = new Set( + pairs.map((p) => `${p.from.toUpperCase()}->${p.to.toUpperCase()}`), + ); + return transferRows.filter((row) => { + const from = (row.F_FBACode || "").trim().toUpperCase(); + const remark = String(row.F_Remark || ""); + const m = TRANSFER_REMARK_RE.exec(remark); + if (!from || !m) return false; + const to = m[1].toUpperCase(); + return pairKeys.has(`${from}->${to}`); + }); +} + /** * INSTRUCTION_HOLD_SPLIT 子意图:出库 > 拆分 > 留仓(默认)。 * 「拆柜清单」 alone 不算拆分。 diff --git a/src/services/parse/match-segment-attachments.ts b/src/services/parse/match-segment-attachments.ts new file mode 100644 index 0000000..f36476c --- /dev/null +++ b/src/services/parse/match-segment-attachments.ts @@ -0,0 +1,120 @@ +/** + * 附件 ↔ 指令段对应(全邮件通用)。 + * 规则真源:docx/产品规则-邮件指令识别与拆分.md §6 附件挂载。 + */ +import { isDoAttachmentFilename } from "./do-upload-extract"; + +/** FBA / 原箱号 YT / FNSKU / WL 唛头。不用 ISO 柜号分发工单附件。 */ +const ID_TOKEN_RE = + /\b(FBA[A-Z0-9]{6,}|YT\d{8,}|X[0-9][A-Z0-9]{6,}|WL\d{4,})\b/gi; + +const SCREENSHOT_RE = /邮箱\.pdf$/i; +const PACKING_XLSX_RE = /卡派资料|装箱清单|packing/i; +const ROLE_SEA_RE = /卡转海/i; +const ROLE_LABEL_RE = /换标|覆盖贴|贴标指令|操作指令|贴标交付/i; + +export function isScreenshotAttachment(filename: string): boolean { + const f = String(filename || "").trim(); + return !f || SCREENSHOT_RE.test(f) || /^QQ邮箱/i.test(f); +} + +export function extractAttachIdTokens(text: string): string[] { + return [ + ...new Set( + (String(text || "").match(ID_TOKEN_RE) || []).map((t) => t.toUpperCase()), + ), + ]; +} + +export function attachmentFileStem(filename: string): string { + return String(filename || "") + .replace(/\.[^.]+$/, "") + .replace(/[((]\d+[))]/g, "") + .trim(); +} + +function hasSeaRole(text: string): boolean { + return ROLE_SEA_RE.test(text || ""); +} + +function hasLabelRole(text: string): boolean { + return ROLE_LABEL_RE.test(text || ""); +} + +function isPackingSourceFile(filename: string): boolean { + return /\.(xlsx|xls|csv)$/i.test(filename) && PACKING_XLSX_RE.test(filename); +} + +export type MatchSegmentAttachmentsInput = { + segmentText: string; + segmentSubject?: string; + filenames: string[]; + /** 角色兜底只给当前可确认段,避免历史卡误挂无主键附件 */ + isCurrent?: boolean; +}; + +/** + * 本段应挂的业务附件(工单 F_AttachFile / 出库 F_LabelFile)。 + * 1) 主键交叉 2) 正文点名文件名 3) 当前段角色兜底(卡转海/换标) + * 跳过截图、DO;卡派 xlsx 仅当正文点名才进工单。 + */ +export function matchSegmentAttachments( + input: MatchSegmentAttachmentsInput, +): string[] { + const filenames = (input.filenames || []).filter(Boolean); + if (!filenames.length) return []; + + const seg = `${input.segmentSubject || ""}\n${input.segmentText || ""}`; + const segUpper = seg.toUpperCase(); + const segIds = extractAttachIdTokens(seg); + const isCurrent = input.isCurrent !== false; + const out: string[] = []; + + for (const filename of filenames) { + if (isScreenshotAttachment(filename)) continue; + if (isDoAttachmentFilename(filename)) continue; + + const fileIds = extractAttachIdTokens(filename); + const stem = attachmentFileStem(filename); + const namedInSegment = + stem.length >= 6 && segUpper.includes(stem.toUpperCase()); + + if (isPackingSourceFile(filename) && !namedInSegment) continue; + + if (fileIds.some((id) => segIds.includes(id))) { + out.push(filename); + continue; + } + if (namedInSegment) { + out.push(filename); + continue; + } + if (!isCurrent || fileIds.length) continue; + if ( + (hasSeaRole(filename) && hasSeaRole(seg)) || + (hasLabelRole(filename) && hasLabelRole(seg)) + ) { + out.push(filename); + } + } + return out; +} + +/** @deprecated 使用 matchSegmentAttachments;保留别名以免旧 import 断裂 */ +export function matchWorkOrderAttachments( + segmentText: string, + filenames: string[], + segmentSubject = "", + isCurrent = true, +): string[] { + return matchSegmentAttachments({ + segmentText, + segmentSubject, + filenames, + isCurrent, + }); +} + +export function extractLabelAttachTokens(text: string): string[] { + return extractAttachIdTokens(text); +} diff --git a/src/services/parse/match-transfer-segment-ocr.ts b/src/services/parse/match-transfer-segment-ocr.ts new file mode 100644 index 0000000..7ddc025 --- /dev/null +++ b/src/services/parse/match-transfer-segment-ocr.ts @@ -0,0 +1,120 @@ +import type { MailInstructionUnit } from "@/services/parse/split-instructions"; +import { parseInstructionRoundAtMs } from "@/services/parse/split-instructions"; +import { + parseTransferInstructionTable, + splitTaggedOcrBlocks, + transferRowsToPairs, + type TransferInstructionRow, +} from "@/services/parse/transfer-instruction-table"; +import type { TransferPair } from "@/services/parse/mail-intent"; + +export type TransferSegmentContext = { + unitId: string; + rows: TransferInstructionRow[]; + pairs: TransferPair[]; + ocrSource?: string; + /** 展示用 OCR/解析原文 */ + sourceText: string; +}; + +export type TransferSegmentCtxLineage = Record< + string, + { + rows: TransferInstructionRow[]; + pairs: TransferPair[]; + ocrSource?: string; + } +>; + +export function serializeTransferSegmentContexts( + ctx: Map, +): TransferSegmentCtxLineage { + const out: TransferSegmentCtxLineage = {}; + for (const [id, v] of ctx) { + out[id] = { + rows: v.rows, + pairs: v.pairs, + ocrSource: v.ocrSource, + }; + } + return out; +} + +export function transferSegmentContextsFromLineage( + lineage: unknown, + fallback: { + transferUnits: MailInstructionUnit[]; + imageFilenames: string[]; + ocrText?: string | null; + containerNo?: string | null; + }, +): Map { + const cached = (lineage as { transfer_segment_ctx?: TransferSegmentCtxLineage }) + ?.transfer_segment_ctx; + if (cached && Object.keys(cached).length > 0) { + return new Map( + Object.entries(cached).map(([id, v]) => [ + id, + { + unitId: id, + rows: v.rows, + pairs: v.pairs, + ocrSource: v.ocrSource, + sourceText: "", + }, + ]), + ); + } + return buildTransferSegmentContexts(fallback); +} + +function isInlineImageName(name: string): boolean { + return /\.(jpe?g|png|webp|bmp)$/i.test(name); +} + +function unitTimeMs(u: MailInstructionUnit): number { + return ( + parseInstructionRoundAtMs(u.roundAt) ?? + (u.segmentIndex >= 0 ? u.segmentIndex : u.segmentIndex + 10_000) + ); +} + +/** + * 将内嵌截图 OCR 按**时间从旧到新**对应附件顺序(首附件≈较早一轮)。 + */ +export function buildTransferSegmentContexts(input: { + transferUnits: MailInstructionUnit[]; + imageFilenames: string[]; + ocrText?: string | null; + containerNo?: string | null; +}): Map { + const out = new Map(); + const units = [...input.transferUnits].sort( + (a, b) => unitTimeMs(a) - unitTimeMs(b), + ); + const images = input.imageFilenames.filter(isInlineImageName); + const blocks = splitTaggedOcrBlocks(input.ocrText || ""); + + const blockByTag = new Map(); + for (const b of blocks) { + if (b.tag) blockByTag.set(b.tag.toLowerCase(), b.text); + } + + units.forEach((unit, i) => { + const img = images[i]; + const tagged = img ? blockByTag.get(img.toLowerCase()) : undefined; + const sourceText = tagged || unit.segmentText || ""; + const rows = parseTransferInstructionTable(sourceText, { + containerNo: input.containerNo, + }); + out.set(unit.id, { + unitId: unit.id, + rows, + pairs: transferRowsToPairs(rows), + ocrSource: img, + sourceText, + }); + }); + + return out; +} diff --git a/src/services/parse/normalize-fba-code.ts b/src/services/parse/normalize-fba-code.ts new file mode 100644 index 0000000..66a7cb0 --- /dev/null +++ b/src/services/parse/normalize-fba-code.ts @@ -0,0 +1,40 @@ +/** + * V1.72 SaveContainer:F_FBACode 必填。 + * 有真实仓代码则原样;Excel 把 ups/fedex 误写入仓库ID 时先清空再按渠道补。 + * 留仓/自提/扣货无仓 → HOLD。 + * UPS/FEDEX/DHL/USPS 无仓 → 目的代码写渠道名(UPS 的 F_FBACode 即为 UPS)。 + */ +const CC_FBA_CHAR = /^[A-Za-z0-9_\- ]+$/; +const HOLD_TRANSPORTER = /^(留仓|存仓|扣货|自提)$/; +const COURIER_AS_WAREHOUSE = /^(ups|fedex|usps|dhl|truck|卡派)$/i; +const COURIER_FBA = /^(UPS|FEDEX|DHL|USPS)$/i; + +export function normalizeFbaCodeForCc( + raw: string | undefined, + transporter: string, +): string { + let fba = (raw || "").trim(); + if (fba === "/" || COURIER_AS_WAREHOUSE.test(fba)) fba = ""; + fba = fba.replace(/[\uFF01-\uFF5E]/g, (ch) => + String.fromCharCode(ch.charCodeAt(0) - 0xfee0), + ); + if (fba && CC_FBA_CHAR.test(fba)) return fba; + if (HOLD_TRANSPORTER.test(transporter)) return "HOLD"; + const channel = (transporter || "").trim().toUpperCase(); + if (COURIER_FBA.test(channel)) return channel; + const stripped = fba.replace(/[^A-Za-z0-9_\- ]/g, "").trim(); + if (stripped && CC_FBA_CHAR.test(stripped)) return stripped; + return ""; +} + +/** 已落库的空仓快递行补上渠道名,详情/确认页不用重解析也能看到 UPS/FEDEX。 */ +export function fillShipmentFbaCodes(shipments: unknown): unknown { + if (!Array.isArray(shipments)) return shipments; + return shipments.map((row) => { + if (!row || typeof row !== "object") return row; + const r = row as { F_FBACode?: string; F_Transporter?: string }; + const code = normalizeFbaCodeForCc(r.F_FBACode, r.F_Transporter || ""); + if (!code || code === (r.F_FBACode || "").trim()) return row; + return { ...r, F_FBACode: code }; + }); +} diff --git a/src/services/parse/ops-subject-template.ts b/src/services/parse/ops-subject-template.ts index 2ff9718..162cfce 100644 --- a/src/services/parse/ops-subject-template.ts +++ b/src/services/parse/ops-subject-template.ts @@ -2,7 +2,7 @@ * 邮件3类主题:柜号+提单+船名航次+ETA+渠道件数汇总(非「客户+提单」新增预报模板)。 * 例:WHSU5574991+WHL063G550810+船名航次:OOCL SINGAPORE / 065W+ETA:6/28+FedEx-29件+… */ -import { toShanghaiDateString } from "@/utils/dates"; +import { toLooseShanghaiDateString } from "@/utils/dates"; import type { MailRecordModules } from "@/types/mail"; const ISO_CN = /^([A-Z]{4}\d{7})$/; @@ -23,21 +23,7 @@ function normLooseDate( raw: string, defaultYear = new Date().getFullYear(), ): string | null { - const t = raw.trim(); - const full = t.match(/(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})/); - if (full) { - return toShanghaiDateString( - `${full[1]}-${full[2].padStart(2, "0")}-${full[3].padStart(2, "0")}`, - ); - } - const md = t.match(/^(\d{1,2})[-/.](\d{1,2})$/); - if (md) { - const y = defaultYear; - return toShanghaiDateString( - `${y}-${md[1].padStart(2, "0")}-${md[2].padStart(2, "0")}`, - ); - } - return null; + return toLooseShanghaiDateString(raw, defaultYear); } /** 从主题中定位「柜号+…」标准串(忽略 Fw/转发/派送要求等前缀) */ diff --git a/src/services/parse/packing-list.ts b/src/services/parse/packing-list.ts index 81e6db1..b0740ec 100644 --- a/src/services/parse/packing-list.ts +++ b/src/services/parse/packing-list.ts @@ -1,12 +1,31 @@ import ExcelJS from "exceljs"; import { mapChannel } from "./channel-map"; import { validatePackingFill } from "./packing-fill-rules"; +import { normalizeFbaCodeForCc } from "./normalize-fba-code"; +import { + columnValueFromCell, + firstFilled, + mergeLabeledHarvest, + parseLabeledCell, + parseRegionComposite, + type LabeledShipmentFields, +} from "./address-block"; +import { flattenExcelCellValue } from "./excel-cell-text"; import type { ContainerHeader, ShipmentRow } from "@/types/mail"; import { isValidContainerNo, normalizeContainerNo } from "@/utils/iso6346"; import { parseFlexibleDate, round4, toShanghaiDateString } from "@/utils/dates"; const HEADER_ALIASES: Record = { - F_FBACode: ["仓库ID", "仓库id", "FBACode", "FBA Code", "SORTBY", "FBA_CODE"], + F_FBACode: [ + "仓库代码", + "仓库ID", + "仓库id", + "目的代码", + "FBACode", + "FBA Code", + "SORTBY", + "FBA_CODE", + ], F_Transporter: ["渠道", "Transporter", "运输方式", "Move Type"], F_CTNS: ["件数", "CTNS", "箱数", "Qty.(CTNS)", "Qty.(CTN)", "QTY(CTNS)"], F_CBM: ["总体积", "体积(m³)", "体积m3", "Val(CBM)", "Vol(CBM)", "CBM"], @@ -39,13 +58,16 @@ const HEADER_ALIASES: Record = { "Customer Ref No", "MarkingNo", ], - F_Address: ["派送地址", "Address"], + F_Address: ["收货地址", "派送地址", "详细地址", "Address"], F_City: ["City", "城市"], - F_State: ["State", "州"], - F_ZipCode: ["ZipCode", "Zip Code", "邮编", "ZIP"], - F_Contact: ["Contact", "联系人"], - F_Mobile: ["Mobile", "电话", "手机"], + F_State: ["State", "州省", "州"], + F_ZipCode: ["邮政编码", "ZipCode", "Zip Code", "邮编", "ZIP"], + F_Contact: ["收件人", "收货人", "Contact", "联系人"], + F_Mobile: ["联系电话", "Mobile", "电话", "手机"], F_Email: ["Email", "邮箱"], + /** 内部列:拆成 State/City,不直接提交 CC */ + F_Region: ["省/市/区", "省市/区", "省市区", "省市"], + F_Company: ["公司名称", "公司名", "Company Name", "Company"], F_Expected_DeliveryDateB: [ "最早送仓", "最早送仓日期", @@ -74,8 +96,7 @@ const HEADER_ALIASES: Record = { /** 模板占位符「/」「-」视为未填 */ function cleanCellText(raw: unknown): string { - if (raw == null) return ""; - const t = String(raw).trim(); + const t = flattenExcelCellValue(raw).trim(); if (!t || t === "/" || t === "/" || t === "-" || t === "—") return ""; return t; } @@ -108,6 +129,8 @@ function matchColumn(header: string): string | null { return null; } +export { parseRegionComposite } from "./address-block"; + function parseNumber(raw: unknown): number | null { if (raw == null || raw === "") return null; if (typeof raw === "number" && Number.isFinite(raw)) return round4(raw); @@ -190,7 +213,9 @@ export async function parsePackingListBuffer( let headerIdx = 0; for (let i = 0; i < Math.min(rows.length, 15); i++) { const texts: string[] = []; - rows[i].eachCell((cell) => texts.push(String(cell.value ?? ""))); + rows[i].eachCell((cell) => + texts.push(flattenExcelCellValue(cell.value) || cell.text || ""), + ); const joined = texts.join("|"); if ( /渠道/.test(joined) && @@ -205,7 +230,9 @@ export async function parsePackingListBuffer( const headerRow = rows[headerIdx]; const colMap: Record = {}; headerRow.eachCell((cell, col) => { - const field = matchColumn(String(cell.value ?? "")); + const field = matchColumn( + flattenExcelCellValue(cell.value) || cell.text || "", + ); if (field) colMap[col] = field; }); @@ -249,9 +276,13 @@ export async function parsePackingListBuffer( for (let i = 0; i < dataRows.length; i++) { const row = dataRows[i]; const raw: Record = {}; + const harvest: LabeledShipmentFields = {}; row.eachCell((cell, col) => { + const text = flattenExcelCellValue(cell.value) || cell.text || ""; const field = colMap[col]; - if (field) raw[field] = cell.value; + const block = parseLabeledCell(text); + if (block) mergeLabeledHarvest(harvest, block, field); + if (field) raw[field] = columnValueFromCell(field, text); }); // 跳过空行 @@ -264,18 +295,23 @@ export async function parsePackingListBuffer( if (raw.F_ContainerNo && !extractedContainer) { extractedContainer = normalizeContainerNo(String(raw.F_ContainerNo)); } + if (harvest.F_ContainerNo && !extractedContainer) { + extractedContainer = normalizeContainerNo(harvest.F_ContainerNo); + } - const channelRaw = cleanCellText(raw.F_Transporter); + const channelRaw = + cleanCellText(raw.F_Transporter) || harvest.F_Transporter || ""; const mapped = mapChannel(channelRaw); - const fbaRaw = cleanCellText(raw.F_FBACode); - const ctnsNum = parseNumber(raw.F_CTNS); + const fbaRaw = cleanCellText(raw.F_FBACode) || harvest.F_FBACode || ""; + const ctnsNum = parseNumber(raw.F_CTNS ?? harvest.F_CTNS); const ctns = ctnsNum == null ? 0 : Math.trunc(ctnsNum); - const cbm = parseNumber(raw.F_CBM); - const weight = parseNumber(raw.F_Weight); - const addr = cleanCellText(raw.F_Address); - const fbaIdRaw = cleanCellText(raw.F_FBAID); - const refRaw = cleanCellText(raw.F_ReferenceId); - const shipmentIdRaw = cleanCellText(raw.F_ShipmentID); + const cbm = parseNumber(raw.F_CBM ?? harvest.F_CBM); + const weight = parseNumber(raw.F_Weight ?? harvest.F_Weight); + const addr = cleanCellText(raw.F_Address) || harvest.F_Address || ""; + const fbaIdRaw = cleanCellText(raw.F_FBAID) || harvest.F_FBAID || ""; + const refRaw = cleanCellText(raw.F_ReferenceId) || harvest.F_ReferenceId || ""; + const shipmentIdRaw = + cleanCellText(raw.F_ShipmentID) || harvest.F_ShipmentID || ""; const fill = validatePackingFill({ channelRaw, @@ -297,28 +333,54 @@ export async function parsePackingListBuffer( const invalid_reasons = [...fill.invalid_reasons]; const warnings = [...fill.warnings]; if (mapped.unmapped && mapped.transporter) warnings.push("CHANNEL_UNMAPPED"); - // CC SaveContainer:F_FBACode 必填且仅 ASCII;存仓/留仓仓库ID常为 / 或空 - if (!fba && /^(留仓|存仓|扣货|自提)$/i.test(mapped.transporter)) { - fba = "HOLD"; + const filledFba = normalizeFbaCodeForCc(fba, mapped.transporter); + if (!fba && filledFba === "HOLD") { warnings.push("存仓/留仓等无仓库ID,已用 HOLD 作为目的代码"); } + fba = filledFba; const dateB = toShanghaiDateString( - raw.F_Expected_DeliveryDateB != null - ? parseFlexibleDate(String(raw.F_Expected_DeliveryDateB)) - : null, + parseFlexibleDate( + String( + raw.F_Expected_DeliveryDateB || + harvest.F_Expected_DeliveryDateB || + "", + ), + ), ); const dateE = toShanghaiDateString( - raw.F_Expected_DeliveryDateE != null - ? parseFlexibleDate(String(raw.F_Expected_DeliveryDateE)) - : null, + parseFlexibleDate( + String( + raw.F_Expected_DeliveryDateE || + harvest.F_Expected_DeliveryDateE || + "", + ), + ), ); - const pcs = parseNumber(raw.F_Pcs); + const pcs = parseNumber(raw.F_Pcs ?? harvest.F_Pcs); const strOrNull = (v: unknown, max = 200) => { const t = cleanCellText(v); return t ? t.slice(0, max) : null; }; + const region = parseRegionComposite(raw.F_Region); + const city = firstFilled( + strOrNull(raw.F_City, 80), + region.city, + harvest.F_City, + ); + const state = firstFilled( + strOrNull(raw.F_State, 80), + region.state, + harvest.F_State, + ); + const englishName = firstFilled( + strOrNull(raw.F_EnglishName, 200), + strOrNull(raw.F_Company, 200), + harvest.F_EnglishName, + harvest.F_Company, + ); + const remark = firstFilled(strOrNull(raw.F_Remark, 500), harvest.F_Remark); shipments.push({ row_index: shipments.length + 1, @@ -332,22 +394,25 @@ export async function parsePackingListBuffer( F_Weight: weight, F_Pcs: pcs == null ? null : Math.trunc(pcs), F_FBAID: fbaIdRaw || null, - F_PO: strOrNull(raw.F_PO, 80), + F_PO: firstFilled(strOrNull(raw.F_PO, 80), harvest.F_PO), F_ReferenceId: refRaw || null, F_ShipmentID: shipmentId, F_Address: addr ? addr.slice(0, 500) : null, - F_City: strOrNull(raw.F_City, 80), - F_State: strOrNull(raw.F_State, 80), - F_ZipCode: strOrNull(raw.F_ZipCode, 40), - F_Contact: strOrNull(raw.F_Contact, 80), - F_Mobile: strOrNull(raw.F_Mobile, 40), - F_Email: strOrNull(raw.F_Email, 120), + F_City: city, + F_State: state, + F_ZipCode: firstFilled(strOrNull(raw.F_ZipCode, 40), harvest.F_ZipCode), + F_Contact: firstFilled(strOrNull(raw.F_Contact, 80), harvest.F_Contact), + F_Mobile: firstFilled(strOrNull(raw.F_Mobile, 40), harvest.F_Mobile), + F_Email: firstFilled(strOrNull(raw.F_Email, 120), harvest.F_Email), F_Expected_DeliveryDateB: dateB, F_Expected_DeliveryDateE: dateE, - F_Remark: strOrNull(raw.F_Remark, 500), - F_SKU: strOrNull(raw.F_SKU, 80), - F_Description: strOrNull(raw.F_Description, 200), - F_EnglishName: strOrNull(raw.F_EnglishName, 200), + F_Remark: remark, + F_SKU: firstFilled(strOrNull(raw.F_SKU, 80), harvest.F_SKU), + F_Description: firstFilled( + strOrNull(raw.F_Description, 200), + harvest.F_Description, + ), + F_EnglishName: englishName, channel_raw: channelRaw || undefined, }); } diff --git a/src/services/parse/pipeline.ts b/src/services/parse/pipeline.ts index b96eae5..8796fd6 100644 --- a/src/services/parse/pipeline.ts +++ b/src/services/parse/pipeline.ts @@ -3,6 +3,7 @@ import path from "path"; import { prisma } from "@/services/db"; import { getEnv } from "@/lib/env"; import { logger } from "@/lib/logger"; +import { runWithMailTrace } from "@/lib/mail-trace"; import { resolveDataFile } from "@/lib/safe-path"; import { classifyMail } from "./classify"; import { isDoAttachmentFilename, resolveDoContainerNo } from "./do-upload-extract"; @@ -28,7 +29,10 @@ import { resolveAfterParse, } from "@/services/state-machine"; import { buildMailIntent } from "./mail-intent"; -import { ocrAttachmentBuffers } from "@/services/ocr"; +import { + ocrAttachmentBuffers, + ocrTransferScreenshotBuffers, +} from "@/services/ocr"; import { isAutoExecEligible, runAutoExec } from "@/services/import/auto-exec"; import type { ContainerHeader, @@ -46,6 +50,12 @@ import { extractForwardChromeMeta, stripMobileMailChrome, } from "./instruction-lexicon"; +import { extractMailInstructions } from "./split-instructions"; +import { + buildTransferSegmentContexts, + serializeTransferSegmentContexts, +} from "./match-transfer-segment-ocr"; +import { getInstructionExecutionStateMap } from "./instruction-execution-state"; function extractContainerFromText(text: string): string | null { const first = extractContainerFromFirstLines(text, 2); @@ -143,6 +153,10 @@ export function resolveNoSpreadsheetOutcome(input: { export class ParsePipeline { static async run(mailId: bigint): Promise { + return runWithMailTrace(mailId, "parse", () => ParsePipeline.runUntraced(mailId)); + } + + private static async runUntraced(mailId: bigint): Promise { const env = getEnv(); const mail = await prisma.mailMessage.findUnique({ where: { id: mailId }, @@ -178,25 +192,20 @@ export class ParsePipeline { .join(" | ") || subject; const filenames = mail.attachments.map((a) => a.filename); let ocrMeta: Record | null = null; + const transferBodyHint = /新增转仓/.test(body); + let transferOcrDone = !!(mail.ocrText || "").includes("【OCR:"); try { - const needOcr = - env.OCR_PROVIDER !== "off" && - (!body.trim() || - mail.attachments.some( - (a) => - !a.rejected && - /\.(pdf|png|jpe?g|webp)$/i.test(a.filename) && - !/\.xlsx?$/i.test(a.filename), - )); - if (needOcr) { + const readAttachmentBuffers = async ( + filter: (filename: string) => boolean, + ) => { const files: Array<{ buffer: Buffer; filename: string; contentType?: string | null; }> = []; for (const att of mail.attachments.filter((a) => !a.rejected)) { - if (!/\.(pdf|png|jpe?g|webp|bmp)$/i.test(att.filename)) continue; + if (!filter(att.filename)) continue; try { const abs = resolveDataFile(att.path); files.push({ @@ -211,6 +220,50 @@ export class ParsePipeline { ); } } + return files; + }; + + if ( + env.OCR_PROVIDER !== "off" && + transferBodyHint && + !transferOcrDone + ) { + const imageFiles = await readAttachmentBuffers((name) => + /\.(jpe?g|png|webp|bmp)$/i.test(name), + ); + if (imageFiles.length) { + const ocr = await ocrTransferScreenshotBuffers(imageFiles); + if (ocr?.text) { + transferOcrDone = true; + ocrMeta = { + provider: ocr.provider, + confidence: ocr.confidence, + pages: ocr.pages, + fast_transfer: true, + }; + body = body.trim() ? `${body}\n\n${ocr.text}` : ocr.text; + await prisma.mailMessage.update({ + where: { id: mailId }, + data: { ocrText: ocr.text }, + }); + } + } + } + + const needOcr = + env.OCR_PROVIDER !== "off" && + !transferOcrDone && + (!body.trim() || + mail.attachments.some( + (a) => + !a.rejected && + /\.(pdf|png|jpe?g|webp)$/i.test(a.filename) && + !/\.xlsx?$/i.test(a.filename), + )); + if (needOcr) { + const files = await readAttachmentBuffers((name) => + /\.(pdf|png|jpe?g|webp|bmp)$/i.test(name), + ); const ocr = await ocrAttachmentBuffers(files); if (ocr && ocr.confidence >= env.OCR_MIN_CONFIDENCE) { ocrMeta = { @@ -409,7 +462,28 @@ export class ParsePipeline { let usedFilename = ""; let lineage: Record = {}; - for (const att of mail.attachments.filter((a) => !a.rejected)) { + const spreadsheetRank = (filename: string): number => { + const f = filename || ""; + // 卡派/提拆派预报表优先;贴标指令单靠后,避免盖住真正预报货件 + if (/数据模版|提拆派|卡派资料|卡派清单|装箱清单|packing/i.test(f)) return 0; + if (/换标|覆盖贴|贴标|操作指令/i.test(f)) return 2; + return 1; + }; + + const sheetAtts = mail.attachments + .filter((a) => !a.rejected) + .filter((a) => { + const ext = path.extname(a.filename).toLowerCase(); + return [".xlsx", ".xls", ".csv", ".zip"].includes(ext); + }) + .slice() + .sort( + (a, b) => + spreadsheetRank(a.filename) - spreadsheetRank(b.filename) || + a.id.toString().localeCompare(b.id.toString()), + ); + + for (const att of sheetAtts) { let data: Buffer; try { data = await fs.readFile(resolveDataFile(att.path)); @@ -634,6 +708,24 @@ export class ParsePipeline { }; } + // 贴标指令单柜号常与主题预报柜不一致:工单邮件以主题/正文柜号为准 + if ( + parsed.lineage?.template === "label-instruction" && + textContainer && + parsed.container_header.F_ContainerNo && + normalizeContainerNo(textContainer) !== + normalizeContainerNo(parsed.container_header.F_ContainerNo) + ) { + parsed.container_header = { + ...parsed.container_header, + F_ContainerNo: normalizeContainerNo(textContainer), + container_no_valid: isValidContainerNo( + normalizeContainerNo(textContainer), + env.ENABLE_ISO_CHECK, + ), + }; + } + if (blPlus) { parsed.container_header = applyBlModulesToHeader( parsed.container_header, @@ -731,6 +823,41 @@ export class ParsePipeline { } } + private static enrichTransferSegmentLineage( + parse: ParseResultData, + ctx: { + subject: string; + body: string; + filenames: string[]; + ocrText: string | null; + }, + ): ParseResultData { + const instructions = extractMailInstructions({ + subject: ctx.subject, + body: ctx.body, + filenames: ctx.filenames, + }); + const transferUnits = instructions.filter((u) => u.uiKind === "transfer"); + if (!transferUnits.length) return parse; + + const imageFilenames = ctx.filenames.filter((f) => + /\.(jpe?g|png|webp|bmp)$/i.test(f), + ); + const segmentCtx = buildTransferSegmentContexts({ + transferUnits, + imageFilenames, + ocrText: ctx.ocrText, + containerNo: parse.container_header?.F_ContainerNo || null, + }); + const serialized = serializeTransferSegmentContexts(segmentCtx); + if (!Object.values(serialized).some((v) => v.rows.length)) return parse; + + return { + ...parse, + lineage: { ...parse.lineage, transfer_segment_ctx: serialized }, + }; + } + private static withMailRecord( parse: ParseResultData, ctx: { @@ -773,12 +900,31 @@ export class ParsePipeline { ) { const current = await prisma.mailMessage.findUnique({ where: { id: mailId }, - include: { attachments: true }, + include: { attachments: true, parseResult: true }, }); if (!current) return; assertTransition(current.status as MailStatus, result.status); - const prevEvidence = (current.typeEvidence || {}) as TypeEvidence & { + let parseResult = result.parse; + if (parseResult) { + parseResult = ParsePipeline.enrichTransferSegmentLineage(parseResult, { + subject: current.subject || "(无主题)", + body: current.bodyText || "", + filenames: current.attachments.map((a) => a.filename), + ocrText: current.ocrText, + }); + const prevInstructionState = getInstructionExecutionStateMap( + current.parseResult?.lineage, + ); + if (Object.keys(prevInstructionState).length) { + parseResult.lineage = { + ...(parseResult.lineage || {}), + instruction_state: prevInstructionState, + }; + } + } + + const prevEvidence = ((current.typeEvidence || {}) as unknown) as TypeEvidence & { mailbox?: { from?: string; inbox?: string; extracted?: string[] }; sample_dir?: string; }; @@ -817,28 +963,38 @@ export class ParsePipeline { version: { increment: 1 }, }, }); - if (result.parse) { + if (parseResult) { await tx.parseResult.upsert({ where: { mailId }, create: { mailId, - containerHeader: result.parse + containerHeader: parseResult .container_header as unknown as Prisma.InputJsonValue, - shipments: result.parse.shipments as unknown as Prisma.InputJsonValue, - lineage: (result.parse.lineage || + shipments: parseResult.shipments as unknown as Prisma.InputJsonValue, + lineage: (parseResult.lineage || {}) as unknown as Prisma.InputJsonValue, }, update: { - containerHeader: result.parse + containerHeader: parseResult .container_header as unknown as Prisma.InputJsonValue, - shipments: result.parse.shipments as unknown as Prisma.InputJsonValue, - lineage: (result.parse.lineage || + shipments: parseResult.shipments as unknown as Prisma.InputJsonValue, + lineage: (parseResult.lineage || {}) as unknown as Prisma.InputJsonValue, }, }); } }); + logger.info( + { + mailId: String(mailId), + mailType: result.mailType, + status: result.status, + lastError: result.lastError, + }, + "parse.staged", + ); + if (result.status === "AUTO_EXECUTING") { void runAutoExec(mailId).catch((err) => { logger.error({ err, mailId: String(mailId) }, "auto_exec.after_parse_failed"); diff --git a/src/services/parse/resolve-confirm-instruction.ts b/src/services/parse/resolve-confirm-instruction.ts new file mode 100644 index 0000000..dd9f528 --- /dev/null +++ b/src/services/parse/resolve-confirm-instruction.ts @@ -0,0 +1,104 @@ +import { + isInstructionImported, +} from "@/services/parse/instruction-execution-state"; +import { + STANDARD_INSTRUCTION_KINDS, + extractMailInstructions, + type MailInstructionUnit, +} from "@/services/parse/split-instructions"; + +export type ConfirmInstructionReason = + | "ok" + | "missing_id" + | "not_found" + | "not_confirmable" + | "already_imported"; + +export type ConfirmInstructionResolution = { + unit: MailInstructionUnit | null; + candidates: MailInstructionUnit[]; + reason: ConfirmInstructionReason; +}; + +type MailLike = { + subject: string; + body_text?: string | null; + attachments?: Array<{ filename: string }>; + parse_result?: { + lineage?: unknown; + mail_record?: { work_order_actions?: string[] } | null; + } | null; +}; + +export function listConfirmableInstructionUnits( + units: MailInstructionUnit[], +): MailInstructionUnit[] { + return units.filter((u) => + (STANDARD_INSTRUCTION_KINDS as string[]).includes(u.uiKind), + ); +} + +export function extractInstructionsFromMail( + mail: MailLike, +): MailInstructionUnit[] { + const lineage = mail.parse_result?.lineage as + | { mail_record?: { work_order_actions?: string[] } } + | undefined; + return extractMailInstructions({ + subject: mail.subject, + body: mail.body_text, + filenames: (mail.attachments || []).map((a) => a.filename), + workOrderActions: + mail.parse_result?.mail_record?.work_order_actions || + lineage?.mail_record?.work_order_actions || + [], + }); +} + +/** + * 确认页只针对「这一条指令」,不看整封邮件主类型。 + * 无 instruction_id 且仅一条可确认指令时直接选中,多条则交给页面列出选择。 + */ +export function resolveConfirmInstruction(input: { + units: MailInstructionUnit[]; + instructionId?: string | null; + lineage?: unknown; +}): ConfirmInstructionResolution { + const candidates = listConfirmableInstructionUnits(input.units); + const instructionId = input.instructionId?.trim() || ""; + + if (instructionId) { + const unit = input.units.find((u) => u.id === instructionId) || null; + if (!unit) { + return { unit: null, candidates, reason: "not_found" }; + } + if (!(STANDARD_INSTRUCTION_KINDS as string[]).includes(unit.uiKind)) { + return { unit, candidates, reason: "not_confirmable" }; + } + if (isInstructionImported(input.lineage, unit.id)) { + return { unit, candidates, reason: "already_imported" }; + } + return { unit, candidates, reason: "ok" }; + } + + const pending = candidates.filter( + (u) => !isInstructionImported(input.lineage, u.id), + ); + if (pending.length === 1) { + return { unit: pending[0], candidates, reason: "ok" }; + } + if (pending.length === 0 && candidates.length === 1) { + return { + unit: candidates[0], + candidates, + reason: isInstructionImported(input.lineage, candidates[0].id) + ? "already_imported" + : "ok", + }; + } + return { unit: null, candidates, reason: "missing_id" }; +} + +export function confirmHref(mailId: string, instructionId: string): string { + return `/mails/${mailId}/confirm?instruction_id=${encodeURIComponent(instructionId)}`; +} diff --git a/src/services/parse/split-instructions.ts b/src/services/parse/split-instructions.ts index 4871102..72b7f6c 100644 --- a/src/services/parse/split-instructions.ts +++ b/src/services/parse/split-instructions.ts @@ -9,6 +9,7 @@ import { ATTACHMENT_HINT_RE, BODY_ACTION_RE, KEYWORD_RULES, + INLINE_QUOTE_BOUNDARY_PATTERNS, SEGMENT_BOUNDARY_PATTERNS, SEGMENT_START_LINE_RE, extractForwardChromeMeta, @@ -20,6 +21,7 @@ import { stripMobileMailChrome, type InstructionUiKind, } from "@/services/parse/instruction-lexicon"; +import { extractOpsSubjectFromMail } from "@/services/parse/ops-subject-template"; import type { MailType } from "@/types/mail"; export { stripMobileMailChrome, extractForwardChromeMeta, extractRoundMeta }; @@ -53,12 +55,13 @@ export interface MailInstructionUnit { const SUBJECT_IN_SEGMENT_RE = /(?:主题|主旨|Subject)\s*[::]?\s*(.+)/i; -const STANDARD_KINDS: InstructionUiKind[] = [ +export const STANDARD_INSTRUCTION_KINDS: InstructionUiKind[] = [ "forecast", "transfer", "work_order", "do_upload", ]; +const STANDARD_KINDS = STANDARD_INSTRUCTION_KINDS; const UI_KIND_ORDER: InstructionUiKind[] = [ ...STANDARD_KINDS, @@ -73,6 +76,100 @@ const UI_KIND_LABEL: Record = { customer_instruction: "客户指令", }; +/** 解析指令轮次时间,用于展示排序(新→旧) */ +export function parseInstructionRoundAtMs(at?: string): number | null { + if (!at?.trim()) return null; + const normalized = at + .trim() + .replace(/ /gi, " ") + .replace(/年/g, "-") + .replace(/月/g, "-") + .replace(/日/g, " ") + .replace(/\./g, "-") + .replace(/\s+/g, " ") + .trim(); + const direct = Date.parse(normalized); + if (!Number.isNaN(direct)) return direct; + const m = + /(\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:\s+(\d{1,2}):(\d{2})(?::(\d{2}))?)?/.exec( + normalized, + ); + if (!m) return null; + return new Date( + +m[1], + +m[2] - 1, + +m[3], + +(m[4] || 0), + +(m[5] || 0), + +(m[6] || 0), + ).getTime(); +} + +function segmentIndexSortKey(index: number): number { + if (index >= 0) return index; + // subject(-1) / attachment(-2) / record(-3) 落在当前段之后、历史正文之前 + return 1000 - index; +} + +function compareInstructionUnits( + a: MailInstructionUnit, + b: MailInstructionUnit, +): number { + const ta = parseInstructionRoundAtMs(a.roundAt); + const tb = parseInstructionRoundAtMs(b.roundAt); + if (ta != null && tb != null && ta !== tb) return tb - ta; + if (ta != null && tb == null) return -1; + if (ta == null && tb != null) return 1; + const ax = segmentIndexSortKey(a.segmentIndex); + const bx = segmentIndexSortKey(b.segmentIndex); + if (ax !== bx) return ax - bx; + return ( + UI_KIND_ORDER.indexOf(b.uiKind) - UI_KIND_ORDER.indexOf(a.uiKind) + ); +} + +function resolveAttachmentAnchor(input: { + kind: InstructionUiKind; + filename: string; + subject: string; + prepared: Array<{ + biz: string; + segSubject: string; + roundAt?: string; + }>; + wholeAt?: string; +}): { roundAt?: string; segmentIndex: number } { + for (let i = 0; i < input.prepared.length; i++) { + const p = input.prepared[i]; + if (!p.biz) continue; + const kinds = detectKindsForSegment( + p.segSubject || "", + p.biz, + [input.filename], + { threadSubject: input.subject }, + ); + if (kinds.includes(input.kind)) { + return { roundAt: p.roundAt || input.wholeAt, segmentIndex: i }; + } + if (input.kind === "do_upload" && /DO|同步上传|附件/.test(p.biz)) { + return { roundAt: p.roundAt || input.wholeAt, segmentIndex: i }; + } + } + if (input.kind === "do_upload") { + for (let i = input.prepared.length - 1; i >= 0; i--) { + const p = input.prepared[i]; + if (!p.biz) continue; + const kinds = detectKindsForSegment(p.segSubject || "", p.biz, [], { + threadSubject: input.subject, + }); + if (kinds.includes("forecast")) { + return { roundAt: p.roundAt || input.wholeAt, segmentIndex: i }; + } + } + } + return { roundAt: input.wholeAt, segmentIndex: -2 }; +} + const UI_KIND_MAIL_TYPE: Record = { forecast: "NEW_CONTAINER", transfer: "TRANSFER", @@ -97,16 +194,26 @@ function clip(s: string, max = 72): string { /** 合并段边界正则为一个 split 用正则 */ function buildQuoteSplitRe(): RegExp { - const inner = SEGMENT_BOUNDARY_PATTERNS.map((r) => + const lineInner = SEGMENT_BOUNDARY_PATTERNS.map((r) => + r.source.replace(/^\^/, "").replace(/\$$/, ""), + ).join("|"); + const inlineInner = INLINE_QUOTE_BOUNDARY_PATTERNS.map((r) => r.source.replace(/^\^/, "").replace(/\$$/, ""), ).join("|"); - return new RegExp(`(?:^|\\n)(?:${inner})`, "gi"); + return new RegExp( + `(?:(?:^|\\n)(?:${lineInner})|(?:${inlineInner}))`, + "gi", + ); } const QUOTE_SPLIT_RE = buildQuoteSplitRe(); export function splitBodySegments(body: string): string[] { - const raw = (body || "").replace(/\r\n/g, "\n").replace(/\\n/g, "\n").trim(); + const raw = (body || "") + .replace(/\r\n/g, "\n") + .replace(/\\n/g, "\n") + .replace(/ /gi, " ") + .trim(); if (!raw) return []; // 保留分隔标签到「后一段」开头,便于展示时间轮 / 原始邮件头 @@ -182,6 +289,53 @@ function prepareSegment(seg: string): { }; } +/** + * 主题/主旨更多是线程底稿摘要,不是最新动作。 + * 若正文里已经拆出多轮时间轮,则把主题补出的单元挂到最早一轮; + * 只有整封几乎没有历史轮次时,才把主题视作当前轮。 + */ +function resolveSubjectAnchor( + prepared: Array<{ + raw: string; + biz: string; + segSubject: string; + roundAt?: string; + roundFrom?: string; + }>, + currentSegIndex: number | null, + wholeAt?: string, +): { segmentIndex: number; roundAt?: string; isCurrent: boolean } { + let oldestTimedIndex: number | null = null; + for (let i = prepared.length - 1; i >= 0; i--) { + const p = prepared[i]; + if (!p.biz) continue; + if (isSubjectEchoSegment(p.biz)) continue; + if (!parseInstructionRoundAtMs(p.roundAt)) continue; + oldestTimedIndex = i; + break; + } + if (oldestTimedIndex != null) { + return { + segmentIndex: oldestTimedIndex, + roundAt: prepared[oldestTimedIndex]?.roundAt || wholeAt, + isCurrent: + currentSegIndex !== null && oldestTimedIndex === currentSegIndex, + }; + } + if (currentSegIndex !== null) { + return { + segmentIndex: currentSegIndex, + roundAt: prepared[currentSegIndex]?.roundAt || wholeAt, + isCurrent: true, + }; + } + return { + segmentIndex: -1, + roundAt: wholeAt, + isCurrent: true, + }; +} + /** 段首主题折行(洛杉矶被切成「杉矶+40HQ…」)丢到 Dear / 原箱号 为止 */ function stripLeadingSubjectEcho(text: string): string { const raw = (text || "").trim(); @@ -299,7 +453,7 @@ export function detectKindsForSegment( const bodyHits = matchKeywordRules(bodyText, "body"); const bodyKinds = new Set(bodyHits.map((h) => h.uiKind)); - // 非标确认类:不要被 Re:新增预报 主题拉成预报 + // 纯信息类:不要被 Re:新增预报 主题拉成预报,也不提升成工单 if ( bodyKinds.has("customer_instruction") && !STANDARD_KINDS.some((k) => bodyKinds.has(k)) @@ -370,9 +524,15 @@ function makeUnit(input: { ...keywordsFromText(input.text), ...(input.extraKeywords || []), ]); - const label = UI_KIND_LABEL[input.uiKind]; - const mailType = UI_KIND_MAIL_TYPE[input.uiKind]; const meta = extractRoundMeta(input.text); + const timeHint = (input.roundAt || meta.at || "") + .replace(/ /gi, " ") + .replace(/\s+/g, " ") + .trim(); + const label = timeHint + ? `${UI_KIND_LABEL[input.uiKind]} · ${timeHint.slice(0, 24)}` + : UI_KIND_LABEL[input.uiKind]; + const mailType = UI_KIND_MAIL_TYPE[input.uiKind]; return { id: `${input.uiKind}-${input.source}-${input.segmentIndex}-${keywords.slice(0, 2).join("_") || "x"}`, uiKind: input.uiKind, @@ -505,11 +665,12 @@ export function extractMailInstructions(input: { /Dear[,,]|[A-Z]{4}\d{7}|FBA|柜号|请查收|操作/.test(p.biz) || p.biz.replace(/\s+/g, "").length > 40; if (isCurrent || hasSubstance) { - kindsInBody.add("customer_instruction"); - if (isCurrent) kindsInCurrentBody.add("customer_instruction"); + // 新口径:除预报/转仓/DO外,可执行正文默认按工单走 + kindsInBody.add("work_order"); + if (isCurrent) kindsInCurrentBody.add("work_order"); push( makeUnit({ - uiKind: "customer_instruction", + uiKind: "work_order", source: "body_segment", segmentIndex: i, isCurrent, @@ -517,7 +678,7 @@ export function extractMailInstructions(input: { segmentSubject: segSubject || undefined, roundAt, roundFrom, - extraKeywords: ["非标准格式"], + extraKeywords: ["非标准工单"], }), ); } @@ -547,6 +708,11 @@ export function extractMailInstructions(input: { !bodyHasHardAction && !STANDARD_KINDS.some((k) => kindsInBody.has(k)) && !kindsInCurrentBody.has("work_order"); + const subjectAnchor = resolveSubjectAnchor( + prepared, + currentSegIndex, + wholeChrome.at, + ); if (allowSubjectSupplement) { for (const kind of detectUiKindsFromText(subject, "", [])) { @@ -555,18 +721,50 @@ export function extractMailInstructions(input: { makeUnit({ uiKind: kind, source: "subject", - segmentIndex: -1, - isCurrent: false, + segmentIndex: subjectAnchor.segmentIndex, + isCurrent: subjectAnchor.isCurrent, text: subject, segmentSubject: subject, + roundAt: subjectAnchor.roundAt, }), ); } } + const opsSubject = extractOpsSubjectFromMail(subject); + const shouldAddForecastFromOpsSubject = + !units.some((u) => u.uiKind === "forecast") && + Boolean( + opsSubject?.channels.length || + opsSubject?.modules.eta || + opsSubject?.modules.vessel_voyage, + ); + if (shouldAddForecastFromOpsSubject) { + push( + makeUnit({ + uiKind: "forecast", + source: "subject", + segmentIndex: subjectAnchor.segmentIndex, + isCurrent: subjectAnchor.isCurrent, + text: subject, + segmentSubject: subject, + roundAt: subjectAnchor.roundAt, + extraKeywords: ["主题预报"], + }), + ); + } + // 3) 附件:仅补充正文/主题未覆盖的 kind for (const f of filenames) { for (const kind of detectUiKindsFromText("", "", [f])) { + // DO:任一正文段已因「DO也同步上传」出过卡时,勿再因同份 DO.pdf 叠一张 + if ( + kind === "do_upload" && + (kindsInBody.has("do_upload") || + units.some((u) => u.uiKind === "do_upload")) + ) { + continue; + } if (kindsInCurrentBody.has(kind)) continue; if (units.some((u) => u.uiKind === kind && u.source === "subject")) { // 主题已有同类型时,附件仍可补充(DO / 贴标文件很重要) @@ -574,19 +772,34 @@ export function extractMailInstructions(input: { } const attachCurrent = kindsInCurrentBody.size === 0 || !kindsInCurrentBody.has(kind); + const attachAnchor = resolveAttachmentAnchor({ + kind, + filename: f, + subject, + prepared, + wholeAt: wholeChrome.at, + }); push( makeUnit({ uiKind: kind, source: "attachment", - segmentIndex: -2, + segmentIndex: attachAnchor.segmentIndex, isCurrent: Boolean(attachCurrent), text: f, + roundAt: attachAnchor.roundAt, extraKeywords: [f.slice(0, 40)], }), ); } } + // 本封确有 DO 附件、且 DO 卡来自历史预报段时:升为可确认(避免只剩只读历史卡) + if (filenames.some((f) => isDoAttachmentFilename(f))) { + for (const u of units) { + if (u.uiKind === "do_upload" && !u.isCurrent) u.isCurrent = true; + } + } + if (input.forceForecast && !units.some((u) => u.uiKind === "forecast")) { push( makeUnit({ @@ -649,26 +862,23 @@ export function extractMailInstructions(input: { const filtered = hasCurrentStandard ? units.filter((u) => { if (u.uiKind !== "customer_instruction" || u.isCurrent) return true; - return KEEP_HIST_CI.test( - `${u.segmentText}\n${u.keywords.join(" ")}`, - ); + const blob = `${u.segmentText}\n${u.keywords.join(" ")}`; + // 有时间轮的历史段:保留实质指令;软词「更新派送单」等仍丢弃 + if (parseInstructionRoundAtMs(u.roundAt) != null) { + if (/更新派送单|改自提/.test(blob)) { + return KEEP_HIST_CI.test(blob); + } + return true; + } + return KEEP_HIST_CI.test(blob); }) : units; // 同线程多次转发同一份指令(仅抄送折行不同)→ 只留最新一条 const deduped = dedupeSamePayloadUnits(filtered); - // 展示顺序:当前段优先,再按业务类型 - return deduped.sort((a, b) => { - if (a.isCurrent !== b.isCurrent) return a.isCurrent ? -1 : 1; - if (a.segmentIndex !== b.segmentIndex) { - // 当前段 index 小;历史段靠后;subject(-1) 在正文当前之后、历史之前偏前 - const ax = a.segmentIndex < 0 ? 1000 + a.segmentIndex : a.segmentIndex; - const bx = b.segmentIndex < 0 ? 1000 + b.segmentIndex : b.segmentIndex; - return ax - bx; - } - return UI_KIND_ORDER.indexOf(a.uiKind) - UI_KIND_ORDER.indexOf(b.uiKind); - }); + // 展示顺序:时间新→旧;无时间则段序(index 小=较新);同段再按业务类型 + return deduped.sort(compareInstructionUnits); } export function instructionUiKindLabel(kind: InstructionUiKind): string { @@ -702,7 +912,10 @@ function dedupeSamePayloadUnits( out.push(u); continue; } - const key = `${u.uiKind}:${fp}`; + const t = + parseInstructionRoundAtMs(u.roundAt) ?? + (u.segmentIndex >= 0 ? u.segmentIndex : u.segmentIndex - 1000); + const key = `${u.uiKind}:${fp}:${t}`; if (seen.has(key)) continue; seen.add(key); out.push(u); diff --git a/src/services/parse/transfer-instruction-table.ts b/src/services/parse/transfer-instruction-table.ts new file mode 100644 index 0000000..9463edd --- /dev/null +++ b/src/services/parse/transfer-instruction-table.ts @@ -0,0 +1,210 @@ +import type { TransferPair } from "@/services/parse/mail-intent"; + +/** 邮件截图/表格里的一行转仓指令(地址 → 目标仓) */ +export type TransferInstructionRow = { + containerNo?: string; + eta?: string; + /** 原地址仓 / FBACode,如 PSC2、FWA4 */ + from: string; + /** 目标仓,如 POC2、VGT2 */ + to: string; + ctns?: number; +}; + +const WH_CODE = /\b([A-Z]{3,4}\d)\b/g; +const ISO_CN = /\b([A-Za-z]{4}\d{7})\b/i; + +function normWh(code: string): string { + const c = code.replace(/[^A-Za-z0-9]/g, "").toUpperCase(); + if (/^FWA[D4]?$/.test(c) || c.startsWith("FWA") || c === "FWAD4" || c === "WAAD4") + return "FWA4"; + if (c === "EWAD" || c === "EWAD4" || c.endsWith("WAD4")) return "FWA4"; + if (c === "PSC" || c.startsWith("PSC")) return "PSC2"; + if (c === "POC" || c.startsWith("POC")) return "POC2"; + if (c.startsWith("VGT")) return "VGT2"; + return c; +} + +function isLikelyWhCode(code: string): boolean { + return /^(PSC2|POC2|FWA4|VGT2)$/.test(normWh(code)); +} + +function pickCompactRowCtns(tokens: string[], from: string, to: string): number | undefined { + const fromIdx = tokens.findIndex((x) => normWh(x) === from); + const toIdx = tokens.findIndex((x) => normWh(x) === to); + if (fromIdx >= 0 && toIdx > fromIdx) { + for (let i = fromIdx + 1; i < toIdx; i++) { + if (/^\d{1,3}$/.test(tokens[i])) return Number(tokens[i]); + } + } + for (let i = tokens.length - 1; i >= 0; i--) { + if (/^\d{1,3}$/.test(tokens[i])) return Number(tokens[i]); + } + return undefined; +} + +function normText(raw: string): string { + return (raw || "") + .replace(/ /gi, " ") + .replace(/\r\n/g, "\n") + .replace(/[||]/g, " ") + .replace(/[ \t]+/g, " ") + .trim(); +} + +/** + * 从 OCR/正文解析「新增转仓」截图表格。 + * 典型列:柜号 / ETA / 地址 / 箱数 / 仓库重定向 + */ +export function parseTransferInstructionTable( + text: string, + fallback?: { + containerNo?: string | null; + }, +): TransferInstructionRow[] { + const t = normText(text); + if (!t) return []; + + const rows: TransferInstructionRow[] = []; + const containerNo = + t.match(ISO_CN)?.[1]?.toUpperCase() || + t.match(/\b([A-Z]{4}\d{7})\b/)?.[1] || + fallback?.containerNo || + undefined; + const eta = + t.match(/ETA\s*[::]?\s*(\d{4}[/.-]\d{1,2}[/.-]\d{1,2})/i)?.[1] || + t.match(/(\d{4}[/.-]\d{1,2}[/.-]\d{1,2})/)?.[1]; + + // 标准表头行:地址 PSC2 箱数 7 … 仓库重定向 POC2 + const rowRe = + /地址\s*[::]?\s*([A-Z]{3,4}\d?)\s*箱数\s*[::]?\s*(\d+)\s*(?:提柜时间[^箱]*?)?仓库重定向\s*[::]?\s*([A-Z]{3,4}\d?)/gi; + let m: RegExpExecArray | null; + while ((m = rowRe.exec(t)) !== null) { + rows.push({ + containerNo, + eta, + from: m[1].toUpperCase(), + to: m[3].toUpperCase(), + ctns: Number(m[2]), + }); + } + if (rows.length) return rows; + + // 宽松:同一行出现 地址 X … 仓库重定向 Y + const looseRe = + /地址\s*[::]?\s*([A-Z]{3,4}\d?)[\s\S]{0,120}?仓库重定向\s*[::]?\s*([A-Z]{3,4}\d?)/gi; + while ((m = looseRe.exec(t)) !== null) { + const slice = m[0]; + const ctns = /箱数\s*[::]?\s*(\d+)/i.exec(slice)?.[1]; + rows.push({ + containerNo, + eta, + from: m[1].toUpperCase(), + to: m[2].toUpperCase(), + ctns: ctns ? Number(ctns) : undefined, + }); + } + if (rows.length) return rows; + + // OCR 截图:柜号行上最后两个仓码即 地址→目标仓 + const cn = t.match(ISO_CN)?.[1]?.toUpperCase(); + if (cn) { + const idx = t.search(new RegExp(cn, "i")); + const after = t.slice(idx + cn.length); + const whs = [...after.matchAll(/\b([A-Za-z]{3,4}\d)\b/g)] + .map((m) => normWh(m[1])) + .filter(isLikelyWhCode); + const ctns = + Number(/箱数\s*[::]?\s*(\d+)/i.exec(t)?.[1]) || + Number(/\|\s*(\d{1,3})\s*\|/.exec(t)?.[1]) || + undefined; + if (whs.length >= 2) { + rows.push({ + containerNo: cn, + eta, + from: whs[0], + to: whs[whs.length - 1], + ctns, + }); + return rows; + } + } + + // OCR 单行:柜号/日期/原仓/箱数/目标仓 + const compactTokens = t + .split(/[\s|]+/) + .map((x) => x.trim()) + .filter(Boolean); + const compactWhs = compactTokens + .map((x) => normWh(x)) + .filter(isLikelyWhCode); + if (compactWhs.length >= 2) { + const from = compactWhs[0]; + const to = compactWhs[compactWhs.length - 1]; + rows.push({ + containerNo, + eta, + from, + to, + ctns: pickCompactRowCtns(compactTokens, from, to), + }); + return rows; + } + + // 仅两个仓码 + 箱数(OCR 丢列名) + const codes = [...t.matchAll(WH_CODE)].map((x) => normWh(x[1])); + const ctnsMatch = t.match(/箱数\s*[::]?\s*(\d+)/i); + if (codes.length >= 2) { + rows.push({ + containerNo, + eta, + from: codes[0], + to: codes[codes.length - 1], + ctns: ctnsMatch ? Number(ctnsMatch[1]) : undefined, + }); + } + + return rows; +} + +export function transferRowsToPairs(rows: TransferInstructionRow[]): TransferPair[] { + const map = new Map(); + for (const r of rows) { + if (!r.from || !r.to) continue; + const key = `${r.from}->${r.to}`; + const prev = map.get(key); + if (prev) prev.rows += r.ctns && r.ctns > 0 ? r.ctns : 1; + else + map.set(key, { + from: r.from, + to: r.to, + rows: r.ctns && r.ctns > 0 ? r.ctns : 1, + }); + } + return [...map.values()]; +} + +/** 拆分带文件标签的 OCR 块:【OCR:xxx.jpg】\n... */ +export function splitTaggedOcrBlocks( + ocrText: string, +): Array<{ tag: string; text: string }> { + const raw = (ocrText || "").trim(); + if (!raw) return []; + const re = /^【OCR:([^】]+)】\s*/gm; + const hits: Array<{ index: number; tag: string; len: number }> = []; + let m: RegExpExecArray | null; + while ((m = re.exec(raw)) !== null) { + hits.push({ index: m.index, tag: m[1].trim(), len: m[0].length }); + } + if (!hits.length) return [{ tag: "", text: raw }]; + const out: Array<{ tag: string; text: string }> = []; + for (let i = 0; i < hits.length; i++) { + const start = hits[i].index + hits[i].len; + const end = i + 1 < hits.length ? hits[i + 1].index : raw.length; + out.push({ + tag: hits[i].tag, + text: raw.slice(start, end).trim(), + }); + } + return out.filter((b) => b.text); +} diff --git a/src/services/parse/us-ports.ts b/src/services/parse/us-ports.ts new file mode 100644 index 0000000..6104b40 --- /dev/null +++ b/src/services/parse/us-ports.ts @@ -0,0 +1,154 @@ +/** + * 常见美国目的港白名单(中美海运预报)。 + * 未命中白名单的文本一律不写入目的港,避免把渠道件数/主题噪声当成港口。 + */ + +export type UsPortEntry = { + /** 写入表单的规范名(中文优先) */ + canonical: string; + aliases: string[]; +}; + +export const US_DESTINATION_PORTS: UsPortEntry[] = [ + { + canonical: "洛杉矶", + aliases: ["洛杉矶", "Los Angeles", "LA", "LAX", "LOS ANGELES"], + }, + { + canonical: "长滩", + aliases: ["长滩", "Long Beach", "LGB", "LONG BEACH"], + }, + { + canonical: "纽约", + aliases: ["纽约", "New York", "NY", "NYC", "NEW YORK", "NY/NJ", "NYNJ"], + }, + { + canonical: "萨凡纳", + aliases: ["萨凡纳", "萨瓦纳", "Savannah", "SAV"], + }, + { + canonical: "奥克兰", + aliases: ["奥克兰", "Oakland", "OAK"], + }, + { + canonical: "西雅图", + aliases: ["西雅图", "Seattle", "SEA"], + }, + { + canonical: "塔科马", + aliases: ["塔科马", "Tacoma", "TAC", "SEA/TAC"], + }, + { + canonical: "休斯顿", + aliases: ["休斯顿", "休斯敦", "Houston", "HOU"], + }, + { + canonical: "迈阿密", + aliases: ["迈阿密", "Miami", "MIA"], + }, + { + canonical: "查尔斯顿", + aliases: ["查尔斯顿", "Charleston", "CHS"], + }, + { + canonical: "诺福克", + aliases: ["诺福克", "Norfolk", "ORF"], + }, + { + canonical: "波士顿", + aliases: ["波士顿", "Boston", "BOS"], + }, + { + canonical: "巴尔的摩", + aliases: ["巴尔的摩", "Baltimore", "BAL"], + }, + { + canonical: "新奥尔良", + aliases: ["新奥尔良", "New Orleans", "MSY", "NEW ORLEANS"], + }, + { + canonical: "杰克逊维尔", + aliases: ["杰克逊维尔", "Jacksonville", "JAX"], + }, + { + canonical: "费城", + aliases: ["费城", "Philadelphia", "PHL"], + }, + { + canonical: "威尔明顿", + aliases: ["威尔明顿", "Wilmington", "ILM"], + }, + { + canonical: "莫比尔", + aliases: ["莫比尔", "Mobile", "MOB"], + }, +]; + +function normalizePortKey(raw: string): string { + return raw + .trim() + .replace(/\s+/g, " ") + .replace(/[.。]/g, ".") + .toUpperCase(); +} + +const PORT_LOOKUP: Array<{ key: string; canonical: string; len: number }> = (() => { + const rows: Array<{ key: string; canonical: string; len: number }> = []; + for (const entry of US_DESTINATION_PORTS) { + const keys = new Set([ + normalizePortKey(entry.canonical), + ...entry.aliases.map(normalizePortKey), + ]); + for (const key of keys) { + if (!key) continue; + rows.push({ key, canonical: entry.canonical, len: key.length }); + } + } + // 长别名优先,避免 LA 抢先于 LOS ANGELES;短码仅整词匹配 + return rows.sort((a, b) => b.len - a.len); +})(); + +/** + * 将文本规范为白名单目的港;未命中返回 null。 + * - 整段等于别名:洛杉矶 / Los Angeles / LA + * - 或段内包含完整别名词界:目的港洛杉矶 / POD Los Angeles + */ +export function matchUsDestinationPort( + raw: string | null | undefined, +): string | null { + const text = (raw || "").trim(); + if (!text || text.length > 60) return null; + const upper = normalizePortKey(text); + + for (const row of PORT_LOOKUP) { + if (upper === row.key) return row.canonical; + } + + for (const row of PORT_LOOKUP) { + // 2–3 字母码(LA/NY)只接受整段相等,避免正文误伤 + if (row.len <= 3) continue; + const safe = row.key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const re = new RegExp( + `(?:^|[^A-Z0-9\u4e00-\u9fff])${safe}(?:[^A-Z0-9\u4e00-\u9fff]|$)`, + "i", + ); + if (re.test(upper) || text.includes(row.canonical)) { + return row.canonical; + } + } + + // 短码整词:仅当段几乎就是该码 + for (const row of PORT_LOOKUP) { + if (row.len > 3) continue; + if (upper === row.key) return row.canonical; + } + + return null; +} + +/** 是否可作为目的港写入(白名单) */ +export function isKnownUsDestinationPort( + raw: string | null | undefined, +): boolean { + return matchUsDestinationPort(raw) != null; +} diff --git a/src/services/parse/work-order-record.ts b/src/services/parse/work-order-record.ts index d024e28..fe118de 100644 --- a/src/services/parse/work-order-record.ts +++ b/src/services/parse/work-order-record.ts @@ -41,6 +41,13 @@ const ISO_CN = /\b([A-Z]{4}\d{7})\b/; const EXPRESS_RE = /快递单号\s*[::#]?\s*([A-Z0-9][A-Z0-9\-]{5,31})/gi; +export { + extractAttachIdTokens, + extractLabelAttachTokens, + matchSegmentAttachments, + matchWorkOrderAttachments, +} from "./match-segment-attachments"; + /** 混乱正文:前两行内找柜号(前四位字母 + 7 数字) */ export function extractContainerFromFirstLines( text: string, diff --git a/src/services/state-machine.ts b/src/services/state-machine.ts index 3b1d4b3..69b7326 100644 --- a/src/services/state-machine.ts +++ b/src/services/state-machine.ts @@ -49,13 +49,10 @@ export function assertTransition(from: MailStatus, to: MailStatus): void { } } +/** 只读探路:不打 illegal_transition 日志(详情页按钮显隐会频繁调用) */ export function canTransition(from: MailStatus, to: MailStatus): boolean { - try { - assertTransition(from, to); - return true; - } catch { - return false; - } + if (from === to) return true; + return (ALLOWED[from] || []).includes(to); } export function resolveAfterParse(input: { diff --git a/src/services/tenant/access.ts b/src/services/tenant/access.ts new file mode 100644 index 0000000..ff2ae7a --- /dev/null +++ b/src/services/tenant/access.ts @@ -0,0 +1,69 @@ +import type { Prisma } from "@prisma/client"; +import type { SessionData, UserRole } from "@/lib/session"; +import { prisma } from "@/services/db"; + +export function isStaffRole(role: UserRole | undefined): boolean { + return role === "admin" || role === "ops"; +} + +export function sessionUserId(session: SessionData): bigint | null { + if (!session.userId) return null; + try { + return BigInt(session.userId); + } catch { + return null; + } +} + +export function mailWhereForSession( + session: SessionData, +): Prisma.MailMessageWhereInput | undefined { + if (isStaffRole(session.role)) return undefined; + const uid = sessionUserId(session); + if (!uid) return { id: { in: [] } }; + return { mailboxAccount: { ownerUserId: uid } }; +} + +export function importWhereForSession( + session: SessionData, +): Prisma.ContainerImportWhereInput | undefined { + const mailWhere = mailWhereForSession(session); + if (!mailWhere) return undefined; + return { mail: mailWhere }; +} + +export async function assertMailReadable( + session: SessionData, + mailId: bigint, +): Promise { + if (isStaffRole(session.role)) return true; + const uid = sessionUserId(session); + if (!uid) return false; + const mail = await prisma.mailMessage.findFirst({ + where: { id: mailId, mailboxAccount: { ownerUserId: uid } }, + select: { id: true }, + }); + return Boolean(mail); +} + +export async function assertMailboxOwned( + session: SessionData, + mailboxId: bigint, +): Promise { + if (isStaffRole(session.role)) return true; + const uid = sessionUserId(session); + if (!uid) return false; + const box = await prisma.mailboxAccount.findFirst({ + where: { id: mailboxId, ownerUserId: uid }, + select: { id: true }, + }); + return Boolean(box); +} + +export async function mailboxIdsForOwner(ownerUserId: bigint): Promise { + const rows = await prisma.mailboxAccount.findMany({ + where: { ownerUserId }, + select: { id: true }, + }); + return rows.map((r) => r.id); +} diff --git a/src/services/test/db-harness.ts b/src/services/test/db-harness.ts new file mode 100644 index 0000000..a8c7e63 --- /dev/null +++ b/src/services/test/db-harness.ts @@ -0,0 +1,172 @@ +import fs from "fs/promises"; +import path from "path"; +import { prisma } from "@/services/db"; +import { ParsePipeline } from "@/services/parse/pipeline"; +import { parseMailSource, saveMailSnapshot } from "@/services/imap/snapshot"; +import { sha256 } from "@/utils/hash"; +import { newTraceId } from "@/lib/log-context"; +import type { LoadedEmailFixture } from "@/services/test/email-fixture-library"; + +export async function pingTestDatabase(): Promise { + try { + await prisma.$queryRaw`SELECT 1`; + return true; + } catch { + return false; + } +} + +function uniqueMessageId(loaded: LoadedEmailFixture, runId: string): string { + return ``; +} + +export async function deleteTestMailsByRunId(runId: string): Promise { + const rows = await prisma.mailMessage.findMany({ + where: { messageId: { contains: `p3.${runId}.` } }, + select: { id: true }, + }); + const ids = rows.map((r) => r.id); + if (!ids.length) return; + + await prisma.importCompensation.deleteMany({ + where: { import: { mailId: { in: ids } } }, + }); + await prisma.containerActiveLock.deleteMany({ where: { mailId: { in: ids } } }); + await prisma.containerImport.deleteMany({ where: { mailId: { in: ids } } }); + await prisma.parseResult.deleteMany({ where: { mailId: { in: ids } } }); + await prisma.mailAttachment.deleteMany({ where: { mailId: { in: ids } } }); + await prisma.imapPullLog.deleteMany({ where: { mailId: { in: ids } } }); + await prisma.mailMessage.deleteMany({ where: { id: { in: ids } } }); + + for (const id of ids) { + const dir = path.join(process.cwd(), "data", "mails", String(id)); + await fs.rm(dir, { recursive: true, force: true }).catch(() => undefined); + } +} + +/** + * 把 fixture 写成 FETCHED 邮件并跑 ParsePipeline(不经 IMAP 锁 / lookback)。 + */ +export async function ingestFixtureAndParse( + loaded: LoadedEmailFixture, + runId: string, +): Promise<{ mailId: bigint }> { + const parsed = await parseMailSource(loaded.eml); + const messageId = uniqueMessageId(loaded, runId); + const rawHash = sha256(`p3|${runId}|${loaded.id}|${loaded.spec.messageId}`); + + const created = await prisma.mailMessage.create({ + data: { + messageId, + folder: "INBOX", + subject: loaded.spec.subject.slice(0, 512), + fromAddr: loaded.spec.from, + receivedAt: loaded.spec.date ? new Date(loaded.spec.date) : new Date(), + status: "FETCHED", + rawHash, + traceId: newTraceId(), + }, + }); + + const snapshot = await saveMailSnapshot(created.id, loaded.eml, parsed); + await prisma.$transaction(async (tx) => { + await tx.mailMessage.update({ + where: { id: created.id }, + data: { + bodyText: snapshot.bodyText, + snapshotPath: snapshot.snapshotPath, + }, + }); + if (snapshot.attachments.length) { + await tx.mailAttachment.createMany({ + data: snapshot.attachments.map((a) => ({ + mailId: created.id, + filename: a.filename, + contentType: a.contentType, + sha256: a.sha256, + path: a.path, + size: a.size, + rejected: a.rejected, + })), + }); + } + }); + + await prisma.mailMessage.update({ + where: { id: created.id }, + data: { status: "PARSING", version: { increment: 1 } }, + }); + await ParsePipeline.run(created.id); + return { mailId: created.id }; +} + +/** 只落到 FETCHED,供 drainFetchedMails 隔离测试 */ +export async function ingestFixtureFetched( + loaded: LoadedEmailFixture, + runId: string, + suffix: string, +): Promise<{ mailId: bigint }> { + const parsed = await parseMailSource(loaded.eml); + const messageId = ``; + const rawHash = sha256(`p3|${runId}|${loaded.id}|${suffix}|${loaded.spec.messageId}`); + const created = await prisma.mailMessage.create({ + data: { + messageId, + folder: "INBOX", + subject: loaded.spec.subject.slice(0, 512), + fromAddr: loaded.spec.from, + receivedAt: new Date(), + status: "FETCHED", + rawHash, + traceId: newTraceId(), + }, + }); + const snapshot = await saveMailSnapshot(created.id, loaded.eml, parsed); + await prisma.$transaction(async (tx) => { + await tx.mailMessage.update({ + where: { id: created.id }, + data: { bodyText: snapshot.bodyText, snapshotPath: snapshot.snapshotPath }, + }); + if (snapshot.attachments.length) { + await tx.mailAttachment.createMany({ + data: snapshot.attachments.map((a) => ({ + mailId: created.id, + filename: a.filename, + contentType: a.contentType, + sha256: a.sha256, + path: a.path, + size: a.size, + rejected: a.rejected, + })), + }); + } + }); + return { mailId: created.id }; +} + +export async function loadParsedMail(mailId: bigint) { + return prisma.mailMessage.findUniqueOrThrow({ + where: { id: mailId }, + include: { parseResult: true, attachments: true, imports: true }, + }); +} + +export async function listWorkbenchMails(limit = 80) { + return prisma.mailMessage.findMany({ + where: { messageId: { contains: "@p3.fixtures.local" } }, + orderBy: { id: "desc" }, + take: limit, + select: { + id: true, + messageId: true, + subject: true, + fromAddr: true, + mailType: true, + status: true, + lastError: true, + createdAt: true, + updatedAt: true, + snapshotPath: true, + }, + }); +} diff --git a/src/services/test/email-fixture-library.ts b/src/services/test/email-fixture-library.ts new file mode 100644 index 0000000..5d630a4 --- /dev/null +++ b/src/services/test/email-fixture-library.ts @@ -0,0 +1,321 @@ +import fs from "fs/promises"; +import path from "path"; +import ExcelJS from "exceljs"; +import { buildRawEml } from "@/services/imap/mock-mail-server"; +import type { MailStatus, MailType } from "@/types/mail"; + +export type FixtureCategory = + | "normal" + | "boundary" + | "abnormal" + | "duplicate" + | "multi_attachment"; + +export type PackingRowSpec = { + 仓库ID?: string; + 渠道?: string; + 件数?: number | string; + 总体积?: number | string; + 毛重?: number | string; + "FBA ID"?: string; + "Amazon reference ID"?: string; + 分货标识?: string; + 派送地址?: string; + 备注?: string; + 柜号?: string; + 最早送仓日期?: string; + 最晚送仓日期?: string; +}; + +export type FixtureAttachmentSpec = + | { + filename: string; + kind: "packing_xlsx"; + sheetName?: string; + headers?: string[]; + rows: PackingRowSpec[]; + } + | { filename: string; kind: "pdf"; text?: string } + | { filename: string; kind: "corrupt_xlsx" } + | { filename: string; kind: "raw"; content: string; contentType?: string } + | { filename: string; kind: "oversized"; bytes: number }; + +export type EmailFixtureSpec = { + id: string; + category: FixtureCategory; + from: string; + subject: string; + text: string; + messageId: string; + date?: string; + charset?: "utf-8" | "gbk"; + attachments: FixtureAttachmentSpec[]; +}; + +export type EmailFixtureExpected = { + mail_type: MailType; + /** 解析崩溃时 pipeline catch 会改写类型;缺省等于 mail_type */ + pipeline_mail_type?: MailType; + status: MailStatus; + last_error: string | null; + container_no?: string | null; + container_no_valid?: boolean; + selected_attachment?: string | null; + selected_attachments?: string[]; + shipment_count?: number; + valid_shipment_count?: number; + shipments?: Array<{ + F_FBACode?: string; + F_Transporter?: string; + F_CTNS?: number; + row_status?: string; + F_Expected_DeliveryDateB?: string | null; + }>; + missing_columns?: boolean; + idempotent_with?: string | null; + pull?: boolean; +}; + +export type LoadedEmailFixture = { + id: string; + dir: string; + spec: EmailFixtureSpec; + expected: EmailFixtureExpected; + eml: Buffer; + attachments: Array<{ + filename: string; + contentType: string; + content: Buffer; + }>; +}; + +const ROOT = path.join(process.cwd(), "fixtures", "emails"); + +const PACKING_HEADERS = [ + "仓库ID", + "渠道", + "件数", + "总体积", + "毛重", + "FBA ID", + "Amazon reference ID", + "分货标识", + "派送地址", + "备注", + "柜号", + "最早送仓日期", + "最晚送仓日期", +] as const; + +export async function buildPackingXlsx( + rows: PackingRowSpec[], + sheetName = "卡派资料", + headers: readonly string[] = PACKING_HEADERS, +): Promise { + const wb = new ExcelJS.Workbook(); + const ws = wb.addWorksheet(sheetName); + ws.addRow([...headers]); + for (const r of rows) { + ws.addRow(headers.map((h) => (r as Record)[h] ?? "")); + } + return Buffer.from(await wb.xlsx.writeBuffer()); +} + +async function materializeAttachment( + spec: FixtureAttachmentSpec, +): Promise<{ filename: string; contentType: string; content: Buffer }> { + switch (spec.kind) { + case "packing_xlsx": + return { + filename: spec.filename, + contentType: + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + content: await buildPackingXlsx(spec.rows, spec.sheetName, spec.headers), + }; + case "pdf": + return { + filename: spec.filename, + contentType: "application/pdf", + content: Buffer.from( + `%PDF-1.4\n1 0 obj<<>>endobj\n${spec.text || "DO fixture"}\n%%EOF\n`, + "utf8", + ), + }; + case "corrupt_xlsx": + return { + filename: spec.filename, + contentType: + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + content: Buffer.from("PK\x03\x04this-is-not-a-valid-xlsx", "binary"), + }; + case "raw": + return { + filename: spec.filename, + contentType: spec.contentType || "application/octet-stream", + content: Buffer.from(spec.content, "utf8"), + }; + case "oversized": + return { + filename: spec.filename, + contentType: + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + content: Buffer.alloc(spec.bytes, 0x58), + }; + } +} + +/** GBK「请查收新增预报 WHSU8127240」 */ +const GBK_FORECAST_HEX = + "c7ebb2e9cad5d0c2d4f6d4a4b1a8205748535538313237323430"; + +function buildGbkEml( + spec: EmailFixtureSpec, + attachments: Array<{ filename: string; contentType: string; content: Buffer }>, +): Buffer { + const body = Buffer.from(GBK_FORECAST_HEX, "hex"); + const date = spec.date || "Tue, 14 Apr 2026 08:00:00 +0800"; + const headers = [ + `From: ${spec.from}`, + "To: inbox@test.local", + `Subject: ${spec.subject}`, + `Date: ${date}`, + `Message-ID: ${spec.messageId}`, + "MIME-Version: 1.0", + ]; + if (!attachments.length) { + headers.push( + "Content-Type: text/plain; charset=gbk", + "Content-Transfer-Encoding: base64", + "", + body.toString("base64"), + "", + ); + return Buffer.from(headers.join("\r\n"), "ascii"); + } + + const boundary = `mock-gbk-${Math.random().toString(16).slice(2)}`; + const chunks: Buffer[] = [ + Buffer.from( + [ + ...headers, + `Content-Type: multipart/mixed; boundary="${boundary}"`, + "", + `--${boundary}`, + "Content-Type: text/plain; charset=gbk", + "Content-Transfer-Encoding: base64", + "", + body.toString("base64"), + "", + ].join("\r\n"), + "ascii", + ), + ]; + for (const att of attachments) { + const filename = att.filename.replace(/"/g, ""); + chunks.push( + Buffer.from( + [ + `--${boundary}`, + `Content-Type: ${att.contentType}; name="${filename}"`, + `Content-Disposition: attachment; filename="${filename}"`, + "Content-Transfer-Encoding: base64", + "", + att.content.toString("base64"), + "", + ].join("\r\n"), + "ascii", + ), + ); + } + chunks.push(Buffer.from(`--${boundary}--\r\n`, "ascii")); + return Buffer.concat(chunks); +} + +export function fixturesRoot(): string { + return ROOT; +} + +export async function listEmailFixtureIds(): Promise { + const entries = await fs.readdir(ROOT, { withFileTypes: true }); + return entries + .filter((e) => e.isDirectory()) + .map((e) => e.name) + .sort(); +} + +export async function loadEmailFixtureMetas(): Promise< + Array<{ id: string; spec: EmailFixtureSpec; expected: EmailFixtureExpected }> +> { + const ids = await listEmailFixtureIds(); + const out: Array<{ + id: string; + spec: EmailFixtureSpec; + expected: EmailFixtureExpected; + }> = []; + for (const id of ids) { + const dir = path.join(ROOT, id); + const spec = JSON.parse( + await fs.readFile(path.join(dir, "spec.json"), "utf8"), + ) as EmailFixtureSpec; + const expected = JSON.parse( + await fs.readFile(path.join(dir, "expected.json"), "utf8"), + ) as EmailFixtureExpected; + out.push({ id, spec, expected }); + } + return out; +} + +export async function loadEmailFixture(id: string): Promise { + const dir = path.join(ROOT, id); + const spec = JSON.parse( + await fs.readFile(path.join(dir, "spec.json"), "utf8"), + ) as EmailFixtureSpec; + const expected = JSON.parse( + await fs.readFile(path.join(dir, "expected.json"), "utf8"), + ) as EmailFixtureExpected; + const attachments = await Promise.all( + (spec.attachments || []).map(materializeAttachment), + ); + const eml = + spec.charset === "gbk" + ? buildGbkEml(spec, attachments) + : buildRawEml({ + from: spec.from, + subject: spec.subject, + text: spec.text, + messageId: spec.messageId, + date: spec.date ? new Date(spec.date) : new Date("2026-04-14T00:00:00Z"), + attachments: attachments.map((a) => ({ + filename: a.filename, + contentType: a.contentType, + content: a.content, + })), + }); + return { id, dir, spec, expected, eml, attachments }; +} + +export async function loadAllEmailFixtures(): Promise { + const ids = await listEmailFixtureIds(); + const out: LoadedEmailFixture[] = []; + for (const id of ids) { + out.push(await loadEmailFixture(id)); + } + return out; +} + +export function fixtureFilenames(spec: EmailFixtureSpec): string[] { + return (spec.attachments || []).map((a) => a.filename); +} + +/** 注入 MockMailServer / 后续工作台 */ +export function toMockMailInput(loaded: LoadedEmailFixture) { + return { + source: loaded.eml, + from: loaded.spec.from, + subject: loaded.spec.subject, + text: loaded.spec.text, + messageId: loaded.spec.messageId, + date: loaded.spec.date ? new Date(loaded.spec.date) : undefined, + attachments: loaded.attachments, + }; +} diff --git a/src/services/test/evaluate-email-fixture.ts b/src/services/test/evaluate-email-fixture.ts new file mode 100644 index 0000000..d0dc694 --- /dev/null +++ b/src/services/test/evaluate-email-fixture.ts @@ -0,0 +1,292 @@ +import { classifyMail } from "@/services/parse/classify"; +import { resolveDoContainerNo } from "@/services/parse/do-upload-extract"; +import { parsePackingListBuffer } from "@/services/parse/packing-list"; +import { resolveNoSpreadsheetOutcome } from "@/services/parse/pipeline"; +import { decidePull } from "@/services/imap/pull-filter"; +import { resolveAfterParse } from "@/services/state-machine"; +import { getEnv } from "@/lib/env"; +import type { MailStatus, MailType, ShipmentRow } from "@/types/mail"; +import { + fixtureFilenames, + type EmailFixtureExpected, + type LoadedEmailFixture, +} from "@/services/test/email-fixture-library"; + +export type FixtureExtractActual = { + mail_type: MailType; + pipeline_mail_type: MailType; + status: MailStatus; + last_error: string | null; + container_no: string | null; + selected_attachment: string | null; + shipment_count: number; + valid_shipment_count: number; + shipments: ShipmentRow[]; + pull: boolean; + missing_columns: boolean; +}; + +function spreadsheetRank(filename: string): number { + const f = filename || ""; + if (/数据模版|提拆派|卡派资料|卡派清单|装箱清单|packing/i.test(f)) return 0; + if (/换标|覆盖贴|贴标|操作指令/i.test(f)) return 2; + return 1; +} + +function pickSpreadsheet(filenames: string[]): string | null { + const sheets = filenames + .filter((f) => /\.(xlsx|xls|csv|zip)$/i.test(f)) + .slice() + .sort( + (a, b) => spreadsheetRank(a) - spreadsheetRank(b) || a.localeCompare(b), + ); + return sheets[0] || null; +} + +function isoFromText(text: string): string | null { + const m = text.match(/[A-Z]{4}\d{7}/); + return m ? m[0] : null; +} + +/** + * 对齐 ParsePipeline 的提取口径(不写库),供 fixture expected.json 断言。 + * 损坏 xlsx 抛错时与 pipeline catch 一致:mail_type 改写 UNKNOWN。 + */ +export async function evaluateEmailFixture( + loaded: LoadedEmailFixture, +): Promise { + const env = getEnv(); + const filenames = fixtureFilenames(loaded.spec); + const pull = decidePull({ + fromAddr: loaded.spec.from, + subject: loaded.spec.subject, + body: loaded.spec.text, + filenames, + rules: [], + }); + + if (!pull.pull) { + return { + mail_type: "UNKNOWN", + pipeline_mail_type: "UNKNOWN", + status: "IGNORED", + last_error: pull.reason, + container_no: isoFromText(`${loaded.spec.subject}\n${loaded.spec.text}`), + selected_attachment: null, + shipment_count: 0, + valid_shipment_count: 0, + shipments: [], + pull: false, + missing_columns: false, + }; + } + + const evidence = classifyMail({ + subject: loaded.spec.subject, + body: loaded.spec.text, + filenames, + }); + const mailType = evidence.mail_type; + const maxBytes = env.ATTACHMENT_MAX_BYTES; + const allOversized = + loaded.attachments.length > 0 && + loaded.attachments.every((a) => a.content.length > maxBytes); + + if (allOversized) { + return { + mail_type: mailType, + pipeline_mail_type: mailType, + status: "REJECTED_VALIDATION", + last_error: "ATTACHMENT_TOO_LARGE", + container_no: isoFromText(`${loaded.spec.subject}\n${loaded.spec.text}`), + selected_attachment: null, + shipment_count: 0, + valid_shipment_count: 0, + shipments: [], + pull: true, + missing_columns: false, + }; + } + + const selected = pickSpreadsheet(filenames); + const textContainer = isoFromText( + `${loaded.spec.subject}\n${loaded.spec.text}`, + ); + + if (!selected) { + const hasAttachments = loaded.attachments.length > 0; + const outcome = resolveNoSpreadsheetOutcome({ + mailType, + hasAttachments, + }); + let status = outcome.status; + if ( + status === "PARSED" && + (mailType === "WORK_ORDER" || + mailType === "DO_UPLOAD" || + mailType === "TRANSFER") + ) { + status = resolveAfterParse({ + mailType, + validCount: 0, + errorCode: null, + autoExecEligible: false, + }); + } + const containerNo = + resolveDoContainerNo({ + subject: loaded.spec.subject, + body: loaded.spec.text, + filenames, + }) || textContainer; + return { + mail_type: mailType, + pipeline_mail_type: mailType, + status, + last_error: outcome.lastError, + container_no: containerNo || textContainer, + selected_attachment: null, + shipment_count: 0, + valid_shipment_count: 0, + shipments: [], + pull: true, + missing_columns: false, + }; + } + + const att = loaded.attachments.find((a) => a.filename === selected); + let parsed; + try { + parsed = await parsePackingListBuffer(att?.content || Buffer.from(""), { + enableIsoCheck: env.ENABLE_ISO_CHECK, + hintContainerNo: textContainer || undefined, + }); + } catch { + return { + mail_type: mailType, + pipeline_mail_type: "UNKNOWN", + status: "PARSE_FAILED", + last_error: "MIME_PARSE_ERROR", + container_no: textContainer, + selected_attachment: selected, + shipment_count: 0, + valid_shipment_count: 0, + shipments: [], + pull: true, + missing_columns: false, + }; + } + + const missing = Array.isArray(parsed.lineage?.missing_columns); + if (parsed.errorCode === "PARSE_FAILED" || parsed.errorCode === "NO_SHIPMENT_ROWS") { + const isRecordOnly = + mailType === "WORK_ORDER" || + mailType === "TRANSFER" || + mailType === "DO_UPLOAD"; + const status: MailStatus = isRecordOnly + ? resolveAfterParse({ + mailType, + validCount: 0, + errorCode: null, + autoExecEligible: false, + }) + : "PARSE_FAILED"; + const lastError = missing + ? `missing_columns:${(parsed.lineage.missing_columns as string[]).join(",")}` + : parsed.errorCode || "NO_SUPPORTED_SPREADSHEET"; + return { + mail_type: mailType, + pipeline_mail_type: mailType, + status, + last_error: lastError, + container_no: + parsed.container_header?.F_ContainerNo || textContainer, + selected_attachment: selected, + shipment_count: parsed.shipments.length, + valid_shipment_count: parsed.shipments.filter((s) => s.row_status === "VALID") + .length, + shipments: parsed.shipments, + pull: true, + missing_columns: missing, + }; + } + + const validCount = parsed.shipments.filter((s) => s.row_status === "VALID").length; + const status = resolveAfterParse({ + mailType, + validCount, + errorCode: parsed.errorCode || null, + autoExecEligible: false, + }); + + return { + mail_type: mailType, + pipeline_mail_type: mailType, + status, + last_error: null, + container_no: parsed.container_header?.F_ContainerNo || textContainer, + selected_attachment: selected, + shipment_count: parsed.shipments.length, + valid_shipment_count: validCount, + shipments: parsed.shipments, + pull: true, + missing_columns: false, + }; +} + +/** 与 expected.json 对齐;返回差异文案,空数组即通过。 */ +export function diffFixtureExtract( + actual: FixtureExtractActual, + expected: EmailFixtureExpected, +): string[] { + const diffs: string[] = []; + const pull = expected.pull !== false; + if (actual.pull !== pull) diffs.push(`pull ${actual.pull} !== ${pull}`); + if (expected.pipeline_mail_type) { + if (actual.pipeline_mail_type !== expected.pipeline_mail_type) { + diffs.push( + `pipeline_mail_type ${actual.pipeline_mail_type} !== ${expected.pipeline_mail_type}`, + ); + } + } else if (actual.mail_type !== expected.mail_type) { + diffs.push(`mail_type ${actual.mail_type} !== ${expected.mail_type}`); + } + if (actual.status !== expected.status) { + diffs.push(`status ${actual.status} !== ${expected.status}`); + } + if (expected.last_error === "missing_columns") { + if (!actual.last_error?.startsWith("missing_columns")) { + diffs.push(`last_error ${actual.last_error} !== missing_columns*`); + } + } else if (actual.last_error !== expected.last_error) { + diffs.push(`last_error ${actual.last_error} !== ${expected.last_error}`); + } + if (expected.container_no && actual.container_no !== expected.container_no) { + diffs.push(`container_no ${actual.container_no} !== ${expected.container_no}`); + } + if ( + expected.selected_attachment && + /\.(xlsx|xls|csv)$/i.test(expected.selected_attachment) && + actual.selected_attachment !== expected.selected_attachment + ) { + diffs.push( + `selected_attachment ${actual.selected_attachment} !== ${expected.selected_attachment}`, + ); + } + if ( + expected.selected_attachment === null && + actual.selected_attachment !== null + ) { + diffs.push(`selected_attachment ${actual.selected_attachment} !== null`); + } + if ( + expected.shipment_count != null && + actual.shipment_count !== expected.shipment_count + ) { + diffs.push(`shipment_count ${actual.shipment_count} !== ${expected.shipment_count}`); + } + if (expected.missing_columns && !actual.missing_columns) { + diffs.push("missing_columns expected"); + } + return diffs; +} diff --git a/src/services/test/test-mode-env.ts b/src/services/test/test-mode-env.ts new file mode 100644 index 0000000..274b2a9 --- /dev/null +++ b/src/services/test/test-mode-env.ts @@ -0,0 +1,28 @@ +import { resetEnvCache } from "@/lib/env"; +import { resetMockMailServer } from "@/services/imap/mock-mail-server"; +import { resetMockTargetAPI } from "@/services/cc/mock-target-api"; +import { setTargetSystemClientOverride } from "@/services/cc/target-system-client"; + +const DEFAULT_DATABASE_URL = + "mysql://app:app@localhost:7023/email_forecast"; + +/** 单测 / 集成共用:打开 TEST_MODE,关掉 OCR,避免打真实 IMAP/CC。 */ +export function enableTestModeEnv(): void { + process.env.DATABASE_URL = + process.env.DATABASE_URL || DEFAULT_DATABASE_URL; + process.env.TEST_MODE = "1"; + process.env.OCR_PROVIDER = "off"; + process.env.ENABLE_FORCE_IMPORT = "1"; + resetEnvCache(); + resetMockMailServer(); + resetMockTargetAPI(); + setTargetSystemClientOverride(null); +} + +export function disableTestModeEnv(): void { + delete process.env.TEST_MODE; + resetEnvCache(); + resetMockMailServer(); + resetMockTargetAPI(); + setTargetSystemClientOverride(null); +} diff --git a/src/services/test/workbench-suite.ts b/src/services/test/workbench-suite.ts new file mode 100644 index 0000000..7f4f25b --- /dev/null +++ b/src/services/test/workbench-suite.ts @@ -0,0 +1,192 @@ +import { getMockMailServer } from "@/services/imap/mock-mail-server"; +import { getMockTargetAPI } from "@/services/cc/mock-target-api"; +import { saveContainer, buildSaveContainerEntity } from "@/services/cc/save-container"; +import { + loadAllEmailFixtures, + loadEmailFixture, +} from "@/services/test/email-fixture-library"; +import { + diffFixtureExtract, + evaluateEmailFixture, +} from "@/services/test/evaluate-email-fixture"; +import { + ingestFixtureAndParse, + loadParsedMail, + pingTestDatabase, +} from "@/services/test/db-harness"; +import type { MailStatus, MailType, ShipmentRow } from "@/types/mail"; + +export type WorkbenchCaseResult = { + id: string; + group: "extract" | "pipeline" | "fault"; + ok: boolean; + detail: string; +}; + +const ENTITY = () => + buildSaveContainerEntity( + { F_ContainerNo: "WHSU8127240", container_no_valid: true }, + { F_TransMode: 0, F_OperationType: 0, F_ContainerNo: "WHSU8127240" }, + ); + +const ROW: ShipmentRow = { + row_index: 0, + row_status: "VALID", + F_FBACode: "LAS1", + F_Transporter: "TRUCK", + F_CTNS: 10, +}; + +async function runFaultMatrix(): Promise { + const out: WorkbenchCaseResult[] = []; + const imap = getMockMailServer(); + const cfg = { + host: "mock.local", + port: 993, + user: "t", + pass: "t", + connectTimeoutMs: 1000, + readTimeoutMs: 1000, + }; + + imap.setFault("auth_fail"); + try { + await imap.createClient(cfg).connect(); + out.push({ id: "imap.auth_fail", group: "fault", ok: false, detail: "应抛鉴权失败" }); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + out.push({ + id: "imap.auth_fail", + group: "fault", + ok: /AUTHENTICATIONFAILED/.test(msg), + detail: msg, + }); + } + + imap.setFault("timeout"); + try { + await imap.createClient(cfg).connect(); + out.push({ id: "imap.timeout", group: "fault", ok: false, detail: "应抛超时" }); + } catch (err) { + const code = (err as { code?: string })?.code; + out.push({ + id: "imap.timeout", + group: "fault", + ok: code === "ETIMEOUT", + detail: code || (err instanceof Error ? err.message : String(err)), + }); + } + imap.setFault("none"); + + const target = getMockTargetAPI(); + const cases: Array<{ + id: string; + fault: "401" | "500" | "timeout" | "partial"; + expectOk: false; + expectStatus: "FAILED" | "TIMEOUT_UNKNOWN"; + }> = [ + { id: "cc.401", fault: "401", expectOk: false, expectStatus: "FAILED" }, + { id: "cc.500", fault: "500", expectOk: false, expectStatus: "FAILED" }, + { id: "cc.timeout", fault: "timeout", expectOk: false, expectStatus: "TIMEOUT_UNKNOWN" }, + { id: "cc.partial", fault: "partial", expectOk: false, expectStatus: "TIMEOUT_UNKNOWN" }, + ]; + for (const c of cases) { + target.setFault(c.fault, "/Container/SaveContainer"); + const res = await saveContainer({ entity: ENTITY(), shipments: [ROW] }); + const status = res.ok ? "SUCCESS" : res.status; + const ok = !res.ok && status === c.expectStatus; + out.push({ + id: c.id, + group: "fault", + ok, + detail: res.ok ? "unexpected success" : `${status}: ${res.error}`, + }); + } + target.setFault("ok", "/Container/SaveContainer"); + target.setFault("ok"); + return out; +} + +export async function runWorkbenchSuite(input?: { + pipeline?: boolean; + runId?: string; +}): Promise<{ + passed: number; + failed: number; + cases: WorkbenchCaseResult[]; +}> { + const cases: WorkbenchCaseResult[] = []; + const all = await loadAllEmailFixtures(); + for (const f of all) { + const actual = await evaluateEmailFixture(f); + const diffs = diffFixtureExtract(actual, f.expected); + cases.push({ + id: `extract.${f.id}`, + group: "extract", + ok: diffs.length === 0, + detail: diffs.length ? diffs.join("; ") : `${actual.status} / ${actual.mail_type}`, + }); + } + + cases.push(...(await runFaultMatrix())); + + if (input?.pipeline) { + const dbOk = await pingTestDatabase(); + if (!dbOk) { + cases.push({ + id: "pipeline.db", + group: "pipeline", + ok: false, + detail: "数据库不可达,跳过 ParsePipeline", + }); + } else { + const runId = input.runId || `wb.${Date.now().toString(36)}`; + for (const f of all) { + if (f.expected.pull === false || f.expected.idempotent_with) continue; + try { + const { mailId } = await ingestFixtureAndParse(f, runId); + const mail = await loadParsedMail(mailId); + const expectType = f.expected.pipeline_mail_type || f.expected.mail_type; + const diffs: string[] = []; + if (mail.mailType !== expectType) { + diffs.push(`mailType ${mail.mailType} !== ${expectType}`); + } + if (mail.status !== f.expected.status) { + diffs.push(`status ${mail.status} !== ${f.expected.status}`); + } + if (f.expected.last_error === "missing_columns") { + if (!mail.lastError?.startsWith("missing_columns")) { + diffs.push(`lastError ${mail.lastError}`); + } + } else if (mail.lastError !== f.expected.last_error) { + diffs.push(`lastError ${mail.lastError} !== ${f.expected.last_error}`); + } + cases.push({ + id: `pipeline.${f.id}`, + group: "pipeline", + ok: diffs.length === 0, + detail: + diffs.length > 0 + ? diffs.join("; ") + : `${mail.status as MailStatus} / ${mail.mailType as MailType}`, + }); + } catch (err) { + cases.push({ + id: `pipeline.${f.id}`, + group: "pipeline", + ok: false, + detail: err instanceof Error ? err.message : String(err), + }); + } + } + } + } + + const passed = cases.filter((c) => c.ok).length; + return { passed, failed: cases.length - passed, cases }; +} + +export async function injectWorkbenchFixture(fixtureId: string, runId: string) { + const loaded = await loadEmailFixture(fixtureId); + return ingestFixtureAndParse(loaded, runId); +} diff --git a/src/types/index.ts b/src/types/index.ts index fb2d088..d4860df 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -54,6 +54,8 @@ export interface MailMessage { type_evidence: import("./mail").TypeEvidence; version: number; last_error?: string | null; + /** 贯穿拉取/解析/提交日志 */ + trace_id?: string | null; attachments: MailAttachment[]; parse_result: ParseResult | null; container_no?: string | null; @@ -76,6 +78,10 @@ export interface MailListItem { ingest_source: "IMAP"; ingest_source_label: string; received_at: string | null; + /** IMAP/样例入库时间 */ + pulled_at?: string | null; + /** 最近一次成功导入 CC 的时间 */ + imported_at?: string | null; updated_at: string; body_summary: string; mail_type: import("./mail").MailType; @@ -83,6 +89,8 @@ export interface MailListItem { content_kind: MailContentKind; content_kind_label: string; status: import("./mail").MailStatus; + last_error?: string | null; + trace_id?: string | null; container_no?: string | null; attachments: Array<{ id: string; filename: string; size: number }>; /** 处理结束时间:导入终态取 updated_at,否则 null */ @@ -116,11 +124,13 @@ export interface ContainerImport { export interface SessionUser { username: string; - role: "admin" | "ops"; + role: "admin" | "ops" | "customer"; + user_id?: string | null; } export interface ImportPayload { version: number; + instruction_id?: string; idempotency_key: string; F_TransMode: import("./mail").TransMode; F_OperationType: import("./mail").OperationType; @@ -153,6 +163,8 @@ export interface HealthStatus { cc_reason?: string | null; enable_force_import?: boolean; enable_type_override?: boolean; + test_mode?: boolean; + alert_webhook_configured?: boolean; } export interface MailboxAccount { diff --git a/src/types/mail.ts b/src/types/mail.ts index 1d58af6..8bd98c4 100644 --- a/src/types/mail.ts +++ b/src/types/mail.ts @@ -23,6 +23,11 @@ export interface MailRecordModules { ops_hints?: string[]; /** 船司匹配线索(优先提单前 4 位字母,如 WHLC) */ shipping_line_hint?: string; + /** + * 主题/正文里带标签、但对不上预报 UI 格的字段(如 封号:EMCEJJ0635), + * 写入柜头备注 F_MemoRemark,不得去抢客户名。 + */ + memo_bits?: string[]; } export interface MailRecordTable { diff --git a/src/utils/dates.ts b/src/utils/dates.ts index 9fd219f..436bb94 100644 --- a/src/utils/dates.ts +++ b/src/utils/dates.ts @@ -36,6 +36,31 @@ export function parseFlexibleDate(raw: string): Date | null { return Number.isNaN(dt.getTime()) ? null : dt; } +/** + * 邮件常用松散日期:完整年月日,或仅月日(补 defaultYear)。 + * 例:2026.07.10 / 7.10 / 7/7 + */ +export function toLooseShanghaiDateString( + raw: string | undefined | null, + defaultYear = new Date().getFullYear(), +): string | null { + if (!raw) return null; + const t = raw.trim(); + const full = t.match(/(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})/); + if (full) { + return toShanghaiDateString( + `${full[1]}-${full[2].padStart(2, "0")}-${full[3].padStart(2, "0")}`, + ); + } + const md = t.match(/^(\d{1,2})[-/.](\d{1,2})$/); + if (md) { + return toShanghaiDateString( + `${defaultYear}-${md[1].padStart(2, "0")}-${md[2].padStart(2, "0")}`, + ); + } + return toShanghaiDateString(t); +} + export function formatShanghaiDateTime(d: Date): string { return new Intl.DateTimeFormat("zh-CN", { timeZone: TZ, diff --git a/src/utils/idempotency-client.ts b/src/utils/idempotency-client.ts new file mode 100644 index 0000000..2d7d594 --- /dev/null +++ b/src/utils/idempotency-client.ts @@ -0,0 +1,34 @@ +/** 浏览器可用的短幂等键,避免把全部行号拼进 key 超过 128 字 */ + +function fnv1a(input: string): string { + let h = 0x811c9dc5; + for (let i = 0; i < input.length; i++) { + h ^= input.charCodeAt(i); + h = Math.imul(h, 0x01000193); + } + return (h >>> 0).toString(16); +} + +export function buildImportIdempotencyKey(input: { + mailId: string; + instructionId?: string; + containerNo: string; + rowIndexes: number[]; + forceSkip?: boolean; +}): string { + const rows = [...input.rowIndexes].sort((a, b) => a - b); + const sig = fnv1a( + `${input.instructionId || "mail"}:${input.containerNo}:${rows.join(",")}`, + ); + const key = [ + input.mailId, + input.instructionId ? "i" : "m", + input.containerNo, + String(rows.length), + sig, + input.forceSkip ? "f" : "", + ] + .filter(Boolean) + .join(":"); + return key.slice(0, 128); +} diff --git a/src/utils/mail-submit-version.ts b/src/utils/mail-submit-version.ts new file mode 100644 index 0000000..39b6a99 --- /dev/null +++ b/src/utils/mail-submit-version.ts @@ -0,0 +1,45 @@ +import type { ApiResponse } from "@/types"; + +export function readServerVersion(details: unknown): number | null { + if (!details || typeof details !== "object") return null; + const raw = (details as { server_version?: unknown }).server_version; + const n = typeof raw === "number" ? raw : Number(raw); + if (!Number.isInteger(n) || n < 1) return null; + return n; +} + +function readResponseVersion(data: unknown): number | null { + if (!data || typeof data !== "object") return null; + const raw = (data as { version?: unknown }).version; + const n = typeof raw === "number" ? raw : Number(raw); + if (!Number.isInteger(n) || n < 1) return null; + return n; +} + +/** + * 导入失败会把邮件 version +1,页面若仍拿旧 version 再点会误报「版本冲突」。 + * 失败/冲突响应里带上服务端 version,冲突时自动用新 version 重试一次。 + */ +export async function withVersionConflictRetry(input: { + version: number; + submit: (version: number) => Promise>; + onVersion: (version: number) => void; +}): Promise> { + const run = async (version: number) => { + const res = await input.submit(version); + if (res.ok) { + const next = readResponseVersion(res.data); + if (next) input.onVersion(next); + return res; + } + const next = readServerVersion(res.error.details); + if (next) input.onVersion(next); + return res; + }; + + const first = await run(input.version); + if (first.ok || first.error.code !== "VERSION_CONFLICT") return first; + const next = readServerVersion(first.error.details); + if (!next || next === input.version) return first; + return run(next); +} diff --git a/src/utils/mailContent.ts b/src/utils/mailContent.ts index a4c22b4..3955392 100644 --- a/src/utils/mailContent.ts +++ b/src/utils/mailContent.ts @@ -1,3 +1,4 @@ +import type { Prisma } from "@prisma/client"; import type { MailContentKind } from "@/types"; export const MAIL_CONTENT_KIND_LABEL: Record = { @@ -9,6 +10,71 @@ export const MAIL_CONTENT_KIND_LABEL: Record = { EMPTY: "空内容", }; +const PACKING_NAME_OR: Prisma.MailAttachmentWhereInput[] = [ + { filename: { contains: ".xlsx" } }, + { filename: { contains: ".xls" } }, + { filename: { contains: ".csv" } }, + { filename: { contains: "卡派" } }, + { filename: { contains: "装箱清单" } }, + { filename: { contains: "拆柜清单" } }, +]; + +const INSTRUCTION_NAME_OR: Prisma.MailAttachmentWhereInput[] = [ + { filename: { contains: ".pdf" } }, + { filename: { contains: "换标" } }, + { filename: { contains: "贴标" } }, + { filename: { contains: "指令" } }, +]; + +/** 列表筛选:尽量对齐 deriveMailContentKind 的优先级 */ +export function contentKindWhere( + kind: MailContentKind, +): Prisma.MailMessageWhereInput { + switch (kind) { + case "PACKING_LIST": + return { attachments: { some: { OR: PACKING_NAME_OR } } }; + case "INSTRUCTION_FILE": + return { + AND: [ + { attachments: { some: { OR: INSTRUCTION_NAME_OR } } }, + { attachments: { none: { OR: PACKING_NAME_OR } } }, + ], + }; + case "ARCHIVE": + return { + AND: [ + { attachments: { some: { filename: { contains: ".zip" } } } }, + { attachments: { none: { OR: PACKING_NAME_OR } } }, + { attachments: { none: { OR: INSTRUCTION_NAME_OR } } }, + ], + }; + case "OTHER_ATTACHMENT": + return { + AND: [ + { attachments: { some: {} } }, + { attachments: { none: { OR: PACKING_NAME_OR } } }, + { attachments: { none: { OR: INSTRUCTION_NAME_OR } } }, + { attachments: { none: { filename: { contains: ".zip" } } } }, + ], + }; + case "PLAIN_TEXT": + return { + attachments: { none: {} }, + AND: [ + { bodyText: { not: null } }, + { bodyText: { not: "" } }, + ], + }; + case "EMPTY": + return { + attachments: { none: {} }, + OR: [{ bodyText: null }, { bodyText: "" }], + }; + default: + return {}; + } +} + export function deriveMailContentKind( filenames: string[], bodyText?: string | null, diff --git a/src/worker/index.ts b/src/worker/index.ts index 9f18a46..c371726 100644 --- a/src/worker/index.ts +++ b/src/worker/index.ts @@ -12,7 +12,6 @@ import { } from "@/services/imap/runtime-status"; import { staleReaper } from "@/services/imap/stale-reaper"; import { drainFetchedMails } from "@/services/parse/drain-fetched"; -import { runDataRetentionCleanup } from "@/services/retention/cleanup"; import { getEnv } from "@/lib/env"; async function tick(): Promise { @@ -59,15 +58,6 @@ async function tick(): Promise { } catch (err) { logger.error({ err }, "worker.compensation_failed"); } - - try { - const retention = await runDataRetentionCleanup({ limit: 50 }); - if (retention.deletedMails > 0 || retention.errors > 0) { - logger.info(retention, "worker.retention"); - } - } catch (err) { - logger.error({ err }, "worker.retention_failed"); - } } async function scheduleLoop(): Promise { @@ -112,7 +102,11 @@ async function main(): Promise { } const idle = IdleSupervisor.create(); - await idle.start(); + try { + await idle.start(); + } catch (err) { + logger.error({ err }, "worker.idle_start_failed"); + } await scheduleLoop(); } diff --git a/tests/e2e/test-mode-pipeline.spec.ts b/tests/e2e/test-mode-pipeline.spec.ts new file mode 100644 index 0000000..b74a960 --- /dev/null +++ b/tests/e2e/test-mode-pipeline.spec.ts @@ -0,0 +1,128 @@ +import { test, expect, type APIRequestContext } from "@playwright/test"; + +async function login(page: import("@playwright/test").Page) { + const res = await page.request.post("/api/auth/login", { + data: { username: "admin", password: "admin123" }, + }); + expect(res.ok(), await res.text()).toBeTruthy(); + await page.goto("/mails"); + await expect(page).toHaveURL(/\/mails/, { timeout: 20_000 }); +} + +async function injectFixture(request: APIRequestContext, fixtureId: string) { + const res = await request.post("/api/test/inject", { + data: { fixture_id: fixtureId }, + }); + if (res.status() === 404) { + test.skip(true, "Server is not in TEST_MODE"); + } + expect(res.ok(), await res.text()).toBeTruthy(); + return res.json() as Promise<{ + ok: true; + data: { + mail_id: string; + status: string; + mail_type: string; + last_error: string | null; + version: number; + subject: string; + }; + }>; +} + +test.describe("TEST_MODE pipeline e2e", () => { + test("inject forecast → UI 待确认 → submit → MockTargetAPI 收到柜号", async ({ + page, + }) => { + const api = page.request; + await login(page); + await api.post("/api/test/reset"); + const injected = await injectFixture(api, "01-normal-forecast"); + const mailId = injected.data.mail_id; + + await page.goto(`/mails/${mailId}`); + await expect(page.getByText(/待确认/).first()).toBeVisible({ timeout: 15_000 }); + await expect(page.getByText(/WHSU8127240/).first()).toBeVisible(); + + const detail = await api.get(`/api/mails/${mailId}`); + const body = await detail.json(); + const shipments = body.data?.parse_result?.shipments || []; + const indexes = shipments + .filter((s: { row_status?: string; row_index: number }) => s.row_status === "VALID") + .map((s: { row_index: number }) => s.row_index); + + const imported = await api.post(`/api/mails/${mailId}/import`, { + data: { + version: body.data.version, + idempotency_key: `e2e-${Date.now()}`, + F_TransMode: 0, + F_OperationType: 0, + container_header: { F_ContainerNo: `TPZU${String(Date.now()).slice(-7)}` }, + selected_row_indexes: indexes, + ack_unmapped_channels: true, + force_skip_conflict: true, + }, + }); + const importedJson = await imported.json(); + expect(importedJson.ok, JSON.stringify(importedJson)).toBe(true); + expect(importedJson.data?.mail_status).toBe("SUCCESS"); + + const rec = await api.get("/api/test/target-requests"); + const recBody = await rec.json(); + const saves = (recBody.data?.requests || []).filter( + (r: { path: string }) => r.path === "/Container/SaveContainer", + ); + expect(saves.length).toBeGreaterThan(0); + expect(JSON.stringify(saves[0].body)).toMatch(/LAS1|WHSU8127240|TPZU/); + + await page.reload(); + await expect(page.getByText(/导入成功/).first()).toBeVisible({ timeout: 15_000 }); + }); + + test("fault: missing attachment shows parse failure on detail", async ({ page }) => { + await login(page); + const injected = await injectFixture(page.request, "09-no-attachment"); + await page.goto(`/mails/${injected.data.mail_id}`); + await expect(page.getByText(/校验拒绝|未解析到货件/).first()).toBeVisible({ + timeout: 15_000, + }); + }); + + test("fault: target 401 keeps mail failed with visible error", async ({ page }) => { + const api = page.request; + await login(page); + await api.post("/api/test/reset"); + const injected = await injectFixture(api, "01-normal-forecast"); + const mailId = injected.data.mail_id; + await api.post("/api/test/faults", { + data: { target: "401", target_path: "/Container/SaveContainer" }, + }); + + const detail = await api.get(`/api/mails/${mailId}`); + const body = await detail.json(); + const shipments = body.data?.parse_result?.shipments || []; + const indexes = shipments + .filter((s: { row_status?: string; row_index: number }) => s.row_status === "VALID") + .map((s: { row_index: number }) => s.row_index); + + const imported = await api.post(`/api/mails/${mailId}/import`, { + data: { + version: body.data.version, + idempotency_key: `e2e-401-${Date.now()}`, + F_TransMode: 0, + F_OperationType: 0, + container_header: { F_ContainerNo: `TPZU${String(Date.now()).slice(-7)}` }, + selected_row_indexes: indexes, + ack_unmapped_channels: true, + force_skip_conflict: true, + }, + }); + const importedJson = await imported.json(); + expect(importedJson.data?.mail_status || importedJson.error).toBeTruthy(); + + await page.goto(`/mails/${mailId}`); + await expect(page.getByText(/导入失败|登录失败|权限|CarrierCentral/i).first()).toBeVisible({ + timeout: 15_000, + }); + }); +}); diff --git a/tests/integration/pipeline-fixtures.test.ts b/tests/integration/pipeline-fixtures.test.ts new file mode 100644 index 0000000..f4dc262 --- /dev/null +++ b/tests/integration/pipeline-fixtures.test.ts @@ -0,0 +1,246 @@ +import { randomUUID } from "crypto"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { prisma } from "@/services/db"; +import { ConfirmImport } from "@/services/import/confirm"; +import { drainFetchedMails } from "@/services/parse/drain-fetched"; +import { getMockTargetAPI } from "@/services/cc/mock-target-api"; +import { ImapPoller } from "@/services/imap/poller"; +import { getMockMailServer } from "@/services/imap/mock-mail-server"; +import { loadEmailFixture, loadAllEmailFixtures } from "@/services/test/email-fixture-library"; +import { enableTestModeEnv, disableTestModeEnv } from "@/services/test/test-mode-env"; +import { + deleteTestMailsByRunId, + ingestFixtureAndParse, + ingestFixtureFetched, + loadParsedMail, + pingTestDatabase, +} from "@/services/test/db-harness"; +import type { ShipmentRow } from "@/types/mail"; + +function uniqueContainer(): string { + return `TPZU${String(Date.now()).slice(-7)}`; +} + +async function confirmForecast( + mailId: bigint, + version: number, + indexes: number[], + containerNo?: string, +) { + return ConfirmImport.execute({ + mailId, + actor: "p3-test", + version, + idempotencyKey: randomUUID(), + F_TransMode: 0, + F_OperationType: 0, + containerHeader: containerNo ? { F_ContainerNo: containerNo } : {}, + selectedRowIndexes: indexes, + ackUnmappedChannels: true, + forceSkipConflict: true, + }); +} + +const RUN_ID = `r${Date.now().toString(36)}`; + +describe("pipeline integration (DB + Mock IMAP/CC)", () => { + let dbOk = false; + + beforeAll(async () => { + enableTestModeEnv(); + dbOk = await pingTestDatabase(); + }); + + afterAll(async () => { + if (dbOk) await deleteTestMailsByRunId(RUN_ID); + disableTestModeEnv(); + }); + + it("ParsePipeline vs expected.json for pullable fixtures", async () => { + if (!dbOk) return; + const all = await loadAllEmailFixtures(); + for (const f of all) { + if (f.expected.pull === false) continue; + if (f.expected.idempotent_with) continue; + const { mailId } = await ingestFixtureAndParse(f, RUN_ID); + const mail = await loadParsedMail(mailId); + const expectType = f.expected.pipeline_mail_type || f.expected.mail_type; + expect(mail.mailType, f.id).toBe(expectType); + expect(mail.status, f.id).toBe(f.expected.status); + if (f.expected.last_error === "missing_columns") { + expect(mail.lastError, f.id).toMatch(/^missing_columns/); + } else { + expect(mail.lastError, f.id).toBe(f.expected.last_error); + } + if (f.expected.status === "PARSE_FAILED" || f.expected.status === "REJECTED_VALIDATION") { + const raw = mail.snapshotPath; + expect(raw, `${f.id} keeps snapshot`).toBeTruthy(); + } + expect(mail.traceId, `${f.id} has traceId`).toBeTruthy(); + const shipments = (mail.parseResult?.shipments as unknown as ShipmentRow[]) || []; + if (f.expected.shipment_count != null && mail.parseResult) { + expect(shipments.length, f.id).toBe(f.expected.shipment_count); + } + } + }, 30_000); + + it("parse fail on one mail does not block drain of another", async () => { + if (!dbOk) return; + const bad = await loadEmailFixture("12-missing-required"); + const good = await loadEmailFixture("01-normal-forecast"); + const a = await ingestFixtureFetched(bad, RUN_ID, "drain-bad"); + const b = await ingestFixtureFetched(good, RUN_ID, "drain-good"); + await drainFetchedMails(20); + const mailA = await loadParsedMail(a.mailId); + const mailB = await loadParsedMail(b.mailId); + expect(mailA.status).toBe("PARSE_FAILED"); + expect(mailA.lastError).toMatch(/missing_columns/); + expect(mailB.status).toBe("PENDING_CONFIRM"); + }); + + it("unknown format keyword miss stays IGNORED (not pulled into ops queue)", async () => { + if (!dbOk) return; + const f = await loadEmailFixture("16-unknown-format"); + expect(f.expected.status).toBe("IGNORED"); + expect(f.expected.last_error).toBe("SKIP_FILTER"); + }); + + it("MockMailServer → parse → stage → MockTargetAPI SaveContainer payload matches source", async () => { + if (!dbOk) return; + const f = await loadEmailFixture("01-normal-forecast"); + const { mailId } = await ingestFixtureAndParse(f, `${RUN_ID}.cc`); + const mail = await loadParsedMail(mailId); + expect(mail.status).toBe("PENDING_CONFIRM"); + const shipments = (mail.parseResult?.shipments as unknown as ShipmentRow[]) || []; + const indexes = shipments.filter((s) => s.row_status === "VALID").map((s) => s.row_index); + const cn = uniqueContainer(); + getMockTargetAPI().reset(); + const result = await confirmForecast(mailId, mail.version, indexes, cn); + expect(result.mailStatus).toBe("SUCCESS"); + const posts = getMockTargetAPI() + .getRequests() + .filter((r) => r.path === "/Container/SaveContainer"); + expect(posts.length).toBeGreaterThanOrEqual(1); + const body = JSON.stringify(posts[0].body); + expect(body).toContain(cn); + expect(body).toContain("LAS1"); + }); + + it("duplicate submit is idempotent (mailId+container+hash), no second CC write", async () => { + if (!dbOk) return; + const f = await loadEmailFixture("01-normal-forecast"); + const { mailId } = await ingestFixtureAndParse(f, `${RUN_ID}.dup`); + const mail = await loadParsedMail(mailId); + const shipments = (mail.parseResult?.shipments as unknown as ShipmentRow[]) || []; + const indexes = shipments.filter((s) => s.row_status === "VALID").map((s) => s.row_index); + const cn = uniqueContainer(); + getMockTargetAPI().reset(); + const first = await confirmForecast(mailId, mail.version, indexes, cn); + expect(first.mailStatus).toBe("SUCCESS"); + const after = await prisma.mailMessage.findUniqueOrThrow({ + where: { id: mailId }, + select: { version: true, status: true }, + }); + getMockTargetAPI().reset(); + const second = await confirmForecast(mailId, after.version, indexes, cn); + expect(second.mailStatus).toBe("SUCCESS"); + expect( + getMockTargetAPI().getRequests().filter((r) => r.path === "/Container/SaveContainer"), + ).toHaveLength(0); + }); + + it("submit 401 leaves mail FAILED, not SUCCESS", async () => { + if (!dbOk) return; + const f = await loadEmailFixture("01-normal-forecast"); + const { mailId } = await ingestFixtureAndParse(f, `${RUN_ID}.401`); + const mail = await loadParsedMail(mailId); + const shipments = (mail.parseResult?.shipments as unknown as ShipmentRow[]) || []; + const indexes = shipments.filter((s) => s.row_status === "VALID").map((s) => s.row_index); + getMockTargetAPI().reset(); + getMockTargetAPI().setFault("401", "/Container/SaveContainer"); + const result = await confirmForecast( + mailId, + mail.version, + indexes, + uniqueContainer(), + ); + expect(result.mailStatus).toBe("FAILED"); + expect(result.imports[0].status).toBe("FAILED"); + expect(result.imports[0].lastError).toMatch(/登录|权限|CarrierCentral/); + const comps = await prisma.importCompensation.findMany({ + where: { importId: result.imports[0].id }, + }); + expect(comps).toHaveLength(0); + }); + + it("submit timeout creates compensation for one-click retry", async () => { + if (!dbOk) return; + const f = await loadEmailFixture("01-normal-forecast"); + const { mailId } = await ingestFixtureAndParse(f, `${RUN_ID}.to`); + const mail = await loadParsedMail(mailId); + const shipments = (mail.parseResult?.shipments as unknown as ShipmentRow[]) || []; + const indexes = shipments.filter((s) => s.row_status === "VALID").map((s) => s.row_index); + getMockTargetAPI().reset(); + getMockTargetAPI().setFault("timeout", "/Container/SaveContainer"); + const result = await confirmForecast( + mailId, + mail.version, + indexes, + uniqueContainer(), + ); + expect(result.mailStatus).toBe("FAILED"); + expect(result.imports[0].status).toBe("TIMEOUT_UNKNOWN"); + const comps = await prisma.importCompensation.findMany({ + where: { importId: result.imports[0].id, reason: "TIMEOUT_UNKNOWN" }, + }); + expect(comps.length).toBe(1); + expect(comps[0].status).toBe("OPEN"); + }); + + it("submit 500 creates CC_5XX compensation, mail not SUCCESS", async () => { + if (!dbOk) return; + const f = await loadEmailFixture("01-normal-forecast"); + const { mailId } = await ingestFixtureAndParse(f, `${RUN_ID}.500`); + const mail = await loadParsedMail(mailId); + const shipments = (mail.parseResult?.shipments as unknown as ShipmentRow[]) || []; + const indexes = shipments.filter((s) => s.row_status === "VALID").map((s) => s.row_index); + getMockTargetAPI().reset(); + getMockTargetAPI().setFault("500", "/Container/SaveContainer"); + const result = await confirmForecast( + mailId, + mail.version, + indexes, + uniqueContainer(), + ); + expect(result.mailStatus).toBe("FAILED"); + expect(result.imports[0].status).toBe("FAILED"); + const comps = await prisma.importCompensation.findMany({ + where: { importId: result.imports[0].id, reason: "CC_5XX" }, + }); + expect(comps.length).toBe(1); + expect(comps[0].status).toBe("OPEN"); + getMockTargetAPI().setFault("ok"); + }); + + it("IMAP auth_fail: tick does not throw and records consecutiveFail", async () => { + if (!dbOk) return; + const statusPath = `${process.cwd().replace(/\\/g, "/")}/data/imap-runtime.json`; + const fs = await import("fs/promises"); + const backup = await fs.readFile(statusPath, "utf8").catch(() => null); + try { + getMockMailServer().setFault("auth_fail"); + const poller = ImapPoller.create(); + await expect(poller.tick(1)).resolves.toMatchObject({ skipped: false }); + const { readImapRuntimeStatus } = await import( + "@/services/imap/runtime-status" + ); + const runtime = await readImapRuntimeStatus(); + expect(runtime.consecutiveFail).toBeGreaterThanOrEqual(1); + expect(runtime.lastError).toMatch(/AUTH|鉴权|同步失败|authentication/i); + } finally { + getMockMailServer().setFault("none"); + if (backup != null) await fs.writeFile(statusPath, backup); + else await fs.rm(statusPath, { force: true }); + } + }); +}); diff --git a/tests/unit/address-block.test.ts b/tests/unit/address-block.test.ts new file mode 100644 index 0000000..81d7847 --- /dev/null +++ b/tests/unit/address-block.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from "vitest"; +import { + columnValueFromCell, + parseLabeledAddressBlock, + parseLabeledCell, +} from "@/services/parse/address-block"; + +const SAMPLE = `仓库代码: LAS1 +收件人: LAS1 +公司名称: AMAZON COM SERVICES INC +联系电话: 0123456789 +省/市/区: NV / HENDERSON / +邮政编码: 89044-8746 +收货地址: 12300 Bermuda Road +备注:三票分开打托`; + +describe("parseLabeledCell", () => { + it("takes the home field and still harvests the rest", () => { + const p = parseLabeledCell( + "仓库代码: LAS1\n收件人: LAS1\n收货地址: 12300 Bermuda Road", + ); + expect(p?.F_FBACode).toBe("LAS1"); + expect(p?.F_Contact).toBe("LAS1"); + expect(p?.F_Address).toBe("12300 Bermuda Road"); + expect(columnValueFromCell("F_FBACode", SAMPLE)).toBe("LAS1"); + expect(columnValueFromCell("F_Address", SAMPLE)).toBe("12300 Bermuda Road"); + expect(columnValueFromCell("F_Remark", SAMPLE)).toBe("三票分开打托"); + }); + + it("splits Address 单元格里的中文键值块", () => { + const p = parseLabeledAddressBlock(SAMPLE); + expect(p).toBeTruthy(); + expect(p!.warehouseCode).toBe("LAS1"); + expect(p!.contact).toBe("LAS1"); + expect(p!.company).toBe("AMAZON COM SERVICES INC"); + expect(p!.mobile).toBe("0123456789"); + expect(p!.state).toBe("NV"); + expect(p!.city).toBe("HENDERSON"); + expect(p!.zip).toBe("89044-8746"); + expect(p!.street).toBe("12300 Bermuda Road"); + expect(p!.remark).toBe("三票分开打托"); + }); + + it("ignores a plain street with no labels", () => { + expect(parseLabeledAddressBlock("12924 Pierce St")).toBeNull(); + }); +}); diff --git a/tests/unit/alert-webhook.test.ts b/tests/unit/alert-webhook.test.ts new file mode 100644 index 0000000..b1e9f82 --- /dev/null +++ b/tests/unit/alert-webhook.test.ts @@ -0,0 +1,63 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { + emitAlert, + getEmittedAlerts, + resetAlertState, +} from "@/services/alert/webhook"; +import { enableTestModeEnv, disableTestModeEnv } from "@/services/test/test-mode-env"; + +describe("alert webhook", () => { + afterEach(() => { + resetAlertState(); + delete process.env.ALERT_WEBHOOK_URL; + disableTestModeEnv(); + vi.unstubAllGlobals(); + }); + + it("records in memory when webhook url empty", async () => { + enableTestModeEnv(); + const sent = await emitAlert({ + key: "imap.consecutive_fail", + title: "IMAP 连续拉取失败", + severity: "error", + fields: { consecutiveFail: 3 }, + }); + expect(sent).toBe(true); + expect(getEmittedAlerts()).toHaveLength(1); + expect(getEmittedAlerts()[0].title).toMatch(/IMAP/); + }); + + it("dedups same key within window", async () => { + enableTestModeEnv(); + await emitAlert({ key: "cc.auth_expired", title: "a", severity: "error" }); + const second = await emitAlert({ + key: "cc.auth_expired", + title: "b", + severity: "error", + }); + expect(second).toBe(false); + expect(getEmittedAlerts()).toHaveLength(1); + }); + + it("POSTs JSON when ALERT_WEBHOOK_URL is set", async () => { + enableTestModeEnv(); + process.env.ALERT_WEBHOOK_URL = "https://example.test/hook"; + const { resetEnvCache } = await import("@/lib/env"); + resetEnvCache(); + const fetchMock = vi.fn().mockResolvedValue({ ok: true }); + vi.stubGlobal("fetch", fetchMock); + resetAlertState(); + await emitAlert({ + key: "cc.login_fail", + title: "CC 登录失败", + severity: "error", + fields: { code: 401 }, + }); + expect(fetchMock).toHaveBeenCalledTimes(1); + const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]; + expect(url).toBe("https://example.test/hook"); + expect(init.method).toBe("POST"); + delete process.env.ALERT_WEBHOOK_URL; + resetEnvCache(); + }); +}); diff --git a/tests/unit/bl-plus-template.test.ts b/tests/unit/bl-plus-template.test.ts index 3a5d134..198c976 100644 --- a/tests/unit/bl-plus-template.test.ts +++ b/tests/unit/bl-plus-template.test.ts @@ -3,6 +3,8 @@ import { extractBlPlusFromMail, mapBlModulesToHeader, parseBlPlusTemplate, + parseSpaceForecastTemplate, + usesPlusFieldDelimiter, } from "@/services/parse/bl-plus-template"; import { extractContainerHeaderFromText, @@ -48,7 +50,7 @@ describe("parseBlPlusTemplate", () => { expect(header.F_OperationType).toBe(0); expect(header.F_TransMode).toBe(0); expect(header.F_Instruction).toMatch(/船名航次.*HMM EMERALD/); - expect(header.F_MemoRemark).toContain("新辰泽"); + expect(header.F_MemoRemark).toBeUndefined(); }); it("extracts from subject + body like template mail", () => { @@ -77,13 +79,148 @@ describe("parseBlPlusTemplate", () => { it("parses mail2 transfer subject (4 segments)", () => { const subject = "Fw: 转发:LINK EVER INC + MATS4583030000+ 柜号:MATU2745683+ ETA : 7/13"; - const r = extractBlPlusFromMail(subject, "新增转仓,请留意"); + const r = extractBlPlusFromMail(subject, "新增转仓,请留意", { + defaultYear: 2026, + }); expect(r).not.toBeNull(); expect(r!.modules.customer_name).toBe("LINK EVER INC"); expect(r!.modules.bl_no).toBe("MATS4583030000"); expect(r!.modules.container_no).toBe("MATU2745683"); + expect(r!.modules.eta).toBe("2026-07-13"); + expect(r!.modules.port).toBeUndefined(); expect(r!.modules.vessel_voyage).toBeUndefined(); }); + + it("parses space-separated ZJYC forecast without inventing port", () => { + const subject = "Fw: Fw:回复:ZJYC COSU6504130784 FCIU7089474 7.10 派拆柜派送"; + const body = [ + "---- 回复的原邮件 ----", + "主题ZJYC COSU6504130784 FCIU7089474 7.10 派拆柜派送", + "", + "FCIU7089474 提柜 COSCO 40HQ ETA:7.10 卡派拆柜派送", + ].join("\n"); + const r = extractBlPlusFromMail(subject, body, { defaultYear: 2026 }); + expect(r).not.toBeNull(); + expect(r!.modules.customer_name).toBe("ZJYC"); + expect(r!.modules.bl_no).toBe("COSU6504130784"); + expect(r!.modules.container_no).toBe("FCIU7089474"); + expect(r!.modules.eta).toBe("2026-07-10"); + expect(r!.modules.cabinet_type).toBe("40HQ"); + expect(r!.modules.shipping_line_hint).toMatch(/COSCO|COSU/i); + expect(r!.modules.port).toBeUndefined(); + const header = mapBlModulesToHeader( + { F_ContainerNo: "", container_no_valid: false }, + r!.modules, + ); + expect(header.F_ETA).toBe("2026-07-10"); + expect(header.F_Dock).toBe(""); + expect(header.F_MemoRemark).toBeUndefined(); + }); + + it("does not treat subjectForParse join noise as destination port", () => { + const subjectForParse = + "Fw: Fw:回复: 回复: LINK EVER INC + ZIMUSHH32195411+ 柜号:ZCSU6581068+ ETA : 7/7 | 回复: 回复: LINK EVER INC + ZIMUSHH32195411+ 柜号:ZCSU6581068+ ETA : 7/7"; + const r = extractBlPlusFromMail(subjectForParse, "新增转仓,请留意", { + defaultYear: 2026, + }); + expect(r).not.toBeNull(); + expect(r!.modules.customer_name).toBe("LINK EVER INC"); + expect(r!.modules.container_no).toBe("ZCSU6581068"); + expect(r!.modules.bl_no).toBe("ZIMUSHH32195411"); + expect(r!.modules.eta).toBe("2026-07-07"); + expect(r!.modules.port).toBeUndefined(); + const header = mapBlModulesToHeader( + { + F_ContainerNo: "", + container_no_valid: false, + // 模拟库里残留的错误目的港 + F_Dock: "ETA : 7/7 | Fw: Fw:回复: 回复: LINK EVER INC", + }, + r!.modules, + ); + expect(header.F_Dock).toBe(""); + expect(header.F_ETA).toBe("2026-07-07"); + }); + + it("parses labeled 柜号/提单号 template without fake customer", () => { + const subject = + "Fw: Fw:回复:柜号:TRHU8524770+提单号:EGLV143661466958+封号:EMCEJJ0635+YWZD+不带车架+整柜直送"; + const r = extractBlPlusFromMail(subject, "", { defaultYear: 2026 }); + expect(r).not.toBeNull(); + expect(r!.modules.container_no).toBe("TRHU8524770"); + expect(r!.modules.bl_no).toBe("EGLV143661466958"); + // 排除已知字段后仅剩 YWZD → 客户 + expect(r!.modules.customer_name).toBe("YWZD"); + expect(r!.modules.classis).toBe(0); + expect(r!.modules.ops_hints).toEqual(expect.arrayContaining(["直送"])); + expect(r!.modules.port).toBeUndefined(); + const header = mapBlModulesToHeader( + { F_ContainerNo: "", container_no_valid: false }, + r!.modules, + ); + expect(header.F_ContainerNo).toBe("TRHU8524770"); + expect(header.F_BLCopyCode).toBe("EGLV143661466958"); + expect(header.F_Dock).toBe(""); + expect(header.F_OperationType).toBe(2); + expect(r!.modules.memo_bits).toEqual(["封号:EMCEJJ0635"]); + expect(header.F_MemoRemark).toBe("封号:EMCEJJ0635"); + expect(header.F_MemoRemark).not.toMatch(/柜号|TRHU/); + expect(header.F_MemoRemark).not.toMatch(/YWZD/); + }); + + it("does not guess customer when multiple leftovers remain", () => { + const subject = + "柜号:TRHU8524770+提单号:EGLV143661466958+封号:EMCEJJ0635+YWZD+ABCDE+不带车架+整柜直送"; + const r = extractBlPlusFromMail(subject, "", { defaultYear: 2026 }); + expect(r).not.toBeNull(); + expect(r!.modules.customer_name).toBeUndefined(); + expect(r!.modules.container_no).toBe("TRHU8524770"); + expect(r!.modules.bl_no).toBe("EGLV143661466958"); + expect(r!.modules.memo_bits).toEqual( + expect.arrayContaining(["封号:EMCEJJ0635", "YWZD", "ABCDE"]), + ); + }); + + it("puts other unmatched labeled fields into memo, not customer", () => { + const subject = + "柜号:TRHU8524770+提单号:EGLV143661466958+预约码:ABC123+YWZD+不带车架+整柜直送"; + const r = extractBlPlusFromMail(subject, "", { defaultYear: 2026 }); + expect(r!.modules.customer_name).toBe("YWZD"); + expect(r!.modules.memo_bits).toEqual(["预约码:ABC123"]); + }); + + it("plus delimiter and space delimiter are mutually exclusive", () => { + expect( + usesPlusFieldDelimiter( + "新辰泽+WHLC027G597465+WHSU8127240+洛杉矶+40HQ", + ), + ).toBe(true); + expect( + parseSpaceForecastTemplate( + "新辰泽+WHLC027G597465+WHSU8127240+洛杉矶+40HQ", + { defaultYear: 2026 }, + ), + ).toBeNull(); + expect( + usesPlusFieldDelimiter("ZJYC COSU6504130784 FCIU7089474 7.10 派拆柜派送"), + ).toBe(false); + const space = extractBlPlusFromMail( + "ZJYC COSU6504130784 FCIU7089474 7.10 派拆柜派送", + "FCIU7089474 提柜 COSCO 40HQ ETA:7.10 卡派拆柜派送", + { defaultYear: 2026 }, + ); + expect(space?.modules.customer_name).toBe("ZJYC"); + expect(space?.modules.container_no).toBe("FCIU7089474"); + }); + + it("does not parse ops work-order subject as customer+port forecast", () => { + const subject = + "Fw: Fw:贴标操作:派送指令:拍照指令:do请查收:TCNU4412465+ ONEYNB3BW9236300+船名航次:WAN HAI 511-V.E110+ETA:6/22+UPS-6件+亚马逊卡派-1275件+私人地址-30件+拆柜清单"; + const r = extractBlPlusFromMail(subject, "注意:派送要求", { + defaultYear: 2026, + }); + expect(r).toBeNull(); + }); }); describe("shipping line hint priority", () => { diff --git a/tests/unit/cc-forecast-form-order.test.ts b/tests/unit/cc-forecast-form-order.test.ts index 8f7d72f..86ec711 100644 --- a/tests/unit/cc-forecast-form-order.test.ts +++ b/tests/unit/cc-forecast-form-order.test.ts @@ -40,6 +40,30 @@ describe("buildCcForecastFormValue", () => { expect(v.F_Instruction).toContain("HMM EMERALD"); }); + it("does not invent 柜型 or other optional fields", () => { + const v = buildCcForecastFormValue({ + customerName: "YWZD", + header: { + F_ContainerNo: "TRHU8524770", + F_BLCopyCode: "EGLV143661466958", + F_OperationType: 2, + F_Classis: 0, + F_MemoRemark: "封号:EMCEJJ0635", + }, + }); + expect(v.F_CabinetType).toBe(""); + expect(v.F_ETD).toBe(""); + expect(v.F_ETA).toBe(""); + expect(v.F_LoadPort).toBe(""); + expect(v.F_Dock).toBe(""); + expect(v.F_ContainerId).toBe(""); + expect(v.F_CustomerService).toBe(""); + expect(v.F_TransMode).toBeUndefined(); + expect(v.F_OperationType).toBe(2); + expect(v.F_Classis).toBe(0); + expect(v.F_MemoRemark).toBe("封号:EMCEJJ0635"); + }); + it("falls back modules when header empty", () => { const v = buildCcForecastFormValue({ modules: { @@ -54,7 +78,48 @@ describe("buildCcForecastFormValue", () => { expect(v.F_ContainerNo).toBe("ABCD1234567"); expect(v.F_CabinetType).toBe("20GP"); expect(v.F_BLCopyCode).toBe("BL1"); - expect(v.F_Dock).toBe("LA"); + expect(v.F_Dock).toBe("洛杉矶"); expect(v.F_Instruction).toBe("船名航次 V1"); }); + + it("clears customer/memo that reuse container number", () => { + const v = buildCcForecastFormValue({ + customerName: "回复:柜号:TRHU8524770", + header: { + F_ContainerNo: "TRHU8524770", + F_BLCopyCode: "EGLV143661466958", + F_Dock: "提单号:EGLV143661466958", + F_MemoRemark: "回复:柜号:TRHU8524770 / 不带车架", + F_Classis: 0, + F_OperationType: 2, + }, + modules: { + customer_name: "回复:柜号:TRHU8524770", + container_no: "TRHU8524770", + bl_no: "EGLV143661466958", + port: "提单号:EGLV143661466958", + service: "不带车架", + }, + }); + expect(v.customerName).toBe(""); + expect(v.F_ContainerNo).toBe("TRHU8524770"); + expect(v.F_BLCopyCode).toBe("EGLV143661466958"); + expect(v.F_Dock).toBe(""); + expect(v.F_MemoRemark).toBe("不带车架"); + }); + + it("keeps 封号 in memo and does not treat it as customer", () => { + const v = buildCcForecastFormValue({ + customerName: "YWZD", + header: { + F_ContainerNo: "TRHU8524770", + F_BLCopyCode: "EGLV143661466958", + F_MemoRemark: "封号:EMCEJJ0635", + F_Classis: 0, + F_OperationType: 2, + }, + }); + expect(v.customerName).toBe("YWZD"); + expect(v.F_MemoRemark).toBe("封号:EMCEJJ0635"); + }); }); diff --git a/tests/unit/cc-save.test.ts b/tests/unit/cc-save.test.ts index 2ce5adb..97b99f1 100644 --- a/tests/unit/cc-save.test.ts +++ b/tests/unit/cc-save.test.ts @@ -1,5 +1,10 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { saveContainer } from "@/services/cc/save-container"; +import { + mapShipmentRows, + normalizeFbaCodeForCc, + saveContainer, +} from "@/services/cc/save-container"; +import { fillShipmentFbaCodes } from "@/services/parse/normalize-fba-code"; import { resetMockCcState, setMockConflictContainers, @@ -53,6 +58,34 @@ describe("cc mock", () => { if (res.ok) expect(res.externalId).toBeTruthy(); }); + it("uses courier name as FBACode when UPS/FEDEX/DHL/USPS have no warehouse", () => { + expect(normalizeFbaCodeForCc("", "UPS")).toBe("UPS"); + expect(normalizeFbaCodeForCc("", "FEDEX")).toBe("FEDEX"); + expect(normalizeFbaCodeForCc("", "DHL")).toBe("DHL"); + expect(normalizeFbaCodeForCc("", "USPS")).toBe("USPS"); + expect(normalizeFbaCodeForCc("ups", "UPS")).toBe("UPS"); + expect(normalizeFbaCodeForCc("", "TRUCK")).toBe(""); + expect(normalizeFbaCodeForCc("", "留仓")).toBe("HOLD"); + expect(normalizeFbaCodeForCc("LAS1", "UPS")).toBe("LAS1"); + const rows = mapShipmentRows([ + { + row_index: 60, + row_status: "VALID", + F_FBACode: "", + F_Transporter: "UPS", + F_CTNS: 2, + }, + ]); + expect(rows[0].F_FBACode).toBe("UPS"); + expect(rows[0].F_Transporter).toBe("UPS"); + const displayed = fillShipmentFbaCodes([ + { row_index: 60, F_FBACode: "", F_Transporter: "UPS" }, + { row_index: 61, F_FBACode: "", F_Transporter: "FEDEX" }, + { row_index: 62, F_FBACode: "LAS1", F_Transporter: "TRUCK" }, + ]) as Array<{ F_FBACode: string }>; + expect(displayed.map((r) => r.F_FBACode)).toEqual(["UPS", "FEDEX", "LAS1"]); + }); + it("conflict when container in mock list", async () => { setMockConflictContainers(["MATU2745683"]); const r = await checkContainerConflict({ diff --git a/tests/unit/channel-map.test.ts b/tests/unit/channel-map.test.ts index ae1dfc2..1c0fc64 100644 --- a/tests/unit/channel-map.test.ts +++ b/tests/unit/channel-map.test.ts @@ -8,8 +8,12 @@ describe("channel-map", () => { it("maps UPS/FEDEX/DHL/USPS", () => { expect(mapChannel("UPS Ground").transporter).toBe("UPS"); expect(mapChannel("FedEx").transporter).toBe("FEDEX"); + expect(mapChannel("TNT Express").transporter).toBe("FEDEX"); expect(mapChannel("DHL").transporter).toBe("DHL"); expect(mapChannel("USPS").transporter).toBe("USPS"); + expect(mapChannel("SF Express").transporter).toBe("SF"); + expect(mapChannel("4PX").transporter).toBe("4PX"); + expect(mapChannel("YunExpress").transporter).toBe("YUNEXPRESS"); }); it("maps 自提/留仓/扣货", () => { expect(mapChannel("自提").transporter).toBe("自提"); diff --git a/tests/unit/customer-accounts.test.ts b/tests/unit/customer-accounts.test.ts new file mode 100644 index 0000000..3333352 --- /dev/null +++ b/tests/unit/customer-accounts.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { + assertCustomerUsername, + CustomerAccountError, +} from "@/services/auth/customers"; +import { hashPassword, passwordMatches } from "@/services/auth/passwords"; +import { isStaffRole } from "@/services/tenant/access"; + +describe("customer accounts", () => { + it("rejects reserved-like short names", () => { + expect(() => assertCustomerUsername("ab")).toThrow(CustomerAccountError); + }); + + it("accepts dotted customer usernames", () => { + expect(assertCustomerUsername("acme.ops")).toBe("acme.ops"); + }); + + it("matches sha256 password hash", () => { + const hash = hashPassword("secret-1"); + expect(passwordMatches("secret-1", hash, "")).toBe(true); + expect(passwordMatches("wrong", hash, "")).toBe(false); + }); + + it("treats admin/ops as staff", () => { + expect(isStaffRole("admin")).toBe(true); + expect(isStaffRole("ops")).toBe(true); + expect(isStaffRole("customer")).toBe(false); + }); +}); diff --git a/tests/unit/do-upload-task-id.test.ts b/tests/unit/do-upload-task-id.test.ts new file mode 100644 index 0000000..3beb0c5 --- /dev/null +++ b/tests/unit/do-upload-task-id.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; +import { resolveDoUploadTaskId } from "@/services/import/confirm-do-upload"; + +describe("resolveDoUploadTaskId", () => { + it("uses CC list hit for the same container no", () => { + const r = resolveDoUploadTaskId({ + containerNo: "TRHU8524770", + listItems: [ + { + F_ContainerNo: "TRHU8524770", + F_ContainerTaskId: "task-real", + }, + ], + }); + expect(r).toEqual({ ok: true, taskId: "task-real" }); + }); + + it("rejects empty list even if import id is mock-task", () => { + const r = resolveDoUploadTaskId({ + containerNo: "TRHU8524770", + importExternalId: "mock-task-TRHU8524770", + listItems: [], + }); + expect(r).toEqual({ ok: false }); + }); + + it("does not attach DO to a different container in the list", () => { + const r = resolveDoUploadTaskId({ + containerNo: "TRHU8524770", + listItems: [ + { + F_ContainerNo: "WHSU8127240", + F_ContainerTaskId: "other-task", + }, + ], + }); + expect(r).toEqual({ ok: false }); + }); + + it("falls back to prior SaveContainer id when list is empty", () => { + const r = resolveDoUploadTaskId({ + containerNo: "TRHU8524770", + importExternalId: "428aca68-9baa-491c-b1ed-e45a5440fb91", + listItems: [], + }); + expect(r).toEqual({ + ok: true, + taskId: "428aca68-9baa-491c-b1ed-e45a5440fb91", + }); + }); +}); diff --git a/tests/unit/email-fixture-extract.test.ts b/tests/unit/email-fixture-extract.test.ts new file mode 100644 index 0000000..e03b409 --- /dev/null +++ b/tests/unit/email-fixture-extract.test.ts @@ -0,0 +1,82 @@ +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { enableTestModeEnv, disableTestModeEnv } from "@/services/test/test-mode-env"; +import { loadAllEmailFixtures } from "@/services/test/email-fixture-library"; +import { evaluateEmailFixture } from "@/services/test/evaluate-email-fixture"; +import type { EmailFixtureExpected } from "@/services/test/email-fixture-library"; +import type { FixtureExtractActual } from "@/services/test/evaluate-email-fixture"; + +function assertExpected(id: string, actual: FixtureExtractActual, expected: EmailFixtureExpected) { + expect(actual.pull, `${id} pull`).toBe(expected.pull !== false); + if (expected.pipeline_mail_type) { + expect(actual.pipeline_mail_type, `${id} pipeline_mail_type`).toBe( + expected.pipeline_mail_type, + ); + } else { + expect(actual.mail_type, `${id} mail_type`).toBe(expected.mail_type); + } + expect(actual.status, `${id} status`).toBe(expected.status); + if (expected.last_error === "missing_columns") { + expect(actual.last_error, `${id} last_error`).toMatch(/^missing_columns/); + } else { + expect(actual.last_error, `${id} last_error`).toBe(expected.last_error); + } + if (expected.container_no) { + expect(actual.container_no, `${id} container_no`).toBe(expected.container_no); + } + if ( + expected.selected_attachment && + /\.(xlsx|xls|csv)$/i.test(expected.selected_attachment) + ) { + expect(actual.selected_attachment, `${id} selected_attachment`).toBe( + expected.selected_attachment, + ); + } + if (expected.selected_attachment === null) { + expect(actual.selected_attachment, `${id} selected_attachment`).toBeNull(); + } + if (expected.shipment_count != null) { + expect(actual.shipment_count, `${id} shipment_count`).toBe(expected.shipment_count); + } + if (expected.valid_shipment_count != null) { + expect(actual.valid_shipment_count, `${id} valid_shipment_count`).toBe( + expected.valid_shipment_count, + ); + } + if (expected.missing_columns) { + expect(actual.missing_columns, `${id} missing_columns`).toBe(true); + } + if (expected.shipments) { + for (let i = 0; i < expected.shipments.length; i++) { + const exp = expected.shipments[i]; + const row = actual.shipments[i]; + expect(row, `${id} shipment[${i}]`).toBeTruthy(); + if (exp.F_FBACode != null) expect(row.F_FBACode, id).toBe(exp.F_FBACode); + if (exp.F_Transporter != null) { + expect(row.F_Transporter, id).toBe(exp.F_Transporter); + } + if (exp.F_CTNS != null) expect(row.F_CTNS, id).toBe(exp.F_CTNS); + if (exp.row_status) expect(row.row_status, id).toBe(exp.row_status); + if (exp.F_Expected_DeliveryDateB === null) { + expect(row.F_Expected_DeliveryDateB ?? null, id).toBeNull(); + } + } + } +} + +describe("parse extract vs fixture expected.json", () => { + beforeAll(() => { + enableTestModeEnv(); + }); + afterAll(() => { + disableTestModeEnv(); + }); + + it("runs every fixture and matches expected.json", async () => { + const all = await loadAllEmailFixtures(); + expect(all.length).toBeGreaterThanOrEqual(15); + for (const f of all) { + const actual = await evaluateEmailFixture(f); + assertExpected(f.id, actual, f.expected); + } + }); +}); diff --git a/tests/unit/email-fixtures-catalog.test.ts b/tests/unit/email-fixtures-catalog.test.ts new file mode 100644 index 0000000..8a25bab --- /dev/null +++ b/tests/unit/email-fixtures-catalog.test.ts @@ -0,0 +1,160 @@ +import { describe, expect, it } from "vitest"; +import { classifyMail } from "@/services/parse/classify"; +import { parsePackingListBuffer } from "@/services/parse/packing-list"; +import { parseMailSource } from "@/services/imap/snapshot"; +import { decidePull } from "@/services/imap/pull-filter"; +import { resolveNoSpreadsheetOutcome } from "@/services/parse/pipeline"; +import { resolveParsedMailBody } from "@/utils/mail-body-text"; +import { + fixtureFilenames, + loadAllEmailFixtures, + loadEmailFixture, +} from "@/services/test/email-fixture-library"; + +function spreadsheetRank(filename: string): number { + const f = filename || ""; + if (/数据模版|提拆派|卡派资料|卡派清单|装箱清单|packing/i.test(f)) return 0; + if (/换标|覆盖贴|贴标|操作指令/i.test(f)) return 2; + return 1; +} + +function pickSpreadsheet(filenames: string[]): string | null { + const sheets = filenames + .filter((f) => /\.(xlsx|xls|csv|zip)$/i.test(f)) + .slice() + .sort((a, b) => spreadsheetRank(a) - spreadsheetRank(b) || a.localeCompare(b)); + return sheets[0] || null; +} + +describe("email fixture catalog", () => { + it("covers >=15 fixtures and all required categories", async () => { + const all = await loadAllEmailFixtures(); + expect(all.length).toBeGreaterThanOrEqual(15); + const cats = new Set(all.map((f) => f.spec.category)); + expect(cats.has("normal")).toBe(true); + expect(cats.has("boundary")).toBe(true); + expect(cats.has("abnormal")).toBe(true); + expect(cats.has("duplicate")).toBe(true); + expect(cats.has("multi_attachment")).toBe(true); + for (const f of all) { + expect(f.expected.mail_type).toBeTruthy(); + expect(f.expected.status).toBeTruthy(); + expect(f.spec.messageId).toBeTruthy(); + } + }); + + it("classifyMail matches expected.mail_type (except pull-skipped unknown)", async () => { + const all = await loadAllEmailFixtures(); + for (const f of all) { + if (f.expected.pull === false) { + const d = decidePull({ + fromAddr: f.spec.from, + subject: f.spec.subject, + body: f.spec.text, + filenames: fixtureFilenames(f.spec), + rules: [], + }); + expect(d.pull, f.id).toBe(false); + expect(f.expected.mail_type).toBe("UNKNOWN"); + continue; + } + const evidence = classifyMail({ + subject: f.spec.subject, + body: f.spec.text, + filenames: fixtureFilenames(f.spec), + }); + expect(evidence.mail_type, f.id).toBe(f.expected.mail_type); + } + }); + + it("packing fixtures: parsePackingListBuffer === expected shipments", async () => { + const ids = [ + "01-normal-forecast", + "13-illegal-date", + "12-missing-required", + ]; + for (const id of ids) { + const f = await loadEmailFixture(id); + const xlsx = f.attachments.find((a) => a.filename.endsWith(".xlsx")); + expect(xlsx, id).toBeTruthy(); + const parsed = await parsePackingListBuffer(xlsx!.content, { + enableIsoCheck: true, + }); + if (f.expected.missing_columns) { + expect(parsed.errorCode, id).toBe("PARSE_FAILED"); + expect(parsed.lineage.missing_columns, id).toBeTruthy(); + continue; + } + expect(parsed.errorCode, id).toBeUndefined(); + if (f.expected.shipment_count != null) { + expect(parsed.shipments.length, id).toBe(f.expected.shipment_count); + } + if (f.expected.shipments) { + for (let i = 0; i < f.expected.shipments.length; i++) { + const exp = f.expected.shipments[i]; + const row = parsed.shipments[i]; + if (exp.F_FBACode != null) expect(row.F_FBACode, id).toBe(exp.F_FBACode); + if (exp.F_Transporter != null) { + expect(row.F_Transporter, id).toBe(exp.F_Transporter); + } + if (exp.F_CTNS != null) expect(row.F_CTNS, id).toBe(exp.F_CTNS); + if (exp.row_status) expect(row.row_status, id).toBe(exp.row_status); + if (exp.F_Expected_DeliveryDateB === null) { + expect(row.F_Expected_DeliveryDateB ?? null, id).toBeNull(); + } + } + } + } + }); + + it("07-gbk-body: mailparser decodes 新增预报", async () => { + const f = await loadEmailFixture("07-gbk-body"); + const parsed = await parseMailSource(f.eml); + const body = resolveParsedMailBody(parsed); + expect(body).toMatch(/新增预报/); + expect(parsed.subject).toMatch(/WHSU8127240/); + }); + + it("08-oversized-attachment exceeds 20MB", async () => { + const f = await loadEmailFixture("08-oversized-attachment"); + expect(f.attachments[0].content.length).toBeGreaterThan(20 * 1024 * 1024); + expect(f.expected.last_error).toBe("ATTACHMENT_TOO_LARGE"); + }); + + it("09/11 no-spreadsheet outcomes match expected status", async () => { + const noAtt = await loadEmailFixture("09-no-attachment"); + expect( + resolveNoSpreadsheetOutcome({ + mailType: noAtt.expected.mail_type, + hasAttachments: false, + }), + ).toEqual({ + status: noAtt.expected.status, + lastError: noAtt.expected.last_error, + }); + const wrong = await loadEmailFixture("11-wrong-format"); + expect( + resolveNoSpreadsheetOutcome({ + mailType: wrong.expected.mail_type, + hasAttachments: true, + }), + ).toEqual({ + status: wrong.expected.status, + lastError: wrong.expected.last_error, + }); + }); + + it("14-duplicate-pull shares Message-ID with 01", async () => { + const a = await loadEmailFixture("01-normal-forecast"); + const b = await loadEmailFixture("14-duplicate-pull"); + expect(b.spec.messageId).toBe(a.spec.messageId); + expect(b.expected.idempotent_with).toBe("01-normal-forecast"); + }); + + it("15-multi-attachment classifies WORK_ORDER but still selects 数据模版 over 换标 xlsx", async () => { + const f = await loadEmailFixture("15-multi-attachment"); + const names = fixtureFilenames(f.spec); + expect(names.length).toBeGreaterThan(2); + expect(pickSpreadsheet(names)).toBe(f.expected.selected_attachment); + }); +}); diff --git a/tests/unit/extract-container-header.test.ts b/tests/unit/extract-container-header.test.ts new file mode 100644 index 0000000..9e6486e --- /dev/null +++ b/tests/unit/extract-container-header.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from "vitest"; +import { + extractContainerHeaderFromText, + matchShippingLineId, +} from "@/services/parse/extract-container-header"; +import { + AIRLINE_ALIASES, + EXPRESS_ALIASES, + SHIPPING_LINE_ALIASES, +} from "@/services/parse/carrier-aliases"; + +describe("matchShippingLineId", () => { + it.each([ + ["COSCO", "中国远洋海运集团有限公司", "cosco-cn"], + ["OOCL", "东方海外货柜航运公司", "oocl-cn"], + ["WAN HAI", "万海航运股份有限公司", "whl-cn"], + ["HMM", "韩新海运", "hmm-cn"], + ["MSC", "地中海航运", "msc-cn"], + ["EMC", "长荣海运", "emc-cn"], + ["EGLV", "长荣海运", "emc-cn"], + ["CMA CGM", "达飞轮船", "cma-cn"], + ["ZIM", "以星综合航运", "zim-cn"], + ["ZIMU", "以星综合航运", "zim-cn"], + ["ONE", "海洋网联船务", "one-cn"], + ["ONEY", "海洋网联船务", "one-cn"], + ["MATSON", "美森轮船", "matson-cn"], + ])("matches alias %s to shipping line %s", (hint, fullName, expectedId) => { + const id = matchShippingLineId(hint, [ + { F_Id: expectedId, F_FullName: fullName }, + { F_Id: "other", F_FullName: "其他船司", F_EnCode: "OTHER" }, + ]); + expect(id).toBe(expectedId); + }); + + it("infers sea mode and evergreen from EGLV master BL", () => { + const h = extractContainerHeaderFromText( + "柜号:TRHU8524770 提单号:EGLV143661466958 整柜直送", + ); + expect(h.F_BLCopyCode).toBe("EGLV143661466958"); + expect(h.shipping_line_hint).toBe("EGLV"); + expect(h.F_TransMode).toBe(0); + const id = matchShippingLineId(h.shipping_line_hint, [ + { F_Id: "eglv", F_FullName: "长荣海运", F_EnCode: "EMC", F_Prefix: "EGLV" }, + ]); + expect(id).toBe("eglv"); + }); + + it("puts 封号 into memo, not other header fields", () => { + const h = extractContainerHeaderFromText( + "柜号:TRHU8524770 提单号:EGLV143661466958 封号:EMCEJJ0635 整柜直送", + ); + expect(h.F_BLCopyCode).toBe("EGLV143661466958"); + expect(h.F_MemoRemark).toBe("封号:EMCEJJ0635"); + expect(h.F_OperationType).toBe(2); + }); +}); + +describe("carrier-aliases catalog", () => { + it("includes common express and airline aliases", () => { + expect(EXPRESS_ALIASES.TNT).toContain("TNT EXPRESS"); + expect(EXPRESS_ALIASES["4PX"]).toContain("递四方"); + expect(AIRLINE_ALIASES.CA).toContain("中国国际航空"); + expect(AIRLINE_ALIASES.FDX).toContain("联邦快递"); + expect(SHIPPING_LINE_ALIASES.MAERSK).toContain("马士基"); + expect(SHIPPING_LINE_ALIASES.PIL).toContain("太平船务"); + }); + + it("infers air trans mode from airline aliases", () => { + const h = extractContainerHeaderFromText( + "航司 Air China 提单号 999-12345678 空运操作", + ); + expect(h.F_TransMode).toBe(1); + }); + + it("parses loose ETA and bare COSCO from forecast body", () => { + const h = extractContainerHeaderFromText( + "FCIU7089474 提柜 COSCO 40HQ ETA:7.10 卡派拆柜派送", + ); + expect(h.F_CabinetType).toBe("40HQ"); + expect(h.F_ETA).toMatch(/^\d{4}-07-10$/); + expect(h.shipping_line_hint).toBe("COSCO"); + expect(h.F_Dock).toBeUndefined(); + }); +}); diff --git a/tests/unit/fault-matrix.test.ts b/tests/unit/fault-matrix.test.ts new file mode 100644 index 0000000..c4020df --- /dev/null +++ b/tests/unit/fault-matrix.test.ts @@ -0,0 +1,164 @@ +import fs from "fs/promises"; +import path from "path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { formatMailLastError } from "@/constants/ui-copy"; +import { + nextPollDelayMs, + readImapRuntimeStatus, + recordImapPollFailure, + recordImapPollSuccess, +} from "@/services/imap/runtime-status"; +import { getEmittedAlerts, resetAlertState } from "@/services/alert/webhook"; +import { getMockMailServer } from "@/services/imap/mock-mail-server"; +import { getMockTargetAPI } from "@/services/cc/mock-target-api"; +import { saveContainer, buildSaveContainerEntity } from "@/services/cc/save-container"; +import { enableTestModeEnv, disableTestModeEnv } from "@/services/test/test-mode-env"; +import type { ShipmentRow } from "@/types/mail"; + +const STATUS_PATH = path.join(process.cwd(), "data", "imap-runtime.json"); + +const ENTITY = buildSaveContainerEntity( + { F_ContainerNo: "WHSU8127240", container_no_valid: true }, + { F_TransMode: 0, F_OperationType: 0, F_ContainerNo: "WHSU8127240" }, +); + +const ROW: ShipmentRow = { + row_index: 0, + row_status: "VALID", + F_FBACode: "LAS1", + F_Transporter: "TRUCK", + F_CTNS: 10, +}; + +describe("fault matrix (current production behavior)", () => { + beforeEach(() => { + enableTestModeEnv(); + }); + afterEach(() => { + disableTestModeEnv(); + }); + + it("pull auth_fail: Mock IMAP throws AUTHENTICATIONFAILED", async () => { + const server = getMockMailServer(); + server.setFault("auth_fail"); + await expect(server.createClient({ + host: "mock.local", + port: 993, + user: "t", + pass: "t", + connectTimeoutMs: 1000, + readTimeoutMs: 1000, + }).connect()).rejects.toThrow(/AUTHENTICATIONFAILED/); + }); + + it("pull timeout: Mock IMAP throws ETIMEOUT", async () => { + const server = getMockMailServer(); + server.setFault("timeout"); + await expect( + server.createClient({ + host: "mock.local", + port: 993, + user: "t", + pass: "t", + connectTimeoutMs: 1000, + readTimeoutMs: 1000, + }).connect(), + ).rejects.toMatchObject({ code: "ETIMEOUT" }); + }); + + it("pull timeout backoff: consecutiveFail>=5 exponential, cap 5min", () => { + expect(nextPollDelayMs(4, 60_000)).toBe(60_000); + expect(nextPollDelayMs(5, 60_000)).toBe(120_000); + expect(nextPollDelayMs(6, 60_000)).toBe(240_000); + expect(nextPollDelayMs(10, 60_000)).toBe(5 * 60_000); + }); + + it("pull failure is recorded with lastError (runtime-status file)", async () => { + let backup: string | null = null; + try { + backup = await fs.readFile(STATUS_PATH, "utf8").catch(() => null); + await recordImapPollSuccess(0); + await recordImapPollFailure(new Error("AUTHENTICATIONFAILED: Invalid credentials")); + const status = await readImapRuntimeStatus(); + expect(status.consecutiveFail).toBe(1); + expect(status.lastError).toMatch(/AUTHENTICATIONFAILED/); + } finally { + if (backup != null) await fs.writeFile(STATUS_PATH, backup); + else await fs.rm(STATUS_PATH, { force: true }); + } + }); + + it("3 consecutive IMAP fails emit alert", async () => { + let backup: string | null = null; + try { + backup = await fs.readFile(STATUS_PATH, "utf8").catch(() => null); + resetAlertState(); + await recordImapPollSuccess(0); + await recordImapPollFailure(new Error("fail-1")); + await recordImapPollFailure(new Error("fail-2")); + expect(getEmittedAlerts()).toHaveLength(0); + await recordImapPollFailure(new Error("fail-3")); + expect(getEmittedAlerts().some((a) => a.key === "imap.consecutive_fail")).toBe( + true, + ); + } finally { + resetAlertState(); + if (backup != null) await fs.writeFile(STATUS_PATH, backup); + else await fs.rm(STATUS_PATH, { force: true }); + } + }); + + it("UI maps last_error codes to visible copy", () => { + expect(formatMailLastError("NO_SHIPMENT_ROWS")).toMatch(/未解析到货件/); + expect(formatMailLastError("NO_SUPPORTED_SPREADSHEET")).toMatch(/表格附件/); + expect(formatMailLastError("missing_columns:仓库ID")).toMatch(/缺列/); + expect(formatMailLastError("Unauthorized")).toMatch(/CarrierCentral|权限|登录/); + }); + + it("submit 401: SaveContainer not ok, status FAILED, no timeout compensation", async () => { + getMockTargetAPI().setFault("401", "/Container/SaveContainer"); + const res = await saveContainer({ + entity: ENTITY, + shipments: [ROW], + }); + expect(res.ok).toBe(false); + if (res.ok) return; + expect(res.status).toBe("FAILED"); + expect(res.error).toMatch(/登录|权限|CarrierCentral/); + }); + + it("submit 500: SaveContainer FAILED (compensation only on TIMEOUT_UNKNOWN)", async () => { + getMockTargetAPI().setFault("500", "/Container/SaveContainer"); + const res = await saveContainer({ + entity: ENTITY, + shipments: [ROW], + }); + expect(res.ok).toBe(false); + if (res.ok) return; + expect(res.status).toBe("FAILED"); + expect(res.error).toMatch(/服务器错误|CarrierCentral/); + }); + + it("submit timeout: SaveContainer TIMEOUT_UNKNOWN", async () => { + getMockTargetAPI().setFault("timeout", "/Container/SaveContainer"); + const res = await saveContainer({ + entity: ENTITY, + shipments: [ROW], + }); + expect(res.ok).toBe(false); + if (res.ok) return; + expect(res.status).toBe("TIMEOUT_UNKNOWN"); + expect(res.error).toMatch(/超时/); + }); + + it("submit partial: SaveContainer treats object data as missing external id → TIMEOUT_UNKNOWN", async () => { + getMockTargetAPI().setFault("partial", "/Container/SaveContainer"); + const res = await saveContainer({ + entity: ENTITY, + shipments: [ROW], + }); + expect(res.ok).toBe(false); + if (res.ok) return; + expect(res.status).toBe("TIMEOUT_UNKNOWN"); + }); +}); diff --git a/tests/unit/forecast-confirm-gate.test.ts b/tests/unit/forecast-confirm-gate.test.ts index 4c93d08..9a91b0e 100644 --- a/tests/unit/forecast-confirm-gate.test.ts +++ b/tests/unit/forecast-confirm-gate.test.ts @@ -1,34 +1,112 @@ import { describe, expect, it } from "vitest"; -import { canEnterForecastConfirm } from "@/services/cc/forecast-confirm-gate"; +import { + canEnterForecastConfirm, + collectForecastImportIssues, +} from "@/services/cc/forecast-confirm-gate"; describe("canEnterForecastConfirm", () => { - it("allows DO_UPLOAD combo with valid rows", () => { + it("allows forecast data regardless of mail main type", () => { expect( canEnterForecastConfirm({ - mailType: "DO_UPLOAD", status: "PENDING_CONFIRM", validShipmentCount: 1, }), ).toBe(true); }); - it("blocks pure work order", () => { + it("blocks empty forecast without container header", () => { expect( canEnterForecastConfirm({ - mailType: "WORK_ORDER", status: "PENDING_CONFIRM", - validShipmentCount: 1, + validShipmentCount: 0, }), ).toBe(false); }); - it("requires valid rows", () => { + it("allows header-only forecast (no shipment rows)", () => { expect( canEnterForecastConfirm({ - mailType: "NEW_CONTAINER", status: "PENDING_CONFIRM", validShipmentCount: 0, + hasContainerHeader: true, }), - ).toBe(false); + ).toBe(true); + }); + + it("allows PARTIAL_SUCCESS so remaining instructions stay confirmable", () => { + expect( + canEnterForecastConfirm({ + status: "PARTIAL_SUCCESS", + validShipmentCount: 1, + }), + ).toBe(true); + }); +}); + +describe("collectForecastImportIssues", () => { + const base = { + transMode: 0 as const, + operationType: 0 as const, + containerNo: "TRHU8524770", + hasContainerConflict: false, + containerSource: null as "body" | "attachment" | null, + validRowCount: 1, + selectedValidCount: 1, + hasUnmappedSelected: false, + ackUnmapped: false, + }; + + it("does not block header-only import when no valid rows", () => { + const issues = collectForecastImportIssues({ + ...base, + validRowCount: 0, + selectedValidCount: 0, + shipmentCount: 0, + }); + expect(issues.blockers).toEqual([]); + expect(issues.notes.some((n) => n.includes("只提交柜头"))).toBe(true); + }); + + it("blocks when valid rows exist but none selected", () => { + const issues = collectForecastImportIssues({ + ...base, + selectedValidCount: 0, + }); + expect(issues.blockers[0]).toContain("至少勾选一行有效货件"); + expect(issues.errors.rows).toBeTruthy(); + }); + + it("explains empty and invalid container numbers", () => { + expect( + collectForecastImportIssues({ ...base, containerNo: "" }).blockers[0], + ).toContain("柜号为空"); + expect( + collectForecastImportIssues({ ...base, containerNo: "ABCD1234567" }) + .blockers[0], + ).toContain("ISO 6346"); + }); + + it("summarizes invalid packing rows without blocking header-only", () => { + const issues = collectForecastImportIssues({ + ...base, + validRowCount: 0, + selectedValidCount: 0, + invalidRows: [ + { row_index: 1, invalid_reasons: ["渠道缺失", "件数无效"] }, + { row_index: 2, invalid_reasons: ["渠道缺失"] }, + ], + }); + expect(issues.blockers).toEqual([]); + expect(issues.notes[0]).toContain("渠道缺失(2行)"); + expect(issues.notes[0]).toContain("件数无效"); + }); + + it("does not block client portal on the first tab", () => { + const issues = collectForecastImportIssues({ + ...base, + portal: "client", + step: 0, + }); + expect(issues.blockers).toEqual([]); }); }); diff --git a/tests/unit/four-business.test.ts b/tests/unit/four-business.test.ts index 5016fda..eabeb16 100644 --- a/tests/unit/four-business.test.ts +++ b/tests/unit/four-business.test.ts @@ -107,6 +107,43 @@ describe("buildCcWorkOrderFormValue", () => { expect(v.mailActions).not.toContain("拦截"); }); + it("mail85 76件换标挂对应附件;833件历史轮不误挂", () => { + const files = [ + "YT2604021091=76件换标走HIA1卡派出库操作指令.xlsx", + "FBA19HW52S0L-76CTN-HIA1-覆盖贴1箱贴2张.pdf", + "X003UHDF7B-304PCS-覆盖贴1箱贴1张.pdf", + ]; + const current = buildCcWorkOrderFormValue({ + subject: "Re:新增预报136", + body: [ + "Dear,", + "原箱号 YT2604021091=FBA199R49LD6-MDW2=76 件操作指令", + "换标后单号: FBA19HW52S0L-2G1MCB6X-HIA1=76 件", + "1:覆盖贴FBA 标签,一箱贴两张", + "2:覆盖贴SKU 标签,一箱 1 张( FNSKU : X003UHDF7B ( 304PCS ))", + ].join("\n"), + filenames: files, + containerNo: "WHSU8127240", + }); + expect(current.F_AttachFile).toContain("YT2604021091"); + expect(current.F_AttachFile).toContain("FBA19HW52S0L"); + expect(current.F_AttachFile).toContain("X003UHDF7B"); + + const hist833 = buildCcWorkOrderFormValue({ + subject: "Fw: 新增预报136", + body: [ + "Dear,", + "WHSU8127240= 原箱号 YT2604021486- 剩余 540 件", + "两个柜子共计 833 件操作指令", + "换标后单号:FBA19DS6CXV4-2G1XD81L-LGB8=87 件", + ].join("\n"), + filenames: files, + containerNo: "WHSU8127240", + isCurrent: false, + }); + expect(hist833.F_AttachFile).toBe(""); + }); + it("strips forward headers; keeps Dear instruction as F_MessageContent", () => { const v = buildCcWorkOrderFormValue({ subject: "Fw: 新增预报136", diff --git a/tests/unit/idempotency-client.test.ts b/tests/unit/idempotency-client.test.ts new file mode 100644 index 0000000..3ce68ba --- /dev/null +++ b/tests/unit/idempotency-client.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { buildImportIdempotencyKey } from "@/utils/idempotency-client"; + +describe("buildImportIdempotencyKey", () => { + it("stays within 128 chars even with hundreds of row indexes", () => { + const key = buildImportIdempotencyKey({ + mailId: "99", + instructionId: "forecast-body_segment-5-派送单请查收", + containerNo: "ZCSU6581068", + rowIndexes: Array.from({ length: 290 }, (_, i) => i), + }); + expect(key.length).toBeLessThanOrEqual(128); + expect(key.startsWith("99:")).toBe(true); + }); + + it("changes when the selected rows change", () => { + const a = buildImportIdempotencyKey({ + mailId: "1", + containerNo: "ABCD1234567", + rowIndexes: [0, 1], + }); + const b = buildImportIdempotencyKey({ + mailId: "1", + containerNo: "ABCD1234567", + rowIndexes: [0, 2], + }); + expect(a).not.toBe(b); + }); +}); diff --git a/tests/unit/imap-connect-error.test.ts b/tests/unit/imap-connect-error.test.ts new file mode 100644 index 0000000..0e3628f --- /dev/null +++ b/tests/unit/imap-connect-error.test.ts @@ -0,0 +1,42 @@ +import { describe, expect, it } from "vitest"; +import { formatImapConnectError } from "@/lib/imap-presets"; +import { formatImapSyncError } from "@/constants/error-copy"; + +describe("formatImapConnectError", () => { + it("digs ETIMEDOUT out of empty AggregateError", () => { + const child = Object.assign(new Error(""), { + code: "ETIMEDOUT", + address: "74.125.204.108", + port: 993, + }); + const err = new AggregateError([child], ""); + (err as NodeJS.ErrnoException).code = "ETIMEDOUT"; + const msg = formatImapConnectError(err, { + host: "imap.gmail.com", + user: "a@gmail.com", + }); + expect(msg).toMatch(/无法连接 Gmail/); + expect(msg).toMatch(/国内网络/); + expect(msg).toMatch(/ETIMEDOUT/); + }); + + it("keeps auth guidance for Gmail", () => { + const msg = formatImapConnectError(new Error("Invalid credentials"), { + host: "imap.gmail.com", + user: "a@gmail.com", + }); + expect(msg).toMatch(/应用专用密码/); + }); +}); + +describe("formatImapSyncError", () => { + it("does not fall back to import-record copy", () => { + expect(formatImapSyncError("")).toMatch(/邮件服务器/); + expect(formatImapSyncError("mailbox_fail:谷歌邮箱测试: ")).not.toMatch( + /导入记录/, + ); + expect(formatImapSyncError("无法连接 imap.gmail.com:ETIMEDOUT")).toMatch( + /无法连接|连接超时|网络/, + ); + }); +}); diff --git a/tests/unit/imap-proxy.test.ts b/tests/unit/imap-proxy.test.ts new file mode 100644 index 0000000..0ff2615 --- /dev/null +++ b/tests/unit/imap-proxy.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "vitest"; +import { + imapProxyForHost, + normalizeImapProxyUrl, + redactProxyUrl, +} from "@/lib/imap-proxy"; + +describe("imap-proxy", () => { + it("accepts socks5 and http URLs", () => { + expect(normalizeImapProxyUrl("socks5://127.0.0.1:7891")).toBe( + "socks5://127.0.0.1:7891", + ); + expect(normalizeImapProxyUrl("http://127.0.0.1:7890")).toBe( + "http://127.0.0.1:7890", + ); + expect(normalizeImapProxyUrl("ftp://127.0.0.1:21")).toBeUndefined(); + expect(normalizeImapProxyUrl("")).toBeUndefined(); + }); + + it("proxies Gmail but not QQ by default", () => { + const proxy = "socks5://127.0.0.1:7891"; + const filter = "gmail,google,outlook,office365,hotmail,live,msn"; + expect(imapProxyForHost("imap.gmail.com", proxy, filter)).toBe(proxy); + expect(imapProxyForHost("imap.qq.com", proxy, filter)).toBeUndefined(); + expect(imapProxyForHost("imap.163.com", proxy, filter)).toBeUndefined(); + }); + + it("proxies all hosts when filter is *", () => { + const proxy = "http://127.0.0.1:7890"; + expect(imapProxyForHost("imap.qq.com", proxy, "*")).toBe(proxy); + }); + + it("redacts proxy password", () => { + expect(redactProxyUrl("socks5://user:secret@127.0.0.1:1080")).toMatch( + /\*\*\*\*/, + ); + expect(redactProxyUrl("socks5://user:secret@127.0.0.1:1080")).not.toMatch( + /secret/, + ); + }); +}); diff --git a/tests/unit/instruction-execution-state.test.ts b/tests/unit/instruction-execution-state.test.ts new file mode 100644 index 0000000..af19451 --- /dev/null +++ b/tests/unit/instruction-execution-state.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it } from "vitest"; +import { + getInstructionExecutionStateMap, + listActionableInstructionUnits, + markInstructionImported, + resolveMailStatusAfterInstructionImport, +} from "@/services/parse/instruction-execution-state"; + +describe("instruction-execution-state", () => { + const subject = "ZCSU6581068 转仓通知"; + const body = [ + "2026-07-07 01:53:15", + "新增转仓,请留意", + "", + "Forwarded message:", + "Date: 2026-07-06 15:59", + "新增转仓,请留意", + ].join("\n"); + + it("lists multiple actionable transfer units", () => { + const units = listActionableInstructionUnits({ + subject, + body, + filenames: ["a.jpg", "b.jpg", "柜号ZCSU6581068 卡派资料.xlsx"], + lineage: {}, + }); + expect(units.filter((u) => u.uiKind === "transfer")).toHaveLength(2); + }); + + it("marks only the imported unit readonly and returns partial status", () => { + const units = listActionableInstructionUnits({ + subject, + body, + filenames: ["a.jpg", "b.jpg"], + lineage: {}, + }); + const lineage = markInstructionImported({}, units[0].id, { + status: "IMPORTED", + importedAt: "2026-08-12T00:00:00.000Z", + importedBy: "ops", + mode: "mock", + externalId: "x1", + }); + const state = getInstructionExecutionStateMap(lineage); + expect(state[units[0].id]?.status).toBe("IMPORTED"); + expect(state[units[1].id]).toBeUndefined(); + expect( + resolveMailStatusAfterInstructionImport({ + currentStatus: "IMPORTING", + subject, + body, + filenames: ["a.jpg", "b.jpg"], + lineage, + }), + ).toBe("PARTIAL_SUCCESS"); + }); + + it("returns success when all actionable units are imported", () => { + const units = listActionableInstructionUnits({ + subject, + body, + filenames: ["a.jpg", "b.jpg"], + lineage: {}, + }); + let lineage: Record = {}; + for (const unit of units) { + lineage = markInstructionImported(lineage, unit.id, { + status: "IMPORTED", + importedAt: "2026-08-12T00:00:00.000Z", + mode: "mock", + }); + } + expect( + resolveMailStatusAfterInstructionImport({ + currentStatus: "IMPORTING", + subject, + body, + filenames: ["a.jpg", "b.jpg"], + lineage, + }), + ).toBe("SUCCESS"); + }); +}); diff --git a/tests/unit/instruction-product-rules.test.ts b/tests/unit/instruction-product-rules.test.ts index 143418c..e67cb6d 100644 --- a/tests/unit/instruction-product-rules.test.ts +++ b/tests/unit/instruction-product-rules.test.ts @@ -46,7 +46,7 @@ describe("product rules: latest confirmable + WO fallback", () => { ); }); - it("LINK EVER: transfer only; no WO from work_order_actions=转仓", () => { + it("LINK EVER: current transfer kept; historical soft ops may become work_order", () => { const subject = "Fw: \u8f6c\u53d1\uff1aLINK EVER INC + MATS4583030000+ \u67dc\u53f7\uff1aMATU2745683+ ETA : 7/13"; const body = [ @@ -71,13 +71,12 @@ describe("product rules: latest confirmable + WO fallback", () => { filenames: ["\u67dc\u53f7\uff1aMATU2745683\u5361\u6d3e\u8d44\u6599.xlsx"], workOrderActions: ["\u8f6c\u4ed3"], }); - expect(units.map((u) => u.uiKind)).toEqual(["transfer"]); - expect(units[0].isCurrent).toBe(true); - expect(units.some((u) => u.uiKind === "work_order")).toBe(false); + expect(units.some((u) => u.uiKind === "transfer" && u.isCurrent)).toBe(true); + expect(units.some((u) => u.uiKind === "work_order")).toBe(true); expect(units.some((u) => u.uiKind === "customer_instruction")).toBe(false); }); - it("soft keywords map to customer_instruction (not work_order)", () => { + it("soft keywords map to work_order by default", () => { for (const kw of [ "\u66f4\u65b0\u6d3e\u9001\u5355", "\u6539\u81ea\u63d0", @@ -88,10 +87,10 @@ describe("product rules: latest confirmable + WO fallback", () => { subject: kw, body: kw, }); + expect(units.some((u) => u.uiKind === "work_order")).toBe(true); expect(units.some((u) => u.uiKind === "customer_instruction")).toBe( - true, + false, ); - expect(units.some((u) => u.uiKind === "work_order")).toBe(false); } }); }); diff --git a/tests/unit/label-instruction.test.ts b/tests/unit/label-instruction.test.ts index 662be8c..c1e326a 100644 --- a/tests/unit/label-instruction.test.ts +++ b/tests/unit/label-instruction.test.ts @@ -21,6 +21,7 @@ describe("parseLabelInstructionBuffer", () => { expect(r!.shipments.length).toBeGreaterThanOrEqual(1); const s0 = r!.shipments[0]; expect(s0.F_FBACode).toBe("HIA1"); + expect(s0.F_Address == null || s0.F_Address === "").toBe(true); expect(s0.F_CTNS).toBe(76); expect(s0.F_Pcs).toBe(304); expect(s0.F_PO).toBe("2G1MCB6X"); diff --git a/tests/unit/link-ever-zcsu.test.ts b/tests/unit/link-ever-zcsu.test.ts new file mode 100644 index 0000000..2459ee6 --- /dev/null +++ b/tests/unit/link-ever-zcsu.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from "vitest"; +import { extractMailInstructions } from "@/services/parse/split-instructions"; +import { extractBlPlusFromMail } from "@/services/parse/bl-plus-template"; + +const SUBJECT = + "Fw: Fw:回复: 回复: LINK EVER INC + ZIMUSHH32195411+ 柜号:ZCSU6581068+ ETA : 7/7"; + +const BODY = [ + "发自我的iPhone", + "", + "------------------ 原始邮件 ------------------", + "寄件人: luo ", + "发送时间: 2026年8月12日 09:50", + "主旨: Fw: Fw:回复: 回复: LINK EVER INC + ZIMUSHH32195411+ 柜号:ZCSU6581068+ ETA : 7/7", + "", + "Forwarded message:", + "", + "From:op7 ", + "", + "Date:2026-07-07 01:53:15(美国山区 (GMT-07:00))", + "", + "Subject:回复: 回复: LINK EVER INC + ZIMUSHH32195411+ 柜号:ZCSU6581068+ ETA : 7/7", + "", + "", + "新增转仓,请留意", + "", + " op7@xinfenginc.com", + "", + "  ", + "发件人: op7", + "发送时间: 2026-07-06 15:59", + "主题: 回复: LINK EVER INC + ZIMUSHH32195411+ 柜号:ZCSU6581068+ ETA : 7/7", + "", + " 新增转仓,请留意", + "", + " op7@xinfenginc.com", + "", + "  ", + "发件人: op7", + "发送时间: 2026-06-27 16:55", + "主题: LINK EVER INC + ZIMUSHH32195411+ 柜号:ZCSU6581068+ ETA : 7/7", + "", + " 柜号:ZCSU6581068  派送单,请查收", + "", + " op7@xinfenginc.com", +].join("\n"); + +describe("ZCSU6581068 LINK EVER thread", () => { + it("parses bl-plus header from subject", () => { + const bl = extractBlPlusFromMail(SUBJECT, BODY, { defaultYear: 2026 }); + expect(bl?.modules.customer_name).toBe("LINK EVER INC"); + expect(bl?.modules.container_no).toBe("ZCSU6581068"); + expect(bl?.modules.bl_no).toBe("ZIMUSHH32195411"); + expect(bl?.modules.eta).toBe("2026-07-07"); + expect(bl?.modules.port).toBeUndefined(); + }); + + it("keeps two transfer rounds and first forecast delivery note", () => { + const units = extractMailInstructions({ + subject: SUBJECT, + body: BODY, + filenames: ["柜号:ZCSU6581068卡派资料.xlsx"], + }); + const transfers = units.filter((u) => u.uiKind === "transfer"); + const forecasts = units.filter((u) => u.uiKind === "forecast"); + expect(transfers).toHaveLength(2); + expect(transfers[0].roundAt).not.toEqual(transfers[1].roundAt); + expect(transfers[0].label).not.toEqual(transfers[1].label); + expect(forecasts).toHaveLength(1); + expect(transfers[0].isCurrent).toBe(true); + expect(forecasts[0].isCurrent).toBe(false); + expect(forecasts[0].segmentText).toMatch(/派送单,请查收/); + expect(units.map((u) => u.uiKind)).toEqual([ + "transfer", + "transfer", + "forecast", + ]); + }); +}); diff --git a/tests/unit/log-context.test.ts b/tests/unit/log-context.test.ts new file mode 100644 index 0000000..984a2a0 --- /dev/null +++ b/tests/unit/log-context.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest"; +import { + currentLogBindings, + getTraceId, + newTraceId, + withLogContext, +} from "@/lib/log-context"; + +describe("log context / traceId", () => { + it("newTraceId is uuid-shaped", () => { + const id = newTraceId(); + expect(id).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, + ); + }); + + it("withLogContext nests and restores", async () => { + expect(getTraceId()).toBeUndefined(); + await withLogContext({ traceId: "t-pull", stage: "pull" }, async () => { + expect(getTraceId()).toBe("t-pull"); + expect(currentLogBindings().stage).toBe("pull"); + await withLogContext({ stage: "parse", mailId: "9" }, async () => { + expect(getTraceId()).toBe("t-pull"); + expect(currentLogBindings()).toMatchObject({ + traceId: "t-pull", + stage: "parse", + mailId: "9", + }); + }); + expect(currentLogBindings().stage).toBe("pull"); + expect(currentLogBindings().mailId).toBeUndefined(); + }); + expect(getTraceId()).toBeUndefined(); + }); +}); diff --git a/tests/unit/mail-content-kind-where.test.ts b/tests/unit/mail-content-kind-where.test.ts new file mode 100644 index 0000000..91ef574 --- /dev/null +++ b/tests/unit/mail-content-kind-where.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "vitest"; +import { contentKindWhere } from "@/utils/mailContent"; + +describe("contentKindWhere", () => { + it("packs list filter looks at spreadsheet filenames", () => { + const w = contentKindWhere("PACKING_LIST"); + expect(w.attachments).toBeTruthy(); + }); + + it("plain text requires no attachments", () => { + const w = contentKindWhere("PLAIN_TEXT"); + expect(w.attachments).toEqual({ none: {} }); + }); + + it("empty is no attachments plus empty body", () => { + const w = contentKindWhere("EMPTY"); + expect(w.attachments).toEqual({ none: {} }); + expect(w.OR).toEqual([{ bodyText: null }, { bodyText: "" }]); + }); +}); diff --git a/tests/unit/mail-submit-version.test.ts b/tests/unit/mail-submit-version.test.ts new file mode 100644 index 0000000..898c5c0 --- /dev/null +++ b/tests/unit/mail-submit-version.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it, vi } from "vitest"; +import { + readServerVersion, + withVersionConflictRetry, +} from "@/utils/mail-submit-version"; + +describe("readServerVersion", () => { + it("reads integer server_version from error details", () => { + expect(readServerVersion({ server_version: 8 })).toBe(8); + expect(readServerVersion({ server_version: "9" })).toBe(9); + expect(readServerVersion({ server_version: 0 })).toBeNull(); + expect(readServerVersion(null)).toBeNull(); + }); +}); + +describe("withVersionConflictRetry", () => { + it("retries once with server_version after VERSION_CONFLICT", async () => { + const submit = vi + .fn() + .mockResolvedValueOnce({ + ok: false, + error: { + code: "VERSION_CONFLICT", + message: "版本冲突,请刷新后重试", + details: { server_version: 4 }, + }, + }) + .mockResolvedValueOnce({ + ok: true, + data: { mail_status: "FAILED", version: 5 }, + }); + const onVersion = vi.fn(); + const res = await withVersionConflictRetry({ + version: 3, + submit, + onVersion, + }); + expect(submit).toHaveBeenCalledTimes(2); + expect(submit).toHaveBeenNthCalledWith(1, 3); + expect(submit).toHaveBeenNthCalledWith(2, 4); + expect(res.ok).toBe(true); + expect(onVersion).toHaveBeenCalledWith(4); + expect(onVersion).toHaveBeenCalledWith(5); + }); + + it("does not retry other errors, but still keeps server_version", async () => { + const submit = vi.fn().mockResolvedValue({ + ok: false, + error: { + code: "CC_FAILED", + message: "写入失败", + details: { server_version: 6 }, + }, + }); + const onVersion = vi.fn(); + const res = await withVersionConflictRetry({ + version: 5, + submit, + onVersion, + }); + expect(submit).toHaveBeenCalledTimes(1); + expect(res.ok).toBe(false); + expect(onVersion).toHaveBeenCalledWith(6); + }); +}); diff --git a/tests/unit/mail4-time-rounds.test.ts b/tests/unit/mail4-time-rounds.test.ts index 9129043..a090f09 100644 --- a/tests/unit/mail4-time-rounds.test.ts +++ b/tests/unit/mail4-time-rounds.test.ts @@ -196,13 +196,13 @@ describe("mail4 time-round split + customer_instruction", () => { expect(units.some((u) => u.uiKind === "forecast")).toBe(true); }); - it("soft \u66f4\u65b0\u6d3e\u9001\u5355 maps to customer_instruction not work_order", () => { + it("soft \u66f4\u65b0\u6d3e\u9001\u5355 now defaults to work_order", () => { const units = extractMailInstructions({ subject: "MATU2745683", body: "\u66f4\u65b0\u6d3e\u9001\u5355\uff0c\u8bf7\u67e5\u6536", filenames: [], }); - expect(units.some((u) => u.uiKind === "customer_instruction")).toBe(true); - expect(units.some((u) => u.uiKind === "work_order")).toBe(false); + expect(units.some((u) => u.uiKind === "work_order")).toBe(true); + expect(units.some((u) => u.uiKind === "customer_instruction")).toBe(false); }); }); diff --git a/tests/unit/match-segment-attachments.test.ts b/tests/unit/match-segment-attachments.test.ts new file mode 100644 index 0000000..f60c67f --- /dev/null +++ b/tests/unit/match-segment-attachments.test.ts @@ -0,0 +1,167 @@ +import { describe, expect, it } from "vitest"; +import { buildCcWorkOrderFormValue } from "@/components/CcWorkOrderForm"; +import { + extractAttachIdTokens, + matchSegmentAttachments, +} from "@/services/parse/match-segment-attachments"; + +const MAIL85_FILES = [ + "YT2604021091=76件换标走HIA1卡派出库操作指令.xlsx", + "FBA19HW52S0L-76CTN-HIA1-覆盖贴1箱贴2张.pdf", + "X003UHDF7B-304PCS-覆盖贴1箱贴1张.pdf", +]; + +const MAIL84_FILES = [ + "WL103276-SBD1(1).pdf", + "WL103944-WL103940.pdf", + "WL98967-VGT2(1).pdf", + "WL99583-LAX9.pdf", + "WHSU5574991 卡转海.zip", +]; + +describe("extractAttachIdTokens", () => { + it("picks FBA / YT / FNSKU / WL, not ISO container", () => { + const ids = extractAttachIdTokens( + "柜号 WHSU8127240 原箱号 YT2604021091 FBA19HW52S0L X003UHDF7B WL103276 SBD1", + ); + expect(ids).toEqual( + expect.arrayContaining([ + "YT2604021091", + "FBA19HW52S0L", + "X003UHDF7B", + "WL103276", + ]), + ); + expect(ids).not.toContain("WHSU8127240"); + }); +}); + +describe("matchSegmentAttachments", () => { + it("mail85: 76件换标段挂 3 个贴标文件;833件历史轮不挂", () => { + const current = matchSegmentAttachments({ + segmentText: [ + "Dear,", + "原箱号 YT2604021091=FBA199R49LD6-MDW2=76 件操作指令", + "换标后单号: FBA19HW52S0L-2G1MCB6X-HIA1=76 件", + "FNSKU : X003UHDF7B ( 304PCS )", + ].join("\n"), + filenames: MAIL85_FILES, + isCurrent: true, + }); + expect(current).toEqual(MAIL85_FILES); + + const hist = matchSegmentAttachments({ + segmentText: [ + "Dear,", + "原箱号 YT2604021486=833 件操作指令", + "换标后单号:FBA19DS6CXV4-2G1XD81L-LGB8=87 件", + ].join("\n"), + filenames: MAIL85_FILES, + isCurrent: false, + }); + expect(hist).toEqual([]); + }); + + it("mail84: 卡转海/贴标交付段按 WL 唛头挂 PDF + zip;拦截段不挂", () => { + const seaBody = [ + "WHSU5574991", + "1.WL104834-2 SCK8 2件", + "2.", + "WL103944-WL103940 2", + "WL99583 LAX9 2", + "WL103276 SBD1 1", + "WL98967 VGT2 2", + "以上货物需要卡转海 麻烦贴标交付,UPS默认一箱贴两张。", + ].join("\n"); + const sea = matchSegmentAttachments({ + segmentText: seaBody, + filenames: MAIL84_FILES, + isCurrent: true, + }); + expect(sea).toEqual( + expect.arrayContaining([ + "WL103276-SBD1(1).pdf", + "WL103944-WL103940.pdf", + "WL98967-VGT2(1).pdf", + "WL99583-LAX9.pdf", + "WHSU5574991 卡转海.zip", + ]), + ); + + const intercept = matchSegmentAttachments({ + segmentText: [ + "注意:", + "1.WL103516", + "WL103616", + "以上几票需要拦截 清单中已备注", + "2.", + "WL101167", + "WL92334", + "这2票改自提", + ].join("\n"), + filenames: MAIL84_FILES, + isCurrent: false, + }); + expect(intercept).toEqual([]); + }); + + it("skips screenshot and DO; packing xlsx only if named in segment", () => { + const files = [ + "3邮箱.pdf", + "WHSU5574991-DO.pdf", + "柜号:MATU2745683卡派资料.xlsx", + "WL103276-SBD1.pdf", + ]; + const wo = matchSegmentAttachments({ + segmentText: "WL103276 SBD1 卡转海 贴标交付", + filenames: files, + isCurrent: true, + }); + expect(wo).toEqual(["WL103276-SBD1.pdf"]); + + const named = matchSegmentAttachments({ + segmentText: "请查收附件 柜号:MATU2745683卡派资料.xlsx", + filenames: files, + isCurrent: true, + }); + expect(named).toContain("柜号:MATU2745683卡派资料.xlsx"); + expect(named).not.toContain("3邮箱.pdf"); + expect(named).not.toContain("WHSU5574991-DO.pdf"); + }); + + it("role-only 卡转海.zip 只挂当前段,不挂历史拦截", () => { + const files = ["WHSU5574991 卡转海.zip"]; + const current = matchSegmentAttachments({ + segmentText: "以上货物需要卡转海 麻烦贴标交付", + filenames: files, + isCurrent: true, + }); + expect(current).toEqual(["WHSU5574991 卡转海.zip"]); + + const hist = matchSegmentAttachments({ + segmentText: "以上货物需要卡转海 麻烦贴标交付", + filenames: files, + isCurrent: false, + }); + expect(hist).toEqual([]); + }); +}); + +describe("buildCcWorkOrderFormValue uses the same rule", () => { + it("mail84 current 卡转海工单 F_AttachFile 含 WL PDF", () => { + const v = buildCcWorkOrderFormValue({ + subject: "拆柜清单更新 WHSU5574991", + body: [ + "WL103944-WL103940 2", + "WL99583 LAX9 2", + "以上货物需要卡转海 麻烦贴标交付", + ].join("\n"), + filenames: MAIL84_FILES, + containerNo: "WHSU5574991", + isCurrent: true, + }); + expect(v.F_AttachFile).toContain("WL103944-WL103940.pdf"); + expect(v.F_AttachFile).toContain("WL99583-LAX9.pdf"); + expect(v.F_AttachFile).toContain("卡转海.zip"); + }); +}); diff --git a/tests/unit/mock-mail-server.test.ts b/tests/unit/mock-mail-server.test.ts new file mode 100644 index 0000000..4fa6d05 --- /dev/null +++ b/tests/unit/mock-mail-server.test.ts @@ -0,0 +1,122 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { resetEnvCache } from "@/lib/env"; +import { + createImapClientFromConfig, + ImapClient, + type ImapClientConfig, +} from "@/services/imap/client"; +import { + getMockMailServer, + resetMockMailServer, +} from "@/services/imap/mock-mail-server"; + +const BASE_CONFIG: ImapClientConfig = { + host: "mock.local", + port: 993, + user: "test@mock.local", + pass: "test", + connectTimeoutMs: 1000, + readTimeoutMs: 1000, + folder: "INBOX", +}; + +function enableTestMode() { + process.env.DATABASE_URL = + process.env.DATABASE_URL || "mysql://app:app@localhost:7023/email_forecast"; + process.env.TEST_MODE = "1"; + resetEnvCache(); + resetMockMailServer(); +} + +function disableTestMode() { + delete process.env.TEST_MODE; + resetEnvCache(); + resetMockMailServer(); +} + +describe("MockMailServer", () => { + beforeEach(() => { + process.env.DATABASE_URL = + process.env.DATABASE_URL || "mysql://app:app@localhost:7023/email_forecast"; + disableTestMode(); + }); + + afterEach(() => { + disableTestMode(); + }); + + it("injects eml with attachment and fetches by uid", async () => { + const server = getMockMailServer(); + const injected = server.inject({ + from: "ops@example.com", + subject: "预报 WHSU8127240", + text: "请查收新增预报", + attachments: [ + { + filename: "数据模版.xlsx", + contentType: + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + content: Buffer.from("xlsx-bytes"), + }, + ], + }); + const client = server.createClient(BASE_CONFIG); + await client.connect(); + const uids = await client.searchSince(new Date(0)); + expect(uids).toEqual([injected.uid]); + const fetched = await client.fetchByUids(uids); + expect(fetched).toHaveLength(1); + expect(fetched[0].source.includes("数据模版.xlsx")).toBe(true); + expect(fetched[0].envelope?.subject).toBe("预报 WHSU8127240"); + await client.disconnect(); + }); + + it("simulates auth failure", async () => { + const server = getMockMailServer(); + server.setFault("auth_fail"); + const client = server.createClient(BASE_CONFIG); + await expect(client.connect()).rejects.toThrow(/AUTHENTICATIONFAILED/); + }); + + it("simulates connect timeout", async () => { + const server = getMockMailServer(); + server.setFault("timeout"); + const client = server.createClient(BASE_CONFIG); + await expect(client.connect()).rejects.toMatchObject({ code: "ETIMEOUT" }); + }); + + it("simulates empty mailbox", async () => { + const server = getMockMailServer(); + server.inject({ subject: "hidden", text: "x" }); + server.setFault("empty"); + const client = server.createClient(BASE_CONFIG); + await client.connect(); + expect(await client.searchSince(new Date(0))).toEqual([]); + expect(await client.countUnseen()).toBe(0); + }); +}); + +describe("IMAP factory TEST_MODE", () => { + afterEach(() => { + disableTestMode(); + }); + + it("returns real ImapClient when TEST_MODE is off", () => { + process.env.DATABASE_URL = + process.env.DATABASE_URL || "mysql://app:app@localhost:7023/email_forecast"; + delete process.env.TEST_MODE; + resetEnvCache(); + const client = createImapClientFromConfig(BASE_CONFIG); + expect(client).toBeInstanceOf(ImapClient); + }); + + it("returns MockMailServer client when TEST_MODE=1", async () => { + enableTestMode(); + getMockMailServer().inject({ subject: "fixture-a", text: "body" }); + const client = createImapClientFromConfig(BASE_CONFIG); + expect(client).not.toBeInstanceOf(ImapClient); + await client.connect(); + const uids = await client.searchSince(new Date(0)); + expect(uids.length).toBe(1); + }); +}); diff --git a/tests/unit/mock-target-api.test.ts b/tests/unit/mock-target-api.test.ts new file mode 100644 index 0000000..181711a --- /dev/null +++ b/tests/unit/mock-target-api.test.ts @@ -0,0 +1,126 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { resetEnvCache } from "@/lib/env"; +import { CcHttpError } from "@/services/cc/errors"; +import { CcHttpClient } from "@/services/cc/http"; +import { ccAuth } from "@/services/cc/auth"; +import { + getMockTargetAPI, + resetMockTargetAPI, +} from "@/services/cc/mock-target-api"; +import { + getTargetSystemClient, + setTargetSystemClientOverride, +} from "@/services/cc/target-system-client"; +import { isCcMock } from "@/services/cc/settings-config"; +import { resolveWriteMode } from "@/services/cc/write-capability"; + +function enableTestMode() { + process.env.DATABASE_URL = + process.env.DATABASE_URL || "mysql://app:app@localhost:7023/email_forecast"; + process.env.TEST_MODE = "1"; + resetEnvCache(); + resetMockTargetAPI(); + setTargetSystemClientOverride(null); +} + +function disableTestMode() { + delete process.env.TEST_MODE; + resetEnvCache(); + resetMockTargetAPI(); + setTargetSystemClientOverride(null); +} + +describe("MockTargetAPI", () => { + beforeEach(() => { + enableTestMode(); + }); + + afterEach(() => { + disableTestMode(); + }); + + it("records each request for assertions", async () => { + const mock = getMockTargetAPI(); + await mock.request("/Container/SaveContainer", { F_ContainerNo: "WHSU8127240" }); + await mock.requestGet("/ConfigModule/GetShippingLineList"); + const rec = mock.getRequests(); + expect(rec).toHaveLength(2); + expect(rec[0]).toMatchObject({ + method: "POST", + path: "/Container/SaveContainer", + body: { F_ContainerNo: "WHSU8127240" }, + }); + expect(rec[1].method).toBe("GET"); + expect(rec[1].path).toBe("/ConfigModule/GetShippingLineList"); + }); + + it.each([ + ["400", 400], + ["401", 401], + ["500", 500], + ] as const)("can return %s", async (fault, code) => { + const mock = getMockTargetAPI(); + mock.setFault(fault); + const res = await mock.request("/Container/SaveContainer", {}); + expect(res.code).toBe(code); + }); + + it("throws timeout as CcHttpError", async () => { + const mock = getMockTargetAPI(); + mock.setFault("timeout"); + await expect(mock.request("/Container/SaveContainer", {})).rejects.toBeInstanceOf( + CcHttpError, + ); + await expect(mock.request("/Container/SaveContainer", {})).rejects.toMatchObject({ + timeout: true, + }); + }); + + it("returns partial success payload", async () => { + const mock = getMockTargetAPI(); + mock.setFault("partial"); + const res = await mock.request("/Container/SaveStockBatchTransfer", {}); + expect(res.code).toBe(200); + expect(res.info).toBe("PARTIAL_SUCCESS"); + expect(res.data).toMatchObject({ ok_count: 1, fail_count: 1 }); + }); + + it("supports per-path fault overlay", async () => { + const mock = getMockTargetAPI(); + mock.setFault("ok"); + mock.setFault("500", "/Container/SaveContainer"); + const save = await mock.request("/Container/SaveContainer", {}); + const list = await mock.request("/Container/GetContainerList", {}); + expect(save.code).toBe(500); + expect(list.code).toBe(200); + }); +}); + +describe("TargetSystemClient TEST_MODE wiring", () => { + afterEach(() => { + disableTestMode(); + }); + + it("CcHttpClient.request goes to MockTargetAPI when TEST_MODE=1", async () => { + enableTestMode(); + const http = new CcHttpClient(ccAuth); + const res = await http.request("/Container/SaveContainer", { + entity: { F_ContainerNo: "ABCD1234567" }, + }); + expect(res.code).toBe(200); + const rec = getMockTargetAPI().getRequests(); + expect(rec.some((r) => r.path === "/Container/SaveContainer")).toBe(true); + }); + + it("getTargetSystemClient returns mock in TEST_MODE", () => { + enableTestMode(); + expect(getTargetSystemClient()).toBe(getMockTargetAPI()); + }); + + it("isCcMock is false and write mode is live so HTTP mock is reachable", async () => { + enableTestMode(); + expect(await isCcMock()).toBe(false); + expect(await resolveWriteMode("save_container")).toBe("live"); + expect(await resolveWriteMode("batch_transfer")).toBe("live"); + }); +}); diff --git a/tests/unit/operator-error-copy.test.ts b/tests/unit/operator-error-copy.test.ts new file mode 100644 index 0000000..d928346 --- /dev/null +++ b/tests/unit/operator-error-copy.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it } from "vitest"; +import { + formatCcCause, + formatImapSkipReason, + formatMailLastError, + formatOAuthCallbackError, + formatOperatorError, +} from "@/constants/error-copy"; + +describe("formatOperatorError", () => { + it("translates last_error machine codes", () => { + expect(formatMailLastError("NO_SHIPMENT_ROWS")).toMatch(/未解析到货件/); + expect(formatMailLastError("NO_SUPPORTED_SPREADSHEET")).toMatch(/表格附件/); + expect(formatMailLastError("NO_BODY_NO_ATTACHMENT")).toMatch(/没有正文/); + expect(formatMailLastError("ATTACHMENT_TOO_LARGE")).toMatch(/过大/); + expect(formatMailLastError("MIME_PARSE_ERROR")).toMatch(/格式损坏/); + expect(formatMailLastError("missing_columns:仓库ID")).toMatch(/缺列/); + expect(formatMailLastError("INVALID_CONTAINER_NO:TRHU8524770")).toMatch( + /TRHU8524770/, + ); + }); + + it("does not show Unauthorized / Invalid body to operators", () => { + expect(formatOperatorError("Unauthorized", "UNAUTHORIZED")).toMatch(/登录/); + expect(formatOperatorError("Unauthorized")).toMatch(/CarrierCentral|权限/); + expect(formatOperatorError("Invalid body")).toMatch(/提交内容|必填/); + expect(formatOperatorError("Mail not found")).toMatch(/邮件/); + expect(formatMailLastError("Unauthorized")).not.toMatch(/Unauthorized/i); + expect(formatMailLastError("Invalid body")).not.toMatch(/Invalid/i); + }); + + it("translates CC transport failures", () => { + expect(formatCcCause("CC request timeout")).toMatch(/超时/); + expect(formatCcCause("CC request failed")).toMatch(/无法连接/); + expect(formatCcCause("CC save failed with code 400")).toMatch(/拒绝保存/); + expect(formatCcCause("GetContainerList timeout")).toMatch(/查询柜号/); + expect(formatMailLastError("CC_WRITE_OFF")).toMatch(/未开启/); + expect(formatCcCause("Unauthorized")).not.toMatch(/Unauthorized/i); + }); + + it("keeps CC Chinese info even when it embeds FBACode / charset lists", () => { + const raw = + "第60行的FBACode是空值或者只输入了正常英文字符[_- ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz]以外的字符。"; + const out = formatCcCause(raw); + expect(out).toMatch(/第60行/); + expect(out).toMatch(/目的代码/); + expect(out).not.toMatch(/请稍后重试或到 CC 核对/); + }); + + it("keeps already-Chinese messages", () => { + expect(formatOperatorError("邮件不存在")).toBe("邮件不存在"); + expect(formatOperatorError("柜号必填")).toBe("柜号必填"); + }); + + it("rewrites English embedded in Chinese", () => { + expect( + formatOperatorError("查柜失败:GetContainerList timeout,默认降级工单"), + ).toMatch(/查询柜号超时/); + }); + + it("maps OAuth and IMAP skip reasons", () => { + expect(formatOAuthCallbackError("missing_code")).toMatch(/授权码/); + expect(formatImapSkipReason("no_credentials")).toMatch(/邮箱/); + expect(formatImapSkipReason("SKIP_BLACKLIST")).toMatch(/黑名单/); + }); +}); diff --git a/tests/unit/packing-list.test.ts b/tests/unit/packing-list.test.ts index 479a7f8..47166c0 100644 --- a/tests/unit/packing-list.test.ts +++ b/tests/unit/packing-list.test.ts @@ -71,13 +71,17 @@ describe("packing-list", () => { expect(parsed.shipments.length).toBeGreaterThan(10); const ups = parsed.shipments.filter((s) => s.F_Transporter === "UPS"); expect(ups.length).toBeGreaterThan(0); - expect(ups.every((s) => !s.F_FBACode)).toBe(true); + expect(ups.every((s) => s.F_FBACode === "UPS")).toBe(true); + const fedex = parsed.shipments.filter((s) => s.F_Transporter === "FEDEX"); + expect(fedex.length).toBeGreaterThan(0); + expect(fedex.every((s) => s.F_FBACode === "FEDEX")).toBe(true); const amazon = parsed.shipments.find((s) => s.F_FBAID === "FBA16SQQ7PTF"); expect(amazon?.F_FBACode).toBe("LAS1"); const priv = parsed.shipments.find((s) => (s.F_Address || "").includes("12924 Pierce"), ); expect(priv).toBeTruthy(); + expect(priv!.F_Transporter).toBe("TRUCK"); expect(priv!.F_FBACode).toBeFalsy(); expect(parsed.shipments.every((s) => s.F_ShipmentID)).toBe(true); }); @@ -177,4 +181,124 @@ describe("packing-list", () => { expect(s.F_Expected_DeliveryDateB).toBeTruthy(); expect(s.F_Expected_DeliveryDateE).toBeTruthy(); }); + + it("maps 中文模板列 仓库代码/收件人/公司/省市区/收货地址/备注", async () => { + const wb = new ExcelJS.Workbook(); + const ws = wb.addWorksheet("Sheet1"); + ws.addRow([ + "仓库代码", + "渠道", + "件数", + "收件人", + "公司名称", + "联系电话", + "省/市/区", + "邮政编码", + "收货地址", + "备注", + ]); + ws.addRow([ + "LAS1", + "卡派", + 3, + "LAS1", + "AMAZON COM SERVICES INC", + "0123456789", + "NV / HENDERSON /", + "89044-8746", + "12300 Bermuda Road", + "三票分开打托", + ]); + const buf = Buffer.from(await wb.xlsx.writeBuffer()); + const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); + expect(parsed.errorCode).toBeUndefined(); + expect(parsed.shipments).toHaveLength(1); + const s = parsed.shipments[0]; + expect(s.F_FBACode).toBe("LAS1"); + expect(s.F_Contact).toBe("LAS1"); + expect(s.F_EnglishName).toBe("AMAZON COM SERVICES INC"); + expect(s.F_Mobile).toBe("0123456789"); + expect(s.F_State).toBe("NV"); + expect(s.F_City).toBe("HENDERSON"); + expect(s.F_ZipCode).toBe("89044-8746"); + expect(s.F_Address).toBe("12300 Bermuda Road"); + expect(s.F_Remark).toBe("三票分开打托"); + }); + + it("splits Address 列里的仓库代码/收件人/收货地址键值块", async () => { + const wb = new ExcelJS.Workbook(); + const ws = wb.addWorksheet("Sheet1"); + ws.addRow(["FBACode", "渠道", "件数", "Address", "行备注"]); + ws.addRow([ + "", + "卡派", + 3, + [ + "仓库代码: LAS1", + "收件人: LAS1", + "公司名称: AMAZON COM SERVICES INC", + "联系电话: 0123456789", + "省/市/区: NV / HENDERSON /", + "邮政编码: 89044-8746", + "收货地址: 12300 Bermuda Road", + ].join("\n"), + "三票分开打托", + ]); + const buf = Buffer.from(await wb.xlsx.writeBuffer()); + const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); + expect(parsed.errorCode).toBeUndefined(); + const s = parsed.shipments[0]; + expect(s.F_FBACode).toBe("LAS1"); + expect(s.F_Contact).toBe("LAS1"); + expect(s.F_EnglishName).toBe("AMAZON COM SERVICES INC"); + expect(s.F_Mobile).toBe("0123456789"); + expect(s.F_State).toBe("NV"); + expect(s.F_City).toBe("HENDERSON"); + expect(s.F_ZipCode).toBe("89044-8746"); + expect(s.F_Address).toBe("12300 Bermuda Road"); + expect(s.F_Remark).toBe("三票分开打托"); + }); + + it("splits 键值块 in FBACode 列 and unused extra column", async () => { + const blob = [ + "仓库代码: LAS1", + "收件人: LAS1", + "公司名称: AMAZON COM SERVICES INC", + "联系电话: 0123456789", + "省/市/区: NV / HENDERSON /", + "邮政编码: 89044-8746", + "收货地址: 12300 Bermuda Road", + "备注:三票分开打托", + ].join("\n"); + const wb = new ExcelJS.Workbook(); + const ws = wb.addWorksheet("Sheet1"); + ws.addRow(["FBACode", "渠道", "件数", "Address", "其它"]); + ws.addRow(["", "卡派", 3, "", blob]); + const buf = Buffer.from(await wb.xlsx.writeBuffer()); + const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); + const s = parsed.shipments[0]; + expect(s.F_FBACode).toBe("LAS1"); + expect(s.F_Address).toBe("12300 Bermuda Road"); + expect(s.F_Contact).toBe("LAS1"); + expect(s.F_Remark).toBe("三票分开打托"); + }); + + it("keeps dedicated Contact column over Address 块里的收件人", async () => { + const wb = new ExcelJS.Workbook(); + const ws = wb.addWorksheet("Sheet1"); + ws.addRow(["FBACode", "渠道", "件数", "Address", "Contact"]); + ws.addRow([ + "", + "卡派", + 1, + "仓库代码: ONT8\n收件人: FROMADDR\n收货地址: 1 Main St", + "Tom", + ]); + const buf = Buffer.from(await wb.xlsx.writeBuffer()); + const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); + const s = parsed.shipments[0]; + expect(s.F_FBACode).toBe("ONT8"); + expect(s.F_Contact).toBe("Tom"); + expect(s.F_Address).toBe("1 Main St"); + }); }); diff --git a/tests/unit/resilience-scenarios.test.ts b/tests/unit/resilience-scenarios.test.ts index 14d8989..9a8794b 100644 --- a/tests/unit/resilience-scenarios.test.ts +++ b/tests/unit/resilience-scenarios.test.ts @@ -186,14 +186,12 @@ describe("A-2 / A-9 确认门禁与幂等", () => { it("A-2.1 / A-2.2 非 PENDING 不可导入;IMPORTING/SUCCESS 不可重解析", () => { expect( canEnterForecastConfirm({ - mailType: "NEW_CONTAINER", status: "PENDING_CONFIRM", validShipmentCount: 1, }), ).toBe(true); expect( canEnterForecastConfirm({ - mailType: "NEW_CONTAINER", status: "IMPORTING", validShipmentCount: 1, }), @@ -204,21 +202,13 @@ describe("A-2 / A-9 确认门禁与幂等", () => { expect(canTransition("PARSE_FAILED", "PARSING")).toBe(true); }); - it("A-2.4 / A-2.6 WORK_ORDER / TRANSFER 不可走预报确认", () => { - expect( - canEnterForecastConfirm({ - mailType: "WORK_ORDER", - status: "PENDING_CONFIRM", - validShipmentCount: 2, - }), - ).toBe(false); + it("A-2.4 预报确认不看邮件主类型,只看状态与柜/货件数据", () => { expect( canEnterForecastConfirm({ - mailType: "TRANSFER", status: "PENDING_CONFIRM", validShipmentCount: 2, }), - ).toBe(false); + ).toBe(true); }); it("A-9.1 未映射未 ack → ACK_UNMAPPED_REQUIRED", () => { diff --git a/tests/unit/resolve-confirm-instruction.test.ts b/tests/unit/resolve-confirm-instruction.test.ts new file mode 100644 index 0000000..0c2c31f --- /dev/null +++ b/tests/unit/resolve-confirm-instruction.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from "vitest"; +import { + resolveConfirmInstruction, +} from "@/services/parse/resolve-confirm-instruction"; +import type { MailInstructionUnit } from "@/services/parse/split-instructions"; + +function unit( + partial: Partial & + Pick, +): MailInstructionUnit { + return { + mailType: "NEW_CONTAINER", + label: partial.uiKind, + summary: "", + keywords: [], + source: "body_segment", + segmentIndex: 0, + isCurrent: false, + segmentText: "", + ...partial, + }; +} + +describe("resolveConfirmInstruction", () => { + const forecast = unit({ + id: "forecast-body_segment-5-派送单请查收", + uiKind: "forecast", + isCurrent: false, + }); + const transfer = unit({ + id: "transfer-body_segment-0-新增转仓", + uiKind: "transfer", + mailType: "TRANSFER", + isCurrent: true, + }); + + it("selects the requested instruction even when it is historical forecast", () => { + const resolved = resolveConfirmInstruction({ + units: [transfer, forecast], + instructionId: forecast.id, + }); + expect(resolved.reason).toBe("ok"); + expect(resolved.unit?.id).toBe(forecast.id); + expect(resolved.unit?.uiKind).toBe("forecast"); + }); + + it("does not infer from mail main type when instruction_id is missing and multiple units exist", () => { + const resolved = resolveConfirmInstruction({ + units: [transfer, forecast], + }); + expect(resolved.reason).toBe("missing_id"); + expect(resolved.unit).toBeNull(); + expect(resolved.candidates.map((u) => u.uiKind)).toEqual([ + "transfer", + "forecast", + ]); + }); + + it("auto-selects the only pending confirmable unit", () => { + const resolved = resolveConfirmInstruction({ + units: [forecast], + }); + expect(resolved.reason).toBe("ok"); + expect(resolved.unit?.id).toBe(forecast.id); + }); + + it("keeps an already-imported unit visible as readonly", () => { + const resolved = resolveConfirmInstruction({ + units: [forecast], + instructionId: forecast.id, + lineage: { + instruction_state: { + [forecast.id]: { status: "IMPORTED", importedAt: "2026-08-17" }, + }, + }, + }); + expect(resolved.reason).toBe("already_imported"); + expect(resolved.unit?.id).toBe(forecast.id); + }); +}); diff --git a/tests/unit/save-compensation-gate.test.ts b/tests/unit/save-compensation-gate.test.ts new file mode 100644 index 0000000..8e6c520 --- /dev/null +++ b/tests/unit/save-compensation-gate.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from "vitest"; +import { shouldEnqueueCompensation } from "@/services/cc/save-container"; + +describe("shouldEnqueueCompensation", () => { + it("timeout → TIMEOUT_UNKNOWN", () => { + expect( + shouldEnqueueCompensation({ + ok: false, + status: "TIMEOUT_UNKNOWN", + error: "CC request timeout", + requestBody: "{}", + }), + ).toBe("TIMEOUT_UNKNOWN"); + }); + + it("500 → CC_5XX", () => { + expect( + shouldEnqueueCompensation({ + ok: false, + status: "FAILED", + error: "Internal Server Error", + requestBody: "{}", + response: { code: 500, info: "Internal Server Error" }, + }), + ).toBe("CC_5XX"); + }); + + it("401 / 400 do not enqueue", () => { + expect( + shouldEnqueueCompensation({ + ok: false, + status: "FAILED", + error: "Unauthorized", + requestBody: "{}", + response: { code: 401, info: "Unauthorized" }, + }), + ).toBeNull(); + expect( + shouldEnqueueCompensation({ + ok: false, + status: "FAILED", + error: "Bad Request", + requestBody: "{}", + response: { code: 400, info: "Bad Request" }, + }), + ).toBeNull(); + }); +}); diff --git a/tests/unit/split-instructions.test.ts b/tests/unit/split-instructions.test.ts index e516d8f..4a9fee0 100644 --- a/tests/unit/split-instructions.test.ts +++ b/tests/unit/split-instructions.test.ts @@ -3,6 +3,7 @@ import { detectUiKindsFromText, extractForecastInstructionText, extractMailInstructions, + parseInstructionRoundAtMs, splitBodySegments, stripMobileMailChrome, extractForwardChromeMeta, @@ -70,11 +71,106 @@ describe("detectUiKindsFromText", () => { }); }); +describe("do_upload dedupe", () => { + it("does not emit two DO cards when forecast body + DO.pdf both hit", () => { + const body = [ + "Dear,", + "原箱号YT2604021091=76件操作指令", + "换标后单号:FBA19HW52S0L=76件", + "3:贴好拍照回传", + "------------------ 原始邮件邮件 ------------------", + "发件人:a@b.com", + "日期:2026-05-08 19:15:53", + "主题:新增预报136:新辰泽+WHSU8127240", + "Dear,", + "请查收新增预报: 新辰泽 +WHLC027G597465+WHSU8127240+ 洛杉矶", + "+40HQ+EDT2026.04-25 ETA2026.05-15 船名航次 HMM EMERALD 013E+", + "提拆派组合柜 - 不带托架DO也同步上传至附件请注意查收!", + ].join("\n"); + const units = extractMailInstructions({ + subject: MAIL4_SUBJECT, + body, + filenames: ["WHSU8127240-DO.pdf", ...MAIL4_FILES], + }); + const dos = units.filter((u) => u.uiKind === "do_upload"); + expect(dos).toHaveLength(1); + expect(dos[0].isCurrent).toBe(true); + expect(dos[0].source).toBe("body_segment"); + }); +}); + +describe("extractMailInstructions display order", () => { + it("sorts units newest round first by roundAt", () => { + const body = [ + "更新派送单 地址更新 麻烦按此份为准!!!", + "---- 转发的邮件 ----", + "发件人江江<1817055288@qq.com>", + "日期 2026年3月28日 下午 5:56", + "主题 更新地址", + "在 2026-03-20 10:00:00 , \"op\" 写道:", + "Dear,", + "DO请查收见附件", + "在 2026-03-15 09:00:00 , \"op\" 写道:", + "Dear,", + "请查收新增预报: 新辰泽 +EGLV143661466958+TRHU8524770", + ].join("\n"); + const units = extractMailInstructions({ + subject: + "Fw: 新增预报136:TRHU8524770+EGLV143661466958+40HQ", + body, + filenames: ["TRHU8524770-DO-1.pdf"], + }); + const kinds = units.map((u) => u.uiKind); + const ci = kinds.indexOf("work_order"); + const doIdx = kinds.indexOf("do_upload"); + const fc = kinds.indexOf("forecast"); + expect(ci).toBeGreaterThanOrEqual(0); + expect(doIdx).toBeGreaterThanOrEqual(0); + expect(fc).toBeGreaterThanOrEqual(0); + expect(ci).toBeLessThan(doIdx); + expect(doIdx).toBeLessThan(fc); + }); + + it("mail4 work_order rounds newest first", () => { + const body = [ + "Dear,", + "原箱号YT2604021091=76件操作指令", + "在 2026-05-25 13:45:03 , \"新辰泽\" 写道:", + "Dear,", + "WHSU8127240= 原箱号 换标", + "在 2026-05-20 15:55:44 , \"新辰泽\" 写道:", + "Dear,", + "换标 167件操作指令", + ].join("\n"); + const units = extractMailInstructions({ + subject: MAIL4_SUBJECT, + body, + filenames: [], + }); + const wo = units.filter((u) => u.uiKind === "work_order"); + const times = wo.map((u) => parseInstructionRoundAtMs(u.roundAt) ?? 0); + for (let i = 1; i < times.length; i++) { + expect(times[i - 1]).toBeGreaterThanOrEqual(times[i]); + } + }); +}); + +describe("parseInstructionRoundAtMs", () => { + it("parses Chinese date", () => { + expect(parseInstructionRoundAtMs("2026年3月28日 17:56")).toBeTruthy(); + }); +}); + describe("extractMailInstructions multi-date", () => { it("mail4: forecast + work_order as separate units", () => { const units = extractMailInstructions({ subject: MAIL4_SUBJECT, - body: MAIL4_BODY, + body: [ + MAIL4_BODY, + "在 2026-05-01 09:00:00 , \"op\" 写道:", + "Dear,", + "请查收新增预报: 新辰泽 +WHSU8127240", + ].join("\n"), filenames: MAIL4_FILES, }); const kinds = units.map((u) => u.uiKind); @@ -116,6 +212,110 @@ describe("extractMailInstructions multi-date", () => { expect(current.some((u) => u.uiKind === "work_order")).toBe(true); expect(current.every((u) => u.uiKind !== "forecast")).toBe(true); }); + + it("splits nested QQ inline quote headers on mail98-style thread", () => { + const body = [ + "Forwarded message:", + "From:朱甜甜", + "Date:2026-07-22 23:18:44(美国山区 (GMT-07:00))", + "TCNU4412465 WL003 FBA19DYHCDLS-XPB2-37件", + "安排卡转海,麻烦安排贴标操作。", + "朱甜甜 在 2026年7月10日 周五 17:07 写道:", + "TCNU4412465", + "WL103262 50433。6件", + "这两票客户需要美时7/10 下午2点前送达", + "朱甜甜 在 2026年7月1日 周三 17:15 写道:", + "TCNU4412465", + "这两票总共31件记得叫仓库拍一下照片过来哈。", + "朱甜甜 在 2026年6月13日 周六 15:47 写道:", + "TCNU4412465+ ONEYNB3BW9236300+船名航次:WAN HAI 511-V.E110+ETA:6/22", + "拆柜清单", + "派送要求: 1,分别按票打板 千万不要混打", + ].join("\n"); + const segs = splitBodySegments(body); + expect(segs.length).toBeGreaterThanOrEqual(4); + const units = extractMailInstructions({ + subject: + "Fw: Fw:贴标操作:派送指令:拍照指令:do请查收:TCNU4412465+ ONEYNB3BW9236300+船名航次:WAN HAI 511-V.E110+ETA:6/22+UPS-6件+亚马逊卡派-1275件+私人地址-30件+拆柜清单", + body, + filenames: [], + }); + const wo = units.filter((u) => u.uiKind === "work_order"); + expect(wo.length).toBeGreaterThanOrEqual(2); + expect(units.some((u) => u.uiKind === "work_order")).toBe(true); + expect(units.some((u) => u.uiKind === "forecast")).toBe(true); + expect(wo.some((u) => /贴标/.test(u.segmentText))).toBe(true); + expect(wo.some((u) => /拍一下照片|拍照/.test(u.segmentText))).toBe(true); + const forecast = units.find((u) => u.uiKind === "forecast"); + expect(forecast?.isCurrent).toBe(false); + expect(forecast?.roundAt).toMatch(/2026年7月1日|2026年6月13日/); + }); + + it("adds forecast from ops subject when current body is work_order", () => { + const subject = + "Fw: Fw:贴标操作:派送指令:拍照指令:do请查收:TCNU4412465+ ONEYNB3BW9236300+船名航次:WAN HAI 511-V.E110+ETA:6/22+UPS-6件+亚马逊卡派-1275件+私人地址-30件+拆柜清单"; + const body = [ + "Forwarded message:", + "From:朱甜甜", + "Date:2026-07-22 23:18:44(美国山区 (GMT-07:00))", + "Subject:贴标操作:派送指令:拍照指令:do请查收:TCNU4412465+ ONEYNB3BW9236300+船名航次:WAN HAI 511-V.E110+ETA:6/22+UPS-6件+亚马逊卡派-1275件+私人地址-30件+拆柜清单", + "TCNU4412465 WL003 FBA19DYHCDLS-XPB2-37件", + "安排卡转海,麻烦安排贴标操作。", + "注意:ups默认一件贴2张面单", + ].join("\n"); + const units = extractMailInstructions({ + subject, + body, + filenames: ["WL100244-37-XPB2(1).pdf"], + }); + expect(units.some((u) => u.uiKind === "work_order")).toBe(true); + expect(units.some((u) => u.uiKind === "forecast")).toBe(true); + expect(units.find((u) => u.uiKind === "forecast")?.isCurrent).toBe(false); + }); + + it("splits reply-original boundary into forecast + redirect-delivery", () => { + const body = [ + "Forwarded message:", + "From:km16@kmfba.com", + "Date:2026-07-21 02:44:04(美国山区 (GMT-07:00))", + "Subject:回复:ZJYC COSU6504130784 FCIU7089474 7.10 派拆柜派送", + " FCIU7089474 MR03002964 改地址派送到 7400 E. Slauson Ave commerce. CA 90040", + "", + "km16", + "km16@kmfba.com", + "", + "---- 回复的原邮件 ----", + "发送日期2026年6月26日 14:09", + "主题ZJYC COSU6504130784 FCIU7089474 7.10 派拆柜派送", + "", + "FCIU7089474 提柜 COSCO 40HQ ETA:7.10 卡派拆柜派送", + "km16", + "", + "km16@kmfba.com", + ].join("\n"); + const units = extractMailInstructions({ + subject: "Fw: Fw:回复:ZJYC COSU6504130784 FCIU7089474 7.10 派拆柜派送", + body, + filenames: [], + }); + expect(units.some((u) => u.uiKind === "work_order")).toBe(true); + expect(units.some((u) => u.uiKind === "forecast")).toBe(true); + expect( + units.some( + (u) => + /改地址派送到/.test(u.segmentText) && + u.uiKind === "work_order" && + u.keywords.includes("改地址派送"), + ), + ).toBe(true); + expect( + units.some( + (u) => + u.uiKind === "forecast" && + /提柜 COSCO 40HQ ETA:7.10 卡派拆柜派送/.test(u.segmentText), + ), + ).toBe(true); + }); }); describe("extractForecastInstructionText", () => { diff --git a/tests/unit/state-machine.test.ts b/tests/unit/state-machine.test.ts index 9de4530..f51365b 100644 --- a/tests/unit/state-machine.test.ts +++ b/tests/unit/state-machine.test.ts @@ -21,4 +21,9 @@ describe("state-machine", () => { it("allows PARSING → FETCHED (stale reaper)", () => { expect(canTransition("PARSING", "FETCHED")).toBe(true); }); + + it("does not log when probing illegal ignore on SUCCESS", () => { + expect(canTransition("SUCCESS", "IGNORED")).toBe(false); + expect(() => canTransition("SUCCESS", "IGNORED")).not.toThrow(); + }); }); diff --git a/tests/unit/transfer-instruction-table.test.ts b/tests/unit/transfer-instruction-table.test.ts new file mode 100644 index 0000000..8657141 --- /dev/null +++ b/tests/unit/transfer-instruction-table.test.ts @@ -0,0 +1,133 @@ +import { describe, expect, it } from "vitest"; +import { + parseTransferInstructionTable, + splitTaggedOcrBlocks, + transferRowsToPairs, +} from "@/services/parse/transfer-instruction-table"; +import { buildTransferSegmentContexts } from "@/services/parse/match-transfer-segment-ocr"; +import type { MailInstructionUnit } from "@/services/parse/split-instructions"; + +describe("parseTransferInstructionTable", () => { + it("parses 地址/箱数/仓库重定向 row", () => { + const rows = parseTransferInstructionTable( + "柜号 ZCSU6581068 ETA 2026/7/7 地址 PSC2 箱数 7 提柜时间 仓库重定向 POC2", + ); + expect(rows).toEqual([ + { + containerNo: "ZCSU6581068", + eta: "2026/7/7", + from: "PSC2", + to: "POC2", + ctns: 7, + }, + ]); + expect(transferRowsToPairs(rows)).toEqual([ + { from: "PSC2", to: "POC2", rows: 7 }, + ]); + }); + + it("parses FWA4 → VGT2", () => { + const rows = parseTransferInstructionTable( + "地址 FWA4 箱数 17 仓库重定向 VGT2 柜号 ZCSU6581068", + ); + expect(rows[0]?.from).toBe("FWA4"); + expect(rows[0]?.to).toBe("VGT2"); + expect(rows[0]?.ctns).toBe(17); + }); + + it("parses noisy OCR table row", () => { + const rows = parseTransferInstructionTable( + "ZCSU6581068 20267777 PSC2 7 POC2", + ); + expect(rows[0]?.from).toBe("PSC2"); + expect(rows[0]?.to).toBe("POC2"); + }); + + it("uses fallback header fields and ignores OCR noise codes", () => { + const rows = parseTransferInstructionTable( + "HES ETA Ek BH ZCSUB581068 2026/7/7 PSC2 7 POC2", + { + containerNo: "ZCSU6581068", + }, + ); + expect(rows).toEqual([ + { + containerNo: "ZCSU6581068", + eta: "2026/7/7", + from: "PSC2", + to: "POC2", + ctns: 7, + }, + ]); + }); + + it("normalizes fuzzy FWA4 OCR row", () => { + const rows = parseTransferInstructionTable( + "155 EH ETA Spit AE RABE CEEEE zcsuessioes ozoze/r/7 Ewad 17 VGT2", + { + containerNo: "ZCSU6581068", + }, + ); + expect(rows).toEqual([ + { + containerNo: "ZCSU6581068", + from: "FWA4", + to: "VGT2", + ctns: 17, + }, + ]); + }); +}); + +describe("splitTaggedOcrBlocks", () => { + it("splits per attachment tag", () => { + const blocks = splitTaggedOcrBlocks( + "【OCR:a.jpg】\n地址 PSC2 箱数 7 仓库重定向 POC2\n\n【OCR:b.jpg】\n地址 FWA4 箱数 17 仓库重定向 VGT2", + ); + expect(blocks).toHaveLength(2); + expect(blocks[0].tag).toBe("a.jpg"); + expect(blocks[1].text).toMatch(/FWA4/); + }); +}); + +describe("buildTransferSegmentContexts", () => { + it("assigns oldest transfer unit to first image OCR block", () => { + const units: MailInstructionUnit[] = [ + { + id: "t-new", + uiKind: "transfer", + mailType: "TRANSFER", + label: "批量转仓", + summary: "", + keywords: [], + source: "body_segment", + segmentIndex: 3, + isCurrent: true, + segmentText: "新增转仓,请留意", + roundAt: "2026-07-07", + }, + { + id: "t-old", + uiKind: "transfer", + mailType: "TRANSFER", + label: "批量转仓", + summary: "", + keywords: [], + source: "body_segment", + segmentIndex: 4, + isCurrent: false, + segmentText: "新增转仓,请留意", + roundAt: "2026-07-06", + }, + ]; + const ctx = buildTransferSegmentContexts({ + transferUnits: units, + imageFilenames: ["img-fwa.jpg", "img-psc.jpg"], + containerNo: "ZCSU6581068", + ocrText: + "【OCR:img-fwa.jpg】\n地址 FWA4 箱数 17 仓库重定向 VGT2\n【OCR:img-psc.jpg】\n地址 PSC2 箱数 7 仓库重定向 POC2", + }); + expect(ctx.get("t-old")?.rows[0]?.from).toBe("FWA4"); + expect(ctx.get("t-new")?.rows[0]?.from).toBe("PSC2"); + }); +}); diff --git a/tests/unit/us-ports.test.ts b/tests/unit/us-ports.test.ts new file mode 100644 index 0000000..d1242c4 --- /dev/null +++ b/tests/unit/us-ports.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from "vitest"; +import { + isKnownUsDestinationPort, + matchUsDestinationPort, +} from "@/services/parse/us-ports"; +import { + isPlausibleDestinationPort, + normalizeDestinationPort, + parseBlPlusTemplate, +} from "@/services/parse/bl-plus-template"; + +describe("us destination ports whitelist", () => { + it("accepts known US ports and normalizes aliases", () => { + expect(matchUsDestinationPort("洛杉矶")).toBe("洛杉矶"); + expect(matchUsDestinationPort("Los Angeles")).toBe("洛杉矶"); + expect(matchUsDestinationPort("LA")).toBe("洛杉矶"); + expect(matchUsDestinationPort("Long Beach")).toBe("长滩"); + expect(matchUsDestinationPort("纽约")).toBe("纽约"); + expect(matchUsDestinationPort("Savannah")).toBe("萨凡纳"); + }); + + it("rejects non-port fragments", () => { + expect(matchUsDestinationPort("UPS-6件")).toBeNull(); + expect(matchUsDestinationPort("ETA : 7/7 | Fw: 回复")).toBeNull(); + expect(matchUsDestinationPort("拆柜清单")).toBeNull(); + expect(matchUsDestinationPort("贴标操作")).toBeNull(); + expect(matchUsDestinationPort("提拆派")).toBeNull(); + expect(isKnownUsDestinationPort("随便中文")).toBe(false); + expect(isPlausibleDestinationPort("随便中文")).toBe(false); + expect(normalizeDestinationPort("UPS-6件")).toBe(""); + }); + + it("keeps 洛杉矶 from bl-plus template and ignores channel as port", () => { + const ok = parseBlPlusTemplate( + "新辰泽+WHLC027G597465+WHSU8127240+洛杉矶+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派组合柜-不带托架", + { defaultYear: 2026 }, + ); + expect(ok?.port).toBe("洛杉矶"); + + const bad = parseBlPlusTemplate( + "LINK EVER INC+ZIMUSHH32195411+柜号:ZCSU6581068+ETA : 7/7+UPS-6件+拆柜清单", + { defaultYear: 2026 }, + ); + // 即使误解析,目的港也不得是渠道件数 + expect(bad?.port == null || bad?.port === "洛杉矶").toBe(true); + expect(bad?.port).not.toBe("UPS-6件"); + }); +}); diff --git a/tests/unit/workbench-suite.test.ts b/tests/unit/workbench-suite.test.ts new file mode 100644 index 0000000..57b306f --- /dev/null +++ b/tests/unit/workbench-suite.test.ts @@ -0,0 +1,26 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { enableTestModeEnv, disableTestModeEnv } from "@/services/test/test-mode-env"; +import { runWorkbenchSuite } from "@/services/test/workbench-suite"; +import { loadEmailFixtureMetas } from "@/services/test/email-fixture-library"; + +describe("workbench suite", () => { + beforeEach(() => { + enableTestModeEnv(); + }); + afterEach(() => { + disableTestModeEnv(); + }); + + it("lists >=15 fixture metas without materializing 20MB att", async () => { + const metas = await loadEmailFixtureMetas(); + expect(metas.length).toBeGreaterThanOrEqual(15); + expect(metas.some((m) => m.id === "01-normal-forecast")).toBe(true); + }); + + it("extract + fault matrix all pass (no DB pipeline)", async () => { + const report = await runWorkbenchSuite({ pipeline: false }); + const fails = report.cases.filter((c) => !c.ok); + expect(fails, JSON.stringify(fails)).toEqual([]); + expect(report.passed).toBeGreaterThanOrEqual(15 + 6); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index 95a7ee3..ff2d6fb 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -5,6 +5,23 @@ export default defineConfig({ test: { environment: "node", include: ["src/**/*.test.ts", "tests/**/*.test.ts"], + env: { + DATABASE_URL: + process.env.DATABASE_URL || + "mysql://app:app@localhost:7023/email_forecast", + }, + coverage: { + provider: "v8", + reporter: ["text", "text-summary", "json-summary"], + reportsDirectory: "./coverage", + reportOnFailure: true, + include: ["src/**/*.{ts,tsx}"], + exclude: [ + "src/**/*.test.ts", + "src/app/**", + "src/components/**", + ], + }, }, resolve: { alias: {