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