入口 📖 マニュアル

🧪 調合いざない

手順どおりに作り、写真で記録(製造記録票)
パトミル | 広い敷地をCMに | 有限会社興和化学工業所

🧪 調合いざない

🧪 つくる(調液)

作成量を入れると配合量を自動計算します。手順書を印刷して紙で作業し、調液後に完成手順書を1枚撮影して保存します。

📋 配合レシピ(マスター)

🗂️ 製造記録 一覧・検索

📦 材料一覧・在庫

材料を登録/編集

🌐 表示する言語 / Language

⚙️ 設定(管理者)

社長がGASをデプロイしたら、ここにURLを貼って保存してください(端末ごとに保存・全員に必要)。
🔄 新しい版があります(タップで更新)
'; var w=window.open('','_blank'); if(!w){ alert('ポップアップを許可してください'); return; } w.document.write(html); w.document.close(); } function abortBatch(){ if(!confirm('この調合を中止しますか?(記録は保存されません)'))return; runBatch=null; document.getElementById('make-run').style.display='none'; document.getElementById('make-start').style.display='block'; } function renderRun(){ if(!runBatch)return; var box=document.getElementById('make-run'); var steps=runBatch.steps, st=steps[runStepIdx]; var dots=steps.map(function(s,i){ return ''; }).join(''); var guide=stepGuideText_(st)||''; var h='
' +'
'+esc(runBatch.recipeName)+' ・ '+esc(runBatch.batchNo)+'
' +sourceLinkHtml_(runBatch.sourceFile,{margin:'6px 0 0'}) +'
'+dots+'
' +'
' +'
'+esc(t('step_of'))+' '+(runStepIdx+1)+' '+t('of')+' '+steps.length+' ・ '+esc(stepKindLabel(st.type))+'
' +'
'+esc(guide|| (st.type==='weigh'? (st.material+' を '+st.amount+(st.unit||'')+' 計量'):''))+'
'; if(st.type==='weigh'){ h+='
'+esc(t('target'))+':'+esc(st.material)+' '+esc(st.amount)+esc(st.unit||'')+'
'; // はかり写真(必須) h+=''; h+=thumbStripHtml_(st.scalePhotos,'scalePhotos'); // 実測値(AI読取 or 手入力) var diff=''; if(st.measured!==''&&st.amount!==''&&!isNaN(parseFloat(st.measured))&&!isNaN(parseFloat(st.amount))&&parseFloat(st.amount)!==0){ var d=(parseFloat(st.measured)-parseFloat(st.amount))/parseFloat(st.amount)*100; var ds=(d>=0?'+':'')+d.toFixed(1)+'%'; diff=''+esc(t('diff'))+' '+ds+(Math.abs(d)>5?' ⚠':'')+''; } h+='
' +'
'+esc(st.unit||'')+'
' +'
'+diff+'
'; // 缶・ラベル写真(ロット) h+=''; h+=thumbStripHtml_(st.canPhotos,'canPhotos'); h+='
' +'
'; } else { h+=''; h+=thumbStripHtml_(st.photos,'photos'); } h+=''; // 計量ステップははかり写真が無いと次へ進めない var blockNext=(st.type==='weigh' && (!st.scalePhotos||!st.scalePhotos.length)); h+=''; if(blockNext) h+='
'+esc(t('need_scale_photo'))+'
'; h+='
'; // stepbox // ここまでの記録 h+='
📝 '+esc(t('hist_here'))+''+stepsHistoryHtml_(steps,runStepIdx)+'
'; h+='
'; box.innerHTML=h; applyI18n(); } function thumbStripHtml_(arr,key){ arr=arr||[]; if(!arr.length)return''; return '
'+arr.map(function(p,i){ return '
'; }).join('')+'
'; } function rmStepPhoto(key,i){ var st=runBatch.steps[runStepIdx]; if(st&&st[key]){ st[key].splice(i,1); renderRun(); } } function stepsHistoryHtml_(steps,upto){ var rows=[]; for(var i=0;i✅ '+esc(line)+''); } return rows.length?rows.join(''):'
'+esc('—')+'
'; } function onMeasuredEdit(v){ var st=runBatch.steps[runStepIdx]; st.measured=v; st.measuredAI=false; renderRun(); } function prevStep(){ saveCurNote_(); if(runStepIdx>0){ runStepIdx--; renderRun(); } } function nextStep(){ saveCurNote_(); var steps=runBatch.steps, st=steps[runStepIdx]; if(st.type==='weigh'&&(!st.scalePhotos||!st.scalePhotos.length)){ toast(t('need_scale_photo')); return; } st.done=true; if(runStepIdxString(b.id)===String(r&&r.id)); showPage('records'); if(nb) openRecordSheet(nb.id); }catch(e){ toast('保存に失敗しました(通信状況をご確認ください)'); } setBusy(false); })(); } // status を「内部固定値」で持つ(言語に依存しない)。表示はラベル化。 function t_const_(k){ return k==='st_done'?'完成':(k==='st_appr'?'承認':(k==='st_wait'?'記録待ち':k)); } function statusLabel_(s){ if(s==='記録待ち')return ''+esc(t('st_wait'))+''; if(s==='完成'||s==='done')return ''+esc(t('st_done'))+''; if(s==='承認'||s==='approved')return ''+esc(t('st_appr'))+''; return ''+esc(s||'')+''; } /* ===== ③ 記録一覧/検索 ===== */ function renderRecords(){ var box=document.getElementById('record-list'); if(!box)return; if(!cReady()){ box.innerHTML=''; return; } var q=((document.getElementById('rec-search')||{}).value||'').trim().toLowerCase(); var list=(state.batches||[]).slice(); if(q) list=list.filter(b=>(String(b.batchNo||'')+String(b.recipeName||'')+String(b.madeBy||'')).toLowerCase().indexOf(q)>=0); list.sort((a,b)=>new Date(b.created)-new Date(a.created)); if(!list.length){ box.innerHTML='
'+esc(t('no_records'))+'
'; return; } box.innerHTML=list.map(function(b){ return '
🧪 '+esc(b.batchNo||'')+' ・ '+esc(b.recipeName||'')+' '+statusLabel_(b.status)+'
' +'
'+esc(jdate(b.date)||'')+' ・ '+esc(t('made_by'))+' '+esc(b.madeBy||'')+'
'; }).join(''); } /* バッチの配合量リスト:新形式 items 優先。旧バッチは steps の計量から復元 */ function itemsList_(b){ if(b.items){ var v=b.items; if(typeof v==='string'){ try{ v=JSON.parse(v); }catch(e){ v=[]; } } if(Array.isArray(v)&&v.length) return v; } return stepsList_(b.steps).filter(function(s){ return s.type==='weigh'&&(s.material||s.amount); }) .map(function(s){ return { material:s.material||'', unit:s.unit||'', amount:(s.measured!==''&&s.measured!=null)?s.measured:s.amount }; }); } function openRecordSheet(id){ var b=(state.batches||[]).find(x=>String(x.id)===String(id)); if(!b)return; var recipe=state.recipes.find(r=>String(r.id)===String(b.recipeId)); var specs=specsList_(recipe?recipe.specs:''); var items=itemsList_(b); var rf=srcObj_(b.recordFile); var canEdit=canManage()||(String(b.madeBy||'')===String(currentUser&¤tUser.name)); var h='
' +''; if(canApprove() && (b.status==='完成'||b.status==='done')) h+=''; h+='
'; h+='

🧪 '+esc(t('sheet_title'))+'

'; h+='
' +'
'+esc(t('sheet_no'))+'
'+esc(b.batchNo||'')+'
' +'
'+esc(t('sheet_product'))+'
'+esc(b.recipeName||'')+(recipe&&recipe.product?'('+esc(recipe.product)+')':'')+'
' +'
'+esc(t('sheet_date'))+'
'+esc(jdate(b.date)||'')+'
' +'
'+esc(t('sheet_amount'))+'
'+(b.makeAmount?(esc(fmtKg_(b.makeAmount))+' kg'):'—')+'
' +'
'+esc(t('made_by'))+'
'+esc(b.madeBy||'')+'
' +'
'+esc(t('status'))+'
'+statusLabel_(b.status)+'
' +(b.approver?'
'+esc(t('approved_by'))+'
'+esc(b.approver)+' ・ '+esc(fmtJST(b.approvedAt))+'
':'') +'
'; if(specs.length) h+='
'+esc(t('spec_label'))+'
    '+specs.map(s=>'
  • '+esc(typeof s==='object'?(s.text||''):s)+'
  • ').join('')+'
'; if(recipe) h+=sourceLinkHtml_(recipe.sourceFile,{thumb:true,margin:'8px 0'}); if(items.length){ h+='' +items.map(function(it,i){ return ''; }).join('')+'
#'+esc(t('sheet_item'))+''+esc(t('item_target'))+'
'+(i+1)+''+esc(it.material||'')+''+esc(fmtKg_(it.amount))+esc(it.unit||'')+'
'; } // 完成手順書(記録)=紙に記入後の1枚を電子保存 h+='
'+esc(t('record_file'))+''; if(rf&&rf.url){ var arg=esc(JSON.stringify(rf)).replace(/'/g,'''); if(isImageSrc_(rf)) h+='
'; else h+='
📄 '+esc(rf.name||t('record_file'))+'
'; } else h+='
'+esc(t('no_record_file'))+'
'; if(canEdit){ h+='
'+esc(t('save_record_hint'))+'
' +'
' +'' +'
' +'' +''; } h+='
'; var v=document.getElementById('record-sheet-view'); v.innerHTML=h; v.style.display='block'; document.getElementById('record-list-view').style.display='none'; applyI18n(); } function reprintRecord(id){ var b=(state.batches||[]).find(x=>String(x.id)===String(id)); if(!b)return; var r=state.recipes.find(x=>String(x.id)===String(b.recipeId))||{name:b.recipeName||'',steps:'[]',specs:'',product:''}; chouPrintSheet_({batchNo:b.batchNo,date:b.date,recipe:r,makeAmount:b.makeAmount||'',items:itemsList_(b)}); } async function attachRecordFile(input,id){ var f=input.files&&input.files[0]; input.value=''; if(!f)return; if(f.size>15*1024*1024){ toast('15MB以下のファイルにしてください'); return; } setBusy(true,'💾 '+t('saved')); try{ var data=await readFileDataUrl_(f); if((f.type||'').indexOf('image/')===0){ data=await compressDataUrl_(data,1800,0.85); } await cGas('update',{table:'batches',id:id,fields:{recordFile:JSON.stringify({name:f.name||'記録',type:f.type||'',data:data}),status:'完成'}}); await reloadData(); renderRecords(); openRecordSheet(id); toast(t('record_saved')); }catch(e){ toast('保存に失敗しました'); } setBusy(false); } function closeRecordSheet(){ document.getElementById('record-sheet-view').style.display='none'; document.getElementById('record-list-view').style.display='block'; } async function approveBatch(id){ if(!confirm(t('confirm_appr')))return; try{ await cGas('update',{table:'batches',id,fields:{status:'承認',approver:currentUser.name,approvedAt:new Date().toISOString()}}); await reloadData(); toast(t('appr_done')); openRecordSheet(id); }catch(e){ toast('失敗しました'); } } /* ===== 共通UI ===== */ let _tt; function toast(m){ const e=document.getElementById('toast'); e.textContent=m; e.classList.add('show'); clearTimeout(_tt); _tt=setTimeout(()=>e.classList.remove('show'),2200); } function setBusy(b,msg){ const ov=document.getElementById('busy'); if(ov){ if(b){ const mm=document.getElementById('busy-msg'); if(mm)mm.textContent=msg||'処理中…'; ov.style.display='flex'; } else ov.style.display='none'; } } async function checkUpdate(){ try{ const r=await fetch('version.json?t='+Date.now(),{cache:'no-store'}); const v=await r.json(); if(v&&v.version&&v.version!==APP_VERSION)document.getElementById('upd').style.display='block'; }catch(e){} } (function init(){ loadUser(); applyI18n(); renderLangPicker(); if('serviceWorker' in navigator) navigator.serviceWorker.register('sw.js').catch(()=>{}); setTimeout(checkUpdate,4000); if(currentUser){ initLogin().then(()=>enterApp()); } else { initLogin(); } })();
処理中…
📖 マニュアルAI