ai-wpa/models/billing.ts

312 lines
7.7 KiB
TypeScript

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<typeof BillingSchema>
// 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<string, any>
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<string, any>
service_status: number
created_by?: string
updated_by?: string
createdAt: Date
updatedAt: Date
}
const billingSchema: Schema = new Schema<IBilling>(
{
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<IBilling>) ||
mongoose.model<IBilling>('Billing', billingSchema)