import mongoose, { Schema, Document } from 'mongoose' import { z } from 'zod' // Zod schema for validation export const BillingSchema = z.object({ billing_id: z.string().min(1, 'Billing ID is required'), service: z.string().min(1, 'Service name is required'), service_type: z.enum([ 'vps', 'kubernetes', 'developer_hire', 'vpn', 'hosting', 'storage', 'database', 'ai_service', 'custom', ]), amount: z.number().min(0, 'Amount must be non-negative'), currency: z.enum(['INR', 'USD']).default('INR'), user_email: z.string().email('Invalid email format'), silicon_id: z.string().min(1, 'Silicon ID is required'), user_id: z.string().min(1, 'User ID is required'), status: z.enum(['pending', 'active', 'completed', 'cancelled', 'failed', 'refunded']), payment_status: z.enum(['pending', 'paid', 'failed', 'refunded']).default('pending'), cycle: z.enum(['onetime', 'hourly', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly']), service_id: z.string().optional(), cluster_id: z.string().optional(), instance_id: z.string().optional(), service_name: z.string().optional(), service_config: z.record(z.string(), z.any()).optional(), billing_period_start: z.date().optional(), billing_period_end: z.date().optional(), next_billing_date: z.date().optional(), auto_renew: z.boolean().default(false), discount_applied: z.number().min(0).default(0), tax_amount: z.number().min(0).default(0), total_amount: z.number().min(0), transaction_id: z.string().optional(), invoice_id: z.string().optional(), remarks: z.string().optional(), metadata: z.record(z.string(), z.any()).optional(), service_status: z.number().min(0).max(2).default(0), // 0: inactive, 1: active, 2: suspended created_by: z.string().optional(), updated_by: z.string().optional(), }) export type BillingType = z.infer // Mongoose interface export interface IBilling extends Document { billing_id: string service: string service_type: | 'vps' | 'kubernetes' | 'developer_hire' | 'vpn' | 'hosting' | 'storage' | 'database' | 'ai_service' | 'custom' amount: number currency: 'INR' | 'USD' user_email: string silicon_id: string user_id: string status: 'pending' | 'active' | 'completed' | 'cancelled' | 'failed' | 'refunded' payment_status: 'pending' | 'paid' | 'failed' | 'refunded' cycle: 'onetime' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'quarterly' | 'yearly' service_id?: string cluster_id?: string instance_id?: string service_name?: string service_config?: Record billing_period_start?: Date billing_period_end?: Date next_billing_date?: Date auto_renew: boolean discount_applied: number tax_amount: number total_amount: number transaction_id?: string invoice_id?: string remarks?: string metadata?: Record service_status: number created_by?: string updated_by?: string createdAt: Date updatedAt: Date } const billingSchema: Schema = new Schema( { billing_id: { type: String, required: true, unique: true, index: true, }, service: { type: String, required: true, trim: true, }, service_type: { type: String, enum: [ 'vps', 'kubernetes', 'developer_hire', 'vpn', 'hosting', 'storage', 'database', 'ai_service', 'custom', ], required: true, }, amount: { type: Number, required: true, min: 0, }, currency: { type: String, enum: ['INR', 'USD'], default: 'INR', }, user_email: { type: String, required: true, lowercase: true, trim: true, index: true, }, silicon_id: { type: String, required: true, index: true, }, user_id: { type: String, required: true, index: true, }, status: { type: String, enum: ['pending', 'active', 'completed', 'cancelled', 'failed', 'refunded'], default: 'pending', }, payment_status: { type: String, enum: ['pending', 'paid', 'failed', 'refunded'], default: 'pending', }, cycle: { type: String, enum: ['onetime', 'hourly', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly'], required: true, }, service_id: { type: String, trim: true, }, cluster_id: { type: String, trim: true, }, instance_id: { type: String, trim: true, }, service_name: { type: String, trim: true, }, service_config: { type: mongoose.Schema.Types.Mixed, }, billing_period_start: { type: Date, }, billing_period_end: { type: Date, }, next_billing_date: { type: Date, }, auto_renew: { type: Boolean, default: false, }, discount_applied: { type: Number, min: 0, default: 0, }, tax_amount: { type: Number, min: 0, default: 0, }, total_amount: { type: Number, required: true, min: 0, }, transaction_id: { type: String, trim: true, }, invoice_id: { type: String, trim: true, }, remarks: { type: String, trim: true, }, metadata: { type: mongoose.Schema.Types.Mixed, }, service_status: { type: Number, min: 0, max: 2, default: 0, }, created_by: { type: String, trim: true, }, updated_by: { type: String, trim: true, }, }, { timestamps: true, } ) // Indexes for performance billingSchema.index({ user_email: 1, createdAt: -1 }) billingSchema.index({ silicon_id: 1, createdAt: -1 }) billingSchema.index({ user_id: 1, createdAt: -1 }) billingSchema.index({ service_type: 1, status: 1 }) billingSchema.index({ payment_status: 1 }) billingSchema.index({ next_billing_date: 1, auto_renew: 1 }) billingSchema.index({ service_id: 1 }) billingSchema.index({ cluster_id: 1 }) billingSchema.index({ instance_id: 1 }) // Pre-save middleware to calculate total_amount billingSchema.pre('save', function (next) { if ( this.isModified('amount') || this.isModified('tax_amount') || this.isModified('discount_applied') ) { const amount = Number(this.amount) || 0 const taxAmount = Number(this.tax_amount) || 0 const discountApplied = Number(this.discount_applied) || 0 this.total_amount = amount + taxAmount - discountApplied } next() }) // Transform method to format response billingSchema.methods.toJSON = function () { const billing = this.toObject() return billing } // Static methods for common queries billingSchema.statics.findByUser = function (userEmail: string, siliconId: string) { return this.find({ $or: [{ user_email: userEmail }, { silicon_id: siliconId }], }).sort({ createdAt: -1 }) } billingSchema.statics.findActiveServices = function (userEmail: string, siliconId: string) { return this.find({ $or: [{ user_email: userEmail }, { silicon_id: siliconId }], status: 'active', service_status: 1, }).sort({ createdAt: -1 }) } billingSchema.statics.findByServiceType = function ( serviceType: string, userEmail?: string, siliconId?: string ) { const query: any = { service_type: serviceType } if (userEmail || siliconId) { query.$or = [] if (userEmail) query.$or.push({ user_email: userEmail }) if (siliconId) query.$or.push({ silicon_id: siliconId }) } return this.find(query).sort({ createdAt: -1 }) } // Check if model already exists before compiling it export const Billing = (mongoose.models?.Billing as mongoose.Model) || mongoose.model('Billing', billingSchema)