260 lines
7.1 KiB
TypeScript
260 lines
7.1 KiB
TypeScript
/**
|
|
* 📦 Admin Packages Store
|
|
*
|
|
* Pinia store for managing SubscriptionTier packages.
|
|
* Mirrors the backend admin_packages.py API endpoints.
|
|
*
|
|
* Endpoints:
|
|
* GET /admin/packages/ — List all packages
|
|
* POST /admin/packages/ — Create a new package
|
|
* PATCH /admin/packages/{id} — Update a package
|
|
* DELETE /admin/packages/{id} — Soft-delete (deactivate) a package
|
|
*/
|
|
|
|
import { defineStore } from 'pinia'
|
|
import { ref } from 'vue'
|
|
import api from '../api/axios'
|
|
|
|
// ── TypeScript Interfaces (mirroring backend Pydantic schemas) ───────────────
|
|
|
|
export interface PricingModel {
|
|
monthly_price: number
|
|
yearly_price: number
|
|
currency: string
|
|
credit_price?: number | null
|
|
}
|
|
|
|
export interface PricingZoneModel {
|
|
monthly_price: number
|
|
yearly_price: number
|
|
currency: string
|
|
credit_price?: number | null
|
|
}
|
|
|
|
export interface MarketingModel {
|
|
subtitle?: string | null
|
|
badge?: string | null
|
|
highlight_color?: string | null
|
|
}
|
|
|
|
export interface AllowancesModel {
|
|
max_vehicles: number
|
|
max_garages: number
|
|
monthly_free_credits: number
|
|
}
|
|
|
|
export interface LifecycleModel {
|
|
is_public: boolean
|
|
available_until?: string | null
|
|
}
|
|
|
|
export interface AffiliateModel {
|
|
commission_rate_percent: number
|
|
referral_bonus_credits: number
|
|
}
|
|
|
|
export interface SubscriptionRulesModel {
|
|
type: 'private' | 'corporate'
|
|
display_name?: string | null
|
|
pricing: PricingModel
|
|
pricing_zones?: Record<string, PricingZoneModel> | null
|
|
allowances: AllowancesModel
|
|
entitlements: string[]
|
|
affiliate: AffiliateModel
|
|
lifecycle?: LifecycleModel | null
|
|
marketing?: MarketingModel | null
|
|
}
|
|
|
|
export interface SubscriptionTierItem {
|
|
id: number
|
|
name: string
|
|
rules: SubscriptionRulesModel
|
|
is_custom: boolean
|
|
}
|
|
|
|
export interface SubscriptionTierListResponse {
|
|
total: number
|
|
tiers: SubscriptionTierItem[]
|
|
}
|
|
|
|
export interface SubscriptionTierCreatePayload {
|
|
name: string
|
|
rules: SubscriptionRulesModel
|
|
is_custom?: boolean
|
|
}
|
|
|
|
export interface SubscriptionTierUpdatePayload {
|
|
name?: string
|
|
rules?: SubscriptionRulesModel
|
|
is_custom?: boolean
|
|
}
|
|
|
|
export interface DeletePackageResponse {
|
|
status: string
|
|
message: string
|
|
tier_id: number
|
|
is_public: boolean
|
|
}
|
|
|
|
// ── Available Entitlements (service codes) ───────────────────────────────────
|
|
|
|
export const AVAILABLE_ENTITLEMENTS: string[] = [
|
|
'SRV_DATA_EXPORT',
|
|
'SRV_AI_UPLOAD',
|
|
'SRV_ACCOUNTING_SYNC',
|
|
'SRV_API_ACCESS',
|
|
'SRV_MULTI_USER',
|
|
'SRV_PRIORITY_SUPPORT',
|
|
'SRV_ADVANCED_ANALYTICS',
|
|
'SRV_CUSTOM_REPORTS',
|
|
'SRV_GARAGE_SHARING',
|
|
'SRV_SERVICE_HISTORY',
|
|
'SRV_FUEL_MANAGEMENT',
|
|
'SRV_MAINTENANCE_ALERTS',
|
|
'SRV_ODOMETER_TRACKING',
|
|
'SRV_DOCUMENT_STORAGE',
|
|
'SRV_INSURANCE_INTEGRATION',
|
|
]
|
|
|
|
// ── Store ────────────────────────────────────────────────────────────────────
|
|
|
|
export const useAdminPackagesStore = defineStore('adminPackages', () => {
|
|
// ── State ────────────────────────────────────────────────────────────────
|
|
const tiers = ref<SubscriptionTierItem[]>([])
|
|
const total = ref(0)
|
|
const isLoading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
|
|
// ── Actions ──────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Fetch all subscription packages from GET /admin/packages/.
|
|
*/
|
|
async function fetchPackages(params: {
|
|
skip?: number
|
|
limit?: number
|
|
include_hidden?: boolean
|
|
type_filter?: string
|
|
date_from?: string
|
|
date_until?: string
|
|
} = {}) {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
const queryParams: Record<string, any> = {
|
|
skip: params.skip ?? 0,
|
|
limit: params.limit ?? 100,
|
|
}
|
|
if (params.include_hidden) {
|
|
queryParams.include_hidden = true
|
|
}
|
|
if (params.type_filter) {
|
|
queryParams.type_filter = params.type_filter
|
|
}
|
|
if (params.date_from) {
|
|
queryParams.date_from = params.date_from
|
|
}
|
|
if (params.date_until) {
|
|
queryParams.date_until = params.date_until
|
|
}
|
|
|
|
const res = await api.get('/admin/packages', { params: queryParams })
|
|
const data = res.data as SubscriptionTierListResponse
|
|
console.log('[AdminPackages] API response:', data)
|
|
console.log('[AdminPackages] tiers array:', data.tiers)
|
|
console.log('[AdminPackages] tiers length:', data.tiers?.length)
|
|
tiers.value = data.tiers
|
|
total.value = data.total
|
|
return data
|
|
} catch (err: any) {
|
|
error.value = err.response?.data?.detail || 'Failed to fetch packages'
|
|
throw err
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new subscription package via POST /admin/packages/.
|
|
* NOTE: Do NOT send the `id` field — PostgreSQL auto-increments it.
|
|
*/
|
|
async function createPackage(payload: SubscriptionTierCreatePayload): Promise<SubscriptionTierItem> {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
const res = await api.post('/admin/packages', payload)
|
|
const created = res.data as SubscriptionTierItem
|
|
// Refresh the list
|
|
await fetchPackages({ include_hidden: true })
|
|
return created
|
|
} catch (err: any) {
|
|
error.value = err.response?.data?.detail || 'Failed to create package'
|
|
throw err
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update an existing package via PATCH /admin/packages/{id}.
|
|
*/
|
|
async function updatePackage(
|
|
tierId: number,
|
|
payload: SubscriptionTierUpdatePayload,
|
|
): Promise<SubscriptionTierItem> {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
const res = await api.patch(`/admin/packages/${tierId}`, payload)
|
|
const updated = res.data as SubscriptionTierItem
|
|
// Refresh the list
|
|
await fetchPackages({ include_hidden: true })
|
|
return updated
|
|
} catch (err: any) {
|
|
error.value = err.response?.data?.detail || 'Failed to update package'
|
|
throw err
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Soft-delete (deactivate) a package via DELETE /admin/packages/{id}.
|
|
* Sets lifecycle.is_public = false.
|
|
*/
|
|
async function deletePackage(tierId: number): Promise<DeletePackageResponse> {
|
|
isLoading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
const res = await api.delete(`/admin/packages/${tierId}`)
|
|
const result = res.data as DeletePackageResponse
|
|
// Refresh the list
|
|
await fetchPackages({ include_hidden: true })
|
|
return result
|
|
} catch (err: any) {
|
|
error.value = err.response?.data?.detail || 'Failed to delete package'
|
|
throw err
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
return {
|
|
// State
|
|
tiers,
|
|
total,
|
|
isLoading,
|
|
error,
|
|
|
|
// Actions
|
|
fetchPackages,
|
|
createPackage,
|
|
updatePackage,
|
|
deletePackage,
|
|
}
|
|
})
|