1import '../shared/price-core.js'; 2import { getCurrentUser } from './client-utils.js'; 3 4const SELECTED_VAT_STORAGE_KEY = 'selectedVat'; 5 6const normalizeSelectedVat = (value) => { 7 if (value === null || value === undefined) { 8 return null; 9 } 10 11 const raw = String(value).trim().replace('%', '').trim(); 12 if (!raw) { 13 return null; 14 } 15 16 const numeric = Number(raw.replace(',', '.')); 17 if (!Number.isFinite(numeric) || numeric < 0 || numeric > 100) { 18 return null; 19 } 20 21 return numeric 22 .toFixed(2) 23 .replace(/\.00$/, '') 24 .replace(/(\.\d)0$/, '$1'); 25}; 26 27const extractVatValue = (entry) => { 28 if (entry === null || entry === undefined) { 29 return null; 30 } 31 if (typeof entry === 'number' || typeof entry === 'string') { 32 return entry; 33 } 34 if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { 35 return null; 36 } 37 38 return ( 39 entry.percentage ?? 40 entry.vat ?? 41 entry.vat_percentage ?? 42 entry.vatPercentage ?? 43 entry.rate ?? 44 entry.percent ?? 45 entry.value ?? 46 entry.amount ?? 47 entry.tax ?? 48 entry.vatRate ?? 49 null 50 ); 51}; 52 53const buildVatOptions = (vatList) => { 54 if (!Array.isArray(vatList)) { 55 return []; 56 } 57 58 const options = []; 59 const seen = new Set(); 60 vatList.forEach((entry) => { 61 const value = normalizeSelectedVat(extractVatValue(entry)); 62 if (value === null || seen.has(value)) { 63 return; 64 } 65 seen.add(value); 66 options.push(Object.freeze({ value, label: `${value}%` })); 67 }); 68 return options; 69}; 70 71const getDefaultStorage = () => { 72 try { 73 return globalThis.localStorage || null; 74 } catch { 75 return null; 76 } 77}; 78 79const readStoredVat = (storage) => { 80 try { 81 return normalizeSelectedVat(storage?.getItem?.(SELECTED_VAT_STORAGE_KEY)); 82 } catch { 83 return null; 84 } 85}; 86 87const persistVat = (storage, value) => { 88 try { 89 storage?.setItem?.(SELECTED_VAT_STORAGE_KEY, value); 90 } catch {} 91}; 92 93const normalizeBulkDiscountLevel = (value) => { 94 const normalize = globalThis.PRICE_CORE?.normalizeBulkDiscountLevel; 95 if (typeof normalize === 'function') { 96 return normalize(value); 97 } 98 const numeric = Number(value); 99 return Number.isFinite(numeric) ? Math.max(0, Math.min(10, Math.floor(numeric))) : 0; 100}; 101 102const createPriceContext = ({ getUser = getCurrentUser, storage = undefined } = {}) => { 103 const listeners = new Set(); 104 const resolvedStorage = storage === undefined ? getDefaultStorage() : storage; 105 const state = { 106 selectedVat: null, 107 vatOptions: [], 108 bulkDiscountLevel: 0, 109 bulkDiscountStatus: 'idle', 110 }; 111 let bulkDiscountPending = null; 112 let bulkDiscountRequestToken = 0; 113 114 const getSnapshot = () => 115 Object.freeze({ 116 selectedVat: state.selectedVat, 117 vatOptions: Object.freeze([...state.vatOptions]), 118 bulkDiscountLevel: state.bulkDiscountLevel, 119 bulkDiscountStatus: state.bulkDiscountStatus, 120 }); 121 122 const notify = (reason) => { 123 const snapshot = getSnapshot(); 124 listeners.forEach((listener) => { 125 try { 126 listener(snapshot, { reason }); 127 } catch (error) { 128 console.error('Price context subscriber failed:', error); 129 } 130 }); 131 return snapshot; 132 }; 133 134 const initializeVatSelection = (vatList) => { 135 const vatOptions = buildVatOptions(vatList); 136 const storedVat = readStoredVat(resolvedStorage); 137 const selectedVat = vatOptions.some((option) => option.value === storedVat) 138 ? storedVat 139 : vatOptions[0]?.value || null; 140 const optionsChanged = 141 vatOptions.length !== state.vatOptions.length || 142 vatOptions.some((option, index) => option.value !== state.vatOptions[index]?.value); 143 const selectionChanged = state.selectedVat !== selectedVat; 144 145 state.vatOptions = vatOptions; 146 state.selectedVat = selectedVat; 147 return optionsChanged || selectionChanged ? notify('vat-initialized') : getSnapshot(); 148 }; 149 150 const setSelectedVat = (value, { persist = true } = {}) => { 151 const normalized = normalizeSelectedVat(value); 152 if (normalized === null) { 153 return getSnapshot(); 154 } 155 if (state.vatOptions.length && !state.vatOptions.some((option) => option.value === normalized)) { 156 return getSnapshot(); 157 } 158 if (state.selectedVat === normalized) { 159 return getSnapshot(); 160 } 161 162 state.selectedVat = normalized; 163 if (persist) { 164 persistVat(resolvedStorage, normalized); 165 } 166 return notify('vat-changed'); 167 }; 168 169 const loadBulkDiscountLevel = ({ force = false } = {}) => { 170 if (!force && state.bulkDiscountStatus === 'success') { 171 return Promise.resolve(state.bulkDiscountLevel); 172 } 173 if (!force && bulkDiscountPending) { 174 return bulkDiscountPending; 175 } 176 177 const requestToken = bulkDiscountRequestToken + 1; 178 bulkDiscountRequestToken = requestToken; 179 state.bulkDiscountStatus = 'loading'; 180 181 const pending = Promise.resolve() 182 .then(() => getUser({ force })) 183 .then((user) => normalizeBulkDiscountLevel(user?.bulk_discount_level)) 184 .catch(() => 0)
185 .then((bulkDiscountLevel) => { 186 if (requestToken === bulkDiscountRequestToken) { 187 state.bulkDiscountLevel = bulkDiscountLevel; 188 state.bulkDiscountStatus = 'success'; 189 notify('bulk-discount-loaded'); 190 } 191 return bulkDiscountLevel; 192 }) 193 .finally(() => { 194 if (bulkDiscountPending === pending) { 195 bulkDiscountPending = null; 196 } 197 }); 198 199 bulkDiscountPending = pending; 200 return pending; 201 }; 202 203 const subscribe = (listener, { emitCurrent = false } = {}) => { 204 if (typeof listener !== 'function') { 205 return () => {}; 206 } 207 listeners.add(listener); 208 if (emitCurrent) { 209 listener(getSnapshot(), { reason: 'current' }); 210 } 211 return () => listeners.delete(listener); 212 }; 213 214 return Object.freeze({ 215 getSnapshot, 216 initializeVatSelection, 217 setSelectedVat, 218 loadBulkDiscountLevel, 219 subscribe, 220 }); 221}; 222 223const priceContext = createPriceContext(); 224 225const getPriceContextSnapshot = () => priceContext.getSnapshot(); 226const initializeVatSelection = (vatList) => priceContext.initializeVatSelection(vatList); 227const setSelectedVat = (value, options) => priceContext.setSelectedVat(value, options); 228const loadBulkDiscountLevel = (options) => priceContext.loadBulkDiscountLevel(options); 229const subscribePriceContext = (listener, options) => priceContext.subscribe(listener, options); 230 231export { 232 SELECTED_VAT_STORAGE_KEY, 233 buildVatOptions, 234 createPriceContext, 235 getPriceContextSnapshot, 236 initializeVatSelection, 237 loadBulkDiscountLevel, 238 normalizeSelectedVat, 239 setSelectedVat, 240 subscribePriceContext, 241};
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.