2025-07-22 17:01:12 +02:00
|
|
|
// src/api/services/TenantService.ts
|
|
|
|
|
import { apiClient } from '../base/apiClient';
|
|
|
|
|
import {
|
|
|
|
|
ApiResponse,
|
2025-07-23 07:26:04 +02:00
|
|
|
TenantInfo, // Assuming TenantInfo is equivalent to TenantResponse from backend
|
2025-07-22 17:01:12 +02:00
|
|
|
} from '../types/api';
|
|
|
|
|
|
|
|
|
|
export interface TenantCreate {
|
|
|
|
|
name: string;
|
|
|
|
|
email: string;
|
|
|
|
|
phone: string;
|
|
|
|
|
address: string;
|
|
|
|
|
latitude?: number;
|
|
|
|
|
longitude?: number;
|
|
|
|
|
business_type: 'individual_bakery' | 'central_workshop';
|
|
|
|
|
subscription_plan?: string;
|
|
|
|
|
settings?: Record<string, any>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TenantUpdate extends Partial<TenantCreate> {
|
|
|
|
|
is_active?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TenantSettings {
|
|
|
|
|
business_hours: {
|
|
|
|
|
monday: { open: string; close: string; closed: boolean };
|
|
|
|
|
tuesday: { open: string; close: string; closed: boolean };
|
|
|
|
|
wednesday: { open: string; close: string; closed: boolean };
|
|
|
|
|
thursday: { open: string; close: string; closed: boolean };
|
|
|
|
|
friday: { open: string; close: string; closed: boolean };
|
|
|
|
|
saturday: { open: string; close: string; closed: boolean };
|
|
|
|
|
sunday: { open: string; close: string; closed: boolean };
|
|
|
|
|
};
|
|
|
|
|
timezone: string;
|
|
|
|
|
currency: string;
|
|
|
|
|
language: string;
|
|
|
|
|
notification_preferences: {
|
|
|
|
|
email_enabled: boolean;
|
|
|
|
|
whatsapp_enabled: boolean;
|
|
|
|
|
forecast_alerts: boolean;
|
|
|
|
|
training_notifications: boolean;
|
|
|
|
|
weekly_reports: boolean;
|
|
|
|
|
};
|
|
|
|
|
forecast_preferences: {
|
|
|
|
|
default_forecast_days: number;
|
|
|
|
|
confidence_level: number;
|
|
|
|
|
include_weather: boolean;
|
|
|
|
|
include_traffic: boolean;
|
|
|
|
|
alert_thresholds: {
|
|
|
|
|
high_demand_increase: number;
|
|
|
|
|
low_demand_decrease: number;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
data_retention_days: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TenantStats {
|
|
|
|
|
total_users: number;
|
|
|
|
|
active_users: number;
|
|
|
|
|
total_sales_records: number;
|
|
|
|
|
total_forecasts: number;
|
|
|
|
|
total_notifications_sent: number;
|
|
|
|
|
storage_used_mb: number;
|
|
|
|
|
api_calls_this_month: number;
|
|
|
|
|
last_activity: string;
|
|
|
|
|
subscription_status: 'active' | 'inactive' | 'suspended';
|
|
|
|
|
subscription_expires: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface TenantUser {
|
|
|
|
|
id: string;
|
|
|
|
|
email: string;
|
|
|
|
|
full_name: string;
|
|
|
|
|
role: string;
|
|
|
|
|
is_active: boolean;
|
|
|
|
|
last_login: string | null;
|
|
|
|
|
created_at: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface InviteUser {
|
|
|
|
|
email: string;
|
|
|
|
|
role: 'admin' | 'manager' | 'user';
|
|
|
|
|
full_name?: string;
|
|
|
|
|
send_invitation_email?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-23 07:26:04 +02:00
|
|
|
// New interface for tenant member response based on backend
|
|
|
|
|
export interface TenantMemberResponse {
|
|
|
|
|
user_id: string;
|
|
|
|
|
tenant_id: string;
|
|
|
|
|
role: string;
|
|
|
|
|
// Add any other fields expected from the backend's TenantMemberResponse
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-22 17:01:12 +02:00
|
|
|
export class TenantService {
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Register a new bakery (tenant)
|
|
|
|
|
* Corresponds to POST /tenants/register
|
|
|
|
|
*/
|
|
|
|
|
async registerBakery(bakeryData: TenantCreate): Promise<TenantInfo> {
|
|
|
|
|
const response = await apiClient.post<ApiResponse<TenantInfo>>('/api/v1/tenants/register', bakeryData);
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a specific tenant by ID
|
|
|
|
|
* Corresponds to GET /tenants/{tenant_id}
|
|
|
|
|
*/
|
|
|
|
|
async getTenantById(tenantId: string): Promise<TenantInfo> {
|
|
|
|
|
const response = await apiClient.get<ApiResponse<TenantInfo>>(`/api/v1/tenants/${tenantId}`);
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update a specific tenant by ID
|
|
|
|
|
* Corresponds to PUT /tenants/{tenant_id}
|
|
|
|
|
*/
|
|
|
|
|
async updateTenant(tenantId: string, updates: TenantUpdate): Promise<TenantInfo> {
|
|
|
|
|
const response = await apiClient.put<ApiResponse<TenantInfo>>(`/api/v1/tenants/${tenantId}`, updates);
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get all tenants associated with a user
|
|
|
|
|
* Corresponds to GET /users/{user_id}/tenants
|
|
|
|
|
*/
|
|
|
|
|
async getUserTenants(userId: string): Promise<TenantInfo[]> {
|
|
|
|
|
const response = await apiClient.get<ApiResponse<TenantInfo[]>>(`/api/v1/users/${userId}/tenants`);
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a team member to a tenant
|
|
|
|
|
* Corresponds to POST /tenants/{tenant_id}/members
|
|
|
|
|
*/
|
|
|
|
|
async addTeamMember(tenantId: string, userId: string, role: string): Promise<TenantMemberResponse> {
|
|
|
|
|
const response = await apiClient.post<ApiResponse<TenantMemberResponse>>(
|
|
|
|
|
`/api/v1/tenants/${tenantId}/members`,
|
|
|
|
|
{ user_id: userId, role }
|
|
|
|
|
);
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Existing methods (kept for completeness, assuming they map to other backend endpoints not provided) ---
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get current tenant info (no direct backend mapping in provided file, but common)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async getCurrentTenant(): Promise<TenantInfo> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.get<ApiResponse<TenantInfo>>('/api/v1/tenants/current');
|
2025-07-22 17:01:12 +02:00
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Update current tenant (no direct backend mapping in provided file, but common)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async updateCurrentTenant(updates: TenantUpdate): Promise<TenantInfo> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.put<ApiResponse<TenantInfo>>('/api/v1/tenants/current', updates);
|
2025-07-22 17:01:12 +02:00
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Get tenant settings (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async getTenantSettings(): Promise<TenantSettings> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.get<ApiResponse<TenantSettings>>('/api/v1/tenants/settings');
|
2025-07-22 17:01:12 +02:00
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Update tenant settings (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async updateTenantSettings(settings: Partial<TenantSettings>): Promise<TenantSettings> {
|
|
|
|
|
const response = await apiClient.put<ApiResponse<TenantSettings>>(
|
2025-07-23 07:26:04 +02:00
|
|
|
'/api/v1/tenants/settings',
|
2025-07-22 17:01:12 +02:00
|
|
|
settings
|
|
|
|
|
);
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Get tenant statistics (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async getTenantStats(): Promise<TenantStats> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.get<ApiResponse<TenantStats>>('/api/v1/tenants/stats');
|
2025-07-22 17:01:12 +02:00
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Get tenant users (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async getTenantUsers(params?: {
|
|
|
|
|
role?: string;
|
|
|
|
|
active?: boolean;
|
|
|
|
|
page?: number;
|
|
|
|
|
limit?: number;
|
|
|
|
|
}): Promise<{
|
|
|
|
|
users: TenantUser[];
|
|
|
|
|
total: number;
|
|
|
|
|
page: number;
|
|
|
|
|
pages: number;
|
|
|
|
|
}> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.get<ApiResponse<any>>('/api/v1/tenants/users', { params });
|
2025-07-22 17:01:12 +02:00
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Invite user to tenant (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async inviteUser(invitation: InviteUser): Promise<{
|
|
|
|
|
invitation_id: string;
|
|
|
|
|
email: string;
|
|
|
|
|
role: string;
|
|
|
|
|
expires_at: string;
|
|
|
|
|
invitation_token: string;
|
|
|
|
|
}> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.post<ApiResponse<any>>('/api/v1/tenants/users/invite', invitation);
|
2025-07-22 17:01:12 +02:00
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Update user role (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async updateUserRole(userId: string, role: string): Promise<TenantUser> {
|
|
|
|
|
const response = await apiClient.patch<ApiResponse<TenantUser>>(
|
2025-07-23 07:26:04 +02:00
|
|
|
`/api/v1/tenants/users/${userId}`,
|
2025-07-22 17:01:12 +02:00
|
|
|
{ role }
|
|
|
|
|
);
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Deactivate user (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async deactivateUser(userId: string): Promise<TenantUser> {
|
|
|
|
|
const response = await apiClient.patch<ApiResponse<TenantUser>>(
|
2025-07-23 07:26:04 +02:00
|
|
|
`/api/v1/tenants/users/${userId}`,
|
2025-07-22 17:01:12 +02:00
|
|
|
{ is_active: false }
|
|
|
|
|
);
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Reactivate user (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async reactivateUser(userId: string): Promise<TenantUser> {
|
|
|
|
|
const response = await apiClient.patch<ApiResponse<TenantUser>>(
|
2025-07-23 07:26:04 +02:00
|
|
|
`/api/v1/tenants/users/${userId}`,
|
2025-07-22 17:01:12 +02:00
|
|
|
{ is_active: true }
|
|
|
|
|
);
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Remove user from tenant (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async removeUser(userId: string): Promise<void> {
|
2025-07-23 07:26:04 +02:00
|
|
|
await apiClient.delete(`/api/v1/tenants/users/${userId}`);
|
2025-07-22 17:01:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Get pending invitations (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async getPendingInvitations(): Promise<{
|
|
|
|
|
id: string;
|
|
|
|
|
email: string;
|
|
|
|
|
role: string;
|
|
|
|
|
invited_at: string;
|
|
|
|
|
expires_at: string;
|
|
|
|
|
invited_by: string;
|
|
|
|
|
}[]> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.get<ApiResponse<any>>('/api/v1/tenants/invitations');
|
2025-07-22 17:01:12 +02:00
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Cancel invitation (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async cancelInvitation(invitationId: string): Promise<void> {
|
2025-07-23 07:26:04 +02:00
|
|
|
await apiClient.delete(`/api/v1/tenants/invitations/${invitationId}`);
|
2025-07-22 17:01:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Resend invitation (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async resendInvitation(invitationId: string): Promise<void> {
|
2025-07-23 07:26:04 +02:00
|
|
|
await apiClient.post(`/api/v1/tenants/invitations/${invitationId}/resend`);
|
2025-07-22 17:01:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Get tenant activity log (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async getActivityLog(params?: {
|
|
|
|
|
userId?: string;
|
|
|
|
|
action?: string;
|
|
|
|
|
startDate?: string;
|
|
|
|
|
endDate?: string;
|
|
|
|
|
page?: number;
|
|
|
|
|
limit?: number;
|
|
|
|
|
}): Promise<{
|
|
|
|
|
activities: {
|
|
|
|
|
id: string;
|
|
|
|
|
user_id: string;
|
|
|
|
|
user_name: string;
|
|
|
|
|
action: string;
|
|
|
|
|
resource: string;
|
|
|
|
|
resource_id: string;
|
|
|
|
|
details?: Record<string, any>;
|
|
|
|
|
ip_address?: string;
|
|
|
|
|
user_agent?: string;
|
|
|
|
|
created_at: string;
|
|
|
|
|
}[];
|
|
|
|
|
total: number;
|
|
|
|
|
page: number;
|
|
|
|
|
pages: number;
|
|
|
|
|
}> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.get<ApiResponse<any>>('/api/v1/tenants/activity', { params });
|
2025-07-22 17:01:12 +02:00
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Get tenant billing info (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async getBillingInfo(): Promise<{
|
|
|
|
|
subscription_plan: string;
|
|
|
|
|
billing_cycle: 'monthly' | 'yearly';
|
|
|
|
|
next_billing_date: string;
|
|
|
|
|
amount: number;
|
|
|
|
|
currency: string;
|
|
|
|
|
payment_method: {
|
|
|
|
|
type: string;
|
|
|
|
|
last_four: string;
|
|
|
|
|
expires: string;
|
|
|
|
|
};
|
|
|
|
|
usage: {
|
|
|
|
|
api_calls: number;
|
|
|
|
|
storage_mb: number;
|
|
|
|
|
users: number;
|
|
|
|
|
limits: {
|
|
|
|
|
api_calls_per_month: number;
|
|
|
|
|
storage_mb: number;
|
|
|
|
|
max_users: number;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.get<ApiResponse<any>>('/api/v1/tenants/billing');
|
2025-07-22 17:01:12 +02:00
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Update billing info (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async updateBillingInfo(billingData: {
|
|
|
|
|
payment_method_token?: string;
|
|
|
|
|
billing_address?: {
|
|
|
|
|
street: string;
|
|
|
|
|
city: string;
|
|
|
|
|
state: string;
|
|
|
|
|
zip: string;
|
|
|
|
|
country: string;
|
|
|
|
|
};
|
|
|
|
|
}): Promise<void> {
|
2025-07-23 07:26:04 +02:00
|
|
|
await apiClient.put('/api/v1/tenants/billing', billingData);
|
2025-07-22 17:01:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Change subscription plan (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async changeSubscriptionPlan(
|
|
|
|
|
planId: string,
|
|
|
|
|
billingCycle: 'monthly' | 'yearly'
|
|
|
|
|
): Promise<{
|
|
|
|
|
subscription_id: string;
|
|
|
|
|
plan: string;
|
|
|
|
|
billing_cycle: string;
|
|
|
|
|
next_billing_date: string;
|
|
|
|
|
proration_amount?: number;
|
|
|
|
|
}> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.post<ApiResponse<any>>('/api/v1/tenants/subscription/change', {
|
2025-07-22 17:01:12 +02:00
|
|
|
plan_id: planId,
|
|
|
|
|
billing_cycle: billingCycle,
|
|
|
|
|
});
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Cancel subscription (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async cancelSubscription(cancelAt: 'immediately' | 'end_of_period'): Promise<{
|
|
|
|
|
cancelled_at: string;
|
|
|
|
|
will_cancel_at: string;
|
|
|
|
|
refund_amount?: number;
|
|
|
|
|
}> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.post<ApiResponse<any>>('/api/v1/tenants/subscription/cancel', {
|
2025-07-22 17:01:12 +02:00
|
|
|
cancel_at: cancelAt,
|
|
|
|
|
});
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Export tenant data (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async exportTenantData(dataTypes: string[], format: 'json' | 'csv'): Promise<Blob> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.post('/api/v1/tenants/export', {
|
2025-07-22 17:01:12 +02:00
|
|
|
data_types: dataTypes,
|
|
|
|
|
format,
|
|
|
|
|
responseType: 'blob',
|
|
|
|
|
});
|
|
|
|
|
return response as unknown as Blob;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-23 07:26:04 +02:00
|
|
|
* Delete tenant (GDPR compliance) (no direct backend mapping in provided file)
|
2025-07-22 17:01:12 +02:00
|
|
|
*/
|
|
|
|
|
async deleteTenant(confirmationToken: string): Promise<{
|
|
|
|
|
deletion_scheduled_at: string;
|
|
|
|
|
data_retention_until: string;
|
|
|
|
|
recovery_period_days: number;
|
|
|
|
|
}> {
|
2025-07-23 07:26:04 +02:00
|
|
|
const response = await apiClient.delete<ApiResponse<any>>('/api/v1/tenants/current', {
|
2025-07-22 17:01:12 +02:00
|
|
|
data: { confirmation_token: confirmationToken },
|
|
|
|
|
});
|
|
|
|
|
return response.data!;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const tenantService = new TenantService();
|