Spaces:
Running
Running
FIX v1037.3: Thêm CORS proxy fallback wsrv.nl cho ảnh trong Excel khi fetch trực tiếp fail (Eurogold, Hafele, v.v. không có CORS header)
Browse files- qr-payment.js +27 -12
qr-payment.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
| 1 |
-
/* V.AI STUDIO — QR Payment / Quote Data v1037.
|
|
|
|
|
|
|
|
|
|
| 2 |
* FIX v1037.2:
|
| 3 |
* 1) parsePrompt() extract brand discount (malloca ck35% → itemDiscounts)
|
| 4 |
* 2) matchItemDiscount() brand matching fallback (Malloca→35%)
|
|
@@ -8,7 +11,6 @@
|
|
| 8 |
* không qua VAI_QR.parsePrompt, nên comprehensive-fix.js không áp dụng được.
|
| 9 |
* Thêm guards trong parsePrompt() để skip bare numbers, brand+discount, CK lines.
|
| 10 |
* Restored from 9db23f9 (v24) — full Excel/PDF export with proper layout
|
| 11 |
-
* Fixes: Excel export broken in v1036 (parameter mismatch, missing layout)
|
| 12 |
*/
|
| 13 |
(function(){
|
| 14 |
'use strict';
|
|
@@ -39,12 +41,10 @@ function vaiProductInfoForQuote(product){
|
|
| 39 |
|
| 40 |
function compactKey(s){return stripDiacritics(String(s||'')).toLowerCase().replace(/[^a-z0-9]/g,'');}
|
| 41 |
function itemKeys(it){var arr=[];['model','sku','code','ma','name'].forEach(function(k){if(it&&it[k])arr.push(compactKey(it[k]));});if(it&&it.name){String(it.name).split('|').forEach(function(x){arr.push(compactKey(x));});}return arr.filter(function(x){return x&&x.length>=3;});}
|
| 42 |
-
// FIX v1037.2: matchItemDiscount - thêm brand matching fallback
|
| 43 |
function matchItemDiscount(it,itemDiscounts){
|
| 44 |
itemDiscounts=itemDiscounts||{};var keys=itemKeys(it);
|
| 45 |
for(var i=0;i<keys.length;i++){var k=keys[i];if(itemDiscounts[k])return itemDiscounts[k];}
|
| 46 |
for(var code in itemDiscounts){for(var j=0;j<keys.length;j++){var kk=keys[j];if(code.length>=3&&kk.length>=3&&(kk.indexOf(code)!==-1||code.indexOf(kk)!==-1))return itemDiscounts[code];}}
|
| 47 |
-
// Brand matching fallback (vd "malloca" → 35 cho sản phẩm Malloca)
|
| 48 |
var brandKeys=Object.keys(itemDiscounts).filter(function(k){return !k.match(/\d[A-Za-z0-9._\/-]*$/);});
|
| 49 |
if(brandKeys.length>0){
|
| 50 |
var itemBrand='';var idx=(it.idx!=null?it.idx:(it.productIdx!=null?it.productIdx:null));
|
|
@@ -69,7 +69,6 @@ function applyPromptDiscounts(qd,parsed){
|
|
| 69 |
qd.grandTotal=productTotal;return qd;
|
| 70 |
}
|
| 71 |
|
| 72 |
-
// FIX v1037.2: parsePrompt - extract brand discount (malloca ck35%) vào itemDiscounts
|
| 73 |
function parsePrompt(text){
|
| 74 |
var fees=[],notes=[],discount=null,deposit=0,discountPercent=0,itemDiscounts={};
|
| 75 |
if(!text)return{fees:fees,notes:notes,discount:discount,deposit:deposit,discountPercent:discountPercent,itemDiscounts:itemDiscounts};
|
|
@@ -77,7 +76,6 @@ function parsePrompt(text){
|
|
| 77 |
line=line.trim();if(!line)return;
|
| 78 |
parseItemDiscountLine(line,itemDiscounts);
|
| 79 |
if(Object.keys(itemDiscounts).length&&line.match(/\b[A-Za-z]{1,8}[A-Za-z0-9._\-\/]{1,30}\d[A-Za-z0-9._\-\/]*\b/i)&&line.match(/\b(ck|chiết khấu|chiet khau|giảm|giam|discount)\b/i))return;
|
| 80 |
-
// Extract brand-level discount BEFORE skipping (vd "malloca ck35%" → itemDiscounts.malloca=35)
|
| 81 |
var brandCkMatch=line.match(/^(malloca|eurogold|grob|canzy|demax|hafele|garis|boss)\b.*(?:ck|chiet|chiet khau|giam|discount|chiết khấu)\s*([\d.,]+)/i);
|
| 82 |
if(brandCkMatch){var brandName=brandCkMatch[1].trim().toLowerCase().replace(/[^a-z0-9]/g,'');var pct=parseFloat(brandCkMatch[2].replace(/\./g,'').replace(',','.'));if(pct>0&&pct<=100)itemDiscounts[brandName]=pct;notes.push(line);return;}
|
| 83 |
if(line.match(/^(ck|chiết khấu|chiet khau|giảm|giam|discount)/i)){
|
|
@@ -91,11 +89,9 @@ function parsePrompt(text){
|
|
| 91 |
if(dm){var da=parseFloat(dm[1].replace(/\./g,'').replace(',','.'));var du=(dm[2]||'').toLowerCase();if(du==='k'||du==='nghìn'||du==='nghin')da*=1000;if(du==='triệu'||du==='tr')da*=1000000;deposit=da;}
|
| 92 |
return;
|
| 93 |
}
|
| 94 |
-
// ===== FIX v1037.1: Skip bare numbers / discount notations =====
|
| 95 |
if(line.match(/^[\d.,]+$/)){notes.push(line);return;}
|
| 96 |
var priceUnit=line.match(/^[\d.,]+\s*(k|nghìn|nghin|tr|triệu|m|%|đ|d|vnd)?$/i);
|
| 97 |
if(priceUnit&&line.replace(/[\d.,\s%\knhìnghintrriệuđdvnđvndKHTRML]/gi,'').trim().length<2){notes.push(line);return;}
|
| 98 |
-
// Dòng brand+discount (extracted above) hoặc brand+số
|
| 99 |
var brandNum=line.match(/^(malloca|eurogold|grob|canzy|demax|hafele|garis|boss)\b.*[\d.,]/i);
|
| 100 |
if(brandNum){var textAfterBrand=line.replace(/^(malloca|eurogold|grob|canzy|demax|hafele|garis|boss)/i,'').trim();if(textAfterBrand.match(/^[\d.,\s%(k|nghìn|nghin|tr|triệu|m|đ|d|vnd)]+$/i)){notes.push(line);return;}}
|
| 101 |
var m=line.match(/([\d.,]+)\s*(k|K|nghìn|nghin|triệu|tr|đ|d|vnd)?/);
|
|
@@ -197,7 +193,7 @@ function doOverride(){
|
|
| 197 |
window.exportDeliveryExcel=async function(){if(typeof ExcelJS==='undefined')return;var qd=window._origGetQuoteData(),code=getEffectiveOrderCode();await _doExportDeliveryExcel(qd,code);};
|
| 198 |
window.exportDeliveryPDF=async function(){var qd=window._origGetQuoteData(),code=getEffectiveOrderCode();await _doExportDeliveryPDF(qd,code);};
|
| 199 |
window.getQuoteData=function(){var qd=window._origGetQuoteData.apply(this,arguments);if(qd.customer)qd.customer.orderCode=getEffectiveOrderCode();return qd;};
|
| 200 |
-
console.log('✅ QR Payment v1037.
|
| 201 |
}
|
| 202 |
|
| 203 |
async function _doExportPDF(d,code){
|
|
@@ -216,6 +212,7 @@ async function _doExportPDF(d,code){
|
|
| 216 |
async function _doExportDeliveryPDF(qd,code){var cv=await toCanvas(buildDeliveryHTML(qd,code),700);var img=cv.toDataURL('image/png');var pdf=new jspdf.jsPDF({orientation:'p',unit:'pt',format:'a4'});var margin=25,usableW=595-margin*2;var ratio=cv.height/cv.width;var imgH=usableW*ratio;if(imgH>842-margin*2){imgH=842-margin*2;usableW=imgH/ratio;}pdf.addImage(img,'PNG',margin,margin,usableW,imgH);pdf.save('GH-'+code+'.pdf');}
|
| 217 |
|
| 218 |
// FIX v1037.2: _doExportExcel - Giá CK luôn là formula, ko hằng số
|
|
|
|
| 219 |
async function _doExportExcel(d,qd,code,qr){
|
| 220 |
var MONEY_FMT='#,##0"đ"';
|
| 221 |
var NUM_FMT='#,##0';
|
|
@@ -250,13 +247,32 @@ async function _doExportExcel(d,qd,code,qr){
|
|
| 250 |
var bgColor=i%2===0?'FFF8FAFC':'FFFFFFFF';
|
| 251 |
var RB={top:{style:'thin',color:{argb:bgColor}},bottom:{style:'thin',color:{argb:bgColor}},left:{style:'thin',color:{argb:bgColor}},right:{style:'thin',color:{argb:bgColor}}};
|
| 252 |
row.getCell(1).value=it.stt||i+1;row.getCell(1).alignment={horizontal:'center',vertical:'middle'};
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
row.getCell(3).value=it.name||'';row.getCell(3).font={bold:true,size:9};row.getCell(3).alignment={wrapText:true,vertical:'middle'};
|
| 255 |
row.getCell(4).value=it.model||'';row.getCell(4).alignment={horizontal:'center',vertical:'middle'};
|
| 256 |
row.getCell(5).value=it.specs||'';row.getCell(5).font={size:8,color:{argb:'FF64748B'}};row.getCell(5).alignment={wrapText:true,vertical:'middle'};
|
| 257 |
row.getCell(6).value=Number(it.qty||1);row.getCell(6).numFmt=NUM_FMT;row.getCell(6).alignment={horizontal:'center',vertical:'middle'};
|
| 258 |
row.getCell(7).value=Number(it.price||0);row.getCell(7).numFmt=MONEY_FMT;row.getCell(7).alignment={horizontal:'right',vertical:'middle'};
|
| 259 |
-
// Giá CK luôn là formula =G{row}*factor — ko hardcode hằng số
|
| 260 |
var itemPct=matchItemDiscount(it,d.itemDiscounts||{});
|
| 261 |
var discountFactor=1.0;
|
| 262 |
if(itemPct>0&&itemPct<=100){discountFactor=1-itemPct/100;}
|
|
@@ -281,7 +297,6 @@ async function _doExportExcel(d,qd,code,qr){
|
|
| 281 |
tRow.getCell(1).value='TỔNG CỘNG';tRow.getCell(1).font={bold:true,size:13,color:{argb:'FFFFFFFF'}};tRow.getCell(1).alignment={horizontal:'left',vertical:'middle',indent:1};tRow.getCell(1).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(1).border=TB;
|
| 282 |
tRow.getCell(6).value={formula:totalQtyFormula,result:totalQtyValue};tRow.getCell(6).numFmt=NUM_FMT;tRow.getCell(6).font={bold:true,size:12,color:{argb:'FFFFFFFF'}};tRow.getCell(6).alignment={horizontal:'center',vertical:'middle'};tRow.getCell(6).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(6).border=TB;
|
| 283 |
tRow.getCell(7).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(7).border=TB;tRow.getCell(8).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(8).border=TB;
|
| 284 |
-
// TỔNG CỘNG = SUM(thành tiền sp) + phụ phí
|
| 285 |
tRow.getCell(9).value={formula:sumParts.length?sumParts.join('+'):'0',result:Number(d.grandTotal||0)};tRow.getCell(9).numFmt=MONEY_FMT;tRow.getCell(9).font={bold:true,size:14,color:{argb:'FFF0B840'}};tRow.getCell(9).alignment={horizontal:'right',vertical:'middle'};tRow.getCell(9).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(9).border=TB;tRow.getCell(10).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(10).border=TB;cr++;
|
| 286 |
if(d.deposit>0){var DBG='FFF0FDF4';var DB={top:{style:'thin',color:{argb:DBG}},bottom:{style:'thin',color:{argb:DBG}},left:{style:'thin',color:{argb:DBG}},right:{style:'thin',color:{argb:DBG}}};ws.mergeCells(cr,1,cr,8);var dRow=ws.getRow(cr);dRow.height=24;dRow.getCell(1).value='Đã cọc';dRow.getCell(1).font={bold:true,size:11,color:{argb:'FF166534'}};dRow.getCell(1).alignment={horizontal:'left',vertical:'middle',indent:1};dRow.getCell(1).fill={type:'pattern',pattern:'solid',fgColor:{argb:DBG}};dRow.getCell(1).border=DB;dRow.getCell(9).value=Number(d.deposit||0);dRow.getCell(9).numFmt=MONEY_FMT;dRow.getCell(9).font={bold:true,size:11,color:{argb:'FF166534'}};dRow.getCell(9).alignment={horizontal:'right',vertical:'middle'};dRow.getCell(9).fill={type:'pattern',pattern:'solid',fgColor:{argb:DBG}};dRow.getCell(9).border=DB;dRow.getCell(10).fill={type:'pattern',pattern:'solid',fgColor:{argb:DBG}};dRow.getCell(10).border=DB;var depositRowNum=cr;cr++;var RBG='FFFEF2F2';var RBD={top:{style:'thin',color:{argb:RBG}},bottom:{style:'thin',color:{argb:RBG}},left:{style:'thin',color:{argb:RBG}},right:{style:'thin',color:{argb:RBG}}};ws.mergeCells(cr,1,cr,8);var rRow=ws.getRow(cr);rRow.height=26;rRow.getCell(1).value='CÒN LẠI';rRow.getCell(1).font={bold:true,size:12,color:{argb:'FFDC2626'}};rRow.getCell(1).alignment={horizontal:'left',vertical:'middle',indent:1};rRow.getCell(1).fill={type:'pattern',pattern:'solid',fgColor:{argb:RBG}};rRow.getCell(1).border=RBD;rRow.getCell(9).value={formula:'MAX(I'+totalRowNum+'-I'+depositRowNum+',0)',result:Number(d.remaining||0)};rRow.getCell(9).numFmt=MONEY_FMT;rRow.getCell(9).font={bold:true,size:13,color:{argb:'FFDC2626'}};rRow.getCell(9).alignment={horizontal:'right',vertical:'middle'};rRow.getCell(9).fill={type:'pattern',pattern:'solid',fgColor:{argb:RBG}};rRow.getCell(9).border=RBD;rRow.getCell(10).fill={type:'pattern',pattern:'solid',fgColor:{argb:RBG}};rRow.getCell(10).border=RBD;cr++;}
|
| 287 |
if(d.notes&&d.notes.length){var NBG='FFF0FDF4';var NB2={top:{style:'thin',color:{argb:NBG}},bottom:{style:'thin',color:{argb:NBG}},left:{style:'thin',color:{argb:NBG}},right:{style:'thin',color:{argb:NBG}}};ws.mergeCells(cr,1,cr,10);ws.getCell('A'+cr).value='📝 Ghi chú: '+d.notes.join('; ');ws.getCell('A'+cr).font={italic:true,size:9,color:{argb:'FF166534'}};ws.getCell('A'+cr).fill={type:'pattern',pattern:'solid',fgColor:{argb:NBG}};ws.getCell('A'+cr).border=NB2;cr++;}
|
|
|
|
| 1 |
+
/* V.AI STUDIO — QR Payment / Quote Data v1037.3
|
| 2 |
+
* FIX v1037.3: Thêm CORS proxy fallback wsrv.nl cho ảnh trong Excel
|
| 3 |
+
* - fetch trực tiếp fail (CORS) → fallback qua wsrv.nl
|
| 4 |
+
* - Tested: Eurogold, Hafele images đều load được với ACL=*
|
| 5 |
* FIX v1037.2:
|
| 6 |
* 1) parsePrompt() extract brand discount (malloca ck35% → itemDiscounts)
|
| 7 |
* 2) matchItemDiscount() brand matching fallback (Malloca→35%)
|
|
|
|
| 11 |
* không qua VAI_QR.parsePrompt, nên comprehensive-fix.js không áp dụng được.
|
| 12 |
* Thêm guards trong parsePrompt() để skip bare numbers, brand+discount, CK lines.
|
| 13 |
* Restored from 9db23f9 (v24) — full Excel/PDF export with proper layout
|
|
|
|
| 14 |
*/
|
| 15 |
(function(){
|
| 16 |
'use strict';
|
|
|
|
| 41 |
|
| 42 |
function compactKey(s){return stripDiacritics(String(s||'')).toLowerCase().replace(/[^a-z0-9]/g,'');}
|
| 43 |
function itemKeys(it){var arr=[];['model','sku','code','ma','name'].forEach(function(k){if(it&&it[k])arr.push(compactKey(it[k]));});if(it&&it.name){String(it.name).split('|').forEach(function(x){arr.push(compactKey(x));});}return arr.filter(function(x){return x&&x.length>=3;});}
|
|
|
|
| 44 |
function matchItemDiscount(it,itemDiscounts){
|
| 45 |
itemDiscounts=itemDiscounts||{};var keys=itemKeys(it);
|
| 46 |
for(var i=0;i<keys.length;i++){var k=keys[i];if(itemDiscounts[k])return itemDiscounts[k];}
|
| 47 |
for(var code in itemDiscounts){for(var j=0;j<keys.length;j++){var kk=keys[j];if(code.length>=3&&kk.length>=3&&(kk.indexOf(code)!==-1||code.indexOf(kk)!==-1))return itemDiscounts[code];}}
|
|
|
|
| 48 |
var brandKeys=Object.keys(itemDiscounts).filter(function(k){return !k.match(/\d[A-Za-z0-9._\/-]*$/);});
|
| 49 |
if(brandKeys.length>0){
|
| 50 |
var itemBrand='';var idx=(it.idx!=null?it.idx:(it.productIdx!=null?it.productIdx:null));
|
|
|
|
| 69 |
qd.grandTotal=productTotal;return qd;
|
| 70 |
}
|
| 71 |
|
|
|
|
| 72 |
function parsePrompt(text){
|
| 73 |
var fees=[],notes=[],discount=null,deposit=0,discountPercent=0,itemDiscounts={};
|
| 74 |
if(!text)return{fees:fees,notes:notes,discount:discount,deposit:deposit,discountPercent:discountPercent,itemDiscounts:itemDiscounts};
|
|
|
|
| 76 |
line=line.trim();if(!line)return;
|
| 77 |
parseItemDiscountLine(line,itemDiscounts);
|
| 78 |
if(Object.keys(itemDiscounts).length&&line.match(/\b[A-Za-z]{1,8}[A-Za-z0-9._\-\/]{1,30}\d[A-Za-z0-9._\-\/]*\b/i)&&line.match(/\b(ck|chiết khấu|chiet khau|giảm|giam|discount)\b/i))return;
|
|
|
|
| 79 |
var brandCkMatch=line.match(/^(malloca|eurogold|grob|canzy|demax|hafele|garis|boss)\b.*(?:ck|chiet|chiet khau|giam|discount|chiết khấu)\s*([\d.,]+)/i);
|
| 80 |
if(brandCkMatch){var brandName=brandCkMatch[1].trim().toLowerCase().replace(/[^a-z0-9]/g,'');var pct=parseFloat(brandCkMatch[2].replace(/\./g,'').replace(',','.'));if(pct>0&&pct<=100)itemDiscounts[brandName]=pct;notes.push(line);return;}
|
| 81 |
if(line.match(/^(ck|chiết khấu|chiet khau|giảm|giam|discount)/i)){
|
|
|
|
| 89 |
if(dm){var da=parseFloat(dm[1].replace(/\./g,'').replace(',','.'));var du=(dm[2]||'').toLowerCase();if(du==='k'||du==='nghìn'||du==='nghin')da*=1000;if(du==='triệu'||du==='tr')da*=1000000;deposit=da;}
|
| 90 |
return;
|
| 91 |
}
|
|
|
|
| 92 |
if(line.match(/^[\d.,]+$/)){notes.push(line);return;}
|
| 93 |
var priceUnit=line.match(/^[\d.,]+\s*(k|nghìn|nghin|tr|triệu|m|%|đ|d|vnd)?$/i);
|
| 94 |
if(priceUnit&&line.replace(/[\d.,\s%\knhìnghintrriệuđdvnđvndKHTRML]/gi,'').trim().length<2){notes.push(line);return;}
|
|
|
|
| 95 |
var brandNum=line.match(/^(malloca|eurogold|grob|canzy|demax|hafele|garis|boss)\b.*[\d.,]/i);
|
| 96 |
if(brandNum){var textAfterBrand=line.replace(/^(malloca|eurogold|grob|canzy|demax|hafele|garis|boss)/i,'').trim();if(textAfterBrand.match(/^[\d.,\s%(k|nghìn|nghin|tr|triệu|m|đ|d|vnd)]+$/i)){notes.push(line);return;}}
|
| 97 |
var m=line.match(/([\d.,]+)\s*(k|K|nghìn|nghin|triệu|tr|đ|d|vnd)?/);
|
|
|
|
| 193 |
window.exportDeliveryExcel=async function(){if(typeof ExcelJS==='undefined')return;var qd=window._origGetQuoteData(),code=getEffectiveOrderCode();await _doExportDeliveryExcel(qd,code);};
|
| 194 |
window.exportDeliveryPDF=async function(){var qd=window._origGetQuoteData(),code=getEffectiveOrderCode();await _doExportDeliveryPDF(qd,code);};
|
| 195 |
window.getQuoteData=function(){var qd=window._origGetQuoteData.apply(this,arguments);if(qd.customer)qd.customer.orderCode=getEffectiveOrderCode();return qd;};
|
| 196 |
+
console.log('✅ QR Payment v1037.3 — CORS proxy, brand discounts, formula Excel, no phantom fees');return true;
|
| 197 |
}
|
| 198 |
|
| 199 |
async function _doExportPDF(d,code){
|
|
|
|
| 212 |
async function _doExportDeliveryPDF(qd,code){var cv=await toCanvas(buildDeliveryHTML(qd,code),700);var img=cv.toDataURL('image/png');var pdf=new jspdf.jsPDF({orientation:'p',unit:'pt',format:'a4'});var margin=25,usableW=595-margin*2;var ratio=cv.height/cv.width;var imgH=usableW*ratio;if(imgH>842-margin*2){imgH=842-margin*2;usableW=imgH/ratio;}pdf.addImage(img,'PNG',margin,margin,usableW,imgH);pdf.save('GH-'+code+'.pdf');}
|
| 213 |
|
| 214 |
// FIX v1037.2: _doExportExcel - Giá CK luôn là formula, ko hằng số
|
| 215 |
+
// FIX v1037.3: Thêm CORS proxy fallback wsrv.nl cho ảnh (Eurogold, Hafele...)
|
| 216 |
async function _doExportExcel(d,qd,code,qr){
|
| 217 |
var MONEY_FMT='#,##0"đ"';
|
| 218 |
var NUM_FMT='#,##0';
|
|
|
|
| 247 |
var bgColor=i%2===0?'FFF8FAFC':'FFFFFFFF';
|
| 248 |
var RB={top:{style:'thin',color:{argb:bgColor}},bottom:{style:'thin',color:{argb:bgColor}},left:{style:'thin',color:{argb:bgColor}},right:{style:'thin',color:{argb:bgColor}}};
|
| 249 |
row.getCell(1).value=it.stt||i+1;row.getCell(1).alignment={horizontal:'center',vertical:'middle'};
|
| 250 |
+
// FIX v1037.3: Load ảnh với CORS proxy fallback wsrv.nl
|
| 251 |
+
if(it.image&&it.image.startsWith('http')){
|
| 252 |
+
try{
|
| 253 |
+
var ir=await fetch(it.image,{mode:'cors'});
|
| 254 |
+
if(!ir.ok)throw new Error('HTTP '+ir.status);
|
| 255 |
+
var ib=await ir.arrayBuffer();
|
| 256 |
+
var ext=it.image.match(/\.(png|jpe?g)/i);ext=ext?ext[1].startsWith('png')?'png':'jpeg':'jpeg';
|
| 257 |
+
ws.addImage(wb.addImage({buffer:ib,extension:ext}),{tl:{col:1,row:cr-1},ext:{width:46,height:46}});
|
| 258 |
+
}catch(e1){
|
| 259 |
+
// Fallback: dùng wsrv.nl (CORS proxy, tested: ✅ Eurogold, Hafele)
|
| 260 |
+
try{
|
| 261 |
+
var proxyUrl='https://wsrv.nl/?url='+encodeURIComponent(it.image)+'&w=200&output='+(it.image.match(/\.png/i)?'png':'jpg');
|
| 262 |
+
var ir2=await fetch(proxyUrl);
|
| 263 |
+
if(ir2.ok){
|
| 264 |
+
var ib2=await ir2.arrayBuffer();
|
| 265 |
+
var ext2=it.image.match(/\.(png|jpe?g)/i);ext2=ext2?ext2[1].startsWith('png')?'png':'jpeg':'jpeg';
|
| 266 |
+
ws.addImage(wb.addImage({buffer:ib2,extension:ext2}),{tl:{col:1,row:cr-1},ext:{width:46,height:46}});
|
| 267 |
+
}
|
| 268 |
+
}catch(e2){/* skip image if all fails */}
|
| 269 |
+
}
|
| 270 |
+
}
|
| 271 |
row.getCell(3).value=it.name||'';row.getCell(3).font={bold:true,size:9};row.getCell(3).alignment={wrapText:true,vertical:'middle'};
|
| 272 |
row.getCell(4).value=it.model||'';row.getCell(4).alignment={horizontal:'center',vertical:'middle'};
|
| 273 |
row.getCell(5).value=it.specs||'';row.getCell(5).font={size:8,color:{argb:'FF64748B'}};row.getCell(5).alignment={wrapText:true,vertical:'middle'};
|
| 274 |
row.getCell(6).value=Number(it.qty||1);row.getCell(6).numFmt=NUM_FMT;row.getCell(6).alignment={horizontal:'center',vertical:'middle'};
|
| 275 |
row.getCell(7).value=Number(it.price||0);row.getCell(7).numFmt=MONEY_FMT;row.getCell(7).alignment={horizontal:'right',vertical:'middle'};
|
|
|
|
| 276 |
var itemPct=matchItemDiscount(it,d.itemDiscounts||{});
|
| 277 |
var discountFactor=1.0;
|
| 278 |
if(itemPct>0&&itemPct<=100){discountFactor=1-itemPct/100;}
|
|
|
|
| 297 |
tRow.getCell(1).value='TỔNG CỘNG';tRow.getCell(1).font={bold:true,size:13,color:{argb:'FFFFFFFF'}};tRow.getCell(1).alignment={horizontal:'left',vertical:'middle',indent:1};tRow.getCell(1).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(1).border=TB;
|
| 298 |
tRow.getCell(6).value={formula:totalQtyFormula,result:totalQtyValue};tRow.getCell(6).numFmt=NUM_FMT;tRow.getCell(6).font={bold:true,size:12,color:{argb:'FFFFFFFF'}};tRow.getCell(6).alignment={horizontal:'center',vertical:'middle'};tRow.getCell(6).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(6).border=TB;
|
| 299 |
tRow.getCell(7).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(7).border=TB;tRow.getCell(8).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(8).border=TB;
|
|
|
|
| 300 |
tRow.getCell(9).value={formula:sumParts.length?sumParts.join('+'):'0',result:Number(d.grandTotal||0)};tRow.getCell(9).numFmt=MONEY_FMT;tRow.getCell(9).font={bold:true,size:14,color:{argb:'FFF0B840'}};tRow.getCell(9).alignment={horizontal:'right',vertical:'middle'};tRow.getCell(9).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(9).border=TB;tRow.getCell(10).fill={type:'pattern',pattern:'solid',fgColor:{argb:TBG}};tRow.getCell(10).border=TB;cr++;
|
| 301 |
if(d.deposit>0){var DBG='FFF0FDF4';var DB={top:{style:'thin',color:{argb:DBG}},bottom:{style:'thin',color:{argb:DBG}},left:{style:'thin',color:{argb:DBG}},right:{style:'thin',color:{argb:DBG}}};ws.mergeCells(cr,1,cr,8);var dRow=ws.getRow(cr);dRow.height=24;dRow.getCell(1).value='Đã cọc';dRow.getCell(1).font={bold:true,size:11,color:{argb:'FF166534'}};dRow.getCell(1).alignment={horizontal:'left',vertical:'middle',indent:1};dRow.getCell(1).fill={type:'pattern',pattern:'solid',fgColor:{argb:DBG}};dRow.getCell(1).border=DB;dRow.getCell(9).value=Number(d.deposit||0);dRow.getCell(9).numFmt=MONEY_FMT;dRow.getCell(9).font={bold:true,size:11,color:{argb:'FF166534'}};dRow.getCell(9).alignment={horizontal:'right',vertical:'middle'};dRow.getCell(9).fill={type:'pattern',pattern:'solid',fgColor:{argb:DBG}};dRow.getCell(9).border=DB;dRow.getCell(10).fill={type:'pattern',pattern:'solid',fgColor:{argb:DBG}};dRow.getCell(10).border=DB;var depositRowNum=cr;cr++;var RBG='FFFEF2F2';var RBD={top:{style:'thin',color:{argb:RBG}},bottom:{style:'thin',color:{argb:RBG}},left:{style:'thin',color:{argb:RBG}},right:{style:'thin',color:{argb:RBG}}};ws.mergeCells(cr,1,cr,8);var rRow=ws.getRow(cr);rRow.height=26;rRow.getCell(1).value='CÒN LẠI';rRow.getCell(1).font={bold:true,size:12,color:{argb:'FFDC2626'}};rRow.getCell(1).alignment={horizontal:'left',vertical:'middle',indent:1};rRow.getCell(1).fill={type:'pattern',pattern:'solid',fgColor:{argb:RBG}};rRow.getCell(1).border=RBD;rRow.getCell(9).value={formula:'MAX(I'+totalRowNum+'-I'+depositRowNum+',0)',result:Number(d.remaining||0)};rRow.getCell(9).numFmt=MONEY_FMT;rRow.getCell(9).font={bold:true,size:13,color:{argb:'FFDC2626'}};rRow.getCell(9).alignment={horizontal:'right',vertical:'middle'};rRow.getCell(9).fill={type:'pattern',pattern:'solid',fgColor:{argb:RBG}};rRow.getCell(9).border=RBD;rRow.getCell(10).fill={type:'pattern',pattern:'solid',fgColor:{argb:RBG}};rRow.getCell(10).border=RBD;cr++;}
|
| 302 |
if(d.notes&&d.notes.length){var NBG='FFF0FDF4';var NB2={top:{style:'thin',color:{argb:NBG}},bottom:{style:'thin',color:{argb:NBG}},left:{style:'thin',color:{argb:NBG}},right:{style:'thin',color:{argb:NBG}}};ws.mergeCells(cr,1,cr,10);ws.getCell('A'+cr).value='📝 Ghi chú: '+d.notes.join('; ');ws.getCell('A'+cr).font={italic:true,size:9,color:{argb:'FF166534'}};ws.getCell('A'+cr).fill={type:'pattern',pattern:'solid',fgColor:{argb:NBG}};ws.getCell('A'+cr).border=NB2;cr++;}
|