78 lines
4.4 KiB
JavaScript
78 lines
4.4 KiB
JavaScript
/* Local amount presentation: never convert rials to tomans for the server.
|
||
Exact BigInt arithmetic; preserve fractional toman (one rial = one tenth toman).
|
||
Visible grouping and Persian words do not replace paid_amount validation. */
|
||
(() => {
|
||
'use strict';
|
||
const input=document.querySelector('#paid-amount');if(!input)return;
|
||
const words=document.querySelector('#amount-words');
|
||
const small=['صفر','یک','دو','سه','چهار','پنج','شش','هفت','هشت','نه','ده','یازده','دوازده','سیزده','چهارده','پانزده','شانزده','هفده','هجده','نوزده'];
|
||
const tens=['','','بیست','سی','چهل','پنجاه','شصت','هفتاد','هشتاد','نود'];
|
||
const hundreds=['','صد','دویست','سیصد','چهارصد','پانصد','ششصد','هفتصد','هشتصد','نهصد'];
|
||
const scales=['','هزار','میلیون','میلیارد','تریلیون','کوادریلیون'];
|
||
const ascii=value=>value.replace(/[۰-۹]/g,digit=>String(digit.charCodeAt(0)-1776)).replace(/[٠-٩]/g,digit=>String(digit.charCodeAt(0)-1632));
|
||
const digitCount=value=>(ascii(value).match(/\d/g) || []).length;
|
||
const separators=/[,٬ \u00a0]/g;
|
||
const grouped=value=>value.replace(/\B(?=(\d{3})+(?!\d))/g,'٬').replace(/\d/g,digit=>'۰۱۲۳۴۵۶۷۸۹'[digit]);
|
||
function threeDigits(value){
|
||
const parts=[];
|
||
if(value>=100)parts.push(hundreds[Math.floor(value/100)]);
|
||
const rest=value%100;
|
||
if(rest && rest<20)parts.push(small[rest]);
|
||
if(rest>=20){parts.push(tens[Math.floor(rest/10)]);if(rest%10)parts.push(small[rest%10]);}
|
||
return parts.join(' و ');
|
||
}
|
||
function numberWords(value){
|
||
if(value===0n)return 'صفر';
|
||
const parts=[];let index=0;
|
||
while(value>0n){
|
||
const group=Number(value%1000n);
|
||
if(group){const text=group===1 && index===1?'هزار':threeDigits(group)+(scales[index]?' '+scales[index]:'');parts.unshift(text);}
|
||
value/=1000n;index++;
|
||
}
|
||
return parts.join(' و ');
|
||
}
|
||
function tomanWords(rials){
|
||
const whole=rials/10n,fraction=rials%10n;
|
||
if(!fraction)return numberWords(whole)+' تومان';
|
||
return (whole?numberWords(whole)+' و ':'')+numberWords(fraction)+' دهم تومان';
|
||
}
|
||
function caretAfterDigits(value,count){
|
||
if(!count)return 0;
|
||
for(let index=0;index<value.length;index++)if(/\d/.test(ascii(value[index])) && --count===0)return index+1;
|
||
return value.length;
|
||
}
|
||
function formatAmount(event){
|
||
if(event?.isComposing)return;
|
||
const raw=input.value,start=input.selectionStart ?? raw.length,end=input.selectionEnd ?? start;
|
||
const normalized=ascii(raw).trim();
|
||
const valid=/^\d+$/.test(normalized) || /^\d{1,3}(?:[,٬ \u00a0]\d{3})+$/.test(normalized);
|
||
// Re-group after genuine digit edits/deletion, but never sanitize letters, signs or decimal input.
|
||
const editing=event?.inputType?.startsWith('delete') || (event?.inputType==='insertText' && /^\d+$/.test(ascii(event.data || '')));
|
||
if(!valid && !(editing && /^[\d,٬ \u00a0]+$/.test(normalized))){words.textContent='';return;}
|
||
const digits=normalized.replace(separators,'').replace(/^0+(?=\d)/,'');
|
||
if(!digits || digits.length>16 || BigInt(digits)>BigInt(Number.MAX_SAFE_INTEGER)){words.textContent='';return;}
|
||
const leading=normalized.replace(separators,'').length-digits.length;
|
||
const left=Math.max(0,digitCount(raw.slice(0,start))-leading),right=Math.max(0,digitCount(raw.slice(0,end))-leading);
|
||
input.value=grouped(digits);
|
||
input.setSelectionRange(caretAfterDigits(input.value,left),caretAfterDigits(input.value,right));
|
||
words.textContent='معادل: '+tomanWords(BigInt(digits));
|
||
}
|
||
input.addEventListener('input',formatAmount);
|
||
input.addEventListener('compositionend',()=>formatAmount());
|
||
input.addEventListener('beforeinput',event=>{
|
||
const start=input.selectionStart,end=input.selectionEnd;
|
||
if(start!==end || event.isComposing)return;
|
||
const backward=event.inputType==='deleteContentBackward',forward=event.inputType==='deleteContentForward';
|
||
const adjacent=backward?start-1:start;
|
||
if((!backward && !forward) || !/[٬,]/.test(input.value[adjacent] || ''))return;
|
||
// Deleting a separator removes the adjacent digit, so grouping cannot trap the caret.
|
||
const target=backward?adjacent-1:adjacent+1;
|
||
const count=digitCount(input.value.slice(0,target));
|
||
const raw=input.value.slice(0,target)+input.value.slice(target+1);
|
||
event.preventDefault();input.value=ascii(raw).replace(separators,'');
|
||
input.setSelectionRange(count,count);input.dispatchEvent(new InputEvent('input',{bubbles:true,inputType:event.inputType}));
|
||
});
|
||
input.addEventListener('blur',()=>formatAmount());
|
||
formatAmount();
|
||
})();
|