Files
LogiFlow/shared/schema.ts
T
michaelschal 9fd0a9d26c Add customer order retrieval and task relation fetching
Implement `getCustomerOrders` and `getCustomerOrder` in `DatabaseStorage` to fetch customer orders with associated supplier and group data. Also, add `TaskWithRelations` type and update imports for `NocodbConfig` and `InvoiceVerificationCache`.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 7c468936-2a88-4686-ac0a-84921a45222d
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/1957c339-2757-4d1f-8e92-e9f71a1ce58e/7c468936-2a88-4686-ac0a-84921a45222d/Xw5SMyq
2025-08-13 12:35:22 +00:00

590 lines
21 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").default("#1976D2"), // hex color code
// Configuration NocoDB pour vérification automatique des factures
nocodbConfigId: integer("nocodb_config_id"), // Référence vers la configuration NocoDB globale
nocodbTableName: varchar("nocodb_table_name"), // Nom de la table spécifique au magasin
// Mapping des colonnes par magasin dans leur table NocoDB
invoiceColumnName: varchar("invoice_column_name"), // Nom de la colonne facture dans leur table
nocodbBlColumnName: varchar("nocodb_bl_column_name"), // Nom de la colonne BL dans leur table
nocodbAmountColumnName: varchar("nocodb_amount_column_name"), // Nom de la colonne montant dans leur table
nocodbSupplierColumnName: varchar("nocodb_supplier_column_name"), // Nom de la colonne fournisseur dans leur table
webhookUrl: varchar("webhook_url", { length: 500 }), // URL de webhook pour notifications par magasin
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 - Schema exact correspondant à la production
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(),
automaticReconciliation: boolean("automatic_reconciliation").default(false), // Rapprochement automatique BL/Factures
});
// 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] })
}));
// Configuration NocoDB globale pour la vérification des factures
export const nocodbConfig = pgTable("nocodb_config", {
id: serial("id").primaryKey(),
name: varchar("name").notNull(),
baseUrl: varchar("base_url").notNull(),
projectId: varchar("project_id").notNull(),
apiToken: varchar("api_token").notNull(),
description: text("description"),
isActive: boolean("is_active").default(true),
createdBy: varchar("created_by"),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// Cache de vérification des factures
export const invoiceVerificationCache = pgTable("invoice_verification_cache", {
id: serial("id").primaryKey(),
cacheKey: varchar("cache_key", { length: 255 }).notNull(),
groupId: integer("group_id").notNull(),
invoiceReference: varchar("invoice_reference", { length: 255 }).notNull(),
supplierName: varchar("supplier_name", { length: 255 }),
exists: boolean("exists").notNull(),
matchType: varchar("match_type", { length: 50 }).notNull(),
errorMessage: text("error_message"),
cacheHit: boolean("cache_hit").default(false),
apiCallTime: integer("api_call_time"),
expiresAt: timestamp("expires_at").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),
}));
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 }) => ({
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 insertCustomerOrderSchema = createInsertSchema(customerOrders).omit({
id: true,
createdAt: true,
updatedAt: true,
});
// Frontend-compatible schema with different field names
export const insertCustomerOrderFrontendSchema = z.object({
customerName: z.string().min(1, "Customer name is required"),
contactNumber: z.string().min(1, "Contact number is required"), // Maps to customerPhone
productName: z.string().min(1, "Product name is required"), // Maps to productDesignation
productDescription: z.string().optional(), // Optional description
quantity: z.coerce.number().int().positive().default(1),
groupId: z.coerce.number().int().positive(),
isPickup: z.boolean().default(false),
notes: z.string().optional(),
// Optional fields with defaults
orderTaker: z.string().optional(), // Will be set to creator name if not provided
gencode: z.string().optional().default(""), // Will be empty if not provided
supplierId: z.coerce.number().int().positive().optional().default(1), // Default supplier ID
deposit: z.coerce.number().optional().default(0),
isPromotionalPrice: z.boolean().default(false),
customerEmail: z.string().email().optional().or(z.literal("")),
productReference: z.string().optional(),
});
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
});
export const insertNocodbConfigSchema = createInsertSchema(nocodbConfig).omit({
id: true,
createdAt: true,
updatedAt: true,
});
export const insertInvoiceVerificationCacheSchema = createInsertSchema(invoiceVerificationCache).omit({
id: true,
createdAt: true,
updatedAt: true,
});
export type InsertNocodbConfig = z.infer<typeof insertNocodbConfigSchema>;
export type InsertInvoiceVerificationCache = z.infer<typeof insertInvoiceVerificationCacheSchema>;
// Types
export type User = typeof users.$inferSelect;
export type UpsertUser = z.infer<typeof insertUserSchema>;
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 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 CustomerOrder = typeof customerOrders.$inferSelect;
export type InsertCustomerOrder = z.infer<typeof insertCustomerOrderSchema>;
export type DlcProduct = typeof dlcProducts.$inferSelect;
export type InsertDlcProduct = z.infer<typeof insertDlcProductSchema>;
export type DlcProductFrontend = Omit<DlcProduct, 'expiryDate'> & { dlcDate: Date };
export type InsertDlcProductFrontend = z.infer<typeof insertDlcProductFrontendSchema>;
export type Task = typeof tasks.$inferSelect;
export type InsertTask = z.infer<typeof insertTaskSchema>;
// Complex types with relations
export type OrderWithRelations = Order & {
supplier: Supplier;
group: Group;
creator: User;
};
export type DeliveryWithRelations = Delivery & {
supplier: Supplier;
group: Group;
creator: User;
order?: Order;
};
export type PublicityWithRelations = Publicity & {
creator: User;
participations: (PublicityParticipation & { group: Group })[];
};
export type CustomerOrderWithRelations = CustomerOrder & {
supplier: Supplier;
group: Group;
creator: User;
};
export type DlcProductWithRelations = DlcProduct & {
supplier: Supplier;
group: Group;
creator: User;
validator?: User;
};
export type UserWithGroups = User & {
userGroups: (UserGroup & { group: Group })[];
};
export type NocodbConfig = typeof nocodbConfig.$inferSelect;
export type InsertNocodbConfig = z.infer<typeof insertNocodbConfigSchema>;
export type InvoiceVerificationCache = typeof invoiceVerificationCache.$inferSelect;
export type InsertInvoiceVerificationCache = z.infer<typeof insertInvoiceVerificationCacheSchema>;
// Task with relations type
export type TaskWithRelations = Task & {
creator: User;
group: Group;
};