Add paged orders API and infinite scroll, short order numbers, and align the guest invite card with the logged-in hero shell. Co-authored-by: Cursor <cursoragent@cursor.com>
200 lines
6.4 KiB
TypeScript
200 lines
6.4 KiB
TypeScript
/**
|
||
* Dev helper: seed >15 mock orders per OrderStatus so the Orders UI can be inspected.
|
||
* Run: npx tsx scripts/seed-mock-orders.ts
|
||
*/
|
||
import {
|
||
OrderChannel,
|
||
OrderStatus,
|
||
Prisma,
|
||
PrismaClient,
|
||
} from '@prisma/client'
|
||
|
||
const prisma = new PrismaClient()
|
||
|
||
const STATUSES: OrderStatus[] = [
|
||
OrderStatus.SUSPENDED,
|
||
OrderStatus.PLACED,
|
||
OrderStatus.ADMIN_APPROVED,
|
||
OrderStatus.SOURCING,
|
||
OrderStatus.PREPARING,
|
||
OrderStatus.DELIVERED_TO_AGENT,
|
||
OrderStatus.DELIVERED_TO_CUSTOMER,
|
||
OrderStatus.CANCELLED,
|
||
]
|
||
|
||
const PER_STATUS = 18
|
||
const TAG = 'MOCK-ORD'
|
||
|
||
function d(n: number | string) {
|
||
return new Prisma.Decimal(n)
|
||
}
|
||
|
||
function orderNumber(status: OrderStatus, i: number) {
|
||
// Unique, easy to spot/delete later
|
||
return `${TAG}-${status.slice(0, 4)}-${String(i).padStart(3, '0')}-${Date.now().toString(36)}`
|
||
}
|
||
|
||
async function main() {
|
||
const mobileOverride = process.env.MOCK_ORDERS_MOBILE?.trim()
|
||
|
||
const customer = mobileOverride
|
||
? await prisma.customer.findUnique({
|
||
where: { mobile: mobileOverride },
|
||
select: { id: true, mobile: true, fullName: true, referredByAgentId: true },
|
||
})
|
||
: await prisma.customer.findFirst({
|
||
orderBy: { updatedAt: 'desc' },
|
||
select: { id: true, mobile: true, fullName: true, referredByAgentId: true },
|
||
})
|
||
|
||
if (!customer) {
|
||
throw new Error(
|
||
mobileOverride
|
||
? `No customer with mobile=${mobileOverride}`
|
||
: 'No customers in DB — create/login a customer first',
|
||
)
|
||
}
|
||
|
||
const variants = await prisma.productVariant.findMany({
|
||
take: 30,
|
||
where: { isActive: true },
|
||
include: { product: { select: { title: true } } },
|
||
})
|
||
if (variants.length === 0) {
|
||
throw new Error('No product variants in DB — seed catalog first')
|
||
}
|
||
|
||
const branch = await prisma.branch.findFirst({ select: { id: true, name: true } })
|
||
|
||
console.log(
|
||
`Seeding ${PER_STATUS} orders × ${STATUSES.length} statuses for customer ${customer.mobile} (${customer.fullName}), ${variants.length} variants` +
|
||
(branch ? `, branch=${branch.name}` : ''),
|
||
)
|
||
|
||
let created = 0
|
||
|
||
for (const status of STATUSES) {
|
||
for (let i = 1; i <= PER_STATUS; i++) {
|
||
const variant = variants[i % variants.length]!
|
||
const qty = 1 + (i % 3)
|
||
const rash = Number(variant.rashWeightGrams ?? 1.25)
|
||
const wagePct = Number(variant.defaultWagePercent ?? 7)
|
||
const discountPct = 0
|
||
const computed = rash * (1 + (wagePct - discountPct) / 100)
|
||
const totalGold = computed * qty
|
||
const wageWeight = totalGold - rash * qty
|
||
const flatRial = i % 4 === 0 ? 150_000 * i : 0
|
||
const daysAgo = (STATUSES.indexOf(status) * PER_STATUS + i) % 40
|
||
const createdAt = new Date(Date.now() - daysAgo * 24 * 60 * 60 * 1000 - i * 60_000)
|
||
const deliveredAt =
|
||
status === OrderStatus.DELIVERED_TO_CUSTOMER
|
||
? new Date(createdAt.getTime() + 2 * 24 * 60 * 60 * 1000)
|
||
: null
|
||
|
||
// 1–2 line items so list/detail both look realistic
|
||
const second = variants[(i + 3) % variants.length]!
|
||
const useSecond = i % 3 === 0
|
||
|
||
const order = await prisma.order.create({
|
||
data: {
|
||
orderNumber: orderNumber(status, i),
|
||
customerId: customer.id,
|
||
branchId: branch?.id ?? null,
|
||
channel: i % 5 === 0 ? OrderChannel.WHOLESALE : OrderChannel.RETAIL,
|
||
status,
|
||
totalGoldWeightGrams: d(
|
||
(useSecond
|
||
? totalGold + Number(second.rashWeightGrams) * (1 + Number(second.defaultWagePercent) / 100)
|
||
: totalGold
|
||
).toFixed(4),
|
||
),
|
||
wageWeightGrams: d(Math.max(0, wageWeight).toFixed(4)),
|
||
flatRialCharge: d(flatRial),
|
||
isSuspendedOverCreditLimit: status === OrderStatus.SUSPENDED,
|
||
deliveredAt,
|
||
salesAgentId: customer.referredByAgentId ?? null,
|
||
createdAt,
|
||
updatedAt: createdAt,
|
||
items: {
|
||
create: [
|
||
{
|
||
productVariantId: variant.id,
|
||
quantity: qty,
|
||
rashWeightGramsSnapshot: d(rash.toFixed(4)),
|
||
wagePercentSnapshot: d(wagePct.toFixed(3)),
|
||
discountPercentSnapshot: d(discountPct.toFixed(3)),
|
||
computedWeightGrams: d(computed.toFixed(4)),
|
||
flatRialCharge: d(flatRial),
|
||
},
|
||
...(useSecond
|
||
? [
|
||
{
|
||
productVariantId: second.id,
|
||
quantity: 1,
|
||
rashWeightGramsSnapshot: d(Number(second.rashWeightGrams).toFixed(4)),
|
||
wagePercentSnapshot: d(Number(second.defaultWagePercent).toFixed(3)),
|
||
discountPercentSnapshot: d(0),
|
||
computedWeightGrams: d(
|
||
(
|
||
Number(second.rashWeightGrams) *
|
||
(1 + Number(second.defaultWagePercent) / 100)
|
||
).toFixed(4),
|
||
),
|
||
flatRialCharge: d(0),
|
||
},
|
||
]
|
||
: []),
|
||
],
|
||
},
|
||
statusEvents: {
|
||
create: [
|
||
{
|
||
fromStatus: null,
|
||
toStatus: OrderStatus.PLACED,
|
||
note: 'mock seed: placed',
|
||
createdAt,
|
||
},
|
||
...(status !== OrderStatus.PLACED
|
||
? [
|
||
{
|
||
fromStatus: OrderStatus.PLACED,
|
||
toStatus: status,
|
||
note: `mock seed: set to ${status}`,
|
||
createdAt: new Date(createdAt.getTime() + 60_000),
|
||
},
|
||
]
|
||
: []),
|
||
],
|
||
},
|
||
},
|
||
})
|
||
created++
|
||
if (i === 1) {
|
||
console.log(` ${status}: first=${order.orderNumber}`)
|
||
}
|
||
}
|
||
console.log(` ${status}: +${PER_STATUS}`)
|
||
}
|
||
|
||
const counts = await prisma.order.groupBy({
|
||
by: ['status'],
|
||
_count: { _all: true },
|
||
where: { orderNumber: { startsWith: TAG } },
|
||
})
|
||
console.log(`Done. Created ${created} mock orders (tag ${TAG}).`)
|
||
console.log(
|
||
'Mock counts:',
|
||
Object.fromEntries(counts.map((c) => [c.status, c._count._all])),
|
||
)
|
||
console.log(`Login as mobile ${customer.mobile} to see them in the app.`)
|
||
}
|
||
|
||
main()
|
||
.catch((e) => {
|
||
console.error(e)
|
||
process.exit(1)
|
||
})
|
||
.finally(async () => {
|
||
await prisma.$disconnect()
|
||
})
|