🏮 品誠素餅店 POS
📦 庫存系統
🕐 打卡系統
訂購單
客戶管理
訂單查詢
生產統計
叫貨
✏️ 正在編輯訂單
儲存後將覆蓋原訂單
取消編輯
📋 貼上客戶訂單文字,自動帶入基本資訊
訂單號
最新單號
一鍵清除
⚠️ 這個訂單號已經有訂單在用了,請換一個號碼才能儲存
電話
姓名
先生
小姐
編輯資料
👤 常用客戶(無電話,點一下直接帶入)
交貨日
*
時間
:
00
30
早上
中午
下午
晚上
運費
✕
工作日
依交貨日期/時間自動推算,需要的話可以自己改
🚚 宅配(時間自動設為14:00,展開地址欄位)
▾ 更多欄位(第二聯絡人/地址/備註)
第二聯絡人
第二聯絡電話
地址
備註
點選上方商品加入訂單
訂單金額
折扣設定 ▾
折扣類型
無
%
$
原因
小計
$0
運費
$0
折扣
總計
$0
💰 確認付款
付款狀態
未付
💾 儲存訂單
+ 同客戶新增訂單
📄 全新訂單
🗑 刪除訂單
近期訂單
查看全部 →
輸入電話後
顯示歷史訂單
修改品項
品名
規格/口味
單價
數量
取消
確認修改
已選:
0
/
0
入
盒數
-
+
盒(同樣內容一次加入幾盒)
取消
加入訂單
喜餅大禮盒 — 選六種口味 $550
填寫各口味數量,合計六個
已選:
0
/6
取消
加入訂單
蛋糕用途(選一種)
🎂 生日蛋糕
👔 父親節蛋糕
🌷 母親節蛋糕
蛋糕體口味(選一種)
🍦 香草
🍫 巧克力
圍邊選擇(選一種)
夾層選擇(各選一種,可相同)
第一層
第二層
蠟燭
不用蠟燭
一隻蠟燭
依歲數
其他
?
餐盤數量
-
0
+
份(不需要可保持 0)
本項總價
$0
備注(祝賀文字、裝飾需求等)
取消
加入訂單
建立餐盒
每一類都可複選,也可不選;金額會自動加總
目前小計
$0
糕點(可複選;山藥/鹹/起糕請先點品項,再選價格)
+ 其他
加入
麵包(可複選)
+ 其他
加入
點心(可複選)
+ 其他
加入
加購(可複選)
+ 其他
加入
餐盒數量
-
+
份(同樣內容一次加入幾份)
其他備注
取消
加入訂單
新增自訂品項
名稱
價格
取消
加入訂單
目前小計
$0
盒數
-
+
盒(同樣內容一次加入幾盒)
取消
加入訂單
貼上客戶訂單文字
把客戶傳給您的整段文字貼進來,系統會自動抓出日期、收件人、電話、地址;訂購內容會放進備註欄供您參考手動加入品項
取消
解析並帶入
選擇運費
常溫
1盒 $130
2-3盒 $170
4盒 $210
5盒 $250
低溫
1盒 $160
2-3盒 $225
4盒 $290
特殊
🎁 免運
代收服務(依訂單金額,疊加在運費上)
需要代收服務(
依金額自動計算
)
其他
確定
取消
指定這項的訂購人
訂購人姓名(留空表示不指定)
取消
確認
修改價格
新價格
這只會改這張訂單裡這一項的單價,不會影響商品目錄的原價
取消
確認修改
數量
-
+
是否要加禮盒包裝(+$20)
取消
加入訂單
編輯客戶資料
電話
姓名
地址
身分
備注
取消
儲存
+ 新增
📥 匯入客戶資料
📤 匯出目前客戶
🏷 身分設定(
0
)
▾
折扣%僅供參考,實際折扣請在訂購單手動設定
+ 新增身分
身分名稱
參考折扣%
說明
操作
新增身分
身分名稱
參考折扣%
說明
取消
儲存
匯入客戶資料
從 Excel 複製「姓名、電話、地址」三欄貼上(用 Tab 分隔,一行一位客戶;姓名或地址留空也沒關係)。電話號碼相同者會自動更新資料,不會重複新增。
取消
開始匯入
訂單記錄
關閉
編輯資料
編輯客戶
電話
姓名
地址
身分
備注
取消
儲存
新增客戶
電話
姓名
地址
身分
備注
取消
儲存
全部狀態
未付
已付
全部客戶
訂單號:新到舊
訂單號:舊到新
日期:新到舊
日期:舊到新
總計:高到低
總計:低到高
全選
已選 0 筆
📋 複製/匯出
快速勾選訂單號
~
(輸入數字就好,不用打#)
勾選
沒有符合訂單
◀
▶
今天
💾 儲存
匯出預覽
按「複製」後直接到 Excel 裡貼上即可
銷貨單
📋 複製銷貨單
客戶資料
📋 複製客戶資料
關閉
💾 也下載 Excel 檔
{if(g('pane-procure').classList.contains('active'))sizeProcureFrame();}); // ---- 生產統計(每天要製作多少商品) ---- const PROD_CAT_ORDER=['鳳梨酥禮盒','喜餅禮盒','彌月禮盒','生日蛋糕','素糕','麵包','餐盒','散裝','小12入','小15入','小20入','大6入','大8入','大10入','大12入','4+6入','6+8入','小6','大6','其他']; function prodDateOf(o){return o.workday||o.date||'';} // 生產統計專用簡稱(只影響生產統計畫面與列印照片,不改商品原名) const PROD_SHORT_NAME={ '起酥素糕':'起糕','咸素糕':'咸糕','山藥素糕':'山藥','巧克力木屑':'巧屑','巧克力瑞士捲':'巧瑞', '香草橘子':'桔子','咖啡素糕':'咖啡','大圓素糕':'大圓','毛巾巧克力':'毛巧','毛巾原味':'毛原','小圓糕':'小圓' }; // 不列入統計的品項(例如餐盒的加購「盒子」) const PROD_SKIP_NAMES=['盒子']; const PROD_SHORT_EXACT={ '彌月-山藥':'彌-山藥','彌月-蜂蜜':'彌-蜂蜜','彌月-咸素糕':'彌-咸糕','彌月-起酥':'彌-起糕', '彌月-巧克力木屑':'彌-巧屑','彌月-巧克力瑞士捲':'彌-巧瑞','彌月-香草橘子':'彌-桔子','彌月-戚風':'彌-戚風' }; function prodShortName(label){ if(PROD_SHORT_EXACT[label])return PROD_SHORT_EXACT[label]; let s=label; Object.keys(PROD_SHORT_NAME).forEach(k=>{s=s.split(k).join(PROD_SHORT_NAME[k]);}); return s; } function buildProductionSummary(dateStr){ const orders=S.orders.filter(o=>prodDateOf(o)===dateStr); const catMap={}; function addLine(cat,label,qty,unit){ if(PROD_SKIP_NAMES.includes(label))return; label=prodShortName(label); // 不分餐盒/零買/禮盒…,同名品項全部合併成一筆;入/個/只視為同一種單位 cat=''; if(!catMap[cat])catMap[cat]=new Map(); const m=catMap[cat]; const isPiece=['入','個','只'].includes(unit); const key=label+'||'+(isPiece?'P':unit); if(!m.has(key))m.set(key,{label,unit,qty:0}); const rec=m.get(key); if(rec.unit!==unit)rec.unit='個'; rec.qty+=qty; } orders.forEach(o=>{ (o.items||[]).forEach(item=>{ const boxQty=item.qty||1; const cat=mapExportCategory(item); const fengsuParsed=cat==='鳳梨酥禮盒'?parseFengsuBoxName(item.name):null; if(fengsuParsed&&!item.spec){ splitFengsuFlavor(fengsuParsed.flavor,fengsuParsed.size).forEach(part=>{ addLine(cat,part.name,boxQty*part.qty,'入'); }); return; } const fixedPieces=(!item.spec&&FIXED_BOX_PIECES[item.name])?FIXED_BOX_PIECES[item.name]:null; if(fixedPieces){ addLine(cat,item.name,boxQty*fixedPieces,'入'); return; } if(cat==='餐盒'){ const bentoBreakdown=parseBentoSpecBreakdown(item.spec); if(bentoBreakdown){ bentoBreakdown.forEach(b=>addLine(cat,b.name,boxQty*b.qty,'個')); return; } } const breakdown=parseSpecBreakdown(item.spec); if(breakdown){ breakdown.forEach(b=>addLine(cat,b.name,boxQty*b.qty,'個')); }else{ addLine(cat,item.exportFlavor||exportItemName(item.name),boxQty,item.unit||''); } }); }); return{orders,catMap}; } function prodFmtDateWeekday(dateStr){ if(!dateStr)return''; const d=new Date(dateStr+'T00:00:00'); if(isNaN(d))return dateStr; const w=['日','一','二','三','四','五','六'][d.getDay()]; return dateStr+'(週'+w+')'; } function prodTodayStr(){ const n=new Date(); return n.getFullYear()+'-'+String(n.getMonth()+1).padStart(2,'0')+'-'+String(n.getDate()).padStart(2,'0'); } function prodShiftDate(dateStr,days){ const d=new Date(dateStr+'T00:00:00'); d.setDate(d.getDate()+days); return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); } let prodCurrentDate=prodTodayStr(); function renderProdQuickDates(){ const today=prodTodayStr(); // 只顯示近五天:昨天、今天、之後三天 const dates=[]; for(let i=-1;i<=3;i++)dates.push(prodShiftDate(today,i)); const box=g('prod-quick-dates'); box.innerHTML=''; dates.forEach(d=>{ const chip=document.createElement('span'); chip.className='prod-qd-chip'+(d===today?' today':'')+(d===prodCurrentDate?' active':''); chip.textContent=(d===today?'📍':'')+d.slice(5); chip.title=prodFmtDateWeekday(d); chip.addEventListener('click',()=>{prodCurrentDate=d;renderProduction();}); box.appendChild(chip); }); } function renderProduction(){ g('prod-pick-date').value=prodCurrentDate; renderProdQuickDates(); const{orders,catMap}=buildProductionSummary(prodCurrentDate); const totalItems=orders.reduce((s,o)=>s+(o.items||[]).reduce((s2,it)=>s2+(it.qty||1),0),0); g('prod-summary').innerHTML=`
日期:
${prodFmtDateWeekday(prodCurrentDate)}
訂單數:
${orders.length}
筆
品項總件數:
${totalItems}
`; const panel=g('prod-result-panel'); const cats=Object.keys(catMap); if(!orders.length||!cats.length){ panel.innerHTML='
這天沒有需要製作的訂單 🎉
'; return; } cats.sort((a,b)=>{ const ia=PROD_CAT_ORDER.indexOf(a),ib=PROD_CAT_ORDER.indexOf(b); return(ia===-1?999:ia)-(ib===-1?999:ib); }); let html=''; cats.forEach(cat=>{ const lines=[...catMap[cat].values()].sort((a,b)=>b.qty-a.qty); html+=`
${cat?`
${cat}
`:''}`; lines.forEach(l=>{ html+=`
${l.label}
${l.qty}${l.unit}
`; }); html+='
'; }); html+=`
▾ 查看這天的訂單明細(${orders.length}筆)
`; html+=`
`; orders.forEach(o=>{ const itemsTxt=(o.items||[]).map(it=>`${it.name}${it.spec?'('+it.spec+')':''}×${it.qty||1}${it.unit||''}`).join('、'); html+=`
#${o.id} ${o.time||''} ${o.custName&&o.custName!==o.phone?o.custName:(o.phone||'')} — ${itemsTxt}
`; }); html+='
'; panel.innerHTML=html; const toggle=g('prod-order-toggle'); if(toggle){ toggle.addEventListener('click',()=>{ const box=g('prod-order-list-box'); const isOpen=box.style.display!=='none'; box.style.display=isOpen?'none':'block'; toggle.textContent=(isOpen?'▾':'▴')+` 查看這天的訂單明細(${orders.length}筆)`; }); } } g('prod-pick-date').addEventListener('change',()=>{ if(g('prod-pick-date').value){prodCurrentDate=g('prod-pick-date').value;renderProduction();} }); g('prod-btn-prev').addEventListener('click',()=>{prodCurrentDate=prodShiftDate(prodCurrentDate,-1);renderProduction();}); g('prod-btn-next').addEventListener('click',()=>{prodCurrentDate=prodShiftDate(prodCurrentDate,1);renderProduction();}); g('prod-btn-today').addEventListener('click',()=>{prodCurrentDate=prodTodayStr();renderProduction();}); const PROD_FONT_STACK='"Noto Sans TC","PingFang TC","Microsoft JhengHei",sans-serif'; // 列印照片前,先確保網路字型已載入到 canvas 可用(失敗或逾時就用系統字型) async function prodLoadFonts(dateStr){ try{ if(!document.fonts||!document.fonts.load)return; const{catMap}=buildProductionSummary(dateStr); let txt='0123456789'; Object.keys(catMap).forEach(c=>catMap[c].forEach(l=>{txt+=l.label+l.unit;})); await Promise.race([ Promise.all([document.fonts.load('700 100px "Noto Sans TC"',txt),document.fonts.load('900 100px "Noto Sans TC"',txt)]), new Promise(r=>setTimeout(r,4000)) ]); }catch(e){} } function buildProductionPlan(dateStr){ const{catMap}=buildProductionSummary(dateStr); const W=640,PAD=32; const F={ title:`700 56px ${RC_FONT_STACK}`,sub:`600 34px ${RC_FONT_STACK}`,meta:`700 44px ${RC_FONT_STACK}`, catTitle:`700 50px ${RC_FONT_STACK}`,itemRow:`600 46px ${RC_FONT_STACK}`, }; const plan=[]; plan.push({op:'gap',h:PAD}); plan.push({op:'text',text:'品誠素餅店',font:F.title,align:'center',h:60}); plan.push({op:'text',text:'生 產 清 單',font:F.sub,align:'center',h:44}); plan.push({op:'gap',h:10}); plan.push({op:'text',text:prodFmtDateShort(dateStr),font:F.meta,align:'center',h:70,bg:'#e2e2e2'}); plan.push({op:'dash',h:30}); const cats=Object.keys(catMap); // 品項與數量字體放大 3 倍(46px→138px);若最長的一行放不下,就等比例縮小到剛好放得下(最小 46px) let itemSize=138; try{ const mc=document.createElement('canvas').getContext('2d'); cats.forEach(cat=>catMap[cat].forEach(l=>{ const txtL=l.label,txtR=`${l.qty}${l.unit}`; mc.font=`700 100px ${PROD_FONT_STACK}`; const w100=mc.measureText(txtL).width+mc.measureText(txtR).width; if(w100>0){ const fit=Math.floor((W-PAD*2-24)/w100*100); if(fit
{ const ia=PROD_CAT_ORDER.indexOf(a),ib=PROD_CAT_ORDER.indexOf(b); return(ia===-1?999:ia)-(ib===-1?999:ib); }); cats.forEach(cat=>{ const lines=[...catMap[cat].values()].sort((a,b)=>b.qty-a.qty); if(cat)plan.push({op:'text',text:cat,font:F.catTitle,align:'left',h:84,color:'#92400e',border:'#d97706',borderWidth:3,borderPad:20}); lines.forEach(l=>{ plan.push({op:'row',left:l.label,right:`${l.qty}${l.unit}`,font:itemFont,h:itemH}); }); plan.push({op:'gap',h:16}); }); } plan.push({op:'gap',h:PAD}); return{plan,W,PAD}; } function prodFmtDateShort(dateStr){ if(!dateStr)return''; const parts=dateStr.split('-'); if(parts.length!==3)return dateStr; const d=new Date(dateStr+'T00:00:00'); const w=isNaN(d)?'':['日','一','二','三','四','五','六'][d.getDay()]; return`${parts[1]}/${parts[2]}(週${w})`; } function renderProductionCanvas(dateStr){ const{plan,W,PAD}=buildProductionPlan(dateStr); return drawPlanToCanvas(plan,W,PAD); } g('prod-btn-print').addEventListener('click',async()=>{ await prodLoadFonts(prodCurrentDate); const canvas=renderProductionCanvas(prodCurrentDate); await saveCanvasAsImage(canvas,`生產清單_${prodCurrentDate}.png`,g('prod-btn-print')); }); function renderCats(){ g('cat-tabs').innerHTML=''; CATS.forEach(c=>{ const b=document.createElement('button'); b.className='cat-tab'+(c.id===S.currentCat?' active':''); b.textContent=c.name; b.addEventListener('click',()=>{S.currentCat=c.id;renderCats();renderProds();}); g('cat-tabs').appendChild(b); }); } function renderProds(){ const cat=CATS.find(c=>c.id===S.currentCat); const wrap=g('prod-area-wrap'); wrap.innerHTML=''; const area=document.createElement('div');area.className='prod-area'; cat.groups.forEach(grp=>{ const gDiv=document.createElement('div');gDiv.className='prod-group'; const lbl=document.createElement('div');lbl.className='prod-group-label';lbl.textContent=grp.l; const row=document.createElement('div');row.className='prod-row'; grp.items.forEach(it=>{ const b=document.createElement('button');b.className='pb'; let txt=it.n;if(it.p>0&&!['suedan','ludou','xbing12','cantonese'].includes(it.c||''))txt+=`\n$${it.p}`; b.textContent=txt; b.addEventListener('click',()=>handleProd(it)); row.appendChild(b); }); gDiv.appendChild(lbl);gDiv.appendChild(row);area.appendChild(gDiv); }); wrap.appendChild(area); } function resolveProdName(item){ if(item.c==='suedan'||item.c==='ludou')return{name:item.n,price:item.p,unit:'只'}; if(item.c==='xbing12'){ const needsSuffix=['烏豆沙核桃','鳳梨','五仁']; const name=needsSuffix.includes(item.n)?item.n+'大餅':item.n; return{name,price:item.p,unit:'只'}; } return{name:item.n,price:item.p,unit:item.unit,spec:item.fixedSpec||''}; } const QTY_PROMPT_CATS=['snack','sogao','bread']; function handleProd(item){ if(item.t==='gifbox'){openGifbox(item);return;} if(item.t==='bento'){openBento(item);return;} if(item.t==='xbing'){openXbing();return;} if(item.t==='bdcake'){openBdcake(item);return;} if(item.t==='custom'){openCustomItemModal();return;} if(item.t==='flexbox'){openFlexbox(item);return;} if(item.qtyPrompt||QTY_PROMPT_CATS.includes(S.currentCat)){openQuickQtyModal(item);return;} const r=resolveProdName(item); addItem(r.name,'',r.price,undefined,r.unit); } function openPriceEditModal(itemId){ const it=S.items.find(x=>x.id===itemId);if(!it)return; S.pendingPriceEditId=itemId; g('price-edit-title').textContent=`修改「${it.name}」這項在這張訂單的單價`; g('price-edit-input').value=it.price; g('price-edit-modal').classList.add('open'); } function openQuickQtyModal(item){ S.pendingQtyItem=item; S.pendingQtyEditId=null; const r=resolveProdName(item); g('quick-qty-title').textContent=r.name+' $'+r.price; g('quick-qty-input').value='1'; g('quick-qty-unit').textContent=r.unit||''; g('quick-qty-giftbox-row').style.display=item.giftBoxOpt?'block':'none'; g('quick-qty-giftbox-check').checked=false; g('quick-qty-modal').classList.add('open'); } g('quick-qty-minus').addEventListener('click',()=>{ const v=Math.max(1,(parseInt(g('quick-qty-input').value)||1)-1); g('quick-qty-input').value=v; }); g('quick-qty-plus').addEventListener('click',()=>{ const v=(parseInt(g('quick-qty-input').value)||1)+1; g('quick-qty-input').value=v; }); g('quick-qty-input').addEventListener('input',e=>{ e.target.value=e.target.value.replace(/[^0-9]/g,''); }); g('quick-qty-confirm').addEventListener('click',()=>{ const qty=Math.max(1,parseInt(g('quick-qty-input').value)||1); if(S.pendingQtyEditId){ const it=S.items.find(x=>x.id===S.pendingQtyEditId); if(it){it.qty=qty;renderTbl();calcTotal();} S.pendingQtyEditId=null; closeM('quick-qty-modal'); return; } const item=S.pendingQtyItem;if(!item)return; const r=resolveProdName(item); const withGiftBox=item.giftBoxOpt&&g('quick-qty-giftbox-check').checked; const price=withGiftBox?r.price+20:r.price; const spec=withGiftBox?'含禮盒包裝(+$20)':(r.spec||''); addItem(r.name,spec,price,qty,r.unit); closeM('quick-qty-modal'); }); g('quick-qty-cancel').addEventListener('click',()=>closeM('quick-qty-modal')); g('price-edit-confirm').addEventListener('click',()=>{ const it=S.items.find(x=>x.id===S.pendingPriceEditId);if(!it)return; const v=parseInt(g('price-edit-input').value); if(isNaN(v)||v<0){alert('請輸入正確的價格');return;} it.price=v; renderTbl(); calcTotal(); closeM('price-edit-modal'); }); g('price-edit-cancel').addEventListener('click',()=>closeM('price-edit-modal')); function openFlexbox(item){ S.pendingFlexbox=item; S.pendingFlexboxEditId=null; S.flexEvenSel=new Set(); g('flexbox-title').textContent=item.n; g('flexbox-note').textContent=item.requiredTotal?`請選滿 ${item.requiredTotal} 入,金額自動加總`:'自由選擇數量,金額自動加總'; g('flexbox-qty-input').value='1'; let body=''; if(item.packagingOptions){ body+='
包裝方式
'; item.packagingOptions.forEach((opt,oi)=>{ body+=`
${opt}
`; }); body+='
'; } item.flexGroups.forEach((grp,gi)=>{ body+=`
${grp.title}
`; grp.items.forEach((it,ii)=>{ const fillCell=item.requiredTotal?`
均分選這
`:''; body+=`
${it.name} $${it.price}
-
+
${fillCell}
`; }); body+='
'; }); g('flexbox-body').innerHTML=body; flexboxRunningTotal(); g('flexbox-modal').classList.add('open'); } function flexboxRunningTotal(){ const inputs=[...document.querySelectorAll('.fx-qty')]; const total=inputs.reduce((s,i)=>s+(+i.value||0)*(+i.dataset.price),0); const count=inputs.reduce((s,i)=>s+(+i.value||0),0); const item=S.pendingFlexbox; g('flexbox-running-total').textContent='$'+total.toLocaleString()+(item&&item.requiredTotal?`(已選 ${count}/${item.requiredTotal} 入)`:`(共 ${count} 入)`); return{total,count}; } function applyFlexEvenSplit(){ const item=S.pendingFlexbox;if(!item||!item.requiredTotal)return; const cap=item.requiredTotal; const keys=[...S.flexEvenSel]; const n=keys.length; const base=n?Math.floor(cap/n):0; let remainder=n?cap-base*n:0; document.querySelectorAll('.fx-qty').forEach(inp=>{ const key=inp.dataset.gi+'-'+inp.dataset.ii; const fillBtn=inp.closest('tr').querySelector('.fx-fill'); if(keys.includes(key)){ let val=base;if(remainder>0){val+=1;remainder--;} inp.value=val; if(fillBtn)fillBtn.classList.add('gf-fill-active'); }else{ inp.value=0; if(fillBtn)fillBtn.classList.remove('gf-fill-active'); } }); flexboxRunningTotal(); } g('flexbox-body').addEventListener('input',e=>{ if(e.target.classList.contains('fx-qty')){e.target.value=e.target.value.replace(/[^0-9]/g,'');flexboxRunningTotal();} }); g('flexbox-body').addEventListener('click',e=>{ const fillBtn=e.target.closest('.fx-fill'); if(fillBtn){ const tr=fillBtn.closest('tr');const inp=tr.querySelector('.fx-qty'); const key=inp.dataset.gi+'-'+inp.dataset.ii; if(!S.flexEvenSel)S.flexEvenSel=new Set(); if(S.flexEvenSel.has(key))S.flexEvenSel.delete(key);else S.flexEvenSel.add(key); applyFlexEvenSplit(); return; } const tr=e.target.closest('tr');if(!tr)return; const inp=tr.querySelector('.fx-qty');if(!inp)return; if(e.target.closest('.fx-minus')){inp.value=Math.max(0,(+inp.value||0)-1);flexboxRunningTotal();} else if(e.target.closest('.fx-plus')){inp.value=(+inp.value||0)+1;flexboxRunningTotal();} }); g('flexbox-qty-minus').addEventListener('click',()=>{ const v=Math.max(1,(parseInt(g('flexbox-qty-input').value)||1)-1); g('flexbox-qty-input').value=v; }); g('flexbox-qty-plus').addEventListener('click',()=>{ const v=(parseInt(g('flexbox-qty-input').value)||1)+1; g('flexbox-qty-input').value=v; }); g('flexbox-qty-input').addEventListener('input',e=>{ e.target.value=e.target.value.replace(/[^0-9]/g,''); }); g('flexbox-confirm').addEventListener('click',()=>{ const item=S.pendingFlexbox;if(!item)return; const{total,count}=flexboxRunningTotal(); if(count<=0){alert('請至少選擇一項');return;} if(item.requiredTotal&&count!==item.requiredTotal){alert(`請選滿 ${item.requiredTotal} 入(目前 ${count} 入)`);return;} const boxQty=Math.max(1,parseInt(g('flexbox-qty-input').value)||1); const inputs=[...document.querySelectorAll('.fx-qty')].filter(i=>+i.value>0); const spec=inputs.map(i=>`${i.dataset.name}×${i.value}`).join('、'); let displayName=item.n; if(item.packagingOptions){ const checked=document.querySelector('input[name="flexbox-packaging"]:checked'); if(checked)displayName=checked.value; } if(S.pendingFlexboxEditId){ const it=S.items.find(x=>x.id===S.pendingFlexboxEditId); if(it){it.name=displayName;it.spec=spec;it.price=total;it.qty=boxQty;renderTbl();calcTotal();} S.pendingFlexboxEditId=null; closeM('flexbox-modal'); return; } addItem(displayName,spec,total,boxQty,'盒'); closeM('flexbox-modal'); }); g('flexbox-cancel').addEventListener('click',()=>closeM('flexbox-modal')); function openGifbox(item){ S.pendingGif=item;S.pendingGifIsAutoMix=false;S.pendingGifEditId=null;g('gifbox-title').textContent=item.n+' $'+item.p; g('gifbox-max').textContent=item.total;g('gifbox-used').textContent=0;g('gifbox-status').textContent=''; g('gifbox-qty-input').value='1'; const normalCols=item.cols.filter(c=>!c.special); const two=normalCols.length>1; g('gifbox-note').textContent=two?normalCols.map(c=>c.title).join(' + '):`口味自由搭配,共 ${item.total} 入`; S.gifEvenSel={};S.gifEvenSelGlobal=new Set(); let body=two?'
':''; item.cols.forEach((col,ci)=>{ if(col.special)return; body+=`
${col.title}
`; col.fl.forEach(f=>{body+=`
${f}
-
+
均分選這
`;}); body+='
'; }); body+=(two?'
':''); item.cols.forEach((col,ci)=>{ if(!col.special)return; body+=`
▾ ${col.title}
`; col.special.forEach(sp=>{ body+=`
${sp.name}${sp.price>0?`
-$${sp.price}
`:''}
-
+
均分選這
`; }); body+='
'; }); g('gifbox-body').innerHTML=body; const specialToggle=document.getElementById('gifbox-special-toggle'); if(specialToggle){ specialToggle.addEventListener('click',()=>{ const sb=document.getElementById('gifbox-special-body'); const isOpen=sb.style.display!=='none'; sb.style.display=isOpen?'none':'block'; specialToggle.textContent=(isOpen?'▾':'▴')+specialToggle.textContent.slice(1); }); } const qa=g('gifbox-quick-actions');qa.innerHTML=''; const autoMixBtn=document.createElement('button'); autoMixBtn.type='button';autoMixBtn.className='pb pb-mix';autoMixBtn.style.cssText='flex:1;min-width:140px;padding:9px'; autoMixBtn.textContent='✨ 一鍵均分成綜合口味'; autoMixBtn.addEventListener('click',applyGifboxAutoMix); qa.appendChild(autoMixBtn); (item.presets||[]).forEach(preset=>{ const presetBtn=document.createElement('button'); presetBtn.type='button';presetBtn.className='pb pb-mix';presetBtn.style.cssText='flex:1;min-width:100px;padding:9px'; presetBtn.textContent=preset.label; presetBtn.addEventListener('click',()=>applyGifboxPreset(preset)); qa.appendChild(presetBtn); }); g('gifbox-modal').classList.add('open'); } function computeGifboxUnitPrice(item,inputs){ const deduction=inputs.reduce((s,i)=>s+(i.dataset.price?(+i.value||0)*(+i.dataset.price):0),0); return Math.max(0,item.p-deduction); } function refreshGifStatus(){ const item=S.pendingGif;const inputs=[...document.querySelectorAll('.gf-qty')]; const t=inputs.reduce((s,i)=>s+(+i.value||0),0); g('gifbox-used').textContent=t; const ok=t===item.total; g('gifbox-status').textContent=ok?'✓ 正確':'尚差 '+(item.total-t)+' 入';g('gifbox-status').style.color=ok?'#059669':'#dc2626'; const unitPrice=computeGifboxUnitPrice(item,inputs); const preview=g('gifbox-price-preview'); if(preview)preview.textContent=unitPrice!==item.p?`禮盒金額:$${unitPrice}(原價$${item.p},已扣除特殊選單)`:''; } function updGif(el){ el.value=el.value.replace(/[^0-9]/g,''); const item=S.pendingGif;const inputs=[...document.querySelectorAll('.gf-qty')]; const groupCis=new Set(); item.cols.forEach((col,ci)=>{if(gifColCapped(col))groupCis.add(gifGroupCi(item,ci));}); groupCis.forEach(groupCi=>{ const cap=item.cols[groupCi].max; const ct=inputs.filter(i=>gifGroupCi(item,+i.dataset.ci)===groupCi).reduce((s,i)=>s+(+i.value||0),0); if(ct>cap&&gifGroupCi(item,+el.dataset.ci)===groupCi){ el.value=Math.max(0,(+el.value||0)-(ct-cap)); } }); const t=inputs.reduce((s,i)=>s+(+i.value||0),0);if(t>item.total){el.value=Math.max(0,(+el.value||0)-(t-item.total));} refreshGifStatus(); } function gifGroupCi(item,ci){ const col=item.cols[ci]; return col.sharesCapWith!=null?col.sharesCapWith:ci; } function gifColCapped(col){ return col.max!=null||col.sharesCapWith!=null; } function toggleGifEven(ci,f,btn){ const item=S.pendingGif;const col=item.cols[ci]; if(gifColCapped(col)){ const groupCi=gifGroupCi(item,ci); if(!S.gifEvenSel[groupCi])S.gifEvenSel[groupCi]=new Set(); const key=ci+'|'+f; const set=S.gifEvenSel[groupCi]; if(set.has(key))set.delete(key);else set.add(key); applyGifEvenSplit(groupCi); }else{ if(!S.gifEvenSelGlobal)S.gifEvenSelGlobal=new Set(); const key=ci+'|'+f; if(S.gifEvenSelGlobal.has(key))S.gifEvenSelGlobal.delete(key);else S.gifEvenSelGlobal.add(key); applyGifEvenSplitGlobal(); } } function applyGifEvenSplit(groupCi){ const item=S.pendingGif;const groupCol=item.cols[groupCi]; const cap=groupCol.max; const set=S.gifEvenSel[groupCi]||new Set(); const n=set.size; const base=n?Math.floor(cap/n):0; let remainder=n?cap-base*n:0; document.querySelectorAll('.gf-qty').forEach(inp=>{ const inpCi=+inp.dataset.ci; if(gifGroupCi(item,inpCi)!==groupCi)return; const key=inpCi+'|'+inp.dataset.f; const row=inp.closest('tr');const fillBtn=row.querySelector('.gf-fill-btn'); if(set.has(key)){ let val=base;if(remainder>0){val+=1;remainder--;} inp.value=val; fillBtn.classList.add('gf-fill-active'); }else{ inp.value=0; fillBtn.classList.remove('gf-fill-active'); } }); refreshGifStatus(); } function applyGifEvenSplitGlobal(){ const item=S.pendingGif; const cappedTotal=[...document.querySelectorAll('.gf-qty')].reduce((s,inp)=>{ const col=item.cols[+inp.dataset.ci]; if(!col||!gifColCapped(col))return s; return s+(+inp.value||0); },0); const cap=Math.max(0,item.total-cappedTotal); const set=S.gifEvenSelGlobal||new Set(); const n=set.size; const base=n?Math.floor(cap/n):0; let remainder=n?cap-base*n:0; document.querySelectorAll('.gf-qty').forEach(inp=>{ const col=item.cols[+inp.dataset.ci]; if(!col||gifColCapped(col))return; const key=inp.dataset.ci+'|'+inp.dataset.f; const row=inp.closest('tr');const fillBtn=row.querySelector('.gf-fill-btn'); if(set.has(key)){ let val=base;if(remainder>0){val+=1;remainder--;} inp.value=val; fillBtn.classList.add('gf-fill-active'); }else{ inp.value=0; fillBtn.classList.remove('gf-fill-active'); } }); refreshGifStatus(); } g('gifbox-body').addEventListener('input',e=>{if(e.target.classList.contains('gf-qty'))updGif(e.target);}); g('gifbox-body').addEventListener('click',e=>{ const tr=e.target.closest('tr');if(!tr)return; const inp=tr.querySelector('.gf-qty');if(!inp)return; if(e.target.closest('.gf-minus')){inp.value=Math.max(0,(+inp.value||0)-1);updGif(inp);} else if(e.target.closest('.gf-plus')){inp.value=(+inp.value||0)+1;updGif(inp);} else if(e.target.closest('.gf-fill-btn')){toggleGifEven(+inp.dataset.ci,inp.dataset.f);} }); function applyGifboxAutoMix(){ const item=S.pendingGif;if(!item)return; item.cols.forEach((col,ci)=>{ if(!col.fl)return; let priorityList,origOrder; if(col.fl.includes('柚子')){priorityList=SUEDAN_MIX_PRIORITY;origOrder=SUEDAN_FL;} else if(col.fl.includes('大甜')){priorityList=LUDOU_MIX_PRIORITY;origOrder=LUDOU_FL;} else return; const cap=col.max!=null?col.max:item.total; const n=priorityList.length; const base=Math.floor(cap/n); const remainder=cap-base*n; const qtyMap={}; priorityList.forEach((f,idx)=>{ let val=base;if(idx
{ if(+inp.dataset.ci!==ci)return; inp.value=qtyMap[inp.dataset.f]||0; }); }); refreshGifStatus(); S.pendingGifIsAutoMix=true; } function applyGifboxPreset(preset){ const item=S.pendingGif;if(!item)return; const partsText=preset.type==='half-suedan-cantonese'?computeHalfSuedanCantonese(item.total):preset.parts; const qtyMap={}; partsText.split('、').forEach(seg=>{ const idx=seg.lastIndexOf('×'); if(idx<0)return; const name=seg.slice(0,idx).trim(); const qty=parseInt(seg.slice(idx+1))||0; qtyMap[name]=qty; }); document.querySelectorAll('.gf-qty').forEach(inp=>{ inp.value=qtyMap[inp.dataset.f]||0; }); refreshGifStatus(); S.pendingGifIsAutoMix=false; } g('gifbox-confirm').addEventListener('click',()=>{ const item=S.pendingGif;const inputs=[...document.querySelectorAll('.gf-qty')]; const total=inputs.reduce((s,i)=>s+(+i.value||0),0); if(total!==item.total){alert(`請填滿 ${item.total} 入(目前 ${total} 入)`);return;} const qty=Math.max(1,parseInt(g('gifbox-qty-input').value)||1); const displayName=S.pendingGifIsAutoMix?item.n+'(綜)':item.n; const spec=inputs.filter(i=>+i.value>0).map(i=>`${i.dataset.f}×${i.value}`).join('、'); const unitPrice=computeGifboxUnitPrice(item,inputs); if(S.pendingGifEditId){ const it=S.items.find(x=>x.id===S.pendingGifEditId); if(it){it.name=displayName;it.spec=spec;it.price=unitPrice;it.qty=qty;renderTbl();calcTotal();} S.pendingGifEditId=null; closeM('gifbox-modal'); return; } addItem(displayName,spec,unitPrice,qty,'盒');closeM('gifbox-modal'); }); g('gifbox-cancel').addEventListener('click',()=>closeM('gifbox-modal')); g('gifbox-qty-minus').addEventListener('click',()=>{ const v=Math.max(1,(parseInt(g('gifbox-qty-input').value)||1)-1); g('gifbox-qty-input').value=v; }); g('gifbox-qty-plus').addEventListener('click',()=>{ const v=(parseInt(g('gifbox-qty-input').value)||1)+1; g('gifbox-qty-input').value=v; }); g('gifbox-qty-input').addEventListener('input',e=>{ e.target.value=e.target.value.replace(/[^0-9]/g,''); }); function openXbing(){ const half=Math.ceil(XBING_FL.length/2); S.xbEvenSel=new Set(); const rowsHtml=fl=>fl.map(f=>`
${f}
-
+
均分選這
`).join(''); let html='
素旦黃酥系列
'+rowsHtml(XBING_FL.slice(0,half))+'
'; html+='
綠豆椪系列
'+rowsHtml(XBING_FL.slice(half))+'
'; g('xbing-body').innerHTML=html;g('xbing-used').textContent=0;g('xbing-status').textContent=''; g('xbing-modal').classList.add('open'); } function refreshXbingStatus(){ const t=[...document.querySelectorAll('.xb-qty')].reduce((s,i)=>s+(+i.value||0),0); g('xbing-used').textContent=t;const ok=t===6; g('xbing-status').textContent=ok?'✓ 正確':'尚差 '+(6-t);g('xbing-status').style.color=ok?'#059669':'#dc2626'; } function clampXbingInput(el){ el.value=el.value.replace(/[^0-9]/g,''); const t=[...document.querySelectorAll('.xb-qty')].reduce((s,i)=>s+(+i.value||0),0); if(t>6)el.value=Math.max(0,(+el.value||0)-(t-6)); refreshXbingStatus(); } function applyXbingEvenSplit(){ const set=S.xbEvenSel;const n=set.size; const base=n?Math.floor(6/n):0; let remainder=n?6-base*n:0; document.querySelectorAll('.xb-qty').forEach(inp=>{ const f=inp.dataset.f; const row=inp.closest('tr');const fillBtn=row.querySelector('.xb-fill'); if(set.has(f)){ let val=base;if(remainder>0){val+=1;remainder--;} inp.value=val; fillBtn.classList.add('gf-fill-active'); }else{ inp.value=0; fillBtn.classList.remove('gf-fill-active'); } }); refreshXbingStatus(); } g('xbing-body').addEventListener('input',e=>{if(e.target.classList.contains('xb-qty'))clampXbingInput(e.target);}); g('xbing-body').addEventListener('click',e=>{ const tr=e.target.closest('tr');if(!tr)return; const inp=tr.querySelector('.xb-qty');if(!inp)return; if(e.target.closest('.xb-minus')){inp.value=Math.max(0,(+inp.value||0)-1);clampXbingInput(inp);} else if(e.target.closest('.xb-plus')){inp.value=(+inp.value||0)+1;clampXbingInput(inp);} else if(e.target.closest('.xb-fill')){ const f=inp.dataset.f; if(S.xbEvenSel.has(f))S.xbEvenSel.delete(f);else S.xbEvenSel.add(f); applyXbingEvenSplit(); } }); g('xbing-confirm').addEventListener('click',()=>{ const inputs=[...document.querySelectorAll('.xb-qty')]; const t=inputs.reduce((s,i)=>s+(+i.value||0),0); if(t!==6){alert('請選滿六種(目前 '+t+' 種)');return;} addItem('喜餅大禮盒',inputs.filter(i=>+i.value>0).map(i=>`${i.dataset.f}×${i.value}`).join('、'),550,undefined,'盒');closeM('xbing-modal'); }); g('xbing-cancel').addEventListener('click',()=>closeM('xbing-modal')); const BD_BORDERS=['山藥邊','巧克力邊','奶油邊','無奶油邊']; const BD_LAYERS=['芋頭','布丁','水果','核桃','蔓越莓','山藥']; function bdcakeUpdatePrice(){ const item=S.pendingBdcake;if(!item)return; const flavorSel=g('bdcake-modal-inner').querySelector('[data-type="bd-flavor"].sel'); const basePrice=flavorSel?item.prices[flavorSel.dataset.val]:null; g('bdcake-total-price').textContent=basePrice!=null?'$'+basePrice.toLocaleString():'(請先選口味)'; if(flavorSel){ g('bdcake-price-hint').textContent=`${flavorSel.dataset.val} ${item.size} 定價 $${basePrice}`; } else { g('bdcake-price-hint').textContent=''; } } function bdcakeUpdateLayerHint(){ const l1=g('bdcake-layer1-row')?.querySelector('.chip.sel'); const l2=g('bdcake-layer2-row')?.querySelector('.chip.sel'); const ok=l1&&l2; g('bdcake-layer-hint').textContent=ok?`✓ 第一層:${l1.dataset.val},第二層:${l2.dataset.val}`:(!l1&&!l2)?'請選擇兩層夾層':!l1?'請選擇第一層':'請選擇第二層'; g('bdcake-layer-hint').style.color=ok?'#059669':'#d97706'; } function openBdcake(item){ S.pendingBdcake=item; S.bdPlateCount=0; g('bdcake-base').textContent='尺寸:'+item.size+' 請選口味後顯示定價'; g('bdcake-note-input').value=''; g('bdcake-plate-count').textContent='0'; g('bdcake-candle-age').value=''; g('bdcake-candle-other').value=''; g('bdcake-candle-age-wrap').style.display='none'; g('bdcake-candle-other-wrap').style.display='none'; g('bdcake-modal-inner').querySelectorAll('[data-type="bd-flavor"]').forEach(c=>c.classList.remove('sel')); g('bdcake-modal-inner').querySelectorAll('[data-type="bd-candle"]').forEach(c=>c.classList.remove('sel')); // 預設選取「生日蛋糕」用途,方便快速下單,需要的話可再改選 g('bdcake-occasion-row').querySelectorAll('.chip').forEach(c=>c.classList.remove('sel')); const defaultOccasion=g('bdcake-occasion-row').querySelector('[data-val="生日蛋糕"]'); defaultOccasion.classList.add('sel'); bdcakeUpdateTitle(); const borderRow=g('bdcake-border-row');borderRow.innerHTML=''; BD_BORDERS.forEach(b=>{ const s=document.createElement('span');s.className='chip';s.textContent=b; s.dataset.type='bd-border';s.dataset.val=b; borderRow.appendChild(s); }); ['bdcake-layer1-row','bdcake-layer2-row'].forEach(rowId=>{ const row=g(rowId);row.innerHTML=''; BD_LAYERS.forEach(l=>{ const s=document.createElement('span');s.className='chip';s.textContent=l; s.dataset.type=rowId==='bdcake-layer1-row'?'bd-layer1':'bd-layer2'; s.dataset.val=l; row.appendChild(s); }); }); bdcakeUpdateLayerHint(); bdcakeUpdatePrice(); g('bdcake-modal').classList.add('open'); } function bdcakeUpdateTitle(){ const item=S.pendingBdcake;if(!item)return; const occSel=g('bdcake-occasion-row').querySelector('.chip.sel'); g('bdcake-title').textContent=(occSel?occSel.dataset.val:'節慶蛋糕')+' — '+item.size; } g('bdcake-modal-inner').addEventListener('click',e=>{ const chip=e.target.closest('.chip'); if(!chip)return; const type=chip.dataset.type; if(type==='bd-occasion'){ g('bdcake-occasion-row').querySelectorAll('.chip').forEach(c=>c.classList.remove('sel')); chip.classList.add('sel'); bdcakeUpdateTitle(); } else if(type==='bd-flavor'){ g('bdcake-modal-inner').querySelectorAll('[data-type="bd-flavor"]').forEach(c=>c.classList.remove('sel')); chip.classList.add('sel'); bdcakeUpdatePrice(); } else if(type==='bd-border'){ g('bdcake-border-row').querySelectorAll('.chip').forEach(c=>c.classList.remove('sel')); chip.classList.add('sel'); } else if(type==='bd-layer1'){ g('bdcake-layer1-row').querySelectorAll('.chip').forEach(c=>c.classList.remove('sel')); chip.classList.add('sel'); bdcakeUpdateLayerHint(); } else if(type==='bd-layer2'){ g('bdcake-layer2-row').querySelectorAll('.chip').forEach(c=>c.classList.remove('sel')); chip.classList.add('sel'); bdcakeUpdateLayerHint(); } else if(type==='bd-candle'){ g('bdcake-candle-row').querySelectorAll('.chip').forEach(c=>c.classList.remove('sel')); chip.classList.add('sel'); g('bdcake-candle-age-wrap').style.display=chip.dataset.val==='age'?'block':'none'; g('bdcake-candle-other-wrap').style.display=chip.dataset.val==='other'?'block':'none'; } }); g('bdcake-plate-minus').addEventListener('click',()=>{ if(S.bdPlateCount>0){S.bdPlateCount--;g('bdcake-plate-count').textContent=S.bdPlateCount;} }); g('bdcake-plate-plus').addEventListener('click',()=>{ S.bdPlateCount++;g('bdcake-plate-count').textContent=S.bdPlateCount; }); g('bdcake-confirm').addEventListener('click',()=>{ const item=S.pendingBdcake; const occSel=g('bdcake-occasion-row').querySelector('.chip.sel'); const flavorSel=g('bdcake-modal-inner').querySelector('[data-type="bd-flavor"].sel'); const borderSel=g('bdcake-border-row').querySelector('.chip.sel'); const layer1Sel=g('bdcake-layer1-row').querySelector('.chip.sel'); const layer2Sel=g('bdcake-layer2-row').querySelector('.chip.sel'); const candleSel=g('bdcake-candle-row').querySelector('.chip.sel'); if(!occSel){alert('請選擇蛋糕用途');return;} if(!flavorSel){alert('請選擇蛋糕體口味');return;} if(!borderSel){alert('請選擇圍邊');return;} if(!layer1Sel||!layer2Sel){alert('請選擇兩層夾層');return;} if(!candleSel){alert('請選擇蠟燭需求');return;} let candleText=''; if(candleSel.dataset.val==='none')candleText='不用蠟燭'; else if(candleSel.dataset.val==='one')candleText='蠟燭:一隻'; else if(candleSel.dataset.val==='unknown')candleText='蠟燭:?'; else if(candleSel.dataset.val==='age'){ const age=g('bdcake-candle-age').value.trim(); if(!age){alert('請輸入蠟燭歲數');return;} candleText='蠟燭:'+age+'歲'; } else if(candleSel.dataset.val==='other'){ const other=g('bdcake-candle-other').value.trim(); if(!other){alert('請描述蠟燭需求');return;} candleText='蠟燭:'+other; } const layers=layer1Sel.dataset.val+'+'+layer2Sel.dataset.val; const finalPrice=item.prices[flavorSel.dataset.val]; const specLines=[`${flavorSel.dataset.val} ${item.size},${borderSel.dataset.val}`,layers]; const line3=[candleText]; if(S.bdPlateCount>0)line3.push(`餐盤:${S.bdPlateCount}份`); specLines.push(line3.join(',')); const note=g('bdcake-note-input').value.trim(); if(note)specLines.push(note); const spec=specLines.join('\n'); const newIt=addItem(occSel.dataset.val,spec,finalPrice); newIt.exportFlavor=item.size; closeM('bdcake-modal'); }); g('bdcake-cancel').addEventListener('click',()=>closeM('bdcake-modal')); /* ===== 餐盒:每一類可複選,自動加總金額 ===== */ function mkChip(text,selected){const s=document.createElement('span');s.className='chip'+(selected?' sel':'');s.textContent=text;return s;} function bentoRunningTotal(){ const total=['cake','bread','dessert','addon'].reduce((s,k)=> s+S.bentoSel[k].reduce((s2,it)=>s2+it.price,0)+S.bentoCustom[k].reduce((s2,it)=>s2+it.price,0),0); g('bento-running-total').textContent='$'+total.toLocaleString(); return total; } function renderBentoCustomTags(catKey){ const row=g('bento-custom-'+catKey);if(!row)return; row.innerHTML=''; S.bentoCustom[catKey].forEach(it=>{ const chip=document.createElement('span');chip.className='chip sel'; chip.textContent=it.name+' $'+it.price+' ✕'; chip.dataset.kind='custom-remove';chip.dataset.cat=catKey;chip.dataset.id=it.id; row.appendChild(chip); }); } function addBentoCustom(catKey){ const nameInput=g('bento-other-name-'+catKey); const priceInput=g('bento-other-price-'+catKey); const name=nameInput.value.trim(); const price=parseInt(priceInput.value); if(!name){alert('請輸入名稱');return;} if(!price&&price!==0||price<0){alert('請輸入正確的價格');return;} S.bentoCustom[catKey].push({id:'bc'+Date.now()+Math.random(),name,price}); nameInput.value='';priceInput.value=''; renderBentoCustomTags(catKey); bentoRunningTotal(); } function renderBentoCakeRow(){ const row=g('bento-cake-row');row.innerHTML=''; const fixedWrap=document.createElement('div');fixedWrap.className='prod-row'; BENTO_CAKE_FIXED.forEach(it=>{ const selected=S.bentoSel.cake.some(s=>s.name===it.name&&s.price===it.price); const chip=mkChip(it.name+' $'+it.price,selected); chip.dataset.kind='fixed';chip.dataset.cat='cake';chip.dataset.name=it.name;chip.dataset.price=it.price; fixedWrap.appendChild(chip); }); row.appendChild(fixedWrap); const varWrap=document.createElement('div');varWrap.style.cssText='display:flex;flex-direction:column;gap:6px;margin-top:8px'; BENTO_CAKE_VARIABLE.forEach(it=>{ const selEntry=S.bentoSel.cake.find(s=>s.name===it.name); const isOpen=S.bentoVarOpenSet.has(it.name); const line=document.createElement('div');line.style.cssText='display:flex;align-items:center;gap:6px;flex-wrap:wrap'; const nameChip=mkChip(it.name+(selEntry?(' $'+selEntry.price+' ✓'):''),!!selEntry); nameChip.dataset.kind='var-toggle';nameChip.dataset.cat='cake';nameChip.dataset.name=it.name; line.appendChild(nameChip); if(isOpen&&!selEntry){ it.prices.forEach(p=>{ const pchip=mkChip('$'+p,false);pchip.style.cssText='padding:6px 10px;font-size:11px'; pchip.dataset.kind='var-price';pchip.dataset.cat='cake';pchip.dataset.name=it.name;pchip.dataset.price=p; line.appendChild(pchip); }); } varWrap.appendChild(line); }); row.appendChild(varWrap); } function renderBentoSimpleRow(rowId,list,catKey){ const row=g(rowId);row.innerHTML=''; list.forEach(it=>{ const selected=S.bentoSel[catKey].some(s=>s.name===it.name&&s.price===it.price); const chip=mkChip(it.label,selected); chip.dataset.kind='fixed';chip.dataset.cat=catKey;chip.dataset.name=it.name;chip.dataset.price=it.price; row.appendChild(chip); }); } function openBento(){ S.bentoSel={cake:[],bread:[],dessert:[],addon:[]}; S.bentoCustom={cake:[],bread:[],dessert:[],addon:[]}; S.bentoVarOpenSet=new Set(); S.bentoQty=1; g('bento-qty-count').value='1'; g('bento-note-input').value=''; ['cake','bread','dessert','addon'].forEach(cat=>{ g('bento-other-name-'+cat).value=''; g('bento-other-price-'+cat).value=''; g('bento-other-form-'+cat).style.display='none'; renderBentoCustomTags(cat); }); renderBentoCakeRow(); renderBentoSimpleRow('bento-bread-row',BENTO_BREAD_ITEMS,'bread'); renderBentoSimpleRow('bento-dessert-row',BENTO_DESSERT_ITEMS,'dessert'); renderBentoSimpleRow('bento-addon-row',BENTO_ADDONS,'addon'); bentoRunningTotal(); g('bento-modal').classList.add('open'); } g('bento-modal-inner').addEventListener('click',e=>{ const otherToggle=e.target.closest('.bento-other-toggle'); if(otherToggle){ const cat=otherToggle.dataset.cat; const form=g('bento-other-form-'+cat); form.style.display=form.style.display==='none'?'flex':'none'; return; } const otherAdd=e.target.closest('.bento-other-add'); if(otherAdd){addBentoCustom(otherAdd.dataset.cat);return;} const chip=e.target.closest('.chip'); if(!chip)return; const kind=chip.dataset.kind; if(!kind)return; const cat=chip.dataset.cat,name=chip.dataset.name,price=+chip.dataset.price; if(kind==='fixed'){ const arr=S.bentoSel[cat]; const idx=arr.findIndex(s=>s.name===name&&s.price===price); if(idx>=0)arr.splice(idx,1);else arr.push({name,price}); if(cat==='cake')renderBentoCakeRow(); else if(cat==='bread')renderBentoSimpleRow('bento-bread-row',BENTO_BREAD_ITEMS,'bread'); else if(cat==='dessert')renderBentoSimpleRow('bento-dessert-row',BENTO_DESSERT_ITEMS,'dessert'); else if(cat==='addon')renderBentoSimpleRow('bento-addon-row',BENTO_ADDONS,'addon'); } else if(kind==='var-toggle'){ const existing=S.bentoSel.cake.find(s=>s.name===name); if(existing){S.bentoSel.cake=S.bentoSel.cake.filter(s=>s.name!==name);S.bentoVarOpenSet.delete(name);} else{if(S.bentoVarOpenSet.has(name))S.bentoVarOpenSet.delete(name);else S.bentoVarOpenSet.add(name);} renderBentoCakeRow(); } else if(kind==='var-price'){ S.bentoSel.cake=S.bentoSel.cake.filter(s=>s.name!==name); S.bentoSel.cake.push({name,price}); S.bentoVarOpenSet.delete(name); renderBentoCakeRow(); } else if(kind==='custom-remove'){ const cat2=chip.dataset.cat,id=chip.dataset.id; S.bentoCustom[cat2]=S.bentoCustom[cat2].filter(x=>String(x.id)!==id); renderBentoCustomTags(cat2); } bentoRunningTotal(); }); g('bento-qty-minus').addEventListener('click',()=>{ if(S.bentoQty>1){S.bentoQty--;g('bento-qty-count').value=S.bentoQty;} }); g('bento-qty-plus').addEventListener('click',()=>{ S.bentoQty++;g('bento-qty-count').value=S.bentoQty; }); g('bento-qty-count').addEventListener('input',e=>{ e.target.value=e.target.value.replace(/[^0-9]/g,''); }); g('bento-qty-count').addEventListener('change',e=>{ const v=Math.max(1,parseInt(e.target.value)||1); S.bentoQty=v;e.target.value=v; }); g('bento-confirm').addEventListener('click',()=>{ const note=g('bento-note-input').value.trim(); const total=bentoRunningTotal(); if(total<=0){alert('請至少選擇一項內容');return;} const partText=(catKey)=>{ const arr=[...S.bentoSel[catKey],...S.bentoCustom[catKey]]; if(!arr.length)return''; return arr.map(x=>x.name+' $'+x.price).join('、'); }; const ch=[partText('cake'),partText('bread'),partText('dessert'),partText('addon')].filter(Boolean); let spec=ch.join('|');if(note)spec+=','+note; const qty=Math.max(1,parseInt(g('bento-qty-count').value)||1); addItem('餐盒 $'+total,spec,total,qty,'盒'); closeM('bento-modal'); }); g('bento-cancel').addEventListener('click',()=>closeM('bento-modal')); function addItem(name,spec,price,qty,unit){const it={id:Date.now()+(Math.random()*100|0),name,spec,price,qty:qty||1,unit:unit||'',subOrderer:''};S.items.push(it);renderTbl();calcTotal();return it;} function openCustomItemModal(){ g('ci-item-name').value=''; g('ci-item-price').value=''; g('custom-item-modal').classList.add('open'); } g('ci-item-confirm').addEventListener('click',()=>{ const name=g('ci-item-name').value.trim(); const price=parseInt(g('ci-item-price').value); if(!name){alert('請輸入名稱');return;} if(isNaN(price)||price<0){alert('請輸入正確的價格');return;} addItem(name,'',price,undefined,'只'); closeM('custom-item-modal'); }); g('ci-item-cancel').addEventListener('click',()=>closeM('custom-item-modal')); function evenSplitParts(priorityList,origOrderList,cap){ const n=priorityList.length; const base=Math.floor(cap/n); const remainder=cap-base*n; const qtyMap={}; priorityList.forEach((f,idx)=>{ let val=base;if(idx
0)qtyMap[f]=val; }); return origOrderList.filter(f=>qtyMap[f]!=null).map(f=>`${f}×${qtyMap[f]}`); } function computeHalfSuedanCantonese(total){ const half1=Math.ceil(total/2),half2=total-half1; const parts=[ ...evenSplitParts(SUEDAN_MIX_PRIORITY,SUEDAN_FL,half1), ...evenSplitParts(CANTONESE_FL,CANTONESE_FL,half2), ]; return parts.join('、'); } function renderTbl(){ const listEl=g('order-list'); if(!S.items.length){listEl.innerHTML='
點選上方商品加入訂單
';return;} listEl.innerHTML=S.items.map((it,idx)=>`
${it.name}
*${it.qty}${it.unit||''}
▲
▼
✏️
🗑
${it.spec?`
${it.spec}
`:''}
${it.subOrderer?'👤 '+it.subOrderer:'+ 指定訂購人'}
${it.qty}
× $${it.price}
-
${it.qty}
+
$${(it.price*it.qty).toLocaleString()}
`).join(''); } function moveItem(id,dir){ const idx=S.items.findIndex(x=>x.id===id);if(idx<0)return; const newIdx=idx+dir; if(newIdx<0||newIdx>=S.items.length)return; [S.items[idx],S.items[newIdx]]=[S.items[newIdx],S.items[idx]]; renderTbl(); } g('order-list').addEventListener('click',e=>{ const qb=e.target.closest('.qbtn');if(qb){chgQ(+qb.dataset.id,+qb.dataset.d);return;} const mu=e.target.closest('[data-move-up]');if(mu){moveItem(+mu.dataset.moveUp,-1);return;} const md=e.target.closest('[data-move-down]');if(md){moveItem(+md.dataset.moveDown,1);return;} const eb=e.target.closest('[data-edit-id]');if(eb){openEditItem(+eb.dataset.editId);return;} const db=e.target.closest('[data-del-id]');if(db){rmItem(+db.dataset.delId);return;} const qe=e.target.closest('[data-qty-edit]');if(qe){openQtyEditModal(+qe.dataset.qtyEdit);return;} const ot=e.target.closest('[data-orderer-tag]');if(ot){openOrdererModal(+ot.dataset.ordererTag);return;} const pe=e.target.closest('[data-price-edit]');if(pe){openPriceEditModal(+pe.dataset.priceEdit);return;} }); function openOrdererModal(itemId){ const it=S.items.find(x=>x.id===itemId);if(!it)return; S.pendingOrdererItemId=itemId; g('orderer-input').value=it.subOrderer||''; const names=[...new Set(S.items.map(x=>x.subOrderer).filter(Boolean))]; const quickList=g('orderer-quick-list'); quickList.innerHTML=''; names.forEach(n=>{ const btn=document.createElement('button'); btn.type='button';btn.className='pb';btn.style.cssText='padding:6px 10px;font-size:12px'; btn.textContent=n; btn.addEventListener('click',()=>{g('orderer-input').value=n;}); quickList.appendChild(btn); }); g('orderer-modal').classList.add('open'); } g('orderer-confirm').addEventListener('click',()=>{ const it=S.items.find(x=>x.id===S.pendingOrdererItemId);if(!it)return; it.subOrderer=g('orderer-input').value.trim(); renderTbl(); closeM('orderer-modal'); }); g('orderer-cancel').addEventListener('click',()=>closeM('orderer-modal')); function openQtyEditModal(itemId){ const item=S.items.find(x=>x.id===itemId);if(!item)return; S.pendingQtyEditId=itemId; S.pendingQtyItem=null; g('quick-qty-title').textContent=item.name+' $'+item.price; g('quick-qty-input').value=item.qty; g('quick-qty-unit').textContent=item.unit||''; g('quick-qty-giftbox-row').style.display='none'; g('quick-qty-modal').classList.add('open'); } function chgQ(id,d){const i=S.items.find(x=>x.id===id);if(!i)return;i.qty=Math.max(1,i.qty+d);renderTbl();calcTotal();} function rmItem(id){S.items=S.items.filter(x=>x.id!==id);renderTbl();calcTotal();} function findOriginalGiftboxDef(cartItem){ const baseName=cartItem.name.replace(/(.+?)$/,'').trim(); for(const cat of CATS){ for(const grp of cat.groups){ for(const it of grp.items){ if(it.t==='gifbox'&&it.n===baseName)return it; } } } return null; } function findOriginalFlexboxDef(cartItem){ for(const cat of CATS){ for(const grp of cat.groups){ for(const it of grp.items){ if(it.t!=='flexbox')continue; if(it.n===cartItem.name)return it; if(it.packagingOptions&&it.packagingOptions.includes(cartItem.name))return it; } } } return null; } function openGifboxForEdit(def,cartItem){ openGifbox(def); S.pendingGifEditId=cartItem.id; g('gifbox-qty-input').value=cartItem.qty; const breakdown=parseSpecBreakdown(cartItem.spec); if(breakdown){ document.querySelectorAll('.gf-qty').forEach(inp=>{ const match=breakdown.find(b=>b.name===inp.dataset.f); if(match)inp.value=match.qty; }); refreshGifStatus(); const specialToggle=document.getElementById('gifbox-special-toggle'); const specialBody=document.getElementById('gifbox-special-body'); if(specialToggle&&specialBody){ const hasSpecialQty=[...specialBody.querySelectorAll('.gf-qty')].some(inp=>+inp.value>0); if(hasSpecialQty){ specialBody.style.display='block'; specialToggle.textContent='▴'+specialToggle.textContent.slice(1); } } } S.pendingGifIsAutoMix=/(綜)$/.test(cartItem.name); } function openFlexboxForEdit(def,cartItem){ openFlexbox(def); S.pendingFlexboxEditId=cartItem.id; g('flexbox-qty-input').value=cartItem.qty; const breakdown=parseSpecBreakdown(cartItem.spec); if(breakdown){ document.querySelectorAll('.fx-qty').forEach(inp=>{ const match=breakdown.find(b=>b.name===inp.dataset.name); if(match)inp.value=match.qty; }); flexboxRunningTotal(); } if(def.packagingOptions){ document.querySelectorAll('input[name="flexbox-packaging"]').forEach(radio=>{ radio.checked=(radio.value===cartItem.name); }); } } function openEditItem(id){ const it=S.items.find(x=>x.id===id);if(!it)return; const giftboxDef=findOriginalGiftboxDef(it); if(giftboxDef&&it.spec){openGifboxForEdit(giftboxDef,it);return;} const flexboxDef=findOriginalFlexboxDef(it); if(flexboxDef){openFlexboxForEdit(flexboxDef,it);return;} S.editItemId=id;g('ei-name').value=it.name;g('ei-spec').value=it.spec||'';g('ei-price').value=it.price;g('ei-qty').value=it.qty;g('edit-item-modal').classList.add('open'); } g('ei-confirm').addEventListener('click',()=>{const it=S.items.find(x=>x.id===S.editItemId);if(!it)return;const newName=g('ei-name').value.trim();if(newName)it.name=newName;it.spec=g('ei-spec').value.trim();it.price=parseInt(g('ei-price').value)||it.price;it.qty=Math.max(1,parseInt(g('ei-qty').value)||1);closeM('edit-item-modal');renderTbl();calcTotal();}); g('ei-cancel').addEventListener('click',()=>closeM('edit-item-modal')); function currentShipAmount(){ if(S.freeShip)return 0; const sub=S.items.reduce((s,i)=>s+i.price*i.qty,0); const cod=g('ship-cod-check').checked?computeCodTier(sub):0; return (S.shipBaseAmount||0)+cod; } function calcTotal(){ const sub=S.items.reduce((s,i)=>s+i.price*i.qty,0);const ship=currentShipAmount(); let disc=0;const dt=g('disc-type').value,dv=parseFloat(g('disc-val').value)||0; if(dt==='pct'&&dv>0)disc=Math.round(sub*dv/100);if(dt==='amt'&&dv>0)disc=Math.round(dv); const total=Math.max(0,sub+ship-disc); g('sum-sub').textContent='$'+sub.toLocaleString();g('sum-ship').textContent='$'+ship.toLocaleString();g('sum-total').textContent='$'+total.toLocaleString(); const dr=g('sum-disc-row');if(disc>0){dr.style.display='flex';g('sum-disc-val').textContent='-$'+disc.toLocaleString();const r=g('disc-reason').value;g('sum-disc-label').textContent='折扣'+(r?' ('+r+')':'');}else dr.style.display='none'; } g('disc-type').addEventListener('change',calcTotal);g('disc-val').addEventListener('input',calcTotal);g('disc-reason').addEventListener('input',calcTotal); function computeCodTier(amount){ if(amount<2000)return 30; if(amount<5000)return 50; if(amount<10000)return 90; if(amount<20000)return 120; return 150; } function updateShipValue(){ const sub=S.items.reduce((s,i)=>s+i.price*i.qty,0); const codCheck=g('ship-cod-check'); if(S.freeShip){ codCheck.checked=false; codCheck.disabled=true; g('ship-cod-preview').textContent='免運已包含代收,不再另外收費'; g('f-ship').value='免運'; g('f-ship').dataset.freeShip='1'; }else{ codCheck.disabled=false; const codOn=codCheck.checked; const cod=codOn?computeCodTier(sub):0; g('ship-cod-preview').textContent=codOn?('本次 +$'+cod):'依金額自動計算'; g('f-ship').value=(S.shipBaseAmount||0)+cod; delete g('f-ship').dataset.freeShip; } calcTotal(); } g('f-ship').addEventListener('click',()=>{ g('ship-custom-input').value=''; updateShipValue(); g('ship-modal').classList.add('open'); }); document.querySelectorAll('.ship-opt').forEach(chip=>{ chip.addEventListener('click',()=>{ S.freeShip=false; S.shipBaseAmount=+chip.dataset.price; updateShipValue(); closeM('ship-modal'); }); }); g('ship-free-opt').addEventListener('click',()=>{ S.freeShip=true; S.shipBaseAmount=0; updateShipValue(); closeM('ship-modal'); }); g('ship-custom-confirm').addEventListener('click',()=>{ const v=parseInt(g('ship-custom-input').value); if(isNaN(v)||v<0){alert('請輸入正確的運費金額');return;} S.freeShip=false; S.shipBaseAmount=v; updateShipValue(); closeM('ship-modal'); }); g('ship-cod-check').addEventListener('change',updateShipValue); g('ship-cancel').addEventListener('click',()=>closeM('ship-modal')); g('btn-paste-order').addEventListener('click',()=>{ g('paste-order-textarea').value=''; g('paste-order-modal').classList.add('open'); }); g('paste-order-cancel').addEventListener('click',()=>closeM('paste-order-modal')); function parseCustOrderText(text){ const result={}; const DATE_LABEL='(?:寄出|訂購|取貨|交貨|出貨|寄送)?日期'; let dateMatch=text.match(new RegExp(DATE_LABEL+'[::]\\s*(\\d+)\\D(\\d+)\\D(\\d+)')); if(dateMatch){ let[,y,m,d]=dateMatch; y=parseInt(y);if(y<1000)y+=1911; result.date=`${y}-${String(m).padStart(2,'0')}-${String(d).padStart(2,'0')}`; }else{ const shortMatch=text.match(new RegExp(DATE_LABEL+'[::]\\s*(\\d{1,2})\\D+(\\d{1,2})(?!\\d)')); if(shortMatch){ const[,m,d]=shortMatch; const y=new Date().getFullYear(); result.date=`${y}-${String(m).padStart(2,'0')}-${String(d).padStart(2,'0')}`; } } const nameMatch=text.match(/(?:收件人|客戶姓名|訂購人|聯絡人|姓名)[::]\s*(\S+)/); if(nameMatch)result.name=nameMatch[1]; const phoneMatch=text.match(/(?:聯絡電話|連絡電話|手機|電話|TEL|Tel)[::]\s*([\d\-#]+)/); if(phoneMatch)result.phone=phoneMatch[1].trim(); const addrMatch=text.match(/(?:收件地址|取件地址|寄送地址|宅配地址|地址)[::]\s*(.+)/); if(addrMatch)result.addr=addrMatch[1].trim(); const contentMatch=text.match(/(?:訂購內容|訂單內容|購買內容|品項|內容)[::]\s*([\s\S]*)/); if(contentMatch)result.content=contentMatch[1].trim(); return result; } g('paste-order-confirm').addEventListener('click',()=>{ const text=g('paste-order-textarea').value.trim(); if(!text){alert('請先貼上訂單文字');return;} const r=parseCustOrderText(text); if(!r.date&&!r.name&&!r.phone&&!r.addr){alert('沒有辨識到任何資訊,請確認格式是否包含「收件人/電話/地址」等欄位');return;} if(r.date){g('f-date').value=r.date;updateDateWeekday();} if(r.name)g('f-name').value=r.name; if(r.addr)g('f-addr').value=r.addr; if(r.phone){ g('f-phone').value=r.phone; const existing=S.customers.find(c=>c.phone===r.phone); if(existing)selCust(existing.id); } closeM('paste-order-modal'); }); g('btn-paid').addEventListener('click',()=>{S.isPaid=true;g('pay-badge').className='badge badge-paid';g('pay-badge').textContent='已付';}); g('disc-toggle').addEventListener('click',()=>{ const body=g('disc-body'); const isOpen=body.style.display!=='none'; body.style.display=isOpen?'none':'block'; g('disc-toggle').textContent='折扣設定 '+(isOpen?'▾':'▴'); }); g('more-fields-toggle').addEventListener('click',()=>{ const body=g('more-fields-body'); const isOpen=body.style.display!=='none'; body.style.display=isOpen?'none':'block'; g('more-fields-toggle').textContent=(isOpen?'▾':'▴')+' 更多欄位(第二聯絡人/地址/備註)'; }); function computeHomeDeliveryFee(boxCount){ if(boxCount<=0)return null; if(boxCount===1)return 130; if(boxCount<=3)return 170; if(boxCount===4)return 210; return 250; } function setHomeDeliveryUI(on){ const btn=g('btn-home-delivery'); if(on){ btn.style.background='#d97706';btn.style.color='#fff'; btn.textContent='🚚 宅配中(點一下取消,地址才會印在單子上)'; }else{ btn.style.background='#fff7ed';btn.style.color='#92400e'; btn.textContent='🚚 宅配(時間自動設為14:00,展開地址欄位)'; } } g('btn-home-delivery').addEventListener('click',()=>{ S.isHomeDelivery=!S.isHomeDelivery; setHomeDeliveryUI(S.isHomeDelivery); if(!S.isHomeDelivery)return; g('f-time-hour').value='14'; g('f-time-hour').dispatchEvent(new Event('change',{bubbles:true})); g('more-fields-body').style.display='block'; g('more-fields-toggle').textContent='▴ 更多欄位(第二聯絡人/地址/備註)'; g('f-addr').focus(); const boxCount=S.items.filter(i=>i.unit==='盒').reduce((s,i)=>s+i.qty,0); const fee=computeHomeDeliveryFee(boxCount); if(fee!=null){ S.shipBaseAmount=fee; S.freeShip=false; updateShipValue(); } }); g('btn-save').addEventListener('click',()=>saveOrder()); function saveOrder(silent){ if(!S.items.length){if(!silent)alert('請先加入商品!');return;} const oid=g('f-ordno').value.trim();if(!oid){alert('請填寫訂單號!');return;} const dateVal=g('f-date').value;if(!dateVal){alert('請選擇交貨日!');return;} const isRenaming=S.isEditMode&&S.currentOrderId&&S.currentOrderId!==oid; if(isRenaming&&S.orders.some(o=>o.id===oid)){alert(`訂單號 ${oid} 已經有別的訂單在用,請換一個號碼`);return;} if(!S.isEditMode&&S.orders.some(o=>o.id===oid)){alert(`訂單號 ${oid} 已經有訂單在用了,請換一個號碼才能儲存`);return;} const phone=g('f-phone').value.trim(); const prevOrderId=S.currentOrderId; const wasEditingExisting=S.isEditMode&&!!prevOrderId; S.currentOrderId=oid;S.lastNo=oid; let cust=null; if(phone){ cust=S.customers.find(c=>c.phone===phone); if(!cust){cust={id:'c'+Date.now(),name:g('f-name').value.trim()||'',phone,addr:'',levelId:'',note:''};S.customers.push(cust);S.selectedCustId=cust.id;} else{const nm=g('f-name').value.trim();if(nm&&!cust.name)cust.name=nm;} }else if(S.selectedCustId){ // 電話欄位空白,但有選取「無電話」常用客戶時,訂單仍要連結到該客戶資料 const sel=S.customers.find(c=>c.id===S.selectedCustId); if(sel&&!sel.phone)cust=sel; } const sub=S.items.reduce((s,i)=>s+i.price*i.qty,0);const ship=currentShipAmount(); let disc=0;const dt=g('disc-type').value,dv=parseFloat(g('disc-val').value)||0; if(dt==='pct')disc=Math.round(sub*dv/100);if(dt==='amt')disc=Math.round(dv); const total=Math.max(0,sub+ship-disc); const typedName=g('f-name').value.trim(); const contact2Name=g('f-contact2-name').value.trim(); const contact2Phone=g('f-contact2-phone').value.trim(); const addr=g('f-addr').value.trim();const ord={id:oid,date:g('f-date').value,time:g('f-time').value||'',workday:g('f-workday').value||'',addr,custId:cust?cust.id:null,custName:typedName||(cust&&cust.name)||phone||'—',phone,contact2Name,contact2Phone, items:S.items.map(i=>({name:i.name,spec:i.spec||'',price:i.price,qty:i.qty,unit:i.unit||'',subOrderer:i.subOrderer||'',exportFlavor:i.exportFlavor||'',_id:i.id})), sub,ship,freeShip:!!S.freeShip,isHomeDelivery:!!S.isHomeDelivery,disc,discReason:g('disc-reason').value,total,paid:S.isPaid,note:g('f-note').value}; if(wasEditingExisting)ord.editedAt=new Date().toISOString(); if(isRenaming)S.orders=S.orders.filter(o=>o.id!==prevOrderId); const ex=S.orders.findIndex(o=>o.id===oid);if(ex>=0)S.orders[ex]=ord;else S.orders.unshift(ord); persistState(); S.isEditMode=false;g('edit-mode-bar').style.display='none'; g('f-ordno').removeAttribute('readonly');g('f-ordno').style.cssText='font-weight:600'; const next=nextNo(oid); renderReceipt(ord); // 儲存完成後清空表單,準備輸入下一筆訂單 S.items=[];S.selectedCustId=null;S.isPaid=false;S.currentOrderId=null; g('f-ordno').value=next;S.lastNo=next; g('f-phone').value='';g('f-name').value='';g('f-addr').value='';g('cust-inline').style.display='none';g('cust-disc-hint').style.display='none'; g('f-contact2-name').value='';g('f-contact2-phone').value=''; g('more-fields-body').style.display='none';g('more-fields-toggle').textContent='▾ 更多欄位(第二聯絡人/地址/備註)'; g('cust-panel').style.display='none';g('cust-empty-panel').style.display='block'; g('disc-type').value='none';g('disc-val').value='0';g('disc-reason').value='';g('disc-body').style.display='none';g('disc-toggle').textContent='折扣設定 ▾';g('f-ship').value='0';S.shipBaseAmount=0;S.freeShip=false;g('ship-cod-check').checked=false;g('f-note').value='';g('f-date').value='';g('f-workday').value='';setFTime('');updateDateWeekday(); S.isHomeDelivery=false;setHomeDeliveryUI(false); g('pay-badge').className='badge badge-unpaid';g('pay-badge').textContent='未付'; renderTbl();calcTotal(); checkOrdnoDuplicate(); } function buildA4Content(o){ const cust=S.customers.find(c=>c.id===o.custId); const itemRow=(it,num)=>`
${num}
${it.name} *${it.qty}${it.unit||''}
${it.spec||''}
$${it.price}×${it.qty}
$${(it.price*it.qty).toLocaleString()}
`; const hasSubOrderers=o.items.some(it=>it.subOrderer); let itemsHtml; if(!hasSubOrderers){ itemsHtml=o.items.map((it,i)=>itemRow(it,i+1)).join(''); }else{ const groups=[];const groupMap={}; o.items.forEach(it=>{ const key=it.subOrderer||'__default__'; if(!groupMap[key]){groupMap[key]={name:it.subOrderer||'未指定訂購人',items:[],subtotal:0};groups.push(groupMap[key]);} groupMap[key].items.push(it); groupMap[key].subtotal+=it.price*it.qty; }); let counter=0; itemsHtml=groups.map(grp=>{ const rows=grp.items.map(it=>{counter++;return itemRow(it,counter);}).join(''); return `
👤 ${grp.name}
${rows}
${grp.name} 小計
$${grp.subtotal.toLocaleString()}
`; }).join(''); } return `
品誠素餅店
訂 購 單
#${o.id}
${o.date?fmtDateWeekday(o.date):''}${o.time?' '+o.time:''}
${o.custName!==o.phone?o.custName:'—'}
${o.phone||''}
${(o.isHomeDelivery&&(o.addr||cust?.addr))?`
🚚 宅配地址:${o.addr||cust.addr}
`:''} ${o.note?`
備註:${o.note}
`:''}
#
品項
規格
單價×數
小計
${itemsHtml}
小計
$${o.sub.toLocaleString()}
${o.freeShip?`
運費
免運
`:(o.ship>0?`
運費
$${o.ship.toLocaleString()}
`:'')} ${o.disc>0?`
折扣${o.discReason?'('+o.discReason+')':''}
-$${o.disc.toLocaleString()}
`:''}
總計
$${o.total.toLocaleString()}
${o.paid?'已付款':'未付款'}
`; } function printOrderA4(o){ const content=buildA4Content(o); const html=`
訂單 #${o.id} 列印
${content}
${content}