|
|
@@ -44,6 +44,9 @@ class Procuremen extends Backend
|
|
|
/** @var array<int, true>|null 单次请求内下发隐藏工序 ID 缓存 */
|
|
|
protected $pickHiddenScydgySetCache = null;
|
|
|
|
|
|
+ /** 邮件发送日志表结构已就绪缓存键 */
|
|
|
+ protected const EMAIL_SEND_LOG_SCHEMA_CACHE_KEY = 'purchase_email_send_log_schema_v1';
|
|
|
+
|
|
|
/**
|
|
|
* 采购子接口均走菜单规则鉴权;下列方法在方法内用 hasProcuremenPerm 做别名校验。
|
|
|
* @var array
|
|
|
@@ -58,6 +61,9 @@ class Procuremen extends Backend
|
|
|
$this->model = new \app\admin\model\Procuremen;
|
|
|
$this->maybeMigrateLegacyWflowStatusText();
|
|
|
$this->assignProcuremenAuthConfig();
|
|
|
+ if (!Cache::get(self::EMAIL_SEND_LOG_SCHEMA_CACHE_KEY)) {
|
|
|
+ $this->ensurePurchaseEmailSendLogTableOnce();
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
@@ -5984,7 +5990,7 @@ class Procuremen extends Backend
|
|
|
* @param array<int, array<string, mixed>> $mergeRows
|
|
|
* @param array{ccydh:string, pos:array} $bundle
|
|
|
*/
|
|
|
- protected function sendAuditSupplierNotifyChannels(array $chosen, array $mergeRows, array $bundle, bool $sendSms, bool $sendEmail): void
|
|
|
+ protected function sendAuditSupplierNotifyChannels(array $chosen, array $mergeRows, array $bundle, bool $sendSms, bool $sendEmail, string $emailLogScene = 'pick_issue'): void
|
|
|
{
|
|
|
if (!$sendSms && !$sendEmail) {
|
|
|
return;
|
|
|
@@ -6080,6 +6086,14 @@ class Procuremen extends Backend
|
|
|
|
|
|
$ctx = $this->buildIssueNotifyContext($mergeRows, $sysRqNotify);
|
|
|
$notifyBundle = $this->composeSupplierNotifyBundle($chosen, $ctx, $detailLinks);
|
|
|
+ $notifyBundle['email_log_scene'] = $emailLogScene;
|
|
|
+ $notifyBundle['email_log_scydgy_id'] = (int)array_key_first($sids);
|
|
|
+ $poIdLog = 0;
|
|
|
+ $pos = $bundle['pos'] ?? [];
|
|
|
+ if (is_array($pos) && isset($pos[0]) && is_array($pos[0])) {
|
|
|
+ $poIdLog = (int)($pos[0]['id'] ?? 0);
|
|
|
+ }
|
|
|
+ $notifyBundle['email_log_purchase_order_id'] = $poIdLog;
|
|
|
|
|
|
if ($sendEmail) {
|
|
|
$mailTplRow = $this->loadNotifyTemplateRow('review_email');
|
|
|
@@ -6198,7 +6212,7 @@ class Procuremen extends Backend
|
|
|
'username' => trim((string)($g['username'] ?? '')),
|
|
|
];
|
|
|
try {
|
|
|
- $this->sendAuditSupplierNotifyChannels($chosen, $mergeRows, $bundle, $sendSms, $sendEmail);
|
|
|
+ $this->sendAuditSupplierNotifyChannels($chosen, $mergeRows, $bundle, $sendSms, $sendEmail, 'audit_resend');
|
|
|
} catch (\Throwable $e) {
|
|
|
$notifyErrors[] = $cname . ':' . $e->getMessage();
|
|
|
}
|
|
|
@@ -6298,7 +6312,42 @@ class Procuremen extends Backend
|
|
|
$mail->isHTML(true);
|
|
|
$mail->Subject = $mailSubject;
|
|
|
$mail->Body = $mailBody;
|
|
|
- $mail->send();
|
|
|
+ $fromEmail = trim((string)($mailConfig['addr'] ?? ''));
|
|
|
+ $sendOk = true;
|
|
|
+ $failReason = '';
|
|
|
+ try {
|
|
|
+ $mail->send();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $sendOk = false;
|
|
|
+ $failReason = $e->getMessage();
|
|
|
+ $this->persistEmailSendLog([
|
|
|
+ 'from_email' => $fromEmail,
|
|
|
+ 'to_email' => $toEmail,
|
|
|
+ 'send_time' => date('Y-m-d H:i:s'),
|
|
|
+ 'scene' => (string)($bundle['email_log_scene'] ?? 'pick_issue'),
|
|
|
+ 'ccydh' => trim((string)(($bundle['notify_vars']['ccydh'] ?? '') ?: ($bundle['ccydh'] ?? ''))),
|
|
|
+ 'company_name' => $companyName,
|
|
|
+ 'mail_subject' => $mailSubject,
|
|
|
+ 'status' => 0,
|
|
|
+ 'fail_reason' => $failReason,
|
|
|
+ 'scydgy_id' => (int)($bundle['email_log_scydgy_id'] ?? 0),
|
|
|
+ 'purchase_order_id' => (int)($bundle['email_log_purchase_order_id'] ?? 0),
|
|
|
+ ]);
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ $this->persistEmailSendLog([
|
|
|
+ 'from_email' => $fromEmail,
|
|
|
+ 'to_email' => $toEmail,
|
|
|
+ 'send_time' => date('Y-m-d H:i:s'),
|
|
|
+ 'scene' => (string)($bundle['email_log_scene'] ?? 'pick_issue'),
|
|
|
+ 'ccydh' => trim((string)(($bundle['notify_vars']['ccydh'] ?? '') ?: ($bundle['ccydh'] ?? ''))),
|
|
|
+ 'company_name' => $companyName,
|
|
|
+ 'mail_subject' => $mailSubject,
|
|
|
+ 'status' => 1,
|
|
|
+ 'fail_reason' => '',
|
|
|
+ 'scydgy_id' => (int)($bundle['email_log_scydgy_id'] ?? 0),
|
|
|
+ 'purchase_order_id' => (int)($bundle['email_log_purchase_order_id'] ?? 0),
|
|
|
+ ]);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -6336,6 +6385,128 @@ class Procuremen extends Backend
|
|
|
Log::write('[协助下发-' . $type . '] ' . json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 'notice');
|
|
|
}
|
|
|
|
|
|
+ protected function ensurePurchaseEmailSendLogTableOnce(): void
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ Db::query('SELECT 1 FROM `purchase_email_send_log` LIMIT 1');
|
|
|
+ Cache::set(self::EMAIL_SEND_LOG_SCHEMA_CACHE_KEY, 1);
|
|
|
+ return;
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ }
|
|
|
+ $sqlFile = APP_PATH . 'extra' . DS . 'purchase_email_send_log_install.sql';
|
|
|
+ if (is_file($sqlFile)) {
|
|
|
+ $sql = file_get_contents($sqlFile);
|
|
|
+ if (is_string($sql) && $sql !== '') {
|
|
|
+ foreach (preg_split('/;\s*[\r\n]+/', $sql) as $stmt) {
|
|
|
+ $stmt = trim($stmt);
|
|
|
+ if ($stmt === '' || stripos($stmt, 'CREATE TABLE') === false) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ Db::execute($stmt);
|
|
|
+ } catch (\Throwable $ignore) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Cache::set(self::EMAIL_SEND_LOG_SCHEMA_CACHE_KEY, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param array<string, mixed> $row
|
|
|
+ */
|
|
|
+ protected function persistEmailSendLog(array $row): void
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $this->ensurePurchaseEmailSendLogTableOnce();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ list($adminId, $adminName) = $this->GetUseName();
|
|
|
+ $cut = function ($s, $max) {
|
|
|
+ $s = (string)$s;
|
|
|
+ if (function_exists('mb_substr')) {
|
|
|
+ return mb_substr($s, 0, $max, 'UTF-8');
|
|
|
+ }
|
|
|
+
|
|
|
+ return strlen($s) <= $max ? $s : substr($s, 0, $max);
|
|
|
+ };
|
|
|
+ $data = [
|
|
|
+ 'from_email' => $cut($row['from_email'] ?? '', 128),
|
|
|
+ 'to_email' => $cut($row['to_email'] ?? '', 128),
|
|
|
+ 'send_time' => $row['send_time'] ?? date('Y-m-d H:i:s'),
|
|
|
+ 'scene' => $cut($row['scene'] ?? 'pick_issue', 32),
|
|
|
+ 'ccydh' => $cut($row['ccydh'] ?? '', 64),
|
|
|
+ 'company_name' => $cut($row['company_name'] ?? '', 128),
|
|
|
+ 'mail_subject' => $cut($row['mail_subject'] ?? '', 255),
|
|
|
+ 'status' => !empty($row['status']) ? 1 : 0,
|
|
|
+ 'fail_reason' => $cut($row['fail_reason'] ?? '', 500),
|
|
|
+ 'admin_id' => (int)$adminId,
|
|
|
+ 'admin_name' => $cut($adminName, 64),
|
|
|
+ 'scydgy_id' => (int)($row['scydgy_id'] ?? 0),
|
|
|
+ 'purchase_order_id' => (int)($row['purchase_order_id'] ?? 0),
|
|
|
+ 'createtime' => date('Y-m-d H:i:s'),
|
|
|
+ ];
|
|
|
+ try {
|
|
|
+ Db::table('purchase_email_send_log')->insert($data);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('persistEmailSendLog: ' . $e->getMessage(), 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function formatEmailLogSceneText(string $scene): string
|
|
|
+ {
|
|
|
+ $map = [
|
|
|
+ 'pick_issue' => '初选下发',
|
|
|
+ 'audit_resend' => '确认页重发',
|
|
|
+ ];
|
|
|
+
|
|
|
+ return $map[$scene] ?? $scene;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 邮件发送日志列表
|
|
|
+ */
|
|
|
+ public function emailLog()
|
|
|
+ {
|
|
|
+ $this->relationSearch = false;
|
|
|
+ $this->searchFields = 'from_email,to_email,ccydh,company_name,admin_name,mail_subject';
|
|
|
+ $this->request->filter(['strip_tags', 'trim']);
|
|
|
+ $bakModel = $this->model;
|
|
|
+ $this->model = new \app\admin\model\Purchaseemailsendlog;
|
|
|
+
|
|
|
+ if ($this->request->isAjax()) {
|
|
|
+ $this->ensurePurchaseEmailSendLogTableOnce();
|
|
|
+ list($where, $sort, $order, $offset, $limit) = $this->buildparams();
|
|
|
+ $sortField = preg_match('/^[a-zA-Z0-9_]+$/', (string)$sort) ? $sort : 'id';
|
|
|
+ $orderDir = strtoupper((string)$order) === 'ASC' ? 'ASC' : 'DESC';
|
|
|
+
|
|
|
+ $list = $this->model
|
|
|
+ ->where($where)
|
|
|
+ ->order($sortField, $orderDir)
|
|
|
+ ->paginate($limit);
|
|
|
+
|
|
|
+ $rows = [];
|
|
|
+ foreach ($list->items() as $item) {
|
|
|
+ $row = is_array($item) ? $item : $item->toArray();
|
|
|
+ $scene = trim((string)($row['scene'] ?? ''));
|
|
|
+ $row['scene_text'] = $this->formatEmailLogSceneText($scene);
|
|
|
+ $row['status_text'] = !empty($row['status']) ? '成功' : '失败';
|
|
|
+ $row['to_email_masked'] = mask_email((string)($row['to_email'] ?? ''));
|
|
|
+ $row['from_email_masked'] = mask_email((string)($row['from_email'] ?? ''));
|
|
|
+ $rows[] = $row;
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->model = $bakModel;
|
|
|
+
|
|
|
+ return json(['total' => $list->total(), 'rows' => $rows]);
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->model = $bakModel;
|
|
|
+
|
|
|
+ return $this->view->fetch('procuremen/emaillog');
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 协助下发弹窗(选多家供应商并通知)
|
|
|
*/
|