import sys,json,collections,copy from pathlib import Path from decimal import Decimal as D sys.stdout.reconfigure(encoding='utf-8') P=Path(__file__).parent;db=json.loads((P/'audit.json').read_text(encoding='utf8'));plan=json.loads((P/'plan.json').read_text(encoding='utf8')) def active(t):return [x for x in db[t]['rows'] if x.get('deleted') not in (1,"b'\\x01'")] inds={x['id']:x for x in plan['indicators']};details=collections.defaultdict(list) for x in plan['details']:details[x['result_id']].append(x) versions={(x['template_id'],x['version']):json.loads(x['schema_json']) for x in active('qc_report_template_version')} new_schema=json.loads(json.dumps(versions[(14,'v1.1')],ensure_ascii=False).replace('item.standardValue','item.requirement').replace('标准值','标准要求')) # Keep inspection names readable and reserve sufficient width for long SOP requirements on A4. new_schema['grapes']['styles'].extend([ {'selectors':['#ipnmhk'],'style':{'table-layout':'fixed','width':'100%'}}, *[{'selectors':['#'+iid],'style':{'width':width}} for iid,width in zip(['iea1ya','iy2rxi','iqwrcq','itsffd','iwpsmz','i0sop1'],['6%','19%','40%','13%','11%','11%'])] ]) versions[(14,'v1.2')]=new_schema (P/'report-template-upgrade.json').write_text(json.dumps({'template_id':14,'version':'v1.2','schema':new_schema,'source_version':'v1.1'},ensure_ascii=False,indent=2),encoding='utf8') users=json.loads(Path('scripts/qc-complete-20261014/audit.json').read_text(encoding='utf8'))['users'];usernames={x['id']:x['nickname'].strip() for x in users} unitnames={x['id']:x['name'] for x in active('mes_md_unit_measure')} heads={k:{x['id']:x for x in active('mes_qc_'+k)} for k in ['iqc','ipqc','oqc','rqc']} def plain(v):return format(D(v).normalize(),'f') if v is not None else '' def requirement(line): low,high=line.get('min_threshold'),line.get('max_threshold') unit=line.get('_unit_name') or unitnames.get(line.get('unit_measure_id')) or inds[line['indicator_id']].get('unit_text') or '' if low is not None and high is not None:text='='+plain(low) if D(low)==D(high) else plain(low)+'~'+plain(high) elif low is not None:text='≥'+plain(low) elif high is not None:text='≤'+plain(high) else: m=line.get('check_method') or '';remark=line.get('remark') or '' return m[3:].split(';方法:')[0] if m.startswith('要求:') else remark if not remark.startswith('10月8日至14日业务链') else '' return text+(' '+unit if unit else '') jobs=[];preserved=[] for instance in active('qc_report_instance'): kind=instance['business_type'].replace('mes_qc_','');qid=int(instance['business_id']);head=heads[kind].get(qid) if not head: preserved.append({'id':instance['id'],'report_no':instance['report_no'],'reason':'来源单据已删除,保留原冻结报告,避免改写历史依据'});continue qc_type={'iqc':1,'ipqc':2,'oqc':3,'rqc':4}[kind] samples=[x for x in active('mes_qc_indicator_result') if x['qc_type']==qc_type and x['qc_id']==qid] lines=[x for x in plan['line_state'][kind] if x[kind+'_id']==qid] child=collections.defaultdict(list);own=collections.defaultdict(list) for line in lines: ind=inds[line['indicator_id']];parent=inds.get(ind['parent_id']) if parent and parent['item_type']==2:child[parent['id']].append(line) else:own[ind['id']].append(line) entries=[] for iid,ll in child.items():entries.append((inds[iid],own.pop(iid,[]),sorted(ll,key=lambda x:inds[x['indicator_id']]['sort_order'] or 999999))) for iid,ll in own.items():entries.append((inds[iid],ll,[])) entries.sort(key=lambda x:(x[0].get('sort_order') or 999999,x[0]['id'])) items=[] def expand(ind,line): values=[d for sample in samples for d in details[sample['id']] if d['indicator_id']==ind['id']] if not values:values=[{'value':None,'remark':None}] output=[] for value in values: unit='' if ind['item_type']==2 else line.get('_unit_name') or unitnames.get(line.get('unit_measure_id')) or ind.get('unit_text') or '' remark=';'.join(x for x in [line.get('remark'),value.get('remark')] if x) output.append({'index':0,'itemCode':ind['code'],'itemName':ind['name'],'standardValue':plain(line.get('standard_value')),'actualValue':value['value'] or '','unit':unit,'requirement':requirement(line),'checkMethod':line.get('check_method') or '', 'group':{'childName':'','span':1,'hidden':''},'upperLimit':float(line['max_threshold']) if line.get('max_threshold') is not None else None,'lowerLimit':float(line['min_threshold']) if line.get('min_threshold') is not None else None,'result':'','resultText':'待判定','remark':remark}) return output for ind,ownlines,children in entries: entry=[] for line in ownlines:entry.extend(expand(ind,line)) if children and not entry:entry=expand(ind,{'indicator_id':ind['id']}) anchors=len(entry) for line in children: leaf=inds[line['indicator_id']];rr=expand(leaf,line) for row in rr:row['group']['childName']=leaf['name'] entry.extend(rr) for index,row in enumerate(entry): row['index']=len(items)+1;row['group']['span']=len(entry) if children and index==0 else 1;row['group']['hidden']='hidden' if children and index>0 else '' if index