Legacy: final % = variant % + customer % − the largest active promotion on the product, its category or its brand; rial charge 0. The apps sent the variant's own % only, so the new app undercharged by the customer's percent (usually 3) and ignored promotions, and the server trusted whatever the app sent. - pricing/wage-formula.ts: one formula, legacy by default, with coefficients, promotion pick, constant, floor/cap and rounding so the admin screen can change it; validated - an app order is priced by it; the app's wage, discount and rial charge are ignored; an operator's manual numbers are kept - POST customer/me/wage-quote; both apps show that figure - admin/pricing/wage-formula (config.wage): read, preview, approve with the sample it was checked on, last 20 versions kept; the setting is kept off the generic const routes Owner approval: 2026-10-08. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
2.1 KiB
TypeScript
65 lines
2.1 KiB
TypeScript
// The making charge («اجرت») the server will charge this customer for each
|
|
// variant — fetched from /customer/me/wage-quote, which runs the same formula
|
|
// that prices the order (MON-08, owner ruling 2026-10-08). The app shows this
|
|
// figure, never one it computes itself, so what is shown is what is charged.
|
|
//
|
|
// Requests are batched: many cards asking at once become one call.
|
|
import { defineStore } from 'pinia'
|
|
import { ref, watch } from 'vue'
|
|
import { api, type WageQuote } from '../api/client'
|
|
import { useSessionStore } from './session'
|
|
|
|
export const useWageQuoteStore = defineStore('wageQuotes', () => {
|
|
const session = useSessionStore()
|
|
const quotes = ref<Record<string, WageQuote>>({})
|
|
const pending = new Set<string>()
|
|
const inFlight = new Set<string>()
|
|
let timer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
// A different customer has a different percent: never reuse another's quotes.
|
|
watch(
|
|
() => session.token,
|
|
() => {
|
|
quotes.value = {}
|
|
pending.clear()
|
|
inFlight.clear()
|
|
},
|
|
)
|
|
|
|
async function flush() {
|
|
timer = null
|
|
const ids = [...pending].slice(0, 200)
|
|
ids.forEach((id) => {
|
|
pending.delete(id)
|
|
inFlight.add(id)
|
|
})
|
|
if (pending.size) timer = setTimeout(flush, 0)
|
|
if (!ids.length) return
|
|
try {
|
|
const rows = await api.getWageQuotes(ids)
|
|
const next = { ...quotes.value }
|
|
for (const r of rows) next[r.variantId] = r
|
|
quotes.value = next
|
|
} catch (e) {
|
|
console.warn('wage quote failed', e)
|
|
} finally {
|
|
ids.forEach((id) => inFlight.delete(id))
|
|
}
|
|
}
|
|
|
|
/** Asks for a variant's quote (idempotent; safe to call from a computed). */
|
|
function request(variantId: string) {
|
|
if (!session.token || quotes.value[variantId] || pending.has(variantId) || inFlight.has(variantId)) return
|
|
pending.add(variantId)
|
|
if (!timer) timer = setTimeout(flush, 30)
|
|
}
|
|
|
|
/** The charged percent, or null while it is still loading. */
|
|
function finalPercent(variantId: string): number | null {
|
|
const q = quotes.value[variantId]
|
|
return q ? parseFloat(q.finalPercent) : null
|
|
}
|
|
|
|
return { quotes, request, finalPercent }
|
|
})
|