<?php
namespace app\mobile\controller;

use think\facade\Db;
use app\mobile\service\PositionDevice;

/** 岗位设备与全部设备入口；身份、周期与关联均由服务端校验。 */
class OmsToolsCheck extends OmsBase
{
    private $cardMaps;
    private $bindingMaps=[];
    private $ruleMaps=[];
    private function ruleState($ids,$period) {
        $current=self::periodFor(['shift'=>$period['shift']],$this->now());
        $at=$period['timestamp']===$current['timestamp']?$this->now():$period['timestamp']+7*3600;
        $start=strtotime(date('Y-m-01',$at));$end=strtotime('+1 month',$start);$cutoff=$period['timestamp']===$current['timestamp']?$this->now()+1:min($end,$period['timestamp']+31*3600);
        $monthly=[];foreach(Db::name('me_check_record')->whereIn('m_id',$ids ?: [-1])->where('status',1)->where('update','>=',$start)->where('update','<',$cutoff)->field('m_id,check_id')->distinct(true)->select()->toArray() as $r)$monthly[$r['m_id']][$r['check_id']]=true;
        $closed=[];foreach(Db::name('me_check_close')->whereIn('m_id',$ids ?: [-1])->where(['check_date'=>$period['timestamp'],'shift'=>$period['shift']])->select()->toArray() as $r)$closed[$r['m_id']][$r['check_id']]=$r;
        foreach($ids as $id)$this->ruleMaps[$id]=['monthStart'=>$start,'monthEnd'=>$end,'monthly'=>$monthly[$id]??[],'closed'=>$closed[$id]??[]];
    }
    private function rules($id,$period) {if(!isset($this->ruleMaps[$id]))$this->ruleState([$id],$period);return $this->ruleMaps[$id];}

    private $clauses=[];
    private $freshClauses=false;
    private $pauseMaps=[];
    private $periodRecords=[];
    private $identities=[];
    private $projects;
    private function projects() {
        if($this->projects===null)$this->projects=Db::name('team_base')->where('mid',1)->whereIn('content',[1,2])->orderRaw('CAST(mark AS SIGNED) ASC')->order('id')->column('title','id');
        return $this->projects;
    }
    private function teamQuery() {
        $ids=array_keys($this->projects()) ?: [-1];
        return Db::name('team_list')->where('status',1)->where('type','<',4)->whereIn('project',$ids)->orderRaw('FIELD(project,'.implode(',',array_map('intval',$ids)).')')->order('team,id');
    }
    protected function memberIdentity($a) { if(!array_key_exists('m'.$a['id'],$this->identities))$this->identities['m'.$a['id']]=OmsLeaveApply::memberFor($a);return $this->identities['m'.$a['id']]; }
    protected function adminIdentity($a) { if(!array_key_exists('a'.$a['id'],$this->identities))$this->identities['a'.$a['id']]=OmsVisitor::adminFor($a);return $this->identities['a'.$a['id']]; }
    protected function isManager($a) { if(!array_key_exists('g'.$a['id'],$this->identities))$this->identities['g'.$a['id']]=self::managerFor($a);return $this->identities['g'.$a['id']]; }
    protected function now() { return time(); }
    protected function result($data=null) { return json(['code'=>0,'message'=>'成功','data'=>$data]); }
    protected function number($value, $label='编号') {
        if (!is_scalar($value) || !preg_match('/^[1-9][0-9]{0,9}$/',(string)$value) || (float)$value>2147483647) $this->fail($label.'不正确');
        return (int)$value;
    }
    protected function textValue($value,$required=true,$max=255) {
        if (!is_string($value) || mb_strlen($value)>$max || ($required && trim($value)==='')) $this->fail('请填写有效说明（最多'.$max.'字）');
        return trim($value);
    }
    public static function managerFor($account) {
        $a=OmsVisitor::adminFor($account);
        return $a && (((int)$a['dpt']===2 && in_array((int)$a['job'],[18,19],true)) || ((int)$a['dpt']===5 && in_array((int)$a['job'],[43,48],true)));
    }
    protected function teamsFor($account) {
        $q=$this->teamQuery();
        if (!self::managerFor($account)) {
            $member=OmsLeaveApply::memberFor($account);$admin=OmsVisitor::adminFor($account);
            $q->where(function($q)use($member,$admin){$q->where('id',-1);if($member)$q->whereOr('id',$member['tid']);if($admin)$q->whereOr('aid',$admin['id']);});
        }
        return $q->select()->toArray();
    }
    protected function teamFor($account,$id=0) {
        $member=OmsLeaveApply::memberFor($account);
        if (!$id && $member)$id=(int)$member['tid'];
        foreach($this->teamsFor($account) as $team)if((int)$team['id']===$id)return $team;
        $this->fail('请选择有权限查看的启用班组',403);
    }
    protected function summaryTeam($id) {
        $team=$this->teamQuery()->find($id);
        if(!$team)$this->fail('请选择启用且类型小于4的班组',404);
        return $team;
    }
    public static function periodFor($team,$now) {
        // 点检周期固定为07:00至次日07:00，不依赖考勤班别编码。
        $day=strtotime(date('Y-m-d',$now));$morning=$day+7*3600;$evening=$day+19*3600;
        $timestamp=$now>=$morning?$day:$day-86400;$shift=(int)$team['shift'];
        $canCheck=$shift===1?($now>=$morning && $now<$evening):($shift===2 && ($now<$morning || $now>=$evening));
        return ['date'=>date('Y-m-d',$timestamp),'timestamp'=>$timestamp,'shift'=>$shift,'boundary'=>'07:00','canCheck'=>$canCheck];
    }

    protected function dateValue($value) {
        if (!is_string($value))$this->fail('日期不正确');
        $date=\DateTime::createFromFormat('!Y-m-d',$value);
        if(!$date || $date->format('Y-m-d')!==$value || $date->getTimestamp()>$this->now())$this->fail('请选择有效日期');
        return $date->getTimestamp();
    }
    protected function lockDevice($id,$action) {
        return \app\mobile\service\CheckSyncGuard::run(false,function()use($id,$action){
            $key='oms-check:'.sha1($this->cacheKey((string)$id));$r=Db::query('SELECT GET_LOCK(?,5) acquired',[$key]);
            if((int)$r[0]['acquired']!==1)$this->fail('设备正在保存，请稍后重试',409);
            try{return $action();}finally{Db::query('SELECT RELEASE_LOCK(?)',[$key]);}
        });
    }
    private function clausesFor($type) {
        if(!array_key_exists($type,$this->clauses))$this->clauses[$type]=\app\mobile\service\CheckCatalog::clauses($type,$this->freshClauses);
        return $this->clauses[$type];
    }
    protected function machine($id) {
        $m=Db::name('me_machine_list')->find($id);if(!$m)$this->fail('设备不存在',404);return $m;
    }
    protected function recordsFor($id,$period) {
        $key=$period['timestamp'].'-'.$period['shift'];if(isset($this->periodRecords[$key]))return $this->periodRecords[$key][$id]??[];
        return Db::name('me_check_record')->where(['m_id'=>$id,'check_date'=>$period['timestamp'],'shift'=>$period['shift'],'status'=>1])->order('id')->select()->toArray();
    }
    private function prepareCards($machines,$p) {
        $ids=array_column($machines,'id');$this->ruleState($ids,$p);$key=$p['timestamp'].'-'.$p['shift'];$this->periodRecords[$key]=[];$rows=Db::name('me_check_record')->whereIn('m_id',$ids ?: [-1])->where(['check_date'=>$p['timestamp'],'shift'=>$p['shift'],'status'=>1])->order('id')->select()->toArray();
        foreach($rows as $r)$this->periodRecords[$key][$r['m_id']][]=$r;
        $clauses=[];foreach(array_unique(array_column($machines,'type')) as $type)$clauses[$type]=$this->clausesFor($type);$names=\app\mobile\service\CheckCatalog::names();
        $this->cardMaps=['clauses'=>$clauses,'types'=>$names['types'],'lines'=>$names['lines'],'lineProjects'=>$names['lineProjects'],'members'=>Db::name('member_list')->whereIn('id',array_unique(array_column($rows,'mid')) ?: [-1])->column('ename','id'),'issues'=>Db::name('me_check_issue')->alias('i')->join('me_check_record r','r.id=i.check_id')->whereIn('r.m_id',$ids ?: [-1])->whereIn('i.status',[1,2])->group('r.m_id')->column('COUNT(*)','r.m_id')];
    }
    protected function versionFor($records) { return hash('sha256',json_encode($records)); }
    protected function card($m,$period,$records,$last=0,$team=null) {
        $clauses=$this->clausesFor($m['type']);
        $counts=[];$issueCount=0;$latest=0;$names=[];
        foreach($records as $r){$counts[$r['check_id']]=($counts[$r['check_id']]??0)+1;if((int)$r['result']===2)$issueCount++;$latest=max($latest,(int)$r['update']);$names[(int)$r['mid']]=$this->cardMaps?($this->cardMaps['members'][$r['mid']]??'历史点检人'):(Db::name('member_list')->where('id',$r['mid'])->value('ename') ?: '历史点检人');}
        $rules=$this->rules($m['id'],$period);$need=0;$done=0;foreach($clauses as $c){$monthly=(int)($c['freq']??1)===2;$n=$monthly?1:max(1,(int)$c['num']);$need+=$n;$done+=isset($rules['closed'][$c['id']])||($monthly&&isset($rules['monthly'][$c['id']]))?$n:($monthly?0:min($n,$counts[$c['id']]??0));}
        $pause=$team?$this->pauseFor($m['id'],$team,$period):null;
        $planId=(int)($team['plan_id']??0);if(!isset($this->bindingMaps[$planId]))$this->bindingMaps[$planId]=PositionDevice::bindings($planId);$binding=$this->bindingMaps[$planId][$m['id']]??null;
        return ['positionId'=>$binding['poid']??0,'positionName'=>$binding['positionName']??'','paused'=>(bool)$pause,'pauseDays'=>$pause?(int)$pause['days']:0,'resumeDate'=>$pause?date('Y-m-d',$pause['end_date']):'','id'=>(int)$m['id'],'name'=>$m['m_name'],'asset'=>(string)$m['fixed_number'],'typeId'=>(int)$m['type'],'typeName'=>$this->cardMaps?($this->cardMaps['types'][$m['type']]??'未分类'):(Db::name('me_base')->where('id',$m['type'])->value('title') ?: '未分类'),'projectName'=>$this->cardMaps?(($this->cardMaps['lineProjects'][$m['line']]??'') ?: '未设置项目'):(Db::name('team_base')->alias('l')->leftJoin('team_base p','p.id=l.mark AND p.mid=1')->where('l.id',$m['line'])->value('p.title') ?: '未设置项目'),'lineId'=>(int)$m['line'],'lineName'=>$this->cardMaps?($this->cardMaps['lines'][$m['line']]??'未设置区域'):(Db::name('team_base')->where('id',$m['line'])->value('title') ?: '未设置区域'),'done'=>$done,'total'=>$need,'status'=>$pause?4:($need===0?0:($done===0?1:($done<$need?2:3))),'statusName'=>$pause?'暂停点检':($need===0?'无点检条款':($done===0?'未点检':($done<$need?'进行中':'已完成'))),'issueCount'=>$issueCount,'openIssues'=>$this->cardMaps?(int)($this->cardMaps['issues'][$m['id']]??0):Db::name('me_check_issue')->alias('i')->join('me_check_record r','r.id=i.check_id')->where('r.m_id',$m['id'])->whereIn('i.status',[1,2])->count(),'lastMine'=>$last?date('Y-m-d H:i',$last):'','latest'=>$latest?date('Y-m-d H:i',$latest):'','checker'=>implode('、',$names),'date'=>$period['date'],'shift'=>$period['shift']];
    }
    protected function pauseFor($id,$team,$period) {
        $date=(int)$period['timestamp'];
        if(!isset($this->pauseMaps[$date])){
            $this->pauseMaps[$date]=[];
            foreach(Db::name('me_check_pause')->where('start_date','<=',$date)->where('end_date','>',$date)->order('id')->select()->toArray() as $p)$this->pauseMaps[$date][$p['tid'].'-'.$p['m_id']]=$p;
        }
        return $this->pauseMaps[$date][$team['id'].'-'.$id]??null;
    }
    public function pause() {
        return $this->guarded(function(){
            $this->requireMethod('POST');$a=$this->current();$team=$this->teamFor($a,$this->number($this->request->post('teamId'),'班组'));$admin=$this->adminIdentity($a);
            if(!$admin || (int)$team['aid']!==(int)$admin['id'])$this->fail('仅当前班组班长可暂停或恢复点检',403);
            $id=$this->number($this->request->post('id'));$mode=$this->request->post('mode');if(!in_array($mode,['pause','resume'],true))$this->fail('操作方式不正确');
            $days=$mode==='pause'?$this->number($this->request->post('days'),'停用天数'):0;if($days>365)$this->fail('停用天数最多365天');
            return $this->lockDevice($id,function()use($a,$team,$id,$mode,$days){return Db::transaction(function()use($a,$team,$id,$mode,$days){
                $m=$this->machine($id);if((int)$m['line']!==(int)$team['line'] || (int)$m['status']!==1 || (int)$m['is_check']!==1)$this->fail('设备不属于该班组产线或已停用',403);
                $p=self::periodFor($team,$this->now());$current=$this->pauseFor($id,$team,$p);
                if($mode==='pause'){
                    if($current)$this->fail('设备已暂停，请刷新核对',409);
                    Db::name('me_check_pause')->insert(['m_id'=>$id,'tid'=>$team['id'],'start_date'=>$p['timestamp'],'end_date'=>strtotime('+'.$days.' days',$p['timestamp']),'days'=>$days,'created_by'=>$a['admin_id'],'created_at'=>$this->now()]);
                }else{
                    if(!$current)$this->fail('设备已恢复，请刷新核对',409);
                    Db::name('me_check_pause')->where('id',$current['id'])->update(['end_date'=>$p['timestamp'],'resumed_by'=>$a['admin_id'],'resumed_at'=>$this->now()]);
                }
                $this->pauseMaps=[];return $this->result(['paused'=>$mode==='pause','device'=>$this->card($m,$p,$this->recordsFor($id,$p),0,$team)]);
            });});
        });
    }
    public function options() {
        return $this->guarded(function(){
            $this->requireMethod('GET');$a=$this->current();$member=OmsLeaveApply::memberFor($a);$teams=$this->teamsFor($a);$projects=$this->projects();
            $items=[];foreach($teams as $t)$items[]=['id'=>(int)$t['id'],'team'=>$t['team'],'project'=>(int)$t['project'],'projectName'=>$projects[$t['project']]??'其他','lineId'=>(int)$t['line'],'shift'=>(int)$t['shift'],'period'=>self::periodFor($t,$this->now()),'canBind'=>PositionDevice::canManage($this->adminIdentity($a),$t),'canPause'=>(bool)($this->adminIdentity($a) && (int)$t['aid']===(int)$this->adminIdentity($a)['id'])];
            $overviewTeams=[];foreach($this->teamQuery()->select()->toArray() as $t)$overviewTeams[]=['id'=>(int)$t['id'],'team'=>$t['team'],'project'=>(int)$t['project'],'projectName'=>$projects[$t['project']]??'其他','lineId'=>(int)$t['line'],'shift'=>(int)$t['shift'],'period'=>self::periodFor($t,$this->now()),'canBind'=>PositionDevice::canManage($this->adminIdentity($a),$t),'canPause'=>(bool)($this->adminIdentity($a) && (int)$t['aid']===(int)$this->adminIdentity($a)['id'])];
            $lineIds=Db::name('me_machine_list')->where(['status'=>1,'is_check'=>1])->distinct(true)->column('line');
            $lines=Db::name('team_base')->whereIn('id',$lineIds ?: [-1])->field('id,title')->order('title')->select()->toArray();
            $line=$member?(Db::name('team_list')->where('id',$member['tid'])->value('line')):($teams[0]['line']??0);$typeIds=Db::name('me_machine_list')->where(['line'=>$line,'status'=>1,'is_check'=>1])->distinct(true)->column('type');$types=Db::name('me_base')->whereIn('id',$typeIds ?: [-1])->field('id,title')->order('title')->select()->toArray();
            return $this->result(['canRequestPosition'=>(bool)$member,'canSetPositions'=>count(\app\mobile\service\MemberPosition::managerTeams($a,$this->adminIdentity($a)))>0,'canBind'=>count(PositionDevice::teams($this->adminIdentity($a)))>0,'teams'=>$items,'overviewTeams'=>$overviewTeams,'defaultTeamId'=>(int)(in_array((int)($member['tid']??0),array_column($items,'id'),true)?$member['tid']:($teams[0]['id']??0)),'canCheck'=>(bool)$member,'canManage'=>self::managerFor($a),'lines'=>$lines,'types'=>$types]);
        });
    }
    public function devices() {
        return $this->guarded(function(){
            $this->requireMethod('GET');$a=$this->current();$member=$this->memberIdentity($a);$scope=$this->request->get('scope','position');$selected=(int)$this->request->get('teamId',0);
            $admin=$this->adminIdentity($a);$leaderSelected=$selected && $admin && Db::name('team_list')->where(['id'=>$selected,'aid'=>$admin['id'],'status'=>1])->count()>0;
            if($scope==='all' && $member && !$leaderSelected && $selected && $selected!==(int)$member['tid'])$this->fail('全部设备仅限本人当前班组产线',403);
            $team=$this->teamFor($a,$scope==='all' && $member && !$leaderSelected?(int)$member['tid']:$selected);$period=self::periodFor($team,$this->now());
            if(!in_array($scope,['position','history','all','records'],true))$this->fail('列表类型不正确');
            $member=OmsLeaveApply::memberFor($a);$q=Db::name('me_machine_list');$last=[];
            $positionContext=PositionDevice::employee((int)($member['id']??0),$team);
            if($scope==='position'){
                $ids=$positionContext['canAssociate']?Db::name('me_position_device')->where(['plan_id'=>$team['plan_id'],'poid'=>$positionContext['ownPositionId']])->column('m_id'):[];
                $q->whereIn('id',$ids ?: [-1])->where('line',$team['line']);
            }
            if(in_array($scope,['history','records'],true)){
                if(!$member)return $this->result(['items'=>[],'total'=>0,'page'=>1,'period'=>$period]);
                $rq=Db::name('me_check_record')->where(['mid'=>$member['id'],'status'=>1]);
                if($scope==='history')$rq->where('update','>=',$this->now()-168*3600)->where('update','<=',$this->now());
                else{$period['timestamp']=$this->dateValue($this->request->get('date',$period['date']));$period['date']=date('Y-m-d',$period['timestamp']);$period['shift']=$this->number($this->request->get('shift',$period['shift']),'班次');if(!in_array($period['shift'],[1,2],true))$this->fail('班次不正确');$rq->where(['check_date'=>$period['timestamp'],'shift'=>$period['shift']]);}
                $last=array_column($rq->field('m_id,MAX(`update`) last_time')->group('m_id')->select()->toArray(),'last_time','m_id');$q->whereIn('id',array_keys($last) ?: [-1]);
            }
            if($scope!=='records')$q->where(['status'=>1,'is_check'=>1]);
            if($scope==='all')$q->where('line',$team['line']);$type=(int)$this->request->get('typeId',0);if($type)$q->where('type',$type);
            $kw=$this->request->get('keyword','');if(!is_string($kw)||mb_strlen($kw)>50)$this->fail('搜索文字过长');if(trim($kw)!=='')$q->whereLike('m_name|fixed_number','%'.trim($kw).'%');
            $page=max(1,min(10000,(int)$this->request->get('page',1)));
            if($this->request->get('unfinished')){
                $prefix=Db::connect()->getConfig('prefix');$machineTable='`'.$prefix.'me_machine_list`';
                $monthStart=strtotime(date('Y-m-01',$this->now()));$monthEnd=strtotime('+1 month',$monthStart);
                $q->whereRaw('EXISTS (SELECT 1 FROM `'.$prefix.'me_check_list` mc WHERE mc.type='.$machineTable.'.type AND mc.status=1 AND (COALESCE(mc.freq,1)<>2 OR NOT EXISTS (SELECT 1 FROM `'.$prefix.'me_check_record` mr WHERE mr.m_id='.$machineTable.'.id AND mr.check_id=mc.id AND mr.status=1 AND mr.`update`>='.$monthStart.' AND mr.`update`<'.$monthEnd.'))) AND NOT EXISTS (SELECT 1 FROM `'.$prefix.'me_check_close` ec WHERE ec.m_id='.$machineTable.'.id AND ec.check_date='.(int)$period['timestamp'].' AND ec.shift='.(int)$period['shift'].') AND EXISTS (SELECT 1 FROM `'.$prefix.'me_check_list` uc WHERE uc.type='.$machineTable.'.type AND uc.status=1) AND NOT EXISTS (SELECT 1 FROM `'.$prefix.'me_check_record` ur WHERE ur.m_id='.$machineTable.'.id AND ur.check_date=? AND ur.shift=? AND ur.status=1) AND NOT EXISTS (SELECT 1 FROM `'.$prefix.'me_check_pause` up WHERE up.m_id='.$machineTable.'.id AND up.tid=? AND up.start_date<=? AND up.end_date>?)',[$period['timestamp'],$period['shift'],$team['id'],$period['timestamp'],$period['timestamp']]);
            }
            $total=(clone $q)->count();
            if($scope==='history' && $last){arsort($last);$q->orderRaw('FIELD(id,'.implode(',',array_map('intval',array_keys($last))).')');}else $q->order($scope==='all'?'type,id':'id');
            $q->limit(($page-1)*20,20);
            $machines=$q->select()->toArray();$this->prepareCards($machines,$period);
            $items=[];foreach($machines as $m){$p=$this->card($m,$period,$this->recordsFor($m['id'],$period),(int)($last[$m['id']]??0),$team);if($this->request->get('unfinished') && $p['status']!==1)continue;$items[]=$p;}
            if($scope==='history')usort($items,function($x,$y)use($last){return (int)$last[$y['id']]<=>(int)$last[$x['id']];});
            return $this->result(['items'=>$items,'total'=>$total,'page'=>$page,'period'=>$period,'teamId'=>(int)$team['id'],'teamName'=>$team['team'],'positionContext'=>$positionContext]);
        });
    }
    /** 只放开本班组班长/有效机动岗位接续；首位点检人仍可继续。 */
    protected static function continuationAllowed($member,$team,$owners,$admin,$position) {
        if(!$member || (int)$member['tid']!==(int)$team['id'])return false;
        if(!$owners || (int)reset($owners)===(int)$member['id'])return true;
        $leader=$admin && (int)$team['aid']>0 && (int)$admin['id']===(int)$team['aid'];
        $jumper=$position && (int)$position['plan_id']===(int)$team['plan_id'] && (int)$position['status']===1
            && mb_strpos((string)$position['position'],'机动工')!==false;
        return $leader || $jumper;
    }
    protected function deviceDetail($a,$id,$team,$period,$historical=false) {
        $m=$this->machine($id);$records=$this->recordsFor($id,$period);if($historical)$records=array_values(array_filter($records,function($r)use($team){return (int)$r['tid']===(int)$team['id'];}));$member=Db::name('member_list')->where(['id'=>(int)(OmsLeaveApply::memberFor($a)['id']??0),'status'=>1])->find();$owners=array_unique(array_map('intval',array_column($records,'mid')));
        $rules=$this->rules($id,$period);foreach($rules['closed'] as $close)$owners[]=(int)$close['mid'];$owners=array_values(array_unique($owners));
        $position=$member && !empty($member['poid'])?Db::name('team_plan_position')->where('id',$member['poid'])->find():null;
        $canContinue=self::continuationAllowed($member,$team,$owners,$this->adminIdentity($a),$position);
        // 接续只针对当前班组的记录，不能接管同产线其他班组的未完点检。
        foreach(array_merge($records,array_values($rules['closed'])) as $record)if((int)$record['tid']!==(int)$team['id'])$canContinue=false;
        $pause=$this->pauseFor($id,$team,$period);$inShift=self::periodFor($team,$this->now())['canCheck'];$can=$inShift && !$pause && $member && (int)$member['tid']===(int)$team['id'] && (int)$m['line']===(int)$team['line'] && !$historical && (int)$m['status']===1 && (int)$m['is_check']===1 && $canContinue;
        $clauses=$this->clausesFor($m['type']);$names=\app\mobile\service\CheckCatalog::names();$members=Db::name('member_list')->whereIn('id',array_unique(array_column($records,'mid')) ?: [-1])->column('ename','id');
        $issues=[];foreach(Db::name('me_check_issue')->whereIn('check_id',array_column($records,'id') ?: [-1])->order('id')->select()->toArray() as $issue)$issues[$issue['check_id']]=$issue;
        $open=Db::name('me_check_issue')->alias('i')->join('me_check_record r','r.id=i.check_id')->where('r.m_id',$id)->whereIn('i.status',[1,2])->count();
        $this->cardMaps=['clauses'=>[$m['type']=>$clauses],'types'=>$names['types'],'lines'=>$names['lines'],'lineProjects'=>$names['lineProjects'],'members'=>$members,'issues'=>[$id=>$open]];
        $items=[];$closeable=[];foreach($clauses as $c){
            $monthly=(int)($c['freq']??1)===2;if($monthly&&!$historical&&isset($rules['monthly'][$c['id']]))continue;
            $closed=isset($rules['closed'][$c['id']]);
            $existing=array_values(array_filter($records,function($r)use($c,$monthly,$rules){return (int)$r['check_id']===(int)$c['id']&&(!$monthly||((int)$r['update']>=$rules['monthStart']&&(int)$r['update']<$rules['monthEnd']));}));$n=$monthly?1:max(1,(int)$c['num']);$complete=$closed||count($existing)>=$n;$previous=0;
            if($can&&!$monthly&&$n>1&&!$complete&&count($existing)>0)$closeable[]=['id'=>(int)$c['id'],'content'=>$c['content']];
            for($i=0;$i<$n;$i++){$r=$existing[$i]??null;$due=$i===0?0:($previous?$previous+14400:PHP_INT_MAX);$issue=$r?($issues[$r['id']]??null):null;
                $items[]=['key'=>$c['id'].'-'.$i,'checkId'=>(int)$c['id'],'round'=>$i+1,'count'=>$n,'content'=>$c['content'],'standard'=>(string)$c['standard'],'method'=>(string)$c['method'],'requiresMark'=>strpos((string)$c['method'],'记录')!==false,'recordId'=>(int)($r['id']??0),'result'=>(int)($r['result']??0),'mark'=>(string)($r['mark']??''),'checker'=>$r?($members[$r['mid']]??'历史点检人'):'','time'=>$r?date('Y-m-d H:i',$r['update']):'','issueId'=>(int)($issue['id']??0),'issueStatus'=>(int)($issue['status']??0),'editable'=>(bool)($can && !$complete && ($r ? (int)$r['mid']===(int)$member['id'] : $due<=$this->now())),'nextAvailableAt'=>(!$r&&!$complete&&$due!==PHP_INT_MAX?$due:0),'closed'=>$closed,'complete'=>$complete,'freq'=>$monthly?2:1,'nextTime'=>$complete?($closed?'本条款已提前结束':'本条款已完成'):($due===PHP_INT_MAX?'请先完成上一次点检':($due>$this->now()?date('m-d H:i',$due).' 后可点检':''))];if($r)$previous=(int)$r['update'];
            }
        }
        return ['association'=>PositionDevice::employee((int)($member['id']??0),$team,$id),'device'=>$this->card($m,$period,$records,0,$team),'items'=>$items,'closeable'=>$closeable,'version'=>$this->versionFor([$records,$clauses,$rules,[(int)$m['type'],(int)$m['line'],(int)$m['status'],(int)$m['is_check']]]),'canEdit'=>(bool)$can,'readOnlyReason'=>!$historical && !$inShift?'员工所在班组的班别与当前实际点检班次不一致，无法提报，请反馈班长进行班组班别设置（白班07:00–19:00，夜班19:00–次日07:00）':($pause?'当前班组已暂停此设备点检，恢复日期 '.date('Y-m-d',$pause['end_date']):($historical?'历史周期只读':(!$member?'当前账号未关联在职员工':((int)$m['line']!==(int)$team['line']?'设备已不属于当前班组产线':(!$can?'设备已停用，或当前周期由其他人员点检；仅原点检人、本班组班长或机动工可继续':''))))),'period'=>$period,'teamId'=>(int)$team['id'],'teamName'=>$team['team']];
    }
    public function detail() {
        return $this->guarded(function(){
            $this->requireMethod('GET');$a=$this->current();$readOnly=(bool)$this->request->get('readOnly',false);$team=$readOnly?$this->summaryTeam($this->number($this->request->get('teamId'),'班组')):$this->teamFor($a,(int)$this->request->get('teamId',0));$p=self::periodFor($team,$this->now());
            if($this->request->get('date')){$p['timestamp']=$this->dateValue($this->request->get('date'));$p['date']=date('Y-m-d',$p['timestamp']);$p['shift']=$this->number($this->request->get('shift'),'班次');if(!in_array($p['shift'],[1,2],true))$this->fail('班次不正确');}
            $historical=$this->request->get('readOnly') || $p!==self::periodFor($team,$this->now());
            if($readOnly && (int)$this->machine($this->number($this->request->get('id')))['line']!==(int)$team['line'])$this->fail('设备不属于所选班组产线',403);
            return $this->result($this->deviceDetail($a,$this->number($this->request->get('id')),$team,$p,(bool)$historical));
        });
    }
    public function save() {
        return $this->guarded(function(){
            $this->requireMethod('POST');$a=$this->current();$member=OmsLeaveApply::memberFor($a);if(!$member)$this->fail('点检需要有效员工关联',403);$id=$this->number($this->request->post('id'));$team=$this->teamFor($a,(int)$member['tid']);$p=self::periodFor($team,$this->now());
            if(!$p['canCheck'])$this->fail('员工所在班组的班别与当前实际点检班次不一致，无法提报，请反馈班长进行班组班别设置（白班07:00–19:00，夜班19:00–次日07:00）',403);
            if($this->request->post('date')!==$p['date'] || (int)$this->request->post('shift')!==$p['shift'])$this->fail('班次或业务日期已切换，请重新打开设备',409);
            return $this->lockDevice($id,function()use($a,$member,$id,$team,$p){return PositionDevice::locked(function()use($a,$member,$id,$team,$p){return Db::transaction(function()use($a,$member,$id,$team,$p){
                $team=PositionDevice::team($team['id']);$member=Db::name('member_list')->where(['id'=>$member['id'],'status'=>1])->find();if(!$member || (string)$member['eid']!==(string)$a['username'] || (int)$member['tid']!==(int)$team['id'])$this->fail('员工班组已变化，请刷新后重新点检',409);if(self::periodFor($team,$this->now())!==$p)$this->fail('班组班次已变化，请刷新',409);
                $this->freshClauses=true;$this->clauses=[];$this->ruleMaps=[];$this->identities=[];
                $d=$this->deviceDetail($a,$id,$team,$p);if(!$d['canEdit'])$this->fail($d['readOnlyReason'],403);if($this->request->post('version')!==$d['version'])$this->fail('记录已变化，请刷新核对后再保存',409);
                $association=PositionDevice::employee($member['id'],$team,$id);PositionDevice::checkChoice($association,$this->request->post());
                $input=$this->request->post('items',[]);$closeIds=$this->request->post('closeIds',[]);if(!is_array($closeIds)||count($closeIds)>count($d['closeable']))$this->fail('提前结束条款不正确');$allowed=array_column($d['closeable'],null,'id');foreach($closeIds as $cid)if(!is_int($cid)||!isset($allowed[$cid]))$this->fail('该条款不能提前结束',409);
                if(!is_array($input)||(!count($input)&&!count($closeIds))||count($input)>count($d['items']))$this->fail('请选择需要保存的点检项');$slots=array_column($d['items'],null,'key');$seen=[];$added=[];$changes=[];
                foreach($input as $v){if(!is_array($v)||!isset($v['key'])||!is_string($v['key'])||isset($seen[$v['key']])||!isset($slots[$v['key']]))$this->fail('点检项不正确');$seen[$v['key']]=1;$s=$slots[$v['key']];
                    if(!$s['editable'])$this->fail($s['recordId']?'已保存的点检结果仅原点检人可修改':($s['nextTime'] ?: '该项不能修改'),409);$r=$v['result']??null;if(!is_int($r)||!in_array($r,[0,1,2,3],true))$this->fail('点检结果不正确');$mark=$this->textValue($v['mark']??'',false);
                    if(($r===2 || ($r===1 && $s['requiresMark'])) && $mark==='')$this->fail('异常和需要记录的正常项必须填写说明');if($r!==2 && !($r===1 && $s['requiresMark']))$mark='';
                    if($r===$s['result'] && $mark===$s['mark'])continue;
                    if($r===0 && $s['count']>1){foreach($d['items'] as $later)if($later['checkId']===$s['checkId'] && $later['round']>$s['round'] && $later['recordId'])$this->fail('多次点检只能从最后一次开始清空');}
                    if($s['issueId'])$this->fail('已上报异常请到异常处理模块办理，不能从点检中删除或改写');
                    if(!$s['recordId'] && $r>0 && $s['count']>1){if(isset($added[$s['checkId']]))$this->fail('同一多次点检条款每次只能保存一次');$added[$s['checkId']]=1;}
                    $changes[]=[$s,$r,$mark];
                }
                foreach($changes as $change){list($s,$r,$mark)=$change;if($s['recordId']){if(!$r)Db::name('me_check_record')->where('id',$s['recordId'])->update(['status'=>0]);else Db::name('me_check_record')->where('id',$s['recordId'])->update(['result'=>$r,'mark'=>$mark]);$rid=$s['recordId'];}
                    elseif($r)$rid=Db::name('me_check_record')->insertGetId(['m_id'=>$id,'check_id'=>$s['checkId'],'check_date'=>$p['timestamp'],'shift'=>$p['shift'],'tid'=>$team['id'],'mid'=>$member['id'],'result'=>$r,'mark'=>$mark,'update'=>$this->now(),'status'=>1]);else continue;
                    if($r===2){$owner=0;$issue=Db::name('me_check_issue')->insertGetId(['check_id'=>$rid,'mid'=>$member['id'],'tid'=>$team['id'],'check_time'=>$this->now(),'owner'=>$owner,'update'=>0,'mark'=>'','status'=>1]);Db::name('me_check_issue_flow')->insert(['issue_id'=>$issue,'version'=>1,'updated_at'=>$this->now(),'draft'=>'','images'=>'[]']);Db::name('me_check_issue_log')->insert(['issue_id'=>$issue,'auth_id'=>$a['id'],'actor'=>$a['username'],'action'=>'上报异常','note'=>$mark,'created_at'=>$this->now()]);}
                }
                foreach(array_unique($closeIds) as $cid)Db::name('me_check_close')->insert(['m_id'=>$id,'check_id'=>$cid,'check_date'=>$p['timestamp'],'shift'=>$p['shift'],'tid'=>$team['id'],'mid'=>$member['id'],'created_at'=>$this->now()]);
                if($closeIds || array_filter($changes,function($v){return $v[1]>0;}))PositionDevice::afterCheck($association,$id,$this->request->post(),$member,$team,$member['ename'].' ('.$a['username'].' #'.$a['id'].')');
                $this->bindingMaps=[];$this->ruleMaps=[];return $this->result($this->deviceDetail($a,$id,$team,$p));
            });});});
        });
    }
    public function positionOptions() {
        return $this->guarded(function(){ $this->requireMethod('GET');$a=$this->current();return $this->result(['teams'=>PositionDevice::teams($this->adminIdentity($a))]); });
    }
    public function positionData() {
        return $this->guarded(function(){ $this->requireMethod('GET');$a=$this->current();$admin=$this->adminIdentity($a);if(!$admin)$this->fail('仅班长或授权管理人员可操作',403);$team=PositionDevice::team($this->number($this->request->get('teamId')),$admin);return $this->result(PositionDevice::data($team)); });
    }
    public function positionSave() {
        return $this->guarded(function(){ $this->requireMethod('POST');$a=$this->current();$admin=$this->adminIdentity($a);if(!$admin)$this->fail('仅班长或授权管理人员可操作',403);return $this->result(PositionDevice::save($this->number($this->request->post('teamId')),$admin,$this->request->post(),'小程序岗位设备管理')); });
    }
    public function positionLogs() {
        return $this->guarded(function(){ $this->requireMethod('GET');$a=$this->current();$admin=$this->adminIdentity($a);if(!$admin)$this->fail('仅班长或授权管理人员可操作',403);return $this->result(PositionDevice::logs(PositionDevice::team($this->number($this->request->get('teamId')),$admin))); });
    }
    public function summary() {
        return $this->guarded(function(){
            $this->requireMethod('GET');$this->current();$team=$this->summaryTeam($this->number($this->request->get('teamId'),'班组'));$p=self::periodFor($team,$this->now());$p['timestamp']=$this->dateValue($this->request->get('date',$p['date']));$p['date']=date('Y-m-d',$p['timestamp']);$p['shift']=$this->number($this->request->get('shift',$p['shift']),'班次');if(!in_array($p['shift'],[1,2],true))$this->fail('班次不正确');
            $totals=['total'=>0,'uncheck'=>0,'doing'=>0,'done'=>0,'issueDevices'=>0,'issueItems'=>0];$items=[];
            $machines=Db::name('me_machine_list')->where(['line'=>(int)$team['line'] ?: -1,'status'=>1,'is_check'=>1])->order('id')->select()->toArray();$this->prepareCards($machines,$p);
            foreach($machines as $m){$rows=array_values(array_filter($this->recordsFor($m['id'],$p),function($r)use($team){return (int)$r['tid']===(int)$team['id'];}));$card=$this->card($m,$p,$rows,0,$team);if($card['paused'] || !$card['total'])continue;$totals['total']++;$key=[1=>'uncheck',2=>'doing',3=>'done'][$card['status']];$totals[$key]++;if($card['issueCount'])$totals['issueDevices']++;$totals['issueItems']+=$card['issueCount'];$items[]=$card;}
            usort($items,function($a,$b){return $a['status']<=>$b['status'];});return $this->result(['items'=>$items,'totals'=>$totals,'period'=>$p]);
        });
    }
    public function overview() {
        return $this->guarded(function(){
            $this->requireMethod('GET');$this->current();
            $timestamp=$this->dateValue($this->request->get('date'));$shift=$this->number($this->request->get('shift'),'班次');if(!in_array($shift,[1,2],true))$this->fail('班次不正确');
            $p=['date'=>date('Y-m-d',$timestamp),'timestamp'=>$timestamp,'shift'=>$shift];
            $teams=$this->teamQuery()->select()->toArray();
            $machines=Db::name('me_machine_list')->whereIn('line',array_unique(array_column($teams,'line')) ?: [-1])->where(['status'=>1,'is_check'=>1])->order('id')->select()->toArray();
            $this->prepareCards($machines,$p);$byLine=[];foreach($machines as $m)$byLine[$m['line']][]=$m;
            $projects=$this->projects();$items=[];
            foreach($teams as $t){
                $total=0;$done=0;$doing=0;$uncheck=0;
                foreach(((int)$t['line']>0?($byLine[$t['line']]??[]):[]) as $m){
                    $rows=array_values(array_filter($this->recordsFor($m['id'],$p),function($r)use($t){return (int)$r['tid']===(int)$t['id'];}));
                    $card=$this->card($m,$p,$rows,0,$t);if($card['paused'] || !$card['total'])continue;$total++;
                    if($card['status']===3)$done++;elseif($card['status']===2)$doing++;else $uncheck++;
                }
                $items[]=['teamId'=>(int)$t['id'],'teamName'=>$t['team'],'project'=>(int)$t['project'],'projectName'=>$projects[$t['project']]??'其他','total'=>$total,'done'=>$done,'doing'=>$doing,'uncheck'=>$uncheck,'rate'=>$total?round(100*$done/$total,1):null];
            }
            return $this->result(['items'=>$items,'date'=>$p['date'],'shift'=>$shift]);
        });
    }
}
