Improve the frontend modals

This commit is contained in:
Urtzi Alfaro
2025-10-27 16:33:26 +01:00
parent 61376b7a9f
commit 858d985c92
143 changed files with 9289 additions and 2306 deletions

View File

@@ -8,11 +8,13 @@ import { useSalesAnalytics } from './sales';
import { useOrdersDashboard } from './orders';
import { inventoryService } from '../services/inventory';
import { getAlertAnalytics } from '../services/alert_analytics';
import { getSustainabilityWidgetData } from '../services/sustainability';
import { ApiError } from '../client/apiClient';
import type { InventoryDashboardSummary } from '../types/dashboard';
import type { AlertAnalytics } from '../services/alert_analytics';
import type { SalesAnalytics } from '../types/sales';
import type { OrdersDashboardSummary } from '../types/orders';
import type { SustainabilityWidgetData } from '../types/sustainability';
export interface DashboardStats {
// Alert metrics
@@ -39,6 +41,10 @@ export interface DashboardStats {
productsSoldToday: number;
productsSoldTrend: number;
// Sustainability metrics
wasteReductionPercentage?: number;
monthlySavingsEur?: number;
// Data freshness
lastUpdated: string;
}
@@ -48,6 +54,7 @@ interface AggregatedDashboardData {
orders?: OrdersDashboardSummary;
sales?: SalesAnalytics;
inventory?: InventoryDashboardSummary;
sustainability?: SustainabilityWidgetData;
}
// Query Keys
@@ -175,6 +182,10 @@ function aggregateDashboardStats(data: AggregatedDashboardData): DashboardStats
productsSoldToday: sales.productsSold,
productsSoldTrend: sales.productsTrend,
// Sustainability
wasteReductionPercentage: data.sustainability?.waste_reduction_percentage,
monthlySavingsEur: data.sustainability?.financial_savings_eur,
// Metadata
lastUpdated: new Date().toISOString(),
};
@@ -199,7 +210,7 @@ export const useDashboardStats = (
queryKey: dashboardKeys.stats(tenantId),
queryFn: async () => {
// Fetch all data in parallel
const [alertsData, ordersData, salesData, inventoryData] = await Promise.allSettled([
const [alertsData, ordersData, salesData, inventoryData, sustainabilityData] = await Promise.allSettled([
getAlertAnalytics(tenantId, 7),
// Note: OrdersService methods are static
import('../services/orders').then(({ OrdersService }) =>
@@ -210,6 +221,7 @@ export const useDashboardStats = (
salesService.getSalesAnalytics(tenantId, todayStr, todayStr)
),
inventoryService.getDashboardSummary(tenantId),
getSustainabilityWidgetData(tenantId, 30), // 30 days for monthly savings
]);
// Extract data or use undefined for failed requests
@@ -218,6 +230,7 @@ export const useDashboardStats = (
orders: ordersData.status === 'fulfilled' ? ordersData.value : undefined,
sales: salesData.status === 'fulfilled' ? salesData.value : undefined,
inventory: inventoryData.status === 'fulfilled' ? inventoryData.value : undefined,
sustainability: sustainabilityData.status === 'fulfilled' ? sustainabilityData.value : undefined,
};
// Log any failures for debugging
@@ -233,6 +246,9 @@ export const useDashboardStats = (
if (inventoryData.status === 'rejected') {
console.warn('[Dashboard] Failed to fetch inventory:', inventoryData.reason);
}
if (sustainabilityData.status === 'rejected') {
console.warn('[Dashboard] Failed to fetch sustainability:', sustainabilityData.reason);
}
return aggregateDashboardStats(aggregatedData);
},

View File

@@ -259,14 +259,12 @@ export const useCreateCustomer = (
return useMutation<CustomerResponse, ApiError, CustomerCreate>({
mutationFn: (customerData: CustomerCreate) => OrdersService.createCustomer(customerData),
onSuccess: (data, variables) => {
// Invalidate customers list for this tenant
// Invalidate all customer list queries for this tenant
// This will match any query with ['orders', 'customers', 'list', ...]
// refetchType: 'active' forces immediate refetch of mounted queries
queryClient.invalidateQueries({
queryKey: ordersKeys.customers(),
predicate: (query) => {
const queryKey = query.queryKey as string[];
return queryKey.includes('list') &&
JSON.stringify(queryKey).includes(variables.tenant_id);
},
refetchType: 'active',
});
// Add the new customer to cache
@@ -278,6 +276,7 @@ export const useCreateCustomer = (
// Invalidate dashboard (for customer metrics)
queryClient.invalidateQueries({
queryKey: ordersKeys.dashboard(variables.tenant_id),
refetchType: 'active',
});
},
...options,
@@ -298,14 +297,18 @@ export const useUpdateCustomer = (
data
);
// Invalidate customers list for this tenant
// Invalidate all customer list queries
// This will match any query with ['orders', 'customers', 'list', ...]
// refetchType: 'active' forces immediate refetch of mounted queries
queryClient.invalidateQueries({
queryKey: ordersKeys.customers(),
predicate: (query) => {
const queryKey = query.queryKey as string[];
return queryKey.includes('list') &&
JSON.stringify(queryKey).includes(variables.tenantId);
},
refetchType: 'active',
});
// Invalidate dashboard (for customer metrics)
queryClient.invalidateQueries({
queryKey: ordersKeys.dashboard(variables.tenantId),
refetchType: 'active',
});
},
...options,

View File

@@ -23,6 +23,7 @@ import type {
RecipeFeasibilityResponse,
RecipeStatisticsResponse,
RecipeCategoriesResponse,
RecipeDeletionSummary,
} from '../types/recipes';
// Query Keys Factory
@@ -225,6 +226,46 @@ export const useDeleteRecipe = (
});
};
/**
* Archive a recipe (soft delete)
*/
export const useArchiveRecipe = (
tenantId: string,
options?: UseMutationOptions<RecipeResponse, ApiError, string>
) => {
const queryClient = useQueryClient();
return useMutation<RecipeResponse, ApiError, string>({
mutationFn: (recipeId: string) => recipesService.archiveRecipe(tenantId, recipeId),
onSuccess: (data) => {
// Update individual recipe cache
queryClient.setQueryData(recipesKeys.detail(tenantId, data.id), data);
// Invalidate lists
queryClient.invalidateQueries({ queryKey: recipesKeys.lists(tenantId) });
// Invalidate statistics
queryClient.invalidateQueries({ queryKey: recipesKeys.statistics(tenantId) });
},
...options,
});
};
/**
* Get deletion summary for a recipe
*/
export const useRecipeDeletionSummary = (
tenantId: string,
recipeId: string,
options?: Omit<UseQueryOptions<RecipeDeletionSummary, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<RecipeDeletionSummary, ApiError>({
queryKey: [...recipesKeys.detail(tenantId, recipeId), 'deletion-summary'],
queryFn: () => recipesService.getRecipeDeletionSummary(tenantId, recipeId),
enabled: !!(tenantId && recipeId),
staleTime: 0, // Always fetch fresh data
...options,
});
};
/**
* Duplicate a recipe
*/

View File

@@ -14,6 +14,7 @@ import type {
SupplierApproval,
SupplierSearchParams,
SupplierStatistics,
SupplierDeletionSummary,
PurchaseOrderCreate,
PurchaseOrderUpdate,
PurchaseOrderResponse,
@@ -324,6 +325,41 @@ export const useUpdateSupplier = (
});
};
export const useApproveSupplier = (
options?: UseMutationOptions<
SupplierResponse,
ApiError,
{ tenantId: string; supplierId: string; approvalData: SupplierApproval }
>
) => {
const queryClient = useQueryClient();
return useMutation<
SupplierResponse,
ApiError,
{ tenantId: string; supplierId: string; approvalData: SupplierApproval }
>({
mutationFn: ({ tenantId, supplierId, approvalData }) =>
suppliersService.approveSupplier(tenantId, supplierId, approvalData),
onSuccess: (data, { tenantId, supplierId }) => {
// Update cache with new supplier status
queryClient.setQueryData(
suppliersKeys.suppliers.detail(tenantId, supplierId),
data
);
// Invalidate lists and statistics as approval changes counts
queryClient.invalidateQueries({
queryKey: suppliersKeys.suppliers.lists()
});
queryClient.invalidateQueries({
queryKey: suppliersKeys.suppliers.statistics(tenantId)
});
},
...options,
});
};
export const useDeleteSupplier = (
options?: UseMutationOptions<
{ message: string },
@@ -358,29 +394,28 @@ export const useDeleteSupplier = (
});
};
export const useApproveSupplier = (
export const useHardDeleteSupplier = (
options?: UseMutationOptions<
SupplierResponse,
SupplierDeletionSummary,
ApiError,
{ tenantId: string; supplierId: string; approval: SupplierApproval }
{ tenantId: string; supplierId: string }
>
) => {
const queryClient = useQueryClient();
return useMutation<
SupplierResponse,
SupplierDeletionSummary,
ApiError,
{ tenantId: string; supplierId: string; approval: SupplierApproval }
{ tenantId: string; supplierId: string }
>({
mutationFn: ({ tenantId, supplierId, approval }) =>
suppliersService.approveSupplier(tenantId, supplierId, approval),
onSuccess: (data, { tenantId, supplierId }) => {
// Update cache
queryClient.setQueryData(
suppliersKeys.suppliers.detail(tenantId, supplierId),
data
);
mutationFn: ({ tenantId, supplierId }) =>
suppliersService.hardDeleteSupplier(tenantId, supplierId),
onSuccess: (_, { tenantId, supplierId }) => {
// Remove from cache
queryClient.removeQueries({
queryKey: suppliersKeys.suppliers.detail(tenantId, supplierId)
});
// Invalidate lists and statistics
queryClient.invalidateQueries({
queryKey: suppliersKeys.suppliers.lists()

View File

@@ -12,6 +12,7 @@ export const userKeys = {
all: ['user'] as const,
current: () => [...userKeys.all, 'current'] as const,
detail: (id: string) => [...userKeys.all, 'detail', id] as const,
activity: (id: string) => [...userKeys.all, 'activity', id] as const,
admin: {
all: () => [...userKeys.all, 'admin'] as const,
list: () => [...userKeys.admin.all(), 'list'] as const,
@@ -31,6 +32,19 @@ export const useCurrentUser = (
});
};
export const useUserActivity = (
userId: string,
options?: Omit<UseQueryOptions<any, ApiError>, 'queryKey' | 'queryFn'>
) => {
return useQuery<any, ApiError>({
queryKey: userKeys.activity(userId),
queryFn: () => userService.getUserActivity(userId),
enabled: !!userId,
staleTime: 1 * 60 * 1000, // 1 minute for activity data
...options,
});
};
export const useAllUsers = (
options?: Omit<UseQueryOptions<UserResponse[], ApiError>, 'queryKey' | 'queryFn'>
) => {
@@ -109,4 +123,4 @@ export const useAdminDeleteUser = (
},
...options,
});
};
};

View File

@@ -77,16 +77,16 @@ export class AuthService {
}
// ===================================================================
// ATOMIC: User Profile
// Backend: services/auth/app/api/users.py
// User Profile (authenticated)
// Backend: services/auth/app/api/auth_operations.py
// ===================================================================
async getProfile(): Promise<UserResponse> {
return apiClient.get<UserResponse>('/users/me');
return apiClient.get<UserResponse>(`${this.baseUrl}/me`);
}
async updateProfile(updateData: UserUpdate): Promise<UserResponse> {
return apiClient.put<UserResponse>('/users/me', updateData);
return apiClient.put<UserResponse>(`${this.baseUrl}/me`, updateData);
}
// ===================================================================
@@ -104,6 +104,106 @@ export class AuthService {
});
}
// ===================================================================
// Account Management (self-service)
// Backend: services/auth/app/api/account_deletion.py
// ===================================================================
async deleteAccount(confirmEmail: string, password: string, reason?: string): Promise<{ message: string; deletion_date: string }> {
return apiClient.delete(`${this.baseUrl}/me/account`, {
data: {
confirm_email: confirmEmail,
password: password,
reason: reason || ''
}
});
}
async getAccountDeletionInfo(): Promise<any> {
return apiClient.get(`${this.baseUrl}/me/account/deletion-info`);
}
// ===================================================================
// GDPR Consent Management
// Backend: services/auth/app/api/consent.py
// ===================================================================
async recordConsent(consentData: {
terms_accepted: boolean;
privacy_accepted: boolean;
marketing_consent?: boolean;
analytics_consent?: boolean;
consent_method: string;
consent_version?: string;
}): Promise<any> {
return apiClient.post(`${this.baseUrl}/me/consent`, consentData);
}
async getCurrentConsent(): Promise<any> {
return apiClient.get(`${this.baseUrl}/me/consent/current`);
}
async getConsentHistory(): Promise<any[]> {
return apiClient.get(`${this.baseUrl}/me/consent/history`);
}
async updateConsent(consentData: {
terms_accepted: boolean;
privacy_accepted: boolean;
marketing_consent?: boolean;
analytics_consent?: boolean;
consent_method: string;
consent_version?: string;
}): Promise<any> {
return apiClient.put(`${this.baseUrl}/me/consent`, consentData);
}
async withdrawConsent(): Promise<{ message: string; withdrawn_count: number }> {
return apiClient.post(`${this.baseUrl}/me/consent/withdraw`);
}
// ===================================================================
// Data Export (GDPR)
// Backend: services/auth/app/api/data_export.py
// ===================================================================
async exportMyData(): Promise<any> {
return apiClient.get(`${this.baseUrl}/me/export`);
}
async getExportSummary(): Promise<any> {
return apiClient.get(`${this.baseUrl}/me/export/summary`);
}
// ===================================================================
// Onboarding Progress
// Backend: services/auth/app/api/onboarding_progress.py
// ===================================================================
async getOnboardingProgress(): Promise<any> {
return apiClient.get(`${this.baseUrl}/me/onboarding/progress`);
}
async updateOnboardingStep(stepName: string, completed: boolean, data?: any): Promise<any> {
return apiClient.put(`${this.baseUrl}/me/onboarding/step`, {
step_name: stepName,
completed: completed,
data: data
});
}
async getNextOnboardingStep(): Promise<{ step: string }> {
return apiClient.get(`${this.baseUrl}/me/onboarding/next-step`);
}
async canAccessOnboardingStep(stepName: string): Promise<{ can_access: boolean }> {
return apiClient.get(`${this.baseUrl}/me/onboarding/can-access/${stepName}`);
}
async completeOnboarding(): Promise<{ success: boolean; message: string }> {
return apiClient.post(`${this.baseUrl}/me/onboarding/complete`);
}
// ===================================================================
// Health Check
// ===================================================================

View File

@@ -82,9 +82,10 @@ export class OrdersService {
* Create a new customer order
* POST /tenants/{tenant_id}/orders
*/
static async createOrder(orderData: OrderCreate): Promise<OrderResponse> {
const { tenant_id, ...data } = orderData;
return apiClient.post<OrderResponse>(`/tenants/${tenant_id}/orders`, data);
static async createOrder(orderData: OrderCreate): Promise<OrderResponse> {
const { tenant_id } = orderData;
// Note: tenant_id is in both URL path and request body (backend schema requirement)
return apiClient.post<OrderResponse>(`/tenants/${tenant_id}/orders`, orderData);
}
/**
@@ -144,11 +145,11 @@ export class OrdersService {
/**
* Create a new customer
* POST /tenants/{tenant_id}/customers
* POST /tenants/{tenant_id}/orders/customers
*/
static async createCustomer(customerData: CustomerCreate): Promise<CustomerResponse> {
const { tenant_id, ...data } = customerData;
return apiClient.post<CustomerResponse>(`/tenants/${tenant_id}/customers`, data);
return apiClient.post<CustomerResponse>(`/tenants/${tenant_id}/orders/customers`, data);
}
/**
@@ -413,4 +414,4 @@ export class OrdersService {
}
export default OrdersService;
export default OrdersService;

View File

@@ -594,4 +594,4 @@ export class POSService {
// Export singleton instance
export const posService = new POSService();
export default posService;
export default posService;

View File

@@ -24,6 +24,7 @@ import type {
RecipeCategoriesResponse,
RecipeQualityConfiguration,
RecipeQualityConfigurationUpdate,
RecipeDeletionSummary,
} from '../types/recipes';
export class RecipesService {
@@ -94,6 +95,22 @@ export class RecipesService {
return apiClient.delete<{ message: string }>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}`);
}
/**
* Archive a recipe (soft delete by setting status to ARCHIVED)
* PATCH /tenants/{tenant_id}/recipes/{recipe_id}/archive
*/
async archiveRecipe(tenantId: string, recipeId: string): Promise<RecipeResponse> {
return apiClient.patch<RecipeResponse>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}/archive`);
}
/**
* Get deletion summary for a recipe
* GET /tenants/{tenant_id}/recipes/{recipe_id}/deletion-summary
*/
async getRecipeDeletionSummary(tenantId: string, recipeId: string): Promise<RecipeDeletionSummary> {
return apiClient.get<RecipeDeletionSummary>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}/deletion-summary`);
}
// ===================================================================
// ATOMIC: Quality Configuration CRUD
// Backend: services/recipes/app/api/recipe_quality_configs.py

View File

@@ -187,21 +187,48 @@ export class SubscriptionService {
/**
* Check if tenant can perform an action within quota limits
*/
async checkQuotaLimit(
async checkQuotaLimit(
tenantId: string,
quotaType: string,
requestedAmount?: number
): Promise<QuotaCheckResponse> {
const queryParams = new URLSearchParams();
if (requestedAmount !== undefined) {
queryParams.append('requested_amount', requestedAmount.toString());
// Map quotaType to the existing endpoints in tenant_operations.py
let endpoint: string;
switch (quotaType) {
case 'inventory_items':
endpoint = 'can-add-product';
break;
case 'users':
endpoint = 'can-add-user';
break;
case 'locations':
endpoint = 'can-add-location';
break;
default:
throw new Error(`Unsupported quota type: ${quotaType}`);
}
const url = queryParams.toString()
? `${this.baseUrl}/subscriptions/${tenantId}/quotas/${quotaType}/check?${queryParams.toString()}`
: `${this.baseUrl}/subscriptions/${tenantId}/quotas/${quotaType}/check`;
return apiClient.get<QuotaCheckResponse>(url);
const url = `${this.baseUrl}/subscriptions/${tenantId}/${endpoint}`;
// Get the response from the endpoint (returns different format than expected)
const response = await apiClient.get<{
can_add: boolean;
current_count?: number;
max_allowed?: number;
reason?: string;
message?: string;
}>(url);
// Map the response to QuotaCheckResponse format
return {
allowed: response.can_add,
current: response.current_count || 0,
limit: response.max_allowed || null,
remaining: response.max_allowed !== undefined && response.current_count !== undefined
? response.max_allowed - response.current_count
: null,
message: response.reason || response.message || ''
};
}
async validatePlanUpgrade(tenantId: string, planKey: string): Promise<PlanUpgradeValidation> {
@@ -348,4 +375,4 @@ export class SubscriptionService {
}
}
export const subscriptionService = new SubscriptionService();
export const subscriptionService = new SubscriptionService();

View File

@@ -22,6 +22,7 @@ import type {
SupplierApproval,
SupplierQueryParams,
SupplierStatistics,
SupplierDeletionSummary,
TopSuppliersResponse,
PurchaseOrderCreate,
PurchaseOrderUpdate,
@@ -53,7 +54,7 @@ class SuppliersService {
supplierData: SupplierCreate
): Promise<SupplierResponse> {
return apiClient.post<SupplierResponse>(
`${this.baseUrl}/${tenantId}/suppliers/suppliers`,
`${this.baseUrl}/${tenantId}/suppliers`,
supplierData
);
}
@@ -74,13 +75,13 @@ class SuppliersService {
const queryString = params.toString() ? `?${params.toString()}` : '';
return apiClient.get<PaginatedResponse<SupplierSummary>>(
`${this.baseUrl}/${tenantId}/suppliers/suppliers${queryString}`
`${this.baseUrl}/${tenantId}/suppliers${queryString}`
);
}
async getSupplier(tenantId: string, supplierId: string): Promise<SupplierResponse> {
return apiClient.get<SupplierResponse>(
`${this.baseUrl}/${tenantId}/suppliers/suppliers/${supplierId}`
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}`
);
}
@@ -90,7 +91,7 @@ class SuppliersService {
updateData: SupplierUpdate
): Promise<SupplierResponse> {
return apiClient.put<SupplierResponse>(
`${this.baseUrl}/${tenantId}/suppliers/suppliers/${supplierId}`,
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}`,
updateData
);
}
@@ -100,7 +101,16 @@ class SuppliersService {
supplierId: string
): Promise<{ message: string }> {
return apiClient.delete<{ message: string }>(
`${this.baseUrl}/${tenantId}/suppliers/suppliers/${supplierId}`
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}`
);
}
async hardDeleteSupplier(
tenantId: string,
supplierId: string
): Promise<SupplierDeletionSummary> {
return apiClient.delete<SupplierDeletionSummary>(
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}/hard`
);
}
@@ -113,7 +123,7 @@ class SuppliersService {
params.append('is_active', isActive.toString());
return apiClient.get<Array<{ inventory_product_id: string }>>(
`${this.baseUrl}/${tenantId}/suppliers/suppliers/${supplierId}/products?${params.toString()}`
`${this.baseUrl}/${tenantId}/suppliers/${supplierId}/products?${params.toString()}`
);
}

View File

@@ -32,6 +32,10 @@ export class UserService {
async getUserById(userId: string): Promise<UserResponse> {
return apiClient.get<UserResponse>(`${this.baseUrl}/admin/${userId}`);
}
async getUserActivity(userId: string): Promise<any> {
return apiClient.get<any>(`/auth/users/${userId}/activity`);
}
}
export const userService = new UserService();
export const userService = new UserService();

View File

@@ -92,7 +92,7 @@ export interface IngredientCreate {
package_size?: number | null;
// Pricing
average_cost?: number | null;
// Note: average_cost is calculated automatically from purchases (not accepted on create)
standard_cost?: number | null;
// Stock management

View File

@@ -528,6 +528,16 @@ export interface ProcurementRequirementResponse extends ProcurementRequirementBa
shelf_life_days?: number;
quality_specifications?: Record<string, any>;
procurement_notes?: string;
// Smart procurement calculation metadata
calculation_method?: string;
ai_suggested_quantity?: number;
adjusted_quantity?: number;
adjustment_reason?: string;
price_tier_applied?: Record<string, any>;
supplier_minimum_applied?: boolean;
storage_limit_applied?: boolean;
reorder_rule_applied?: boolean;
}
// Procurement Plan Types

View File

@@ -157,6 +157,10 @@ export interface RecipeQualityConfiguration {
stages: Record<string, ProcessStageQualityConfig>;
global_parameters?: Record<string, any>;
default_templates?: string[];
overall_quality_threshold?: number;
critical_stage_blocking?: boolean;
auto_create_quality_checks?: boolean;
quality_manager_approval_required?: boolean;
}
// Filter and query types

View File

@@ -360,6 +360,23 @@ export interface RecipeStatisticsResponse {
category_breakdown: Array<Record<string, any>>;
}
/**
* Summary of what will be deleted when hard-deleting a recipe
* Backend: RecipeDeletionSummary in schemas/recipes.py (lines 235-246)
*/
export interface RecipeDeletionSummary {
recipe_id: string;
recipe_name: string;
recipe_code: string;
production_batches_count: number;
recipe_ingredients_count: number;
dependent_recipes_count: number;
affected_orders_count: number;
last_used_date?: string | null;
can_delete: boolean;
warnings: string[]; // Default: []
}
/**
* Response for recipe categories list
* Backend: get_recipe_categories endpoint in api/recipe_operations.py (lines 168-186)

View File

@@ -15,6 +15,11 @@ export interface ProcurementSettings {
safety_stock_percentage: number;
po_approval_reminder_hours: number;
po_critical_escalation_hours: number;
use_reorder_rules: boolean;
economic_rounding: boolean;
respect_storage_limits: boolean;
use_supplier_minimums: boolean;
optimize_price_tiers: boolean;
}
export interface InventorySettings {

View File

@@ -288,6 +288,13 @@ export interface SupplierSummary {
phone: string | null;
city: string | null;
country: string | null;
// Business terms - Added for list view
payment_terms: PaymentTerms;
standard_lead_time: number;
minimum_order_amount: number | null;
// Performance metrics
quality_rating: number | null;
delivery_rating: number | null;
total_orders: number;
@@ -945,3 +952,15 @@ export interface ExportDataResponse {
status: 'generating' | 'ready' | 'expired' | 'failed';
error_message: string | null;
}
// ===== DELETION =====
export interface SupplierDeletionSummary {
supplier_name: string;
deleted_price_lists: number;
deleted_quality_reviews: number;
deleted_performance_metrics: number;
deleted_alerts: number;
deleted_scorecards: number;
deletion_timestamp: string;
}

View File

@@ -88,12 +88,22 @@ export interface GrantProgramEligibility {
eligible: boolean;
confidence: 'high' | 'medium' | 'low';
requirements_met: boolean;
funding_eur?: number;
deadline?: string;
program_type?: string;
sector_specific?: string;
}
export interface SpainCompliance {
law_1_2025: boolean;
circular_economy_strategy: boolean;
}
export interface GrantReadiness {
overall_readiness_percentage: number;
grant_programs: Record<string, GrantProgramEligibility>;
recommended_applications: string[];
spain_compliance?: SpainCompliance;
}
export interface SustainabilityMetrics {