Files
tehran_taj/apps/user-app/src/stores/wageQuoteStore.ts
T
s.esmaeiliandClaude Opus 5.5 699657e483 feat(pricing): the server prices app orders with legacy's making-charge formula (MON-08)
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>
2026-10-08 11:26:55 +03:30

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 }
})