/* InvoiceScreen #/master/invoice/:measurementId
Rooms: chip grid (3 cols, mono-add) + list with rename/remove */
const InvoiceScreen = (function () {
'use strict';
const ROOM_GROUPS = [
['Гостиная','Спальня','Детская'],
['Кабинет','Кухня','Кухня-гостиная'],
['Ванная','Санузел','Прихожая'],
['Коридор','Кладовая','Балкон'],
['Лоджия','Столовая','Доп. помещение'],
];
const ALL_CHIPS = ROOM_GROUPS.flat();
function escHtml(s){return String(s==null?'':s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');}
function el(html){const t=document.createElement('template');t.innerHTML=html.trim();return t.content.firstChild;}
function fmtMoney(n){return Math.round(n||0).toLocaleString('ru-RU')+' ₽';}
const FEE_BASE=2500,FEE_EXTRA=1000;
function calcTotal(rooms){if(!rooms.length)return 0;return FEE_BASE+Math.max(0,rooms.length-1)*FEE_EXTRA;}
async function _api(path,body){
const ctrl=new AbortController();const t=setTimeout(()=>ctrl.abort(),30000);
try{
const res=await fetch(BACKEND_URL+'/api/'+path,{method:'POST',signal:ctrl.signal,
headers:{'Content-Type':'application/json'},
body:JSON.stringify(Object.assign({
initData:(typeof Platform!=='undefined'?Platform.initData:''),
initDataUnsafe:(typeof Platform!=='undefined'?Platform.initDataUnsafe:null),
},body))});
if(!res.ok)throw new Error('HTTP '+res.status);return await res.json();
}catch(e){if(e.name==='AbortError')throw new Error('Timeout');throw e;}
finally{clearTimeout(t);}
}
async function mount(container,measurementId){
container.innerHTML='';
document.body.classList.remove('has-bottom-nav');
const nav=document.getElementById('bottom-nav');if(nav)nav.remove();
const icons=window.ICONS||{};
const header=el('');
header.querySelector('.podbor-back').addEventListener('click',()=>{if(typeof haptic!=='undefined')haptic('impact');history.back();});
const screen=el('
');
container.appendChild(header);container.appendChild(screen);
screen.innerHTML='';
try{
const data=await _api('measurement_detail',{measurement_id:measurementId});
if(data.error)throw new Error(data.error);
_renderForm(screen,data,measurementId);
}catch(e){screen.innerHTML='Ошибка: '+escHtml(e.message)+'
';}
}
function _renderForm(screen,meas,measurementId){
screen.innerHTML='';
const existingFee=parseFloat(meas.measurement_fee)||0;
const rooms=[];let nextId=0;
// Client card
screen.appendChild(el(
''+
'
Клиент
'+
'
'+escHtml(meas.client_name||'—')+'
'+
(meas.client_phone?'
'+escHtml(meas.client_phone)+'
':'')+
(meas.address?'
📍 '+escHtml(meas.address)+'
':'')+
'
'
));
// Already invoiced warning
if(existingFee>0){
const eb=el(''+
'
⚠ Счёт уже выставлен
'+
'
'+fmtMoney(existingFee)+'
'+
'
');
eb.querySelector('#reviseBtn').addEventListener('click',()=>{eb.remove();if(typeof haptic!=='undefined')haptic('impact');});
screen.appendChild(eb);
}
// Rooms list
const listWrap=el('');
screen.appendChild(listWrap);
// Total bar
const totalWrap=el('');
screen.appendChild(totalWrap);
const totalEl=totalWrap.querySelector('#totalAmt');
// Issue button
const bw=el('');
const rb=el('');
const issueBtn=bw.querySelector('#issueBtn');
// ── CHIP GRID ──────────────────────────────────────────────────────────
const chipLabel=el('Выберите помещения
');
const chipGrid=el('');
function updateTotal(){
totalEl.textContent=rooms.length?fmtMoney(calcTotal(rooms)):'0 ₽';
issueBtn.disabled=!rooms.length;
issueBtn.style.opacity=rooms.length?'1':'0.45';
}
function removeRoom(id){
const idx=rooms.findIndex(r=>r.id===id);if(idx===-1)return;
rooms.splice(idx,1);
const card=listWrap.querySelector('[data-room-id="'+id+'"]');
if(card)card.remove();
updateTotal();
}
function addRoomCard(room){
const isBase=rooms.length===1&&rooms[0].id===room.id;
const price=isBase?FEE_BASE:FEE_EXTRA;
const card=el(
''+
''+
''+fmtMoney(price)+''+
''+
'
'
);
card.querySelector('input').addEventListener('input',e=>{room.name=e.target.value;});
card.querySelector('button').addEventListener('click',()=>{
if(typeof haptic!=='undefined')haptic('selection');
removeRoom(room.id);
// re-render first card price if needed
_refreshPriceLabels();
});
listWrap.appendChild(card);
}
function _refreshPriceLabels(){
const cards=listWrap.querySelectorAll('[data-room-id]');
cards.forEach((card,i)=>{
const span=card.querySelector('span');
if(span)span.textContent=fmtMoney(i===0?FEE_BASE:FEE_EXTRA);
});
}
// Chip click: count how many rooms have this base name, auto-number
function addFromChip(chipName){
const existing=rooms.filter(r=>r.name===chipName||r.name.startsWith(chipName+' ')).length;
let name=chipName;
if(existing===1)name=chipName+' 2';
else if(existing>1)name=chipName+' '+(existing+1);
const room={id:nextId++,name};
rooms.push(room);
addRoomCard(room);
updateTotal();
}
ALL_CHIPS.forEach(chipName=>{
const chip=el(
''
);
chip.addEventListener('click',()=>{
if(typeof haptic!=='undefined')haptic('selection');
addFromChip(chipName);
// flash chip
chip.style.background='var(--accent)';chip.style.color='#fff';chip.style.borderColor='var(--accent)';
setTimeout(()=>{chip.style.background='';chip.style.color='';chip.style.borderColor='';},180);
});
chipGrid.appendChild(chip);
});
// Pre-fill if rooms_count set
const existingCount=parseInt(meas.rooms_count)||0;
for(let i=0;i{
if(typeof haptic!=='undefined')haptic('impact');
issueBtn.disabled=true;issueBtn.textContent='Создаём счёт…';
const names=rooms.map((r,i)=>r.name||(i===0?'Основное помещение':'Помещение '+(i+1)));
_api('invoice_create',{measurement_id:measurementId,rooms_count:rooms.length,rooms_names:names})
.then(data=>{
if(data.error)throw new Error(data.error);
_renderResult(rb,data);issueBtn.style.display='none';
chipLabel.style.display='none';chipGrid.style.display='none';
})
.catch(e=>{
rb.innerHTML='Ошибка: '+escHtml(e.message)+'
';
issueBtn.disabled=false;issueBtn.textContent='Выставить счёт';
});
});
updateTotal();
}
function _renderResult(container,data){
container.innerHTML='';
const qr=data.qr_b64
?'QR для оплаты (СБП)
+')
'
:'';
container.appendChild(el(
''+
'
✅ Счёт выставлен
'+
'
'+fmtMoney(data.amount)+'
'+
'
'+
'
Получатель: '+escHtml(data.ip_name||'—')+'
'+
'
ИНН: '+escHtml(data.ip_inn||'—')+'
'+
'
Банк: '+escHtml(data.bank_name||'—')+'
'+
'
БИК: '+escHtml(data.bic||'—')+'
'+
'
Р/С: '+escHtml(data.rs||'—')+'
'+
(data.ks?'
К/С: '+escHtml(data.ks)+'
':'')+
'
Назначение: '+escHtml(data.purpose||'—')+'
'+
'
'+qr+'
'
));
}
return{mount};
})();