initial commit
This commit is contained in:
311
models/billing.ts
Normal file
311
models/billing.ts
Normal file
@@ -0,0 +1,311 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user