2026-10-08 11:26:55 +03:30
|
|
|
// 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>()
|
2026-10-10 16:56:30 +03:30
|
|
|
// Asked for but not answered: the server only returns variants that exist,
|
|
|
|
|
// and re-asking from a computed on every re-render looped forever.
|
|
|
|
|
const unanswered = new Set<string>()
|
|
|
|
|
// A failed call is retried, but not before this time.
|
|
|
|
|
let retryAfter = 0
|
2026-10-08 11:26:55 +03:30
|
|
|
let timer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
|
|
|
|
|
|
// A different customer has a different percent: never reuse another's quotes.
|
2026-10-12 11:48:59 +03:30
|
|
|
// That includes «ورود به حساب مشتری», which swaps WHICH customer we are
|
|
|
|
|
// quoting for without swapping the token — so a sales expert would otherwise
|
|
|
|
|
// see their own making charge on the customer's products, and the figure on
|
|
|
|
|
// screen would not be the one the order is placed at.
|
2026-10-08 11:26:55 +03:30
|
|
|
watch(
|
2026-10-12 11:48:59 +03:30
|
|
|
() => [session.token, session.actingAs?.id ?? null],
|
2026-10-08 11:26:55 +03:30
|
|
|
() => {
|
|
|
|
|
quotes.value = {}
|
|
|
|
|
pending.clear()
|
|
|
|
|
inFlight.clear()
|
2026-10-10 16:56:30 +03:30
|
|
|
unanswered.clear()
|
|
|
|
|
retryAfter = 0
|
2026-10-08 11:26:55 +03:30
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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)
|
2026-10-10 16:56:30 +03:30
|
|
|
const answered = new Set(rows.map((r) => r.variantId))
|
|
|
|
|
ids.forEach((id) => {
|
|
|
|
|
if (!answered.has(id)) unanswered.add(id)
|
|
|
|
|
})
|
|
|
|
|
if (rows.length) {
|
|
|
|
|
const next = { ...quotes.value }
|
|
|
|
|
for (const r of rows) next[r.variantId] = r
|
|
|
|
|
quotes.value = next
|
|
|
|
|
}
|
2026-10-08 11:26:55 +03:30
|
|
|
} catch (e) {
|
2026-10-10 16:56:30 +03:30
|
|
|
retryAfter = Date.now() + 30_000
|
2026-10-08 11:26:55 +03:30
|
|
|
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) {
|
2026-10-10 16:56:30 +03:30
|
|
|
if (!session.token || Date.now() < retryAfter) return
|
|
|
|
|
if (quotes.value[variantId] || unanswered.has(variantId) || pending.has(variantId) || inFlight.has(variantId)) return
|
2026-10-08 11:26:55 +03:30
|
|
|
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 }
|
|
|
|
|
})
|