Files
LogiFlow/shared/schema.ts
T
michaelschal df479800e3 Implement a new hardcoded permissions system for enhanced user access control
This commit replaces the previous dynamic, database-driven permissions system with a hardcoded, role-based access control (RBAC) structure. New files in `client/src/lib/permissions.ts` and `server/permissions.ts` define permissions per module and role, simplifying management and improving performance. Database tables related to roles, permissions, and user roles have been removed, and associated storage interface methods and routes have been deprecated or updated to reflect the new architecture. The `replit.md` file has also been updated to document these significant changes.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: a8a78c07-e900-425c-a577-5b4c5894379d
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/a8a78c07-e900-425c-a577-5b4c5894379d/7SnSmh4
2025-08-11 19:11:11 +00:00

551 lines
18 KiB
TypeScript

import {
pgTable,
text,
varchar,
timestamp,
jsonb,
index,
serial,
integer,
boolean,
date,
decimal,
primaryKey,
} from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
import { createInsertSchema } from "drizzle-zod";
import { z } from "zod";
// Session storage table - mandatory for Replit Auth
export const sessions = pgTable(
"sessions",
{
sid: varchar("sid").primaryKey(),
sess: jsonb("sess").notNull(),
expire: timestamp("expire").notNull(),
},
(table) => [index("IDX_session_expire").on(table.expire)],
);
// User storage table - supports both Replit Auth and local auth
export const users = pgTable("users", {
id: varchar("id").primaryKey().notNull(),
username: varchar("username").unique().notNull(), // For simple login
email: varchar("email").unique(),
name: varchar("name"), // Single name field for compatibility
firstName: varchar("first_name"),
lastName: varchar("last_name"),
profileImageUrl: varchar("profile_image_url"),
password: varchar("password"), // For local auth only
role: varchar("role").notNull().default("employee"), // Legacy role field for compatibility
passwordChanged: boolean("password_changed").default(false), // Track if default password was changed
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// Store/Group management
export const groups = pgTable("groups", {
id: serial("id").primaryKey(),
name: varchar("name").notNull(),
color: varchar("color").notNull(), // hex color code
// Configuration NocoDB pour vérification automatique des factures
nocodbConfigId: integer("nocodb_config_id"), // Référence vers la configuration NocoDB
nocodbTableId: varchar("nocodb_table_id"), // ID de la table dans NocoDB
nocodbTableName: varchar("nocodb_table_name"), // Nom de la table dans NocoDB
invoiceColumnName: varchar("invoice_column_name").default("Ref Facture"), // Nom de la colonne contenant les références de facture
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// User-Group assignments (many-to-many)
export const userGroups = pgTable("user_groups", {
userId: varchar("user_id").notNull(),
groupId: integer("group_id").notNull(),
createdAt: timestamp("created_at").defaultNow(),
});
// Suppliers
export const suppliers = pgTable("suppliers", {
id: serial("id").primaryKey(),
name: varchar("name").notNull(),
contact: varchar("contact"),
phone: varchar("phone"),
hasDlc: boolean("has_dlc").default(false), // Coche DLC pour la gestion DLC
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// Orders
export const orders = pgTable("orders", {
id: serial("id").primaryKey(),
supplierId: integer("supplier_id").notNull(),
groupId: integer("group_id").notNull(),
plannedDate: date("planned_date").notNull(),
quantity: integer("quantity"), // Optional - will be set when delivery is linked
unit: varchar("unit"), // Optional - 'palettes' or 'colis'
status: varchar("status").notNull().default("pending"), // pending, planned, delivered
notes: text("notes"),
createdBy: varchar("created_by").notNull(),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// Deliveries
export const deliveries = pgTable("deliveries", {
id: serial("id").primaryKey(),
orderId: integer("order_id"), // optional link to order
supplierId: integer("supplier_id").notNull(),
groupId: integer("group_id").notNull(),
scheduledDate: date("scheduled_date").notNull(),
deliveredDate: timestamp("delivered_date"),
quantity: integer("quantity").notNull(),
unit: varchar("unit").notNull(), // 'palettes' or 'colis'
status: varchar("status").notNull().default("planned"), // planned, delivered
notes: text("notes"),
// Champs pour le rapprochement BL/Factures
blNumber: varchar("bl_number"), // Numéro de Bon de Livraison
blAmount: decimal("bl_amount", { precision: 10, scale: 2 }), // Montant BL
invoiceReference: varchar("invoice_reference"), // Référence facture
invoiceAmount: decimal("invoice_amount", { precision: 10, scale: 2 }), // Montant facture
reconciled: boolean("reconciled").default(false), // Rapprochement effectué
validatedAt: timestamp("validated_at"), // Date de validation de la livraison
createdBy: varchar("created_by").notNull(),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// Publicities
export const publicities = pgTable("publicities", {
id: serial("id").primaryKey(),
pubNumber: varchar("pub_number").notNull().unique(),
designation: text("designation").notNull(),
startDate: date("start_date").notNull(),
endDate: date("end_date").notNull(),
year: integer("year").notNull(),
createdBy: varchar("created_by").notNull(),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// Publicity Participations
export const publicityParticipations = pgTable("publicity_participations", {
publicityId: integer("publicity_id").notNull(),
groupId: integer("group_id").notNull(),
createdAt: timestamp("created_at").defaultNow(),
}, (table) => ({
pk: primaryKey({ columns: [table.publicityId, table.groupId] })
}));
// NocoDB configuration globale (une seule instance NocoDB partagée)
export const nocodbConfig = pgTable("nocodb_config", {
id: serial("id").primaryKey(),
name: varchar("name").notNull(), // Nom de la configuration
baseUrl: varchar("base_url").notNull(), // URL de l'instance NocoDB
projectId: varchar("project_id").notNull(), // NocoDB Project ID
apiToken: varchar("api_token").notNull(), // Personal API Token
description: text("description"), // Description de la configuration
isActive: boolean("is_active").default(true), // Configuration active ou non
createdBy: varchar("created_by").notNull(),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// Customer Orders (Commandes Client)
export const customerOrders = pgTable("customer_orders", {
id: serial("id").primaryKey(),
// Information de commande
orderTaker: varchar("order_taker").notNull(), // Qui a pris la commande
customerName: varchar("customer_name").notNull(), // Nom du client
customerPhone: varchar("customer_phone").notNull(), // N° de téléphone
customerEmail: varchar("customer_email"), // Email du client (optionnel)
// Information produit
productDesignation: text("product_designation").notNull(), // Désignation du produit
productReference: varchar("product_reference"), // Référence
gencode: varchar("gencode").notNull(), // Code à barres (obligatoire)
quantity: integer("quantity").notNull().default(1), // Quantité commandée
supplierId: integer("supplier_id").notNull(), // Fournisseur
// Statuts
status: varchar("status").notNull().default("En attente de Commande"), // Statut du produit
// Options financières
deposit: decimal("deposit", { precision: 10, scale: 2 }).default("0.00"), // Acompte
isPromotionalPrice: boolean("is_promotional_price").default(false), // Prix publicité
// Communication client
customerNotified: boolean("customer_notified").default(false), // Client appelé
// Notes additionnelles
notes: text("notes"), // Notes sur la commande
// Métadonnées
groupId: integer("group_id").notNull(), // Magasin
createdBy: varchar("created_by").notNull(), // Créateur
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// DLC Products (Date Limite de Consommation)
export const dlcProducts = pgTable("dlc_products", {
id: serial("id").primaryKey(),
productName: varchar("product_name").notNull(), // Nom du produit
gencode: varchar("gencode"), // Code-barres/gencode EAN13 (optionnel)
supplierId: integer("supplier_id").notNull(), // ID du fournisseur
groupId: integer("group_id").notNull(), // ID du magasin/groupe
expiryDate: date("expiry_date").notNull(), // Date limite de consommation
dateType: varchar("date_type").notNull().default("dlc"), // Type: dlc, ddm, dluo
quantity: integer("quantity").notNull().default(1), // Quantité
unit: varchar("unit").notNull().default("unité"), // Unité (fixe par défaut)
location: varchar("location").notNull().default("Magasin"), // Emplacement (fixe par défaut)
alertThreshold: integer("alert_threshold").notNull().default(15), // Seuil d'alerte fixe à 15 jours
status: varchar("status").notNull().default("en_cours"), // Statut: en_cours, expires_soon, expires, valides
notes: text("notes"), // Notes (optionnel)
createdBy: varchar("created_by").notNull(), // Créateur
validatedBy: varchar("validated_by"), // Validé par (optionnel)
validatedAt: timestamp("validated_at"), // Date de validation (optionnel)
createdAt: timestamp("created_at").defaultNow(), // Date de création
updatedAt: timestamp("updated_at").defaultNow(), // Date de mise à jour
});
// Tasks table
export const tasks = pgTable("tasks", {
id: serial("id").primaryKey(),
title: varchar("title").notNull(), // Titre de la tâche (requis)
description: text("description"), // Description de la tâche (optionnel)
dueDate: timestamp("due_date"), // Date d'échéance (optionnel)
priority: varchar("priority").notNull().default("medium"), // low, medium, high
status: varchar("status").notNull().default("pending"), // pending, completed
assignedTo: text("assigned_to").notNull(), // Utilisateur responsable (champ libre)
createdBy: varchar("created_by").notNull(), // Utilisateur créateur
groupId: integer("group_id").notNull(), // Magasin/groupe associé
completedAt: timestamp("completed_at"), // Date de completion
completedBy: varchar("completed_by"), // Utilisateur qui a complété la tâche
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// Relations
export const usersRelations = relations(users, ({ many }) => ({
userGroups: many(userGroups),
createdOrders: many(orders),
createdDeliveries: many(deliveries),
createdPublicities: many(publicities),
createdCustomerOrders: many(customerOrders),
createdDlcProducts: many(dlcProducts),
createdTasks: many(tasks),
assignedTasks: many(tasks),
}));
export const groupsRelations = relations(groups, ({ one, many }) => ({
userGroups: many(userGroups),
orders: many(orders),
deliveries: many(deliveries),
publicityParticipations: many(publicityParticipations),
customerOrders: many(customerOrders),
dlcProducts: many(dlcProducts),
tasks: many(tasks),
nocodbConfig: one(nocodbConfig, {
fields: [groups.nocodbConfigId],
references: [nocodbConfig.id],
}),
}));
export const userGroupsRelations = relations(userGroups, ({ one }) => ({
user: one(users, {
fields: [userGroups.userId],
references: [users.id],
}),
group: one(groups, {
fields: [userGroups.groupId],
references: [groups.id],
}),
}));
export const suppliersRelations = relations(suppliers, ({ many }) => ({
orders: many(orders),
deliveries: many(deliveries),
dlcProducts: many(dlcProducts),
}));
export const ordersRelations = relations(orders, ({ one, many }) => ({
supplier: one(suppliers, {
fields: [orders.supplierId],
references: [suppliers.id],
}),
group: one(groups, {
fields: [orders.groupId],
references: [groups.id],
}),
creator: one(users, {
fields: [orders.createdBy],
references: [users.id],
}),
deliveries: many(deliveries),
}));
export const deliveriesRelations = relations(deliveries, ({ one }) => ({
order: one(orders, {
fields: [deliveries.orderId],
references: [orders.id],
}),
supplier: one(suppliers, {
fields: [deliveries.supplierId],
references: [suppliers.id],
}),
group: one(groups, {
fields: [deliveries.groupId],
references: [groups.id],
}),
creator: one(users, {
fields: [deliveries.createdBy],
references: [users.id],
}),
}));
export const publicitiesRelations = relations(publicities, ({ one, many }) => ({
creator: one(users, {
fields: [publicities.createdBy],
references: [users.id],
}),
participations: many(publicityParticipations),
}));
export const publicityParticipationsRelations = relations(publicityParticipations, ({ one }) => ({
publicity: one(publicities, {
fields: [publicityParticipations.publicityId],
references: [publicities.id],
}),
group: one(groups, {
fields: [publicityParticipations.groupId],
references: [groups.id],
}),
}));
export const customerOrdersRelations = relations(customerOrders, ({ one }) => ({
group: one(groups, {
fields: [customerOrders.groupId],
references: [groups.id],
}),
creator: one(users, {
fields: [customerOrders.createdBy],
references: [users.id],
}),
supplier: one(suppliers, {
fields: [customerOrders.supplierId],
references: [suppliers.id],
}),
}));
export const dlcProductsRelations = relations(dlcProducts, ({ one }) => ({
supplier: one(suppliers, {
fields: [dlcProducts.supplierId],
references: [suppliers.id],
}),
group: one(groups, {
fields: [dlcProducts.groupId],
references: [groups.id],
}),
creator: one(users, {
fields: [dlcProducts.createdBy],
references: [users.id],
}),
validator: one(users, {
fields: [dlcProducts.validatedBy],
references: [users.id],
}),
}));
export const tasksRelations = relations(tasks, ({ one }) => ({
assignedUser: one(users, {
fields: [tasks.assignedTo],
references: [users.id],
}),
creator: one(users, {
fields: [tasks.createdBy],
references: [users.id],
}),
group: one(groups, {
fields: [tasks.groupId],
references: [groups.id],
}),
}));
// Zod schemas
export const insertUserSchema = createInsertSchema(users).pick({
id: true,
username: true,
email: true,
firstName: true,
lastName: true,
name: true,
profileImageUrl: true,
password: true,
role: true,
passwordChanged: true,
}).extend({
email: z.union([z.string().email(), z.literal("")]).optional(),
});
export const insertGroupSchema = createInsertSchema(groups).omit({
id: true,
createdAt: true,
updatedAt: true,
});
export const insertSupplierSchema = createInsertSchema(suppliers).omit({
id: true,
createdAt: true,
updatedAt: true,
});
export const insertOrderSchema = createInsertSchema(orders).omit({
id: true,
createdAt: true,
updatedAt: true,
});
export const insertDeliverySchema = createInsertSchema(deliveries).omit({
id: true,
createdAt: true,
updatedAt: true,
});
export const insertUserGroupSchema = createInsertSchema(userGroups).omit({
createdAt: true,
});
export const insertPublicitySchema = createInsertSchema(publicities).omit({
id: true,
createdAt: true,
updatedAt: true,
});
export const insertPublicityParticipationSchema = createInsertSchema(publicityParticipations).omit({
createdAt: true,
});
export const insertNocodbConfigSchema = createInsertSchema(nocodbConfig).omit({
id: true,
createdAt: true,
updatedAt: true,
});
export const insertCustomerOrderSchema = createInsertSchema(customerOrders).omit({
id: true,
createdAt: true,
updatedAt: true,
});
export const insertDlcProductSchema = createInsertSchema(dlcProducts).omit({
id: true,
createdAt: true,
updatedAt: true,
});
// Frontend-compatible schema with dlcDate instead of expiryDate
export const insertDlcProductFrontendSchema = insertDlcProductSchema
.omit({ expiryDate: true })
.extend({ dlcDate: z.coerce.date() });
export const insertTaskSchema = createInsertSchema(tasks).omit({
id: true,
createdAt: true,
updatedAt: true,
completedAt: true,
completedBy: true,
}).extend({
dueDate: z.coerce.date().optional().nullable(), // Convertit automatiquement les chaînes en Date
});
// Types
export type UpsertUser = z.infer<typeof insertUserSchema>;
export type User = typeof users.$inferSelect;
export type Group = typeof groups.$inferSelect;
export type InsertGroup = z.infer<typeof insertGroupSchema>;
export type Supplier = typeof suppliers.$inferSelect;
export type InsertSupplier = z.infer<typeof insertSupplierSchema>;
export type Order = typeof orders.$inferSelect;
export type InsertOrder = z.infer<typeof insertOrderSchema>;
export type Delivery = typeof deliveries.$inferSelect;
export type InsertDelivery = z.infer<typeof insertDeliverySchema>;
export type UserGroup = typeof userGroups.$inferSelect;
export type InsertUserGroup = z.infer<typeof insertUserGroupSchema>;
export type Task = typeof tasks.$inferSelect;
export type InsertTask = z.infer<typeof insertTaskSchema>;
// Extended types with relations
export type OrderWithRelations = Order & {
supplier: Supplier;
group: Group;
creator: User;
deliveries?: Delivery[];
};
export type DeliveryWithRelations = Delivery & {
supplier: Supplier;
group: Group;
creator: User;
order?: Order | null;
};
export type UserWithGroups = User & {
userGroups: (UserGroup & { group: Group })[];
};
export type Publicity = typeof publicities.$inferSelect;
export type InsertPublicity = z.infer<typeof insertPublicitySchema>;
export type PublicityParticipation = typeof publicityParticipations.$inferSelect;
export type InsertPublicityParticipation = z.infer<typeof insertPublicityParticipationSchema>;
export type PublicityWithRelations = Publicity & {
creator: User;
participations: (PublicityParticipation & { group: Group })[];
};
export type NocodbConfig = typeof nocodbConfig.$inferSelect;
export type InsertNocodbConfig = z.infer<typeof insertNocodbConfigSchema>;
export type CustomerOrder = typeof customerOrders.$inferSelect;
export type InsertCustomerOrder = z.infer<typeof insertCustomerOrderSchema>;
export type CustomerOrderWithRelations = CustomerOrder & {
group: Group;
creator: User;
supplier: Supplier;
};
export type DlcProduct = typeof dlcProducts.$inferSelect;
export type InsertDlcProduct = z.infer<typeof insertDlcProductSchema>;
// Frontend-compatible types with dlcDate field
export type DlcProductFrontend = Omit<DlcProduct, 'expiryDate'> & {
dlcDate: Date;
};
export type InsertDlcProductFrontend = Omit<InsertDlcProduct, 'expiryDate'> & {
dlcDate: Date;
};
export type DlcProductWithRelations = DlcProductFrontend & {
supplier: Supplier;
group: Group;
creator: User;
validator?: User | null;
};