|
1 | 1 | import {readWorkbook} from './workbook' |
2 | 2 |
|
3 | | -export type HistoricalProductionRow={ |
4 | | - sourceRow:number |
5 | | - recordStatus:'operational'|'void' |
6 | | - eventDate:string|null |
7 | | - receptionDate:string|null |
8 | | - processDate:string|null |
9 | | - productionDate:string|null |
10 | | - guideNumber:string|null |
11 | | - supplierOriginal:string|null |
12 | | - supplierName:string|null |
13 | | - extractionZone:string|null |
14 | | - guidePriceClp:number|null |
15 | | - processSiteOriginal:string|null |
16 | | - lotCode:string |
17 | | - guideKg:number|null |
18 | | - receivedKg:number|null |
19 | | - differenceKg:number|null |
20 | | - qualityDiscount:number|null |
21 | | - gradeBreakdown:Record<string,number|null> |
22 | | - yields:Record<string,number|null> |
23 | | - client:string|null |
24 | | - observations:string|null |
25 | | - dataQualityFlags:string[] |
26 | | - rawRecord:Record<string,unknown> |
27 | | -} |
28 | | - |
29 | | -export type HistoricalWorkbookPreview={ |
30 | | - kind:'pescamar-production' |
31 | | - fileName:string |
32 | | - fileHash:string |
33 | | - sheetName:string |
34 | | - sheetCount:number |
35 | | - rows:HistoricalProductionRow[] |
36 | | - operational:number |
37 | | - voids:number |
38 | | - flagged:number |
39 | | -} |
40 | | - |
| 3 | +export type GradeValue={boxes:number|null;kg:number|null} |
| 4 | +export type HistoricalProductionRow={sourceRow:number;recordStatus:'operational'|'void';eventDate:string|null;receptionDate:string|null;processDate:string|null;productionDate:string|null;guideNumber:string|null;supplierOriginal:string|null;supplierName:string|null;extractionZone:string|null;guidePriceClp:number|null;processSiteOriginal:string|null;lotCode:string;guideKg:number|null;receivedKg:number|null;differenceKg:number|null;qualityDiscount:number|null;gradeBreakdown:Record<string,GradeValue>;yields:Record<string,number|null>;client:string|null;observations:string|null;dataQualityFlags:string[];rawRecord:Record<string,unknown>} |
| 5 | +export type HistoricalWorkbookPreview={kind:'pescamar-production';fileName:string;fileHash:string;sheetName:string;sheetCount:number;rows:HistoricalProductionRow[];operational:number;voids:number;flagged:number} |
41 | 6 | const text=(value:unknown)=>{const v=String(value??'').trim();return v||null} |
42 | 7 | const number=(value:unknown)=>typeof value==='number'&&Number.isFinite(value)?value:(()=>{const raw=String(value??'').trim();if(!raw)return null;const parsed=Number(raw.replace(/\./g,'').replace(',','.'));return Number.isFinite(parsed)?parsed:null})() |
43 | 8 | const date=(value:unknown)=>{if(value instanceof Date&&!Number.isNaN(value.getTime()))return value.toISOString().slice(0,10);const raw=String(value??'').trim();if(!raw)return null;const parsed=new Date(raw);return Number.isNaN(parsed.getTime())?null:parsed.toISOString().slice(0,10)} |
44 | 9 | const normalize=(value:unknown)=>String(value??'').normalize('NFD').replace(/[\u0300-\u036f]/g,'').toLowerCase().replace(/[^a-z0-9]/g,'') |
45 | 10 | const cell=(row:unknown[],index:number)=>row[index]??null |
46 | | - |
47 | | -export async function readHistoricalProductionWorkbook(file:File):Promise<HistoricalWorkbookPreview|null>{ |
48 | | - const workbook=await readWorkbook(file) |
49 | | - const top=workbook.rows[0]??[],sub=workbook.rows[1]??[] |
50 | | - const signature=[ |
51 | | - [4,'guia','n'],[5,'proveedor','nombre'],[6,'lugarextraccion','zona'],[8,'planta','proceso'],[9,'lote','n'],[10,'controlrecepcionmp','kilosguia'],[11,'','kilosrecep'] |
52 | | - ] as const |
53 | | - const compatible=signature.every(([index,a,b])=>(!a||normalize(top[index]).includes(a))&&(!b||normalize(sub[index]).includes(b))) |
54 | | - if(!compatible)return null |
55 | | - |
56 | | - const rows:HistoricalProductionRow[]=[] |
57 | | - workbook.rows.slice(2).forEach((values,index)=>{ |
58 | | - if(!values.some(value=>value!==null&&String(value).trim()!==''))return |
59 | | - const sourceRow=index+3 |
60 | | - const lotCode=text(cell(values,9)) |
61 | | - if(!lotCode)return |
62 | | - const guideKg=number(cell(values,10)),receivedKg=number(cell(values,11)) |
63 | | - const receptionDate=date(cell(values,0)),processDate=date(cell(values,1)),productionDate=date(cell(values,2)) |
64 | | - const guideNumber=text(cell(values,4)),supplier=text(cell(values,5)),site=text(cell(values,8)) |
65 | | - const flags:string[]=[] |
66 | | - if(!guideNumber)flags.push('guide_number_missing') |
67 | | - if(!supplier)flags.push('supplier_missing') |
68 | | - if(!receptionDate)flags.push('reception_date_missing') |
69 | | - if(guideKg==null)flags.push('guide_kg_missing') |
70 | | - if(receivedKg==null)flags.push('received_kg_missing') |
71 | | - if(!site)flags.push('process_site_missing') |
72 | | - if(guideKg!=null&&guideKg<0)flags.push('guide_kg_negative') |
73 | | - if(receivedKg!=null&&receivedKg<0)flags.push('received_kg_negative') |
74 | | - const recordStatus=/no\s*existe|anulad|void/i.test(lotCode)?'void':'operational' |
75 | | - rows.push({ |
76 | | - sourceRow,recordStatus, |
77 | | - eventDate:productionDate??processDate??receptionDate,receptionDate,processDate,productionDate, |
78 | | - guideNumber,supplierOriginal:supplier,supplierName:supplier,extractionZone:text(cell(values,6)),guidePriceClp:number(cell(values,7)), |
79 | | - processSiteOriginal:site,lotCode,guideKg,receivedKg,differenceKg:number(cell(values,12))??(guideKg!=null&&receivedKg!=null?guideKg-receivedKg:null), |
80 | | - qualityDiscount:number(cell(values,13)), |
81 | | - gradeBreakdown:{A1:number(cell(values,15)),AA:number(cell(values,17)),V1:number(cell(values,19)),Vj50:number(cell(values,21)),C1:number(cell(values,23)),C2:number(cell(values,25)),D:number(cell(values,27)),PT:number(cell(values,31)),R:number(cell(values,33))}, |
82 | | - yields:{gradeA:number(cell(values,37)),volcadoA:number(cell(values,38)),gradeC:number(cell(values,39)),total:number(cell(values,40)),D:number(cell(values,41)),pasta:number(cell(values,42)),rechazo:number(cell(values,43))}, |
83 | | - client:text(cell(values,44)),observations:text(cell(values,45)),dataQualityFlags:flags, |
84 | | - rawRecord:Object.fromEntries(values.slice(0,46).map((value,column)=>[`c${column+1}`,value instanceof Date?value.toISOString():value])) |
85 | | - }) |
86 | | - }) |
87 | | - const digest=await crypto.subtle.digest('SHA-256',await file.arrayBuffer()) |
88 | | - const fileHash=Array.from(new Uint8Array(digest)).map(byte=>byte.toString(16).padStart(2,'0')).join('') |
89 | | - return {kind:'pescamar-production',fileName:file.name,fileHash,sheetName:workbook.sheetName,sheetCount:workbook.sheetCount,rows,operational:rows.filter(row=>row.recordStatus==='operational').length,voids:rows.filter(row=>row.recordStatus==='void').length,flagged:rows.filter(row=>row.dataQualityFlags.length>0).length} |
90 | | -} |
91 | | - |
92 | | -export async function publishHistoricalProduction(preview:HistoricalWorkbookPreview){ |
93 | | - const response=await fetch('/api/historical-import',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({fileName:preview.fileName,fileHash:preview.fileHash,rows:preview.rows})}) |
94 | | - const payload=await response.json().catch(()=>({})) as {ok?:boolean;error?:string;inserted?:number;duplicates?:number} |
95 | | - if(!response.ok||!payload.ok)throw new Error(payload.error||'No fue posible publicar la planilla histórica') |
96 | | - return payload |
97 | | -} |
| 11 | +const grade=(row:unknown[],boxes:number,kg:number):GradeValue=>({boxes:number(cell(row,boxes)),kg:number(cell(row,kg))}) |
| 12 | +export async function readHistoricalProductionWorkbook(file:File):Promise<HistoricalWorkbookPreview|null>{const workbook=await readWorkbook(file);const top=workbook.rows[0]??[],sub=workbook.rows[1]??[];const signature=[[4,'guia','n'],[5,'proveedor','nombre'],[6,'lugarextraccion','zona'],[8,'planta','proceso'],[9,'lote','n'],[10,'controlrecepcionmp','kilosguia'],[11,'','kilosrecep']] as const;const compatible=signature.every(([index,a,b])=>(!a||normalize(top[index]).includes(a))&&(!b||normalize(sub[index]).includes(b)));if(!compatible)return null;const rows:HistoricalProductionRow[]=[];workbook.rows.slice(2).forEach((values,index)=>{if(!values.some(value=>value!==null&&String(value).trim()!==''))return;const sourceRow=index+3,lotCode=text(cell(values,9));if(!lotCode)return;const guideKg=number(cell(values,10)),receivedKg=number(cell(values,11)),receptionDate=date(cell(values,0)),processDate=date(cell(values,1)),productionDate=date(cell(values,2)),guideNumber=text(cell(values,4)),supplier=text(cell(values,5)),site=text(cell(values,8));const flags:string[]=[];if(!guideNumber)flags.push('guide_number_missing');if(!supplier)flags.push('supplier_missing');if(!receptionDate)flags.push('reception_date_missing');if(guideKg==null)flags.push('guide_kg_missing');if(receivedKg==null)flags.push('received_kg_missing');if(!site)flags.push('process_site_missing');if(guideKg!=null&&guideKg<0)flags.push('guide_kg_negative');if(receivedKg!=null&&receivedKg<0)flags.push('received_kg_negative');const recordStatus=/no\s*existe|anulad|void/i.test(lotCode)?'void':'operational';rows.push({sourceRow,recordStatus,eventDate:productionDate??processDate??receptionDate,receptionDate,processDate,productionDate,guideNumber,supplierOriginal:supplier,supplierName:supplier,extractionZone:text(cell(values,6)),guidePriceClp:number(cell(values,7)),processSiteOriginal:site,lotCode,guideKg,receivedKg,differenceKg:number(cell(values,12))??(guideKg!=null&&receivedKg!=null?guideKg-receivedKg:null),qualityDiscount:number(cell(values,13)),gradeBreakdown:{A1:grade(values,14,15),AA:grade(values,16,17),V1:grade(values,18,19),VJ50:grade(values,20,21),C1:grade(values,22,23),C2:grade(values,24,25),D:grade(values,26,27),PT:grade(values,30,31),R:grade(values,32,33)},yields:{grade_a:number(cell(values,37)),volcado_a:number(cell(values,38)),grade_c:number(cell(values,39)),total:number(cell(values,40)),d:number(cell(values,41)),pasta:number(cell(values,42)),rechazo:number(cell(values,43))},client:text(cell(values,44)),observations:text(cell(values,45)),dataQualityFlags:flags,rawRecord:Object.fromEntries(values.slice(0,46).map((value,column)=>[`c${column+1}`,value instanceof Date?value.toISOString():value]))})});const digest=await crypto.subtle.digest('SHA-256',await file.arrayBuffer());const fileHash=Array.from(new Uint8Array(digest)).map(byte=>byte.toString(16).padStart(2,'0')).join('');return {kind:'pescamar-production',fileName:file.name,fileHash,sheetName:workbook.sheetName,sheetCount:workbook.sheetCount,rows,operational:rows.filter(row=>row.recordStatus==='operational').length,voids:rows.filter(row=>row.recordStatus==='void').length,flagged:rows.filter(row=>row.dataQualityFlags.length>0).length}} |
| 13 | +export async function publishHistoricalProduction(preview:HistoricalWorkbookPreview){const response=await fetch('/api/historical-import',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({fileName:preview.fileName,fileHash:preview.fileHash,rows:preview.rows})});const payload=await response.json().catch(()=>({})) as {ok?:boolean;error?:string;inserted?:number;duplicates?:number};if(!response.ok||!payload.ok)throw new Error(payload.error||'No fue posible publicar la planilla histórica');window.dispatchEvent(new CustomEvent('pescamar:data-updated',{detail:{scope:'historical-production'}}));return payload} |
0 commit comments