1'use strict'; 2(() => { 3 const tr=(a,pt,en)=>a.state.lang==='pt'?pt:en,esc=(a,v)=>a.esc(v??''),label=(a,x)=>x?.[a.state.lang==='pt'?'label_pt':'label_en']||x?.code||''; 4 const money=(a,cents)=>cents===null||cents===undefined?'â':new Intl.NumberFormat(a.state.lang==='pt'?'pt-BR':'en',{style:'currency',currency:'BRL'}).format(Number(cents)/100); 5 const date=(a,d)=>d?new Intl.DateTimeFormat(a.state.lang==='pt'?'pt-BR':'en',{dateStyle:'medium',timeZone:'UTC'}).format(new Date(d.slice(0,10)+'T12:00:00Z')):'â'; 6 const statuses={submitted:['Enviado','Submitted'],processing:['Em processo','Processing'],needs_revision:['Revisão solicitada','Revision requested'],approved:['Aprovado','Approved'],rejected:['Reprovado','Rejected']};let filter=''; 7 const status=(a,code)=>statuses[code]?.[a.state.lang==='pt'?0:1]||code; 8 const actionLabel=(a,code)=>code==='needs_revision'?tr(a,'Enviar para revisão','Request revision'):code==='submitted'?tr(a,'Reenviar para análise','Resubmit for review'):code==='approved'?tr(a,'Aprovar pedido','Approve request'):code==='rejected'?tr(a,'Reprovar pedido','Reject request'):tr(a,'Iniciar análise','Start review'); 9 const requestModule=()=>window.AltaVoltagemFinanceRequests; 10 const allowed=a=>['admin','student','mentor'].includes(a.state.actor?.role); 11 function incomingRequest(search,actor){if(!allowed({state:{actor}}))return null;const params=new URLSearchParams(search);if(params.getAll('page').length!==1||params.get('page')!=='finance'||params.getAll('request_id').length!==1)return null;const raw=params.get('request_id');return /^[1-9]\d*$/.test(raw||'')&&Number.isSafeInteger(Number(raw))?Number(raw):null;} 12 const editable=(a,options,permission)=>!a.readOnlySeason?.()&&options.permissions?.[permission]===true; 13 async function load(a){const season=a.state.seasonId,[options,requests]=await Promise.all([a.api('finance.options',{season_id:season}),a.api('finance.list',{season_id:season,...(filter?{status:filter}:{})})]);const [summary,contributions]=options.permissions.can_view_finance?await Promise.all([a.api('finance.summary',{season_id:season}),a.api('finance.contributions.list',{season_id:season})]):[null,null];return {options,requests,summary,contributions};} 14 function itemLabel(a,item,options){if(!['mdf','filament'].includes(item.material_type))return item.description||tr(a,'Item do pedido','Request item');if(item.material_type==='mdf')return tr(a,'Placa de MDF','MDF sheet');const color=item.color_code==='other'?item.color_other:label(a,(options.colors||[]).find(x=>x.code===item.color_code));return tr(a,'Rolo de filamento','Filament roll')+' · '+item.filament_type+' · '+color;} 15 function itemsTable(a,items,options){if(items.some(item=>['general','engineering'].includes(item.material_type)||Object.hasOwn(item,'quote_unit_cost_cents'))&&requestModule())return requestModule().itemsHTML(a,items);return '<div class="table-wrap"><table class="data-table finance-items-table"><thead><tr><th>'+tr(a,'Material','Material')+'</th><th>'+tr(a,'Quantidade','Quantity')+'</th><th>'+tr(a,'Custo unitário','Unit cost')+'</th><th>'+tr(a,'Total do item','Item total')+'</th></tr></thead><tbody>'+items.map(i=>'<tr><td><strong>'+esc(a,itemLabel(a,i,options))+'</strong><small>'+esc(a,i.description)+'</small></td><td>'+i.quantity+'</td><td>'+money(a,i.unit_cost_cents)+'</td><td>'+money(a,i.line_total_cents)+'</td></tr>').join('')+'</tbody></table></div>';} 16 const tabs={overview:['Visão geral','Overview'],revenues:['Entradas','Income'],contributions:['Bens recebidos','Received goods'],expenses:['Despesas','Expenses'],requests:['Pedidos','Requests']};let selectedTab='overview'; 17 const allowedTabs=data=>data.options.permissions.can_view_finance?Object.keys(tabs):['requests']; 18 const activeTab=data=>allowedTabs(data).includes(selectedTab)?selectedTab:'requests'; 19 function selectTab(value){if(tabs[value])selectedTab=value;} 20 function getRouteState(){return {tab:selectedTab};} 21 function managerList(options){return Array.isArray(options.managers)?options.managers:options.manager?.person_id?[options.manager]:[];} 22 function managerCard(a,options){ 23 const managers=managerList(options);return '<section class="finance-manager-group" aria-label="'+tr(a,'Responsáveis Financeiros','Finance Managers')+'"><div class="finance-manager-people">'+(managers.length?managers.map(manager=>{ 24 const canProfile=Number.isSafeInteger(Number(manager.person_id))&&Number(manager.person_id)>0&&['ready','pending_activation'].includes(manager.status),tag=canProfile?'button':'div',name=manager.full_name||''; 25 return '<article class="finance-manager-card"><'+tag+' class="finance-manager-person" '+(canProfile?'type="button" data-finance-person="'+Number(manager.person_id)+'" aria-label="'+esc(a,tr(a,'Abrir perfil de ','Open profile of ')+name)+'"':'')+'>'+(manager.person_id&&a.avatarHTML?a.avatarHTML(manager.person_id,name,'initials',manager.photo||null):'')+'<span class="finance-manager-copy"><span>'+tr(a,'Responsável Financeiro','Finance Manager')+'</span><strong>'+esc(a,name)+'</strong>'+(manager.status==='pending_activation'?'<small>'+tr(a,'Conta aguarda ativação','Account awaiting activation')+'</small>':'')+(manager.status==='unavailable'?'<small>'+tr(a,'Designação indisponÃvel nesta temporada','Designation unavailable in this season')+'</small>':'')+(canProfile?'<span class="finance-manager-profile">
25'+tr(a,'Ver perfil','View profile')+' '+(a.icon?.('arrow')||'')+'</span>':'')+'</span></'+tag+'></article>'; 26 }).join(''):'<p class="muted finance-managers-empty">'+tr(a,'Responsáveis financeiros ainda não selecionados.','Finance managers have not been selected yet.')+'</p>')+'</div></section>'; 27 } 28 function managerActions(a,options){return editable(a,options,'can_assign_manager')&&a.state.actor?.role==='admin'?'<div class="finance-heading-actions"><button type="button" class="button" data-finance-manager>'+tr(a,'Atualizar responsáveis','Update managers')+'</button></div>':'';} 29 function requestsHTML(a,data){return '<section><div class="finance-heading"><h2>'+tr(a,'Pedidos','Requests')+'</h2>'+a.selectField('finance_status',tr(a,'Situação','Status'),[['',tr(a,'Todas','All')],...(data.options.statuses||[]).map(x=>[x.code,label(a,x)])],filter)+'</div><div class="finance-request-grid">'+(data.requests.items.length?data.requests.items.map(r=>'<article class="activity-card"><span class="status finance-status '+esc(a,r.status)+'">'+esc(a,status(a,r.status))+'</span><h3>'+esc(a,r.title)+'</h3><p class="muted">'+tr(a,'Autor: ','Author: ')+esc(a,r.requester_name)+' · '+date(a,r.created_at)+'</p><p class="muted">'+esc(a,requestModule()?.categoryLabel(a,r.origin==='workshop'?'workshop':r.category)||'')+'</p><p>'+r.items.length+' '+tr(a,'itens','items')+' · <strong>'+money(a,r.total_cents)+'</strong></p>'+(r.missing_cost_items?'<p class="muted">'+tr(a,'Há itens aguardando preço ou câmbio.','Some items await a price or exchange rate.')+'</p>':'')+(['rejected','needs_revision'].includes(r.status)&&r.decision_reason?'<p class="notice">'+esc(a,r.decision_reason)+'</p>':'')+(r.purchase?'<span class="status finance-purchase-status">'+tr(a,'Compra registrada','Purchase recorded')+'</span>':'')+'<button type="button" class="button" data-finance-request="'+Number(r.id)+'">'+a.t('open')+'</button></article>').join(''):'<p class="empty-inline">'+tr(a,'Nenhum pedido nesta seleção.','No requests in this selection.')+'</p>')+'</div></section>';} 30 function renderPage(a,data){const options=data.options,manager=options.manager,tab=activeTab(data),ledger=window.AltaVoltagemFinanceLedger;const content=tab==='requests'?requestsHTML(a,data):!ledger?'<p class="notice">'+tr(a,'O componente financeiro não foi carregado. Atualize a página.','The finance component did not load. Reload the page.')+'</p>':tab==='overview'?ledger.overview(a,data):tab==='contributions'?ledger.contributionsHTML(a,data):ledger.recordsHTML(a,data,tab); 31 return '<div class="finance-heading finance-page-heading"><div><h1>'+tr(a,'Financeiro','Finance')+'</h1><p class="muted">'+tr(a,'Entradas, despesas e pedidos da temporada, com previsões separadas do caixa.','Season income, expenses and requests, with forecasts separate from cash.')+'</p></div>'+'<div class="finance-managers-line">'+managerCard(a,options)+managerActions(a,options)+'</div>'+(tab==='requests'&&editable(a,options,'can_request')?'<button type="button" class="button primary finance-new-request" data-finance-new>'+tr(a,'Novo pedido','New request')+'</button>':'')+'</div>'+(!managerList(options).some(person=>['ready','pending_activation'].includes(person.status))?'<p class="notice">'+tr(a,'Os pedidos podem ser registrados. Os avisos ao financeiro ficam pendentes até um responsável elegÃvel ser definido.','Requests can be recorded. Finance notices remain pending until an eligible manager is assigned.')+'</p>':'')+'<div class="finance-tabs" role="tablist" aria-label="'+tr(a,'Seções do financeiro','Finance sections')+'">'+allowedTabs(data).map(key=>'<button type="button" role="tab" id="finance-tab-'+key+'" aria-controls="finance-active-panel" aria-selected="'+String(key===tab)+'" tabindex="'+(key===tab?'0':'-1')+'" data-finance-tab="'+key+'">'+tr(a,...tabs[key])+'</button>').join('')+'</div><section id="finance-active-panel" role="tabpanel" aria-labelledby="finance-tab-'+tab+'">'+content+'</section>';} 32 function bindPage(a,data,container){container.querySelectorAll('[data-finance-person]').forEach(button=>button.addEventListener('click',event=>{event.stopPropagation();
32a.openPersonProfile?.(Number(event.currentTarget.dataset.financePerson));}));const moveTab=async(value,focus=false)=>{if(!allowedTabs(data).includes(value))return;selectedTab=value;await a.loadPage();if(focus&&container.isConnected)container.querySelector('[data-finance-tab="'+value+'"]')?.focus();};const controls=[...container.querySelectorAll('[data-finance-tab]')];controls.forEach((button,index)=>{button.addEventListener('click',()=>moveTab(button.dataset.financeTab));button.addEventListener('keydown',event=>{if(!['ArrowLeft','ArrowRight','Home','End'].includes(event.key))return;event.preventDefault();const next=event.key==='Home'?0:event.key==='End'?controls.length-1:(index+(event.key==='ArrowRight'?1:-1)+controls.length)%controls.length;moveTab(controls[next].dataset.financeTab,true);});});container.querySelector('[data-finance-new]')?.addEventListener('click',()=>editRequest(a,data.options));container.querySelector('[data-finance-manager]')?.addEventListener('click',()=>editManager(a,data.options));container.querySelector('[name=finance_status]')?.addEventListener('change',e=>{filter=e.target.value;a.loadPage();});container.querySelectorAll('[data-finance-request]').forEach(b=>b.addEventListener('click',()=>openRequest(a,Number(b.dataset.financeRequest))));container.querySelector('[data-finance-new-expense]')?.addEventListener('click',()=>editExpense(a,data.options));window.AltaVoltagemFinanceLedger?.bind(a,data,container,activeTab(data));} 33 function itemEditor(a,options,item={}){return '<fieldset class="finance-item-editor" data-finance-item><legend>'+tr(a,'Item do pedido','Request item')+'</legend><div class="form-grid">'+a.selectField('material_type',tr(a,'Material','Material'),[['filament',tr(a,'Rolo de filamento','Filament roll')],['mdf',tr(a,'Placa de MDF','MDF sheet')]],item.material_type||'filament')+a.field('quantity',tr(a,'Quantidade','Quantity'),item.quantity||1,'number','required min="1" max="10000" step="1"')+'</div><div data-finance-filament class="form-grid">'+a.selectField('filament_type',tr(a,'Tipo de filamento','Filament type'),options.filament_types.map(x=>[x,x]),item.filament_type||'PLA')+a.selectField('color_code',tr(a,'Cor','Color'),options.colors.map(x=>[x.code,label(a,x)]),item.color_code||'yellow')+'</div><div data-finance-other>'+a.field('color_other',tr(a,'Outra cor','Other color'),item.color_other||'','text','maxlength="100"')+'</div>'+a.field('unit_cost',tr(a,'Custo unitário em R$ (opcional até aprovação)','Unit cost in R$ (optional until approval)'),window.AltaVoltagemFinanceLedger?.amountInput(item.unit_cost_cents)??'','text','inputmode="numeric" data-money placeholder="89,90"')+a.textArea('description',tr(a,'Especificação do item (opcional)','Item specification (optional)'),item.description||'','maxlength="1000"')+'<button type="button" class="button small" data-finance-remove>'+tr(a,'Remover item','Remove item')+'</button></fieldset>';} 34 function requestPayload(a,options,form,item={}){if(!editable(a,options,'can_request_workshop')||item.id&&(!item.can_edit||Number(item.created_by)!==Number(a.state.actor?.id)))throw Error(a.t('noPermission'));if(String(a.state.seasonId)!==String(options.season.id))throw Error(tr(a,'A temporada mudou. Reabra o pedido.','The season changed. Reopen the request.'));const names=['material_type','filament_type','color_code','color_other','quantity','unit_cost','description'],values=Object.fromEntries(names.map(n=>[n,form.getAll(n)])),items=values.material_type.map((type,i)=>{const quantity=Number(values.quantity[i]),cost=String(values.unit_cost[i]||'').trim();if(!Number.isInteger(quantity)||quantity<1||quantity>10000)throw Error(tr(a,'Confira a quantidade de cada item.','Check the quantity of each item.'));if(cost&&!/^(0|[1-9][0-9]{0,8})(?:[.,][0-9]{1,2})?$/.test(cost))throw Error(tr(a,'Informe o custo sem milhares e com até duas casas decimais.','Enter cost without thousands separators and with up to two decimal places.'));return {material_type:type,filament_type:type==='filament'?values.filament_type[i]:null,color_code:type==='filament'?values.color_code[i]:null,color_other:type==='filament'&&values.color_code[i]==='other'?String(values.color_other[i]||''):'',description:String(values.description[i]||''),quantity,unit_cost:cost||null};});if(items.length<1||items.length>40)throw Error(tr(a,'Inclua de 1 a 40 itens.','Include 1 to 40 items.'));return {season_id:Number(options.season.id),title:String(form.get('title')||''),notes:String(form.get('notes')||''),items,...(item.id?{id:item.id,version:item.version}:{})};} 35 function editWorkshopRequest(a,options,item={}){if(!editable(a,options,'can_request_workshop')||item.id&&(!item.can_edit||Number(item.created_by)!==Number(a.state.actor?.id)))return;const body=a.field('title',a.t('title'),item.title||'','text','required maxlength="200"')+a.textArea('notes',tr(a,'Observação do pedido','Request note'),item.notes||'','maxlength="4000"')+'<div data-finance-items>
35'+(item.items?.length?item.items:[{}]).map(i=>itemEditor(a,options,i)).join('')+'</div><button type="button" class="button" data-finance-add>'+tr(a,'Adicionar item','Add item')+'</button>';const dialog=a.openDialog(item.id?tr(a,'Editar pedido','Edit request'):tr(a,'Novo pedido de materiais','New material request'),body,a.editorFooter(),async(form,dialog)=>{await a.api('finance.workshop.save',requestPayload(a,options,form,item),'POST');await a.saved(dialog);});const sync=row=>{const filament=row.querySelector('[name=material_type]').value==='filament';row.querySelector('[data-finance-filament]').hidden=!filament;row.querySelector('[data-finance-other]').hidden=!filament||row.querySelector('[name=color_code]').value!=='other';};const wire=row=>{['material_type','color_code'].forEach(n=>row.querySelector('[name='+n+']').addEventListener('change',()=>sync(row)));row.querySelector('[data-finance-remove]').addEventListener('click',()=>{if(dialog.querySelectorAll('[data-finance-item]').length>1)row.remove();});sync(row);};dialog.querySelectorAll('[data-finance-item]').forEach(wire);dialog.querySelector('[data-finance-add]').addEventListener('click',()=>{if(dialog.querySelectorAll('[data-finance-item]').length>=40)return;const parent=dialog.querySelector('[data-finance-items]');parent.insertAdjacentHTML('beforeend',itemEditor(a,options));wire(parent.lastElementChild);});} 36 async function openRequest(a,id,known=null){ 37 if(!allowed(a))return;const season=String(a.state.seasonId),wait=a.openDialog(tr(a,'Pedido','Request'),a.loading()); 38 try{ 39 const r=known||await a.api('finance.get',{id}),options=await a.api('finance.options',{season_id:r.season_id}); 40 if(!wait.isConnected||!wait.open||String(a.state.seasonId)!==season){wait.close();return;}wait.close(); 41 const module=requestModule(),sameSeason=String(r.season_id)===String(a.state.seasonId),writable=sameSeason&&!a.readOnlySeason?.(),author=Number(r.created_by)===Number(a.state.actor?.id); 42 const history=(r.history||[]).map(h=>{const snapshot=h.snapshot||{};return '<article class="profile-review"><strong>'+tr(a,'Versão ','Version ')+Number(h.version)+' · '+esc(a,h.actor_name)+'</strong><p class="muted">'+esc(a,module?.stamp(a,h.created_at)||date(a,h.created_at))+'</p><p>'+esc(a,status(a,snapshot.status))+'</p>'+(snapshot.decision_reason||snapshot.notes?'<p class="detail-copy">'+esc(a,snapshot.decision_reason||snapshot.notes)+'</p>':'')+itemsTable(a,snapshot.items||[],options)+'</article>';}).join(''); 43 const body='<span class="status finance-status '+esc(a,r.status)+'">'+esc(a,status(a,r.status))+'</span><p class="muted">'+tr(a,'Autor: ','Author: ')+esc(a,r.requester_name)+' · '+esc(a,module?.stamp(a,r.created_at)||date(a,r.created_at))+'</p><p class="muted">'+esc(a,module?.categoryLabel(a,r.origin==='workshop'?'workshop':r.category)||'')+'</p>'+(r.notes?'<p class="detail-copy">'+esc(a,r.notes)+'</p>':'')+(r.decision_reason?'<p class="notice">'+esc(a,r.decision_reason)+'</p>':'')+itemsTable(a,r.items,options)+'<p><strong>'+tr(a,'Estimativa em reais: ','Estimate in BRL: ')+money(a,r.total_cents)+'</strong></p>'+(module?.purchaseHTML(a,r.purchase)||'')+(history?'<details><summary>'+tr(a,'Histórico de alterações e decisões','Change and decision history')+'</summary>'+history+'</details>':''); 44 const transitions=(writable?r.allowed_transitions||[]:[]).filter(code=>Object.hasOwn(statuses,code)); 45 const actions=(r.can_edit&&author&&writable?'<button type="button" class="button" data-finance-edit>'+a.t('edit')+'</button>':'')+transitions.map(code=>'<button type="button" class="button '+(code==='approved'?'primary':'')+'" data-finance-transition="'+code+'">'+actionLabel(a,code)+'</button>').join('')+(module?.canIssue(a,options,r)?'<button type="button" class="button primary" data-finance-issue>'+tr(a,'Registrar compra e pagamento','Record purchase and payment')+'</button>':'')+'<button type="button" class="button" data-dialog-close>
45'+a.t('close')+'</button>'; 46 const dialog=a.openDialog(r.title,body,actions); 47 if(sameSeason)a.markResumeDialog?.(dialog,'finance-request',r.id); 48 dialog.dataset.removalType='finance_request';dialog.dataset.removalId=String(r.id); 49 dialog.querySelector('[data-finance-edit]')?.addEventListener('click',()=>editRequest(a,options,r)); 50 dialog.querySelector('[data-finance-issue]')?.addEventListener('click',()=>module.issue(a,options,r)); 51 dialog.querySelector('[data-purchase-expense]')?.addEventListener('click',event=>module.openExpense(a,options,Number(event.currentTarget.dataset.purchaseExpense))); 52 dialog.querySelectorAll('[data-finance-transition]').forEach(button=>button.addEventListener('click',()=>transition(a,options,r,button.dataset.financeTransition))); 53 return true; 54 }catch(error){wait.close();a.toast(a.errorMessage(error),true);return false;} 55 } 56 function transition(a,options,r,target){ 57 if(a.readOnlySeason?.()||String(a.state.seasonId)!==String(options.season.id)||!(r.allowed_transitions||[]).includes(target))return; 58 const reasonRequired=['rejected','needs_revision'].includes(target),label=target==='needs_revision'?tr(a,'O que precisa ser revisado','What needs to be revised'):target==='rejected'?tr(a,'Motivo da reprovação','Reason for rejection'):tr(a,'Observação (opcional)','Note (optional)'); 59 a.openDialog(actionLabel(a,target),'<p>'+esc(a,r.title)+'</p>'+a.textArea('reason',label,'','maxlength="1000" '+(reasonRequired?'required':'')),a.editorFooter(),async(form,dialog)=>{ 60 if(a.readOnlySeason?.()||String(a.state.seasonId)!==String(options.season.id))throw Error(a.t('noPermission')); 61 const reason=String(form.get('reason')||'').trim();if(reasonRequired&&!reason)throw Error(tr(a,'Explique o motivo antes de enviar.','Explain the reason before submitting.')); 62 await a.api('finance.transition',{id:r.id,version:r.version,status:target,reason},'POST');await a.saved(dialog); 63 }); 64 } 65 function editRequest(a,options,item={}){if(item.origin==='workshop')return editWorkshopRequest(a,options,item);if(!requestModule()){a.toast(tr(a,'Atualize a página para carregar os pedidos.','Reload the page to load requests.'),true);return;}return requestModule().edit(a,options,item);} 66 const workshopMounts=new WeakMap(); 67 async function mountWorkshopMaterial(a,container){ 68 const slot=container.querySelector('[data-workshop-material-slot]');if(!slot||a.readOnlySeason?.()||!(a.state.actor?.role==='admin'||a.state.actor?.permissions?.includes('workshop.operate')))return; 69 const token={},season=String(a.state.seasonId),actor=String(a.state.actor.id);workshopMounts.set(container,token); 70 const current=()=>container.isConnected&&workshopMounts.get(container)===token&&String(a.state.seasonId)===season&&String(a.state.actor?.id)===actor&&a.state.page==='workshop'&&!a.readOnlySeason?.(); 71 try{const options=await a.api('finance.options',{season_id:Number(season)});if(!current())return;slot.innerHTML='';if(!options.permissions?.can_request_workshop)return; 72 slot.innerHTML='<button type="button" class="button" data-workshop-material-request>'+tr(a,'Pedido de material','Material request')+'</button>'; 73 slot.querySelector('[data-workshop-material-request]').addEventListener('click',()=>{if(current())editWorkshopRequest(a,options);}); 74 }catch(error){if(!current())return;slot.innerHTML='<button type="button" class="button small" data-workshop-material-retry>'+tr(a,'Pedidos de material indisponÃveis · tentar novamente','Material requests unavailable · retry')+'</button>';slot.querySelector('[data-workshop-material-retry]').addEventListener('click',()=>mountWorkshopMaterial(a,container));} 75 } 76 function editManager(a,options){ 77 if(!editable(a,options,'can_assign_manager')||a.state.actor?.role!=='admin')return; 78 const candidates=(options.manager_candidates||[]).filter(person=>Number.isSafeInteger(Number(person.person_id))&&Number(person.person_id)>0),selected=new Set((options.manager_person_ids||managerList(options).map(person=>person.person_id)).map(Number)); 79 const body='<fieldset class="finance-manager-choices"><legend>'+tr(a,'Responsáveis da temporada','Season managers')+'</legend>'+candidates.map(person=>'<label class="check-label"><input type="checkbox" name="person_ids" value="'+Number(person.person_id)+'" '+(selected.has(Number(person.person_id))?'checked':'')+'><span>'+esc(a,person.full_name)+(person.status==='pending'?'<small>'+tr(a,'Conta aguarda ativação','Account awaiting activation')+'</small>':'')+'</span></label>').join('')+'</fieldset><p class="notice">'+tr(a,'A escolha permite gerenciar o financeiro e receber os avisos internos dos pedidos. Não ativa contas nem envia e-mails. Desmarque todos para remover as designações.','Selection allows financial management and internal request notices. It does not activate accounts or send emails. Uncheck everyone to remove the assignments.')+'</p>'; 80 a.openDialog(tr(a,'Atualizar responsáveis financeiros','Update finance managers'),body,a.editorFooter(),async(form,dialog)=>{ 81 if(String(a.state.seasonId)!==String(options.season.id)||a.readOnlySeason?.()||a.state.actor?.role!=='admin')throw Error(a.t('noPermission')); 82 const ids=[...new Set(form.getAll('person_ids').map(Number))];if(ids.some(id=>!candidates.some(person=>Number(person.person_id)===id)))throw Error(a.t('noPermission')); 83 await a.api('finance.manager.save',{season_id:Number(options.season.id),person_ids:ids,version:options.manager_version??options.manager?.version??0},'POST');await a.refreshBootstrap();await a.saved(dialog); 84 }); 85 } 86 async function editExpense(a,options,item={}){if(!editable(a,options,'can_manage')||item.id&&!item.can_edit)return;try{const season=String(options.season.id),approved=await a.api('finance.list',{season_id:options.season.id,status:'approved'});if(String(a.state.seasonId)!==season)return;const links=[['',tr(a,'Despesa sem vÃnculo a pedido','Expense without request link')],...approved.items.flatMap(r=>r.items.map(i=>[i.id,r.title+' · '+itemLabel(a,i,options)]))];const ledger=window.AltaVoltagemFinanceLedger;if(!ledger)throw Error(tr(a,'Atualize a página para carregar o financeiro.','Reload the page to load finance.'));const body=a.selectField('request_item_id',tr(a,'Item aprovado (opcional)','Approved item (optional)'),links,item.request_item_id||'',item.id?'disabled':'')+a.textArea('description',tr(a,'Descrição da despesa real','Actual expense description'),item.description||'','required maxlength="1000"')+a.field('amount',tr(a,'Valor da despesa em R$','Expense amount in R$'),ledger.amountInput(item.amount_cents),'text','required inputmode="numeric" data-money placeholder="89,90"')+'<div class="form-grid">'+a.field('incurred_on',tr(a,'Data da despesa','Expense date'),item.incurred_on||'','date','required')+a.field('due_on',tr(a,'Vencimento (opcional)','Due date (optional)'),item.due_on||'','date')+'</div><p class="muted">'+tr(a,'Salvar a despesa não registra pagamento. Abra a despesa para informar cada pagamento já realizado, inclusive parcelas.','S
86aving the expense does not record payment. Open the expense to record each payment already made, including partial payments.')+'</p>';a.openDialog(item.id?tr(a,'Revisar despesa','Revise expense'):tr(a,'Registrar despesa','Record expense'),body,a.editorFooter(),async(form,dialog)=>{await a.api('finance.expense.save',ledger.expensePayload(a,{options},form,item),'POST');await a.saved(dialog);});}catch(e){a.toast(a.errorMessage(e),true);}} 87 window.AltaVoltagemFinance={load,renderPage,bindPage,openRequest,editRequest,editWorkshopRequest,mountWorkshopMaterial,transition,actionLabel,itemsTable,editManager,editExpense,requestPayload,itemEditor,allowed,money,selectTab,getRouteState,activeTab,allowedTabs,incomingRequest,managerCard,managerActions,managerList}; 88})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.