<?php
namespace app\adminVue\controller;

use think\facade\Db;

/** Employee IDs come from the intranet; detail IDs and employee IDs are distinct. */
abstract class V2EmployeeImport
{
    const MAIN = ['id','eid','ename','sex','tid','hc_tid','poid','temp_skill_id','changed','indirect','hiredate','status','on_job','lock','position_mark','leavedate','mark','cost_center','update'];
    const DETAIL = ['id','m_id','ename','workday_id','sex','identity','phone','edu','address','supplier','source_type','category','size','level','classification','annual_start','transfer_date','contract_date','urgency','relation','urgency_phone','nation','armoire','dorm','laber_type'];
    const NEW_FIELDS = ['id','eid','ename','sex','edu','phone','hiredate','supplier','source_type','identity','address','cost_center'];
    const EDIT = ['ename','phone','supplier','source_type','identity','address'];
    const LABELS = ['id'=>'ID','m_id'=>'员工ID','eid'=>'工号','ename'=>'姓名','sex'=>'性别','tid'=>'班组','hc_tid'=>'HC班组','poid'=>'岗位','temp_skill_id'=>'临时技能','changed'=>'变化点','indirect'=>'直接/间接','hiredate'=>'入职日期','status'=>'状态','on_job'=>'在职时长类型','lock'=>'锁定','position_mark'=>'岗位备注','leavedate'=>'最后工作日','mark'=>'备注','cost_center'=>'成本中心','update'=>'更新时间','workday_id'=>'Workday ID','identity'=>'身份证','phone'=>'联系电话','edu'=>'学历','address'=>'地址','supplier'=>'劳务公司','source_type'=>'劳务类型','category'=>'员工类型','size'=>'工衣尺码','level'=>'等级','classification'=>'员工分类','annual_start'=>'年假起始日','transfer_date'=>'转合同工日期','contract_date'=>'合同日期','urgency'=>'紧急联系人','relation'=>'联系人关系','urgency_phone'=>'紧急联系电话','nation'=>'民族','armoire'=>'衣柜','dorm'=>'宿舍','laber_type'=>'劳务分类'];

    public static function text($value,$length,$label,$required=false,$multiline=false)
    {
        if ($value===null) $value='';
        if (!is_scalar($value)) throw new \InvalidArgumentException($label.'格式不正确');
        $value=trim((string)$value);
        $invalid=$multiline?'/[<>\x00-\x08\x0B\x0C\x0E-\x1F]/u':'/[<>\x00-\x1F]/u';
        if (($required && $value==='') || mb_strlen($value)>$length || preg_match($invalid,$value)) throw new \InvalidArgumentException($label.'为空、超长或包含无效字符');
        return $value;
    }

    public static function integer($value,$label,$min=0,$max=2147483647)
    {
        if (!is_scalar($value) || !preg_match('/^[0-9]+$/D',(string)$value) || (float)$value<$min || (float)$value>$max) throw new \InvalidArgumentException($label.'须为有效整数');
        return (int)$value;
    }

    public static function source($value)
    {
        $value=self::text($value,20,'劳务类型');
        $map=['小时工'=>'1','社会工'=>'2','合同工'=>'2','学生工'=>'3'];
        return $map[$value] ?? $value;
    }

    public static function sourceLabel($value)
    {
        $value=self::source($value);
        return ['1'=>'小时工','2'=>'合同工','3'=>'学生工'][$value] ?? ($value===''?'—':'原值：'.$value);
    }

    public static function educationLabel($value)
    {
        return ['1'=>'小学','2'=>'初中','3'=>'高中','4'=>'大专','5'=>'本科'][(string)$value] ?? ((string)$value===''?'—':(ctype_digit((string)$value)?'原编码：'.$value:(string)$value));
    }

    public static function dateValue($value,$label,$required=false)
    {
        if ($value==='' || $value===null || (string)$value==='0') {
            if ($required) throw new \InvalidArgumentException($label.'必填');
            return 0;
        }
        if (!is_scalar($value)) throw new \InvalidArgumentException($label.'格式无效');
        if (is_numeric($value)) {
            $number=(float)$value;
            if ($number>=1 && $number<100000) {
                $value=gmdate('Y-m-d',(int)round(($number-25569)*86400));
            } elseif ($number>=315504000 && $number<=2147483647 && floor($number)===$number) return (int)$number;
            else throw new \InvalidArgumentException($label.'须为Excel日期、日期文字或有效时间戳');
        }
        $value=trim((string)$value);
        foreach (['Y-m-d','Y/n/j','Y/n/d','Y-m-d H:i:s'] as $format) {
            $date=\DateTimeImmutable::createFromFormat('!'.$format,$value);
            $errors=\DateTimeImmutable::getLastErrors();
            if ($date && (!$errors || (!$errors['warning_count'] && !$errors['error_count'])) && $date->format($format)===$value && $date->getTimestamp()>0 && $date->getTimestamp()<=2147483647) return $date->getTimestamp();
        }
        throw new \InvalidArgumentException($label.'格式无效，请使用YYYY-MM-DD');
    }

    public static function identity($value,$adult=false)
    {
        $value=strtoupper(self::text($value,20,'身份证',$adult));
        if ($value==='') return '';
        if (!preg_match('/^[1-9][0-9]{16}[0-9X]$/D',$value)) throw new \InvalidArgumentException('身份证格式无效');
        $birthday=substr($value,6,8);
        $date=\DateTimeImmutable::createFromFormat('!Ymd',$birthday);
        if (!$date || $date->format('Ymd')!==$birthday || $date>new \DateTimeImmutable('today')) throw new \InvalidArgumentException('身份证出生日期无效');
        $weights=[7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2];$sum=0;
        foreach ($weights as $index=>$weight) $sum+=(int)$value[$index]*$weight;
        if ('10X98765432'[$sum%11]!==$value[17]) throw new \InvalidArgumentException('身份证校验位无效');
        if ($adult && $date->diff(new \DateTimeImmutable('today'))->y<18) throw new \InvalidArgumentException('新员工未满18岁');
        return $value;
    }

    public static function editValues($input,$old)
    {
        if (!is_array($input) || array_diff(array_keys($input),self::EDIT)) throw new \InvalidArgumentException('编辑字段无效');
        $data=[];
        foreach ($input as $field=>$value) {
            $data[$field]=self::normalize($field,$value,$old[$field] ?? null,false);
        }
        if (!array_key_exists('ename',$data)) throw new \InvalidArgumentException('姓名必填');
        return $data;
    }

    private static function normalize($field,$value,$old,$new)
    {
        $label=self::LABELS[$field] ?? $field;
        if ($field==='eid' && !is_string($value)) throw new \InvalidArgumentException('工号须为文本格式，请保留前导零');
        if (in_array($field,['hiredate','leavedate','annual_start','transfer_date','contract_date','update'],true)) return self::dateValue($value,$label,$field==='hiredate');
        if ($field==='identity') {
            // Historical values remain editable without silently rewriting them.
            if (!$new && (string)$value===(string)$old) return $old;
            return self::identity($value,$new);
        }
        if ($field==='source_type') {
            $v=self::source($value);
            if (!$new && $v===self::source($old)) return $old;
            if (!in_array($v,['','0','1','2','3','重庆社保'],true)) throw new \InvalidArgumentException('劳务类型无效');
            if ($new && !in_array($v,['1','2','3'],true)) throw new \InvalidArgumentException('新员工劳务类型须为小时工、合同工或学生工');
            return $v;
        }
        if ($field==='edu') {
            $v=self::text($value,20,$label,$new);
            if (!$new && $v==='中专') return $v; // Keep the supplied label; no verified numeric mapping exists.
            $map=['小学'=>'1','初中'=>'2','高中'=>'3','大专'=>'4','本科'=>'5'];
            $v=$map[$v] ?? $v;
            if (!in_array($v,['','0','1','2','3','4','5','6','7'],true) && $v!==(string)$old) throw new \InvalidArgumentException('学历无效');
            return $v;
        }
        $enums=['sex'=>['男'=>1,'女'=>0],'status'=>['在职'=>1,'离职'=>2],'indirect'=>['直接'=>1,'间接'=>2]];
        if (isset($enums[$field])) {
            if (!is_scalar($value)) throw new \InvalidArgumentException($label.'格式无效');
            $v=$enums[$field][(string)$value] ?? $value;
            $allowed=in_array($field,['status','indirect'],true)?[1,2]:[0,1];
            if (!in_array((string)$v,array_map('strval',$allowed),true)) throw new \InvalidArgumentException($label.'编码无效');
            return (int)$v;
        }
        $numbers=['tid'=>2147483647,'hc_tid'=>2147483647,'poid'=>2147483647,'temp_skill_id'=>2147483647,'changed'=>5,'on_job'=>5,'lock'=>1,'laber_type'=>9];
        if (isset($numbers[$field])) {
            if (($value==='' || $value===null) && !in_array($field,['tid','indirect','lock'],true)) return null;
            return self::integer($value,$label,0,$numbers[$field]);
        }
        if ($field==='level') {
            if ($value==='' || $value===null) return null;
            if (!is_scalar($value) || !is_numeric($value) || (float)$value<0 || (float)$value>9.9 || round((float)$value,1)!==(float)$value) throw new \InvalidArgumentException('等级须为0至9.9的一位小数');
            return (float)$value;
        }
        $lengths=['ename'=>10,'eid'=>20,'workday_id'=>20,'phone'=>30,'address'=>50,'supplier'=>50,'position_mark'=>50,'mark'=>50,'category'=>10,'size'=>10,'classification'=>5,'urgency'=>50,'relation'=>255,'urgency_phone'=>20,'nation'=>50,'armoire'=>10,'dorm'=>50];
        $v=self::text($value,$lengths[$field] ?? 50,$label,in_array($field,['ename','eid'],true),in_array($field,['address','mark','position_mark','relation'],true));
        if ($field==='eid' && preg_match('/\s/u',$v)) throw new \InvalidArgumentException('工号不能含空白字符');
        return $v;
    }

    private static function parse($input,$mode)
    {
        if (!is_array($input) || !$input) throw new \InvalidArgumentException('数据行无效');
        $aliases=['ID'=>'id','员工ID'=>'m_id','工号'=>'eid','姓名'=>'ename','性别'=>'sex','学历'=>'edu','电话'=>'phone','联系电话'=>'phone','入职日期'=>'hiredate','劳务公司'=>'supplier','来源类型'=>'source_type','劳务类型'=>'source_type','身份证'=>'identity','身份证号'=>'identity','家庭住址'=>'address','家庭地址'=>'address','成本中心'=>'cost_center'];
        $allowed=$mode==='new'?self::NEW_FIELDS:($mode==='detail'?self::DETAIL:self::MAIN);
        $row=[];
        foreach ($input as $key=>$value) {
            if ($key==='__line') continue;
            $field=$aliases[trim((string)$key)] ?? trim((string)$key);
            if (!in_array($field,$allowed,true)) throw new \InvalidArgumentException('存在不支持的列：'.self::text($key,80,'表头'));
            if (array_key_exists($field,$row)) throw new \InvalidArgumentException('重复字段：'.self::LABELS[$field]);
            $row[$field]=$value;
        }
        return $row;
    }

    private static function selected($table,$fields,$ids,$keys=[],$keyField='eid')
    {
        if (!$ids && !$keys) return [];
        return Db::name($table)->where(function($q)use($ids,$keys,$keyField){
            if ($ids) $q->whereIn('id',$ids);
            if ($keys) { if ($ids) $q->whereOr($keyField,'in',$keys); else $q->whereIn($keyField,$keys); }
        })->field(implode(',',$fields))->order('id')->select()->toArray();
    }

    public static function plan($rows,$mode)
    {
        if ($mode!=='new' || !is_array($rows) || !$rows || count($rows)>5000) throw new \InvalidArgumentException('请上传1至5000行新员工数据');
        $prepared=[];$errors=[];$ids=[];$eids=[];
        foreach (array_values($rows) as $index=>$input) {
            $line=is_array($input) && isset($input['__line'])?self::integer($input['__line'],'Excel行号',2,10000000):$index+2;
            try {
                $row=self::parse($input,'new');$row['id']=self::integer($row['id'] ?? null,'ID',1);
                $row['eid']=self::normalize('eid',$row['eid'] ?? null,null,true);
                $ids[]=$row['id'];$eids[]=$row['eid'];$prepared[]=['line'=>$line,'row'=>$row];
            } catch (\InvalidArgumentException $e) { $errors[]=['line'=>$line,'message'=>$e->getMessage()]; }
        }
        $members=self::selected('member_list',self::MAIN,$ids,$eids);
        $details=self::selected('member_interview',self::DETAIL,[],$ids,'m_id');
        $team=Db::name('team_list')->where('id',99)->field('id,status')->find();
        $costs=Db::name('team_base')->where('mid',8)->field('id,content')->order('id')->select()->toArray();
        $byId=array_column($members,null,'id');$byMid=array_column($details,null,'m_id');$byEid=[];
        foreach ($members as $member) $byEid[strtolower(trim($member['eid']))]=true;
        $preview=[];$changes=[];$seen=[];$seenEids=[];
        foreach ($prepared as $item) {
            $row=$item['row'];$line=$item['line'];$id=$row['id'];$eid=strtolower($row['eid']);
            try {
                if (isset($seen[$id])) throw new \InvalidArgumentException('ID重复，首次出现在第'.$seen[$id].'行');
                $seen[$id]=$line;
                if (isset($seenEids[$eid])) throw new \InvalidArgumentException('工号重复，首次出现在第'.$seenEids[$eid].'行');
                $seenEids[$eid]=$line;
                if (isset($byId[$id]) || isset($byMid[$id])) throw new \InvalidArgumentException('来源ID已存在，新员工导入不会覆盖历史档案');
                if (isset($byEid[$eid])) throw new \InvalidArgumentException('工号已存在（含离职历史），请核对来源ID');
                if (!$team) throw new \InvalidArgumentException('默认班组99不存在');
                foreach (['ename','sex','hiredate','edu','source_type','identity','cost_center'] as $required) if (!array_key_exists($required,$row)) throw new \InvalidArgumentException(self::LABELS[$required].'必填');
                $main=['id'=>$id,'eid'=>$row['eid'],'tid'=>99,'hc_tid'=>99,'indirect'=>1,'status'=>1,'changed'=>0];
                foreach (['ename','sex','hiredate'] as $field) $main[$field]=self::normalize($field,$row[$field],null,true);
                $code=self::text($row['cost_center'],50,'成本中心',true);
                $matches=array_values(array_filter($costs,function($cost)use($code){return (string)$cost['content']===$code;}));
                if (count($matches)!==1) throw new \InvalidArgumentException('成本中心不存在或重名');
                $main['cost_center']=(int)$matches[0]['id'];
                $detail=['m_id'=>$id,'ename'=>$main['ename'],'sex'=>$main['sex']];
                foreach (['edu','phone','supplier','source_type','identity','address'] as $field) if (array_key_exists($field,$row)) $detail[$field]=self::normalize($field,$row[$field],null,true);
                $diff=[];self::delta($main,null,$diff,false);self::delta($detail,null,$diff,true);
                $preview[]=['line'=>$line,'id'=>$id,'employeeId'=>$id,'eid'=>$row['eid'],'name'=>$main['ename'],'action'=>'new','changes'=>$diff];
                $changes[]=['line'=>$line,'employeeId'=>$id,'main'=>$main,'mainNew'=>true,'detail'=>$detail,'detailNew'=>true];
            } catch (\InvalidArgumentException $e) { $errors[]=['line'=>$line,'message'=>$e->getMessage()]; }
        }
        return ['stats'=>['total'=>count($rows),'new'=>count($changes),'update'=>0,'unchanged'=>0,'errors'=>count($errors)],'preview'=>$preview,'errors'=>$errors,'changes'=>$changes,'snapshot'=>hash('sha256',json_encode([$members,$details,$team,$costs]))];
    }

    /** Literal per-sheet upsert. Only file/schema safety is checked; source business values are unchanged. */
    public static function planWorkbook($sheets)
    {
        $tables=['base'=>'team_base','team'=>'team_list','list'=>'member_list','detail'=>'member_interview'];
        $modern=is_array($sheets) && array_key_exists('member_list',$sheets);
        if ($modern) $tables=['team_base'=>'team_base','team_plan_capacity'=>'team_plan_capacity','team_plan_position'=>'team_plan_position','team_list'=>'team_list','member_list'=>'member_list','member_interview'=>'member_interview'];
        if (!is_array($sheets) || count($sheets)!==count($tables) || array_diff(array_keys($sheets),array_keys($tables))) throw new \InvalidArgumentException('请选择六张数据库表名工作表的新文件，或list、detail、team、base旧版四表文件');
        $listSheet=$modern?'member_list':'list';
        if (empty($sheets[$listSheet]) || !is_array($sheets[$listSheet])) throw new \InvalidArgumentException('list不能为空，避免把所有在职员工误设为离职');
        $prefix=config('database.connections.'.config('database.default').'.prefix');
        if (!preg_match('/^[a-zA-Z0-9_]*$/D',$prefix)) throw new \InvalidArgumentException('数据库表配置无效');
        $stats=['total'=>0,'new'=>0,'update'=>0,'unchanged'=>0,'errors'=>0,'leaveCount'=>0];
        $errors=[];$preview=[];$changes=[];$dictionaries=[];$listIds=[];
        foreach ($tables as $sheet=>$table) {
            $rows=$sheets[$sheet];
            if (!is_array($rows) || count($rows)>5000) throw new \InvalidArgumentException($sheet.'工作表最多5000行');
            $stats[$sheet.'Rows']=count($rows);$stats['total']+=count($rows);
            $columns=array_column(Db::query('SHOW COLUMNS FROM `'.$prefix.$table.'`'),null,'Field');
            $ids=[];$seen=[];
            foreach ($rows as $row) if (is_array($row) && isset($row['id']) && is_scalar($row['id']) && preg_match('/^[1-9][0-9]*$/D',(string)$row['id'])) $ids[]=(int)$row['id'];
            $existing=$ids?Db::name($table)->whereIn('id',array_unique($ids))->select()->toArray():[];
            $byId=array_column($existing,null,'id');$counts=['total'=>count($rows),'new'=>0,'update'=>0,'unchanged'=>0];
            foreach (array_values($rows) as $index=>$row) {
                $line=is_array($row) && isset($row['__line'])?self::integer($row['__line'],'Excel行号',2,10000000):$index+2;
                try {
                    if (!is_array($row)) throw new \InvalidArgumentException('数据行格式无效');
                    $id=self::integer($row['id'] ?? null,'ID',1);$data=[];
                    if (isset($seen[$id])) throw new \InvalidArgumentException('ID重复：'.$id);
                    $seen[$id]=true;
                    foreach ($row as $field=>$value) {
                        if ($field==='__line') continue;
                        if (!isset($columns[$field])) throw new \InvalidArgumentException('目标表不存在列：'.self::text($field,80,'表头'));
                        if (!is_scalar($value) && $value!==null) throw new \InvalidArgumentException('单元格不能包含数组或对象');
                        $data[$field]=$value;
                    }
                    $data['id']=$id;$old=$byId[$id] ?? null;$action=$old?'update':'new';$differences=[];
                    foreach ($data as $field=>$value) if ($field!=='id' && (!$old || $value!==($old[$field] ?? null))) {
                        $differences[]=['field'=>$field,'before'=>self::rawPreview($field,$old[$field] ?? null),'after'=>self::rawPreview($field,$value)];
                    }
                    $counts[$action]++;$stats[$action]++;
                    $preview[]=['sheet'=>$sheet,'line'=>$line,'id'=>$id,'employeeId'=>$id,'eid'=>$data['eid'] ?? '', 'name'=>$data['ename'] ?? $data['team'] ?? $data['title'] ?? '', 'action'=>$action,'changes'=>$differences];
                    $changes[]=['table'=>$table,'sheet'=>$sheet,'id'=>$id,'line'=>$line,'data'=>$data,'new'=>!$old];
                    $byId[$id]=array_merge($old ?: [],$data);
                    if ($table==='member_list') $listIds[$id]=true;
                } catch (\InvalidArgumentException $e) { $errors[]=['sheet'=>$sheet,'line'=>$line,'message'=>$e->getMessage()]; }
            }
            if (in_array($table,['team_base','team_list','team_plan_capacity','team_plan_position'],true)) $dictionaries[$sheet]=$counts;
        }
        if ($modern) {
            $plans=array_column($sheets['team_plan_capacity'],null,'id');
            foreach ($sheets['team_list'] as $index=>$team) {
                $planId=(int)($team['plan_id'] ?? 0);
                if ((int)($team['status'] ?? 0)===1 && $planId>0 && !isset($plans[$planId])) $errors[]=['sheet'=>'team_list','line'=>$index+2,'message'=>'有效班组引用的方案未包含在文件中：'.$planId];
            }
            foreach ($sheets['team_plan_position'] as $index=>$position) if (!isset($plans[$position['plan_id'] ?? 0])) $errors[]=['sheet'=>'team_plan_position','line'=>$index+2,'message'=>'岗位引用的方案未包含在文件中'];
        }
        $leavers=[];
        $active=Db::name('member_list')->where('status',1)->field('id,eid,ename,mark')->order('id')->select()->toArray();
        $markColumn=Db::query("SHOW COLUMNS FROM `".$prefix."member_list` LIKE 'mark'")[0];
        preg_match('/\((\d+)\)/',$markColumn['Type'],$length);$maxLength=(int)($length[1] ?? 50);
        foreach ($active as $row) if (!isset($listIds[(int)$row['id']])) {
            $mark=(string)($row['mark'] ?? '');$note='内网同步更新为离职';
            if (mb_strpos($mark,$note)===false) $mark=$mark===''?$note:$mark.'；'.$note;
            if (mb_strlen($mark)>$maxLength) { $errors[]=['sheet'=>$listSheet,'line'=>0,'message'=>'员工ID '.$row['id'].'的备注不足以保留原文并追加同步说明（上限'.$maxLength.'字）'];continue; }
            $leavers[]=['id'=>(int)$row['id'],'eid'=>$row['eid'],'name'=>$row['ename'],'beforeMark'=>$row['mark'] ?? '', 'afterMark'=>$mark];
            // Appended after all four sheets, so any preceding write failure skips departure reconciliation.
            $changes[]=['table'=>'member_list','sheet'=>'名单外离职','id'=>(int)$row['id'],'line'=>0,'data'=>['status'=>2,'mark'=>$mark],'new'=>false,'leave'=>true];
        }
        $stats['errors']=count($errors);$stats['leaveCount']=count($leavers);
        return ['stats'=>$stats,'preview'=>$preview,'changes'=>$changes,'errors'=>$errors,'dictionaries'=>$dictionaries,'leavers'=>$leavers,'snapshot'=>''];
    }

    private static function rawPreview($field,$value)
    {
        if ($value===null) return 'NULL';
        if (in_array($field,['identity','phone','urgency_phone','address'],true)) return self::display($field,$value);
        return (string)$value;
    }

    private static function delta($data,$old,&$diff,$detail)
    {
        $patch=[];
        foreach ($data as $field=>$value) {
            if (in_array($field,['id','m_id'],true)) continue;
            if (!$old || (string)($old[$field] ?? '')!==(string)$value) {
                $patch[$field]=$value;
                $diff[]=['field'=>($detail?'详情 · ':'').(self::LABELS[$field] ?? $field),'before'=>self::display($field,$old[$field] ?? null),'after'=>self::display($field,$value)];
            }
        }
        if (!$old) return $data;
        if ($patch) { $patch['id']=$old['id'];if ($detail) $patch['m_id']=$old['m_id']; }
        return $patch;
    }

    private static function display($field,$value)
    {
        if ($value===null || (string)$value==='') return '—';
        $value=(string)$value;
        if (in_array($field,['identity','phone','urgency_phone'],true)) return mb_strlen($value)>7?mb_substr($value,0,3).'****'.mb_substr($value,-4):'****';
        if ($field==='address') return '（已填写）';
        if ($field==='status') return ['1'=>'在职','2'=>'离职'][$value] ?? $value;
        if ($field==='sex') return ['1'=>'男','0'=>'女'][$value] ?? $value;
        if ($field==='indirect') return ['1'=>'直接','2'=>'间接'][$value] ?? $value;
        if ($field==='source_type') return self::sourceLabel($value);
        if ($field==='edu') return self::educationLabel($value);
        if (in_array($field,['hiredate','leavedate','annual_start','transfer_date','contract_date','update'],true)) return (int)$value>0?date($field==='update'?'Y-m-d H:i:s':'Y-m-d',(int)$value):'—';
        return $value;
    }

    public static function version($main,$detail)
    {
        return hash('sha256',json_encode([$main,$detail]));
    }

    public static function locked($callback)
    {
        $config=config('database.connections.'.config('database.default'));$prefix=$config['prefix'];
        if (!preg_match('/^[a-zA-Z0-9_]*$/D',$prefix)) throw new \RuntimeException('数据库配置无效');
        $lock='employee:'.substr(hash('sha256',$config['database'].'|'.$prefix),0,48);
        $got=Db::query('SELECT GET_LOCK(:name,5) acquired',['name'=>$lock]);
        if ((int)$got[0]['acquired']!==1) throw new \RuntimeException('其他员工资料正在保存，请稍后重试');
        $pdo=null;
        try {
            $pdo=Db::connect()->getConnection()->getPdo();
            $tables=[];
            foreach (['member_list'=>'WRITE','member_interview'=>'WRITE','team_list'=>'WRITE','team_base'=>'WRITE','team_plan_capacity'=>'WRITE','team_plan_position'=>'WRITE','member_position_request'=>'WRITE'] as $table=>$access) $tables[]='`'.$prefix.$table.'` '.$access;
            $pdo->exec('SET SESSION lock_wait_timeout=10');
            $pdo->exec('LOCK TABLES '.implode(',',$tables));
            return $callback();
        } finally { if ($pdo) $pdo->exec('UNLOCK TABLES');Db::query('SELECT RELEASE_LOCK(:name)',['name'=>$lock]); }
    }
}
