getMessage(), 'error'); } if ($changed) { self::clearTableFieldsCache([ 'purchase_order', 'purchase_order_detail', 'purchase_order_oper_log', 'purchase_order_bid_open', 'purchase_order_inbound_score', 'purchase_email_send_log', 'purchase_sms_template', ]); } try { Cache::set(self::CACHE_KEY, 1, 86400); } catch (\Throwable $e) { } } catch (\Throwable $e) { Log::write('ProcuremenSchema::ensureAll: ' . $e->getMessage(), 'error'); } } /** * 强制下次请求重新探测(改库后可清 runtime cache 或调此方法) */ public static function invalidateCache(): void { self::$done = false; try { Cache::rm(self::CACHE_KEY); } catch (\Throwable $e) { } } protected static function tableExists(string $table): bool { try { Db::query('SELECT 1 FROM `' . str_replace('`', '', $table) . '` LIMIT 1'); return true; } catch (\Throwable $e) { return false; } } /** * @param array $columns col => ADD COLUMN ... DDL fragment (without ADD COLUMN prefix ok if starts with ADD) */ protected static function ensureColumns(string $table, array $columns): bool { $changed = false; $table = str_replace('`', '', $table); foreach ($columns as $col => $ddl) { $col = str_replace('`', '', (string)$col); if ($col === '') { continue; } try { $exists = Db::query("SHOW COLUMNS FROM `{$table}` LIKE '{$col}'"); if (is_array($exists) && $exists !== []) { continue; } $ddl = trim($ddl); if (stripos($ddl, 'ADD ') !== 0) { $ddl = 'ADD COLUMN ' . $ddl; } Db::execute("ALTER TABLE `{$table}` {$ddl}"); $changed = true; } catch (\Throwable $e) { Log::write("ProcuremenSchema {$table}.{$col}: " . $e->getMessage(), 'error'); } } return $changed; } /** * @param string[] $tables */ protected static function clearTableFieldsCache(array $tables): void { $db = ''; try { $db = (string)Config::get('database.database'); } catch (\Throwable $e) { } try { $ref = new \ReflectionProperty(\think\db\Query::class, 'info'); $ref->setAccessible(true); $info = $ref->getValue(); if (is_array($info)) { foreach ($tables as $t) { $key = ($db !== '' ? $db . '.' : '') . $t; unset($info[$key], $info[$t]); } $ref->setValue(null, $info); } } catch (\Throwable $e) { } if ($db === '') { return; } foreach ($tables as $t) { $schemaFile = RUNTIME_PATH . 'schema' . DIRECTORY_SEPARATOR . $db . '.' . $t . '.php'; if (is_file($schemaFile)) { @unlink($schemaFile); } } } protected static function ensurePurchaseOrderTable(): bool { $changed = false; if (!self::tableExists('purchase_order')) { try { Db::execute("CREATE TABLE IF NOT EXISTS `purchase_order` ( `id` int NOT NULL AUTO_INCREMENT, `scydgy_id` int DEFAULT NULL COMMENT '工序行ID(手工询价为负数)', `CCYDH` varchar(255) DEFAULT NULL COMMENT '订单号', `CYJMC` varchar(255) DEFAULT NULL COMMENT '印件名称', `CDXMC` varchar(255) DEFAULT NULL, `CGYBH` varchar(255) DEFAULT NULL, `CCLBMMC` varchar(255) DEFAULT NULL COMMENT '需求部门', `CGYMC` varchar(255) DEFAULT NULL COMMENT '工序名称', `CDW` varchar(255) DEFAULT NULL COMMENT '单位', `czlyq` varchar(64) NOT NULL DEFAULT '' COMMENT '类型', `NGZL` varchar(255) DEFAULT NULL COMMENT '工作量', `This_quantity` varchar(255) DEFAULT NULL COMMENT '本次数量', `ceilingPrice` varchar(255) DEFAULT NULL COMMENT '最高限价', `CDF` varchar(255) DEFAULT NULL COMMENT '订法', `cGzzxMc` varchar(255) DEFAULT NULL COMMENT '外厂单位', `MBZ` varchar(255) DEFAULT NULL COMMENT '备注', `bwjg` varchar(255) DEFAULT NULL, `iStatus` varchar(255) DEFAULT NULL, `dStamp` varchar(255) DEFAULT NULL, `dputrecord` varchar(255) DEFAULT NULL, `cywyxm` varchar(255) DEFAULT NULL COMMENT '业务员/需求发起人', `notify_salesman` varchar(500) NOT NULL DEFAULT '' COMMENT '通知业务员(可多选)', `rfq_salesman_notified` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '手工询价已通知业务员0否1是', `rfq_salesman_notify_time` datetime DEFAULT NULL COMMENT '手工询价通知业务员时间', `status` varchar(255) DEFAULT NULL COMMENT '空=未完结、0=进行中、1=已完结', `createtime` varchar(255) DEFAULT NULL COMMENT '录入时间', `pdf_url` varchar(255) DEFAULT NULL COMMENT 'pdf路径', `sys_rq` varchar(255) DEFAULT NULL COMMENT '招标截止日期', `delivery_deadline` date DEFAULT NULL COMMENT '交货截止日期', `wflow_status` varchar(255) DEFAULT NULL COMMENT '流程状态', `pick_company_name` varchar(255) DEFAULT NULL COMMENT '中标/选定供应商', `manual_pick` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '1=自选供应商(免开标)', `manual_pick_reason` varchar(500) NOT NULL DEFAULT '' COMMENT '自选供应商说明', `pick_time` datetime DEFAULT NULL COMMENT '选定时间', `score_rule_id` int unsigned NOT NULL DEFAULT 0 COMMENT '评分规则ID', `score_rule_name` varchar(100) NOT NULL DEFAULT '' COMMENT '规则名称快照', `score_quality_weight` int unsigned NOT NULL DEFAULT 0 COMMENT '质量权重快照%', `score_price_weight` int unsigned NOT NULL DEFAULT 0 COMMENT '价格权重快照%', `score_lead_weight` int unsigned NOT NULL DEFAULT 0 COMMENT '交货权重快照%', `mod_rq` varchar(255) DEFAULT NULL COMMENT '软删除日期', PRIMARY KEY (`id`), KEY `idx_scydgy_id` (`scydgy_id`), KEY `idx_CCYDH` (`CCYDH`(64)), KEY `idx_wflow_status` (`wflow_status`(32)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='协助采购订单(工序行)'"); $changed = true; } catch (\Throwable $e) { Log::write('ProcuremenSchema create purchase_order: ' . $e->getMessage(), 'error'); } } $changed = self::ensureColumns('purchase_order', [ 'czlyq' => "`czlyq` varchar(64) NOT NULL DEFAULT '' COMMENT '类型'", 'notify_salesman' => "`notify_salesman` varchar(500) NOT NULL DEFAULT '' COMMENT '通知业务员(可多选)'", 'rfq_salesman_notified' => "`rfq_salesman_notified` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '手工询价已通知业务员0否1是'", 'rfq_salesman_notify_time' => "`rfq_salesman_notify_time` datetime DEFAULT NULL COMMENT '手工询价通知业务员时间'", 'manual_pick' => "`manual_pick` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '1=自选供应商(免开标)'", 'manual_pick_reason' => "`manual_pick_reason` varchar(500) NOT NULL DEFAULT '' COMMENT '自选供应商说明'", 'pick_time' => "`pick_time` datetime DEFAULT NULL COMMENT '选定时间'", 'delivery_deadline' => "`delivery_deadline` date DEFAULT NULL COMMENT '交货截止日期'", 'score_rule_id' => "`score_rule_id` int unsigned NOT NULL DEFAULT 0 COMMENT '评分规则ID'", 'score_rule_name' => "`score_rule_name` varchar(100) NOT NULL DEFAULT '' COMMENT '规则名称快照'", 'score_quality_weight' => "`score_quality_weight` int unsigned NOT NULL DEFAULT 0 COMMENT '质量权重快照%'", 'score_price_weight' => "`score_price_weight` int unsigned NOT NULL DEFAULT 0 COMMENT '价格权重快照%'", 'score_lead_weight' => "`score_lead_weight` int unsigned NOT NULL DEFAULT 0 COMMENT '交货权重快照%'", 'This_quantity' => "`This_quantity` varchar(255) DEFAULT NULL COMMENT '本次数量'", 'ceilingPrice' => "`ceilingPrice` varchar(255) DEFAULT NULL COMMENT '最高限价'", 'pick_company_name' => "`pick_company_name` varchar(255) DEFAULT NULL COMMENT '中标/选定供应商'", 'wflow_status' => "`wflow_status` varchar(255) DEFAULT NULL COMMENT '流程状态'", 'mod_rq' => "`mod_rq` varchar(255) DEFAULT NULL COMMENT '软删除日期'", 'cywyxm' => "`cywyxm` varchar(255) DEFAULT NULL COMMENT '业务员/需求发起人'", 'CCLBMMC' => "`CCLBMMC` varchar(255) DEFAULT NULL COMMENT '需求部门'", 'MBZ' => "`MBZ` varchar(255) DEFAULT NULL COMMENT '备注'", 'createtime' => "`createtime` varchar(255) DEFAULT NULL COMMENT '录入时间'", 'pdf_url' => "`pdf_url` varchar(255) DEFAULT NULL COMMENT 'pdf路径'", 'sys_rq' => "`sys_rq` varchar(255) DEFAULT NULL COMMENT '招标截止日期'", ]) || $changed; return $changed; } protected static function ensurePurchaseOrderDetailTable(): bool { $changed = false; if (!self::tableExists('purchase_order_detail')) { try { Db::execute("CREATE TABLE IF NOT EXISTS `purchase_order_detail` ( `id` int NOT NULL AUTO_INCREMENT, `scydgy_id` int DEFAULT NULL COMMENT '工序行ID', `CCYDH` varchar(255) DEFAULT NULL COMMENT '订单号', `CYJMC` varchar(255) DEFAULT NULL COMMENT '印件名称', `CGYMC` varchar(255) DEFAULT NULL COMMENT '工序名称', `amount` varchar(255) DEFAULT NULL COMMENT '单价/金额', `delivery` datetime DEFAULT NULL COMMENT '交货日期', `delivery_createtime` datetime DEFAULT NULL COMMENT '报价提交时间', `lead_days` int unsigned DEFAULT NULL COMMENT '预估工期(天)', `company_name` varchar(255) DEFAULT NULL COMMENT '供应商名称', `username` varchar(128) DEFAULT NULL COMMENT '供应商账号', `email` varchar(255) DEFAULT NULL COMMENT '邮箱', `phone` varchar(255) DEFAULT NULL COMMENT '手机号', `createtime` datetime DEFAULT NULL COMMENT '下发时间', `status` varchar(255) DEFAULT NULL COMMENT '数值状态', `status_name` varchar(255) DEFAULT NULL COMMENT '状态名', `remark` varchar(500) DEFAULT NULL COMMENT '供应商整单备注', PRIMARY KEY (`id`), KEY `idx_scydgy_id` (`scydgy_id`), KEY `idx_CCYDH` (`CCYDH`(64)), KEY `idx_company_name` (`company_name`(64)), KEY `idx_status_name` (`status_name`(32)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='协助采购报价明细'"); $changed = true; } catch (\Throwable $e) { Log::write('ProcuremenSchema create purchase_order_detail: ' . $e->getMessage(), 'error'); } } $changed = self::ensureColumns('purchase_order_detail', [ 'amount' => "`amount` varchar(255) DEFAULT NULL COMMENT '单价/金额'", 'delivery' => "`delivery` datetime DEFAULT NULL COMMENT '交货日期'", 'delivery_createtime' => "`delivery_createtime` datetime DEFAULT NULL COMMENT '报价提交时间'", 'lead_days' => "`lead_days` int unsigned DEFAULT NULL COMMENT '预估工期(天)'", 'company_name' => "`company_name` varchar(255) DEFAULT NULL COMMENT '供应商名称'", 'username' => "`username` varchar(128) DEFAULT NULL COMMENT '供应商账号'", 'email' => "`email` varchar(255) DEFAULT NULL COMMENT '邮箱'", 'phone' => "`phone` varchar(255) DEFAULT NULL COMMENT '手机号'", 'status' => "`status` varchar(255) DEFAULT NULL COMMENT '数值状态'", 'status_name' => "`status_name` varchar(255) DEFAULT NULL COMMENT '状态名'", 'remark' => "`remark` varchar(500) DEFAULT NULL COMMENT '供应商整单备注'", 'CYJMC' => "`CYJMC` varchar(255) DEFAULT NULL COMMENT '印件名称'", 'CGYMC' => "`CGYMC` varchar(255) DEFAULT NULL COMMENT '工序名称'", 'CCYDH' => "`CCYDH` varchar(255) DEFAULT NULL COMMENT '订单号'", 'scydgy_id' => "`scydgy_id` int DEFAULT NULL COMMENT '工序行ID'", 'createtime' => "`createtime` datetime DEFAULT NULL COMMENT '下发时间'", ]) || $changed; return $changed; } protected static function ensurePurchaseOrderOperLogTable(): bool { if (self::tableExists('purchase_order_oper_log')) { return self::ensureColumns('purchase_order_oper_log', [ 'scydgy_id' => "`scydgy_id` int NOT NULL DEFAULT 0 COMMENT '工序行ID'", 'purchase_order_id' => "`purchase_order_id` int unsigned DEFAULT NULL COMMENT 'purchase_order.id'", 'admin_id' => "`admin_id` int unsigned DEFAULT NULL COMMENT '操作人'", 'admin_name' => "`admin_name` varchar(64) NOT NULL DEFAULT '' COMMENT '操作人快照'", 'action' => "`action` varchar(64) NOT NULL DEFAULT '' COMMENT '动作码'", 'content' => "`content` varchar(1000) NOT NULL DEFAULT '' COMMENT '描述'", 'createtime' => "`createtime` int unsigned NOT NULL DEFAULT 0 COMMENT 'Unix时间戳'", ]); } try { Db::execute("CREATE TABLE IF NOT EXISTS `purchase_order_oper_log` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `scydgy_id` int NOT NULL DEFAULT 0 COMMENT '工序行 scydgy.ID', `purchase_order_id` int unsigned DEFAULT NULL COMMENT 'purchase_order.id', `admin_id` int unsigned DEFAULT NULL COMMENT '操作人 admin.id', `admin_name` varchar(64) NOT NULL DEFAULT '' COMMENT '操作人昵称/账号快照', `action` varchar(64) NOT NULL DEFAULT '' COMMENT '动作码', `content` varchar(1000) NOT NULL DEFAULT '' COMMENT '中文描述', `createtime` int unsigned NOT NULL DEFAULT 0 COMMENT 'Unix 时间戳', PRIMARY KEY (`id`), KEY `idx_scydgy_ct` (`scydgy_id`,`createtime`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='协助采购操作日志'"); return true; } catch (\Throwable $e) { Log::write('ProcuremenSchema create purchase_order_oper_log: ' . $e->getMessage(), 'error'); return false; } } protected static function ensurePurchaseOrderBidOpenTable(): bool { if (self::tableExists('purchase_order_bid_open')) { return false; } try { Db::execute("CREATE TABLE IF NOT EXISTS `purchase_order_bid_open` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `ccydh` varchar(64) NOT NULL DEFAULT '' COMMENT '订单号', `verifier1_id` int unsigned NOT NULL DEFAULT 0, `verifier1_name` varchar(64) NOT NULL DEFAULT '', `verifier2_id` int unsigned NOT NULL DEFAULT 0, `verifier2_name` varchar(64) NOT NULL DEFAULT '', `operator_id` int unsigned NOT NULL DEFAULT 0, `operator_name` varchar(64) NOT NULL DEFAULT '', `createtime` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_ccydh` (`ccydh`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开标双重验证'"); return true; } catch (\Throwable $e) { Log::write('ProcuremenSchema create purchase_order_bid_open: ' . $e->getMessage(), 'error'); return false; } } protected static function ensurePurchaseOrderInboundScoreTable(): bool { $changed = false; if (!self::tableExists('purchase_order_inbound_score')) { try { Db::execute("CREATE TABLE IF NOT EXISTS `purchase_order_inbound_score` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `scydgy_id` int NOT NULL DEFAULT 0 COMMENT '工序ID', `purchase_order_id` int unsigned NOT NULL DEFAULT 0 COMMENT 'purchase_order.id', `ccydh` varchar(64) NOT NULL DEFAULT '' COMMENT '订单号', `cyjmc` varchar(255) NOT NULL DEFAULT '' COMMENT '印件名称', `cgymc` varchar(255) NOT NULL DEFAULT '' COMMENT '工序名称', `company_name` varchar(255) NOT NULL DEFAULT '' COMMENT '中标供应商', `result` varchar(16) NOT NULL DEFAULT '' COMMENT '合格/不合格', `delivery_status` varchar(16) NOT NULL DEFAULT '' COMMENT '交货情况 准时/滞后', `delivery_date` date DEFAULT NULL COMMENT '交货日期', `customer_complaint` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '客户是否投诉 1=是', `complaint_remark` varchar(500) NOT NULL DEFAULT '' COMMENT '投诉说明', `order_interrupt` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '订单是否中断 1=是', `admin_id` int unsigned NOT NULL DEFAULT 0, `admin_name` varchar(64) NOT NULL DEFAULT '', `remark` varchar(500) NOT NULL DEFAULT '', `createtime` datetime DEFAULT NULL, `updatetime` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_scydgy_id` (`scydgy_id`), KEY `idx_ccydh` (`ccydh`), KEY `idx_result` (`result`), KEY `idx_updatetime` (`updatetime`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='质量/交货评分'"); $changed = true; } catch (\Throwable $e) { Log::write('ProcuremenSchema create inbound_score: ' . $e->getMessage(), 'error'); } } return self::ensureColumns('purchase_order_inbound_score', [ 'delivery_status' => "`delivery_status` varchar(16) NOT NULL DEFAULT '' COMMENT '交货情况 准时/滞后'", 'delivery_date' => "`delivery_date` date DEFAULT NULL COMMENT '交货日期'", 'customer_complaint' => "`customer_complaint` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '客户是否投诉 1=是'", 'complaint_remark' => "`complaint_remark` varchar(500) NOT NULL DEFAULT '' COMMENT '投诉说明'", 'order_interrupt' => "`order_interrupt` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '订单是否中断 1=是'", 'result' => "`result` varchar(16) NOT NULL DEFAULT '' COMMENT '合格/不合格'", 'company_name' => "`company_name` varchar(255) NOT NULL DEFAULT '' COMMENT '中标供应商'", 'ccydh' => "`ccydh` varchar(64) NOT NULL DEFAULT '' COMMENT '订单号'", 'scydgy_id' => "`scydgy_id` int NOT NULL DEFAULT 0 COMMENT '工序ID'", ]) || $changed; } protected static function ensurePurchaseEmailSendLogTable(): bool { if (self::tableExists('purchase_email_send_log')) { return false; } try { Db::execute("CREATE TABLE IF NOT EXISTS `purchase_email_send_log` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `from_email` varchar(128) NOT NULL DEFAULT '' COMMENT '发件邮箱', `to_email` varchar(128) NOT NULL DEFAULT '' COMMENT '收件邮箱', `send_time` datetime DEFAULT NULL COMMENT '发送时间', `scene` varchar(32) NOT NULL DEFAULT '' COMMENT '场景', `ccydh` varchar(64) NOT NULL DEFAULT '' COMMENT '订单号', `company_name` varchar(128) NOT NULL DEFAULT '' COMMENT '供应商名称', `mail_subject` varchar(255) NOT NULL DEFAULT '' COMMENT '邮件主题', `status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '1成功 0失败', `fail_reason` varchar(500) NOT NULL DEFAULT '' COMMENT '失败原因', `admin_id` int unsigned NOT NULL DEFAULT 0 COMMENT '操作人ID', `admin_name` varchar(64) NOT NULL DEFAULT '' COMMENT '操作人', `scydgy_id` int NOT NULL DEFAULT 0 COMMENT '工序行ID', `purchase_order_id` int unsigned NOT NULL DEFAULT 0 COMMENT 'purchase_order.id', `createtime` datetime DEFAULT NULL COMMENT '记录创建时间', PRIMARY KEY (`id`), KEY `idx_send_time` (`send_time`), KEY `idx_to_email` (`to_email`), KEY `idx_ccydh` (`ccydh`), KEY `idx_scene` (`scene`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='协助采购邮件发送日志'"); return true; } catch (\Throwable $e) { Log::write('ProcuremenSchema create email_send_log: ' . $e->getMessage(), 'error'); return false; } } protected static function ensurePurchaseSmsTemplateTable(): bool { if (self::tableExists('purchase_sms_template')) { return self::ensureColumns('purchase_sms_template', [ 'scene' => "`scene` varchar(32) DEFAULT NULL COMMENT '场景码'", 'title' => "`title` varchar(100) DEFAULT '' COMMENT '模版名称'", 'content' => "`content` text COMMENT '正文'", 'remark' => "`remark` varchar(255) DEFAULT '' COMMENT '备注'", 'status' => "`status` varchar(30) DEFAULT 'normal'", ]); } try { Db::execute("CREATE TABLE IF NOT EXISTS `purchase_sms_template` ( `id` int unsigned NOT NULL AUTO_INCREMENT, `scene` varchar(32) DEFAULT NULL COMMENT '场景码', `title` varchar(100) DEFAULT '' COMMENT '模版名称', `content` text COMMENT '正文', `remark` varchar(255) DEFAULT '' COMMENT '备注', `status` varchar(30) DEFAULT 'normal', `createtime` datetime DEFAULT NULL, `updatetime` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_scene` (`scene`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='协助采购短信/邮件模版'"); return true; } catch (\Throwable $e) { Log::write('ProcuremenSchema create sms_template: ' . $e->getMessage(), 'error'); return false; } } }