From 60f504b1f7308bcf8d1079a25d0db6922926ebae Mon Sep 17 00:00:00 2001
From: zwl <1051256694@qq.com>
Date: 星期二, 10 三月 2026 10:43:16 +0800
Subject: [PATCH] #

---
 src/main/java/com/zy/asrs/task/WorkOutErpReportScheduler.java         |   41 ++
 src/main/java/com/zy/asrs/entity/param/ErpPakoutReportParam.java      |   13 
 src/main/java/com/zy/api/controller/params/StopOutTaskParams.java     |   40 ++
 skills/build-asrs-solutions/agents/openai.yaml                        |    4 
 skills/build-asrs-solutions/SKILL.md                                  |   75 ++++
 skills/build-asrs-solutions/references/asrs-delivery-playbook.md      |   92 +++++
 src/main/java/com/zy/asrs/task/handler/WorkErpReportHandler.java      |  334 ++++++++++++++++++++
 src/main/java/com/zy/asrs/task/WorkErpReportScheduler.java            |   41 ++
 src/main/java/com/zy/asrs/entity/param/ErpPakinReportParam.java       |   21 +
 src/main/java/com/zy/asrs/entity/param/OpenOrderPakoutPauseParam.java |   11 
 src/main/java/com/zy/asrs/task/handler/WorkOutErpReportHandler.java   |  281 +++++++++++++++++
 11 files changed, 953 insertions(+), 0 deletions(-)

diff --git a/skills/build-asrs-solutions/SKILL.md b/skills/build-asrs-solutions/SKILL.md
new file mode 100644
index 0000000..585b981
--- /dev/null
+++ b/skills/build-asrs-solutions/SKILL.md
@@ -0,0 +1,75 @@
+---
+name: build-asrs-solutions
+description: Plan, design, implement, and communicate automated warehouse upper-level systems for ASRS projects, including WMS, WCS, ERP/MES integration, customer-facing requirement clarification, product scoping, interface definition, troubleshooting, and delivery coordination. Use when Codex supports 鑷姩鍖栫珛浣撲粨搴撲笂浣嶆満绯荤粺銆乄MS/WCS 鍔熻兘璁捐銆丒RP/MES/绗笁鏂圭郴缁熷鎺ャ�侀渶姹傛媶瑙c�佹帴鍙f枃妗c�佹祦绋嬫⒊鐞嗐�佸鎴锋矡閫氱邯瑕併�佸紓甯稿垎鏋愭垨椤圭洰浜や粯鎺ㄨ繘銆�
+---
+
+# Build ASRS Solutions
+
+## Overview
+
+浣滀负鑷姩鍖栫珛浣撲粨搴撻」鐩殑鏂规鍨嬪伐绋嬫惌妗o紝鍏堟妸涓氬姟娴併�佽澶囨祦鍜岀郴缁熻竟鐣岃娓咃紝鍐嶈緭鍑哄彲鎵ц鐨勮璁°�佹帴鍙c�佹帓鏈熷拰娌熼�氭潗鏂欍��
+濮嬬粓鎶婅繖涓妧鑳藉綋浣溾�滃叏鏍堝伐绋� + 浜у搧缁忕悊 + 瀹㈡埛闆嗘垚鎺ュ彛浜衡�濈殑宸ヤ綔杈呭姪锛岃�屼笉鏄崟绾殑浠g爜鐢熸垚鍣ㄣ��
+
+## Core Workflow
+
+1. 鍏堝缓绔嬩笂涓嬫枃
+
+- 鏄庣‘椤圭洰闃舵銆佽涓氬満鏅�佸簱鍖�/宸烽亾/宸ヤ綅缁撴瀯銆佽揣鐗╁睘鎬э紝浠ュ強鍏ュ簱銆佸嚭搴撱�佽ˉ璐с�佺Щ搴撱�佺洏鐐圭瓑浣滀笟绫诲瀷銆�
+- 鍒楀嚭鍙備笌绯荤粺鍜岃矗浠绘柟锛欵RP銆丮ES銆乄MS銆乄CS銆丳LC/璁惧灞傘�丳DA銆佹姤琛ㄥ钩鍙般�佺涓夋柟绯荤粺銆�
+- 纭涓绘暟鎹�佽鍗曘�佸簱瀛樸�佷换鍔°�佽澶囩姸鎬佸悇鑷殑 source of truth銆�
+
+2. 鍐嶅垝娓呯郴缁熻竟鐣�
+
+- 鏄庣‘鍝簺瑙勫垯灞炰簬 ERP/MES锛屽摢浜涘睘浜� WMS锛屽摢浜涘睘浜� WCS锛屽摢浜涘睘浜� PLC 鎴栬澶囨帶鍒跺櫒銆�
+- 鍖哄垎涓氬姟瀹屾垚銆佸簱瀛樺畬鎴愩�佷换鍔″畬鎴愩�佽澶囧畬鎴愶紝閬垮厤鎶婁笉鍚屽眰绾х姸鎬佸帇鎴愪竴涓姸鎬佸瓧娈点��
+- 涓烘瘡涓紓姝ユ帴鍙hˉ榻愯Е鍙戞椂鏈恒�佽姹�/鍥炴墽銆侀噸璇曘�佸箓绛夈�佽秴鏃躲�佽ˉ鍋裤�佸璐﹁鍒欍��
+
+3. 鎶婇棶棰樿浆鎹㈡垚浜や粯鐗�
+
+- 褰撻渶姹備笉娓呮櫚鏃讹紝鍏堣緭鍑烘緞娓呴棶棰樸�佽竟鐣屽亣璁俱�侀闄╃偣锛屼笉瑕佺洿鎺ヨ繘鍏ュ疄鐜般��
+- 褰撲换鍔″亸璁捐鏃讹紝杈撳嚭璐d换鐭╅樀銆佹椂搴忓浘銆佺姸鎬佹満銆佹帴鍙e崗璁�佸瓧娈垫槧灏勩�佹暟鎹ā鍨嬫垨 API 鑽夋銆�
+- 褰撲换鍔″亸鎵ц鏃讹紝杈撳嚭寮�鍙戞媶鍒嗐�佽仈璋冭鍒掋�佹祴璇曡寖鍥淬�佷笂绾垮垏鎹㈠拰楠屾敹娓呭崟銆�
+
+4. 鐢ㄥ満鏅弽鎺ㄦ柟妗堣川閲�
+
+- 璧伴�氭甯告祦绋嬨�侀珮宄版祦绋嬨�佸紓甯告祦绋嬨�佷汉宸ュ共棰勩�佸け璐ラ噸璇曘�佸洖婊氭仮澶嶃��
+- 妫�鏌ュ簱瀛樺樊寮傘�佽澶囨姤璀︺�佹秷鎭噸澶嶃�佸弽棣堝欢杩熴�佷笂娓搁樆濉炪�佷笅娓镐笉鍙敤鏃舵柟妗堟槸鍚︿粛鎴愮珛銆�
+
+## Working Rules
+
+- 鍏堢粦瀹氬叿浣撳璞★細浠撳簱銆佸簱鍖恒�佽澶囥�佽鍗曠被鍨嬨�佷换鍔$被鍨嬨�佺姸鎬佺爜銆佸紓甯哥爜銆佹帴鍙f柟鍚戙��
+- 浼樺厛鐢ㄨ〃鏍笺�佹椂搴忋�佺姸鎬佽縼绉汇�佽矗浠荤煩闃佃〃杈捐法绯荤粺閫昏緫锛屼笉瑕佸彧缁欐娊璞℃弿杩般��
+- 鎶婂垎鏋愰摼璺浐瀹氬埌涓氬姟鍗曞彿銆佷换鍔″彿銆佽澶囨寚浠ゅ彿銆佹秷鎭祦姘村彿銆�
+- 鍏堟毚闇插亣璁惧拰缂哄け杈撳叆锛屽啀缁欐柟妗堬紱涓嶈鎶婂叧閿笉纭畾鎬ц棌鍦ㄥ疄鐜扮粏鑺傞噷銆�
+- 榛樿杈撳嚭鍙互鐩存帴缁欑爺鍙戙�佹祴璇曘�佸疄鏂姐�佸鎴锋垨绗笁鏂瑰巶鍟嗚瘎瀹$殑鏉愭枡銆�
+
+## Default Output Shapes
+
+- 闇�姹傛緞娓咃細杈撳嚭 `鑳屾櫙/鑼冨洿/杈圭晫/鍏抽敭闂/寰呯‘璁ら」/椋庨櫓`銆�
+- 鎺ュ彛璁捐锛氳緭鍑� `鎺ュ彛鐩爣/璐d换褰掑睘/瑙﹀彂鏃舵満/瀛楁鏄犲皠/骞傜瓑涓庨噸璇�/寮傚父鍥炰紶/楠屾敹鍙e緞`銆�
+- 鏂规璇勫锛氳緭鍑� `鐜扮姸/闂鎷嗚В/鍊欓�夋柟妗�/鍙栬垗/椋庨櫓/鎺ㄨ崘鏂规`銆�
+- 鏁呴殰鍒嗘瀽锛氳緭鍑� `鐜拌薄/褰卞搷鑼冨洿/閾捐矾瀹氫綅/鎬�鐤戠偣/鍙栬瘉姝ラ/涓存椂姝㈣/姘镐箙淇/闃插洖褰掓帾鏂絗銆�
+- 瀹㈡埛娌熼�氾細杈撳嚭 `涓氬姟缁撹/绯荤粺杈圭晫/闇�瀵规柟鎻愪緵鍐呭/鎴戞柟鍔ㄤ綔/鏃堕棿鐐�/鏈喅闂`銆�
+
+## Common Deliverables
+
+- 闇�姹傛緞娓呮竻鍗�
+- WMS/WCS/ERP/MES 璐d换鐭╅樀
+- 鎺ュ彛鏂囨。鍜屽瓧娈垫槧灏勮〃
+- 涓氬姟娴佺▼鍥俱�佸紓甯告祦绋嬪浘銆佺姸鎬佹満
+- 鏁版嵁搴撹璁″缓璁�丼QL 鑽夋銆丄PI 鑽夋銆佷簨浠舵ā鍨�
+- 鑱旇皟璁″垝銆佹祴璇曟渚嬨�佷笂绾垮垏鎹㈡竻鍗�
+- 瀹㈡埛浼氳绾銆佽鍔ㄩ」銆佸澶栨緞娓呴偖浠惰崏绋�
+- 鏁呴殰澶嶇洏鍜屾牴鍥犲垎鏋愭姤鍛�
+
+## Default Heuristics
+
+- 鍏堟妸搴撳瓨宸紓鐪嬫垚鈥滃綊灞為敊璇�佹椂搴忛敊璇�佷汉宸ュ共棰勩�佸箓绛夊け鏁堛�佽ˉ鍋跨己澶扁�濋棶棰橈紝鑰屼笉鏄崟绾暟鎹敊銆�
+- 鍏堟妸閲嶅涓嬪彂銆佷换鍔″崱姝汇�佽澶囧弽澶嶆姤璀︾湅鎴愨�淲MS/WCS/璁惧鐘舵�佸垎鍙夆�濋棶棰橈紝鐩村埌璇佹嵁璇佹槑涓嶆槸銆�
+- 涓烘墍鏈夊閮ㄦ帴鍙i粯璁よ璁¢噸璇曘�佸幓閲嶃�佸洖鏌ャ�佸璐﹀拰浜哄伐鍏滃簳銆�
+- 鎶婂紓甯搁摼璺拰浜哄伐鎭㈠璁捐鎴愪骇鍝佽兘鍔涳紝涓嶈褰撲綔涓婄嚎鍚庝复鏃惰ˉ涓併��
+- 榛樿鐢ㄢ�淲MS 鍋忎笟鍔$瓥鐣ワ紝WCS 鍋忔墽琛岀紪鎺掆�濈殑鍘熷垯寤烘ā锛涢」鐩湁鍋忓樊鏃跺啀鏄惧紡鍐欐槑銆�
+
+## Reference
+
+褰撲换鍔¢渶瑕佹洿缁嗙殑璋冪爺鎻愮翰銆佽矗浠荤煩闃垫ā鏉裤�佹帴鍙f鏌ユ竻鍗曘�佹晠闅滄帓鏌ラ『搴忔垨浼氳绾楠ㄦ灦鏃讹紝璇诲彇 [references/asrs-delivery-playbook.md](references/asrs-delivery-playbook.md)銆�
diff --git a/skills/build-asrs-solutions/agents/openai.yaml b/skills/build-asrs-solutions/agents/openai.yaml
new file mode 100644
index 0000000..e25d9f8
--- /dev/null
+++ b/skills/build-asrs-solutions/agents/openai.yaml
@@ -0,0 +1,4 @@
+interface:
+  display_name: "ASRS Solutions"
+  short_description: "WMS/WCS and ERP/MES delivery playbook"
+  default_prompt: "Use $build-asrs-solutions to scope a warehouse-system request, map WMS/WCS and ERP/MES boundaries, and produce an actionable delivery plan."
diff --git a/skills/build-asrs-solutions/references/asrs-delivery-playbook.md b/skills/build-asrs-solutions/references/asrs-delivery-playbook.md
new file mode 100644
index 0000000..c4be6f7
--- /dev/null
+++ b/skills/build-asrs-solutions/references/asrs-delivery-playbook.md
@@ -0,0 +1,92 @@
+# ASRS Delivery Playbook
+
+## Table of Contents
+
+- Context checklist
+- Responsibility matrix template
+- Integration checklist
+- Scenario review set
+- Troubleshooting workflow
+- Communication templates
+
+## Context Checklist
+
+鍦ㄥ紑濮嬭璁℃垨鎺掓煡鍓嶏紝鍏堣ˉ榻愪笅闈㈢殑淇℃伅锛�
+
+- 椤圭洰闃舵锛氬敭鍓嶃�佽摑鍥俱�佸紑鍙戙�佽仈璋冦�佷笂绾裤�佽繍缁淬��
+- 鍦烘櫙杈圭晫锛氭暣搴撱�佸崟搴撳尯銆佸崟璁惧绾裤�佸崟瀹㈡埛銆佸崟宸ュ巶锛岃繕鏄泦鍥㈠鍩哄湴銆�
+- 璐х墿灞炴�э細鎵樼洏銆佹枡绠便�佸懆杞銆佸崟浠讹紝鏄惁鎵规銆佹晥鏈熴�佸簭鍒楀彿绠$悊銆�
+- 浣滀笟绫诲瀷锛氬叆搴撱�佸嚭搴撱�佽ˉ璐с�佺Щ搴撱�佽秺搴撱�佺洏鐐广�佸喕缁撱�佽В鍐汇�佺┖鎵樼鐞嗐��
+- 璁惧缁勬垚锛氬爢鍨涙満銆佺┛姊溅銆佹彁鍗囨満銆佽緭閫佺嚎銆佹満姊拌噦銆丄GV銆丷GV銆佺數瀛愭爣绛俱�佹壂鎻忓櫒銆�
+- 澶栭儴绯荤粺锛欵RP銆丮ES銆丱MS銆丼RM銆丵MS銆佽储鍔$郴缁熴�丅I銆佺涓夋柟浠撻厤骞冲彴銆�
+- 鏍稿績鎸囨爣锛氬悶鍚愩�佸簱瀛樺噯纭巼銆佷换鍔℃椂鏁堛�佹尝娆″畬鎴愮巼銆佽澶囧彲鐢ㄧ巼銆�
+- 褰撳墠闂锛氶渶姹傛柊澧炪�佷笂绾垮欢鏈熴�佸簱瀛樹笉鍑嗐�佷换鍔″崱姝汇�佹帴鍙e弽澶嶅け璐ャ�佸鎴峰彛寰勪笉涓�鑷淬��
+
+## Responsibility Matrix Template
+
+鐢ㄨ繖涓〃鍏堝仛璐d换褰掑睘锛屼笉瑕佺洿鎺ュ啓瀹炵幇銆�
+
+| Domain | ERP/MES | WMS | WCS | Device/PLC | Notes |
+| --- | --- | --- | --- | --- | --- |
+| 涓绘暟鎹� | 涓绘暟鎹潵婧�/瀹℃壒 | 涓绘暟鎹惤鍦板拰鏍¢獙 | 璁惧渚у繀瑕佹槧灏� | 鍙繚鐣欒澶囨墽琛屽繀闇�閰嶇疆 | 鏄庣‘缂栫爜銆佺増鏈�佸悓姝ラ鐜� |
+| 鍗曟嵁/鐢熶骇鎸囦护 | 涓嬪彂涓氬姟闇�姹� | 鎷嗗崟銆佸簱瀛樻牎楠屻�佹尝娆�/绛栫暐 | 鎺ユ敹鎵ц浠诲姟 | 鎺ユ敹鎺у埗鎸囦护 | 鏄庣‘璋佸彲浠ユ挙閿�鍜屾敼娲� |
+| 搴撳瓨鐪熷�� | 璐㈠姟鎴栫敓浜у彛寰� | 浠撳偍鐪熷�� | 缂撳瓨鎵ц鎬� | 涓嶄綔涓洪暱鏈熺湡鍊� | 鏄庣‘宸紓鍥炲啓璺緞 |
+| 浠诲姟鐘舵�� | 鍏虫敞涓氬姟缁撴灉 | 绠$悊涓氬姟浠诲姟鐘舵�� | 绠$悊鎵ц浠诲姟鐘舵�� | 杩斿洖鍔ㄤ綔缁撴灉 | 鍖哄垎瀹屾垚銆佸け璐ャ�佸彇娑堛�佹寕璧� |
+| 寮傚父澶勭悊 | 澶勭悊涓氬姟闃诲 | 澶勭悊搴撳瓨鍜屼笟鍔″紓甯� | 澶勭悊璁惧鍜屾墽琛屽紓甯� | 杈撳嚭鎶ヨ鍜屽弽棣� | 鏄庣‘浜哄伐浠嬪叆鐐� |
+
+## Integration Checklist
+
+姣忎釜鎺ュ彛閮芥鏌ヤ互涓嬮」鐩細
+
+- 鎺ュ彛鏂瑰悜锛氳皝鍙戣捣銆佽皝纭銆佽皝鍥炲啓銆佽皝閲嶈瘯銆�
+- 瑙﹀彂鏃舵満锛氬垱寤恒�佷笅鍙戙�佸紑濮嬨�侀儴鍒嗗畬鎴愩�佸畬鎴愩�佸彇娑堛�佸紓甯搞�佹仮澶嶃��
+- 鍞竴閿細璁㈠崟鍙枫�佽鍙枫�佷换鍔″彿銆佸鍣ㄥ彿銆佽澶囦换鍔″彿銆佹秷鎭祦姘村彿銆�
+- 瀛楁鏄犲皠锛氱紪鐮佷綋绯汇�佸崟浣嶃�佺姸鎬佺爜銆佹椂闂存牸寮忋�佹壒娆�/鏁堟湡/搴忓垪鍙峰彛寰勩��
+- 骞傜瓑鏈哄埗锛氬箓绛夐敭銆侀噸澶嶆姤鏂囪瘑鍒�佸幓閲嶇獥鍙c�侀噸澶嶅洖鎵у鐞嗐��
+- 閲嶈瘯绛栫暐锛氳秴鏃堕槇鍊笺�侀噸璇曟鏁般�侀��閬胯鍒欍�佷汉宸ヨˉ鍙戞柟寮忋��
+- 寮傚父闂幆锛氶敊璇爜銆侀敊璇弿杩般�侀�氱煡瀵硅薄銆佸崌绾ц矾寰勩�佹仮澶嶅姩浣溿��
+- 瀵硅处鏈哄埗锛氭棩鍒囥�佺彮娆°�佸閲忓洖鏌ャ�佸叏閲忔牎楠屻�佽ˉ鍋块�昏緫銆�
+- 瀹¤杩借釜锛氳姹傛棩蹇椼�佸洖鎵ф棩蹇椼�佺姸鎬佽縼绉绘棩蹇椼�佹搷浣滀汉銆佹椂闂存埑銆�
+- 鍒囨崲绛栫暐锛氬瓨閲忔暟鎹垵濮嬪寲銆佺伆搴﹁寖鍥淬�佸洖婊氭潯浠躲�佸弻鍐欐垨鍐荤粨绐楀彛銆�
+
+## Scenario Review Set
+
+鏂规鑷冲皯瑕嗙洊涓嬮潰杩欎簺鍦烘櫙锛�
+
+- 姝e父鍏ュ簱锛氶绾﹀埌璐с�佹敹璐с�佽川妫�銆佷笂鏋躲�佸簱瀛樼‘璁ゃ��
+- 姝e父鍑哄簱锛氭尝娆°�佸垎閰嶃�佹嫞閫夈�佸嚭搴撱�佽杞︺�佸洖鍐欍��
+- 搴撳瓨璋冩暣锛氱洏鐐瑰樊寮傘�佸喕缁撱�佽В鍐汇�佹姤鎹熴�佹姤婧€��
+- 浜哄伐骞查锛氬己鍒跺畬鎴愩�佸彇娑堜换鍔°�佹敼搴撲綅銆佹敼瀹瑰櫒銆佺嚎杈硅ˉ褰曘��
+- 寮傚父鎭㈠锛氳澶囨姤璀﹀悗閲嶄笅銆佷换鍔℃媶鍒嗐�佷换鍔″悎骞躲�佹柇鐐圭画浼犮��
+- 璺ㄧ郴缁熷け璐ワ細ERP 宸插洖鍐欎絾 WMS 鏈畬鎴愩�乄MS 宸插畬鎴愪絾 WCS 鏈洖鎵с�佽澶囧凡鍔ㄤ綔浣嗙姸鎬佹湭鍚屾銆�
+
+## Troubleshooting Workflow
+
+鎺掓煡鏃舵寜杩欎釜椤哄簭鎺ㄨ繘锛�
+
+1. 閿佸畾涓氬姟涓婚敭锛氳鍗曞彿銆佷换鍔″彿銆佸鍣ㄥ彿銆佽澶囨寚浠ゅ彿銆�
+2. 鎷夐綈鏃堕棿绾匡細ERP/MES銆乄MS銆乄CS銆佽澶囨棩蹇楃殑鏃堕棿鎴炽��
+3. 鎵炬渶鍚庝竴娆′竴鑷寸姸鎬侊紝纭闂鍙戠敓鍦ㄢ�滀笅鍙戝墠銆佹帴鍙d腑銆佹墽琛屼腑銆佸洖鍐欎腑鈥濈殑鍝竴娈点��
+4. 妫�鏌ユ槸鍚︽湁閲嶅娑堟伅銆佹紡鍥炴墽銆佷汉宸ユ敼鏁般�佹椂閽熸紓绉汇�佺姸鎬佸洖婊氬け璐ャ��
+5. 鍖哄垎涓存椂姝㈣鍜屾案涔呬慨澶嶏細鍏堟仮澶嶄綔涓氾紝鍐嶈ˉ鐩戞帶銆佽ˉ鏍¢獙銆佽ˉ琛ュ伩鏈哄埗銆�
+6. 杈撳嚭鍙獙璇佺粨璁猴細鐜拌薄銆佽瘉鎹�佹牴鍥犲亣璁俱�侀獙璇佺粨鏋溿�佷慨澶嶅姩浣溿�侀闃叉帾鏂姐��
+
+## Communication Templates
+
+### Requirement Clarification
+
+- 涓氬姟鐩爣鏄粈涔堬紝鎴愬姛鍙e緞鏄粈涔堛��
+- 鍝釜绯荤粺鍏堝彂璧凤紝鍝釜绯荤粺浣滀负鏈�缁堢‘璁ゅ彛寰勩��
+- 鍝簺鐘舵�侀渶瑕佸洖浼狅紝鍝簺鐘舵�佸彧鍦ㄦ湰绯荤粺鍐呴儴鍙銆�
+- 寮傚父鏃剁敱璋佸鐞嗭紝鍏佽鍝簺浜哄伐鎿嶄綔銆�
+- 瀹㈡埛蹇呴』鎻愪緵鍝簺涓绘暟鎹�佺紪鐮佽鍒欍�佹帴鍙f牱渚嬨�佹祴璇曠幆澧冦�侀獙鏀舵渚嬨��
+
+### Meeting Summary
+
+- 鑳屾櫙鍜岀洰鏍�
+- 宸茬‘璁よ寖鍥�
+- 鏈‘璁よ寖鍥�
+- 绯荤粺杈圭晫缁撹
+- 鍙屾柟琛屽姩椤�
+- 椋庨櫓鍜岄樆濉為」
+- 涓嬫妫�鏌ョ偣
diff --git a/src/main/java/com/zy/api/controller/params/StopOutTaskParams.java b/src/main/java/com/zy/api/controller/params/StopOutTaskParams.java
new file mode 100644
index 0000000..8c263e3
--- /dev/null
+++ b/src/main/java/com/zy/api/controller/params/StopOutTaskParams.java
@@ -0,0 +1,40 @@
+package com.zy.api.controller.params;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@ApiModel(value = "StopOutTaskParams", description = "pause out task params")
+public class StopOutTaskParams implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("order no")
+    private String orderNo;
+
+    @ApiModelProperty("pause reason")
+    private String reason;
+
+    @ApiModelProperty("task list")
+    private List<TaskItem> tasks = new ArrayList<>();
+
+    @Data
+    public static class TaskItem implements Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        @ApiModelProperty("task no")
+        private String taskNo;
+
+        @ApiModelProperty("station no")
+        private String staNo;
+
+        @ApiModelProperty("loc no")
+        private String locNo;
+    }
+}
diff --git a/src/main/java/com/zy/asrs/entity/param/ErpPakinReportParam.java b/src/main/java/com/zy/asrs/entity/param/ErpPakinReportParam.java
new file mode 100644
index 0000000..280c523
--- /dev/null
+++ b/src/main/java/com/zy/asrs/entity/param/ErpPakinReportParam.java
@@ -0,0 +1,21 @@
+package com.zy.asrs.entity.param;
+
+import lombok.Data;
+
+@Data
+public class ErpPakinReportParam {
+
+    private String palletId;
+
+    private Double anfme;
+
+    private String locId;
+
+    private Double weight;
+
+    private String createTime;
+
+    private String BizNo;
+
+    private String startTime;
+}
diff --git a/src/main/java/com/zy/asrs/entity/param/ErpPakoutReportParam.java b/src/main/java/com/zy/asrs/entity/param/ErpPakoutReportParam.java
new file mode 100644
index 0000000..79c5610
--- /dev/null
+++ b/src/main/java/com/zy/asrs/entity/param/ErpPakoutReportParam.java
@@ -0,0 +1,13 @@
+package com.zy.asrs.entity.param;
+
+import lombok.Data;
+
+@Data
+public class ErpPakoutReportParam {
+
+    private String palletId;
+
+    private String createTime;
+
+    private String startTime;
+}
diff --git a/src/main/java/com/zy/asrs/entity/param/OpenOrderPakoutPauseParam.java b/src/main/java/com/zy/asrs/entity/param/OpenOrderPakoutPauseParam.java
new file mode 100644
index 0000000..7e45467
--- /dev/null
+++ b/src/main/java/com/zy/asrs/entity/param/OpenOrderPakoutPauseParam.java
@@ -0,0 +1,11 @@
+package com.zy.asrs.entity.param;
+
+import lombok.Data;
+
+@Data
+public class OpenOrderPakoutPauseParam {
+
+    private String orderNo;
+
+    private String reason;
+}
diff --git a/src/main/java/com/zy/asrs/task/WorkErpReportScheduler.java b/src/main/java/com/zy/asrs/task/WorkErpReportScheduler.java
new file mode 100644
index 0000000..6caaf66
--- /dev/null
+++ b/src/main/java/com/zy/asrs/task/WorkErpReportScheduler.java
@@ -0,0 +1,41 @@
+package com.zy.asrs.task;
+
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.zy.asrs.entity.WrkMast;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.asrs.task.core.ReturnT;
+import com.zy.asrs.task.handler.WorkErpReportHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Slf4j
+@Component
+public class WorkErpReportScheduler {
+
+    @Autowired
+    private WrkMastService wrkMastService;
+
+    @Autowired
+    private WorkErpReportHandler workErpReportHandler;
+
+    @Scheduled(cron = "0/10 * * * * ? ")
+    private void execute() {
+        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
+                .eq("wrk_sts", WorkErpReportHandler.ERP_REPORT_PENDING_WRK_STS)
+                .orderBy("io_time", true)
+                .orderBy("wrk_no", true));
+        if (wrkMasts.isEmpty()) {
+            return;
+        }
+        for (WrkMast wrkMast : wrkMasts) {
+            ReturnT<String> result = workErpReportHandler.start(wrkMast);
+            if (!result.isSuccess()) {
+                log.error("workNo={} erp report failed: {}", wrkMast.getWrkNo(), result.getMsg());
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/zy/asrs/task/WorkOutErpReportScheduler.java b/src/main/java/com/zy/asrs/task/WorkOutErpReportScheduler.java
new file mode 100644
index 0000000..d822a99
--- /dev/null
+++ b/src/main/java/com/zy/asrs/task/WorkOutErpReportScheduler.java
@@ -0,0 +1,41 @@
+package com.zy.asrs.task;
+
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.zy.asrs.entity.WrkMast;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.asrs.task.core.ReturnT;
+import com.zy.asrs.task.handler.WorkOutErpReportHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Slf4j
+@Component
+public class WorkOutErpReportScheduler {
+
+    @Autowired
+    private WrkMastService wrkMastService;
+
+    @Autowired
+    private WorkOutErpReportHandler workOutErpReportHandler;
+
+    @Scheduled(cron = "0/10 * * * * ? ")
+    private void execute() {
+        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>()
+                .eq("wrk_sts", WorkOutErpReportHandler.ERP_REPORT_PENDING_WRK_STS)
+                .orderBy("io_time", true)
+                .orderBy("wrk_no", true));
+        if (wrkMasts.isEmpty()) {
+            return;
+        }
+        for (WrkMast wrkMast : wrkMasts) {
+            ReturnT<String> result = workOutErpReportHandler.start(wrkMast);
+            if (!result.isSuccess()) {
+                log.error("workNo={} outbound erp report failed: {}", wrkMast.getWrkNo(), result.getMsg());
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/zy/asrs/task/handler/WorkErpReportHandler.java b/src/main/java/com/zy/asrs/task/handler/WorkErpReportHandler.java
new file mode 100644
index 0000000..87043a6
--- /dev/null
+++ b/src/main/java/com/zy/asrs/task/handler/WorkErpReportHandler.java
@@ -0,0 +1,334 @@
+package com.zy.asrs.task.handler;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.mapper.EntityWrapper;
+import com.core.common.Cools;
+import com.zy.asrs.entity.WaitPakin;
+import com.zy.asrs.entity.WrkDetl;
+import com.zy.asrs.entity.WrkMast;
+import com.zy.asrs.entity.param.ErpPakinReportParam;
+import com.zy.asrs.service.ApiLogService;
+import com.zy.asrs.service.WaitPakinService;
+import com.zy.asrs.service.WrkDetlService;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.asrs.task.AbstractHandler;
+import com.zy.asrs.task.core.ReturnT;
+import com.zy.common.entity.Parameter;
+import com.zy.common.utils.HttpHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+@Slf4j
+@Service
+public class WorkErpReportHandler extends AbstractHandler<String> {
+
+    public static final long ERP_REPORT_PENDING_WRK_STS = 16L;
+    public static final long ERP_REPORT_FINISHED_WRK_STS = 5L;
+    public static final int ERP_REPORT_MAX_RETRY_TIMES = 3;
+    public static final String ERP_REPORT_PENDING_FLAG = "P";
+    public static final String ERP_REPORT_SUCCESS_FLAG = "Y";
+    public static final String ERP_REPORT_FAIL_FLAG = "F";
+
+    private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+    @Autowired
+    private WrkMastService wrkMastService;
+    @Autowired
+    private WrkDetlService wrkDetlService;
+    @Autowired
+    private WaitPakinService waitPakinService;
+    @Autowired
+    private ApiLogService apiLogService;
+
+    @Value("${erp.switch.ErpReportOld}")
+    private boolean erpReportOld;
+
+    @Value("${erp.address.URL}")
+    private String erpUrl;
+
+    @Value("${erp.address.Inaddress}")
+    private String erpInAddress;
+
+    @Transactional(rollbackFor = Exception.class)
+    public ReturnT<String> start(WrkMast source) {
+        WrkMast wrkMast = wrkMastService.selectById(source.getWrkNo());
+        if (wrkMast == null || !Long.valueOf(ERP_REPORT_PENDING_WRK_STS).equals(wrkMast.getWrkSts())) {
+            return SUCCESS;
+        }
+
+        if (!isErpReportEnabled()) {
+            finishReport(wrkMast, false, getRetryTimes(wrkMast), "ERP reporting is disabled", false);
+            return FAIL.setMsg("ERP reporting is disabled");
+        }
+
+        List<WrkDetl> wrkDetls = wrkDetlService.selectByWrkNo(wrkMast.getWrkNo());
+        if (Cools.isEmpty(wrkDetls)) {
+            finishReport(wrkMast, false, getRetryTimes(wrkMast), "work details are empty", false);
+            return FAIL.setMsg("work details are empty");
+        }
+
+        WaitPakin waitPakin = findWaitPakin(wrkMast.getBarcode());
+        ErpPakinReportParam param = buildParam(wrkMast, wrkDetls, waitPakin);
+        String request = JSON.toJSONString(param);
+        String response = "";
+        boolean success = false;
+        String errorMsg = null;
+        String requestUrl = buildRequestUrl();
+
+        try {
+            response = new HttpHandler.Builder()
+                    .setUri(erpUrl)
+                    .setPath(erpInAddress)
+                    .setJson(request)
+                    .build()
+                    .doPost();
+            success = isErpReportSuccess(response);
+            if (!success) {
+                errorMsg = extractErrorMsg(response);
+            }
+            finishReport(wrkMast, success, getRetryTimes(wrkMast), errorMsg, true);
+        } catch (Exception e) {
+            errorMsg = e.getMessage();
+            finishReport(wrkMast, false, getRetryTimes(wrkMast), errorMsg, true);
+        } finally {
+            try {
+                apiLogService.save(
+                        "Inbound ERP Report",
+                        requestUrl,
+                        null,
+                        "127.0.0.1",
+                        request,
+                        response,
+                        success,
+                        "workNo=" + wrkMast.getWrkNo()
+                );
+            } catch (Exception logEx) {
+                log.error("save erp api log failed", logEx);
+            }
+        }
+
+        if (success) {
+            return SUCCESS;
+        }
+        return FAIL.setMsg(errorMsg);
+    }
+
+    private boolean isErpReportEnabled() {
+        if (!erpReportOld) {
+            return false;
+        }
+        String erpReport = Parameter.get().getErpReport();
+        return Cools.isEmpty(erpReport) || "true".equalsIgnoreCase(erpReport);
+    }
+
+    private WaitPakin findWaitPakin(String barcode) {
+        if (Cools.isEmpty(barcode)) {
+            return null;
+        }
+        return waitPakinService.selectOne(new EntityWrapper<WaitPakin>().eq("zpallet", barcode));
+    }
+
+    private ErpPakinReportParam buildParam(WrkMast wrkMast, List<WrkDetl> wrkDetls, WaitPakin waitPakin) {
+        ErpPakinReportParam param = new ErpPakinReportParam();
+        param.setPalletId(resolvePalletId(wrkMast, wrkDetls));
+        param.setAnfme(sumAnfme(wrkDetls));
+        param.setLocId(wrkMast.getLocNo());
+        param.setWeight(sumWeight(wrkDetls));
+        param.setCreateTime(formatDate(resolveCreateTime(wrkMast)));
+        param.setBizNo(resolveBizNo(wrkDetls, waitPakin));
+        param.setStartTime(formatDate(resolveStartTime(wrkMast, waitPakin)));
+        return param;
+    }
+
+    private String resolvePalletId(WrkMast wrkMast, List<WrkDetl> wrkDetls) {
+        if (!Cools.isEmpty(wrkMast.getBarcode())) {
+            return wrkMast.getBarcode();
+        }
+        for (WrkDetl wrkDetl : wrkDetls) {
+            if (!Cools.isEmpty(wrkDetl.getZpallet())) {
+                return wrkDetl.getZpallet();
+            }
+        }
+        return null;
+    }
+
+    private Double sumAnfme(List<WrkDetl> wrkDetls) {
+        double total = 0D;
+        for (WrkDetl wrkDetl : wrkDetls) {
+            if (!Cools.isEmpty(wrkDetl.getAnfme())) {
+                total += wrkDetl.getAnfme();
+            }
+        }
+        return total;
+    }
+
+    private Double sumWeight(List<WrkDetl> wrkDetls) {
+        double total = 0D;
+        for (WrkDetl wrkDetl : wrkDetls) {
+            if (Cools.isEmpty(wrkDetl.getWeight())) {
+                continue;
+            }
+            double qty = Cools.isEmpty(wrkDetl.getAnfme()) ? 1D : wrkDetl.getAnfme();
+            total += wrkDetl.getWeight() * qty;
+        }
+        return total;
+    }
+
+    private Date resolveCreateTime(WrkMast wrkMast) {
+        if (!Cools.isEmpty(wrkMast.getCrnEndTime())) {
+            return wrkMast.getCrnEndTime();
+        }
+        if (!Cools.isEmpty(wrkMast.getModiTime())) {
+            return wrkMast.getModiTime();
+        }
+        if (!Cools.isEmpty(wrkMast.getIoTime())) {
+            return wrkMast.getIoTime();
+        }
+        return new Date();
+    }
+
+    private String resolveBizNo(List<WrkDetl> wrkDetls, WaitPakin waitPakin) {
+        for (WrkDetl wrkDetl : wrkDetls) {
+            if (!Cools.isEmpty(wrkDetl.getThreeCode())) {
+                return wrkDetl.getThreeCode();
+            }
+        }
+        return waitPakin == null ? null : waitPakin.getThreeCode();
+    }
+
+    private Date resolveStartTime(WrkMast wrkMast, WaitPakin waitPakin) {
+        if (waitPakin != null && !Cools.isEmpty(waitPakin.getAppeTime())) {
+            return waitPakin.getAppeTime();
+        }
+        if (!Cools.isEmpty(wrkMast.getAppeTime())) {
+            return wrkMast.getAppeTime();
+        }
+        if (!Cools.isEmpty(wrkMast.getIoTime())) {
+            return wrkMast.getIoTime();
+        }
+        return new Date();
+    }
+
+    private String formatDate(Date date) {
+        if (date == null) {
+            return null;
+        }
+        return new SimpleDateFormat(DATE_TIME_PATTERN).format(date);
+    }
+
+    private boolean isErpReportSuccess(String response) {
+        if (Cools.isEmpty(response)) {
+            return false;
+        }
+        try {
+            JSONObject jsonObject = JSON.parseObject(response);
+            if (jsonObject == null) {
+                return false;
+            }
+            Boolean success = jsonObject.getBoolean("success");
+            if (success != null) {
+                return success;
+            }
+            Integer code = jsonObject.getInteger("code");
+            if (code != null) {
+                return code == 200 || code == 0;
+            }
+            Integer status = jsonObject.getInteger("status");
+            if (status != null) {
+                return status == 200 || status == 0;
+            }
+            String result = jsonObject.getString("result");
+            if (!Cools.isEmpty(result)) {
+                return "success".equalsIgnoreCase(result) || "ok".equalsIgnoreCase(result) || "true".equalsIgnoreCase(result);
+            }
+        } catch (Exception ignore) {
+            return response.toLowerCase().contains("success") && response.toLowerCase().contains("true");
+        }
+        return false;
+    }
+
+    private String extractErrorMsg(String response) {
+        if (Cools.isEmpty(response)) {
+            return "empty erp response";
+        }
+        try {
+            JSONObject jsonObject = JSON.parseObject(response);
+            if (jsonObject == null) {
+                return response;
+            }
+            String[] keys = new String[]{"msg", "message", "error", "errMsg"};
+            for (String key : keys) {
+                String value = jsonObject.getString(key);
+                if (!Cools.isEmpty(value)) {
+                    return value;
+                }
+            }
+        } catch (Exception ignore) {
+        }
+        return response;
+    }
+
+    private int getRetryTimes(WrkMast wrkMast) {
+        if (wrkMast.getExpTime() == null) {
+            return 0;
+        }
+        return wrkMast.getExpTime().intValue();
+    }
+
+    private void finishReport(WrkMast wrkMast, boolean success, int currentRetryTimes, String errorMsg, boolean countCurrentAttempt) {
+        int retryTimes = currentRetryTimes + (countCurrentAttempt ? 1 : 0);
+        Date now = new Date();
+        wrkMast.setExpTime((double) retryTimes);
+        wrkMast.setModiTime(now);
+        if (success) {
+            wrkMast.setWrkSts(ERP_REPORT_FINISHED_WRK_STS);
+            wrkMast.setLogMk(ERP_REPORT_SUCCESS_FLAG);
+            wrkMast.setLogErrMemo(null);
+            wrkMast.setLogErrTime(null);
+        } else {
+            wrkMast.setLogErrMemo(truncate(errorMsg, 500));
+            wrkMast.setLogErrTime(now);
+            if (retryTimes >= ERP_REPORT_MAX_RETRY_TIMES || !countCurrentAttempt) {
+                wrkMast.setWrkSts(ERP_REPORT_FINISHED_WRK_STS);
+                wrkMast.setLogMk(ERP_REPORT_FAIL_FLAG);
+            } else {
+                wrkMast.setWrkSts(ERP_REPORT_PENDING_WRK_STS);
+                wrkMast.setLogMk(ERP_REPORT_PENDING_FLAG);
+            }
+        }
+        if (!wrkMastService.updateById(wrkMast)) {
+            throw new IllegalStateException("update erp report status failed, workNo=" + wrkMast.getWrkNo());
+        }
+    }
+
+    private String truncate(String message, int maxLength) {
+        if (message == null || message.length() <= maxLength) {
+            return message;
+        }
+        return message.substring(0, maxLength);
+    }
+
+    private String buildRequestUrl() {
+        if (Cools.isEmpty(erpUrl)) {
+            return erpInAddress;
+        }
+        if (erpInAddress == null) {
+            return erpUrl;
+        }
+        if (erpUrl.endsWith("/") && erpInAddress.startsWith("/")) {
+            return erpUrl + erpInAddress.substring(1);
+        }
+        if (!erpUrl.endsWith("/") && !erpInAddress.startsWith("/")) {
+            return erpUrl + "/" + erpInAddress;
+        }
+        return erpUrl + erpInAddress;
+    }
+}
diff --git a/src/main/java/com/zy/asrs/task/handler/WorkOutErpReportHandler.java b/src/main/java/com/zy/asrs/task/handler/WorkOutErpReportHandler.java
new file mode 100644
index 0000000..433efdb
--- /dev/null
+++ b/src/main/java/com/zy/asrs/task/handler/WorkOutErpReportHandler.java
@@ -0,0 +1,281 @@
+package com.zy.asrs.task.handler;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.core.common.Cools;
+import com.zy.asrs.entity.WrkDetl;
+import com.zy.asrs.entity.WrkMast;
+import com.zy.asrs.entity.param.ErpPakoutReportParam;
+import com.zy.asrs.service.ApiLogService;
+import com.zy.asrs.service.WrkDetlService;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.asrs.task.AbstractHandler;
+import com.zy.asrs.task.core.ReturnT;
+import com.zy.common.entity.Parameter;
+import com.zy.common.utils.HttpHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+@Slf4j
+@Service
+public class WorkOutErpReportHandler extends AbstractHandler<String> {
+
+    public static final long ERP_REPORT_PENDING_WRK_STS = 17L;
+    public static final long ERP_REPORT_FINISHED_WRK_STS = 15L;
+    public static final int ERP_REPORT_MAX_RETRY_TIMES = 3;
+    public static final String ERP_REPORT_PENDING_FLAG = "P";
+    public static final String ERP_REPORT_SUCCESS_FLAG = "Y";
+    public static final String ERP_REPORT_FAIL_FLAG = "F";
+
+    private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+    @Autowired
+    private WrkMastService wrkMastService;
+    @Autowired
+    private WrkDetlService wrkDetlService;
+    @Autowired
+    private ApiLogService apiLogService;
+
+    @Value("${erp.switch.ErpReportOld}")
+    private boolean erpReportOld;
+
+    @Value("${erp.address.URL}")
+    private String erpUrl;
+
+    @Value("${erp.address.Outaddress}")
+    private String erpOutAddress;
+
+    @Transactional(rollbackFor = Exception.class)
+    public ReturnT<String> start(WrkMast source) {
+        WrkMast wrkMast = wrkMastService.selectById(source.getWrkNo());
+        if (wrkMast == null || !Long.valueOf(ERP_REPORT_PENDING_WRK_STS).equals(wrkMast.getWrkSts())) {
+            return SUCCESS;
+        }
+
+        if (!isErpReportEnabled()) {
+            finishReport(wrkMast, false, getRetryTimes(wrkMast), "ERP reporting is disabled", false);
+            return FAIL.setMsg("ERP reporting is disabled");
+        }
+
+        ErpPakoutReportParam param = buildParam(wrkMast);
+        String request = JSON.toJSONString(param);
+        String response = "";
+        boolean success = false;
+        String errorMsg = null;
+        String requestUrl = buildRequestUrl();
+
+        try {
+            response = new HttpHandler.Builder()
+                    .setUri(erpUrl)
+                    .setPath(erpOutAddress)
+                    .setJson(request)
+                    .build()
+                    .doPost();
+            success = isErpReportSuccess(response);
+            if (!success) {
+                errorMsg = extractErrorMsg(response);
+            }
+            finishReport(wrkMast, success, getRetryTimes(wrkMast), errorMsg, true);
+        } catch (Exception e) {
+            errorMsg = e.getMessage();
+            finishReport(wrkMast, false, getRetryTimes(wrkMast), errorMsg, true);
+        } finally {
+            try {
+                apiLogService.save(
+                        "Outbound ERP Report",
+                        requestUrl,
+                        null,
+                        "127.0.0.1",
+                        request,
+                        response,
+                        success,
+                        "workNo=" + wrkMast.getWrkNo()
+                );
+            } catch (Exception logEx) {
+                log.error("save outbound erp api log failed", logEx);
+            }
+        }
+
+        if (success) {
+            return SUCCESS;
+        }
+        return FAIL.setMsg(errorMsg);
+    }
+
+    private boolean isErpReportEnabled() {
+        if (!erpReportOld) {
+            return false;
+        }
+        String erpReport = Parameter.get().getErpReport();
+        return Cools.isEmpty(erpReport) || "true".equalsIgnoreCase(erpReport);
+    }
+
+    private ErpPakoutReportParam buildParam(WrkMast wrkMast) {
+        ErpPakoutReportParam param = new ErpPakoutReportParam();
+        param.setPalletId(resolvePalletId(wrkMast));
+        param.setCreateTime(formatDate(resolveCreateTime(wrkMast)));
+        param.setStartTime(formatDate(resolveStartTime(wrkMast)));
+        return param;
+    }
+
+    private String resolvePalletId(WrkMast wrkMast) {
+        if (!Cools.isEmpty(wrkMast.getBarcode())) {
+            return wrkMast.getBarcode();
+        }
+        List<WrkDetl> wrkDetls = wrkDetlService.selectByWrkNo(wrkMast.getWrkNo());
+        for (WrkDetl wrkDetl : wrkDetls) {
+            if (!Cools.isEmpty(wrkDetl.getZpallet())) {
+                return wrkDetl.getZpallet();
+            }
+        }
+        return null;
+    }
+
+    private Date resolveCreateTime(WrkMast wrkMast) {
+        if (!Cools.isEmpty(wrkMast.getCrnEndTime())) {
+            return wrkMast.getCrnEndTime();
+        }
+        if (!Cools.isEmpty(wrkMast.getModiTime())) {
+            return wrkMast.getModiTime();
+        }
+        if (!Cools.isEmpty(wrkMast.getIoTime())) {
+            return wrkMast.getIoTime();
+        }
+        return new Date();
+    }
+
+    private Date resolveStartTime(WrkMast wrkMast) {
+        if (!Cools.isEmpty(wrkMast.getCrnStrTime())) {
+            return wrkMast.getCrnStrTime();
+        }
+        if (!Cools.isEmpty(wrkMast.getPlcStrTime())) {
+            return wrkMast.getPlcStrTime();
+        }
+        if (!Cools.isEmpty(wrkMast.getIoTime())) {
+            return wrkMast.getIoTime();
+        }
+        return new Date();
+    }
+
+    private String formatDate(Date date) {
+        if (date == null) {
+            return null;
+        }
+        return new SimpleDateFormat(DATE_TIME_PATTERN).format(date);
+    }
+
+    private boolean isErpReportSuccess(String response) {
+        if (Cools.isEmpty(response)) {
+            return false;
+        }
+        try {
+            JSONObject jsonObject = JSON.parseObject(response);
+            if (jsonObject == null) {
+                return false;
+            }
+            Boolean success = jsonObject.getBoolean("success");
+            if (success != null) {
+                return success;
+            }
+            Integer code = jsonObject.getInteger("code");
+            if (code != null) {
+                return code == 200 || code == 0;
+            }
+            Integer status = jsonObject.getInteger("status");
+            if (status != null) {
+                return status == 200 || status == 0;
+            }
+            String result = jsonObject.getString("result");
+            if (!Cools.isEmpty(result)) {
+                return "success".equalsIgnoreCase(result) || "ok".equalsIgnoreCase(result) || "true".equalsIgnoreCase(result);
+            }
+        } catch (Exception ignore) {
+            return response.toLowerCase().contains("success") && response.toLowerCase().contains("true");
+        }
+        return false;
+    }
+
+    private String extractErrorMsg(String response) {
+        if (Cools.isEmpty(response)) {
+            return "empty erp response";
+        }
+        try {
+            JSONObject jsonObject = JSON.parseObject(response);
+            if (jsonObject == null) {
+                return response;
+            }
+            String[] keys = new String[]{"msg", "message", "error", "errMsg"};
+            for (String key : keys) {
+                String value = jsonObject.getString(key);
+                if (!Cools.isEmpty(value)) {
+                    return value;
+                }
+            }
+        } catch (Exception ignore) {
+        }
+        return response;
+    }
+
+    private int getRetryTimes(WrkMast wrkMast) {
+        if (wrkMast.getExpTime() == null) {
+            return 0;
+        }
+        return wrkMast.getExpTime().intValue();
+    }
+
+    private void finishReport(WrkMast wrkMast, boolean success, int currentRetryTimes, String errorMsg, boolean countCurrentAttempt) {
+        int retryTimes = currentRetryTimes + (countCurrentAttempt ? 1 : 0);
+        Date now = new Date();
+        wrkMast.setExpTime((double) retryTimes);
+        wrkMast.setModiTime(now);
+        if (success) {
+            wrkMast.setWrkSts(ERP_REPORT_FINISHED_WRK_STS);
+            wrkMast.setLogMk(ERP_REPORT_SUCCESS_FLAG);
+            wrkMast.setLogErrMemo(null);
+            wrkMast.setLogErrTime(null);
+        } else {
+            wrkMast.setLogErrMemo(truncate(errorMsg, 500));
+            wrkMast.setLogErrTime(now);
+            if (retryTimes >= ERP_REPORT_MAX_RETRY_TIMES || !countCurrentAttempt) {
+                wrkMast.setWrkSts(ERP_REPORT_FINISHED_WRK_STS);
+                wrkMast.setLogMk(ERP_REPORT_FAIL_FLAG);
+            } else {
+                wrkMast.setWrkSts(ERP_REPORT_PENDING_WRK_STS);
+                wrkMast.setLogMk(ERP_REPORT_PENDING_FLAG);
+            }
+        }
+        if (!wrkMastService.updateById(wrkMast)) {
+            throw new IllegalStateException("update outbound erp report status failed, workNo=" + wrkMast.getWrkNo());
+        }
+    }
+
+    private String truncate(String message, int maxLength) {
+        if (message == null || message.length() <= maxLength) {
+            return message;
+        }
+        return message.substring(0, maxLength);
+    }
+
+    private String buildRequestUrl() {
+        if (Cools.isEmpty(erpUrl)) {
+            return erpOutAddress;
+        }
+        if (erpOutAddress == null) {
+            return erpUrl;
+        }
+        if (erpUrl.endsWith("/") && erpOutAddress.startsWith("/")) {
+            return erpUrl + erpOutAddress.substring(1);
+        }
+        if (!erpUrl.endsWith("/") && !erpOutAddress.startsWith("/")) {
+            return erpUrl + "/" + erpOutAddress;
+        }
+        return erpUrl + erpOutAddress;
+    }
+}

--
Gitblit v1.9.1