feat: initial Spring Boot API - modules Auth, Articles, Tiers, Achats, Ventes, Production, Stock, Dashboard
- Architecture n-tiers : Controller → Service → Repository → Model - Sécurité JWT complète (Spring Security 6 + JJWT 0.12) - 6 rôles : PDG, Vente, Achat, Production, Magasinier, RH - Entités JPA : User, Role, Article, Client, Fournisseur, PurchaseOrder, SalesOrder, DeliveryNote, ProductionOrder, BomLine, StockMovement - Services métier : StockService, PurchaseOrderService, SalesOrderService, ProductionOrderService - DataInitializer : création des rôles + admin par défaut au démarrage - Docker Compose : Spring Boot + MySQL 8 PFE Ali Guennari — SUARL Rayhan
This commit is contained in:
parent
07b7b133fe
commit
b53fcf0ab9
|
|
@ -1,45 +1,41 @@
|
||||||
# ---> Java
|
# Java
|
||||||
# Compiled class file
|
|
||||||
*.class
|
*.class
|
||||||
|
|
||||||
# Log file
|
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# BlueJ files
|
|
||||||
*.ctxt
|
|
||||||
|
|
||||||
# Mobile Tools for Java (J2ME)
|
|
||||||
.mtj.tmp/
|
|
||||||
|
|
||||||
# Package Files #
|
|
||||||
*.jar
|
*.jar
|
||||||
*.war
|
*.war
|
||||||
*.nar
|
*.nar
|
||||||
*.ear
|
*.ear
|
||||||
*.zip
|
|
||||||
*.tar.gz
|
|
||||||
*.rar
|
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
|
||||||
hs_err_pid*
|
hs_err_pid*
|
||||||
replay_pid*
|
replay_pid*
|
||||||
|
|
||||||
# ---> Maven
|
# Maven
|
||||||
|
backend/target/
|
||||||
target/
|
target/
|
||||||
pom.xml.tag
|
pom.xml.tag
|
||||||
pom.xml.releaseBackup
|
pom.xml.releaseBackup
|
||||||
pom.xml.versionsBackup
|
pom.xml.versionsBackup
|
||||||
pom.xml.next
|
|
||||||
release.properties
|
|
||||||
dependency-reduced-pom.xml
|
|
||||||
buildNumber.properties
|
|
||||||
.mvn/timing.properties
|
|
||||||
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
|
|
||||||
.mvn/wrapper/maven-wrapper.jar
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
|
||||||
# Eclipse m2e generated files
|
# IDE
|
||||||
# Eclipse Core
|
.idea/
|
||||||
|
*.iml
|
||||||
|
.vscode/
|
||||||
.project
|
.project
|
||||||
# JDT-specific (Eclipse Java Development Tools)
|
|
||||||
.classpath
|
.classpath
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
*.env.local
|
||||||
|
|
||||||
|
# Flutter
|
||||||
|
frontend/.dart_tool/
|
||||||
|
frontend/.flutter-plugins
|
||||||
|
frontend/.flutter-plugins-dependencies
|
||||||
|
frontend/build/
|
||||||
|
|
||||||
|
# Docs sensibles
|
||||||
|
memoire de fin d'etude.docx
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
# Suivi du Projet ERP SUARL Rayhan
|
||||||
|
**PFE — Ali Guennari**
|
||||||
|
**Coach & Développeur : Claude (Nabil Derouiche)**
|
||||||
|
**Dernière mise à jour : 19 Avril 2026**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## État Global du Projet
|
||||||
|
|
||||||
|
| Phase | Description | Statut |
|
||||||
|
|-------|-------------|--------|
|
||||||
|
| 1 | Analyse & Stratégie | ✅ Terminé |
|
||||||
|
| 2 | Modélisation UML | ✅ Terminé (par Ali) |
|
||||||
|
| 3 | Backend Spring Boot API | 🔄 En cours |
|
||||||
|
| 4 | Frontend Flutter | ⏳ À faire |
|
||||||
|
| 5 | Tests & Validation | ⏳ À faire |
|
||||||
|
| 6 | Déploiement Production | ⏳ À faire |
|
||||||
|
| 7 | Rapport de PFE | 🔄 En cours |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dépôt Git
|
||||||
|
|
||||||
|
- **URL** : https://gitea.bolbol.tn/bolbol/rayhan-erp
|
||||||
|
- **Branche principale** : `main`
|
||||||
|
- **Organisation** : Spring Boot (backend/) + Flutter (frontend/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3 — Backend Spring Boot API
|
||||||
|
|
||||||
|
### Architecture Technique
|
||||||
|
- **Framework** : Spring Boot 3.x (Java 17)
|
||||||
|
- **Sécurité** : Spring Security + JWT (JJWT 0.12.x)
|
||||||
|
- **Base de données** : MySQL 8 (JPA/Hibernate)
|
||||||
|
- **Build** : Maven
|
||||||
|
|
||||||
|
### Modules API — Détail
|
||||||
|
|
||||||
|
| Module | Endpoints | Statut |
|
||||||
|
|--------|-----------|--------|
|
||||||
|
| Authentification & Sécurité | `POST /api/auth/signin`, `POST /api/auth/signup` | ✅ Codé |
|
||||||
|
| Gestion des Utilisateurs | `GET/PUT /api/users` | ✅ Codé |
|
||||||
|
| Articles (Référentiel) | `CRUD /api/articles` | ✅ Codé |
|
||||||
|
| Clients & Fournisseurs | `CRUD /api/clients`, `/api/fournisseurs` | ✅ Codé |
|
||||||
|
| Gestion des Stocks | `GET /api/stock`, `POST /api/inventory/adjust` | ✅ Codé |
|
||||||
|
| Cycle d'Achat | `POST /api/purchase-orders`, `/api/goods-receipts` | ✅ Codé |
|
||||||
|
| Cycle de Vente | `POST /api/sales-orders`, `/api/delivery-notes` | ✅ Codé |
|
||||||
|
| Cycle de Production (BOM + OF) | `GET/POST /api/bom`, `/api/production-orders` | ✅ Codé |
|
||||||
|
| Tableau de Bord (KPIs) | `GET /api/dashboard` | ✅ Codé |
|
||||||
|
| Facturation | `POST /api/invoices` | ⏳ À faire |
|
||||||
|
| Paie & RH | `POST /api/payroll` | ⏳ À faire |
|
||||||
|
|
||||||
|
### Rôles Utilisateurs
|
||||||
|
|
||||||
|
| Rôle | Accès |
|
||||||
|
|------|-------|
|
||||||
|
| `ROLE_PDG` | Accès complet + tableau de bord |
|
||||||
|
| `ROLE_RESPONSABLE_VENTE` | Ventes, clients, facturation |
|
||||||
|
| `ROLE_RESPONSABLE_ACHAT` | Achats, fournisseurs |
|
||||||
|
| `ROLE_RESPONSABLE_PRODUCTION` | Production, BOM, ordres de fabrication |
|
||||||
|
| `ROLE_MAGASINIER` | Stock, mouvements |
|
||||||
|
| `ROLE_RH` | Paie, congés, employés |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4 — Frontend Flutter (À venir)
|
||||||
|
|
||||||
|
- Architecture : Provider / BLoC
|
||||||
|
- Écrans prioritaires : Login → Dashboard → Articles → Ventes → Achats → Production
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Livrables Produits
|
||||||
|
|
||||||
|
| Fichier | Description | Statut |
|
||||||
|
|---------|-------------|--------|
|
||||||
|
| `SUIVI-PROJET.md` (ce fichier) | Suivi global du projet | ✅ |
|
||||||
|
| `backend/` | Code source API Spring Boot | 🔄 |
|
||||||
|
| `docs/UML-DiagrammeClasses.md` | Explication du diagramme de classes | ⏳ |
|
||||||
|
| `docs/UML-CasUtilisation.md` | Explication des cas d'utilisation | ⏳ |
|
||||||
|
| `docs/Architecture-API.md` | Documentation complète des endpoints | ⏳ |
|
||||||
|
| `docs/Guide-Deploiement.md` | Guide Docker + déploiement local | ⏳ |
|
||||||
|
| `docs/Guide-Tests-Postman.md` | Collection Postman + scénarios de test | ⏳ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Infrastructure Serveur
|
||||||
|
|
||||||
|
- **Serveur local** : 192.168.100.33
|
||||||
|
- **SSH** : port 22222, user Best0f
|
||||||
|
- **Portainer** : http://192.168.100.33:9000/
|
||||||
|
- **Gitea** : https://gitea.bolbol.tn
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes Importantes
|
||||||
|
|
||||||
|
- LDPE (pas BDPE) = Polyéthylène Basse Densité
|
||||||
|
- TVA Tunisie : 19% standard (vérifier avec Rayhan)
|
||||||
|
- CNSS Tunisie : patronal ~16.57%, salarial ~9.18%
|
||||||
|
- Timbre fiscal sur factures : 0.600 DT (à confirmer)
|
||||||
|
- 4 produits finis : Sac Bertel, Sac Poubelle, Sac Alimentaire, Film Rétractable
|
||||||
|
- 3 machines : Extrudeuse, Découpe/Soudure, Densificateur (recyclage chutes)
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
# ==========================================
|
||||||
|
# Dockerfile — Rayhan ERP Backend
|
||||||
|
# Spring Boot 3 / Java 17
|
||||||
|
# ==========================================
|
||||||
|
|
||||||
|
# Étape 1 : Build avec Maven
|
||||||
|
FROM maven:3.9.6-eclipse-temurin-17 AS build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY pom.xml .
|
||||||
|
RUN mvn dependency:go-offline -B
|
||||||
|
COPY src ./src
|
||||||
|
RUN mvn clean package -DskipTests -B
|
||||||
|
|
||||||
|
# Étape 2 : Image finale légère
|
||||||
|
FROM eclipse-temurin:17-jre-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/target/erp-1.0.0-SNAPSHOT.jar app.jar
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.2.5</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.rayhan</groupId>
|
||||||
|
<artifactId>erp</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<name>Rayhan ERP</name>
|
||||||
|
<description>ERP sur mesure pour SUARL Rayhan — PFE Ali Guennari</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<jjwt.version>0.12.5</jjwt.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Boot Web (REST API) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JPA / Hibernate pour la base de données -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Security -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Validation des données -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Connecteur MySQL -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JWT — API -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- JWT — Implémentation -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- JWT — Jackson (sérialisation) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok (réduit le boilerplate Java) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Tests -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.rayhan.erp;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class RayhanErpApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(RayhanErpApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.rayhan.erp.config;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.ERole;
|
||||||
|
import com.rayhan.erp.model.Role;
|
||||||
|
import com.rayhan.erp.model.User;
|
||||||
|
import com.rayhan.erp.repository.RoleRepository;
|
||||||
|
import com.rayhan.erp.repository.UserRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise la base de données avec les rôles et un utilisateur PDG par défaut.
|
||||||
|
* S'exécute au démarrage de l'application si la base est vide.
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class DataInitializer implements CommandLineRunner {
|
||||||
|
|
||||||
|
@Autowired private RoleRepository roleRepository;
|
||||||
|
@Autowired private UserRepository userRepository;
|
||||||
|
@Autowired private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(String... args) {
|
||||||
|
initRoles();
|
||||||
|
initDefaultAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initRoles() {
|
||||||
|
for (ERole eRole : ERole.values()) {
|
||||||
|
if (roleRepository.findByName(eRole).isEmpty()) {
|
||||||
|
roleRepository.save(new Role(eRole));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initDefaultAdmin() {
|
||||||
|
if (!userRepository.existsByUsername("admin")) {
|
||||||
|
User admin = new User(
|
||||||
|
"admin",
|
||||||
|
"admin@rayhan.tn",
|
||||||
|
passwordEncoder.encode("Rayhan2024!"),
|
||||||
|
"Ahmed",
|
||||||
|
"Fekih");
|
||||||
|
Role pdgRole = roleRepository.findByName(ERole.ROLE_PDG)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Rôle PDG non trouvé"));
|
||||||
|
admin.setRoles(Set.of(pdgRole));
|
||||||
|
userRepository.save(admin);
|
||||||
|
System.out.println("✅ Utilisateur admin créé (username: admin, password: Rayhan2024!)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package com.rayhan.erp.config;
|
||||||
|
|
||||||
|
import com.rayhan.erp.security.jwt.AuthEntryPointJwt;
|
||||||
|
import com.rayhan.erp.security.jwt.AuthTokenFilter;
|
||||||
|
import com.rayhan.erp.security.services.UserDetailsServiceImpl;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableMethodSecurity
|
||||||
|
public class WebSecurityConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
UserDetailsServiceImpl userDetailsService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AuthEntryPointJwt unauthorizedHandler;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthTokenFilter authenticationJwtTokenFilter() {
|
||||||
|
return new AuthTokenFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DaoAuthenticationProvider authenticationProvider() {
|
||||||
|
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
|
||||||
|
authProvider.setUserDetailsService(userDetailsService);
|
||||||
|
authProvider.setPasswordEncoder(passwordEncoder());
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
|
||||||
|
return authConfig.getAuthenticationManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers("/api/auth/**").permitAll()
|
||||||
|
.requestMatchers("/api/test/**").permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
|
||||||
|
http.authenticationProvider(authenticationProvider());
|
||||||
|
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
package com.rayhan.erp.controller;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.Article;
|
||||||
|
import com.rayhan.erp.repository.ArticleRepository;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/articles")
|
||||||
|
public class ArticleController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ArticleRepository articleRepository;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
public List<Article> getAllArticles() {
|
||||||
|
return articleRepository.findByActifTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
public ResponseEntity<Article> getArticleById(@PathVariable Long id) {
|
||||||
|
return articleRepository.findById(id)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/type/{type}")
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
public List<Article> getArticlesByType(@PathVariable Article.TypeArticle type) {
|
||||||
|
return articleRepository.findByType(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/alertes-stock")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_MAGASINIER', 'ROLE_RESPONSABLE_PRODUCTION')")
|
||||||
|
public List<Article> getArticlesEnAlerte() {
|
||||||
|
return articleRepository.findAll().stream()
|
||||||
|
.filter(a -> a.isActif() && a.getStockActuel().compareTo(a.getStockMinimum()) <= 0)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_PRODUCTION', 'ROLE_MAGASINIER')")
|
||||||
|
public Article createArticle(@Valid @RequestBody Article article) {
|
||||||
|
return articleRepository.save(article);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_PRODUCTION')")
|
||||||
|
public ResponseEntity<Article> updateArticle(@PathVariable Long id, @Valid @RequestBody Article details) {
|
||||||
|
return articleRepository.findById(id)
|
||||||
|
.map(article -> {
|
||||||
|
article.setDesignation(details.getDesignation());
|
||||||
|
article.setType(details.getType());
|
||||||
|
article.setUniteMesure(details.getUniteMesure());
|
||||||
|
article.setPrixUnitaire(details.getPrixUnitaire());
|
||||||
|
article.setStockMinimum(details.getStockMinimum());
|
||||||
|
return ResponseEntity.ok(articleRepository.save(article));
|
||||||
|
})
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
@PreAuthorize("hasRole('ROLE_PDG')")
|
||||||
|
public ResponseEntity<?> deleteArticle(@PathVariable Long id) {
|
||||||
|
return articleRepository.findById(id)
|
||||||
|
.map(article -> {
|
||||||
|
article.setActif(false);
|
||||||
|
articleRepository.save(article);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
})
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
package com.rayhan.erp.controller;
|
||||||
|
|
||||||
|
import com.rayhan.erp.dto.request.LoginRequest;
|
||||||
|
import com.rayhan.erp.dto.request.SignupRequest;
|
||||||
|
import com.rayhan.erp.dto.response.JwtResponse;
|
||||||
|
import com.rayhan.erp.dto.response.MessageResponse;
|
||||||
|
import com.rayhan.erp.model.ERole;
|
||||||
|
import com.rayhan.erp.model.Role;
|
||||||
|
import com.rayhan.erp.model.User;
|
||||||
|
import com.rayhan.erp.repository.RoleRepository;
|
||||||
|
import com.rayhan.erp.repository.UserRepository;
|
||||||
|
import com.rayhan.erp.security.jwt.JwtUtils;
|
||||||
|
import com.rayhan.erp.security.services.UserDetailsImpl;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/auth")
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
@Autowired AuthenticationManager authenticationManager;
|
||||||
|
@Autowired UserRepository userRepository;
|
||||||
|
@Autowired RoleRepository roleRepository;
|
||||||
|
@Autowired PasswordEncoder encoder;
|
||||||
|
@Autowired JwtUtils jwtUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/auth/signin
|
||||||
|
* Connexion d'un utilisateur — retourne un token JWT
|
||||||
|
*/
|
||||||
|
@PostMapping("/signin")
|
||||||
|
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
|
||||||
|
Authentication authentication = authenticationManager.authenticate(
|
||||||
|
new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
String jwt = jwtUtils.generateJwtToken(authentication);
|
||||||
|
|
||||||
|
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
|
||||||
|
List<String> roles = userDetails.getAuthorities().stream()
|
||||||
|
.map(item -> item.getAuthority())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(new JwtResponse(jwt,
|
||||||
|
userDetails.getId(),
|
||||||
|
userDetails.getUsername(),
|
||||||
|
userDetails.getEmail(),
|
||||||
|
userDetails.getFirstName(),
|
||||||
|
userDetails.getLastName(),
|
||||||
|
roles));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST /api/auth/signup
|
||||||
|
* Inscription d'un nouvel utilisateur (réservé au PDG en production)
|
||||||
|
*/
|
||||||
|
@PostMapping("/signup")
|
||||||
|
public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {
|
||||||
|
if (userRepository.existsByUsername(signUpRequest.getUsername())) {
|
||||||
|
return ResponseEntity.badRequest()
|
||||||
|
.body(new MessageResponse("Erreur : Ce nom d'utilisateur est déjà pris."));
|
||||||
|
}
|
||||||
|
if (userRepository.existsByEmail(signUpRequest.getEmail())) {
|
||||||
|
return ResponseEntity.badRequest()
|
||||||
|
.body(new MessageResponse("Erreur : Cet email est déjà utilisé."));
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = new User(
|
||||||
|
signUpRequest.getUsername(),
|
||||||
|
signUpRequest.getEmail(),
|
||||||
|
encoder.encode(signUpRequest.getPassword()),
|
||||||
|
signUpRequest.getFirstName(),
|
||||||
|
signUpRequest.getLastName());
|
||||||
|
|
||||||
|
Set<String> strRoles = signUpRequest.getRoles();
|
||||||
|
Set<Role> roles = new HashSet<>();
|
||||||
|
|
||||||
|
if (strRoles == null || strRoles.isEmpty()) {
|
||||||
|
Role magasinierRole = roleRepository.findByName(ERole.ROLE_MAGASINIER)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Rôle introuvable en base."));
|
||||||
|
roles.add(magasinierRole);
|
||||||
|
} else {
|
||||||
|
strRoles.forEach(role -> {
|
||||||
|
ERole eRole = switch (role.toLowerCase()) {
|
||||||
|
case "pdg" -> ERole.ROLE_PDG;
|
||||||
|
case "vente" -> ERole.ROLE_RESPONSABLE_VENTE;
|
||||||
|
case "achat" -> ERole.ROLE_RESPONSABLE_ACHAT;
|
||||||
|
case "production" -> ERole.ROLE_RESPONSABLE_PRODUCTION;
|
||||||
|
case "rh" -> ERole.ROLE_RH;
|
||||||
|
default -> ERole.ROLE_MAGASINIER;
|
||||||
|
};
|
||||||
|
Role foundRole = roleRepository.findByName(eRole)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Rôle introuvable : " + role));
|
||||||
|
roles.add(foundRole);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setRoles(roles);
|
||||||
|
userRepository.save(user);
|
||||||
|
return ResponseEntity.ok(new MessageResponse("Utilisateur créé avec succès !"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.rayhan.erp.controller;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.Client;
|
||||||
|
import com.rayhan.erp.repository.ClientRepository;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/clients")
|
||||||
|
public class ClientController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
ClientRepository clientRepository;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_VENTE')")
|
||||||
|
public List<Client> getAllClients() {
|
||||||
|
return clientRepository.findByActifTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/search")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_VENTE')")
|
||||||
|
public List<Client> searchClients(@RequestParam String q) {
|
||||||
|
return clientRepository.findByRaisonSocialeContainingIgnoreCase(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_VENTE')")
|
||||||
|
public ResponseEntity<Client> getClientById(@PathVariable Long id) {
|
||||||
|
return clientRepository.findById(id)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_VENTE')")
|
||||||
|
public Client createClient(@Valid @RequestBody Client client) {
|
||||||
|
return clientRepository.save(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_VENTE')")
|
||||||
|
public ResponseEntity<Client> updateClient(@PathVariable Long id, @Valid @RequestBody Client details) {
|
||||||
|
return clientRepository.findById(id)
|
||||||
|
.map(client -> {
|
||||||
|
client.setRaisonSociale(details.getRaisonSociale());
|
||||||
|
client.setMatriculeFiscal(details.getMatriculeFiscal());
|
||||||
|
client.setAdresse(details.getAdresse());
|
||||||
|
client.setTelephone(details.getTelephone());
|
||||||
|
client.setEmail(details.getEmail());
|
||||||
|
client.setVille(details.getVille());
|
||||||
|
client.setTypeClient(details.getTypeClient());
|
||||||
|
client.setPlafondCredit(details.getPlafondCredit());
|
||||||
|
client.setDelaiPaiement(details.getDelaiPaiement());
|
||||||
|
return ResponseEntity.ok(clientRepository.save(client));
|
||||||
|
})
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package com.rayhan.erp.controller;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.Article;
|
||||||
|
import com.rayhan.erp.model.ProductionOrder;
|
||||||
|
import com.rayhan.erp.model.SalesOrder;
|
||||||
|
import com.rayhan.erp.repository.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/dashboard")
|
||||||
|
public class DashboardController {
|
||||||
|
|
||||||
|
@Autowired private SalesOrderRepository salesOrderRepository;
|
||||||
|
@Autowired private PurchaseOrderRepository purchaseOrderRepository;
|
||||||
|
@Autowired private ProductionOrderRepository productionOrderRepository;
|
||||||
|
@Autowired private ArticleRepository articleRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GET /api/dashboard
|
||||||
|
* KPIs principaux pour le tableau de bord du PDG
|
||||||
|
*/
|
||||||
|
@GetMapping
|
||||||
|
@PreAuthorize("hasRole('ROLE_PDG')")
|
||||||
|
public Map<String, Object> getDashboard() {
|
||||||
|
Map<String, Object> dashboard = new HashMap<>();
|
||||||
|
|
||||||
|
LocalDate debutMois = LocalDate.now().withDayOfMonth(1);
|
||||||
|
LocalDate finMois = LocalDate.now();
|
||||||
|
|
||||||
|
// KPIs Ventes
|
||||||
|
BigDecimal caMois = salesOrderRepository
|
||||||
|
.sumTotalTTCByDateCommandeBetween(debutMois, finMois);
|
||||||
|
long nbCommandesMois = salesOrderRepository
|
||||||
|
.countByDateCommandeBetween(debutMois, finMois);
|
||||||
|
|
||||||
|
Map<String, Object> ventes = new HashMap<>();
|
||||||
|
ventes.put("chiffreAffairesMois", caMois != null ? caMois : BigDecimal.ZERO);
|
||||||
|
ventes.put("nbCommandesMois", nbCommandesMois);
|
||||||
|
ventes.put("commandesEnCours",
|
||||||
|
salesOrderRepository.findByStatutOrderByDateCommandeDesc(SalesOrder.StatutCommande.CONFIRMEE).size());
|
||||||
|
dashboard.put("ventes", ventes);
|
||||||
|
|
||||||
|
// KPIs Achats
|
||||||
|
Map<String, Object> achats = new HashMap<>();
|
||||||
|
achats.put("commandesEnAttente",
|
||||||
|
purchaseOrderRepository.countByStatutIn(
|
||||||
|
List.of(PurchaseOrder.StatutCommande.CONFIRMEE,
|
||||||
|
PurchaseOrder.StatutCommande.PARTIELLEMENT_RECUE)));
|
||||||
|
dashboard.put("achats", achats);
|
||||||
|
|
||||||
|
// KPIs Production
|
||||||
|
Map<String, Object> production = new HashMap<>();
|
||||||
|
production.put("ofPlanifies",
|
||||||
|
productionOrderRepository.countByStatut(ProductionOrder.StatutOF.PLANIFIE));
|
||||||
|
production.put("ofEnCours",
|
||||||
|
productionOrderRepository.countByStatut(ProductionOrder.StatutOF.LANCE));
|
||||||
|
dashboard.put("production", production);
|
||||||
|
|
||||||
|
// KPIs Stock
|
||||||
|
List<Article> alertesStock = articleRepository.findAll().stream()
|
||||||
|
.filter(a -> a.isActif() && a.getStockActuel().compareTo(a.getStockMinimum()) <= 0)
|
||||||
|
.toList();
|
||||||
|
Map<String, Object> stock = new HashMap<>();
|
||||||
|
stock.put("articlesEnAlerte", alertesStock.size());
|
||||||
|
stock.put("articlesEnAlerteDetails", alertesStock.stream()
|
||||||
|
.map(a -> Map.of("reference", a.getReference(),
|
||||||
|
"designation", a.getDesignation(),
|
||||||
|
"stockActuel", a.getStockActuel(),
|
||||||
|
"stockMinimum", a.getStockMinimum()))
|
||||||
|
.toList());
|
||||||
|
dashboard.put("stock", stock);
|
||||||
|
|
||||||
|
return dashboard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.rayhan.erp.controller;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.Fournisseur;
|
||||||
|
import com.rayhan.erp.repository.FournisseurRepository;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/fournisseurs")
|
||||||
|
public class FournisseurController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
FournisseurRepository fournisseurRepository;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_ACHAT')")
|
||||||
|
public List<Fournisseur> getAllFournisseurs() {
|
||||||
|
return fournisseurRepository.findByActifTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/search")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_ACHAT')")
|
||||||
|
public List<Fournisseur> searchFournisseurs(@RequestParam String q) {
|
||||||
|
return fournisseurRepository.findByRaisonSocialeContainingIgnoreCase(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_ACHAT')")
|
||||||
|
public ResponseEntity<Fournisseur> getFournisseurById(@PathVariable Long id) {
|
||||||
|
return fournisseurRepository.findById(id)
|
||||||
|
.map(ResponseEntity::ok)
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_ACHAT')")
|
||||||
|
public Fournisseur createFournisseur(@Valid @RequestBody Fournisseur fournisseur) {
|
||||||
|
return fournisseurRepository.save(fournisseur);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_ACHAT')")
|
||||||
|
public ResponseEntity<Fournisseur> updateFournisseur(@PathVariable Long id,
|
||||||
|
@Valid @RequestBody Fournisseur details) {
|
||||||
|
return fournisseurRepository.findById(id)
|
||||||
|
.map(f -> {
|
||||||
|
f.setRaisonSociale(details.getRaisonSociale());
|
||||||
|
f.setMatriculeFiscal(details.getMatriculeFiscal());
|
||||||
|
f.setAdresse(details.getAdresse());
|
||||||
|
f.setTelephone(details.getTelephone());
|
||||||
|
f.setEmail(details.getEmail());
|
||||||
|
f.setVille(details.getVille());
|
||||||
|
f.setPays(details.getPays());
|
||||||
|
f.setCategorieProduit(details.getCategorieProduit());
|
||||||
|
f.setDelaiLivraison(details.getDelaiLivraison());
|
||||||
|
f.setModePaiement(details.getModePaiement());
|
||||||
|
return ResponseEntity.ok(fournisseurRepository.save(f));
|
||||||
|
})
|
||||||
|
.orElse(ResponseEntity.notFound().build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package com.rayhan.erp.controller;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.BomLine;
|
||||||
|
import com.rayhan.erp.model.ProductionOrder;
|
||||||
|
import com.rayhan.erp.model.User;
|
||||||
|
import com.rayhan.erp.repository.BomLineRepository;
|
||||||
|
import com.rayhan.erp.repository.UserRepository;
|
||||||
|
import com.rayhan.erp.security.services.UserDetailsImpl;
|
||||||
|
import com.rayhan.erp.service.ProductionOrderService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/production")
|
||||||
|
public class ProductionOrderController {
|
||||||
|
|
||||||
|
@Autowired private ProductionOrderService productionOrderService;
|
||||||
|
@Autowired private BomLineRepository bomLineRepository;
|
||||||
|
@Autowired private UserRepository userRepository;
|
||||||
|
|
||||||
|
// --- BOM (Nomenclatures) ---
|
||||||
|
|
||||||
|
@GetMapping("/bom/{produitFiniId}")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_PRODUCTION')")
|
||||||
|
public List<BomLine> getBom(@PathVariable Long produitFiniId) {
|
||||||
|
return bomLineRepository.findByProduitFiniId(produitFiniId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/bom")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_PRODUCTION')")
|
||||||
|
public BomLine addBomLine(@RequestBody BomLine bomLine) {
|
||||||
|
return bomLineRepository.save(bomLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/bom/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_PRODUCTION')")
|
||||||
|
public ResponseEntity<?> deleteBomLine(@PathVariable Long id) {
|
||||||
|
bomLineRepository.deleteById(id);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Ordres de Fabrication ---
|
||||||
|
|
||||||
|
@GetMapping("/orders")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_PRODUCTION')")
|
||||||
|
public List<ProductionOrder> getAllOFs() {
|
||||||
|
return productionOrderService.getAllOFs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/orders/plan")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_PRODUCTION')")
|
||||||
|
public ResponseEntity<ProductionOrder> planOF(@RequestBody Map<String, Object> request,
|
||||||
|
@AuthenticationPrincipal UserDetailsImpl userDetails) {
|
||||||
|
Long produitId = Long.valueOf(request.get("produitFiniId").toString());
|
||||||
|
BigDecimal quantite = new BigDecimal(request.get("quantite").toString());
|
||||||
|
LocalDate date = LocalDate.parse(request.get("datePlanifiee").toString());
|
||||||
|
User user = userRepository.findById(userDetails.getId()).orElse(null);
|
||||||
|
return ResponseEntity.ok(productionOrderService.planifierOF(produitId, quantite, date, user));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/orders/{id}/launch")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_PRODUCTION')")
|
||||||
|
public ResponseEntity<ProductionOrder> launchOF(@PathVariable Long id,
|
||||||
|
@AuthenticationPrincipal UserDetailsImpl userDetails) {
|
||||||
|
User user = userRepository.findById(userDetails.getId()).orElse(null);
|
||||||
|
return ResponseEntity.ok(productionOrderService.lancerOF(id, user));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/orders/{id}/complete")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_PRODUCTION')")
|
||||||
|
public ResponseEntity<ProductionOrder> completeOF(@PathVariable Long id,
|
||||||
|
@RequestBody Map<String, Object> request,
|
||||||
|
@AuthenticationPrincipal UserDetailsImpl userDetails) {
|
||||||
|
BigDecimal quantiteRealisee = new BigDecimal(request.get("quantiteRealisee").toString());
|
||||||
|
User user = userRepository.findById(userDetails.getId()).orElse(null);
|
||||||
|
return ResponseEntity.ok(productionOrderService.terminerOF(id, quantiteRealisee, user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.rayhan.erp.controller;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.GoodsReceipt;
|
||||||
|
import com.rayhan.erp.model.PurchaseOrder;
|
||||||
|
import com.rayhan.erp.model.User;
|
||||||
|
import com.rayhan.erp.repository.UserRepository;
|
||||||
|
import com.rayhan.erp.security.services.UserDetailsImpl;
|
||||||
|
import com.rayhan.erp.service.PurchaseOrderService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/purchase-orders")
|
||||||
|
public class PurchaseOrderController {
|
||||||
|
|
||||||
|
@Autowired private PurchaseOrderService purchaseOrderService;
|
||||||
|
@Autowired private UserRepository userRepository;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_ACHAT')")
|
||||||
|
public List<PurchaseOrder> getAllOrders() {
|
||||||
|
return purchaseOrderService.getAllPurchaseOrders();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_ACHAT')")
|
||||||
|
public ResponseEntity<PurchaseOrder> createOrder(@RequestBody PurchaseOrder order,
|
||||||
|
@AuthenticationPrincipal UserDetailsImpl userDetails) {
|
||||||
|
User user = userRepository.findById(userDetails.getId()).orElse(null);
|
||||||
|
order.setCreePar(user);
|
||||||
|
return ResponseEntity.ok(purchaseOrderService.createPurchaseOrder(order));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/receive")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_ACHAT', 'ROLE_MAGASINIER')")
|
||||||
|
public ResponseEntity<GoodsReceipt> receiveGoods(@PathVariable Long id,
|
||||||
|
@RequestBody GoodsReceipt reception,
|
||||||
|
@AuthenticationPrincipal UserDetailsImpl userDetails) {
|
||||||
|
User user = userRepository.findById(userDetails.getId()).orElse(null);
|
||||||
|
return ResponseEntity.ok(purchaseOrderService.receiveGoods(id, reception, user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.rayhan.erp.controller;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.DeliveryNote;
|
||||||
|
import com.rayhan.erp.model.SalesOrder;
|
||||||
|
import com.rayhan.erp.model.User;
|
||||||
|
import com.rayhan.erp.repository.UserRepository;
|
||||||
|
import com.rayhan.erp.security.services.UserDetailsImpl;
|
||||||
|
import com.rayhan.erp.service.SalesOrderService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/sales-orders")
|
||||||
|
public class SalesOrderController {
|
||||||
|
|
||||||
|
@Autowired private SalesOrderService salesOrderService;
|
||||||
|
@Autowired private UserRepository userRepository;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_VENTE')")
|
||||||
|
public List<SalesOrder> getAllOrders() {
|
||||||
|
return salesOrderService.getAllSalesOrders();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_VENTE')")
|
||||||
|
public ResponseEntity<SalesOrder> createOrder(@RequestBody SalesOrder order,
|
||||||
|
@AuthenticationPrincipal UserDetailsImpl userDetails) {
|
||||||
|
User user = userRepository.findById(userDetails.getId()).orElse(null);
|
||||||
|
order.setCreePar(user);
|
||||||
|
return ResponseEntity.ok(salesOrderService.createSalesOrder(order));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/deliver")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_RESPONSABLE_VENTE', 'ROLE_MAGASINIER')")
|
||||||
|
public ResponseEntity<DeliveryNote> createDelivery(@PathVariable Long id,
|
||||||
|
@RequestBody DeliveryNote bonLivraison,
|
||||||
|
@AuthenticationPrincipal UserDetailsImpl userDetails) {
|
||||||
|
User user = userRepository.findById(userDetails.getId()).orElse(null);
|
||||||
|
return ResponseEntity.ok(salesOrderService.createDeliveryNote(id, bonLivraison, user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.rayhan.erp.controller;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.Article;
|
||||||
|
import com.rayhan.erp.model.StockMovement;
|
||||||
|
import com.rayhan.erp.model.User;
|
||||||
|
import com.rayhan.erp.repository.ArticleRepository;
|
||||||
|
import com.rayhan.erp.repository.UserRepository;
|
||||||
|
import com.rayhan.erp.security.services.UserDetailsImpl;
|
||||||
|
import com.rayhan.erp.service.StockService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@CrossOrigin(origins = "*", maxAge = 3600)
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/stock")
|
||||||
|
public class StockController {
|
||||||
|
|
||||||
|
@Autowired private StockService stockService;
|
||||||
|
@Autowired private ArticleRepository articleRepository;
|
||||||
|
@Autowired private UserRepository userRepository;
|
||||||
|
|
||||||
|
@GetMapping("/historique/{articleId}")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_MAGASINIER', 'ROLE_RESPONSABLE_PRODUCTION')")
|
||||||
|
public List<StockMovement> getHistorique(@PathVariable Long articleId) {
|
||||||
|
return stockService.getHistoriqueArticle(articleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/adjust")
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_PDG', 'ROLE_MAGASINIER')")
|
||||||
|
public ResponseEntity<StockMovement> adjustStock(@RequestBody Map<String, Object> request,
|
||||||
|
@AuthenticationPrincipal UserDetailsImpl userDetails) {
|
||||||
|
Long articleId = Long.valueOf(request.get("articleId").toString());
|
||||||
|
BigDecimal quantite = new BigDecimal(request.get("quantite").toString());
|
||||||
|
String type = request.get("type").toString(); // "IN" ou "OUT"
|
||||||
|
String motif = request.getOrDefault("motif", "Ajustement manuel").toString();
|
||||||
|
|
||||||
|
Article article = articleRepository.findById(articleId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Article introuvable"));
|
||||||
|
User user = userRepository.findById(userDetails.getId()).orElse(null);
|
||||||
|
|
||||||
|
StockMovement mouvement;
|
||||||
|
if ("IN".equals(type)) {
|
||||||
|
mouvement = stockService.entreeStock(article, quantite, "AJUSTEMENT", "ADJ", motif, user);
|
||||||
|
} else {
|
||||||
|
mouvement = stockService.sortieStock(article, quantite, "AJUSTEMENT", "ADJ", motif, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(mouvement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.rayhan.erp.dto.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class LoginRequest {
|
||||||
|
@NotBlank(message = "Le nom d'utilisateur est obligatoire")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@NotBlank(message = "Le mot de passe est obligatoire")
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.rayhan.erp.dto.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class SignupRequest {
|
||||||
|
@NotBlank
|
||||||
|
@Size(min = 3, max = 20)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 50)
|
||||||
|
@Email
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(min = 6, max = 40)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
private Set<String> roles;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.rayhan.erp.dto.response;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class JwtResponse {
|
||||||
|
private String token;
|
||||||
|
private String type = "Bearer";
|
||||||
|
private Long id;
|
||||||
|
private String username;
|
||||||
|
private String email;
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
private List<String> roles;
|
||||||
|
|
||||||
|
public JwtResponse(String accessToken, Long id, String username, String email,
|
||||||
|
String firstName, String lastName, List<String> roles) {
|
||||||
|
this.token = accessToken;
|
||||||
|
this.id = id;
|
||||||
|
this.username = username;
|
||||||
|
this.email = email;
|
||||||
|
this.firstName = firstName;
|
||||||
|
this.lastName = lastName;
|
||||||
|
this.roles = roles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package com.rayhan.erp.dto.response;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class MessageResponse {
|
||||||
|
private String message;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "articles")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Article {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false, length = 30)
|
||||||
|
private String reference;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 150)
|
||||||
|
private String designation;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false, length = 10)
|
||||||
|
private TypeArticle type; // MP, PF, PSF
|
||||||
|
|
||||||
|
@Column(length = 20)
|
||||||
|
private String uniteMesure; // kg, unité, rouleau, m
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal prixUnitaire = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal stockActuel = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal stockMinimum = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
private boolean actif = true;
|
||||||
|
|
||||||
|
public enum TypeArticle {
|
||||||
|
MP, // Matière Première (HDPE, LDPE, colorants)
|
||||||
|
PSF, // Produit Semi-Fini (film tubulaire)
|
||||||
|
PF // Produit Fini (sacs, film rétractable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ligne de nomenclature (BOM — Bill of Materials).
|
||||||
|
* Définit quelle matière première (ou PSF) est nécessaire pour fabriquer un produit fini.
|
||||||
|
* Ex : Pour 1000 Sacs Bertel (PF), il faut 15 kg de HDPE (MP) + 0.5 kg de colorant.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "bom_lines", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(columnNames = {"produit_fini_id", "composant_id"})
|
||||||
|
})
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class BomLine {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "produit_fini_id")
|
||||||
|
private Article produitFini; // doit être de type PF ou PSF
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "composant_id")
|
||||||
|
private Article composant; // doit être de type MP ou PSF
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 15, scale = 6)
|
||||||
|
private BigDecimal quantiteParUnite; // quantité de composant par unité de produit fini
|
||||||
|
|
||||||
|
@Column(length = 20)
|
||||||
|
private String uniteMesure; // kg, g, L, unité
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "clients")
|
||||||
|
@PrimaryKeyJoinColumn(name = "tiers_id")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Client extends Tiers {
|
||||||
|
|
||||||
|
@Column(length = 30)
|
||||||
|
private String typeClient; // Grossiste, Détaillant, Industrie
|
||||||
|
|
||||||
|
private BigDecimal plafondCredit = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
private Integer delaiPaiement = 30; // jours
|
||||||
|
|
||||||
|
@Column(length = 50)
|
||||||
|
private String representantNom;
|
||||||
|
|
||||||
|
@Column(length = 20)
|
||||||
|
private String representantTelephone;
|
||||||
|
|
||||||
|
public Client(String raisonSociale, String matriculeFiscal, String telephone) {
|
||||||
|
super(raisonSociale, matriculeFiscal, telephone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "delivery_notes")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DeliveryNote {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false, length = 30)
|
||||||
|
private String reference; // ex: BL-2024-001
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "sales_order_id")
|
||||||
|
private SalesOrder salesOrder;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDate dateLivraison = LocalDate.now();
|
||||||
|
|
||||||
|
@Column(length = 200)
|
||||||
|
private String adresseLivraison;
|
||||||
|
|
||||||
|
@Column(length = 200)
|
||||||
|
private String notes;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(length = 20)
|
||||||
|
private StatutLivraison statut = StatutLivraison.LIVRE;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "deliveryNote", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
private List<DeliveryNoteLine> lignes = new ArrayList<>();
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "created_by")
|
||||||
|
private User creePar;
|
||||||
|
|
||||||
|
public enum StatutLivraison {
|
||||||
|
EN_PREPARATION, LIVRE, RETOURNE_PARTIEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "delivery_note_lines")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DeliveryNoteLine {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "delivery_note_id")
|
||||||
|
private DeliveryNote deliveryNote;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "sales_order_line_id")
|
||||||
|
private SalesOrderLine salesOrderLine;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "article_id")
|
||||||
|
private Article article;
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 15, scale = 3)
|
||||||
|
private BigDecimal quantiteLivree;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
public enum ERole {
|
||||||
|
ROLE_PDG, // Gérant (accès complet)
|
||||||
|
ROLE_RESPONSABLE_VENTE, // Responsable Commercial
|
||||||
|
ROLE_RESPONSABLE_ACHAT, // Responsable Achats
|
||||||
|
ROLE_RESPONSABLE_PRODUCTION, // Responsable Production
|
||||||
|
ROLE_MAGASINIER, // Magasinier
|
||||||
|
ROLE_RH // Responsable RH
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "fournisseurs")
|
||||||
|
@PrimaryKeyJoinColumn(name = "tiers_id")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Fournisseur extends Tiers {
|
||||||
|
|
||||||
|
@Column(length = 100)
|
||||||
|
private String pays = "Tunisie";
|
||||||
|
|
||||||
|
@Column(length = 50)
|
||||||
|
private String categorieProduit; // Matières plastiques, Emballages...
|
||||||
|
|
||||||
|
private Integer delaiLivraison = 7; // jours
|
||||||
|
|
||||||
|
@Column(length = 30)
|
||||||
|
private String modePaiement; // Virement, Chèque, Espèces
|
||||||
|
|
||||||
|
public Fournisseur(String raisonSociale, String matriculeFiscal, String telephone) {
|
||||||
|
super(raisonSociale, matriculeFiscal, telephone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "goods_receipts")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class GoodsReceipt {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false, length = 30)
|
||||||
|
private String reference; // ex: BR-2024-001
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "purchase_order_id")
|
||||||
|
private PurchaseOrder purchaseOrder;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDate dateReception = LocalDate.now();
|
||||||
|
|
||||||
|
@Column(length = 200)
|
||||||
|
private String notes;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "goodsReceipt", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
private List<GoodsReceiptLine> lignes = new ArrayList<>();
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "created_by")
|
||||||
|
private User creePar;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "goods_receipt_lines")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class GoodsReceiptLine {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "goods_receipt_id")
|
||||||
|
private GoodsReceipt goodsReceipt;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "purchase_order_line_id")
|
||||||
|
private PurchaseOrderLine purchaseOrderLine;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "article_id")
|
||||||
|
private Article article;
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 15, scale = 3)
|
||||||
|
private BigDecimal quantiteRecue;
|
||||||
|
|
||||||
|
@Column(length = 200)
|
||||||
|
private String observations;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "production_orders")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ProductionOrder {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false, length = 30)
|
||||||
|
private String reference; // ex: OF-2024-001
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "produit_fini_id")
|
||||||
|
private Article produitFini;
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 15, scale = 3)
|
||||||
|
private BigDecimal quantitePlanifiee;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal quantiteRealisee = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDate datePlanifiee;
|
||||||
|
|
||||||
|
private LocalDateTime dateLancement;
|
||||||
|
private LocalDateTime dateTerminaison;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false, length = 20)
|
||||||
|
private StatutOF statut = StatutOF.PLANIFIE;
|
||||||
|
|
||||||
|
@Column(length = 500)
|
||||||
|
private String notes;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "created_by")
|
||||||
|
private User creePar;
|
||||||
|
|
||||||
|
public enum StatutOF {
|
||||||
|
PLANIFIE, LANCE, EN_COURS, TERMINE, ANNULE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "purchase_orders")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PurchaseOrder {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false, length = 30)
|
||||||
|
private String reference; // ex: BC-2024-001
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "fournisseur_id")
|
||||||
|
private Fournisseur fournisseur;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDate dateCommande = LocalDate.now();
|
||||||
|
|
||||||
|
private LocalDate dateLivraisonPrevue;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false, length = 20)
|
||||||
|
private StatutCommande statut = StatutCommande.BROUILLON;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal totalHT = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal totalTVA = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal totalTTC = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@Column(length = 500)
|
||||||
|
private String notes;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "purchaseOrder", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
private List<PurchaseOrderLine> lignes = new ArrayList<>();
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "created_by")
|
||||||
|
private User creePar;
|
||||||
|
|
||||||
|
public enum StatutCommande {
|
||||||
|
BROUILLON, CONFIRMEE, PARTIELLEMENT_RECUE, COMPLETEMENT_RECUE, ANNULEE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "purchase_order_lines")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class PurchaseOrderLine {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "purchase_order_id")
|
||||||
|
private PurchaseOrder purchaseOrder;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "article_id")
|
||||||
|
private Article article;
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 15, scale = 3)
|
||||||
|
private BigDecimal quantiteCommandee;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal quantiteRecue = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 15, scale = 3)
|
||||||
|
private BigDecimal prixUnitaireHT;
|
||||||
|
|
||||||
|
@Column(precision = 5, scale = 2)
|
||||||
|
private BigDecimal tauxTVA = new BigDecimal("19.00");
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal montantHT;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal montantTTC;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "roles")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Role {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(length = 30, unique = true, nullable = false)
|
||||||
|
private ERole name;
|
||||||
|
|
||||||
|
public Role(ERole name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "sales_orders")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class SalesOrder {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(unique = true, nullable = false, length = 30)
|
||||||
|
private String reference; // ex: CC-2024-001
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "client_id")
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDate dateCommande = LocalDate.now();
|
||||||
|
|
||||||
|
private LocalDate dateLivraisonSouhaitee;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false, length = 25)
|
||||||
|
private StatutCommande statut = StatutCommande.CONFIRMEE;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal totalHT = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal totalTVA = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal totalTTC = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@Column(length = 500)
|
||||||
|
private String notes;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
private List<SalesOrderLine> lignes = new ArrayList<>();
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "created_by")
|
||||||
|
private User creePar;
|
||||||
|
|
||||||
|
public enum StatutCommande {
|
||||||
|
CONFIRMEE, EN_PREPARATION, PARTIELLEMENT_LIVREE, COMPLETEMENT_LIVREE, ANNULEE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "sales_order_lines")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class SalesOrderLine {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "sales_order_id")
|
||||||
|
private SalesOrder salesOrder;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "article_id")
|
||||||
|
private Article article;
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 15, scale = 3)
|
||||||
|
private BigDecimal quantiteCommandee;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal quantiteLivree = BigDecimal.ZERO;
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 15, scale = 3)
|
||||||
|
private BigDecimal prixUnitaireHT;
|
||||||
|
|
||||||
|
@Column(precision = 5, scale = 2)
|
||||||
|
private BigDecimal tauxTVA = new BigDecimal("19.00");
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal montantHT;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal montantTTC;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "stock_movements")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class StockMovement {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne(optional = false)
|
||||||
|
@JoinColumn(name = "article_id")
|
||||||
|
private Article article;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false, length = 10)
|
||||||
|
private TypeMouvement type; // IN = entrée, OUT = sortie
|
||||||
|
|
||||||
|
@Column(nullable = false, precision = 15, scale = 3)
|
||||||
|
private BigDecimal quantite;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal stockAvant;
|
||||||
|
|
||||||
|
@Column(precision = 15, scale = 3)
|
||||||
|
private BigDecimal stockApres;
|
||||||
|
|
||||||
|
@Column(length = 50)
|
||||||
|
private String sourceDocument; // BON_RECEPTION, BON_LIVRAISON, OF, AJUSTEMENT
|
||||||
|
|
||||||
|
@Column(length = 30)
|
||||||
|
private String referenceDocument; // ex: BR-2024-001
|
||||||
|
|
||||||
|
@Column(length = 200)
|
||||||
|
private String motif;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDateTime dateHeure = LocalDateTime.now();
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "user_id")
|
||||||
|
private User creePar;
|
||||||
|
|
||||||
|
public enum TypeMouvement {
|
||||||
|
IN, // Entrée stock
|
||||||
|
OUT // Sortie stock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "tiers")
|
||||||
|
@Inheritance(strategy = InheritanceType.JOINED)
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public abstract class Tiers {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 100)
|
||||||
|
private String raisonSociale;
|
||||||
|
|
||||||
|
@Column(length = 20)
|
||||||
|
private String matriculeFiscal;
|
||||||
|
|
||||||
|
@Column(length = 200)
|
||||||
|
private String adresse;
|
||||||
|
|
||||||
|
@Column(length = 20)
|
||||||
|
private String telephone;
|
||||||
|
|
||||||
|
@Column(length = 100)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column(length = 50)
|
||||||
|
private String ville;
|
||||||
|
|
||||||
|
private boolean actif = true;
|
||||||
|
|
||||||
|
public Tiers(String raisonSociale, String matriculeFiscal, String telephone) {
|
||||||
|
this.raisonSociale = raisonSociale;
|
||||||
|
this.matriculeFiscal = matriculeFiscal;
|
||||||
|
this.telephone = telephone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package com.rayhan.erp.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "users", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(columnNames = "username"),
|
||||||
|
@UniqueConstraint(columnNames = "email")
|
||||||
|
})
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class User {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 50)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 100)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Column(length = 50)
|
||||||
|
private String firstName;
|
||||||
|
|
||||||
|
@Column(length = 50)
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
@ManyToMany(fetch = FetchType.EAGER)
|
||||||
|
@JoinTable(name = "user_roles",
|
||||||
|
joinColumns = @JoinColumn(name = "user_id"),
|
||||||
|
inverseJoinColumns = @JoinColumn(name = "role_id"))
|
||||||
|
private Set<Role> roles = new HashSet<>();
|
||||||
|
|
||||||
|
public User(String username, String email, String password, String firstName, String lastName) {
|
||||||
|
this.username = username;
|
||||||
|
this.email = email;
|
||||||
|
this.password = password;
|
||||||
|
this.firstName = firstName;
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.Article;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ArticleRepository extends JpaRepository<Article, Long> {
|
||||||
|
Optional<Article> findByReference(String reference);
|
||||||
|
Boolean existsByReference(String reference);
|
||||||
|
List<Article> findByType(Article.TypeArticle type);
|
||||||
|
List<Article> findByActifTrue();
|
||||||
|
List<Article> findByStockActuelLessThanEqualAndActifTrue(java.math.BigDecimal seuil);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.BomLine;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface BomLineRepository extends JpaRepository<BomLine, Long> {
|
||||||
|
List<BomLine> findByProduitFiniId(Long produitFiniId);
|
||||||
|
void deleteByProduitFiniId(Long produitFiniId);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.Client;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ClientRepository extends JpaRepository<Client, Long> {
|
||||||
|
List<Client> findByActifTrue();
|
||||||
|
List<Client> findByRaisonSocialeContainingIgnoreCase(String search);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.DeliveryNote;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface DeliveryNoteRepository extends JpaRepository<DeliveryNote, Long> {
|
||||||
|
Optional<DeliveryNote> findByReference(String reference);
|
||||||
|
boolean existsByReference(String reference);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.Fournisseur;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface FournisseurRepository extends JpaRepository<Fournisseur, Long> {
|
||||||
|
List<Fournisseur> findByActifTrue();
|
||||||
|
List<Fournisseur> findByRaisonSocialeContainingIgnoreCase(String search);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.GoodsReceipt;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface GoodsReceiptRepository extends JpaRepository<GoodsReceipt, Long> {
|
||||||
|
Optional<GoodsReceipt> findByReference(String reference);
|
||||||
|
boolean existsByReference(String reference);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.ProductionOrder;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ProductionOrderRepository extends JpaRepository<ProductionOrder, Long> {
|
||||||
|
Optional<ProductionOrder> findByReference(String reference);
|
||||||
|
boolean existsByReference(String reference);
|
||||||
|
List<ProductionOrder> findByStatutOrderByDatePlanifieeDesc(ProductionOrder.StatutOF statut);
|
||||||
|
long countByStatut(ProductionOrder.StatutOF statut);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.PurchaseOrder;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface PurchaseOrderRepository extends JpaRepository<PurchaseOrder, Long> {
|
||||||
|
Optional<PurchaseOrder> findByReference(String reference);
|
||||||
|
boolean existsByReference(String reference);
|
||||||
|
List<PurchaseOrder> findByStatutOrderByDateCommandeDesc(PurchaseOrder.StatutCommande statut);
|
||||||
|
long countByStatutIn(List<PurchaseOrder.StatutCommande> statuts);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.ERole;
|
||||||
|
import com.rayhan.erp.model.Role;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface RoleRepository extends JpaRepository<Role, Integer> {
|
||||||
|
Optional<Role> findByName(ERole name);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.SalesOrder;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface SalesOrderRepository extends JpaRepository<SalesOrder, Long> {
|
||||||
|
Optional<SalesOrder> findByReference(String reference);
|
||||||
|
boolean existsByReference(String reference);
|
||||||
|
List<SalesOrder> findByStatutOrderByDateCommandeDesc(SalesOrder.StatutCommande statut);
|
||||||
|
|
||||||
|
@Query("SELECT COALESCE(SUM(s.totalTTC), 0) FROM SalesOrder s WHERE s.dateCommande BETWEEN :debut AND :fin")
|
||||||
|
BigDecimal sumTotalTTCByDateCommandeBetween(LocalDate debut, LocalDate fin);
|
||||||
|
|
||||||
|
long countByDateCommandeBetween(LocalDate debut, LocalDate fin);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.StockMovement;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface StockMovementRepository extends JpaRepository<StockMovement, Long> {
|
||||||
|
List<StockMovement> findByArticleIdOrderByDateHeureDesc(Long articleId);
|
||||||
|
List<StockMovement> findByDateHeureBetweenOrderByDateHeureDesc(LocalDateTime debut, LocalDateTime fin);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.rayhan.erp.repository;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.User;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
|
Optional<User> findByUsername(String username);
|
||||||
|
Boolean existsByUsername(String username);
|
||||||
|
Boolean existsByEmail(String email);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.rayhan.erp.security.jwt;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class AuthEntryPointJwt implements AuthenticationEntryPoint {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commence(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
AuthenticationException authException) throws IOException {
|
||||||
|
logger.error("Erreur d'accès non autorisé : {}", authException.getMessage());
|
||||||
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
|
||||||
|
final Map<String, Object> body = new HashMap<>();
|
||||||
|
body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
body.put("error", "Non autorisé");
|
||||||
|
body.put("message", authException.getMessage());
|
||||||
|
body.put("path", request.getServletPath());
|
||||||
|
|
||||||
|
final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
mapper.writeValue(response.getOutputStream(), body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.rayhan.erp.security.jwt;
|
||||||
|
|
||||||
|
import com.rayhan.erp.security.services.UserDetailsServiceImpl;
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class AuthTokenFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtUtils jwtUtils;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserDetailsServiceImpl userDetailsService;
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
try {
|
||||||
|
String jwt = parseJwt(request);
|
||||||
|
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
|
||||||
|
String username = jwtUtils.getUsernameFromJwtToken(jwt);
|
||||||
|
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||||
|
UsernamePasswordAuthenticationToken authentication =
|
||||||
|
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||||
|
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("Impossible de définir l'authentification : {}", e.getMessage());
|
||||||
|
}
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseJwt(HttpServletRequest request) {
|
||||||
|
String headerAuth = request.getHeader("Authorization");
|
||||||
|
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
|
||||||
|
return headerAuth.substring(7);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package com.rayhan.erp.security.jwt;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.*;
|
||||||
|
import io.jsonwebtoken.io.Decoders;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class JwtUtils {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
|
||||||
|
|
||||||
|
@Value("${rayhan.erp.jwtSecret}")
|
||||||
|
private String jwtSecret;
|
||||||
|
|
||||||
|
@Value("${rayhan.erp.jwtExpirationMs}")
|
||||||
|
private int jwtExpirationMs;
|
||||||
|
|
||||||
|
private SecretKey key() {
|
||||||
|
return Keys.hmacShaKeyFor(jwtSecret.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String generateJwtToken(Authentication authentication) {
|
||||||
|
org.springframework.security.core.userdetails.UserDetails userPrincipal =
|
||||||
|
(org.springframework.security.core.userdetails.UserDetails) authentication.getPrincipal();
|
||||||
|
return Jwts.builder()
|
||||||
|
.subject(userPrincipal.getUsername())
|
||||||
|
.issuedAt(new Date())
|
||||||
|
.expiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
|
||||||
|
.signWith(key())
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsernameFromJwtToken(String token) {
|
||||||
|
return Jwts.parser().verifyWith(key()).build()
|
||||||
|
.parseSignedClaims(token).getPayload().getSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateJwtToken(String authToken) {
|
||||||
|
try {
|
||||||
|
Jwts.parser().verifyWith(key()).build().parseSignedClaims(authToken);
|
||||||
|
return true;
|
||||||
|
} catch (MalformedJwtException e) {
|
||||||
|
logger.error("Token JWT invalide : {}", e.getMessage());
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
logger.error("Token JWT expiré : {}", e.getMessage());
|
||||||
|
} catch (UnsupportedJwtException e) {
|
||||||
|
logger.error("Token JWT non supporté : {}", e.getMessage());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
logger.error("JWT claims vide : {}", e.getMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.rayhan.erp.security.services;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.rayhan.erp.model.User;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class UserDetailsImpl implements UserDetails {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String username;
|
||||||
|
private String email;
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private Collection<? extends GrantedAuthority> authorities;
|
||||||
|
|
||||||
|
public UserDetailsImpl(Long id, String username, String email,
|
||||||
|
String firstName, String lastName,
|
||||||
|
String password,
|
||||||
|
Collection<? extends GrantedAuthority> authorities) {
|
||||||
|
this.id = id;
|
||||||
|
this.username = username;
|
||||||
|
this.email = email;
|
||||||
|
this.firstName = firstName;
|
||||||
|
this.lastName = lastName;
|
||||||
|
this.password = password;
|
||||||
|
this.authorities = authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserDetailsImpl build(User user) {
|
||||||
|
List<GrantedAuthority> authorities = user.getRoles().stream()
|
||||||
|
.map(role -> new SimpleGrantedAuthority(role.getName().name()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return new UserDetailsImpl(
|
||||||
|
user.getId(),
|
||||||
|
user.getUsername(),
|
||||||
|
user.getEmail(),
|
||||||
|
user.getFirstName(),
|
||||||
|
user.getLastName(),
|
||||||
|
user.getPassword(),
|
||||||
|
authorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() { return id; }
|
||||||
|
public String getEmail() { return email; }
|
||||||
|
public String getFirstName() { return firstName; }
|
||||||
|
public String getLastName() { return lastName; }
|
||||||
|
|
||||||
|
@Override public String getUsername() { return username; }
|
||||||
|
@Override public String getPassword() { return password; }
|
||||||
|
@Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; }
|
||||||
|
@Override public boolean isAccountNonExpired() { return true; }
|
||||||
|
@Override public boolean isAccountNonLocked() { return true; }
|
||||||
|
@Override public boolean isCredentialsNonExpired() { return true; }
|
||||||
|
@Override public boolean isEnabled() { return true; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.rayhan.erp.security.services;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.User;
|
||||||
|
import com.rayhan.erp.repository.UserRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
UserRepository userRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
User user = userRepository.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException(
|
||||||
|
"Utilisateur introuvable : " + username));
|
||||||
|
return UserDetailsImpl.build(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
package com.rayhan.erp.service;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.*;
|
||||||
|
import com.rayhan.erp.repository.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class ProductionOrderService {
|
||||||
|
|
||||||
|
@Autowired private ProductionOrderRepository productionOrderRepository;
|
||||||
|
@Autowired private BomLineRepository bomLineRepository;
|
||||||
|
@Autowired private ArticleRepository articleRepository;
|
||||||
|
@Autowired private StockService stockService;
|
||||||
|
|
||||||
|
private static int seqOF = 1;
|
||||||
|
|
||||||
|
private String generateRefOF() {
|
||||||
|
return "OF-" + LocalDate.now().getYear() + "-" + String.format("%03d", seqOF++);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Planifie un ordre de fabrication après vérification des matières premières.
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public ProductionOrder planifierOF(Long produitFiniId, BigDecimal quantite,
|
||||||
|
LocalDate datePlanifiee, User user) {
|
||||||
|
Article produitFini = articleRepository.findById(produitFiniId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Produit introuvable : " + produitFiniId));
|
||||||
|
|
||||||
|
List<BomLine> bom = bomLineRepository.findByProduitFiniId(produitFiniId);
|
||||||
|
if (bom.isEmpty()) {
|
||||||
|
throw new RuntimeException("Aucune nomenclature définie pour " + produitFini.getDesignation());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérification du stock matières premières
|
||||||
|
for (BomLine ligne : bom) {
|
||||||
|
BigDecimal qteNecessaire = ligne.getQuantiteParUnite().multiply(quantite);
|
||||||
|
Article composant = ligne.getComposant();
|
||||||
|
if (composant.getStockActuel().compareTo(qteNecessaire) < 0) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Stock insuffisant de " + composant.getDesignation() +
|
||||||
|
" : disponible " + composant.getStockActuel() +
|
||||||
|
", nécessaire " + qteNecessaire);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProductionOrder of = new ProductionOrder();
|
||||||
|
of.setReference(generateRefOF());
|
||||||
|
of.setProduitFini(produitFini);
|
||||||
|
of.setQuantitePlanifiee(quantite);
|
||||||
|
of.setDatePlanifiee(datePlanifiee);
|
||||||
|
of.setStatut(ProductionOrder.StatutOF.PLANIFIE);
|
||||||
|
of.setCreePar(user);
|
||||||
|
|
||||||
|
return productionOrderRepository.save(of);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lance un OF : consomme les matières premières du stock.
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public ProductionOrder lancerOF(Long ofId, User user) {
|
||||||
|
ProductionOrder of = productionOrderRepository.findById(ofId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("OF introuvable : " + ofId));
|
||||||
|
|
||||||
|
if (of.getStatut() != ProductionOrder.StatutOF.PLANIFIE) {
|
||||||
|
throw new IllegalStateException("L'OF doit être à l'état PLANIFIE pour être lancé.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BomLine> bom = bomLineRepository.findByProduitFiniId(of.getProduitFini().getId());
|
||||||
|
|
||||||
|
for (BomLine ligne : bom) {
|
||||||
|
BigDecimal qteConsommee = ligne.getQuantiteParUnite().multiply(of.getQuantitePlanifiee());
|
||||||
|
Article composant = articleRepository.findById(ligne.getComposant().getId()).get();
|
||||||
|
stockService.sortieStock(composant, qteConsommee,
|
||||||
|
"ORDRE_FABRICATION", of.getReference(),
|
||||||
|
"Lancement OF " + of.getReference(), user);
|
||||||
|
}
|
||||||
|
|
||||||
|
of.setStatut(ProductionOrder.StatutOF.LANCE);
|
||||||
|
of.setDateLancement(LocalDateTime.now());
|
||||||
|
return productionOrderRepository.save(of);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Termine un OF : entre le produit fini en stock.
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public ProductionOrder terminerOF(Long ofId, BigDecimal quantiteRealisee, User user) {
|
||||||
|
ProductionOrder of = productionOrderRepository.findById(ofId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("OF introuvable : " + ofId));
|
||||||
|
|
||||||
|
if (of.getStatut() != ProductionOrder.StatutOF.LANCE) {
|
||||||
|
throw new IllegalStateException("L'OF doit être à l'état LANCE pour être terminé.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Article produitFini = articleRepository.findById(of.getProduitFini().getId()).get();
|
||||||
|
stockService.entreeStock(produitFini, quantiteRealisee,
|
||||||
|
"ORDRE_FABRICATION", of.getReference(),
|
||||||
|
"Production OF " + of.getReference(), user);
|
||||||
|
|
||||||
|
of.setQuantiteRealisee(quantiteRealisee);
|
||||||
|
of.setStatut(ProductionOrder.StatutOF.TERMINE);
|
||||||
|
of.setDateTerminaison(LocalDateTime.now());
|
||||||
|
return productionOrderRepository.save(of);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ProductionOrder> getAllOFs() {
|
||||||
|
return productionOrderRepository.findAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
package com.rayhan.erp.service;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.*;
|
||||||
|
import com.rayhan.erp.repository.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PurchaseOrderService {
|
||||||
|
|
||||||
|
@Autowired private PurchaseOrderRepository purchaseOrderRepository;
|
||||||
|
@Autowired private GoodsReceiptRepository goodsReceiptRepository;
|
||||||
|
@Autowired private ArticleRepository articleRepository;
|
||||||
|
@Autowired private StockService stockService;
|
||||||
|
|
||||||
|
private static int seqBC = 1;
|
||||||
|
private static int seqBR = 1;
|
||||||
|
|
||||||
|
private String generateRefBC() {
|
||||||
|
return "BC-" + LocalDate.now().getYear() + "-" + String.format("%03d", seqBC++);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateRefBR() {
|
||||||
|
return "BR-" + LocalDate.now().getYear() + "-" + String.format("%03d", seqBR++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public PurchaseOrder createPurchaseOrder(PurchaseOrder order) {
|
||||||
|
order.setReference(generateRefBC());
|
||||||
|
order.setStatut(PurchaseOrder.StatutCommande.CONFIRMEE);
|
||||||
|
|
||||||
|
BigDecimal totalHT = BigDecimal.ZERO;
|
||||||
|
for (PurchaseOrderLine ligne : order.getLignes()) {
|
||||||
|
ligne.setPurchaseOrder(order);
|
||||||
|
BigDecimal montantHT = ligne.getPrixUnitaireHT()
|
||||||
|
.multiply(ligne.getQuantiteCommandee());
|
||||||
|
ligne.setMontantHT(montantHT);
|
||||||
|
BigDecimal tva = montantHT.multiply(ligne.getTauxTVA().divide(new BigDecimal("100")));
|
||||||
|
ligne.setMontantTTC(montantHT.add(tva));
|
||||||
|
totalHT = totalHT.add(montantHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
order.setTotalHT(totalHT);
|
||||||
|
BigDecimal tvaGlobal = totalHT.multiply(new BigDecimal("0.19"));
|
||||||
|
order.setTotalTVA(tvaGlobal);
|
||||||
|
order.setTotalTTC(totalHT.add(tvaGlobal));
|
||||||
|
|
||||||
|
return purchaseOrderRepository.save(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public GoodsReceipt receiveGoods(Long purchaseOrderId, GoodsReceipt reception, User user) {
|
||||||
|
PurchaseOrder order = purchaseOrderRepository.findById(purchaseOrderId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Commande introuvable : " + purchaseOrderId));
|
||||||
|
|
||||||
|
reception.setReference(generateRefBR());
|
||||||
|
reception.setPurchaseOrder(order);
|
||||||
|
reception.setCreePar(user);
|
||||||
|
|
||||||
|
for (GoodsReceiptLine ligne : reception.getLignes()) {
|
||||||
|
ligne.setGoodsReceipt(reception);
|
||||||
|
Article article = articleRepository.findById(ligne.getArticle().getId())
|
||||||
|
.orElseThrow(() -> new RuntimeException("Article introuvable"));
|
||||||
|
ligne.setArticle(article);
|
||||||
|
|
||||||
|
stockService.entreeStock(article, ligne.getQuantiteRecue(),
|
||||||
|
"BON_RECEPTION", reception.getReference(),
|
||||||
|
"Réception commande " + order.getReference(), user);
|
||||||
|
|
||||||
|
ligne.getPurchaseOrderLine().setQuantiteRecue(
|
||||||
|
ligne.getPurchaseOrderLine().getQuantiteRecue().add(ligne.getQuantiteRecue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
order.setStatut(PurchaseOrder.StatutCommande.COMPLETEMENT_RECUE);
|
||||||
|
purchaseOrderRepository.save(order);
|
||||||
|
|
||||||
|
return goodsReceiptRepository.save(reception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PurchaseOrder> getAllPurchaseOrders() {
|
||||||
|
return purchaseOrderRepository.findAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
package com.rayhan.erp.service;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.*;
|
||||||
|
import com.rayhan.erp.repository.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SalesOrderService {
|
||||||
|
|
||||||
|
@Autowired private SalesOrderRepository salesOrderRepository;
|
||||||
|
@Autowired private DeliveryNoteRepository deliveryNoteRepository;
|
||||||
|
@Autowired private ArticleRepository articleRepository;
|
||||||
|
@Autowired private StockService stockService;
|
||||||
|
|
||||||
|
private static int seqCC = 1;
|
||||||
|
private static int seqBL = 1;
|
||||||
|
|
||||||
|
private String generateRefCC() {
|
||||||
|
return "CC-" + LocalDate.now().getYear() + "-" + String.format("%03d", seqCC++);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateRefBL() {
|
||||||
|
return "BL-" + LocalDate.now().getYear() + "-" + String.format("%03d", seqBL++);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public SalesOrder createSalesOrder(SalesOrder order) {
|
||||||
|
// Vérification du stock disponible avant validation
|
||||||
|
for (SalesOrderLine ligne : order.getLignes()) {
|
||||||
|
Article article = articleRepository.findById(ligne.getArticle().getId())
|
||||||
|
.orElseThrow(() -> new RuntimeException("Article introuvable"));
|
||||||
|
if (article.getStockActuel().compareTo(ligne.getQuantiteCommandee()) < 0) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Stock insuffisant pour " + article.getDesignation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
order.setReference(generateRefCC());
|
||||||
|
order.setStatut(SalesOrder.StatutCommande.CONFIRMEE);
|
||||||
|
|
||||||
|
BigDecimal totalHT = BigDecimal.ZERO;
|
||||||
|
for (SalesOrderLine ligne : order.getLignes()) {
|
||||||
|
ligne.setSalesOrder(order);
|
||||||
|
BigDecimal montantHT = ligne.getPrixUnitaireHT().multiply(ligne.getQuantiteCommandee());
|
||||||
|
ligne.setMontantHT(montantHT);
|
||||||
|
BigDecimal tva = montantHT.multiply(ligne.getTauxTVA().divide(new BigDecimal("100")));
|
||||||
|
ligne.setMontantTTC(montantHT.add(tva));
|
||||||
|
totalHT = totalHT.add(montantHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
order.setTotalHT(totalHT);
|
||||||
|
BigDecimal tvaGlobal = totalHT.multiply(new BigDecimal("0.19"));
|
||||||
|
order.setTotalTVA(tvaGlobal);
|
||||||
|
order.setTotalTTC(totalHT.add(tvaGlobal));
|
||||||
|
|
||||||
|
return salesOrderRepository.save(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public DeliveryNote createDeliveryNote(Long salesOrderId, DeliveryNote bonLivraison, User user) {
|
||||||
|
SalesOrder order = salesOrderRepository.findById(salesOrderId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Commande introuvable : " + salesOrderId));
|
||||||
|
|
||||||
|
bonLivraison.setReference(generateRefBL());
|
||||||
|
bonLivraison.setSalesOrder(order);
|
||||||
|
bonLivraison.setCreePar(user);
|
||||||
|
|
||||||
|
for (DeliveryNoteLine ligne : bonLivraison.getLignes()) {
|
||||||
|
ligne.setDeliveryNote(bonLivraison);
|
||||||
|
Article article = articleRepository.findById(ligne.getArticle().getId())
|
||||||
|
.orElseThrow(() -> new RuntimeException("Article introuvable"));
|
||||||
|
ligne.setArticle(article);
|
||||||
|
|
||||||
|
stockService.sortieStock(article, ligne.getQuantiteLivree(),
|
||||||
|
"BON_LIVRAISON", bonLivraison.getReference(),
|
||||||
|
"Livraison commande " + order.getReference(), user);
|
||||||
|
|
||||||
|
ligne.getSalesOrderLine().setQuantiteLivree(
|
||||||
|
ligne.getSalesOrderLine().getQuantiteLivree().add(ligne.getQuantiteLivree()));
|
||||||
|
}
|
||||||
|
|
||||||
|
order.setStatut(SalesOrder.StatutCommande.COMPLETEMENT_LIVREE);
|
||||||
|
salesOrderRepository.save(order);
|
||||||
|
|
||||||
|
return deliveryNoteRepository.save(bonLivraison);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SalesOrder> getAllSalesOrders() {
|
||||||
|
return salesOrderRepository.findAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
package com.rayhan.erp.service;
|
||||||
|
|
||||||
|
import com.rayhan.erp.model.Article;
|
||||||
|
import com.rayhan.erp.model.StockMovement;
|
||||||
|
import com.rayhan.erp.model.User;
|
||||||
|
import com.rayhan.erp.repository.ArticleRepository;
|
||||||
|
import com.rayhan.erp.repository.StockMovementRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class StockService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ArticleRepository articleRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StockMovementRepository stockMovementRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Effectue une entrée en stock (réception achat, production terminée, ajustement +)
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public StockMovement entreeStock(Article article, BigDecimal quantite,
|
||||||
|
String sourceDocument, String referenceDocument,
|
||||||
|
String motif, User user) {
|
||||||
|
if (quantite.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
throw new IllegalArgumentException("La quantité doit être positive.");
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal stockAvant = article.getStockActuel();
|
||||||
|
BigDecimal stockApres = stockAvant.add(quantite);
|
||||||
|
|
||||||
|
article.setStockActuel(stockApres);
|
||||||
|
articleRepository.save(article);
|
||||||
|
|
||||||
|
StockMovement mouvement = new StockMovement();
|
||||||
|
mouvement.setArticle(article);
|
||||||
|
mouvement.setType(StockMovement.TypeMouvement.IN);
|
||||||
|
mouvement.setQuantite(quantite);
|
||||||
|
mouvement.setStockAvant(stockAvant);
|
||||||
|
mouvement.setStockApres(stockApres);
|
||||||
|
mouvement.setSourceDocument(sourceDocument);
|
||||||
|
mouvement.setReferenceDocument(referenceDocument);
|
||||||
|
mouvement.setMotif(motif);
|
||||||
|
mouvement.setCreePar(user);
|
||||||
|
|
||||||
|
return stockMovementRepository.save(mouvement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Effectue une sortie de stock (livraison client, lancement OF, ajustement -)
|
||||||
|
* Lance une exception si stock insuffisant.
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public StockMovement sortieStock(Article article, BigDecimal quantite,
|
||||||
|
String sourceDocument, String referenceDocument,
|
||||||
|
String motif, User user) {
|
||||||
|
if (quantite.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
throw new IllegalArgumentException("La quantité doit être positive.");
|
||||||
|
}
|
||||||
|
if (article.getStockActuel().compareTo(quantite) < 0) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Stock insuffisant pour " + article.getDesignation() +
|
||||||
|
" : disponible " + article.getStockActuel() + ", demandé " + quantite);
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal stockAvant = article.getStockActuel();
|
||||||
|
BigDecimal stockApres = stockAvant.subtract(quantite);
|
||||||
|
|
||||||
|
article.setStockActuel(stockApres);
|
||||||
|
articleRepository.save(article);
|
||||||
|
|
||||||
|
StockMovement mouvement = new StockMovement();
|
||||||
|
mouvement.setArticle(article);
|
||||||
|
mouvement.setType(StockMovement.TypeMouvement.OUT);
|
||||||
|
mouvement.setQuantite(quantite);
|
||||||
|
mouvement.setStockAvant(stockAvant);
|
||||||
|
mouvement.setStockApres(stockApres);
|
||||||
|
mouvement.setSourceDocument(sourceDocument);
|
||||||
|
mouvement.setReferenceDocument(referenceDocument);
|
||||||
|
mouvement.setMotif(motif);
|
||||||
|
mouvement.setCreePar(user);
|
||||||
|
|
||||||
|
return stockMovementRepository.save(mouvement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<StockMovement> getHistoriqueArticle(Long articleId) {
|
||||||
|
return stockMovementRepository.findByArticleIdOrderByDateHeureDesc(articleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
# ===================================
|
||||||
|
# Configuration de l'application ERP
|
||||||
|
# SUARL Rayhan — PFE Ali Guennari
|
||||||
|
# ===================================
|
||||||
|
|
||||||
|
# Serveur
|
||||||
|
server.port=8080
|
||||||
|
|
||||||
|
# Base de données MySQL
|
||||||
|
spring.datasource.url=jdbc:mysql://localhost:3306/rayhan_erp_db?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Africa/Tunis
|
||||||
|
spring.datasource.username=root
|
||||||
|
spring.datasource.password=rayhan_erp_2024
|
||||||
|
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
|
||||||
|
|
||||||
|
# JPA / Hibernate
|
||||||
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
|
spring.jpa.show-sql=false
|
||||||
|
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
|
||||||
|
spring.jpa.properties.hibernate.format_sql=true
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
rayhan.erp.jwtSecret=RayhanERP_SecretKey_PFE_AliGuennari_2024_TunisiePlasturgie_SUARL
|
||||||
|
rayhan.erp.jwtExpirationMs=86400000
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
logging.level.com.rayhan.erp=DEBUG
|
||||||
|
logging.level.org.springframework.security=INFO
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# Docker Compose — Rayhan ERP
|
||||||
|
# Backend Spring Boot + MySQL
|
||||||
|
# ==========================================
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
container_name: rayhan-mysql
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: rayhan_erp_2024
|
||||||
|
MYSQL_DATABASE: rayhan_erp_db
|
||||||
|
MYSQL_USER: rayhan_user
|
||||||
|
MYSQL_PASSWORD: rayhan_erp_2024
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
ports:
|
||||||
|
- "3307:3306"
|
||||||
|
networks:
|
||||||
|
- rayhan-net
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prayhan_erp_2024"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build: ./backend
|
||||||
|
container_name: rayhan-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
mysql:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/rayhan_erp_db?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Africa/Tunis
|
||||||
|
SPRING_DATASOURCE_USERNAME: root
|
||||||
|
SPRING_DATASOURCE_PASSWORD: rayhan_erp_2024
|
||||||
|
RAYHAN_ERP_JWTSECRET: RayhanERP_SecretKey_PFE_AliGuennari_2024_TunisiePlasturgie_SUARL
|
||||||
|
RAYHAN_ERP_JWTEXPIRATIONMS: 86400000
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
networks:
|
||||||
|
- rayhan-net
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
rayhan-net:
|
||||||
|
driver: bridge
|
||||||
Loading…
Reference in New Issue