✂️ BOBINO

DÉCOPHANIE · Calculateur de refente · P0 à gauche

1 · Préparer le chantier

Rouleau fournisseur

Profils

Bibliothèque des matières par client

Une référence client n'est jamais réutilisée pour un autre client. Les correspondances sont conservées sur cet appareil.

Temps de pose estimé

Temps par pièce, hors pauses, préparation et imprévus. Enregistre chaque référence pour réutiliser son temps sur les prochains chantiers.

—Temps total estimé
—Pièces avec temps connu
—Références sans temps

Profils mémorisés

Mes chantiers enregistrés

Conservés dans ce navigateur sur cet appareil. Pour conserver un chantier dans iCloud Drive, appuie sur « Exporter vers iCloud » à côté de son nom. Le bouton « Exporter toutes les sauvegardes » conserve une copie complète.

Importer une commande ou un BL analysé

Importe un fichier JSON préparé à partir de photos. Vérifie les données avant de créer le chantier. La lecture automatique des photos dans l’application n’est pas encore activée.

Photo du bon de livraison

Photographie chaque page du BL ou choisis un document existant. Les documents restent sur cet appareil et ne sont pas envoyés à Google Sheets.

Aucun document sélectionné.

À savoir : la prise de photo et l'aperçu fonctionnent dès cette version. La lecture automatique des références et quantités n'est pas encore disponible. Tu peux toujours importer le JSON analysé ci-dessous.

RéférenceDésignationLongueur mmQtéLargeur film mmPose min

Largeur du bobinot et temps de pose inconnus : compléter avant calcul. Les données ne seront pas enregistrées sans validation.

3 · Contrôle de réception des profilés

Contrôle indépendant du calcul des bobinots. Les données sont enregistrées sur cet appareil ; export CSV pour Google Sheets. La synchronisation Google Sheets nécessite le Worker Cloudflare configuré et autorisé.

Une référence en 2 m et la même en 3 m doivent être sur deux lignes. Les quantités reçues et abîmées sont à renseigner après comptage.

Contrôles enregistrés sur cet appareil

Photos : conservées localement dans le navigateur de cet appareil, et non incluses dans les exports CSV/JSON. Utilise « Télécharger la photo » pour les archiver dans iCloud ou Drive. Ne supprime pas les données Safari sans sauvegarde.

2 · Résultat et positions de coupe

—Rouleaux
—Bobinots
—Mètres profils

Calcul indicatif : chaque bobinot est prélevé sur toute la longueur du rouleau. La bande à droite de la lame est récupérée, la partie gauche est réutilisée. Regroupement des profils de même largeur et répartition gloutonne des bandes (ne garantit pas l'optimum absolu). Les chantiers enregistrés restent sur cet appareil ; export manuel possible vers iCloud Drive.

Sur iPhone, télécharge le PDF A4 puis ouvre-le dans Fichiers pour l’imprimer ou le conserver sur iCloud.

'; // Open synchronously from the button tap: avoids popup blocking on iOS. const tab=window.open('','_blank'); if(tab){try{tab.document.open();tab.document.write(html);tab.document.close();tab.focus();return}catch(e){try{tab.close()}catch(_){}}} // Fallback: let the user open a standalone HTML file in Files/Safari. const blob=new Blob([html],{type:'text/html;charset=utf-8'}); const url=URL.createObjectURL(blob);const a=document.createElement('a'); a.href=url;a.download='BOBINO_Fiche_'+safeTitle.replace(/[^a-zA-Z0-9_-]/g,'_')+'.html'; document.body.appendChild(a);a.click();a.remove(); alert('La fiche a été téléchargée. Ouvre-la dans Safari depuis Fichiers pour imprimer.'); } $('printIphone').addEventListener('click',()=>downloadChantierPdf()); $('openPrintPage').addEventListener('click',openStandalonePrint); $('printNow').addEventListener('click',()=>{try{window.print()}catch(e){alert('Utilise Partager > Imprimer dans Safari.')}}); $('closePrint').addEventListener('click',()=>{$('printSheet').classList.remove('preview-open');$('printActions').classList.remove('preview-open');$('printJob').scrollIntoView({behavior:'smooth'})}); $('saveJob').addEventListener('click',saveJob);$('showJobs').addEventListener('click',()=>{$('savedPanel').classList.remove('hide');renderJobs();$('savedPanel').scrollIntoView({behavior:'smooth'})});$('closeJobs').addEventListener('click',()=>$('savedPanel').classList.add('hide'));$('exportJobs').addEventListener('click',exportJobs);$('importJobs').addEventListener('click',()=>$('importFile').click());$('importFile').addEventListener('change',e=>importJobs(e.target.files[0]));function blank(){$('rollCount').textContent='—';$('stripCount').textContent='—';$('totalLength').textContent='—';$('cost').textContent=''}$('add').addEventListener('click',()=>{const row=addRow();row.scrollIntoView({behavior:'smooth',block:'nearest'});row.querySelector('input').focus()});$('calc').addEventListener('click',calculate);$('reset').addEventListener('click',reset);['job','rw','rl','price','kerf'].forEach(id=>$(id).addEventListener('input',()=>{save();calculate()}));let stored;try{stored=JSON.parse(localStorage.getItem('bobino-v3')||'null')}catch(e){}if(stored&&Array.isArray(stored.rows)){['job','rw','rl','price','kerf'].forEach(id=>{if(stored[id]!==undefined)$(id).value=stored[id]});stored.rows.forEach(addRow);calculate()}else reset();; // PDF A4 autonome : aucun appel externe, aucune dépendance à Safari window.print(). function downloadChantierPdf(){ try{ if(!buildPrintSheet()){alert('Ajoute un profil valide et calcule le plan avant de télécharger la fiche.');return;} const data=buildChantierPdf(); const name=($('job').value.trim()||'Chantier').replace(/[\\/:*?"<>|]/g,'-').slice(0,90); const url=URL.createObjectURL(new Blob([data],{type:'application/pdf'})); const a=document.createElement('a');a.href=url;a.download='BOBINO_'+name+'.pdf';document.body.appendChild(a);a.click();a.remove(); setTimeout(()=>URL.revokeObjectURL(url),60000); }catch(e){alert('Erreur de création du PDF : '+e.message);} } function buildChantierPdf(){ const W=595.28,H=841.89, margin=39, bottom=42; const pages=[];let commands=[],y=H-margin; const enc=s=>String(s).replace(/[\u2018\u2019]/g,"'").replace(/[\u2013\u2014]/g,'-').replace(/\u2022/g,'-').replace(/\u00a0/g,' '); const pdfstr=s=>{let out='';for(const c of enc(s)){const cp=c.codePointAt(0);let b=cp;const extra={8364:128,8218:130,402:131,8222:132,8230:133,8224:134,8225:135,710:136,8240:137,352:138,8249:139,338:140,381:142,8216:145,8217:146,8220:147,8221:148,8226:149,8211:150,8212:151,732:152,8482:153,353:154,8250:155,339:156,382:158,376:159};if(cp>255)b=extra[cp]||63;if(b===40||b===41||b===92)out+='\\'+String.fromCharCode(b);else if(b<32||b>126)out+='\\'+b.toString(8).padStart(3,'0');else out+=String.fromCharCode(b);}return out;}; const text=(s,x,yy,size=10,bold=false)=>{commands.push('BT /'+(bold?'F2':'F1')+' '+size+' Tf 1 0 0 1 '+x.toFixed(1)+' '+yy.toFixed(1)+' Tm ('+pdfstr(s)+') Tj ET');}; const line=(x1,y1,x2,y2)=>commands.push(x1+' '+y1.toFixed(1)+' m '+x2+' '+y2.toFixed(1)+' l S'); const page=()=>{if(commands.length)pages.push(commands.join('\n'));commands=[];y=H-margin;text('BOBINO - DECOPHANIE',margin,y,11,true);y-=18;line(margin,y,W-margin,y);y-=20;}; const ensure=h=>{if(y-henc(s).length*size*.51; const wrap=(s,max,size)=>{const words=enc(s).split(/\s+/),arr=[];let l='';for(const word of words){if(!word)continue;const n=l?l+' '+word:word;if(approx(n,size)>max&&l){arr.push(l);l=word;}else l=n;}if(l)arr.push(l);return arr.length?arr:[''];}; const paragraph=(s,size=10,bold=false,space=6)=>{const ls=wrap(s,W-2*margin,size);ensure(ls.length*(size+4)+space);ls.forEach(l=>{text(l,margin,y,size,bold);y-=size+4;});y-=space;}; const heading=s=>{ensure(32);y-=8;text(s,margin,y,13,true);y-=22;}; const table=(headers,rows,widths)=>{ const x=[margin];widths.forEach(w=>x.push(x[x.length-1]+w)); const drawrow=(cells,head=false)=>{ const font=head?9:9;const inner=widths.map(w=>w-10); const wrapped=cells.map((c,i)=>wrap(String(c),inner[i],font));const h=Math.max(24,...wrapped.map(a=>a.length*12+9)); ensure(h+3);const top=y; line(margin,top,W-margin,top);for(const xx of x)line(xx,top,xx,top-h); wrapped.forEach((arr,i)=>arr.forEach((t,j)=>text(t,x[i]+5,top-14-j*12,font,head))); y-=h;line(margin,y,W-margin,y); }; drawrow(headers,true);rows.forEach(r=>drawrow(r));y-=10; }; page(); paragraph('FICHE CHANTIER - BOBINO',18,true,10); paragraph($('job').value.trim()||'Chantier sans nom',14,true,9); paragraph('Rouleau fournisseur : '+$('rw').value+' mm x '+$('rl').value+' m - Perte par coupe : '+$('kerf').value+' mm',10,false,6); heading('Profils et temps de pose'); const profileRows=[...document.querySelectorAll('#printSheet table')][0]; if(profileRows){const rows=[...profileRows.querySelectorAll('tbody tr')].map(tr=>[...tr.cells].map(c=>c.textContent.trim()));table(['Reference','Bobinot','Longueur','Qte','Pose/piece','Temps total'],rows,[105,74,73,45,103,117.28]);} paragraph('Temps de pose estime : '+$('timeTotal').textContent,11,true,8); heading('Bilan de refente'); paragraph('Rouleaux : '+$('rollCount').textContent+' - Bobinots : '+$('stripCount').textContent+' - Longueur totale profils : '+$('totalLength').textContent+' m',10); // La fiche A4 ne contient jamais le cout du rouleau. const sheetText=$('printSheet').textContent; const totalMatch=sheetText.match(/Largeur totale des bobinots\s*:\s*([^\n]+?mm)/i); if(totalMatch)paragraph('Largeur totale des bobinots : '+totalMatch[1],10); heading('Positions de coupe depuis P0'); const rollTables=[...document.querySelectorAll('#printSheet .print-roll')]; rollTables.forEach((roll,i)=>{const title=roll.querySelector('h3')?.textContent||'Rouleau '+(i+1);ensure(32);paragraph(title,11,true,4);const rows=[...roll.querySelectorAll('table tbody tr')].map(tr=>[...tr.cells].map(c=>c.textContent.trim()));table(['Coupe','Profil','Bobinot','CUT depuis P0'],rows,[55,240,90,132.28]);}); paragraph('Calcul indicatif. Bande droite recuperee, reste a gauche reutilise. Temps hors pauses, preparation et imprevus.',8,false,0); pages.push(commands.join('\n')); // Assemble un PDF valide sans librairie et sans connexion internet. const objects=[null];const add=o=>{objects.push(o);return objects.length-1;}; const font1=add('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>'); const font2=add('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>'); const pagesId=add('');let pageIds=[]; for(const content of pages){const stream=add('<< /Length '+content.length+' >>\nstream\n'+content+'\nendstream');const pid=add('<< /Type /Page /Parent '+pagesId+' 0 R /MediaBox [0 0 '+W+' '+H+'] /Resources << /Font << /F1 '+font1+' 0 R /F2 '+font2+' 0 R >> >> /Contents '+stream+' 0 R >>');pageIds.push(pid);} objects[pagesId]='<< /Type /Pages /Count '+pageIds.length+' /Kids ['+pageIds.map(id=>id+' 0 R').join(' ')+'] >>'; const root=add('<< /Type /Catalog /Pages '+pagesId+' 0 R >>'); let result='%PDF-1.4\n%BOBINO\n',offsets=[0]; for(let i=1;i>\nstartxref\n'+start+'\n%%EOF'; const bytes=new Uint8Array(result.length);for(let i=0;i