[ * '字段名' => '产品类型', // 如 'serial_no' => 'PCBA' * ] * * 运行时从 sys_config.code_prefix_rules 读取产品类型对应的前缀, * 自动生成 codePrefix 验证规则。如果该产品类型未配置前缀(空字符串),则跳过验证。 * * @param array|null $fieldMappings 字段到产品类型的映射 * @return array 格式化后的 codePrefix 规则 */ function buildCodePrefixRules($fieldMappings, $prefixTypeMap = null) { if (empty($fieldMappings)) return []; // 如果未传入前缀映射,从 SysConfig 读取(兼容旧用法) if ($prefixTypeMap === null) { try { $config = new \app\models\SysConfig(); $prefixTypeMap = $config->getCodePrefixRules(); } catch (\Throwable $e) { error_log('[buildCodePrefixRules] getCodePrefixRules failed: ' . $e->getMessage()); $prefixTypeMap = []; } } if (!is_array($prefixTypeMap)) $prefixTypeMap = []; $rules = []; foreach ($fieldMappings as $fieldName => $productType) { // 动态前缀:*dynamic* 标记表示根据提交时的 product_type 动态匹配 if ($productType === '*dynamic*') { $rules[$fieldName] = ['*dynamic*', '序列号前缀与产品类型不匹配']; continue; } $prefix = $prefixTypeMap[$productType] ?? ''; // 原则:所有工位序列号都受前缀管控。类型已配置但前缀为空时, // 自动回退为产品类型名拼音首字母(如 外壳→WK、包装→BZ、半成品→BCP、配件→PJ)。 if ($prefix === '') { try { $sc = new \app\models\SysConfig(); $prefix = $sc->pinyinInitials($productType); } catch (\Throwable $e) { error_log('[buildCodePrefixRules] pinyinInitials failed: ' . $e->getMessage()); $prefix = ''; } } if ($prefix === '') continue; // 实在无法解析(如类型根本未配置)才跳过 $rules[$fieldName] = [$prefix, "{$productType}序列号必须以 {$prefix} 开头"]; } return $rules; } /** * 获取指定工位的业务配置 * @param string $stationType 工位类型标识 * @return array|null 业务配置数组,null 表示使用通用 station_records 存储 */ function getStationBusinessConfig($stationType) { // ========== 所有工位统一走自动生成路径 ========== // // 之前的内置工位配置(inbound, pcb_test, battery, assembly, finished_test, // delivery, warehouse)已全部移除。 // // 现在所有工位(包括内置和动态)都通过 FrontController::buildGenericBizConfig() // 自动生成业务配置,确保与 aging/semi_finished 等已验证正常的工位使用完全相同的代码路径。 // // 特殊业务逻辑(如 delivery 的箱序列号、inbound 的成品入库切换等) // 将逐步在 FrontController::handleBuiltinSubmit() 中按 stationType 判断实现。 // // 如需恢复某个工位的特殊配置,请以 aging/semi_finished 的自动生成配置为基准, // 在此基础上增量添加,避免重写整个配置导致不一致。 $configs = [ // ========== 成品入库工位(已合并到入库) ========== // warehouse 重定向到 inbound,保留此逻辑 'warehouse' => [ 'model' => 'StationDefinition', 'redirect' => 'inbound', ], ]; if (!isset($configs[$stationType])) { return null; } $bizConfig = $configs[$stationType]; // 动态构建 codePrefix:从 codePrefixFields 映射到实际前缀规则 if (!empty($bizConfig['codePrefixFields'])) { $bizConfig['codePrefix'] = buildCodePrefixRules($bizConfig['codePrefixFields']); } return $bizConfig; } /** * 获取工位的测试状态颜色映射(用于 labelFields 动态颜色) */ function getTestStatusColor($status) { $map = [ 'passed' => 'success', 'failed' => 'danger', 'pending' => 'warning', '通过' => 'success', '失败' => 'danger', '待测试' => 'warning', ]; return $map[$status] ?? 'default'; }