liuhairui 2 週間 前
コミット
8f0cb8255f
28 ファイル変更400 行追加212 行削除
  1. 142 80
      application/admin/controller/Procuremen.php
  2. 1 1
      application/admin/controller/Procuremenarchive.php
  3. 40 19
      application/admin/controller/Procuremenexport.php
  4. 5 5
      application/admin/controller/Procuremenmenu.php
  5. 1 1
      application/admin/controller/Procuremensms.php
  6. 1 1
      application/admin/controller/Purchaseemail.php
  7. 7 7
      application/admin/model/Purchasesmstemplate.php
  8. 1 1
      application/admin/view/index/login.html
  9. 1 1
      application/admin/view/index/生产经营驾驶舱系统登录页面.html
  10. 1 1
      application/admin/view/procuremen/add.html
  11. 1 1
      application/admin/view/procuremen/index.html
  12. 1 1
      application/admin/view/procuremenarchive/index.html
  13. 2 2
      application/admin/view/procuremenexport/index.html
  14. 2 2
      application/api/controller/Procuremen.php
  15. 1 1
      application/config.php
  16. 1 1
      application/extra/mproc.php
  17. 1 1
      application/extra/procuremen_drop_optional_columns.sql
  18. 8 8
      application/extra/procuremen_tables.sql
  19. 4 4
      application/extra/procuremen_workflow.sql
  20. 1 1
      application/extra/public_entry.php
  21. 2 2
      application/extra/purchase_email.sql
  22. 4 4
      application/extra/purchase_sms_template_split.sql
  23. 88 40
      application/index/controller/Index.php
  24. 70 13
      application/index/view/index/index.html
  25. 1 1
      application/index/view/index/login.html
  26. 3 3
      public/assets/css/backend.css
  27. 9 9
      public/assets/js/backend/procuremen.js
  28. 1 1
      public/assets/js/backend/procuremenarchive.js

+ 142 - 80
application/admin/controller/Procuremen.php

@@ -55,7 +55,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 手机端外发明细首页直达链接(登录后打开对应明细,免搜索)
+     * 手机端协助明细首页直达链接(登录后打开对应明细,免搜索)
      * 配置 application/extra/mproc.php:mobile_base_url、mobile_index_path
      * 配置 application/extra/mproc.php:mobile_base_url、mobile_index_path
      *
      *
      * @param int $purchaseOrderDetailId purchase_order_detail 主键
      * @param int $purchaseOrderDetailId purchase_order_detail 主键
@@ -115,7 +115,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 解析用于外发邮件/短信的手机端站点根
+     * 解析用于协助邮件/短信的手机端站点根
      */
      */
     protected function resolveMprocMobilePublicBaseUrl()
     protected function resolveMprocMobilePublicBaseUrl()
     {
     {
@@ -145,7 +145,7 @@ class Procuremen extends Backend
             return $reqBase;
             return $reqBase;
         }
         }
 
 
-        Log::write('外发邮件手机链接:配置地址失效', 'warning');
+        Log::write('协助邮件手机链接:配置地址失效', 'warning');
         return '';
         return '';
     }
     }
 
 
@@ -197,7 +197,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 从 ERP 库拉取外发工序池(与 api/procuremen/getprocuremen 一致),并尽力写入 Redis
+     * 从 ERP 库拉取协助工序池(与 api/procuremen/getprocuremen 一致),并尽力写入 Redis
      *
      *
      * @return array<int, array<string, mixed>>
      * @return array<int, array<string, mixed>>
      */
      */
@@ -237,7 +237,7 @@ class Procuremen extends Backend
 
 
             return $list;
             return $list;
         } catch (\Throwable $e) {
         } catch (\Throwable $e) {
-            Log::write('外发列表拉取 ERP 数据失败:' . $e->getMessage(), 'error');
+            Log::write('协助列表拉取 ERP 数据失败:' . $e->getMessage(), 'error');
 
 
             return [];
             return [];
         }
         }
@@ -338,7 +338,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 左侧菜单(外发初选:Redis/ERP 工序按提交日期分组;与列表月份筛选字段一致)
+     * 左侧菜单(协助初选:Redis/ERP 工序按提交日期分组;与列表月份筛选字段一致)
      */
      */
     protected function GetIndexYearMonths()
     protected function GetIndexYearMonths()
     {
     {
@@ -651,12 +651,12 @@ class Procuremen extends Backend
         $this->view->assign('procuremenPageTitle', $pageTitle);
         $this->view->assign('procuremenPageTitle', $pageTitle);
     }
     }
 
 
-    /** 外发下发(选工序+供应商,发短信邮件通知报价) */
+    /** 协助下发(选工序+供应商,发短信邮件通知报价) */
     public function pick()
     public function pick()
     {
     {
         $this->request->filter(['strip_tags', 'trim']);
         $this->request->filter(['strip_tags', 'trim']);
         if (!$this->request->isAjax()) {
         if (!$this->request->isAjax()) {
-            $this->assignProcuremenListPage('pick', '外发下发');
+            $this->assignProcuremenListPage('pick', '协助下发');
             return $this->view->fetch('procuremen/index');
             return $this->view->fetch('procuremen/index');
         }
         }
 
 
@@ -688,14 +688,14 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 列表页(兼容旧菜单 procuremen/index,等同外发下发)
+     * 列表页(兼容旧菜单 procuremen/index,等同协助下发)
      */
      */
     public function index()
     public function index()
     {
     {
         $this->request->filter(['strip_tags', 'trim']);
         $this->request->filter(['strip_tags', 'trim']);
 
 
         if (!$this->request->isAjax()) {
         if (!$this->request->isAjax()) {
-            $this->assignProcuremenListPage('pick', '外发下发');
+            $this->assignProcuremenListPage('pick', '协助下发');
 
 
             return $this->view->fetch('procuremen/index');
             return $this->view->fetch('procuremen/index');
         }
         }
@@ -1056,7 +1056,7 @@ class Procuremen extends Backend
                 'rows'  => $rows,
                 'rows'  => $rows,
             ]);
             ]);
         } catch (\Throwable $e) {
         } catch (\Throwable $e) {
-            Log::write('外发列表加载异常:' . $e->getMessage(), 'error');
+            Log::write('协助列表加载异常:' . $e->getMessage(), 'error');
 
 
             return json([
             return json([
                 'total' => 0,
                 'total' => 0,
@@ -1066,7 +1066,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 已外发/不可再审核判定(与「未发」列表隐藏规则一致,避免仅保存数量却被当成已下发)
+     * 已协助/不可再审核判定(与「未发」列表隐藏规则一致,避免仅保存数量却被当成已下发)
      * - purchase_order.status 为 0 或 1:已审核下发或已完结
      * - purchase_order.status 为 0 或 1:已审核下发或已完结
      * - 或存在 purchase_order_detail:已向供应商下过明细
      * - 或存在 purchase_order_detail:已向供应商下过明细
      * 仅写入 purchase_order 且 status 为空:视为未发(如只填本次数量、最高限价)
      * 仅写入 purchase_order 且 status 为空:视为未发(如只填本次数量、最高限价)
@@ -1109,7 +1109,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发下发列表需隐藏的工序 scydgy_id(已下发通知或已进入后续流程)
+     * 协助下发列表需隐藏的工序 scydgy_id(已下发通知或已进入后续流程)
      *
      *
      * @return array<int, true>
      * @return array<int, true>
      */
      */
@@ -1167,7 +1167,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发下发列表:手工新增工序(purchase_order.scydgy_id 为负数,与 ERP 缓存行区分)
+     * 协助下发列表:手工新增工序(purchase_order.scydgy_id 为负数,与 ERP 缓存行区分)
      *
      *
      * @return array<int, array<string, mixed>>
      * @return array<int, array<string, mixed>>
      */
      */
@@ -1339,7 +1339,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发初选列表软删除(mod_rq 记删除时间;ERP 行无记录则插入 purchase_order)
+     * 协助初选列表软删除(mod_rq 记删除时间;ERP 行无记录则插入 purchase_order)
      */
      */
     protected function softDeleteProcuremenPickRow(array $row): void
     protected function softDeleteProcuremenPickRow(array $row): void
     {
     {
@@ -1387,11 +1387,11 @@ class Procuremen extends Backend
         if ($gymcLog !== '') {
         if ($gymcLog !== '') {
             $logTail .= ' / ' . $gymcLog;
             $logTail .= ' / ' . $gymcLog;
         }
         }
-        $this->addOrderLog($sid, 'pick_soft_delete', '外发初选删除:' . $logTail, $poIdLog > 0 ? $poIdLog : null);
+        $this->addOrderLog($sid, 'pick_soft_delete', '协助初选删除:' . $logTail, $poIdLog > 0 ? $poIdLog : null);
     }
     }
 
 
     /**
     /**
-     * 手工新增外发工序(写入 purchase_order)
+     * 手工新增协助工序(写入 purchase_order)
      *
      *
      * @param array<string, mixed> $params
      * @param array<string, mixed> $params
      * @return int scydgy_id
      * @return int scydgy_id
@@ -1427,7 +1427,7 @@ class Procuremen extends Backend
         ];
         ];
         Db::table('purchase_order')->insert($data);
         Db::table('purchase_order')->insert($data);
         $poId = (int)Db::getLastInsID();
         $poId = (int)Db::getLastInsID();
-        $this->addOrderLog($sid, 'manual_add', '手工新增外发工序', $poId > 0 ? $poId : null);
+        $this->addOrderLog($sid, 'manual_add', '手工新增协助工序', $poId > 0 ? $poId : null);
 
 
         return $sid;
         return $sid;
     }
     }
@@ -3060,7 +3060,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发采购操作日志(表未建时仅写 runtime 日志,不中断业务)
+     * 协助采购操作日志(表未建时仅写 runtime 日志,不中断业务)
      */
      */
     protected function addOrderLog(int $scydgyId, string $action, string $content, ?int $purchaseOrderId = null): void
     protected function addOrderLog(int $scydgyId, string $action, string $content, ?int $purchaseOrderId = null): void
     {
     {
@@ -3207,7 +3207,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 是否已向外发/供应商下发(有下发明细、已进入审批流或记录下发时间)
+     * 是否已协助向供应商下发(有下发明细、已进入审批流或记录下发时间)
      */
      */
     protected function isProcuremenOrderIssued(array $main, int $detailCount): bool
     protected function isProcuremenOrderIssued(array $main, int $detailCount): bool
     {
     {
@@ -3550,7 +3550,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 加载并 assign 外发采购「详情」弹窗所需变量(与 {@see details()} 页面一致,供模板 / PDF 共用)。
+     * 加载并 assign 协助采购「详情」弹窗所需变量(与 {@see details()} 页面一致,供模板 / PDF 共用)。
      *
      *
      * @return array{ok: bool, ccydh: string} ok 表示存在主表或至少一条下发明细(否则 PDF 无可写内容)
      * @return array{ok: bool, ccydh: string} ok 表示存在主表或至少一条下发明细(否则 PDF 无可写内容)
      */
      */
@@ -3705,7 +3705,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 解析合并审核工序行(订单号须一致,且均未外发
+     * 解析合并审核工序行(订单号须一致,且均未协助
      *
      *
      * @return array<int, array<string, mixed>>
      * @return array<int, array<string, mixed>>
      */
      */
@@ -4080,7 +4080,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 详情 / PDF:同一外发批次下的全部工序行(与确认供应商弹窗一致)
+     * 详情 / PDF:同一协助批次下的全部工序行(与确认供应商弹窗一致)
      *
      *
      * @return array<int, array<string, mixed>>
      * @return array<int, array<string, mixed>>
      */
      */
@@ -4788,7 +4788,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发下发 — 仅写入 purchase_order_detail(第一步不发通知)
+     * 协助下发 — 仅写入 purchase_order_detail(第一步不发通知)
      *
      *
      * @return array<int, array{detail_id:int, cgymc:string, url:string}>
      * @return array<int, array{detail_id:int, cgymc:string, url:string}>
      */
      */
@@ -5173,7 +5173,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发下发 — 写入下发明细并拼好短信/邮件内容(供第二步发送)
+     * 协助下发 — 写入下发明细并拼好短信/邮件内容(供第二步发送)
      *
      *
      * @return array<string, mixed>
      * @return array<string, mixed>
      */
      */
@@ -5185,7 +5185,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发下发 — 发送短信(一家供应商)
+     * 协助下发 — 发送短信(一家供应商)
      *
      *
      * @param array<string, mixed> $bundle issueBuildSupplierNotifyBundle 返回值
      * @param array<string, mixed> $bundle issueBuildSupplierNotifyBundle 返回值
      */
      */
@@ -5216,7 +5216,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发下发 — 发送邮件(一家供应商)
+     * 协助下发 — 发送邮件(一家供应商)
      *
      *
      * @param array<string, mixed> $bundle
      * @param array<string, mixed> $bundle
      * @param array<string, mixed> $mailConfig
      * @param array<string, mixed> $mailConfig
@@ -5268,7 +5268,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发下发通知上下文(订单号、工序明细等,供短信/邮件共用)
+     * 协助下发通知上下文(订单号、工序明细等,供短信/邮件共用)
      *
      *
      * @param array<int, array<string, mixed>> $mergeRows
      * @param array<int, array<string, mixed>> $mergeRows
      * @return array<string, mixed>
      * @return array<string, mixed>
@@ -5299,11 +5299,11 @@ class Procuremen extends Backend
         if (!Config::get('app_debug') && !$this->isProcuremenNotifyDryRun()) {
         if (!Config::get('app_debug') && !$this->isProcuremenNotifyDryRun()) {
             return;
             return;
         }
         }
-        Log::write('[外发下发-' . $type . '] ' . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 'notice');
+        Log::write('[协助下发-' . $type . '] ' . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 'notice');
     }
     }
 
 
     /**
     /**
-     * 外发下发弹窗(选多家供应商并通知)
+     * 协助下发弹窗(选多家供应商并通知)
      */
      */
     public function pickReview()
     public function pickReview()
     {
     {
@@ -5316,7 +5316,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发新增(独立页面)
+     * 协助新增(独立页面)
      */
      */
     public function add()
     public function add()
     {
     {
@@ -5345,7 +5345,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 业务员外发新增进度查询(仅手工新增工序;普通用户只看 cywyxm=本人,超管看全部)
+     * 业务员新增进度查询(仅手工新增工序;普通用户只看 cywyxm=本人,超管看全部)
      */
      */
     public function rfqlist()
     public function rfqlist()
     {
     {
@@ -5434,7 +5434,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 手工新增工序详情:业务员只看本人;超管及外发流程岗位可看全部
+     * 手工新增工序详情:业务员只看本人;超管及协助流程岗位可看全部
      */
      */
     protected function assertManualOrderDetailsViewable(int $scydgyId): void
     protected function assertManualOrderDetailsViewable(int $scydgyId): void
     {
     {
@@ -5481,7 +5481,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 手工新增外发工序进度文案
+     * 手工新增协助工序进度文案
      */
      */
     protected function formatProcuremenRfqProgressText(array $row): string
     protected function formatProcuremenRfqProgressText(array $row): string
     {
     {
@@ -5510,11 +5510,11 @@ class Procuremen extends Backend
             }
             }
         }
         }
 
 
-        return '待外发下发';
+        return '待协助下发';
     }
     }
 
 
     /**
     /**
-     * 外发初选列表 — 批量软删除
+     * 协助初选列表 — 批量软删除
      */
      */
     public function pickDelete()
     public function pickDelete()
     {
     {
@@ -5553,7 +5553,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发下发 — 手工新增工序(弹窗)
+     * 协助下发 — 手工新增工序(弹窗)
      */
      */
     public function pickAdd()
     public function pickAdd()
     {
     {
@@ -5576,7 +5576,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 外发下发提交 — 弹窗「确认下发」→ POST procuremen/picksubmit
+     * 协助下发提交 — 弹窗「确认下发」→ POST procuremen/picksubmit
      */
      */
     public function pickSubmit()
     public function pickSubmit()
     {
     {
@@ -5615,7 +5615,7 @@ class Procuremen extends Backend
         $sysRqDb = date('Y-m-d H:i:s', $sysRqTs);
         $sysRqDb = date('Y-m-d H:i:s', $sysRqTs);
 
 
         $isMerge = count($mergeRows) > 1;
         $isMerge = count($mergeRows) > 1;
-        $logMsg = ($isMerge ? '合并外发初选(' . count($mergeRows) . ' 道工序)' : '外发初选')
+        $logMsg = ($isMerge ? '合并协助初选(' . count($mergeRows) . ' 道工序)' : '协助初选')
             . ',通知供应商(' . count($issueCompanies) . ' 家):' . $issueNamesSummary;
             . ',通知供应商(' . count($issueCompanies) . ' 家):' . $issueNamesSummary;
 
 
         $issueTime = date('Y-m-d H:i:s');
         $issueTime = date('Y-m-d H:i:s');
@@ -5795,7 +5795,7 @@ class Procuremen extends Backend
                 'notify_preview' => $this->notifyDryRunPreview,
                 'notify_preview' => $this->notifyDryRunPreview,
             ]);
             ]);
         }
         }
-        $this->success('已向 ' . count($issueCompanies) . ' 家供应商发送短信与邮件,请至「外发审批下发」确认供应商');
+        $this->success('已向 ' . count($issueCompanies) . ' 家供应商发送短信与邮件,请至「协助审批下发」确认供应商');
     }
     }
 
 
     /**
     /**
@@ -5822,7 +5822,7 @@ class Procuremen extends Backend
         $po = $pos[0];
         $po = $pos[0];
         $quoteGroups = $this->loadAuditSupplierQuoteGroups($bundle);
         $quoteGroups = $this->loadAuditSupplierQuoteGroups($bundle);
         if ($quoteGroups === []) {
         if ($quoteGroups === []) {
-            $this->error('暂无下发明细,请确认该订单已在「外发初选」完成下发');
+            $this->error('暂无下发明细,请确认该订单已在「协助初选」完成下发');
         }
         }
         $pickedName = '';
         $pickedName = '';
         foreach ($pos as $poRow) {
         foreach ($pos as $poRow) {
@@ -5870,7 +5870,7 @@ class Procuremen extends Backend
         }
         }
         $quoteGroups = $this->loadAuditSupplierQuoteGroups($bundle);
         $quoteGroups = $this->loadAuditSupplierQuoteGroups($bundle);
         if ($quoteGroups === []) {
         if ($quoteGroups === []) {
-            $this->error('暂无供应商报价,请确认已完成外发初选通知');
+            $this->error('暂无供应商报价,请确认已完成协助初选通知');
         }
         }
 
 
         $companyName = trim((string)$this->request->post('selected_company', ''));
         $companyName = trim((string)$this->request->post('selected_company', ''));
@@ -5995,12 +5995,12 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 废弃单个订单 bundle(退回外发初选,明细归档留痕)
+     * 废弃单个订单 bundle(退回协助初选,明细归档留痕)
      *
      *
      * @param array{ccydh:string, pos:array, merge_rows:array} $bundle
      * @param array{ccydh:string, pos:array, merge_rows:array} $bundle
      * @return array<int, true> 已处理的 scydgy_id 集合
      * @return array<int, true> 已处理的 scydgy_id 集合
      */
      */
-    protected function abandonOrderBundleToPick(array $bundle, string $logAction, string $logSuffix = ',退回外发初选'): array
+    protected function abandonOrderBundleToPick(array $bundle, string $logAction, string $logSuffix = ',退回协助初选'): array
     {
     {
         $pos = $bundle['pos'] ?? [];
         $pos = $bundle['pos'] ?? [];
         if (!is_array($pos) || $pos === []) {
         if (!is_array($pos) || $pos === []) {
@@ -6043,7 +6043,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 废弃单个审批订单 bundle(退回外发初选,明细归档留痕)
+     * 废弃单个审批订单 bundle(退回协助初选,明细归档留痕)
      *
      *
      * @param array{ccydh:string, pos:array, merge_rows:array} $bundle
      * @param array{ccydh:string, pos:array, merge_rows:array} $bundle
      * @return array<int, true> 已处理的 scydgy_id 集合
      * @return array<int, true> 已处理的 scydgy_id 集合
@@ -6109,7 +6109,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 废弃订单 — 退回外发初选重新询价(支持单条或批量)
+     * 废弃订单 — 退回协助初选重新询价(支持单条或批量)
      */
      */
     public function auditAbandon()
     public function auditAbandon()
     {
     {
@@ -6178,13 +6178,13 @@ class Procuremen extends Backend
         $this->pickHiddenScydgySetCache = null;
         $this->pickHiddenScydgySetCache = null;
 
 
         $msg = $doneOrders > 1
         $msg = $doneOrders > 1
-            ? ('已重新下发 ' . $doneOrders . ' 个订单并退回外发初选,历史下发与操作记录已保留')
-            : '已退回外发初选,请重新选择供应商下发(历史记录已保留)';
+            ? ('已重新下发 ' . $doneOrders . ' 个订单并退回协助初选,历史下发与操作记录已保留')
+            : '已退回协助初选,请重新选择供应商下发(历史记录已保留)';
         $this->success($msg);
         $this->success($msg);
     }
     }
 
 
     /**
     /**
-     * 历史存证 — 已完结订单重新下发,退回外发初选(支持单条或批量)
+     * 历史存证 — 已完结订单重新下发,退回协助初选(支持单条或批量)
      */
      */
     public function archiveAbandon()
     public function archiveAbandon()
     {
     {
@@ -6234,7 +6234,7 @@ class Procuremen extends Backend
                 $sids = $this->abandonOrderBundleToPick(
                 $sids = $this->abandonOrderBundleToPick(
                     $bundle,
                     $bundle,
                     'archive_abandon',
                     'archive_abandon',
-                    ',从历史存证退回外发初选'
+                    ',从历史存证退回协助初选'
                 );
                 );
                 if ($sids === []) {
                 if ($sids === []) {
                     continue;
                     continue;
@@ -6253,13 +6253,13 @@ class Procuremen extends Backend
         $this->pickHiddenScydgySetCache = null;
         $this->pickHiddenScydgySetCache = null;
 
 
         $msg = $doneOrders > 1
         $msg = $doneOrders > 1
-            ? ('已重新下发 ' . $doneOrders . ' 个订单并退回外发初选,历史记录已保留')
-            : '已退回外发初选,请重新选择供应商下发(历史记录已保留)';
+            ? ('已重新下发 ' . $doneOrders . ' 个订单并退回协助初选,历史记录已保留')
+            : '已退回协助初选,请重新选择供应商下发(历史记录已保留)';
         $this->success($msg);
         $this->success($msg);
     }
     }
 
 
     /**
     /**
-     * 采购终审 — 审批驳回(退回外发审批下发,不发送短信)
+     * 采购终审 — 审批驳回(退回协助审批下发,不发送短信)
      */
      */
     public function purchaseApprovalReject()
     public function purchaseApprovalReject()
     {
     {
@@ -6291,7 +6291,7 @@ class Procuremen extends Backend
             $this->error($e->getMessage() !== '' ? $e->getMessage() : '驳回失败');
             $this->error($e->getMessage() !== '' ? $e->getMessage() : '驳回失败');
         }
         }
 
 
-        $this->success('已驳回并退回「外发审批下发」');
+        $this->success('已驳回并退回「协助审批下发」');
     }
     }
 
 
     /**
     /**
@@ -6320,7 +6320,7 @@ class Procuremen extends Backend
             }
             }
         } catch (\Throwable $e) {
         } catch (\Throwable $e) {
         }
         }
-        $this->addOrderLog($sid, 'purchase_reject', '采购终审驳回,退回外发审批下发', $poId > 0 ? $poId : null);
+        $this->addOrderLog($sid, 'purchase_reject', '采购终审驳回,退回协助审批下发', $poId > 0 ? $poId : null);
     }
     }
 
 
     /**
     /**
@@ -6329,7 +6329,7 @@ class Procuremen extends Backend
     public function review()
     public function review()
     {
     {
         if ($this->request->isPost()) {
         if ($this->request->isPost()) {
-            $this->error('请使用第一步「外发下发」通知供应商,再在「确认供应商」中选定一家');
+            $this->error('请使用第一步「协助下发」通知供应商,再在「确认供应商」中选定一家');
         }
         }
         $this->view->assign('pickMode', 0);
         $this->view->assign('pickMode', 0);
 
 
@@ -6635,14 +6635,38 @@ class Procuremen extends Backend
                   PRIMARY KEY (`id`),
                   PRIMARY KEY (`id`),
                   KEY `idx_ym` (`ym`),
                   KEY `idx_ym` (`ym`),
                   KEY `idx_ct` (`createtime`)
                   KEY `idx_ct` (`createtime`)
-                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='月度外发明细导出记录'"
+                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='月度协助明细导出记录'"
             );
             );
         }
         }
     }
     }
 
 
     /**
     /**
-     * 按月份导出:已完结且采购确认已选定供应商(明细 status=1),与列表「已完结」∩「已选中」一致。
-     * 表头固定 8 列(与历史「外发明细」模板一致):序号、传票号、传票名称、外加工单位、订法、客户名称、工序、加工金额。
+     * 已完结订单归属月份(与历史存证、月度列表一致)
+     *
+     * @param array<string, mixed> $row purchase_order 行
+     */
+    protected function resolveCompletedPurchaseOrderYm(array $row): string
+    {
+        foreach (['createtime', 'dStamp', 'updatetime'] as $key) {
+            $raw = $row[$key] ?? null;
+            if ($raw === null || $raw === '') {
+                continue;
+            }
+            if (is_numeric($raw) && (int)$raw > 946684800) {
+                return date('Y-m', (int)$raw);
+            }
+            $t = trim((string)$raw);
+            if ($t !== '' && preg_match('/^(\d{4}-\d{2})/', $t, $m)) {
+                return $m[1];
+            }
+        }
+
+        return '';
+    }
+
+    /**
+     * 按月份导出:已完结订单(与历史存证、月度列表一致),按完结月份筛选。
+     * 表头固定 8 列(与历史「协助明细」模板一致):序号、传票号、传票名称、外加工单位、订法、客户名称、工序、加工金额。
      */
      */
     public function export_month_outward()
     public function export_month_outward()
     {
     {
@@ -6651,8 +6675,6 @@ class Procuremen extends Backend
         if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
         if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
             $ym = date('Y-m');
             $ym = date('Y-m');
         }
         }
-        $monthStart = $ym . '-01 00:00:00';
-        $monthEnd = date('Y-m-t 23:59:59', strtotime($monthStart));
 
 
         $dbRows = [];
         $dbRows = [];
         try {
         try {
@@ -6665,21 +6687,16 @@ class Procuremen extends Backend
         if (!is_array($dbRows)) {
         if (!is_array($dbRows)) {
             $dbRows = [];
             $dbRows = [];
         }
         }
-        $pickedSidSet = $this->loadScydgyIdsWithPickedSupplierDetail();
-        $dbRows = array_values(array_filter($dbRows, function ($dbRow) use ($pickedSidSet) {
+        $dbRows = array_values(array_filter($dbRows, function ($dbRow) use ($ym) {
             if (!is_array($dbRow)) {
             if (!is_array($dbRow)) {
                 return false;
                 return false;
             }
             }
-            $sid = (int)($dbRow['scydgy_id'] ?? 0);
-            if ($sid <= 0 && isset($dbRow['SCYDGY_ID'])) {
-                $sid = (int)$dbRow['SCYDGY_ID'];
-            }
 
 
-            return $sid > 0 && isset($pickedSidSet[$sid]);
+            return $this->resolveCompletedPurchaseOrderYm($dbRow) === $ym;
         }));
         }));
 
 
         $pool = $this->procuremenPoolFromPurchaseOrderDbRows($dbRows);
         $pool = $this->procuremenPoolFromPurchaseOrderDbRows($dbRows);
-        $filtered = $this->filterProcuremenIndexPool($pool, $monthStart, $monthEnd, true, '', [], [], 'pick_time');
+        $filtered = $pool;
 
 
         $mainBySid = [];
         $mainBySid = [];
         foreach ($filtered as $rw) {
         foreach ($filtered as $rw) {
@@ -6697,7 +6714,8 @@ class Procuremen extends Backend
             try {
             try {
                 $detailRows = Db::table('purchase_order_detail')
                 $detailRows = Db::table('purchase_order_detail')
                     ->where('scydgy_id', 'in', array_keys($mainBySid))
                     ->where('scydgy_id', 'in', array_keys($mainBySid))
-                    ->where('status', 1)
+                    ->where('status', '<>', self::PURCHASE_DETAIL_STATUS_VOID)
+                    ->order('status', 'desc')
                     ->order('CCYDH', 'asc')
                     ->order('CCYDH', 'asc')
                     ->order('company_name', 'asc')
                     ->order('company_name', 'asc')
                     ->order('id', 'asc')
                     ->order('id', 'asc')
@@ -6706,7 +6724,8 @@ class Procuremen extends Backend
                 try {
                 try {
                     $detailRows = Db::table('purchase_order_detail')
                     $detailRows = Db::table('purchase_order_detail')
                         ->where('scydgy_id', 'in', array_keys($mainBySid))
                         ->where('scydgy_id', 'in', array_keys($mainBySid))
-                        ->where('status', 1)
+                        ->where('status', '<>', self::PURCHASE_DETAIL_STATUS_VOID)
+                        ->order('status', 'desc')
                         ->order('CCYDH', 'asc')
                         ->order('CCYDH', 'asc')
                         ->order('company_name', 'asc')
                         ->order('company_name', 'asc')
                         ->order('ID', 'asc')
                         ->order('ID', 'asc')
@@ -6721,6 +6740,7 @@ class Procuremen extends Backend
         }
         }
 
 
         $exportLines = [];
         $exportLines = [];
+        $detailSidSeen = [];
         foreach ($detailRows as $dr) {
         foreach ($detailRows as $dr) {
             if (!is_array($dr)) {
             if (!is_array($dr)) {
                 continue;
                 continue;
@@ -6729,6 +6749,33 @@ class Procuremen extends Backend
             if ($sid <= 0 || !isset($mainBySid[$sid])) {
             if ($sid <= 0 || !isset($mainBySid[$sid])) {
                 continue;
                 continue;
             }
             }
+            if (isset($detailSidSeen[$sid])) {
+                continue;
+            }
+            if ((int)($dr['status'] ?? 0) !== 1) {
+                continue;
+            }
+            $detailSidSeen[$sid] = true;
+            $m = $mainBySid[$sid];
+            $exportLines[] = [
+                'CCYDH'        => (string)($dr['CCYDH'] ?? $m['CCYDH'] ?? ''),
+                'CYJMC'        => (string)($dr['CYJMC'] ?? $m['CYJMC'] ?? ''),
+                'company_name' => (string)($dr['company_name'] ?? ''),
+                'CDF'          => (string)($m['CDF'] ?? ''),
+                'cGzzxMc'      => (string)($m['cGzzxMc'] ?? ''),
+                'gx'           => $this->procuremenExportGxText($m),
+                'detail'       => $dr,
+            ];
+        }
+        foreach ($detailRows as $dr) {
+            if (!is_array($dr)) {
+                continue;
+            }
+            $sid = (int)($dr['scydgy_id'] ?? $dr['SCYDGY_ID'] ?? 0);
+            if ($sid <= 0 || !isset($mainBySid[$sid]) || isset($detailSidSeen[$sid])) {
+                continue;
+            }
+            $detailSidSeen[$sid] = true;
             $m = $mainBySid[$sid];
             $m = $mainBySid[$sid];
             $exportLines[] = [
             $exportLines[] = [
                 'CCYDH'        => (string)($dr['CCYDH'] ?? $m['CCYDH'] ?? ''),
                 'CCYDH'        => (string)($dr['CCYDH'] ?? $m['CCYDH'] ?? ''),
@@ -6740,6 +6787,21 @@ class Procuremen extends Backend
                 'detail'       => $dr,
                 'detail'       => $dr,
             ];
             ];
         }
         }
+        foreach ($mainBySid as $sid => $m) {
+            if (isset($detailSidSeen[$sid])) {
+                continue;
+            }
+            $company = trim((string)($m['pick_company_name'] ?? ''));
+            $exportLines[] = [
+                'CCYDH'        => (string)($m['CCYDH'] ?? ''),
+                'CYJMC'        => (string)($m['CYJMC'] ?? ''),
+                'company_name' => $company,
+                'CDF'          => (string)($m['CDF'] ?? ''),
+                'cGzzxMc'      => (string)($m['cGzzxMc'] ?? ''),
+                'gx'           => $this->procuremenExportGxText($m),
+                'detail'       => [],
+            ];
+        }
 
 
         $groups = [];
         $groups = [];
         foreach ($exportLines as $line) {
         foreach ($exportLines as $line) {
@@ -6753,11 +6815,11 @@ class Procuremen extends Backend
 
 
         $spreadsheet = new Spreadsheet();
         $spreadsheet = new Spreadsheet();
         $sheet = $spreadsheet->getActiveSheet();
         $sheet = $spreadsheet->getActiveSheet();
-        $sheet->setTitle('外发明细');
+        $sheet->setTitle('协助明细');
 
 
         $mon = (int)substr($ym, 5, 2);
         $mon = (int)substr($ym, 5, 2);
         $sheet->mergeCells('A1:H1');
         $sheet->mergeCells('A1:H1');
-        $sheet->setCellValue('A1', $mon . '月外发明细');
+        $sheet->setCellValue('A1', $mon . '月协助明细');
         $sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);
         $sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);
         $sheet->getStyle('A1')->getAlignment()
         $sheet->getStyle('A1')->getAlignment()
             ->setHorizontal(Alignment::HORIZONTAL_CENTER)
             ->setHorizontal(Alignment::HORIZONTAL_CENTER)
@@ -6778,7 +6840,7 @@ class Procuremen extends Backend
 
 
         if ($groups === []) {
         if ($groups === []) {
             $sheet->mergeCells('A3:H3');
             $sheet->mergeCells('A3:H3');
-            $sheet->setCellValue('A3', '(当月暂无符合条件的外发明细)');
+            $sheet->setCellValue('A3', '(当月暂无符合条件的协助明细)');
             $sheet->getStyle('A3')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
             $sheet->getStyle('A3')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
             $rowNum = 4;
             $rowNum = 4;
         } else {
         } else {
@@ -6853,7 +6915,7 @@ class Procuremen extends Backend
         $sheet->getColumnDimension('H')->setWidth(13);
         $sheet->getColumnDimension('H')->setWidth(13);
         $sheet->getRowDimension(1)->setRowHeight(28);
         $sheet->getRowDimension(1)->setRowHeight(28);
 
 
-        $fileBase = '外发明细_' . str_replace('-', '', $ym);
+        $fileBase = '协助明细_' . str_replace('-', '', $ym);
         $filename = $fileBase . '.xlsx';
         $filename = $fileBase . '.xlsx';
 
 
         try {
         try {
@@ -7211,7 +7273,7 @@ class Procuremen extends Backend
     protected function resolveProcuremenEmailSubject(string $scene): string
     protected function resolveProcuremenEmailSubject(string $scene): string
     {
     {
         $map = [
         $map = [
-            'review_email' => '您有新的外发加工订单',
+            'review_email' => '您有新的协助加工订单',
         ];
         ];
         $subject = trim((string)($map[$scene] ?? ''));
         $subject = trim((string)($map[$scene] ?? ''));
         if ($subject === '') {
         if ($subject === '') {
@@ -7224,8 +7286,8 @@ class Procuremen extends Backend
     protected function notifyTemplateMissingMessage(string $scene, bool $titleRequired = false): string
     protected function notifyTemplateMissingMessage(string $scene, bool $titleRequired = false): string
     {
     {
         $map = [
         $map = [
-            'review_email' => '外发下发-邮箱',
-            'review_sms'   => '外发下发-短信',
+            'review_email' => '协助下发-邮箱',
+            'review_sms'   => '协助下发-短信',
             'confirm_ok'   => '采购确认-通过',
             'confirm_ok'   => '采购确认-通过',
             'confirm_fail' => '采购确认-未通过',
             'confirm_fail' => '采购确认-未通过',
         ];
         ];
@@ -7244,7 +7306,7 @@ class Procuremen extends Backend
     }
     }
 
 
     /**
     /**
-     * 是否开启外发通知演练(不发真实短信/邮件)
+     * 是否开启协助通知演练(不发真实短信/邮件)
      */
      */
     protected function isProcuremenNotifyDryRun(): bool
     protected function isProcuremenNotifyDryRun(): bool
     {
     {
@@ -7257,7 +7319,7 @@ class Procuremen extends Backend
     protected function recordNotifyDryRunPreview(array $payload): void
     protected function recordNotifyDryRunPreview(array $payload): void
     {
     {
         $this->notifyDryRunPreview[] = $payload;
         $this->notifyDryRunPreview[] = $payload;
-        Log::write('[外发通知演练] ' . json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 'notice');
+        Log::write('[协助通知演练] ' . json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 'notice');
     }
     }
 
 
     /**
     /**

+ 1 - 1
application/admin/controller/Procuremenarchive.php

@@ -6,7 +6,7 @@ use app\common\controller\Backend;
 use think\Db;
 use think\Db;
 
 
 /**
 /**
- * 外发采购 — 历史存证档案查询(已完结工序 + 操作记录详情)
+ * 协助采购 — 历史存证档案查询(已完结工序 + 操作记录详情)
  *
  *
  * @icon fa fa-archive
  * @icon fa fa-archive
  */
  */

+ 40 - 19
application/admin/controller/Procuremenexport.php

@@ -6,7 +6,7 @@ use app\common\controller\Backend;
 use think\Db;
 use think\Db;
 
 
 /**
 /**
- * 外发采购 — 月度报表导出列表
+ * 协助采购 — 月度报表导出列表
  *
  *
  * @icon fa fa-file-excel-o
  * @icon fa fa-file-excel-o
  */
  */
@@ -36,7 +36,7 @@ class Procuremenexport extends Backend
     }
     }
 
 
     /**
     /**
-     * 已完结、已选定供应商、可按月份导出的订单(合并为一单一行)
+     * 已完结、可按月份导出的订单(合并为一单一行)
      */
      */
     public function exportable()
     public function exportable()
     {
     {
@@ -57,20 +57,17 @@ class Procuremenexport extends Backend
     }
     }
 
 
     /**
     /**
+     * 已完结订单(与历史存证一致),按完结月份筛选后合并为一单一行
+     *
      * @return array<int, array<string, mixed>>
      * @return array<int, array<string, mixed>>
      */
      */
     protected function buildExportableOrderRows(string $ym): array
     protected function buildExportableOrderRows(string $ym): array
     {
     {
-        $pickedSet = $this->loadPickedScydgyIdSet();
-        if ($pickedSet === []) {
-            return [];
-        }
         try {
         try {
             $query = Db::table('purchase_order');
             $query = Db::table('purchase_order');
             $query->whereRaw("(TRIM(CAST(`status` AS CHAR)) = '1' OR CAST(`status` AS UNSIGNED) = 1)");
             $query->whereRaw("(TRIM(CAST(`status` AS CHAR)) = '1' OR CAST(`status` AS UNSIGNED) = 1)");
-            $query->where('scydgy_id', 'in', array_keys($pickedSet));
             $dbRows = $query
             $dbRows = $query
-                ->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,pick_time,createtime,dStamp')
+                ->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,pick_time,createtime,dStamp,updatetime')
                 ->order('id', 'desc')
                 ->order('id', 'desc')
                 ->select();
                 ->select();
         } catch (\Throwable $e) {
         } catch (\Throwable $e) {
@@ -90,7 +87,7 @@ class Procuremenexport extends Backend
                 $sidList[$sid] = true;
                 $sidList[$sid] = true;
             }
             }
         }
         }
-        $amountMap = $this->loadPickedAmountSumByScydgyIds(array_keys($sidList));
+        $amountMap = $this->loadDetailAmountSumByScydgyIds(array_keys($sidList));
 
 
         $pool = [];
         $pool = [];
         foreach ($dbRows as $r) {
         foreach ($dbRows as $r) {
@@ -148,30 +145,52 @@ class Procuremenexport extends Backend
     }
     }
 
 
     /**
     /**
-     * @return array<int, true>
+     * 工序行金额合计:优先已选定明细 status=1,否则汇总有效明细上的加工金额
+     *
+     * @param int[] $scydgyIds
+     * @return array<int, float>
      */
      */
-    protected function loadPickedScydgyIdSet(): array
+    protected function loadDetailAmountSumByScydgyIds(array $scydgyIds): array
     {
     {
-        $set = [];
+        $picked = $this->loadPickedAmountSumByScydgyIds($scydgyIds);
+        $scydgyIds = array_values(array_unique(array_filter(array_map('intval', $scydgyIds))));
+        $missing = [];
+        foreach ($scydgyIds as $sid) {
+            if ($sid > 0 && !isset($picked[$sid])) {
+                $missing[] = $sid;
+            }
+        }
+        if ($missing === []) {
+            return $picked;
+        }
         try {
         try {
-            $rows = Db::table('purchase_order_detail')->where('status', 1)->field('scydgy_id')->select();
+            $rows = Db::table('purchase_order_detail')
+                ->where('scydgy_id', 'in', $missing)
+                ->where('status', '<>', 2)
+                ->field('scydgy_id,amount')
+                ->select();
         } catch (\Throwable $e) {
         } catch (\Throwable $e) {
             $rows = [];
             $rows = [];
         }
         }
         if (!is_array($rows)) {
         if (!is_array($rows)) {
-            return $set;
+            return $picked;
         }
         }
         foreach ($rows as $r) {
         foreach ($rows as $r) {
             if (!is_array($r)) {
             if (!is_array($r)) {
                 continue;
                 continue;
             }
             }
             $sid = (int)($r['scydgy_id'] ?? 0);
             $sid = (int)($r['scydgy_id'] ?? 0);
-            if ($sid > 0) {
-                $set[$sid] = true;
+            $am = trim((string)($r['amount'] ?? ''));
+            if ($sid <= 0 || $am === '' || $am === '0' || $am === '0.00') {
+                continue;
+            }
+            if (!isset($picked[$sid])) {
+                $picked[$sid] = 0.0;
             }
             }
+            $picked[$sid] += (float)$am;
         }
         }
 
 
-        return $set;
+        return $picked;
     }
     }
 
 
     /**
     /**
@@ -216,11 +235,13 @@ class Procuremenexport extends Backend
     }
     }
 
 
     /**
     /**
+     * 归属月份:与历史存证「完结时间」一致(createtime / dStamp),不用下发时间 pick_time
+     *
      * @param array<string, mixed> $row
      * @param array<string, mixed> $row
      */
      */
     protected function resolvePurchaseOrderRowYm(array $row): string
     protected function resolvePurchaseOrderRowYm(array $row): string
     {
     {
-        foreach (['pick_time', 'createtime', 'dStamp'] as $key) {
+        foreach (['createtime', 'dStamp', 'updatetime'] as $key) {
             $raw = $row[$key] ?? null;
             $raw = $row[$key] ?? null;
             if ($raw === null || $raw === '') {
             if ($raw === null || $raw === '') {
                 continue;
                 continue;
@@ -242,7 +263,7 @@ class Procuremenexport extends Backend
      */
      */
     protected function formatPurchaseOrderDoneTime(array $row): string
     protected function formatPurchaseOrderDoneTime(array $row): string
     {
     {
-        foreach (['createtime', 'pick_time', 'dStamp'] as $key) {
+        foreach (['createtime', 'dStamp', 'updatetime'] as $key) {
             $raw = $row[$key] ?? null;
             $raw = $row[$key] ?? null;
             if ($raw === null || $raw === '') {
             if ($raw === null || $raw === '') {
                 continue;
                 continue;

+ 5 - 5
application/admin/controller/Procuremenmenu.php

@@ -6,7 +6,7 @@ use app\common\controller\Backend;
 use think\Db;
 use think\Db;
 
 
 /**
 /**
- * 外发采购子菜单安装(仅超级管理员访问一次)
+ * 协助采购子菜单安装(仅超级管理员访问一次)
  */
  */
 class Procuremenmenu extends Backend
 class Procuremenmenu extends Backend
 {
 {
@@ -23,7 +23,7 @@ class Procuremenmenu extends Backend
             $listRule = Db::name('auth_rule')->where('name', 'procuremen/index')->find();
             $listRule = Db::name('auth_rule')->where('name', 'procuremen/index')->find();
         }
         }
         if (!$listRule) {
         if (!$listRule) {
-            $this->error('未找到外发下发菜单,请先在权限规则中配置 procuremen/pick 或 procuremen/index');
+            $this->error('未找到协助下发菜单,请先在权限规则中配置 procuremen/pick 或 procuremen/index');
         }
         }
         $pid = (int)($listRule['pid'] ?? 0);
         $pid = (int)($listRule['pid'] ?? 0);
         if ($pid <= 0) {
         if ($pid <= 0) {
@@ -35,7 +35,7 @@ class Procuremenmenu extends Backend
                     'type'       => 'file',
                     'type'       => 'file',
                     'pid'        => 0,
                     'pid'        => 0,
                     'name'       => 'procuremenroot',
                     'name'       => 'procuremenroot',
-                    'title'      => '外发采购',
+                    'title'      => '协助采购',
                     'icon'       => 'fa fa-share-alt',
                     'icon'       => 'fa fa-share-alt',
                     'url'        => '',
                     'url'        => '',
                     'ismenu'     => 1,
                     'ismenu'     => 1,
@@ -48,14 +48,14 @@ class Procuremenmenu extends Backend
             }
             }
             Db::name('auth_rule')->where('id', (int)$listRule['id'])->update([
             Db::name('auth_rule')->where('id', (int)$listRule['id'])->update([
                 'pid'        => $pid,
                 'pid'        => $pid,
-                'title'      => '外发下发',
+                'title'      => '协助下发',
                 'name'       => 'procuremen/pick',
                 'name'       => 'procuremen/pick',
                 'updatetime' => $t,
                 'updatetime' => $t,
             ]);
             ]);
         }
         }
         $added = 0;
         $added = 0;
         $workflowMenus = [
         $workflowMenus = [
-            ['name' => 'procuremen/pick', 'title' => '外发下发', 'icon' => 'fa fa-paper-plane', 'weigh' => 90],
+            ['name' => 'procuremen/pick', 'title' => '协助下发', 'icon' => 'fa fa-paper-plane', 'weigh' => 90],
             ['name' => 'procuremen/audit', 'title' => '确认供应商', 'icon' => 'fa fa-check-square-o', 'weigh' => 89],
             ['name' => 'procuremen/audit', 'title' => '确认供应商', 'icon' => 'fa fa-check-square-o', 'weigh' => 89],
             ['name' => 'procuremen/confirm', 'title' => '采购确认', 'icon' => 'fa fa-shopping-cart', 'weigh' => 88],
             ['name' => 'procuremen/confirm', 'title' => '采购确认', 'icon' => 'fa fa-shopping-cart', 'weigh' => 88],
         ];
         ];

+ 1 - 1
application/admin/controller/Procuremensms.php

@@ -9,7 +9,7 @@ use think\exception\ValidateException;
 use Exception;
 use Exception;
 
 
 /**
 /**
- * 外发采购 — 短信模版维护
+ * 协助采购 — 短信模版维护
  *
  *
  * @icon fa fa-commenting-o
  * @icon fa fa-commenting-o
  */
  */

+ 1 - 1
application/admin/controller/Purchaseemail.php

@@ -9,7 +9,7 @@ use think\exception\ValidateException;
 use Exception;
 use Exception;
 
 
 /**
 /**
- * 外发发件邮箱配置(仅维护编辑,不增删)
+ * 协助发件邮箱配置(仅维护编辑,不增删)
  *
  *
  * @icon fa fa-envelope
  * @icon fa fa-envelope
  */
  */

+ 7 - 7
application/admin/model/Purchasesmstemplate.php

@@ -18,8 +18,8 @@ class Purchasesmstemplate extends Model
     public function getSceneList()
     public function getSceneList()
     {
     {
         return [
         return [
-            'review_email' => '外发下发-邮箱',
-            'review_sms'   => '外发下发-短信',
+            'review_email' => '协助下发-邮箱',
+            'review_sms'   => '协助下发-短信',
             'confirm_ok'   => '采购确认-通过',
             'confirm_ok'   => '采购确认-通过',
             'confirm_fail' => '采购确认-未通过',
             'confirm_fail' => '采购确认-未通过',
         ];
         ];
@@ -67,12 +67,12 @@ class Purchasesmstemplate extends Model
             ['tag' => '{email}', 'label' => '邮箱', 'example' => 'user@example.com', 'scenes' => '全部'],
             ['tag' => '{email}', 'label' => '邮箱', 'example' => 'user@example.com', 'scenes' => '全部'],
             ['tag' => '{ccydh}', 'label' => '订单号', 'example' => '202603668L', 'scenes' => '全部'],
             ['tag' => '{ccydh}', 'label' => '订单号', 'example' => '202603668L', 'scenes' => '全部'],
             ['tag' => '{cyjmc}', 'label' => '印件名称', 'example' => '藏书票2', 'scenes' => '全部'],
             ['tag' => '{cyjmc}', 'label' => '印件名称', 'example' => '藏书票2', 'scenes' => '全部'],
-            ['tag' => '{cgymc}', 'label' => '工序名称(单道工序)', 'example' => '骑马订', 'scenes' => '外发下发'],
-            ['tag' => '{category}', 'label' => '业务分类', 'example' => '出版物印刷', 'scenes' => '外发下发'],
-            ['tag' => '{deadline}', 'label' => '截止时间', 'example' => '2026-05-18 14:30', 'scenes' => '外发下发'],
+            ['tag' => '{cgymc}', 'label' => '工序名称(单道工序)', 'example' => '骑马订', 'scenes' => '协助下发'],
+            ['tag' => '{category}', 'label' => '业务分类', 'example' => '出版物印刷', 'scenes' => '协助下发'],
+            ['tag' => '{deadline}', 'label' => '截止时间', 'example' => '2026-05-18 14:30', 'scenes' => '协助下发'],
             ['tag' => '{process_lines}', 'label' => '订单工序明细(文本)', 'example' => "订单号:YW20240629001\n印件名称:藏书票2\n1.工序名称:做刀版 单位:张 本次数量:500", 'scenes' => '全部'],
             ['tag' => '{process_lines}', 'label' => '订单工序明细(文本)', 'example' => "订单号:YW20240629001\n印件名称:藏书票2\n1.工序名称:做刀版 单位:张 本次数量:500", 'scenes' => '全部'],
-            ['tag' => '{process_lines_html}', 'label' => '订单工序明细(表格)', 'example' => '<table>…</table>', 'scenes' => '外发下发邮箱'],
-            ['tag' => '{platform_url}', 'label' => '平台链接', 'example' => 'https://…', 'scenes' => '外发下发邮箱'],
+            ['tag' => '{process_lines_html}', 'label' => '订单工序明细(表格)', 'example' => '<table>…</table>', 'scenes' => '协助下发邮箱'],
+            ['tag' => '{platform_url}', 'label' => '平台链接', 'example' => 'https://…', 'scenes' => '协助下发邮箱'],
         ];
         ];
     }
     }
 }
 }

+ 1 - 1
application/admin/view/index/login.html

@@ -240,7 +240,7 @@
     </div>
     </div>
 </div>
 </div>
 <div class="footer">
 <div class="footer">
-    <p>Copyright @ 浙江易盒包装科技有限公司 2023-{:date('Y',time())} 版权所有 <a href="https://beian.miit.gov.cn" target="_blank">{$site.beian|htmlentities}</a></p>
+    <p>Copyright @ 杭州可集达科技有限公司 2023-{:date('Y',time())} 版权所有 <a href="https://beian.miit.gov.cn" target="_blank">{$site.beian|htmlentities}</a></p>
 </div>
 </div>
 {include file="common/script" /}
 {include file="common/script" /}
 </body>
 </body>

+ 1 - 1
application/admin/view/index/生产经营驾驶舱系统登录页面.html

@@ -240,7 +240,7 @@
     </div>
     </div>
 </div>
 </div>
 <div class="footer">
 <div class="footer">
-    <p>Copyright @ 浙江易盒包装科技有限公司 2023-{:date('Y',time())} 版权所有 <a href="https://beian.miit.gov.cn" target="_blank">{$site.beian|htmlentities}</a></p>
+    <p>Copyright @ 杭州可集达科技有限公司 2023-{:date('Y',time())} 版权所有 <a href="https://beian.miit.gov.cn" target="_blank">{$site.beian|htmlentities}</a></p>
 </div>
 </div>
 {include file="common/script" /}
 {include file="common/script" /}
 </body>
 </body>

+ 1 - 1
application/admin/view/procuremen/add.html

@@ -24,7 +24,7 @@
 <div class="procuremen-add-wrap">
 <div class="procuremen-add-wrap">
     <form id="add-form" class="form-horizontal procuremen-add-form" role="form" data-toggle="validator" method="POST" action="">
     <form id="add-form" class="form-horizontal procuremen-add-form" role="form" data-toggle="validator" method="POST" action="">
         <p class="form-tip">
         <p class="form-tip">
-            手工录入一条外发工序,保存后可在「外发初选列表」中勾选并进行下发。
+            手工录入一条协助工序,保存后可在「协助初选列表」中勾选并进行下发。
         </p>
         </p>
         <div class="form-group">
         <div class="form-group">
             <label class="control-label col-xs-12 col-sm-2">订单号:</label>
             <label class="control-label col-xs-12 col-sm-2">订单号:</label>

+ 1 - 1
application/admin/view/procuremen/index.html

@@ -349,7 +349,7 @@
                             <div class="procuremen-table-area">
                             <div class="procuremen-table-area">
                                 <div id="toolbar" class="toolbar">
                                 <div id="toolbar" class="toolbar">
                                     <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" >刷新 <i class="fa fa-refresh"></i> </a>
                                     <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" >刷新 <i class="fa fa-refresh"></i> </a>
-                                    <a href="javascript:;" class="btn btn-danger" id="btn-procuremen-audit-abandon" title="勾选一条或多条订单退回外发初选重新下发(历史记录保留)" style="display:none;"><i class="fa fa-repeat"></i> 重新下发</a>
+                                    <a href="javascript:;" class="btn btn-danger" id="btn-procuremen-audit-abandon" title="勾选一条或多条订单退回协助初选重新下发(历史记录保留)" style="display:none;"><i class="fa fa-repeat"></i> 重新下发</a>
                                     <a href="javascript:;" class="btn btn-info" id="btn-procuremen-pick-review" title="勾选一条或多条工序进行下发(多条须同一订单号)" style="display:none;"><i class="fa fa-paper-plane"></i> 下发</a>
                                     <a href="javascript:;" class="btn btn-info" id="btn-procuremen-pick-review" title="勾选一条或多条工序进行下发(多条须同一订单号)" style="display:none;"><i class="fa fa-paper-plane"></i> 下发</a>
                                     <a href="javascript:;" class="btn btn-warning" id="btn-procuremen-batch-finish" title="勾选一条或多条工序标记为已完结" style="display:none;"><i class="fa fa-flag-checkered"></i> 完结</a>
                                     <a href="javascript:;" class="btn btn-warning" id="btn-procuremen-batch-finish" title="勾选一条或多条工序标记为已完结" style="display:none;"><i class="fa fa-flag-checkered"></i> 完结</a>
                                     <a href="javascript:;" class="btn btn-danger" id="btn-procuremen-pick-delete" title="勾选一条或多条工序从初选列表移除" style="display:none;"><i class="fa fa-trash"></i> 删除</a>
                                     <a href="javascript:;" class="btn btn-danger" id="btn-procuremen-pick-delete" title="勾选一条或多条工序从初选列表移除" style="display:none;"><i class="fa fa-trash"></i> 删除</a>

+ 1 - 1
application/admin/view/procuremenarchive/index.html

@@ -5,7 +5,7 @@
         <div class="widget-body no-padding">
         <div class="widget-body no-padding">
             <div id="toolbar" class="toolbar">
             <div id="toolbar" class="toolbar">
                 {:build_toolbar('refresh')}
                 {:build_toolbar('refresh')}
-                <a href="javascript:;" class="btn btn-danger" id="btn-archive-abandon" title="勾选一条或多条已完结订单退回外发初选重新下发"><i class="fa fa-repeat"></i> 重新下发</a>
+                <a href="javascript:;" class="btn btn-danger" id="btn-archive-abandon" title="勾选一条或多条已完结订单退回协助初选重新下发"><i class="fa fa-repeat"></i> 重新下发</a>
             </div>
             </div>
             <table id="table" class="table table-striped table-bordered table-hover table-nowrap" width="100%"></table>
             <table id="table" class="table table-striped table-bordered table-hover table-nowrap" width="100%"></table>
         </div>
         </div>

+ 2 - 2
application/admin/view/procuremenexport/index.html

@@ -5,14 +5,14 @@
         <div class="widget-body no-padding">
         <div class="widget-body no-padding">
             <div id="toolbar" class="toolbar">
             <div id="toolbar" class="toolbar">
                 <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}"><i class="fa fa-refresh"></i> 刷新</a>
                 <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}"><i class="fa fa-refresh"></i> 刷新</a>
-                <a href="javascript:;" class="btn btn-success" id="btn-export-month-outward" title="导出当前所选月份的外发明细 Excel"><i class="fa fa-download"></i> 导出 Excel</a>
+                <a href="javascript:;" class="btn btn-success" id="btn-export-month-outward" title="导出当前所选月份的协助明细 Excel"><i class="fa fa-download"></i> 导出 Excel</a>
                 <span class="procuremen-export-ym-wrap" style="display:inline-block;margin-left:12px;vertical-align:middle;">
                 <span class="procuremen-export-ym-wrap" style="display:inline-block;margin-left:12px;vertical-align:middle;">
                     <label style="margin:0 6px 0 0;font-weight:normal;">查看月份</label>
                     <label style="margin:0 6px 0 0;font-weight:normal;">查看月份</label>
                     <input type="month" id="export-preview-ym" class="form-control input-sm" style="display:inline-block;width:150px;height:30px;" />
                     <input type="month" id="export-preview-ym" class="form-control input-sm" style="display:inline-block;width:150px;height:30px;" />
                 </span>
                 </span>
             </div>
             </div>
             <table id="table" class="table table-striped table-bordered table-hover table-nowrap" width="100%"></table>
             <table id="table" class="table table-striped table-bordered table-hover table-nowrap" width="100%"></table>
-            <p class="help-block" style="padding:8px 12px;margin:0;">显示所选月份内<strong>已采购确认完结</strong>的外发订单(同一订单号合并为一行),点击「导出 Excel」下载该月外发明细。</p>
+            <p class="help-block" style="padding:8px 12px;margin:0;">显示所选月份内<strong>已完结</strong>的协助订单(月份按历史存证中的「完结时间」统计,同一订单号合并为一行),点击「导出 Excel」下载该月协助明细。</p>
         </div>
         </div>
     </div>
     </div>
 </div>
 </div>

+ 2 - 2
application/api/controller/Procuremen.php

@@ -6,12 +6,12 @@ use think\Controller;
 use think\Db;
 use think\Db;
 
 
 /**
 /**
- * 外发采购列表数据 API(供第三方拉取并写入 Redis,后台列表读同一份缓存)
+ * 协助采购列表数据 API(供第三方拉取并写入 Redis,后台列表读同一份缓存)
  */
  */
 class Procuremen extends Controller
 class Procuremen extends Controller
 {
 {
     /**
     /**
-     * 外发采购询价审核接口
+     * 协助采购询价审核接口
      *
      *
      * 请求:GET
      * 请求:GET
      * 参数:
      * 参数:

+ 1 - 1
application/config.php

@@ -369,7 +369,7 @@ return [
         'log_path'        => '', // 日志路径, 可选, 不配置日志驱动时启用默认日志驱动, 默认路径是 /path/to/tp-mailer/log, 要保证该目录有可写权限, 最好配置自己的日志路径
         'log_path'        => '', // 日志路径, 可选, 不配置日志驱动时启用默认日志驱动, 默认路径是 /path/to/tp-mailer/log, 要保证该目录有可写权限, 最好配置自己的日志路径
         'embed'           => 'embed:', // 邮件中嵌入图片元数据标记
         'embed'           => 'embed:', // 邮件中嵌入图片元数据标记
     ],
     ],
-    // 外发通知演练:true=不发真实短信/邮件,仅写日志并在 pickSubmit 返回 notify_preview
+    // 协助通知演练:true=不发真实短信/邮件,仅写日志并在 pickSubmit 返回 notify_preview
     'procuremen_notify_dry_run' => Env::get('procuremen.notify_dry_run', false),
     'procuremen_notify_dry_run' => Env::get('procuremen.notify_dry_run', false),
     // 正式环境 OSS(采购确认 PDF 等);public_base 可选自定义 CDN 域名
     // 正式环境 OSS(采购确认 PDF 等);public_base 可选自定义 CDN 域名
     'oss' => [
     'oss' => [

+ 1 - 1
application/extra/mproc.php

@@ -1,7 +1,7 @@
 <?php
 <?php
 
 
 /**
 /**
- * 手机端外发明细(purchase_order_detail)登录与展示
+ * 手机端协助明细(purchase_order_detail)登录与展示
  * 手机验证码:仅 customer 表登记手机号为普通外协(按 customer 公司名筛明细);否则查 admin.mobile 为管理员(看全部、不可改金额/交期);两者皆无则无权限
  * 手机验证码:仅 customer 表登记手机号为普通外协(按 customer 公司名筛明细);否则查 admin.mobile 为管理员(看全部、不可改金额/交期);两者皆无则无权限
  * 账号密码:与后台 admin 一致,可看全部,不可改金额/交期
  * 账号密码:与后台 admin 一致,可看全部,不可改金额/交期
  */
  */

+ 1 - 1
application/extra/procuremen_drop_optional_columns.sql

@@ -18,7 +18,7 @@ ALTER TABLE `purchase_order` DROP COLUMN `audit_time`;
 -- 建议保留的 workflow 字段:
 -- 建议保留的 workflow 字段:
 -- wflow_status      流程:0待下发 1待确认供应商 2待采购确认(与 status 完结 不同,不能互相替代)
 -- wflow_status      流程:0待下发 1待确认供应商 2待采购确认(与 status 完结 不同,不能互相替代)
 -- pick_company_name 审核选定的合作供应商
 -- pick_company_name 审核选定的合作供应商
--- pick_time         下发时间(用于判断是否已从「外发下发」列表隐藏,也可用 detail 表判断)
+-- pick_time         下发时间(用于判断是否已从「协助下发」列表隐藏,也可用 detail 表判断)
 -- status            您原有字段:完结等
 -- status            您原有字段:完结等
 -- sys_rq            您原有字段:报价截止时间
 -- sys_rq            您原有字段:报价截止时间
 -- CCYDH/CYJMC/CGYMC/本次数量/最高限价 等 列表与弹窗展示
 -- CCYDH/CYJMC/CGYMC/本次数量/最高限价 等 列表与弹窗展示

+ 8 - 8
application/extra/procuremen_tables.sql

@@ -1,4 +1,4 @@
--- 外发采购审核扩展表(执行一次)
+-- 协助采购审核扩展表(执行一次)
 
 
 CREATE TABLE IF NOT EXISTS `purchase_sms_template` (
 CREATE TABLE IF NOT EXISTS `purchase_sms_template` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
@@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS `purchase_sms_template` (
   `updatetime` datetime DEFAULT NULL,
   `updatetime` datetime DEFAULT NULL,
   PRIMARY KEY (`id`),
   PRIMARY KEY (`id`),
   UNIQUE KEY `uk_scene` (`scene`)
   UNIQUE KEY `uk_scene` (`scene`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='外发通知模版';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='协助通知模版';
 
 
 CREATE TABLE IF NOT EXISTS `purchase_month_export_log` (
 CREATE TABLE IF NOT EXISTS `purchase_month_export_log` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
@@ -24,12 +24,12 @@ CREATE TABLE IF NOT EXISTS `purchase_month_export_log` (
   PRIMARY KEY (`id`),
   PRIMARY KEY (`id`),
   KEY `idx_ym` (`ym`),
   KEY `idx_ym` (`ym`),
   KEY `idx_ct` (`createtime`)
   KEY `idx_ct` (`createtime`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='月度外发明细导出记录';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='月度协助明细导出记录';
 
 
 INSERT IGNORE INTO `purchase_sms_template` (`scene`, `title`, `content`, `remark`, `status`, `createtime`, `updatetime`) VALUES
 INSERT IGNORE INTO `purchase_sms_template` (`scene`, `title`, `content`, `remark`, `status`, `createtime`, `updatetime`) VALUES
-('review_email', '外发下发-邮箱', '您好,{company_name}:
+('review_email', '协助下发-邮箱', '您好,{company_name}:
 
 
-您有新的外发加工订单待处理:
+您有新的协助加工订单待处理:
 订单号:{ccydh}
 订单号:{ccydh}
 印件名称:{cyjmc}
 印件名称:{cyjmc}
 {process_lines}
 {process_lines}
@@ -39,17 +39,17 @@ INSERT IGNORE INTO `purchase_sms_template` (`scene`, `title`, `content`, `remark
 {platform_links_html}
 {platform_links_html}
 
 
 请及时查收并处理,谢谢!', '邮箱可用链接 HTML', 1, NOW(), NOW()),
 请及时查收并处理,谢谢!', '邮箱可用链接 HTML', 1, NOW(), NOW()),
-('review_sms', '外发下发-短信', '您好,{company_name}:您有新的外发加工订单待处理。订单号:{ccydh},印件名称:{cyjmc}。{process_lines}请在 {deadline} 前登录我司平台处理。谢谢!', '短信勿含链接', 1, NOW(), NOW()),
+('review_sms', '协助下发-短信', '您好,{company_name}:您有新的协助加工订单待处理。订单号:{ccydh},印件名称:{cyjmc}。{process_lines}请在 {deadline} 前登录我司平台处理。谢谢!', '短信勿含链接', 1, NOW(), NOW()),
 ('confirm_ok', '采购确认-通过', '您好,{company_name}:
 ('confirm_ok', '采购确认-通过', '您好,{company_name}:
 
 
-您参与的外发加工订单采购确认结果:已通过。
+您参与的协助加工订单采购确认结果:已通过。
 订单号:{ccydh}
 订单号:{ccydh}
 印件名称:{cyjmc}
 印件名称:{cyjmc}
 
 
 ', '', 1, NOW(), NOW()),
 ', '', 1, NOW(), NOW()),
 ('confirm_fail', '采购确认-未通过', '您好,{company_name}:
 ('confirm_fail', '采购确认-未通过', '您好,{company_name}:
 
 
-您参与的外发加工订单采购确认结果:未通过。
+您参与的协助加工订单采购确认结果:未通过。
 订单号:{ccydh}
 订单号:{ccydh}
 印件名称:{cyjmc}
 印件名称:{cyjmc}
 
 

+ 4 - 4
application/extra/procuremen_workflow.sql

@@ -1,11 +1,11 @@
--- 外发三步流程(仅保留必要列;若列已存在会报错,可跳过)
--- ① 外发下发 pick:选工序+多家供应商,发短信邮件通知报价 → wflow_status=1
+-- 协助三步流程(仅保留必要列;若列已存在会报错,可跳过)
+-- ① 协助下发 pick:选工序+多家供应商,发短信邮件通知报价 → wflow_status=1
 -- ② 确认供应商 audit:从已通知的报价中选一家 → wflow_status=2,不发短信
 -- ② 确认供应商 audit:从已通知的报价中选一家 → wflow_status=2,不发短信
 -- ③ 采购确认 confirm:定标,发送通过/未通过短信 → status 完结等
 -- ③ 采购确认 confirm:定标,发送通过/未通过短信 → status 完结等
 -- purchase_order.status(与 wflow_status 不同):
 -- purchase_order.status(与 wflow_status 不同):
 --   空/null = 未完结:仅保存本次数量/最高限价,或尚未写入状态
 --   空/null = 未完结:仅保存本次数量/最高限价,或尚未写入状态
---   0       = 外发流程进行中(已下发/待确认供应商/待采购确认,未点「完结」)
---   1       = 已完结(外发下发点「完结」,或采购确认提交成功)
+--   0       = 协助流程进行中(已下发/待确认供应商/待采购确认,未点「完结」)
+--   1       = 已完结(协助下发点「完结」,或采购确认提交成功)
 -- wflow_status:0待下发 1待确认供应商 2待采购确认(三步菜单用)
 -- wflow_status:0待下发 1待确认供应商 2待采购确认(三步菜单用)
 -- wflow_status:流程阶段,与 status 分工不同,不能只用 status 代替
 -- wflow_status:流程阶段,与 status 分工不同,不能只用 status 代替
 
 

+ 1 - 1
application/extra/public_entry.php

@@ -4,7 +4,7 @@
  * public/index.php 入口行为(与 FastAdmin 分离后台入口配合使用)
  * public/index.php 入口行为(与 FastAdmin 分离后台入口配合使用)
  *
  *
  * 后台实际入口为 public 目录下绑定 admin 的脚本(如 lqHKfByepX.php),
  * 后台实际入口为 public 目录下绑定 admin 的脚本(如 lqHKfByepX.php),
- * index.php 默认进 index 模块(手机外发等前端)。若希望「只打开 index.php、URL 上无任何 GET、且无 path」时进入后台,
+ * index.php 默认进 index 模块(手机协助等前端)。若希望「只打开 index.php、URL 上无任何 GET、且无 path」时进入后台,
  * 可将 index_bare_redirect_to_admin 设为 true,并填写与 public 目录一致的后台文件名。
  * 可将 index_bare_redirect_to_admin 设为 true,并填写与 public 目录一致的后台文件名。
  * 注意:带 ?addtabs=1&sort=… 等(后台表格 Ajax 常走 index.php)时不会跳转,否则会请求失败。
  * 注意:带 ?addtabs=1&sort=… 等(后台表格 Ajax 常走 index.php)时不会跳转,否则会请求失败。
  */
  */

+ 2 - 2
application/extra/purchase_email.sql

@@ -1,4 +1,4 @@
--- 外发发件邮箱:仅存储发件邮箱与授权码,其余 SMTP 参数见 config.php Mailer
+-- 协助发件邮箱:仅存储发件邮箱与授权码,其余 SMTP 参数见 config.php Mailer
 
 
 CREATE TABLE IF NOT EXISTS `purchase_email` (
 CREATE TABLE IF NOT EXISTS `purchase_email` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
@@ -7,4 +7,4 @@ CREATE TABLE IF NOT EXISTS `purchase_email` (
   `createtime` int(10) unsigned DEFAULT NULL,
   `createtime` int(10) unsigned DEFAULT NULL,
   `updatetime` int(10) unsigned DEFAULT NULL,
   `updatetime` int(10) unsigned DEFAULT NULL,
   PRIMARY KEY (`id`)
   PRIMARY KEY (`id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='外发发件邮箱(仅账号)';
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='协助发件邮箱(仅账号)';

+ 4 - 4
application/extra/purchase_sms_template_split.sql

@@ -10,7 +10,7 @@ UPDATE `purchase_sms_template` SET `scene` = 'review_sms' WHERE `scene` = '' OR
 
 
 -- 确保四条场景都存在(无则插入默认)
 -- 确保四条场景都存在(无则插入默认)
 INSERT IGNORE INTO `purchase_sms_template` (`scene`, `title`, `content`, `remark`, `status`, `createtime`, `updatetime`) VALUES
 INSERT IGNORE INTO `purchase_sms_template` (`scene`, `title`, `content`, `remark`, `status`, `createtime`, `updatetime`) VALUES
-('review_email', '外发下发-邮箱', '您好,{company_name}:您有新的外发加工订单待处理。订单号:{ccydh},印件名称:{cyjmc}。{process_lines}请在 {deadline} 前登陆我司平台处理。{platform_links_html}', '邮箱可含链接', '1', NOW(), NOW()),
-('review_sms', '外发下发-短信', '您好,{company_name}:您有新的外发加工订单待处理。订单号:{ccydh},印件名称:{cyjmc}。{process_lines}请在 {deadline} 前登录我司平台处理。谢谢!', '短信勿含链接', '1', NOW(), NOW()),
-('confirm_ok', '采购确认-通过', '您好,{company_name}:您参与的外发加工订单采购确认结果:已通过。订单号:{ccydh},印件名称:{cyjmc}。', '', '1', NOW(), NOW()),
-('confirm_fail', '采购确认-未通过', '您好,{company_name}:您参与的外发加工订单采购确认结果:未通过。订单号:{ccydh},印件名称:{cyjmc}。', '', '1', NOW(), NOW());
+('review_email', '协助下发-邮箱', '您好,{company_name}:您有新的协助加工订单待处理。订单号:{ccydh},印件名称:{cyjmc}。{process_lines}请在 {deadline} 前登陆我司平台处理。{platform_links_html}', '邮箱可含链接', '1', NOW(), NOW()),
+('review_sms', '协助下发-短信', '您好,{company_name}:您有新的协助加工订单待处理。订单号:{ccydh},印件名称:{cyjmc}。{process_lines}请在 {deadline} 前登录我司平台处理。谢谢!', '短信勿含链接', '1', NOW(), NOW()),
+('confirm_ok', '采购确认-通过', '您好,{company_name}:您参与的协助加工订单采购确认结果:已通过。订单号:{ccydh},印件名称:{cyjmc}。', '', '1', NOW(), NOW()),
+('confirm_fail', '采购确认-未通过', '您好,{company_name}:您参与的协助加工订单采购确认结果:未通过。订单号:{ccydh},印件名称:{cyjmc}。', '', '1', NOW(), NOW());

+ 88 - 40
application/index/controller/Index.php

@@ -13,7 +13,7 @@ use think\Session;
 use think\Validate;
 use think\Validate;
 
 
 /**
 /**
- * 手机端:外发明细(purchase_order_detail)验证码 / 账号密码登录 + 列表
+ * 手机端:协助明细(purchase_order_detail)验证码 / 账号密码登录 + 列表
  * 普通用户:customer 表(手机号验证码 或 登录账号+密码);管理员:admin 表账号密码(看全部、仅查看)
  * 普通用户:customer 表(手机号验证码 或 登录账号+密码);管理员:admin 表账号密码(看全部、仅查看)
  */
  */
 class Index extends Frontend
 class Index extends Frontend
@@ -372,7 +372,7 @@ class Index extends Frontend
     }
     }
 
 
     /**
     /**
-     * 登录成功后的回跳地址校验(仅允许本站「外发明细订单页」路径,防止开放重定向)
+     * 登录成功后的回跳地址校验(仅允许本站「协助明细订单页」路径,防止开放重定向)
      *
      *
      * @param string $raw GET/POST 的 redirect 或当前 REQUEST_URI
      * @param string $raw GET/POST 的 redirect 或当前 REQUEST_URI
      */
      */
@@ -1400,6 +1400,57 @@ class Index extends Frontend
         ];
         ];
     }
     }
 
 
+    /**
+     * status_name → 手机端左侧 Tab
+     */
+    protected function mprocStatusNameToListTab(string $statusName): string
+    {
+        $map = ['未提交' => 'draft', '已提交' => 'submitted', '已完成' => 'done'];
+        $sn = trim($statusName);
+
+        return isset($map[$sn]) ? $map[$sn] : '';
+    }
+
+    /**
+     * 短信/邮件 focus_eid 应落在的列表 Tab
+     */
+    protected function mprocResolveListTabForFocusEid(int $focusEid, array $user): string
+    {
+        if ($focusEid <= 0) {
+            return '';
+        }
+        $idCol = $this->mprocResolveProcuremenColumn(['id']);
+        if ($idCol === null) {
+            return '';
+        }
+        try {
+            $qrow = Db::table('purchase_order_detail')->where($idCol, $focusEid);
+            $qw = $this->mprocListWhereForLoginUser($user);
+            if ($qw !== []) {
+                $qrow->where($qw);
+            }
+            $dr = $qrow->find();
+        } catch (\Throwable $e) {
+            $dr = null;
+        }
+        if (!is_array($dr) || $dr === []) {
+            return '';
+        }
+        $sid = (int)($dr['scydgy_id'] ?? $dr['SCYDGY_ID'] ?? 0);
+        $po = null;
+        if ($this->mprocIsValidScydgyRowId($sid)) {
+            try {
+                $po = Db::table('purchase_order')->where('scydgy_id', $sid)->find();
+            } catch (\Throwable $e) {
+                $po = null;
+            }
+        }
+
+        return $this->mprocStatusNameToListTab(
+            $this->mprocResolveEffectiveStatusName($dr, is_array($po) ? $po : null)
+        );
+    }
+
     /**
     /**
      * 短信/邮件直达链接:确保 focus 对应明细出现在当前列表(便于高亮定位)
      * 短信/邮件直达链接:确保 focus 对应明细出现在当前列表(便于高亮定位)
      *
      *
@@ -1407,8 +1458,14 @@ class Index extends Frontend
      * @param array<string, mixed> $user
      * @param array<string, mixed> $user
      * @return array<string, mixed>
      * @return array<string, mixed>
      */
      */
-    protected function mprocEnsureFocusRowInList(array $bundle, int $focusEid, array $user, $statusNameCol): array
-    {
+    protected function mprocEnsureFocusRowInList(
+        array $bundle,
+        int $focusEid,
+        array $user,
+        $statusNameCol,
+        string $tab = 'draft',
+        string $q = ''
+    ): array {
         if ($focusEid <= 0) {
         if ($focusEid <= 0) {
             return $bundle;
             return $bundle;
         }
         }
@@ -1466,6 +1523,12 @@ class Index extends Frontend
             }
             }
         }
         }
         $effectiveSn = $this->mprocResolveEffectiveStatusName($row, is_array($poRow) ? $poRow : null);
         $effectiveSn = $this->mprocResolveEffectiveStatusName($row, is_array($poRow) ? $poRow : null);
+        if (trim((string)$q) === '') {
+            $rowTab = $this->mprocStatusNameToListTab($effectiveSn);
+            if ($rowTab !== '' && $rowTab !== $tab) {
+                return $bundle;
+            }
+        }
         $row['status_name'] = $effectiveSn;
         $row['status_name'] = $effectiveSn;
         $row['mproc_can_edit'] = $this->mprocCanEditRow($user, $row) ? 1 : 0;
         $row['mproc_can_edit'] = $this->mprocCanEditRow($user, $row) ? 1 : 0;
         $am = $row['amount'] ?? null;
         $am = $row['amount'] ?? null;
@@ -1656,7 +1719,7 @@ class Index extends Frontend
     }
     }
 
 
     /**
     /**
-     * 外发明细首页(需登录)
+     * 协助明细首页(需登录)
      * GET:main_tab=orders|me,orders 时 tab=draft|submitted|done 对应 status_name:未提交|已提交|已完成;q 搜索词
      * GET:main_tab=orders|me,orders 时 tab=draft|submitted|done 对应 status_name:未提交|已提交|已完成;q 搜索词
      */
      */
     public function index()
     public function index()
@@ -1700,35 +1763,16 @@ class Index extends Frontend
         $focusEid = $this->mprocReadFocusEidFromRequest();
         $focusEid = $this->mprocReadFocusEidFromRequest();
         if ($focusEid > 0 && $mainTab === 'orders') {
         if ($focusEid > 0 && $mainTab === 'orders') {
             $mprocFocusEid = $focusEid;
             $mprocFocusEid = $focusEid;
-            $idCol = $this->mprocResolveProcuremenColumn(['id']);
-            if ($idCol) {
-                $qw = $this->mprocListWhereForLoginUser($user);
-                $dr = null;
-                try {
-                    $qrow = Db::table('purchase_order_detail')->where($idCol, $focusEid);
-                    if ($qw !== []) {
-                        $qrow->where($qw);
-                    }
-                    $dr = $qrow->find();
-                } catch (\Throwable $e) {
-                    $dr = null;
-                }
-                if (is_array($dr) && $dr !== []) {
-                    $q = '';
-                    $sid = (int)($dr['scydgy_id'] ?? $dr['SCYDGY_ID'] ?? 0);
-                    $po = null;
-                    if ($this->mprocIsValidScydgyRowId($sid)) {
-                        try {
-                            $po = Db::table('purchase_order')->where('scydgy_id', $sid)->find();
-                        } catch (\Throwable $e) {
-                            $po = null;
-                        }
-                    }
-                    $sn = $this->mprocResolveEffectiveStatusName($dr, is_array($po) ? $po : null);
-                    $mapTab = ['未提交' => 'draft', '已提交' => 'submitted', '已完成' => 'done'];
-                    if ($sn !== '' && isset($mapTab[$sn])) {
-                        $tab = $mapTab[$sn];
-                    }
+            // 仅邮件/短信直链(URL 带 focus_eid)时自动切 Tab;Session 记忆不覆盖用户手动点的状态
+            $focusFromUrl = (int)$this->request->param('focus_eid', 0) > 0;
+            if (!$focusFromUrl) {
+                $uri = isset($_SERVER['REQUEST_URI']) ? (string)$_SERVER['REQUEST_URI'] : '';
+                $focusFromUrl = $this->mprocParseFocusEidFromUriString($uri) > 0;
+            }
+            if ($focusFromUrl && trim((string)$q) === '') {
+                $resolvedTab = $this->mprocResolveListTabForFocusEid($focusEid, $user);
+                if ($resolvedTab !== '') {
+                    $tab = $resolvedTab;
                 }
                 }
             }
             }
         }
         }
@@ -1745,6 +1789,8 @@ class Index extends Frontend
         $cid = (int)($user['customer_id'] ?? $user['customer_user_id'] ?? 0);
         $cid = (int)($user['customer_id'] ?? $user['customer_user_id'] ?? 0);
         $this->view->assign('mprocCanChangePwd', empty($user['is_admin']) && $cid > 0 ? 1 : 0);
         $this->view->assign('mprocCanChangePwd', empty($user['is_admin']) && $cid > 0 ? 1 : 0);
         $this->view->assign('mprocFocusEid', $mprocFocusEid);
         $this->view->assign('mprocFocusEid', $mprocFocusEid);
+        $mprocFocusTab = $mprocFocusEid > 0 ? $this->mprocResolveListTabForFocusEid($mprocFocusEid, $user) : '';
+        $this->view->assign('mprocFocusTab', $mprocFocusTab);
         $this->view->assign('mprocBootstrapToken', trim((string)($user['token'] ?? '')));
         $this->view->assign('mprocBootstrapToken', trim((string)($user['token'] ?? '')));
         $this->view->assign('mprocBootstrapKeepHours', $this->mprocKeepHours());
         $this->view->assign('mprocBootstrapKeepHours', $this->mprocKeepHours());
 
 
@@ -1756,7 +1802,7 @@ class Index extends Frontend
 
 
         $bundle = $this->mprocFetchProcuremenList($user, $tab, $q, $statusNameCol);
         $bundle = $this->mprocFetchProcuremenList($user, $tab, $q, $statusNameCol);
         if ($mprocFocusEid > 0) {
         if ($mprocFocusEid > 0) {
-            $bundle = $this->mprocEnsureFocusRowInList($bundle, $mprocFocusEid, $user, $statusNameCol);
+            $bundle = $this->mprocEnsureFocusRowInList($bundle, $mprocFocusEid, $user, $statusNameCol, $tab, $q);
         }
         }
         $this->view->assign('rows', $bundle['rows']);
         $this->view->assign('rows', $bundle['rows']);
 
 
@@ -1764,7 +1810,7 @@ class Index extends Frontend
     }
     }
 
 
     /**
     /**
-     * 外发明细列表 JSON(需登录)
+     * 协助明细列表 JSON(需登录)
      * main_tab=orders|me;orders 时 tab=draft|submitted|done、q=搜索词
      * main_tab=orders|me;orders 时 tab=draft|submitted|done、q=搜索词
      */
      */
     public function mprocList()
     public function mprocList()
@@ -1800,14 +1846,16 @@ class Index extends Frontend
         }
         }
 
 
         $statusNameCol = $this->mprocResolveProcuremenColumn(['status_name', 'status_txt', 'status_text']);
         $statusNameCol = $this->mprocResolveProcuremenColumn(['status_name', 'status_txt', 'status_text']);
-        $bundle = $this->mprocFetchProcuremenList($user, $tab, $q, $statusNameCol);
         $focusEid = $this->mprocReadFocusEidFromRequest();
         $focusEid = $this->mprocReadFocusEidFromRequest();
+        $focusTab = $focusEid > 0 ? $this->mprocResolveListTabForFocusEid($focusEid, $user) : '';
+        $bundle = $this->mprocFetchProcuremenList($user, $tab, $q, $statusNameCol);
         if ($focusEid > 0) {
         if ($focusEid > 0) {
-            $bundle = $this->mprocEnsureFocusRowInList($bundle, $focusEid, $user, $statusNameCol);
+            $bundle = $this->mprocEnsureFocusRowInList($bundle, $focusEid, $user, $statusNameCol, $tab, $q);
         }
         }
         $this->success('ok', '', array_merge([
         $this->success('ok', '', array_merge([
             'main_tab'  => 'orders',
             'main_tab'  => 'orders',
             'tab'       => $tab,
             'tab'       => $tab,
+            'focus_tab' => $focusTab,
             'is_admin'  => !empty($user['is_admin']) ? 1 : 0,
             'is_admin'  => !empty($user['is_admin']) ? 1 : 0,
             'focus_eid' => $focusEid,
             'focus_eid' => $focusEid,
         ], $bundle));
         ], $bundle));
@@ -2109,7 +2157,7 @@ class Index extends Frontend
     }
     }
 
 
     /**
     /**
-     * 保存单条外发明细的金额、交期(POST:id、amount、delivery)
+     * 保存单条协助明细的金额、交期(POST:id、amount、delivery)
      */
      */
     public function mprocSave()
     public function mprocSave()
     {
     {
@@ -2308,7 +2356,7 @@ class Index extends Frontend
     }
     }
 
 
     /**
     /**
-     * 短信宝(与后台外发审核一致,便于复用账号)
+     * 短信宝(与后台协助审核一致,便于复用账号)
      *
      *
      * @throws \Exception
      * @throws \Exception
      */
      */

+ 70 - 13
application/index/view/index/index.html

@@ -3,7 +3,7 @@
 <head>
 <head>
     <meta charset="UTF-8">
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
-    <title>外发明细</title>
+    <title>协助明细</title>
     <style>
     <style>
         * { box-sizing: border-box; }
         * { box-sizing: border-box; }
         html {
         html {
@@ -219,10 +219,14 @@
             color: #aaa;
             color: #aaa;
             font-size: 16px;
             font-size: 16px;
             line-height: 1.4;
             line-height: 1.4;
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
             pointer-events: none;
             pointer-events: none;
-            z-index: 1;
+            z-index: 3;
         }
         }
         .date-field-shell.has-value .date-placeholder { display: none; }
         .date-field-shell.has-value .date-placeholder { display: none; }
+        .date-field-shell:focus-within:not(.has-value) .date-placeholder { color: #bbb; }
         .date-display-text {
         .date-display-text {
             display: none;
             display: none;
             margin: 6px 0 0;
             margin: 6px 0 0;
@@ -249,6 +253,33 @@
             box-sizing: border-box;
             box-sizing: border-box;
             -webkit-appearance: none;
             -webkit-appearance: none;
             appearance: none;
             appearance: none;
+            pointer-events: none;
+        }
+        /* 未选日期时隐藏浏览器原生「年/月/日」占位,避免与自定义提示叠字 */
+        .date-field-shell:not(.has-value) .inp-date {
+            color: transparent;
+        }
+        .date-field-shell:not(.has-value) .inp-date::-webkit-datetime-edit,
+        .date-field-shell:not(.has-value) .inp-date::-webkit-datetime-edit-fields-wrapper,
+        .date-field-shell:not(.has-value) .inp-date::-webkit-datetime-edit-text,
+        .date-field-shell:not(.has-value) .inp-date::-webkit-datetime-edit-month-field,
+        .date-field-shell:not(.has-value) .inp-date::-webkit-datetime-edit-day-field,
+        .date-field-shell:not(.has-value) .inp-date::-webkit-datetime-edit-year-field {
+            color: transparent;
+            opacity: 0;
+        }
+        .date-field-shell.has-value .inp-date,
+        .date-field-shell:focus-within.has-value .inp-date {
+            color: #222;
+        }
+        .date-field-shell.has-value .inp-date::-webkit-datetime-edit,
+        .date-field-shell.has-value .inp-date::-webkit-datetime-edit-fields-wrapper,
+        .date-field-shell.has-value .inp-date::-webkit-datetime-edit-text,
+        .date-field-shell.has-value .inp-date::-webkit-datetime-edit-month-field,
+        .date-field-shell.has-value .inp-date::-webkit-datetime-edit-day-field,
+        .date-field-shell.has-value .inp-date::-webkit-datetime-edit-year-field {
+            color: #222;
+            opacity: 1;
         }
         }
         .inp-date:focus { outline: none; }
         .inp-date:focus { outline: none; }
         .inp-date::-webkit-calendar-picker-indicator {
         .inp-date::-webkit-calendar-picker-indicator {
@@ -296,7 +327,7 @@
     <div class="empty">暂无数据</div>
     <div class="empty">暂无数据</div>
     {else /}
     {else /}
     {volist name="rows" id="r"}
     {volist name="rows" id="r"}
-    <div class="card js-card {if $mprocFocusEid && $mprocFocusEid == $r.eid}mproc-card-highlight{/if}"
+    <div class="card js-card {if $mprocFocusEid && $mprocFocusEid == $r.eid && ($mprocFocusTab == '' || $mprocFocusTab == $mprocTab)}mproc-card-highlight{/if}"
          data-id="{$r.eid|default=0}"
          data-id="{$r.eid|default=0}"
          data-ccydh="{$r.CCYDH|default=''|htmlentities}"
          data-ccydh="{$r.CCYDH|default=''|htmlentities}"
          data-amount="{$r.amount|default=''|htmlentities}"
          data-amount="{$r.amount|default=''|htmlentities}"
@@ -378,8 +409,8 @@
         </div>
         </div>
         <div class="modal-field">
         <div class="modal-field">
             <label class="modal-field-label" for="inp-delivery" id="lbl-delivery">交货日期</label>
             <label class="modal-field-label" for="inp-delivery" id="lbl-delivery">交货日期</label>
-            <div class="date-field-shell" id="date-field-shell" role="button" tabindex="0" title="点击选择年月日">
-                <span class="date-placeholder" id="delivery-placeholder">请选择年月日</span>
+            <div class="date-field-shell" id="date-field-shell" role="button" tabindex="0" title="点击选择日">
+                <span class="date-placeholder" id="delivery-placeholder">请选择日</span>
                 <input type="date" id="inp-delivery" class="inp-date" lang="zh-CN" autocomplete="off" enterkeyhint="done" aria-labelledby="lbl-delivery">
                 <input type="date" id="inp-delivery" class="inp-date" lang="zh-CN" autocomplete="off" enterkeyhint="done" aria-labelledby="lbl-delivery">
             </div>
             </div>
             <p class="date-display-text" id="delivery-display-text" aria-live="polite"></p>
             <p class="date-display-text" id="delivery-display-text" aria-live="polite"></p>
@@ -520,7 +551,7 @@
         });
         });
     })();
     })();
 
 
-    var boot = {:json_encode(['main_tab' => $mprocMainTab, 'tab' => $mprocTab, 'q' => $mprocSearchQ, 'is_admin' => $mprocIsAdmin, 'can_change_pwd' => isset($mprocCanChangePwd) ? (int)$mprocCanChangePwd : 0, 'focus_eid' => isset($mprocFocusEid) ? (int)$mprocFocusEid : 0], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)};
+    var boot = {:json_encode(['main_tab' => $mprocMainTab, 'tab' => $mprocTab, 'q' => $mprocSearchQ, 'is_admin' => $mprocIsAdmin, 'can_change_pwd' => isset($mprocCanChangePwd) ? (int)$mprocCanChangePwd : 0, 'focus_eid' => isset($mprocFocusEid) ? (int)$mprocFocusEid : 0, 'focus_tab' => isset($mprocFocusTab) ? (string)$mprocFocusTab : ''], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)};
     var changePwdUrl = mprocEndpointUrl('mprocChangePwd.html');
     var changePwdUrl = mprocEndpointUrl('mprocChangePwd.html');
     var currentMain = boot.main_tab === 'me' ? 'me' : 'orders';
     var currentMain = boot.main_tab === 'me' ? 'me' : 'orders';
     var currentListTab = boot.tab && ['draft', 'submitted', 'done'].indexOf(boot.tab) !== -1 ? boot.tab : 'draft';
     var currentListTab = boot.tab && ['draft', 'submitted', 'done'].indexOf(boot.tab) !== -1 ? boot.tab : 'draft';
@@ -799,7 +830,9 @@
             var ccydh = pick(r, ['CCYDH', 'ccydh']);
             var ccydh = pick(r, ['CCYDH', 'ccydh']);
             var cname = pick(r, ['company_name', 'Company_name']);
             var cname = pick(r, ['company_name', 'Company_name']);
             var focusEid = mprocGetFocusEid();
             var focusEid = mprocGetFocusEid();
-            var hl = focusEid > 0 && eid === focusEid ? ' mproc-card-highlight' : '';
+            var focusTab = boot.focus_tab || '';
+            var shouldFocus = focusEid > 0 && (focusTab === '' || focusTab === currentListTab);
+            var hl = shouldFocus && eid === focusEid ? ' mproc-card-highlight' : '';
             return '<div class="card js-card' + hl + '" data-id="' + eid + '" data-ccydh="' + mprocEscAttr(ccydh) + '" data-amount="' + mprocEscAttr(amtRaw) + '" data-delivery="' + mprocEscAttr(delRaw) + '" data-title="' + mprocEscAttr(tit) + '" data-ceiling-price="' + mprocEscAttr(ceilP) + '" data-this-quantity="' + mprocEscAttr(thisQty) + '">'
             return '<div class="card js-card' + hl + '" data-id="' + eid + '" data-ccydh="' + mprocEscAttr(ccydh) + '" data-amount="' + mprocEscAttr(amtRaw) + '" data-delivery="' + mprocEscAttr(delRaw) + '" data-title="' + mprocEscAttr(tit) + '" data-ceiling-price="' + mprocEscAttr(ceilP) + '" data-this-quantity="' + mprocEscAttr(thisQty) + '">'
                 + '<p class="title">' + mprocEsc(tit) + '</p>'
                 + '<p class="title">' + mprocEsc(tit) + '</p>'
                 + '<div class="kv">' + mprocEsc(cname) + '</div>'
                 + '<div class="kv">' + mprocEsc(cname) + '</div>'
@@ -884,8 +917,12 @@
             }
             }
             if (d.focus_eid) {
             if (d.focus_eid) {
                 boot.focus_eid = parseInt(d.focus_eid, 10) || boot.focus_eid;
                 boot.focus_eid = parseInt(d.focus_eid, 10) || boot.focus_eid;
+                mprocPersistFocusEid(boot.focus_eid);
             }
             }
-            mprocScheduleFocusScroll();
+            if (d.focus_tab) {
+                boot.focus_tab = d.focus_tab;
+            }
+            mprocApplyFocusToList();
         }).catch(function (e) {
         }).catch(function (e) {
             if (myAbort && mprocListAbort !== myAbort) {
             if (myAbort && mprocListAbort !== myAbort) {
                 return;
                 return;
@@ -1156,10 +1193,10 @@
         inpDelivery.focus();
         inpDelivery.focus();
     }
     }
 
 
-    // 交期外壳点击/键盘触发选日期
+    // 交期外壳点击/键盘触发选日期(整框可点,不仅右侧日历图标)
     if (dateFieldShell && inpDelivery) {
     if (dateFieldShell && inpDelivery) {
         dateFieldShell.addEventListener('click', function (e) {
         dateFieldShell.addEventListener('click', function (e) {
-            if (e.target === inpDelivery) return;
+            e.preventDefault();
             openDeliveryPicker();
             openDeliveryPicker();
         });
         });
         dateFieldShell.addEventListener('keydown', function (e) {
         dateFieldShell.addEventListener('keydown', function (e) {
@@ -1295,7 +1332,27 @@
         });
         });
     });
     });
 
 
-    /** 邮件/短信直达链接:高亮并滚动到对应明细卡片 */
+    /** 邮件/短信直达链接:仅在当前 Tab 与定位订单状态一致时高亮并滚动 */
+    function mprocApplyFocusToList() {
+        var eid = mprocGetFocusEid();
+        if (!listInner) {
+            return;
+        }
+        var cards = listInner.querySelectorAll('.js-card');
+        if (!cards.length) {
+            return;
+        }
+        var focusTab = boot.focus_tab || '';
+        var shouldFocus = eid > 0 && (focusTab === '' || focusTab === currentListTab);
+        cards.forEach(function (card) {
+            var cid = parseInt(card.getAttribute('data-id'), 10) || 0;
+            card.classList.toggle('mproc-card-highlight', shouldFocus && cid === eid);
+        });
+        if (shouldFocus) {
+            mprocTryScrollToFocusCard();
+        }
+    }
+
     function mprocTryScrollToFocusCard() {
     function mprocTryScrollToFocusCard() {
         var eid = mprocGetFocusEid();
         var eid = mprocGetFocusEid();
         if (!eid || !listInner) {
         if (!eid || !listInner) {
@@ -1334,8 +1391,8 @@
         if (!mprocGetFocusEid()) {
         if (!mprocGetFocusEid()) {
             return;
             return;
         }
         }
-        [0, 120, 350, 700, 1200, 2000].forEach(function (ms) {
-            setTimeout(mprocTryScrollToFocusCard, ms);
+        [0, 80, 200, 450, 900, 1500].forEach(function (ms) {
+            setTimeout(mprocApplyFocusToList, ms);
         });
         });
     }
     }
 
 

+ 1 - 1
application/index/view/index/login.html

@@ -3,7 +3,7 @@
 <head>
 <head>
     <meta charset="UTF-8">
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
-    <title>外发明细登录</title>
+    <title>协助明细登录</title>
     <style>
     <style>
         * { box-sizing: border-box; }
         * { box-sizing: border-box; }
         html, body { margin: 0; height: 100%; width: 100%; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f0f2f5; }
         html, body { margin: 0; height: 100%; width: 100%; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: #f0f2f5; }

+ 3 - 3
public/assets/css/backend.css

@@ -1643,7 +1643,7 @@ table.table-nowrap thead > tr > th {
   color: #ccc;
   color: #ccc;
 }
 }
 
 
-/* 外发采购-采购确认:短信二次确认弹层(居中;勿与 footer 内 .layui-layer-confirm 透明按钮样式混淆) */
+/* 协助采购-采购确认:短信二次确认弹层(居中;勿与 footer 内 .layui-layer-confirm 透明按钮样式混淆) */
 .layui-layer.procuremen-purchase-sms-confirm,
 .layui-layer.procuremen-purchase-sms-confirm,
 .layui-layer-fast.procuremen-purchase-sms-confirm.layui-layer-dialog {
 .layui-layer-fast.procuremen-purchase-sms-confirm.layui-layer-dialog {
   position: fixed !important;
   position: fixed !important;
@@ -1660,7 +1660,7 @@ table.table-nowrap thead > tr > th {
   background: #fff !important;
   background: #fff !important;
   color: #333 !important;
   color: #333 !important;
 }
 }
-/* 外发采购-审核:弹层 footer 由 iframe 内克隆到主窗口 .layui-layer-footer,子页内 style 不作用于克隆节点 */
+/* 协助采购-审核:弹层 footer 由 iframe 内克隆到主窗口 .layui-layer-footer,子页内 style 不作用于克隆节点 */
 .layui-layer-fast .layui-layer-footer .btn.procuremen-btn-slate,
 .layui-layer-fast .layui-layer-footer .btn.procuremen-btn-slate,
 .layui-layer-footer .btn.procuremen-btn-slate {
 .layui-layer-footer .btn.procuremen-btn-slate {
   color: #fff !important;
   color: #fff !important;
@@ -1699,7 +1699,7 @@ table.table-nowrap thead > tr > th {
   padding-left: 0;
   padding-left: 0;
   padding-right: 0;
   padding-right: 0;
 }
 }
-/* 外发下发-新增:footer 克隆到主窗口,须在此右对齐 */
+/* 协助下发-新增:footer 克隆到主窗口,须在此右对齐 */
 .layui-layer-fast .layui-layer-footer .procuremen-pick-add-footer-btns,
 .layui-layer-fast .layui-layer-footer .procuremen-pick-add-footer-btns,
 .layui-layer-footer .procuremen-pick-add-footer-btns {
 .layui-layer-footer .procuremen-pick-add-footer-btns {
   width: 100%;
   width: 100%;

+ 9 - 9
public/assets/js/backend/procuremen.js

@@ -697,7 +697,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
             $(document).off('click.procuremenPickReview', '#btn-procuremen-pick-review').on('click.procuremenPickReview', '#btn-procuremen-pick-review', function (e) {
             $(document).off('click.procuremenPickReview', '#btn-procuremen-pick-review').on('click.procuremenPickReview', '#btn-procuremen-pick-review', function (e) {
                 e.preventDefault();
                 e.preventDefault();
                 if (Controller.wffTab !== 'pick') {
                 if (Controller.wffTab !== 'pick') {
-                    Toastr.warning('下发仅在外发下发列表可用');
+                    Toastr.warning('下发仅在协助下发列表可用');
                     return;
                     return;
                 }
                 }
                 var selPick = procuremenRowsWithPoInputs(table.bootstrapTable('getSelections') || []);
                 var selPick = procuremenRowsWithPoInputs(table.bootstrapTable('getSelections') || []);
@@ -706,7 +706,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
             $(document).off('click.procuremenAuditAbandon', '#btn-procuremen-audit-abandon').on('click.procuremenAuditAbandon', '#btn-procuremen-audit-abandon', function (e) {
             $(document).off('click.procuremenAuditAbandon', '#btn-procuremen-audit-abandon').on('click.procuremenAuditAbandon', '#btn-procuremen-audit-abandon', function (e) {
                 e.preventDefault();
                 e.preventDefault();
                 if (Controller.wffTab !== 'audit') {
                 if (Controller.wffTab !== 'audit') {
-                    Toastr.warning('重新下发仅在外发审批下发列表可用');
+                    Toastr.warning('重新下发仅在协助审批下发列表可用');
                     return;
                     return;
                 }
                 }
                 var selAb = table.bootstrapTable('getSelections') || [];
                 var selAb = table.bootstrapTable('getSelections') || [];
@@ -733,7 +733,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                     return;
                     return;
                 }
                 }
                 var abandonMsg = '<div style="text-align:left;line-height:1.65;font-size:13px;">'
                 var abandonMsg = '<div style="text-align:left;line-height:1.65;font-size:13px;">'
-                    + '<p style="margin:0 0 8px 0;">即将退回外发初选并重新下发以下 <strong>' + idList.length + '</strong> 个订单(历史下发与操作记录会保留):</p>'
+                    + '<p style="margin:0 0 8px 0;">即将退回协助初选并重新下发以下 <strong>' + idList.length + '</strong> 个订单(历史下发与操作记录会保留):</p>'
                     + rowsHtml
                     + rowsHtml
                     + '<p style="margin:10px 0 0;color:#a94442;"><strong>提示:</strong>退回后需重新选择供应商下发。</p></div>';
                     + '<p style="margin:10px 0 0;color:#a94442;"><strong>提示:</strong>退回后需重新选择供应商下发。</p></div>';
                 Layer.confirm(abandonMsg, {
                 Layer.confirm(abandonMsg, {
@@ -759,7 +759,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
             $(document).off('click.procuremenPickDelete', '#btn-procuremen-pick-delete').on('click.procuremenPickDelete', '#btn-procuremen-pick-delete', function (e) {
             $(document).off('click.procuremenPickDelete', '#btn-procuremen-pick-delete').on('click.procuremenPickDelete', '#btn-procuremen-pick-delete', function (e) {
                 e.preventDefault();
                 e.preventDefault();
                 if (Controller.wffTab !== 'pick') {
                 if (Controller.wffTab !== 'pick') {
-                    Toastr.warning('删除仅在外发初选列表可用');
+                    Toastr.warning('删除仅在协助初选列表可用');
                     return;
                     return;
                 }
                 }
                 var selDel = procuremenRowsWithPoInputs(table.bootstrapTable('getSelections') || []);
                 var selDel = procuremenRowsWithPoInputs(table.bootstrapTable('getSelections') || []);
@@ -802,7 +802,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
             $(document).off('click.procuremenBatchFinish', '#btn-procuremen-batch-finish').on('click.procuremenBatchFinish', '#btn-procuremen-batch-finish', function (e) {
             $(document).off('click.procuremenBatchFinish', '#btn-procuremen-batch-finish').on('click.procuremenBatchFinish', '#btn-procuremen-batch-finish', function (e) {
                 e.preventDefault();
                 e.preventDefault();
                 if (Controller.wffTab !== 'pick') {
                 if (Controller.wffTab !== 'pick') {
-                    Toastr.warning('完结仅在外发下发列表可用');
+                    Toastr.warning('完结仅在协助下发列表可用');
                     return;
                     return;
                 }
                 }
                 var selFin = procuremenRowsWithPoInputs(table.bootstrapTable('getSelections') || []);
                 var selFin = procuremenRowsWithPoInputs(table.bootstrapTable('getSelections') || []);
@@ -1759,7 +1759,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                     return;
                     return;
                 }
                 }
                 Lr.confirm(
                 Lr.confirm(
-                    '驳回后本单将退回「外发审批下发」。是否确认驳回?',
+                    '驳回后本单将退回「协助审批下发」。是否确认驳回?',
                     {
                     {
                         icon: 0,
                         icon: 0,
                         title: '采购终审 — 审批驳回',
                         title: '采购终审 — 审批驳回',
@@ -2613,8 +2613,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
             }
             }
 
 
             /*
             /*
-             * 外发下发:打开弹窗仅展示供应商,不写 purchase_order(确认下发走 picksubmit)。
-             * 非外发模式:仅当列表已填本次数量或最高限价时才 snapshot(草稿)。
+             * 协助下发:打开弹窗仅展示供应商,不写 purchase_order(确认下发走 picksubmit)。
+             * 非协助模式:仅当列表已填本次数量或最高限价时才 snapshot(草稿)。
              */
              */
             if (isPickMode) {
             if (isPickMode) {
                 loadReviewCompaniesPanel();
                 loadReviewCompaniesPanel();
@@ -2766,7 +2766,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                     return;
                     return;
                 }
                 }
                 Layer.confirm(
                 Layer.confirm(
-                    '确认选定供应商「' + company + '」并进入采购终审?<br/><span style="color:#666;">本步不发送短信。</span>',
+                    '选定供应商「' + company + '」并进入审批<br/><span style="color:#666;">本步不发送短信。</span>',
                     {icon: 3, title: '选择供应商', btn: ['确认', '取消']},
                     {icon: 3, title: '选择供应商', btn: ['确认', '取消']},
                     function (layerIdx) {
                     function (layerIdx) {
                         Layer.close(layerIdx);
                         Layer.close(layerIdx);

+ 1 - 1
public/assets/js/backend/procuremenarchive.js

@@ -156,7 +156,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                     return;
                     return;
                 }
                 }
                 var abandonMsg = '<div style="text-align:left;line-height:1.65;font-size:13px;">'
                 var abandonMsg = '<div style="text-align:left;line-height:1.65;font-size:13px;">'
-                    + '<p style="margin:0 0 8px 0;">即将从历史存证退回外发初选并重新下发以下 <strong>' + idList.length + '</strong> 个订单(历史记录会保留):</p>'
+                    + '<p style="margin:0 0 8px 0;">即将从历史存证退回协助初选并重新下发以下 <strong>' + idList.length + '</strong> 个订单(历史记录会保留):</p>'
                     + rowsHtml
                     + rowsHtml
                     + '<p style="margin:10px 0 0;color:#a94442;"><strong>提示:</strong>退回后需重新选择供应商下发。</p></div>';
                     + '<p style="margin:10px 0 0;color:#a94442;"><strong>提示:</strong>退回后需重新选择供应商下发。</p></div>';
                 Layer.confirm(abandonMsg, {
                 Layer.confirm(abandonMsg, {