전표 입력
전표 헤더
명세 0행
Tab: 다음 셀  ·  Enter/↓: 다음 행  ·  ↑: 이전 행  ·  Ctrl+D: 행 복사
빠른선택:
No 세부구분 * 제품 * 재질 규격(실) 청구규격 길이(m) 본/개 실중량 청구중량 단가 금액 매입단가 매입금액 마진 원산지 비고
현장 선택
제품 선택
🚚 송장 조회 / 수정 / 재출력
검색하거나 현재 전표의 송장을 확인하세요
`; const win=window.open('','_blank');win.document.write(html);win.document.close(); } function printMS(txRows,comp,isSale){ const dates=txRows.map(r=>(r.일자||'').substring(0,10)).filter(Boolean).sort(); const first=dates[0]||'',last=dates[dates.length-1]||''; const [yy,mm,dd]=(first||'----').split('-'); const [,mm2,dd2]=(last||first||'----').split('-'); const 날짜=first===last?`${yy}년 ${parseInt(mm)}월 ${parseInt(dd)}일`:`${yy}년 ${parseInt(mm)}월 ${parseInt(dd)}일 ~ ${parseInt(mm2)}월 ${parseInt(dd2)}일`; const 현장=txRows[0].현장명||''; const gmap=new Map(); txRows.forEach(r=>{ const d=(r.일자||'').substring(0,10); const 단가=parseFloat(r.매출단가||r.단가)||0; const key=[d,r.품목명||'',r.청구규격||r.규격||'',Math.round(단가)].join('|'); if(!gmap.has(key))gmap.set(key,{일자:d,품목명:r.품목명||'',규격:r.청구규격||r.규격||'',길이:parseFloat(r.길이_m)||0,본수:0,중량:0,청구중량:0,금액:0,단가,비고:r.비고||''}); const g=gmap.get(key); g.본수+=parseInt(r.본수)||0;g.중량+=parseFloat(r.중량_kg)||0; g.청구중량+=parseFloat(r.청구중량_kg)||parseFloat(r.중량_kg)||0; g.금액+=parseFloat(r.매출금액||r.금액)||0; if(r.비고&&!g.비고)g.비고=r.비고; }); const grouped=Array.from(gmap.values()).sort((a,b)=>a.일자.localeCompare(b.일자)||a.품목명.localeCompare(b.품목명,'ko')); const totalAmt=grouped.reduce((s,g)=>s+g.금액,0); const totalWt=grouped.reduce((s,g)=>s+g.중량,0); const totalBWt=grouped.reduce((s,g)=>s+g.청구중량,0); const vat=Math.round(totalAmt*0.1),grand=totalAmt+vat; let rowsHtml=''; grouped.forEach(g=>{ const dt=g.일자.substring(5).replace('-','/'); if(isSale)rowsHtml+=`${dt}${g.품목명}${g.규격}${g.길이?g.길이.toFixed(1)+'M':''}${g.본수||''}${g.중량?(g.중량/1000).toFixed(3):''}${g.단가?Math.round(g.단가).toLocaleString():''}${g.금액?Math.round(g.금액).toLocaleString():''}${g.비고}`; else rowsHtml+=`${dt}${g.품목명}${g.규격}${g.길이?g.길이.toFixed(0)+' m':''}${g.본수||''}${g.중량?(g.중량/1000).toFixed(3):''}${g.청구중량?(g.청구중량/1000).toFixed(3):''}${g.단가?Math.round(g.단가).toLocaleString():''}${g.금액?Math.round(g.금액).toLocaleString():''}${g.비고}`; }); for(let i=grouped.length;i<15;i++) rowsHtml+=isSale?``:``; const lbS='background:#f9f9f9;padding:2px 5px;width:42px;min-width:42px;font-weight:500;border-right:1px solid #ddd;text-align:center;display:flex;align-items:center;justify-content:center;font-size:7pt;'; const vlS='padding:2px 6px;flex:1;display:flex;align-items:center;'; const rS='display:flex;border-bottom:1px solid #ddd;font-size:7.5pt;'; const lbS2='background:#f0f0f0;padding:3px 5px;width:62px;min-width:62px;font-weight:600;border-right:1px solid #ccc;text-align:center;display:flex;align-items:center;justify-content:center;font-size:7pt;'; const vS2='padding:3px 7px;flex:1;'; const rS2='display:flex;border-bottom:1px solid #ddd;font-size:8pt;'; const supRows=[['등록번호',HAESOL.사업자번호],['상   호',HAESOL.상호],['성   명',HAESOL.성명],['사업장',HAESOL.사업장],['업   태',HAESOL.업태],['종   목',HAESOL.종목],['전   화',HAESOL.전화+' FAX '+HAESOL.fax]].map(([l,v])=>`
${l}
${v}
`).join(''); const buyRows=`${comp.사업자번호?`
등 록 번 호
${comp.사업자번호}
`:''}
상    호
${comp.거래처명||''}
대 표 자
${comp.대표자||''}
사업장주소
${comp.주소||''}
`; const thead=isSale?`날짜품명규격길이본수중량(ton)단가공급가액비고`:`날짜품명규격길이본수실중량(ton)청구중량(ton)단가공급가액비고`; const tfoot=isSale?`합 계${(totalWt/1000).toFixed(3)}${Math.round(totalAmt).toLocaleString()}`:`합 계${(totalWt/1000).toFixed(3)}${(totalBWt/1000).toFixed(3)}${Math.round(totalAmt).toLocaleString()}`; const html=`
거 래 명 세 서 ( ${isSale?'판 매':'임 대'} )
일 자 : ${날짜}
공사명 : ${현장}
계산서 이메일 : ${comp.이메일||''}
계산서 이메일 : ${comp.계산서이메일||''}
공급자
${supRows}
공급받는자
${buyRows}
합     계${grand.toLocaleString()}원
${thead}${rowsHtml}${tfoot}
합   계${Math.round(totalAmt).toLocaleString()}결제조건 : ${comp.결제조건||'현금 결제'}
공 급 가 액${Math.round(totalAmt).toLocaleString()}
부 가 세${vat.toLocaleString()}
합   계${grand.toLocaleString()}
${HAESOL.계좌}
setTimeout(()=>window.print(),600); // 현재 페이지 nav-item 자동 active (function(){ var cur = location.pathname.split('/').pop() || 'index.html'; document.querySelectorAll('.nav-item').forEach(function(a){ var href = a.getAttribute('href'); if(href === cur) a.classList.add('active'); }); })(); `; const win=window.open('','_blank');win.document.write(html);win.document.close(); } // ── 송장 DB 저장/조회 ── async function saveWaybillToDB(d, 송장번호){ try{ const body={ headerno:d.hno, 송장번호, 운송사:d.carrier, 차량번호:d.carnum, 기사명:d.driver, 기사연락처:d.dtel, 도착일자:d.arrive, 배차구분:d.dispatch, 현장명:d.site, 거래처명:d.compName }; const r=await fetch(`${API}/waybill`,{method:'POST',body:JSON.stringify(body)}); if(r.ok){ const saved=await r.json(); return saved.송장id; } }catch(e){console.warn('송장 저장 실패',e);} return null; } function openWaybillPanel(){ document.getElementById('wb-panel').classList.add('open'); document.getElementById('wb-overlay').classList.add('open'); // 현재 전표 있으면 자동 조회 const hno=document.getElementById('h-no-display').textContent||_lastSaved.hno; if(hno){ document.getElementById('wb-search-hno').value=hno; searchWaybills(); } } function closeWaybillPanel(){ document.getElementById('wb-panel').classList.remove('open'); document.getElementById('wb-overlay').classList.remove('open'); } async function searchWaybills(){ const q=document.getElementById('wb-search-hno').value.trim(); const dt=document.getElementById('wb-search-date').value; const params=new URLSearchParams({limit:30}); if(q) params.append('headerno',q); if(dt){params.append('datefrom',dt);params.append('dateto',dt);} const el=document.getElementById('wb-list'); el.innerHTML='
조회중...
'; // q가 있으면 headerno, 거래처 둘 다 검색 if(q){ // headerno 형식이면 headerno로, 아니면 거래처명으로 if(q.match(/^[SP]O\d/i)) params.set('headerno',q); else { params.delete('headerno'); params.append('거래처',q); } } try{ const r=await fetch(`${API}/waybill?${params}`); const data=await r.json(); const list=data.data||[]; if(!list.length){el.innerHTML='
없음
';return;} el.innerHTML=list.map(w=>`
${w.headerno} ${w.발행일자?.substring(0,10)||''} ${w.송장번호?w.송장번호+'번':''}
${w.거래처명||''} · ${w.현장명||''}
${w.운송사||''} ${w.차량번호||''} ${w.기사명||''} ${w.기사연락처||''}
${w.도착일자||''}
`).join(''); }catch(e){el.innerHTML=`
오류: ${e.message}
`;} } let _wbEditData={}; async function openWaybillEdit(id){ const r=await fetch(`${API}/waybill/${id}`); if(!r.ok)return; const w=await r.json(); _wbEditData=w; document.getElementById('we-id').value=w.송장id; document.getElementById('we-headerno').value=w.headerno; document.getElementById('we-no').value=w.송장번호||''; document.getElementById('we-carrier').value=w.운송사||''; document.getElementById('we-carnum').value=w.차량번호||''; document.getElementById('we-driver').value=w.기사명||''; document.getElementById('we-dtel').value=w.기사연락처||''; document.getElementById('we-arrive').value=w.도착일자||''; document.getElementById('we-dispatch').value=w.배차구분||''; document.getElementById('we-note').value=w.비고||''; // 해당 전표 rows 로드 const tr=await fetch(`${API}/tx?headerno=${w.headerno}&limit=500`); const txRows=await tr.json(); document.getElementById('we-rows-json').value=JSON.stringify(txRows); document.getElementById('wb-edit-modal').classList.add('show'); } async function saveWaybillEdit(andPrint=false){ const id=document.getElementById('we-id').value; const body={ 송장번호:parseInt(document.getElementById('we-no').value)||null, 운송사:document.getElementById('we-carrier').value, 차량번호:document.getElementById('we-carnum').value, 기사명:document.getElementById('we-driver').value, 기사연락처:document.getElementById('we-dtel').value, 도착일자:document.getElementById('we-arrive').value, 배차구분:document.getElementById('we-dispatch').value, 비고:document.getElementById('we-note').value, }; await fetch(`${API}/waybill/${id}`,{method:'PUT',body:JSON.stringify(body)}); document.getElementById('wb-edit-modal').classList.remove('show'); showToast('송장 수정 완료','success'); searchWaybills(); if(andPrint){ const txRows=JSON.parse(document.getElementById('we-rows-json').value||'[]'); const d={ hno:document.getElementById('we-headerno').value, carrier:body.운송사, carnum:body.차량번호, driver:body.기사명, dtel:body.기사연락처, arrive:body.도착일자, dispatch:body.배차구분, site:_wbEditData.현장명, compName:_wbEditData.거래처명 }; let comp={}; if(_wbEditData.거래처명){ // 거래처 정보 로드 try{const cr=await fetch(`${API}/tx?headerno=${d.hno}&limit=1`);const tx=await cr.json();if(tx[0]?.거래처코드){const cr2=await fetch(`${API}/company/${tx[0].거래처코드}`);if(cr2.ok)comp=await cr2.json();}}catch(e){} } printWaybill(txRows,comp,d); } } async function reprints(id){ const r=await fetch(`${API}/waybill/${id}`); if(!r.ok)return; const w=await r.json(); const tr=await fetch(`${API}/tx?headerno=${w.headerno}&limit=500`); const txRows=await tr.json(); let comp={}; if(txRows[0]?.거래처코드){try{const cr=await fetch(`${API}/company/${txRows[0].거래처코드}`);if(cr.ok)comp=await cr.json();}catch(e){}} const d={hno:w.headerno,carrier:w.운송사,carnum:w.차량번호,driver:w.기사명, dtel:w.기사연락처,arrive:w.도착일자,dispatch:w.배차구분,site:w.현장명,compName:w.거래처명}; printWaybill(txRows,comp,d); } async function deleteWaybill(id){ if(!confirm('이 송장을 삭제하시겠습니까?'))return; await fetch(`${API}/waybill/${id}`,{method:'DELETE'}); showToast('삭제됨','success'); searchWaybills(); } function clearAll(){ clearRows(); ['h-company','h-site','h-invoice','h-no-display','h-carrier','h-carnum','h-driver','h-dtel','h-arrive','h-inspector'].forEach(id=>{ const el=document.getElementById(id);if(el){el.value='';el.classList.remove('filled');} }); ['h-company-code','h-site-code'].forEach(id=>{const el=document.getElementById(id);if(el)el.value='';}); document.getElementById('h-dispatch').selectedIndex=0; document.getElementById('h-no-display').textContent=''; } function showToast(msg,type='success'){ const t=document.getElementById('toast'); t.textContent=msg;t.className=`toast toast-${type} show`; clearTimeout(t._t);t._t=setTimeout(()=>t.classList.remove('show'),3000); } // 클릭하면 행 추가 document.addEventListener('click',e=>{ if(e.target.closest('#rows-body')||e.target.closest('.quick-bar'))return; });