Merge pull request #3 from R0m1k3/1.1

1.1
This commit is contained in:
LogiFlow authored and GitHub committed 2025-08-12 12:18:26 +02:00
commit dce2e6296b
7 files changed
+1611 -954

No files matched your search

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+108 -18
View File
@@ -20,7 +20,8 @@ import {
Package,
Truck,
CheckCircle,
XCircle
XCircle,
Clock
} from "lucide-react";
import type { Supplier } from "@shared/schema";
@@ -37,7 +38,10 @@ export default function Suppliers() {
name: "",
contact: "",
phone: "",
email: "",
address: "",
hasDlc: false,
automaticReconciliation: false,
});
const { data: suppliers = [], isLoading } = useQuery<Supplier[]>({
@@ -66,7 +70,15 @@ export default function Suppliers() {
});
queryClient.invalidateQueries({ queryKey: ['/api/suppliers'] });
setShowCreateModal(false);
setFormData({ name: "", contact: "", phone: "", hasDlc: false });
setFormData({
name: "",
contact: "",
phone: "",
email: "",
address: "",
hasDlc: false,
automaticReconciliation: false
});
},
onError: (error) => {
if (isUnauthorizedError(error)) {
@@ -137,7 +149,15 @@ export default function Suppliers() {
setShowEditModal(false);
setSelectedSupplier(null);
setFormData({ name: "", contact: "", phone: "", hasDlc: false });
setFormData({
name: "",
contact: "",
phone: "",
email: "",
address: "",
hasDlc: false,
automaticReconciliation: false
});
},
onError: (error, variables, context) => {
// Rollback optimiste en cas d'erreur
@@ -214,7 +234,15 @@ export default function Suppliers() {
};
const handleCreate = () => {
setFormData({ name: "", contact: "", phone: "", hasDlc: false });
setFormData({
name: "",
contact: "",
phone: "",
email: "",
address: "",
hasDlc: false,
automaticReconciliation: false
});
setShowCreateModal(true);
};
@@ -224,7 +252,10 @@ export default function Suppliers() {
name: supplier.name,
contact: supplier.contact || "",
phone: supplier.phone || "",
email: supplier.email || "",
address: supplier.address || "",
hasDlc: supplier.hasDlc || false,
automaticReconciliation: supplier.automaticReconciliation || false,
});
setShowEditModal(true);
};
@@ -400,19 +431,30 @@ export default function Suppliers() {
{supplier.phone}
</div>
)}
<div className="flex items-center text-sm">
{supplier.hasDlc ? (
<div className="flex items-center text-green-600">
<CheckCircle className="w-4 h-4 mr-2" />
DLC activé
</div>
) : (
<div className="flex items-center text-gray-400">
<XCircle className="w-4 h-4 mr-2" />
DLC désactivé
</div>
)}
<div className="flex items-center justify-between text-sm">
<div className="flex items-center space-x-2">
{supplier.hasDlc && (
<div className="flex items-center text-green-600 bg-green-50 px-2 py-1 rounded-full">
<Clock className="w-3 h-3 mr-1" />
<span className="text-xs font-medium">DLC</span>
</div>
)}
{supplier.automaticReconciliation && (
<div className="flex items-center text-blue-600 bg-blue-50 px-2 py-1 rounded-full">
<CheckCircle className="w-3 h-3 mr-1" />
<span className="text-xs font-medium">Auto-rapprochement</span>
</div>
)}
</div>
</div>
{!supplier.hasDlc && !supplier.automaticReconciliation && (
<div className="text-xs text-gray-500 mt-2">
Aucune option activée
</div>
)}
{/* Statistics section follows */}
</div>
<div className="border-t pt-4">
@@ -453,7 +495,15 @@ export default function Suppliers() {
setShowCreateModal(false);
setShowEditModal(false);
setSelectedSupplier(null);
setFormData({ name: "", contact: "", phone: "", hasDlc: false });
setFormData({
name: "",
contact: "",
phone: "",
email: "",
address: "",
hasDlc: false,
automaticReconciliation: false
});
}}>
<DialogContent className="sm:max-w-md" aria-describedby="supplier-modal-description">
<DialogHeader>
@@ -497,6 +547,27 @@ export default function Suppliers() {
/>
</div>
<div>
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => handleChange('email', e.target.value)}
placeholder="Adresse email"
/>
</div>
<div>
<Label htmlFor="address">Adresse</Label>
<Input
id="address"
value={formData.address}
onChange={(e) => handleChange('address', e.target.value)}
placeholder="Adresse complète"
/>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="hasDlc"
@@ -508,6 +579,17 @@ export default function Suppliers() {
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="automaticReconciliation"
checked={formData.automaticReconciliation}
onCheckedChange={(checked) => handleChange('automaticReconciliation', checked === true)}
/>
<Label htmlFor="automaticReconciliation" className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
Rapprochement automatique BL/Factures
</Label>
</div>
<div className="flex items-center space-x-3 pt-4">
<Button
type="button"
@@ -516,7 +598,15 @@ export default function Suppliers() {
setShowCreateModal(false);
setShowEditModal(false);
setSelectedSupplier(null);
setFormData({ name: "", contact: "", phone: "", hasDlc: false });
setFormData({
name: "",
contact: "",
phone: "",
email: "",
address: "",
hasDlc: false,
automaticReconciliation: false
});
}}
>
Annuler
+2
View File
@@ -30,6 +30,7 @@ Preferred communication style: Simple, everyday language.
### Core Business Entities
- **Management**: Orders, Deliveries, Customer Orders, Suppliers, Publicities, DLC Products (limited shelf life), Tasks.
- **Automatic Reconciliation**: Dual-mode BL/Invoice reconciliation system with automatic validation for designated suppliers.
### Data Flow
- **Request Flow**: Client requests via React Query, processed by Express middleware for authentication/security, then by route handlers using Drizzle ORM for database operations.
@@ -41,6 +42,7 @@ Preferred communication style: Simple, everyday language.
- **Multi-Store Management**: Global store context, role-based access control (admin, manager, employee, directeur), and data isolation.
- **Universal Pagination**: Reusable client-side pagination component across all main tabular data pages.
- **Reconciliation Module**: For balancing and tracking financial discrepancies.
- **Automatic Reconciliation System**: Dual-mode BL/Invoice reconciliation with automatic validation for suppliers in automatic mode. Auto-validates deliveries when status = "delivered" AND BL number exists.
- **Permission System**: Granular, hardcoded permissions (54 permissions across 12 categories, assigned to 4 roles) for improved performance and maintenance.
- **User Management**: Comprehensive features including user deletion with ownership transfer, consistent name and email field handling, and robust password hashing.
- **Calendar Synchronization**: Proper display of delivery dates and automatic synchronization of order statuses.
+25
View File
@@ -702,6 +702,31 @@ export async function registerRoutes(app: Express): Promise<Server> {
console.error(`❌ Auto-sync failed for order #${updatedDelivery.orderId}:`, error);
}
}
// AUTO-VALIDATION RAPPROCHEMENT AUTOMATIQUE : Si fournisseur en mode automatique, livraison delivered + BL → auto-valider
if (data.status === 'delivered' || data.blNumber) {
try {
// Récupérer le fournisseur pour vérifier le mode automatique
const supplier = await storage.getSupplier(updatedDelivery.supplierId);
if (supplier?.automaticReconciliation &&
updatedDelivery.status === 'delivered' &&
updatedDelivery.blNumber) {
console.log(`🤖 Auto-reconciliation: Delivery #${id} from automatic supplier ${supplier.name}, auto-validating...`);
// Auto-valider le rapprochement
await storage.updateDelivery(id, {
reconciled: true,
validatedAt: new Date()
});
console.log(`✅ Auto-reconciliation: Delivery #${id} automatically validated for supplier ${supplier.name}`);
}
} catch (error) {
console.error(`❌ Auto-reconciliation failed for delivery #${id}:`, error);
}
}
res.json(updatedDelivery);
} catch (error) {
+5
View File
@@ -470,6 +470,11 @@ export class DatabaseStorage implements IStorage {
return await db.select().from(suppliers).orderBy(suppliers.name);
}
async getSupplier(id: number): Promise<Supplier | undefined> {
const [supplier] = await db.select().from(suppliers).where(eq(suppliers.id, id));
return supplier;
}
async createSupplier(supplier: InsertSupplier): Promise<Supplier> {
const [newSupplier] = await db.insert(suppliers).values(supplier).returning();
return newSupplier;
+67
View File
@@ -74,7 +74,10 @@ export const suppliers = pgTable("suppliers", {
name: varchar("name").notNull(),
contact: varchar("contact"),
phone: varchar("phone"),
email: varchar("email"),
address: text("address"),
hasDlc: boolean("has_dlc").default(false), // Coche DLC pour la gestion DLC
automaticReconciliation: boolean("automatic_reconciliation").default(false), // Rapprochement automatique BL/Factures
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
@@ -470,6 +473,70 @@ export const insertTaskSchema = createInsertSchema(tasks).omit({
dueDate: z.coerce.date().optional().nullable(), // Convertit automatiquement les chaînes en Date
});
// 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 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 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 UserWithGroups = User & {
groups: Group[];
};
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;
};
// Types
export type UpsertUser = z.infer<typeof insertUserSchema>;
export type User = typeof users.$inferSelect;