Files
goodgo-platform/scripts/seed-plans.ts
Ho Ngoc Hai 51c6eed565 feat(seed): add standalone seed scripts for districts, plans, and market data
- scripts/seed-districts.ts: Vietnam district/ward data for HCM, Hanoi, Da Nang with sample properties
- scripts/seed-plans.ts: Subscription plans (FREE, AGENT_PRO, INVESTOR, ENTERPRISE)
- scripts/import-market-data.ts: Market index data across all 3 cities with realistic pricing
- All scripts are idempotent (upsert/ON CONFLICT DO NOTHING)
- Refactored prisma/seed.ts to import shared data from scripts, removing duplication

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-08 05:10:05 +07:00

130 lines
3.0 KiB
TypeScript

/**
* Seed subscription plans (FREE, AGENT_PRO, INVESTOR, ENTERPRISE).
*
* Usage: npx tsx scripts/seed-plans.ts
* Idempotent: uses upsert on PlanTier unique constraint.
*/
import { PrismaClient, PlanTier } from '@prisma/client';
const prisma = new PrismaClient();
export const PLANS = [
{
tier: PlanTier.FREE,
name: 'Miễn phí',
priceMonthlyVND: BigInt(0),
priceYearlyVND: BigInt(0),
maxListings: 3,
maxSavedSearches: 5,
features: {
basicSearch: true,
listingPost: true,
maxPhotos: 5,
analytics: false,
prioritySupport: false,
aiValuation: false,
featuredListing: false,
},
},
{
tier: PlanTier.AGENT_PRO,
name: 'Agent Pro',
priceMonthlyVND: BigInt(499_000),
priceYearlyVND: BigInt(4_990_000),
maxListings: 50,
maxSavedSearches: 30,
features: {
basicSearch: true,
listingPost: true,
maxPhotos: 30,
analytics: true,
prioritySupport: true,
aiValuation: true,
featuredListing: true,
leadManagement: true,
agentProfile: true,
},
},
{
tier: PlanTier.INVESTOR,
name: 'Investor',
priceMonthlyVND: BigInt(999_000),
priceYearlyVND: BigInt(9_990_000),
maxListings: 20,
maxSavedSearches: 100,
features: {
basicSearch: true,
listingPost: true,
maxPhotos: 15,
analytics: true,
prioritySupport: true,
aiValuation: true,
featuredListing: false,
marketReports: true,
priceAlerts: true,
portfolioTracking: true,
},
},
{
tier: PlanTier.ENTERPRISE,
name: 'Enterprise',
priceMonthlyVND: BigInt(4_990_000),
priceYearlyVND: BigInt(49_900_000),
maxListings: null,
maxSavedSearches: null,
features: {
basicSearch: true,
listingPost: true,
maxPhotos: 100,
analytics: true,
prioritySupport: true,
aiValuation: true,
featuredListing: true,
leadManagement: true,
agentProfile: true,
marketReports: true,
priceAlerts: true,
portfolioTracking: true,
apiAccess: true,
whiteLabel: true,
dedicatedSupport: true,
},
},
];
async function seedPlans() {
console.log('Seeding subscription plans...\n');
for (const plan of PLANS) {
const result = await prisma.plan.upsert({
where: { tier: plan.tier },
update: {
name: plan.name,
priceMonthlyVND: plan.priceMonthlyVND,
priceYearlyVND: plan.priceYearlyVND,
maxListings: plan.maxListings,
maxSavedSearches: plan.maxSavedSearches,
features: plan.features,
},
create: plan,
});
const monthly = Number(plan.priceMonthlyVND).toLocaleString('vi-VN');
console.log(` ${plan.tier.padEnd(12)} ${plan.name.padEnd(14)} ${monthly} VND/tháng`);
}
console.log(`\n${PLANS.length} plans seeded.`);
}
if (require.main === module) {
seedPlans()
.catch((e) => {
console.error('Seed plans failed:', e);
process.exit(1);
})
.finally(() => prisma.$disconnect());
}
export { seedPlans };