PageSourceSearch

https://grupoaltavoltagem.com.br/dashboard/assets/modules/finance-requests-ui.js?v=58a376f8bde4c9bb

js grupoaltavoltagem.com.br collected 2026-10-04 07:04:32 UTC 20,185 bytes, 43 lines download raw bytes

1'use strict';
2(() => {
3  const tr=(a,pt,en)=>a.state.lang==='pt'?pt:en,esc=(a,value)=>a.esc(value??''),ledger=()=>window.AltaVoltagemFinanceLedger;
4  const categories={stationery:['Papelaria','Stationery'],portfolio:['Portfólio','Portfolio'],competition:['Competição','Competition'],engineering:['Engenharia','Engineering'],other:['Outros','Other']};
5  const engineering={motor:['Motor','Motor'],servo:['Servo','Servo'],sensor:['Sensor','Sensor'],odometry:['Odometria','Odometry'],controller:['Controlador','Controller'],power:['Alimentação elétrica','Power'],structure:['Estrutura','Structure'],transmission:['Transmissão','Transmission'],wheels:['Rodas','Wheels'],linear_motion:['Movimento linear','Linear motion'],kit:['Kit','Kit'],hardware:['Fixadores','Hardware'],tool:['Ferramentas','Tools'],wiring:['Cabos e conectores','Wiring and connectors'],other:['Outro componente','Other component']};
6  const currencies=['BRL','USD','EUR','GBP','CAD','CNY','JPY'];let rowKey=0;
7  const categoryLabel=(a,value)=>categories[value]?tr(a,...categories[value]):value==='workshop'?tr(a,'Materiais da Oficina','Workshop materials'):'';
8  const writable=(a,options)=>options.permissions?.can_request===true&&!a.readOnlySeason?.()&&String(a.state.seasonId)===String(options.season.id);
9  const canEdit=(a,options,record={})=>writable(a,options)&&(!record.id||record.can_edit===true&&Number(record.created_by)===Number(a.state.actor?.id));
10  const canIssue=(a,options,record)=>options.permissions?.can_manage===true&&record.can_issue===true&&!record.purchase&&!a.readOnlySeason?.()&&String(a.state.seasonId)===String(options.season.id);
11  const originalMoney=(a,cents,currency='BRL')=>Number.isSafeInteger(cents)&&currencies.includes(currency)?new Intl.NumberFormat(a.state.lang==='pt'?'pt-BR':'en',{style:'currency',currency,minimumFractionDigits:2,maximumFractionDigits:2}).format(cents/100):'—';
12  const date=value=>/^\d{4}-\d{2}-\d{2}$/.test(String(value||''))?String(value).split('-').reverse().join('/'):'—';
13  function stamp(a,value){if(!value)return '—';const raw=String(value),parsed=new Date(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(raw)?raw.replace(' ','T')+'Z':raw);return Number.isNaN(parsed.getTime())?'—':new Intl.DateTimeFormat(a.state.lang==='pt'?'pt-BR':'en',{dateStyle:'short',timeStyle:'short',hourCycle:'h23',timeZone:'America/Sao_Paulo'}).format(parsed);}
14  function safeURL(value){if(!value)return '';try{const url=new URL(value);return url.protocol==='https:'&&!url.username&&!url.password&&!/[\s\\]/.test(value)?url.href:null;}catch{return null;}}
15  function quoteInput(value,cents){if(Number.isSafeInteger(cents))return ledger().amountInput(cents);if(value===null||value===undefined||value==='')return '';try{return ledger().amountInput(ledger().parseAmount(value,{zero:true}).cents);}catch{return '';}}
16  function rate(value){const raw=String(value??'').trim().replace(',','.');if(!raw)return null;if(!/^(0|[1-9][0-9]{0,5})(?:\.[0-9]{1,6})?$/.test(raw)||!/[1-9]/.test(raw))throw Error('FINANCE_EXCHANGE_RATE');return raw;}
17  function uniqueFields(html){const suffix='-purchase-'+(++rowKey);return html.replace(/\b(id|for)="(field-[^"]+)"/g,(_,attribute,id)=>attribute+'="'+id+suffix+'"');}
18  function itemEditor(a,options,item={},kind='stationery'){
19    const region=item.purchase_region||'domestic',currency=item.currency||'BRL',details=kind==='engineering';
20    const body=a.textArea('description',tr(a,'O que precisa comprar','What needs to be purchased'),item.description||'','required maxlength="1000"')+'<div class="form-grid">'+a.field('quantity',tr(a,'Quantidade','Quantity'),item.quantity||1,'number','required min="1" max="10000" step="1"')+a.selectField('purchase_region',tr(a,'Compra','Purchase'),[['domestic',tr(a,'Nacional','Domestic')],['international',tr(a,'Internacional','International')]],region,'required')+a.selectField('currency',tr(a,'Moeda da cotação','Quote currency'),currencies.map(code=>[code,code]),currency,'required')+a.field('unit_cost',tr(a,'Preço unitário · moeda selecionada (opcional)','Unit price · selected currency (optional)'),quoteInput(item.unit_cost,item.quote_unit_cost_cents),'text','inputmode="numeric" data-money placeholder="0,00"')+a.field('shipping_cost',tr(a,'Frete desta linha · moeda selecionada','Shipping for this line · selected currency'),quoteInput(item.shipping_cost,item.quote_shipping_cost_cents),'text','required inputmode="numeric" data-money placeholder="0,00"')+'<div data-purchase-rate>'+a.field('exchange_rate_brl',tr(a,'Câmbio: R$ por 1 unidade da moeda','
20Exchange rate: R$ per 1 currency unit'),currency==='BRL'?'':item.exchange_rate_brl||'','text','inputmode="decimal" placeholder="5,123456"')+'</div></div><p class="muted finance-quote-help">'+tr(a,'Informe 0 no frete se não houver cobrança. O preço unitário pode ficar vazio até a aprovação. Conversão em reais usa somente o câmbio informado; não buscamos cotações.','Enter 0 for shipping if there is no charge. Unit price may remain empty until approval. BRL conversion uses only the exchange rate entered; no rates are fetched.')+'</p><div class="finance-engineering-fields" data-purchase-engineering '+(details?'':'hidden')+'><div class="form-grid">'+a.selectField('engineering_type',tr(a,'Tipo de componente','Component type'),[['',tr(a,'Selecione','Select')],...Object.entries(engineering).map(([key,labels])=>[key,tr(a,...labels)])],item.engineering_type||'',details?'required':'')+a.field('brand',tr(a,'Marca','Brand'),item.brand||'','text','maxlength="200"')+a.field('model',tr(a,'Modelo','Model'),item.model||'','text','maxlength="200"')+'</div>'+a.textArea('specifications',tr(a,'Especificações','Specifications'),item.specifications||'','maxlength="2000"')+a.textArea('compatibility',tr(a,'Compatibilidade necessária','Required compatibility'),item.compatibility||'','maxlength="1000"')+'</div><div class="form-grid">'+a.field('supplier',tr(a,'Fornecedor (opcional)','Supplier (optional)'),item.supplier||'','text','maxlength="200"')+a.field('supplier_url',tr(a,'Site do fornecedor (opcional)','Supplier website (optional)'),item.supplier_url||'','url','maxlength="2000" placeholder="https://"')+'</div><button type="button" class="button small" data-purchase-remove>'+tr(a,'Remover item','Remove item')+'</button>';
21    return '<fieldset class="finance-general-item" data-purchase-item><legend>'+tr(a,'Item do pedido','Request item')+'</legend>'+uniqueFields(body)+'</fieldset>';
22  }
23  function payload(a,options,form,record={}){
24    if(!canEdit(a,options,record))throw Error(a.t('noPermission'));const kind=String(form.get('category')||'');if(!categories[kind])throw Error(tr(a,'Selecione a categoria do pedido.','Select the request category.'));
25    const names=['description','quantity','unit_cost','shipping_cost','purchase_region','currency','exchange_rate_brl','engineering_type','brand','model','specifications','compatibility','supplier','supplier_url'],values=Object.fromEntries(names.map(name=>[name,form.getAll(name)]));
26    const items=values.description.map((description,index)=>{const quantity=Number(values.quantity[index]),purchase_region=String(values.purchase_region[index]||''),currency=String(values.currency[index]||''),rawUnit=String(values.unit_cost[index]||'').trim(),rawShipping=String(values.shipping_cost[index]??'').trim();if(!Number.isInteger(quantity)||quantity<1||quantity>10000)throw Error(tr(a,'Confira a quantidade de cada item.','Check each item quantity.'));if(!['domestic','international'].includes(purchase_region)||!currencies.includes(currency)||purchase_region==='domestic'&&currency!=='BRL')throw Error(tr(a,'Confira a origem da compra e a moeda. Compras nacionais usam BRL.','Check purchase region and currency. Domestic purchases use BRL.'));if(!String(description).trim())throw Error(tr(a,'Descreva cada item do pedido.','Describe every request item.'));let exchange_rate_brl;try{exchange_rate_brl=currency==='BRL'?null:rate(values.exchange_rate_brl[index]);}catch{throw Error(tr(a,'Informe o câmbio positivo com até seis casas decimais.','Enter a positive exchange rate with up to six decimal places.'));}const supplier_url=String(values.supplier_url[index]||'').trim();if(safeURL(supplier_url)===null)throw Error(tr(a,'Informe um site HTTPS sem credenciais.','Enter an HTTPS website without credentials.'));const engineering_type=kind==='engineering'?String(values.engineering_type[index]||''):null;if(kind==='engineering'&&!engineering[engineering_type])throw Error(tr(a,'Selecione o tipo de cada componente.','Select every component type.'));let unit_cost,shipping_cost;try{unit_cost=rawUnit?ledger().parseAmount(rawUnit,{zero:true}).amount:null;shipping_cost=ledger().parseAmount(rawShipping,{zero:true}).amount;}catch{throw Error(tr(a,'Confira os valores. Informe 0 no frete quando não houver cobrança.','Check the amounts. Enter 0 for shipping when there is no charge.'));}return {description:String(description).trim(),quantity,unit_cost,shipping_cost,purchase_region,currency,exchange_rate_brl,engineering_type,brand:kind==='engineering'?String(values.brand[index]||''):'',model:kind==='engineering'?String(values.model[index]||''):'',specifications:kind==='engineering'?String(values.specifications[index]||''):'',compatibility:kind==='engineering'?String(values.compatibility[index]||''):'',supplier:String(values.supplier[index]||''),supplier_url};});
27    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')||''),category:kind,items,...(record.id?{id:record.id,version:record.version}:{})};
28  }
29  function edit(a,options,record={}){
30    if(!canEdit(a,options,record))return;const category=record.category||'stationery',body=a.field('title',tr(a,'Título do pedido','Request title'),record.title||'','text','required maxlength="200"')+a.selectField('category',tr(a,'Categoria','Category'),Object.entries(categories).map(([key,labels])=>[key,tr(a,...labels)]),category,'required')+a.textArea('notes',tr(a,'Observações do pedido','Request notes'),record.notes||'','maxlength="4000"')+'<div data-purchase-items>'+(record.items?.length?record.items:[{}]).map(item=>itemEditor(a,options,item,category)).join('')+'</div>
30<button type="button" class="button" data-purchase-add>'+tr(a,'Adicionar item','Add item')+'</button><p class="muted">'+tr(a,'Somente o autor pode editar os itens e valores. Salvar uma correção mantém o estado do pedido; depois, use Reenviar para análise.','Only the author may edit items and prices. Saving a correction preserves request status; then use Resubmit for review.')+'</p>';
31    const dialog=a.openDialog(record.id?tr(a,'Editar pedido','Edit request'):tr(a,'Novo pedido','New request'),body,a.editorFooter(),async(form,current)=>{await a.api('finance.save',payload(a,options,form,record),'POST');await a.saved(current);});
32    const selected=()=>dialog.querySelector('[name=category]').value;const sync=row=>{const domestic=row.querySelector('[name=purchase_region]').value==='domestic',currency=row.querySelector('[name=currency]');if(domestic)currency.value='BRL';[...currency.options].forEach(option=>option.disabled=domestic&&option.value!=='BRL');row.querySelector('[data-purchase-rate]').hidden=currency.value==='BRL';const technical=selected()==='engineering';row.querySelector('[data-purchase-engineering]').hidden=!technical;row.querySelector('[name=engineering_type]').required=technical;};
33    const wire=row=>{['purchase_region','currency'].forEach(name=>row.querySelector('[name='+name+']').addEventListener('change',()=>sync(row)));row.querySelector('[data-purchase-remove]').addEventListener('click',()=>{if(dialog.querySelectorAll('[data-purchase-item]').length>1)row.remove();});sync(row);};dialog.querySelectorAll('[data-purchase-item]').forEach(wire);dialog.querySelector('[name=category]').addEventListener('change',()=>dialog.querySelectorAll('[data-purchase-item]').forEach(sync));dialog.querySelector('[data-purchase-add]').addEventListener('click',()=>{if(dialog.querySelectorAll('[data-purchase-item]').length>=40)return;const parent=dialog.querySelector('[data-purchase-items]');parent.insertAdjacentHTML('beforeend',itemEditor(a,options,{},selected()));wire(parent.lastElementChild);});
34  }
35  function itemsHTML(a,items){return '<div class="finance-quote-items">'+(items||[]).map(item=>{const currency=currencies.includes(item.currency)?item.currency:'BRL',supplier=safeURL(item.supplier_url);return '<article class="finance-quote-item"><h4>'+esc(a,item.description)+'</h4><p class="muted">'+esc(a,item.quantity)+' '+tr(a,'unidades','units')+' · '+(item.purchase_region==='international'?tr(a,'Compra internacional','International purchase'):tr(a,'Compra nacional','Domestic purchase'))+' · '+esc(a,currency)+'</p><dl><div><dt>'+tr(a,'Preço unitário original','Original unit price')+'</dt><dd>'+originalMoney(a,item.quote_unit_cost_cents,currency)+'</dd></div><div><dt>'+tr(a,'Frete da linha','Line shipping')+'</dt><dd>'+originalMoney(a,item.quote_shipping_cost_cents,currency)+'</dd></div><div><dt>'+tr(a,'Total original','Original total')+'</dt><dd>'+originalMoney(a,item.quote_total_cents,currency)+'</dd></div><div><dt>'+tr(a,'Estimativa em reais','Estimate in BRL')+'</dt><dd>'+ledger().money(a,item.estimated_brl_cents??item.line_total_cents)+'</dd></div></dl>'+(currency!=='BRL'?'<p class="muted">'+tr(a,'Câmbio informado: ','Entered exchange rate: ')+(item.exchange_rate_brl?esc(a,item.exchange_rate_brl)+' BRL / '+esc(a,currency):tr(a,'ainda não informado','not provided yet'))+'</p>':'')+[['engineering_type',tr(a,'Componente','Component')],['brand',tr(a,'Marca','Brand')],['model',tr(a,'Modelo','Model')],['specifications',tr(a,'Especificações','Specifications')],['compatibility',tr(a,'Compatibilidade','Compatibility')],['supplier',tr(a,'Fornecedor','Supplier')]].filter(([key])=>item[key]).map(([key,label])=>'<p class="detail-copy"><strong>'+label+':</strong> '+esc(a,key==='engineering_type'&&engineering[item[key]]?tr(a,...engineering[item[key]]):item[key])+'</p>').join('')+(supplier?'<a class="button small" href="'+esc(a,supplier)+'" target="_blank" rel="noopener noreferrer">'+tr(a,'Site do fornecedor','Supplier website')+'</a>':'')+'</article>';}).join('')+'</div>';}
36  function issuePayload(a,options,record,form){if(!canIssue(a,options,record))throw Error(a.t('noPermission'));let actual;try{actual=ledger().parseAmount(form.get('paid_actual'));}catch{throw Error(tr(a,'Informe o valor total realmente pago em reais.','Enter the total actually paid in BRL.'));}const source=String(form.get('payment_source')||'');if(!['savings','cash','mixed'].includes(source))throw Error(tr(a,'Selecione de onde saiu o pagamento.','Select where the payment came from.'));const allocations=source==='mixed'?['savings','cash'].map(code=>{try{return {cash_account:code,...ledger().parseAmount(form.get('allocation_'+code))};}catch{throw Error(tr(a,'Informe valores positivos para as duas contas do pagamento misto.','Enter positive amounts for both accounts in a mixed payment.'));}}):[{cash_account:source,...actual}];if(allocations.reduce((sum,item)=>sum+item.cents,0)!==actual.cents)throw Error(tr(a,'A soma da poupança e do dinheiro físico deve ser igual ao valor pago.','Savings plus physical cash must equal the amount paid.'));return {id:Number(record.id),version:record.version,paid_actual:actual.amount,occurred_on:form.get('occurred_on'),description:String(form.get('description')||record.title),allocations:allocations.map(({cash_account,amount})=>({cash_account,amount}))};}
37  function issue(a,options,record){if(!canIssue(a,options,record))return;const body='<p class="detail-copy"><strong>'+esc(a,record.title)+'</strong></p><p>'+tr(a,'Estimativa aprovada: ','Approved estimate: ')+ledger().money(a,record.total_cents)+'</p>'+a.field('paid_actual',tr(a,'Valor total pago em R$','Total paid in BRL'),'','text','required inputmode="numeric" data-money placeholder="0,00"')+a.field('occurred_on',tr(a,'Data em que pagou','Actual payment date'),'','date','required')+a.selectField('payment_source',tr(a,'De onde saiu o pagamento','Payment source'),[['',tr(a,'Selecione','Select')],['savings',ledger().accountLabel(a,'savings')],['cash',ledger().accountLabel(a,'cash')],['mixed',tr(a,'Misto · poupança e dinheiro físico','Mixed · savings and physical cash')]],'','required')+'<div class="form-grid" data-purchase-
37allocations hidden>'+a.field('allocation_savings',tr(a,'Pago pela conta poupança · R$','Paid from savings · BRL'),'','text','inputmode="numeric" data-money placeholder="0,00"')+a.field('allocation_cash',tr(a,'Pago em dinheiro físico · R$','Paid with physical cash · BRL'),'','text','inputmode="numeric" data-money placeholder="0,00"')+'</div><p class="muted" data-purchase-allocation-status aria-live="polite"></p>'+a.textArea('description',tr(a,'Descrição da compra','Purchase description'),record.title,'required maxlength="1000"')+'<p class="notice">'+tr(a,'Ao confirmar, o valor realmente pago será registrado como despesa e descontado das contas selecionadas. A estimativa aprovada continuará no histórico.','On confirmation, the amount actually paid is recorded as an expense and deducted from the selected accounts. The approved estimate stays in history.')+'</p>';
38    const dialog=a.openDialog(tr(a,'Registrar compra e pagamento','Record purchase and payment'),body,a.editorFooter(),async(form,current)=>{await a.api('finance.purchase.issue',issuePayload(a,options,record,form),'POST');await a.saved(current);});const update=()=>{const mixed=dialog.querySelector('[name=payment_source]').value==='mixed';dialog.querySelector('[data-purchase-allocations]').hidden=!mixed;dialog.querySelectorAll('[name^=allocation_]').forEach(input=>input.required=mixed);const slot=dialog.querySelector('[data-purchase-allocation-status]');slot.textContent='';if(!mixed)return;try{const actual=ledger().parseAmount(dialog.querySelector('[name=paid_actual]').value,{zero:true}).cents,sum=['savings','cash'].reduce((total,code)=>total+ledger().parseAmount(dialog.querySelector('[name=allocation_'+code+']').value,{zero:true}).cents,0);slot.textContent=sum===actual?tr(a,'A divisão confere com o valor pago.','The split matches the amount paid.'):tr(a,'Diferença para conferir: ','Difference to check: ')+ledger().money(a,actual-sum);}catch{slot.textContent=tr(a,'Preencha o total e a parte de cada conta.','Fill in the total and each account portion.');}};dialog.querySelector('[name=payment_source]').addEventListener('change',update);dialog.querySelectorAll('[data-money]').forEach(input=>input.addEventListener('input',update));
38update();
39  }
40  function purchaseHTML(a,purchase){if(!purchase)return '';return '<section class="finance-purchase-record"><h3>'+tr(a,'Compra registrada','Purchase recorded')+'</h3><p><strong>'+ledger().money(a,purchase.paid_actual_cents)+'</strong> · '+date(purchase.occurred_on)+'</p><p class="muted">'+tr(a,'Registrada por ','Recorded by ')+esc(a,purchase.recorded_by_name)+' · '+esc(a,stamp(a,purchase.recorded_at))+'</p><ul>'+(purchase.allocations||[]).map(part=>'<li>'+esc(a,ledger().accountLabel(a,part.cash_account))+': <strong>'+ledger().money(a,part.amount_cents)+'</strong></li>').join('')+'</ul><p class="muted">'+tr(a,'Estimativa preservada: ','Preserved estimate: ')+ledger().money(a,purchase.approved_snapshot?.total_cents)+'</p><button type="button" class="button" data-purchase-expense="'+Number(purchase.expense_id)+'">'+tr(a,'Abrir despesa e pagamentos','Open expense and payments')+'</button></section>';}
41  async function openExpense(a,options,id){try{const season=String(a.state.seasonId),response=await a.api('finance.expense.get',{id:Number(id)});if(String(a.state.seasonId)!==season)return;ledger().openRecord(a,{options,summary:{expenses:[response.expense]}},'expenses',id);}catch(error){a.toast(a.errorMessage(error),true);}}
42  window.AltaVoltagemFinanceRequests={edit,payload,itemEditor,itemsHTML,issue,issuePayload,purchaseHTML,openExpense,categoryLabel,canEdit,canIssue,quoteInput,rate,stamp};
43})();

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.