From 133b771d3b0c74b52ba38f42ebb4c8df13cdacf9 Mon Sep 17 00:00:00 2001
From: Kevin <kghisalberti@takima.fr>
Date: Wed, 26 Feb 2025 11:50:59 +0100
Subject: [PATCH] feat: API working with all necessary endpoint

---
 README.md                                     | 119 ++++++--------
 docker/Dockerfile                             |   7 +
 docker/docker-compose.yml                     |  22 +++
 examples.http                                 |   0
 pom.xml                                       |  30 ++--
 .../core/exception/ErrorFactory.java          |  29 ++++
 .../features/block/entity/Block.java          |  53 +++++++
 .../features/block/mapper/BlockMapper.java    |  43 ++++++
 .../block/model/BlockCreationRequest.java     |   6 +
 .../features/block/model/BlockDTO.java        |   8 +
 .../block/service/SupplyBlockService.java     |  65 ++++++++
 .../features/market/model/Market.java         |  27 ++++
 .../offer/controller/OfferController.java     |  50 ++++++
 .../features/offer/entity/Offer.java          |  45 ++++++
 .../features/offer/mapper/OfferMapper.java    |  23 +++
 .../offer/model/OfferCreationRequest.java     |   9 ++
 .../features/offer/model/OfferDTO.java        |   8 +
 .../repository/OfferCustomRepository.java     |  29 ++++
 .../offer/repository/OfferRepository.java     |  10 ++
 .../features/offer/service/OfferService.java  |  87 +++++++++++
 .../controller/PowerPlantController.java      |  49 ++++++
 .../powerplant/entity/PowerPlant.java         |  50 ++++++
 .../powerplant/mapper/PowerPlantMapper.java   |  43 ++++++
 .../model/PowerPlantCreationRequest.java      |   4 +
 .../powerplant/model/PowerPlantDTO.java       |   5 +
 .../powerplant/model/PowerPlantOfferDTO.java  |   4 +
 .../powerplant/model/PowerPlantType.java      |   7 +
 .../repository/PowerPlantRepository.java      |  30 ++++
 .../powerplant/service/PowerPlantService.java |  53 +++++++
 src/main/resources/application.yml            |  15 ++
 .../db/migration/V1__Init_database.sql        |  28 ++++
 .../TestcontainersConfiguration.java          |   5 +-
 .../example/agregiokata/data/TestData.java    |  56 +++++++
 .../block/service/SupplyBlockServiceTest.java | 135 ++++++++++++++++
 .../repository/PowerPlantRepositoryTest.java  | 145 ++++++++++++++++++
 src/test/resources/application.yml            |   5 +
 36 files changed, 1220 insertions(+), 84 deletions(-)
 create mode 100644 docker/Dockerfile
 create mode 100644 docker/docker-compose.yml
 create mode 100644 examples.http
 create mode 100644 src/main/java/takima/example/agregiokata/core/exception/ErrorFactory.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/block/entity/Block.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/block/mapper/BlockMapper.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/block/model/BlockCreationRequest.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/block/model/BlockDTO.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/block/service/SupplyBlockService.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/market/model/Market.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/offer/controller/OfferController.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/offer/entity/Offer.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/offer/mapper/OfferMapper.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/offer/model/OfferCreationRequest.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/offer/model/OfferDTO.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/offer/repository/OfferCustomRepository.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/offer/repository/OfferRepository.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/offer/service/OfferService.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/powerplant/controller/PowerPlantController.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/powerplant/entity/PowerPlant.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/powerplant/mapper/PowerPlantMapper.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantCreationRequest.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantDTO.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantOfferDTO.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantType.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/powerplant/repository/PowerPlantRepository.java
 create mode 100644 src/main/java/takima/example/agregiokata/features/powerplant/service/PowerPlantService.java
 create mode 100644 src/main/resources/db/migration/V1__Init_database.sql
 create mode 100644 src/test/java/takima/example/agregiokata/data/TestData.java
 create mode 100644 src/test/java/takima/example/agregiokata/features/block/service/SupplyBlockServiceTest.java
 create mode 100644 src/test/java/takima/example/agregiokata/features/powerplant/repository/PowerPlantRepositoryTest.java
 create mode 100644 src/test/resources/application.yml

diff --git a/README.md b/README.md
index 1d732a9..f8b2e81 100644
--- a/README.md
+++ b/README.md
@@ -1,93 +1,76 @@
-# Agregio-kata
+# Kata Agregio
 
+### La stack technique
 
+##### - GitLab
 
-## Getting started
+##### - Java version 21
 
-To make it easy for you to get started with GitLab, here's a list of recommended next steps.
+##### - SpringBoot version 3.4.3
 
-Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
+##### - Maven
 
-## Add your files
+##### - PostgresSQL version 15.7
 
-- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
-- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
+### L'objectif
 
-```
-cd existing_repo
-git remote add origin https://gitlab.takima.io/kghisalberti/agregio-kata.git
-git branch -M main
-git push -uf origin main
-```
-
-## Integrate with your tools
-
-- [ ] [Set up project integrations](https://gitlab.takima.io/kghisalberti/agregio-kata/-/settings/integrations)
-
-## Collaborate with your team
-
-- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
-- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
-- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
-- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
-- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
-
-## Test and Deploy
-
-Use the built-in continuous integration in GitLab.
+Une API fonctionnelle avec des endpoints qui permettent de:
 
-- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
-- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
-- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
-- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
-- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
+- créer une offre
+- créer un parc
+- lister les offres pour chaque marché
+- voir la liste des parcs qui vendent sur un marché
 
-***
+L'API est principalement composée en trois domaines :
 
-# Editing this README
+- Les offres associé à un marché(parmi les 3 suivants: la Réserve Primaire, la Réserve
+  Secondaire et la Réserve Rapide.), et sont composé de plusieurs blocs horaires.
+- Les parcs qui ont pour caractéristique d'avoir un type(électrique, éolien, hydraulique), une capacité de production
+  et des bloc horaires à qui ils fournissent l'électricité.
+- Les blocs horaires quant à eux ont une production d'électricité qu'ils présentent, un prix plancher et sont liés à une
+  offre et un parc.
 
-When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
+### Documentation
 
-## Suggestions for a good README
+#### Lancer la database
 
-Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
+Pour lancer la database, vous devez utilisez la commande au niveau de la racine du projet:
 
-## Name
-Choose a self-explaining name for your project.
-
-## Description
-Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
-
-## Badges
-On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
-
-## Visuals
-Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
+```
+cd db
+docker compose up -d
+```
 
-## Installation
-Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
+#### Lancer l'application
 
-## Usage
-Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
+Pour démarrer l'application à partir de l'IDE, il suffit de lancer la classe AgregioKataApplication.  
+Pour lancer l'application à partir d'un terminal de commande, il faut avoir Maven et un JDK(min. version 21) d'installer
+, se mettre à la racine du projet et utiliser la commande
 
-## Support
-Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
+```
+mvn spring-boot:run
+```
 
-## Roadmap
-If you have ideas for releases in the future, it is a good idea to list them in the README.
+#### Visualisation de l'ensemble de l'API
 
-## Contributing
-State if you are open to contributions and what your requirements are for accepting them.
+Il est possible de visualiser les endpoints via cette URL:
+http://localhost:8080/swagger-ui/index.html
 
-For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
+### Le reste à faire et évolutions envisageables
 
-You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
+En reste à faire:
 
-## Authors and acknowledgment
-Show your appreciation to those who have contributed to the project.
+- Mettre des tests d'intégrations sur les repository qui n'en ont pas.
+- Faire un test end-to-end avec un MockMVC
 
-## License
-For open source projects, say how it is licensed.
+Évolution possible:
 
-## Project status
-If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
+- Donner la possibilité au bloc d'être associé à plusieurs parcs pour une attribution plus fine et complète de l'
+  énergie.
+- Mettre un prix à l'énergie produit par un parc et rajouter ainsi une contrainte d'attribution d'énergie qui ne peut
+  descendre en dessous du prix plancher du bloc.
+- Mettre en place de la logique concernant le temps sur les blocs d'une même offre
+  (pour éviter que 2 blocs puissent se chevaucher sur une même période)
+- Mettre en place une sécurité sur les endpoints et ce que ça implique avec
+  (système de rôle et de permission, utilisation d'outils comme Keycloak, etc...)
+- Création d'une CI/CD pour faciliter le déploiement et s'assurer de la qualité du code.
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 0000000..0b605b7
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,7 @@
+FROM postgres:15.7-alpine
+
+ENV POSTGRES_USER takima
+ENV POSTGRES_PASSWORD takima
+ENV POSTGRES_DB agregio-db
+
+EXPOSE 5432
\ No newline at end of file
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 0000000..2f515d1
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,22 @@
+version: "3.9"
+
+networks:
+  agregio-network:
+
+services:
+  db:
+    build: .
+    image: agregio_db
+    container_name: agregio_db
+    networks:
+      - game
+    restart: always
+    ports:
+      - "5432:5432"
+    command:
+      - "-c"
+      - "log_statement=all"
+    volumes:
+      - agregio-data:/var/lib/postgresql/data
+volumes:
+  agregio-data:
\ No newline at end of file
diff --git a/examples.http b/examples.http
new file mode 100644
index 0000000..e69de29
diff --git a/pom.xml b/pom.xml
index 5a16f9a..6e2bebe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,21 +13,11 @@
     <version>0.0.1-SNAPSHOT</version>
     <name>agregio-kata</name>
     <description>agregio-kata</description>
-    <url/>
-    <licenses>
-        <license/>
-    </licenses>
-    <developers>
-        <developer/>
-    </developers>
-    <scm>
-        <connection/>
-        <developerConnection/>
-        <tag/>
-        <url/>
-    </scm>
+
     <properties>
         <java.version>21</java.version>
+        <open-api.version>2.8.4</open-api.version>
+        <mockito.version>5.15.2</mockito.version>
     </properties>
     <dependencies>
         <dependency>
@@ -46,7 +36,6 @@
             <groupId>org.flywaydb</groupId>
             <artifactId>flyway-database-postgresql</artifactId>
         </dependency>
-
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-devtools</artifactId>
@@ -83,6 +72,19 @@
             <artifactId>postgresql</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
+            <version>${open-api.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-junit-jupiter</artifactId>
+            <version>${mockito.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/src/main/java/takima/example/agregiokata/core/exception/ErrorFactory.java b/src/main/java/takima/example/agregiokata/core/exception/ErrorFactory.java
new file mode 100644
index 0000000..0e7279d
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/core/exception/ErrorFactory.java
@@ -0,0 +1,29 @@
+package takima.example.agregiokata.core.exception;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ProblemDetail;
+import org.springframework.web.ErrorResponseException;
+
+public final class ErrorFactory {
+
+    private ErrorFactory() {
+    }
+
+    private static ErrorResponseException buildError(
+            HttpStatus status, String detail, Throwable cause) {
+        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(status, detail);
+        return new ErrorResponseException(status, problemDetail, cause);
+    }
+
+    public static ErrorResponseException badRequest(String detail, Throwable cause) {
+        return buildError(HttpStatus.BAD_REQUEST, detail, cause);
+    }
+
+    public static ErrorResponseException notFound(String ressourceName, Long id) {
+        return notFound("No " + ressourceName + " found with id '" + id + "'");
+    }
+
+    public static ErrorResponseException notFound(String detail) {
+        return buildError(HttpStatus.NOT_FOUND, detail, null);
+    }
+}
diff --git a/src/main/java/takima/example/agregiokata/features/block/entity/Block.java b/src/main/java/takima/example/agregiokata/features/block/entity/Block.java
new file mode 100644
index 0000000..db7938a
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/block/entity/Block.java
@@ -0,0 +1,53 @@
+package takima.example.agregiokata.features.block.entity;
+
+import jakarta.persistence.Entity;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.JoinColumn;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import takima.example.agregiokata.features.offer.entity.Offer;
+import takima.example.agregiokata.features.powerplant.entity.PowerPlant;
+
+import java.time.LocalTime;
+
+@Builder(toBuilder = true)
+@ToString
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@EqualsAndHashCode(onlyExplicitlyIncluded = true)
+@Entity
+@Table(name = "block")
+public class Block {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @EqualsAndHashCode.Include
+    private Long id;
+
+    private LocalTime startTime;
+
+    private LocalTime endTime;
+
+    private Double production;
+
+    private Double floorPrice;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    private Offer offer;
+
+    @ManyToOne(fetch = FetchType.EAGER)
+    @JoinColumn(name = "powerplant_id")
+    @ToString.Exclude
+    private PowerPlant plant;
+}
diff --git a/src/main/java/takima/example/agregiokata/features/block/mapper/BlockMapper.java b/src/main/java/takima/example/agregiokata/features/block/mapper/BlockMapper.java
new file mode 100644
index 0000000..acb8341
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/block/mapper/BlockMapper.java
@@ -0,0 +1,43 @@
+package takima.example.agregiokata.features.block.mapper;
+
+import org.springframework.stereotype.Component;
+import takima.example.agregiokata.features.block.entity.Block;
+import takima.example.agregiokata.features.block.model.BlockCreationRequest;
+import takima.example.agregiokata.features.block.model.BlockDTO;
+import takima.example.agregiokata.features.powerplant.mapper.PowerPlantMapper;
+
+import java.util.List;
+
+@Component
+public class BlockMapper {
+
+    private final PowerPlantMapper powerPlantMapper;
+
+    public BlockMapper(PowerPlantMapper powerPlantMapper) {
+        this.powerPlantMapper = powerPlantMapper;
+    }
+
+    public BlockDTO toDTO(Block block) {
+        return new BlockDTO(
+                block.getId(),
+                block.getStartTime(),
+                block.getEndTime(),
+                block.getProduction(),
+                block.getFloorPrice(),
+                powerPlantMapper.toOfferDTO(block.getPlant())
+        );
+    }
+
+    public Block toEntity(BlockCreationRequest request) {
+        return Block.builder()
+                .startTime(request.startTime())
+                .endTime(request.endTime())
+                .production(request.production())
+                .floorPrice(request.floorPrice())
+                .build();
+    }
+
+    public List<Block> toEntities(List<BlockCreationRequest> requests) {
+        return requests.stream().map(this::toEntity).toList();
+    }
+}
diff --git a/src/main/java/takima/example/agregiokata/features/block/model/BlockCreationRequest.java b/src/main/java/takima/example/agregiokata/features/block/model/BlockCreationRequest.java
new file mode 100644
index 0000000..261df5e
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/block/model/BlockCreationRequest.java
@@ -0,0 +1,6 @@
+package takima.example.agregiokata.features.block.model;
+
+import java.time.LocalTime;
+
+public record BlockCreationRequest(LocalTime startTime, LocalTime endTime, double production, double floorPrice) {
+}
diff --git a/src/main/java/takima/example/agregiokata/features/block/model/BlockDTO.java b/src/main/java/takima/example/agregiokata/features/block/model/BlockDTO.java
new file mode 100644
index 0000000..aba9b88
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/block/model/BlockDTO.java
@@ -0,0 +1,8 @@
+package takima.example.agregiokata.features.block.model;
+
+import takima.example.agregiokata.features.powerplant.model.PowerPlantOfferDTO;
+
+import java.time.LocalTime;
+
+public record BlockDTO(Long id, LocalTime start, LocalTime end, double production, double floorPrice, PowerPlantOfferDTO plant) {
+}
diff --git a/src/main/java/takima/example/agregiokata/features/block/service/SupplyBlockService.java b/src/main/java/takima/example/agregiokata/features/block/service/SupplyBlockService.java
new file mode 100644
index 0000000..924a34e
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/block/service/SupplyBlockService.java
@@ -0,0 +1,65 @@
+package takima.example.agregiokata.features.block.service;
+
+import lombok.val;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import takima.example.agregiokata.features.block.entity.Block;
+import takima.example.agregiokata.features.powerplant.entity.PowerPlant;
+import takima.example.agregiokata.features.powerplant.repository.PowerPlantRepository;
+
+import javax.naming.InsufficientResourcesException;
+import java.util.Comparator;
+import java.util.List;
+import java.util.function.Predicate;
+
+import static takima.example.agregiokata.core.exception.ErrorFactory.badRequest;
+
+@Service
+public class SupplyBlockService {
+    private final PowerPlantRepository powerPlantRepository;
+
+    public SupplyBlockService(PowerPlantRepository powerPlantRepository) {
+        this.powerPlantRepository = powerPlantRepository;
+    }
+
+    @Transactional
+    public void plantsSupplyBlocks(List<Block> blocks) {
+        val powerPlants = powerPlantRepository.availablePowerPlant();
+
+        if (powerPlants.isEmpty()) {
+            throw badRequest("There is no or not enough power plants available for the offer", new InsufficientResourcesException());
+        }
+
+        val sortedBlocks = blocks.stream()
+                .sorted(Comparator.comparingDouble(Block::getProduction).reversed())
+                .toList();
+
+        val sortedPowerPlants = powerPlants.stream()
+                .sorted(Comparator.comparingDouble(PowerPlant::getCapacityProduction).reversed())
+                .toList();
+
+        sortedBlocks.forEach(block -> {
+                    val plantToSupply = sortedPowerPlants.stream().filter(filterByAvailablePowerplant(block.getProduction()))
+                            .findFirst()
+                            .orElseThrow(() -> badRequest("There is no or not enough power plants available for the offer", new InsufficientResourcesException()));
+                    block.setPlant(plantToSupply);
+                    List<Block> plantBlocks = plantToSupply.getBlocks();
+                    plantBlocks.add(block);
+                    plantToSupply.setBlocks(plantBlocks);
+                }
+        );
+        System.out.println("t");
+    }
+
+    private static Predicate<PowerPlant> filterByAvailablePowerplant(Double powerToAdd) {
+        return plant -> plant.getCapacityProduction() >=
+                getCurrentlySuppliedPlantPowerProduction(plant) + powerToAdd;
+    }
+
+    private static double getCurrentlySuppliedPlantPowerProduction(PowerPlant plant) {
+        if (plant.getBlocks().isEmpty()) {
+            return 0.0;
+        }
+        return plant.getBlocks().stream().mapToDouble(Block::getProduction).sum();
+    }
+}
diff --git a/src/main/java/takima/example/agregiokata/features/market/model/Market.java b/src/main/java/takima/example/agregiokata/features/market/model/Market.java
new file mode 100644
index 0000000..fcb1732
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/market/model/Market.java
@@ -0,0 +1,27 @@
+package takima.example.agregiokata.features.market.model;
+
+import lombok.Getter;
+
+import java.util.Arrays;
+
+import static takima.example.agregiokata.core.exception.ErrorFactory.badRequest;
+
+@Getter
+public enum Market {
+    PRIMARY_RESERVE("Réserve Primaire"),
+    SECONDARY_RESERVE("Réserve Secondaire"),
+    FAST_RESERVE("Réserve Rapide");
+
+    private final String label;
+
+    Market(String label) {
+        this.label = label;
+    }
+
+    public static Market fromString(String value) {
+        return Arrays.stream(values())
+                .filter(market -> market.name().equalsIgnoreCase(value) || market.label.equalsIgnoreCase(value))
+                .findFirst()
+                .orElseThrow(() -> badRequest("Valeur de marché invalide", new IllegalArgumentException("Valeur de marché invalide : " + value)));
+    }
+}
diff --git a/src/main/java/takima/example/agregiokata/features/offer/controller/OfferController.java b/src/main/java/takima/example/agregiokata/features/offer/controller/OfferController.java
new file mode 100644
index 0000000..0237633
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/offer/controller/OfferController.java
@@ -0,0 +1,50 @@
+package takima.example.agregiokata.features.offer.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import takima.example.agregiokata.features.offer.model.OfferCreationRequest;
+import takima.example.agregiokata.features.offer.model.OfferDTO;
+import takima.example.agregiokata.features.offer.service.OfferService;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/offers")
+public class OfferController {
+
+    private final OfferService offerService;
+
+    public OfferController(OfferService offerService) {
+        this.offerService = offerService;
+    }
+
+    @GetMapping("/{id}")
+    public OfferDTO getById(@PathVariable Long id) {
+        return offerService.getById(id);
+    }
+
+    @GetMapping()
+    public List<OfferDTO> find(@RequestParam String market) {
+        return offerService.search(market);
+    }
+
+    @PostMapping
+    @ResponseStatus(HttpStatus.CREATED)
+    public OfferDTO create(@RequestBody OfferCreationRequest request) {
+        return offerService.create(request);
+    }
+
+    @DeleteMapping("/{id}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public void delete(@PathVariable Long id) {
+        offerService.delete(id);
+    }
+}
diff --git a/src/main/java/takima/example/agregiokata/features/offer/entity/Offer.java b/src/main/java/takima/example/agregiokata/features/offer/entity/Offer.java
new file mode 100644
index 0000000..c37cc95
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/offer/entity/Offer.java
@@ -0,0 +1,45 @@
+package takima.example.agregiokata.features.offer.entity;
+
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import takima.example.agregiokata.features.block.entity.Block;
+import takima.example.agregiokata.features.market.model.Market;
+
+import java.util.List;
+
+@Builder(toBuilder = true)
+@ToString
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@EqualsAndHashCode(onlyExplicitlyIncluded = true)
+@Entity
+@Table(name = "offer")
+public class Offer {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @EqualsAndHashCode.Include
+    private Long id;
+
+    @Enumerated(EnumType.STRING)
+    private Market market;
+
+    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "offer")
+    private List<Block> blocks;
+}
diff --git a/src/main/java/takima/example/agregiokata/features/offer/mapper/OfferMapper.java b/src/main/java/takima/example/agregiokata/features/offer/mapper/OfferMapper.java
new file mode 100644
index 0000000..717baef
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/offer/mapper/OfferMapper.java
@@ -0,0 +1,23 @@
+package takima.example.agregiokata.features.offer.mapper;
+
+import org.springframework.stereotype.Component;
+import takima.example.agregiokata.features.block.mapper.BlockMapper;
+import takima.example.agregiokata.features.offer.entity.Offer;
+import takima.example.agregiokata.features.offer.model.OfferDTO;
+
+@Component
+public class OfferMapper {
+
+    private final BlockMapper blockMapper;
+
+    public OfferMapper(BlockMapper blockMapper) {
+        this.blockMapper = blockMapper;
+    }
+
+    public OfferDTO toDTO(Offer offer) {
+        return new OfferDTO(offer.getId(),
+                offer.getMarket().getLabel(),
+                offer.getBlocks().stream().map(blockMapper::toDTO).toList()
+        );
+    }
+}
diff --git a/src/main/java/takima/example/agregiokata/features/offer/model/OfferCreationRequest.java b/src/main/java/takima/example/agregiokata/features/offer/model/OfferCreationRequest.java
new file mode 100644
index 0000000..250bad3
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/offer/model/OfferCreationRequest.java
@@ -0,0 +1,9 @@
+package takima.example.agregiokata.features.offer.model;
+
+import takima.example.agregiokata.features.block.model.BlockCreationRequest;
+
+import java.util.List;
+
+public record OfferCreationRequest(String market,
+                                   List<BlockCreationRequest> blocks) {
+}
diff --git a/src/main/java/takima/example/agregiokata/features/offer/model/OfferDTO.java b/src/main/java/takima/example/agregiokata/features/offer/model/OfferDTO.java
new file mode 100644
index 0000000..49f2397
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/offer/model/OfferDTO.java
@@ -0,0 +1,8 @@
+package takima.example.agregiokata.features.offer.model;
+
+import takima.example.agregiokata.features.block.model.BlockDTO;
+
+import java.util.List;
+
+public record OfferDTO(Long id, String market, List<BlockDTO> blocks) {
+}
\ No newline at end of file
diff --git a/src/main/java/takima/example/agregiokata/features/offer/repository/OfferCustomRepository.java b/src/main/java/takima/example/agregiokata/features/offer/repository/OfferCustomRepository.java
new file mode 100644
index 0000000..1207faa
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/offer/repository/OfferCustomRepository.java
@@ -0,0 +1,29 @@
+package takima.example.agregiokata.features.offer.repository;
+
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+import takima.example.agregiokata.features.market.model.Market;
+import takima.example.agregiokata.features.offer.entity.Offer;
+
+import java.util.List;
+import java.util.Optional;
+
+@Repository
+public interface OfferCustomRepository {
+
+    @Query(value = """
+                select o from Offer o
+                    join fetch o.blocks b
+                    join fetch b.plant
+                    where o.id = :id
+            """)
+    public Optional<Offer> searchById(Long id);
+
+    @Query("""
+                select o from Offer o
+                    join fetch o.blocks b
+                    join fetch b.plant
+                    where o.market = :market
+            """)
+    public List<Offer> findAllByMarket(Market market);
+}
diff --git a/src/main/java/takima/example/agregiokata/features/offer/repository/OfferRepository.java b/src/main/java/takima/example/agregiokata/features/offer/repository/OfferRepository.java
new file mode 100644
index 0000000..da075c5
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/offer/repository/OfferRepository.java
@@ -0,0 +1,10 @@
+package takima.example.agregiokata.features.offer.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import takima.example.agregiokata.features.offer.entity.Offer;
+
+//TODO créer une interface pour bouger les findbyID, findAll que je redéfini et extends ici
+@Repository
+public interface OfferRepository extends JpaRepository<Offer, Long>, OfferCustomRepository {
+}
diff --git a/src/main/java/takima/example/agregiokata/features/offer/service/OfferService.java b/src/main/java/takima/example/agregiokata/features/offer/service/OfferService.java
new file mode 100644
index 0000000..8d5b164
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/offer/service/OfferService.java
@@ -0,0 +1,87 @@
+package takima.example.agregiokata.features.offer.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import takima.example.agregiokata.features.block.entity.Block;
+import takima.example.agregiokata.features.block.mapper.BlockMapper;
+import takima.example.agregiokata.features.block.model.BlockCreationRequest;
+import takima.example.agregiokata.features.block.service.SupplyBlockService;
+import takima.example.agregiokata.features.market.model.Market;
+import takima.example.agregiokata.features.offer.entity.Offer;
+import takima.example.agregiokata.features.offer.mapper.OfferMapper;
+import takima.example.agregiokata.features.offer.model.OfferCreationRequest;
+import takima.example.agregiokata.features.offer.model.OfferDTO;
+import takima.example.agregiokata.features.offer.repository.OfferRepository;
+import takima.example.agregiokata.features.powerplant.repository.PowerPlantRepository;
+
+import java.util.List;
+
+import static takima.example.agregiokata.core.exception.ErrorFactory.notFound;
+
+@Service
+public class OfferService {
+
+    private final OfferRepository offerRepository;
+    private final OfferMapper offerMapper;
+    private final BlockMapper blockMapper;
+    private final PowerPlantRepository powerPlantRepository;
+    private final SupplyBlockService supplyBlockService;
+
+    public OfferService(OfferRepository offerRepository, OfferMapper offerMapper, BlockMapper blockMapper,
+                        PowerPlantRepository powerPlantRepository, SupplyBlockService supplyBlockService) {
+        this.offerRepository = offerRepository;
+        this.offerMapper = offerMapper;
+        this.blockMapper = blockMapper;
+        this.powerPlantRepository = powerPlantRepository;
+        this.supplyBlockService = supplyBlockService;
+    }
+
+    @Transactional(readOnly = true)
+    public OfferDTO getById(Long id) {
+        return offerRepository.searchById(id)
+                .map(offerMapper::toDTO)
+                .orElseThrow(() -> notFound("Power plant with id " + id + " not found"));
+    }
+
+    @Transactional(readOnly = true)
+    public List<OfferDTO> search(String search) {
+        var market = Market.fromString(search);
+        List<Offer> offers;
+        if (market == null) {
+            offers = offerRepository.findAll();
+        } else {
+            offers = offerRepository.findAllByMarket(market);
+        }
+        return offers
+                .stream()
+                .map(offerMapper::toDTO)
+                .toList();
+    }
+
+    @Transactional
+    public OfferDTO create(OfferCreationRequest request) {
+        var offer = Offer.builder()
+                .market(Market.fromString(request.market()))
+                .build();
+        var blocks = createBlocksWithPowerPlant(request.blocks());
+        offer.setBlocks(blocks);
+        blocks.forEach(block -> {
+            block.setOffer(offer);
+        });
+        var offerSaved = offerRepository.save(offer);
+        return offerMapper.toDTO(offerSaved);
+    }
+
+    @Transactional
+    public List<Block> createBlocksWithPowerPlant(List<BlockCreationRequest> requests) {
+        var blocks = blockMapper.toEntities(requests);
+        supplyBlockService.plantsSupplyBlocks(blocks);
+        return blocks;
+    }
+
+
+    @Transactional
+    public void delete(Long id) {
+        offerRepository.deleteById(id);
+    }
+}
diff --git a/src/main/java/takima/example/agregiokata/features/powerplant/controller/PowerPlantController.java b/src/main/java/takima/example/agregiokata/features/powerplant/controller/PowerPlantController.java
new file mode 100644
index 0000000..a968358
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/powerplant/controller/PowerPlantController.java
@@ -0,0 +1,49 @@
+package takima.example.agregiokata.features.powerplant.controller;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestController;
+import takima.example.agregiokata.features.powerplant.model.PowerPlantCreationRequest;
+import takima.example.agregiokata.features.powerplant.model.PowerPlantDTO;
+import takima.example.agregiokata.features.powerplant.service.PowerPlantService;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/powerplants")
+public class PowerPlantController {
+    private final PowerPlantService powerPlantService;
+
+    public PowerPlantController(PowerPlantService powerPlantService) {
+        this.powerPlantService = powerPlantService;
+    }
+
+    @GetMapping("/{id}")
+    public PowerPlantDTO getById(@PathVariable Long id) {
+        return powerPlantService.getById(id);
+    }
+
+    @GetMapping()
+    public List<PowerPlantDTO> search(@RequestParam String market) {
+        return powerPlantService.search(market);
+    }
+
+    @PostMapping
+    @ResponseStatus(HttpStatus.CREATED)
+    public PowerPlantDTO create(@RequestBody PowerPlantCreationRequest request) {
+        return powerPlantService.create(request);
+    }
+
+    @DeleteMapping("/{id}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public void delete(@PathVariable Long id) {
+        powerPlantService.delete(id);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/takima/example/agregiokata/features/powerplant/entity/PowerPlant.java b/src/main/java/takima/example/agregiokata/features/powerplant/entity/PowerPlant.java
new file mode 100644
index 0000000..0d83bc8
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/powerplant/entity/PowerPlant.java
@@ -0,0 +1,50 @@
+package takima.example.agregiokata.features.powerplant.entity;
+
+import jakarta.persistence.CascadeType;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.FetchType;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.OneToMany;
+import jakarta.persistence.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import takima.example.agregiokata.features.block.entity.Block;
+import takima.example.agregiokata.features.powerplant.model.PowerPlantType;
+
+import java.util.List;
+
+@Builder(toBuilder = true)
+@ToString
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@EqualsAndHashCode(onlyExplicitlyIncluded = true)
+@Entity
+@Table(name = "powerplant")
+public class PowerPlant {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @EqualsAndHashCode.Include
+    private Long id;
+
+    private String name;
+
+    @Enumerated(EnumType.STRING)
+    private PowerPlantType type;
+
+    private Double capacityProduction;
+
+    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "plant")
+    private List<Block> blocks;
+
+}
diff --git a/src/main/java/takima/example/agregiokata/features/powerplant/mapper/PowerPlantMapper.java b/src/main/java/takima/example/agregiokata/features/powerplant/mapper/PowerPlantMapper.java
new file mode 100644
index 0000000..ed1960c
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/powerplant/mapper/PowerPlantMapper.java
@@ -0,0 +1,43 @@
+package takima.example.agregiokata.features.powerplant.mapper;
+
+import org.springframework.stereotype.Component;
+import takima.example.agregiokata.features.powerplant.entity.PowerPlant;
+import takima.example.agregiokata.features.powerplant.model.PowerPlantCreationRequest;
+import takima.example.agregiokata.features.powerplant.model.PowerPlantDTO;
+import takima.example.agregiokata.features.powerplant.model.PowerPlantOfferDTO;
+
+import java.util.List;
+
+@Component
+public class PowerPlantMapper {
+    public PowerPlantDTO toDTO(PowerPlant powerPlant) {
+        return new PowerPlantDTO(
+                powerPlant.getId(),
+                powerPlant.getName(),
+                powerPlant.getType(),
+                powerPlant.getCapacityProduction()
+        );
+    }
+
+    public PowerPlantOfferDTO toOfferDTO(PowerPlant powerPlant) {
+        return new PowerPlantOfferDTO(
+                powerPlant.getId(),
+                powerPlant.getName(),
+                powerPlant.getType()
+        );
+    }
+
+    public PowerPlant toEntity(PowerPlantCreationRequest request) {
+        return PowerPlant.builder()
+                .name(request.name())
+                .type(request.type())
+                .capacityProduction(request.capacityProduction())
+                .build();
+    }
+
+    public List<PowerPlantDTO> toDTOs(List<PowerPlant> powerPlants) {
+        return powerPlants.stream()
+                .map(this::toDTO)
+                .toList();
+    }
+}
diff --git a/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantCreationRequest.java b/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantCreationRequest.java
new file mode 100644
index 0000000..c30b98a
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantCreationRequest.java
@@ -0,0 +1,4 @@
+package takima.example.agregiokata.features.powerplant.model;
+
+public record PowerPlantCreationRequest(String name, PowerPlantType type, double capacityProduction) {
+}
diff --git a/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantDTO.java b/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantDTO.java
new file mode 100644
index 0000000..f6f35b5
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantDTO.java
@@ -0,0 +1,5 @@
+package takima.example.agregiokata.features.powerplant.model;
+
+public record PowerPlantDTO(Long id, String name, PowerPlantType type, double capacityProduction) {
+
+}
\ No newline at end of file
diff --git a/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantOfferDTO.java b/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantOfferDTO.java
new file mode 100644
index 0000000..dac87e6
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantOfferDTO.java
@@ -0,0 +1,4 @@
+package takima.example.agregiokata.features.powerplant.model;
+
+public record PowerPlantOfferDTO(Long id, String name, PowerPlantType type) {
+}
diff --git a/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantType.java b/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantType.java
new file mode 100644
index 0000000..c5598cb
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/powerplant/model/PowerPlantType.java
@@ -0,0 +1,7 @@
+package takima.example.agregiokata.features.powerplant.model;
+
+public enum PowerPlantType {
+    SOLAR,
+    WIND,
+    HYDRO
+}
diff --git a/src/main/java/takima/example/agregiokata/features/powerplant/repository/PowerPlantRepository.java b/src/main/java/takima/example/agregiokata/features/powerplant/repository/PowerPlantRepository.java
new file mode 100644
index 0000000..47d3d3c
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/powerplant/repository/PowerPlantRepository.java
@@ -0,0 +1,30 @@
+package takima.example.agregiokata.features.powerplant.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+import takima.example.agregiokata.features.market.model.Market;
+import takima.example.agregiokata.features.powerplant.entity.PowerPlant;
+
+import java.util.List;
+
+@Repository
+public interface PowerPlantRepository extends JpaRepository<PowerPlant, Long> {
+
+    @Query("""
+            select p from PowerPlant p
+                        left join p.blocks b
+                        group by p.id
+                        having coalesce(sum(b.production), 0) < p.capacityProduction
+            """)
+    public List<PowerPlant> availablePowerPlant();
+
+
+    @Query("""
+                select distinct pp from PowerPlant pp
+                    left join pp.blocks b
+                    left join b.offer o
+                    where o.market = :market
+            """)
+    public List<PowerPlant> findAllByMarket(Market market);
+}
diff --git a/src/main/java/takima/example/agregiokata/features/powerplant/service/PowerPlantService.java b/src/main/java/takima/example/agregiokata/features/powerplant/service/PowerPlantService.java
new file mode 100644
index 0000000..d0a5e09
--- /dev/null
+++ b/src/main/java/takima/example/agregiokata/features/powerplant/service/PowerPlantService.java
@@ -0,0 +1,53 @@
+package takima.example.agregiokata.features.powerplant.service;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import takima.example.agregiokata.features.market.model.Market;
+import takima.example.agregiokata.features.powerplant.entity.PowerPlant;
+import takima.example.agregiokata.features.powerplant.mapper.PowerPlantMapper;
+import takima.example.agregiokata.features.powerplant.model.PowerPlantCreationRequest;
+import takima.example.agregiokata.features.powerplant.model.PowerPlantDTO;
+import takima.example.agregiokata.features.powerplant.repository.PowerPlantRepository;
+
+import java.util.List;
+
+import static takima.example.agregiokata.core.exception.ErrorFactory.notFound;
+
+@Service
+public class PowerPlantService {
+    private final PowerPlantRepository powerPlantRepository;
+    private final PowerPlantMapper powerPlantMapper;
+
+    public PowerPlantService(PowerPlantRepository powerPlantRepository, PowerPlantMapper powerPlantMapper) {
+        this.powerPlantRepository = powerPlantRepository;
+        this.powerPlantMapper = powerPlantMapper;
+    }
+
+    @Transactional(readOnly = true)
+    public List<PowerPlantDTO> search(String searchMarket) {
+        var market = Market.fromString(searchMarket);
+        var result = powerPlantRepository.findAllByMarket(market);
+        if(result.isEmpty()) {
+            return List.of();
+        }
+        return result.stream().map(powerPlantMapper::toDTO).toList();
+    }
+
+    @Transactional(readOnly = true)
+    public PowerPlantDTO getById(Long id) {
+        return powerPlantRepository.findById(id)
+                .map(powerPlantMapper::toDTO)
+                .orElseThrow(() -> notFound("Power plant with id " + id + " not found"));
+    }
+
+    @Transactional
+    public PowerPlantDTO create(PowerPlantCreationRequest request) {
+        PowerPlant powerPlant = powerPlantMapper.toEntity(request);
+        return powerPlantMapper.toDTO(powerPlantRepository.save(powerPlant));
+    }
+
+    @Transactional
+    public void delete(Long id) {
+        powerPlantRepository.deleteById(id);
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 63237dd..2c44b0f 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,3 +1,18 @@
 spring:
   application:
     name: agregio-kata
+  datasource:
+    url: ${DB_URL:jdbc:postgresql://localhost:5432/agregio-db}
+    username: ${POSTGRES_USER:takima}
+    password: ${POSTGRES_PASSWORD:takima}
+  jpa:
+    properties:
+      hibernate:
+        ddl-auto: validate
+    open-in-view: false
+  flyway:
+    schemas: public
+
+logging:
+  level:
+    sql: debug
\ No newline at end of file
diff --git a/src/main/resources/db/migration/V1__Init_database.sql b/src/main/resources/db/migration/V1__Init_database.sql
new file mode 100644
index 0000000..0fbc4ae
--- /dev/null
+++ b/src/main/resources/db/migration/V1__Init_database.sql
@@ -0,0 +1,28 @@
+CREATE TABLE IF NOT EXISTS public.powerplant (
+    id                  BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    name                VARCHAR(255)     NOT NULL,
+    type                VARCHAR(25)      NOT NULL,
+    capacity_production DOUBLE PRECISION NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS public.offer (
+    id     BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    market VARCHAR(25) NOT NULL
+);
+
+CREATE TABLE IF NOT EXISTS public.block (
+    id            BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+    start_time    TIME             NOT NULL,
+    end_time      TIME             NOT NULL,
+    production    DOUBLE PRECISION NOT NULL,
+    floor_price   DOUBLE PRECISION NOT NULL,
+    offer_id      BIGINT           NOT NULL,
+    powerplant_id BIGINT           NOT NULL,
+    FOREIGN KEY (offer_id) REFERENCES offer (id) ON DELETE CASCADE,
+    FOREIGN KEY (powerplant_id) REFERENCES powerplant (id) ON DELETE CASCADE
+);
+
+
+
+
+
diff --git a/src/test/java/takima/example/agregiokata/TestcontainersConfiguration.java b/src/test/java/takima/example/agregiokata/TestcontainersConfiguration.java
index 3bb8435..b6b5feb 100644
--- a/src/test/java/takima/example/agregiokata/TestcontainersConfiguration.java
+++ b/src/test/java/takima/example/agregiokata/TestcontainersConfiguration.java
@@ -12,7 +12,8 @@ class TestcontainersConfiguration {
     @Bean
     @ServiceConnection
     PostgreSQLContainer<?> postgresContainer() {
-        return new PostgreSQLContainer<>(DockerImageName.parse("postgres:latest"));
+        var container = new PostgreSQLContainer<>(DockerImageName.parse("postgres:latest"));
+        container.withReuse(true);
+        return container;
     }
-
 }
diff --git a/src/test/java/takima/example/agregiokata/data/TestData.java b/src/test/java/takima/example/agregiokata/data/TestData.java
new file mode 100644
index 0000000..937d786
--- /dev/null
+++ b/src/test/java/takima/example/agregiokata/data/TestData.java
@@ -0,0 +1,56 @@
+package takima.example.agregiokata.data;
+
+import takima.example.agregiokata.features.block.entity.Block;
+import takima.example.agregiokata.features.powerplant.entity.PowerPlant;
+import takima.example.agregiokata.features.powerplant.model.PowerPlantType;
+
+import java.time.LocalTime;
+import java.util.ArrayList;
+
+public class TestData {
+
+    public static final Block blockPlantless1 = Block.builder()
+            .startTime(LocalTime.of(8, 0))
+            .endTime(LocalTime.of(9, 0))
+            .floorPrice(100.0)
+            .production(300.0)
+            .build();
+
+    public static final Block blockPlantless2 = Block.builder()
+            .startTime(LocalTime.of(9, 0))
+            .endTime(LocalTime.of(12, 0))
+            .floorPrice(300.0)
+            .production(400.0)
+            .build();
+
+    public static final Block blockPlantless3 = Block.builder()
+            .startTime(LocalTime.of(12, 0))
+            .endTime(LocalTime.of(15, 0))
+            .floorPrice(50.0)
+            .production(100.0)
+            .build();
+
+    public static PowerPlant solarPlant = PowerPlant.builder()
+            .id(1L)
+            .name("Solar Plant")
+            .type(PowerPlantType.SOLAR)
+            .capacityProduction(500.0)
+            .blocks(new ArrayList<>())
+            .build();
+
+    public static final PowerPlant windPlant = PowerPlant.builder()
+            .id(2L)
+            .name("Wind Plant")
+            .type(PowerPlantType.WIND)
+            .capacityProduction(400.0)
+            .blocks(new ArrayList<>())
+            .build();
+
+    public static PowerPlant hydroPlant = PowerPlant.builder()
+            .name("Hydro Plant")
+            .type(PowerPlantType.HYDRO)
+            .capacityProduction(500.0)
+            .blocks(new ArrayList<>())
+            .build();
+
+}
diff --git a/src/test/java/takima/example/agregiokata/features/block/service/SupplyBlockServiceTest.java b/src/test/java/takima/example/agregiokata/features/block/service/SupplyBlockServiceTest.java
new file mode 100644
index 0000000..a3013d8
--- /dev/null
+++ b/src/test/java/takima/example/agregiokata/features/block/service/SupplyBlockServiceTest.java
@@ -0,0 +1,135 @@
+package takima.example.agregiokata.features.block.service;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.ErrorResponseException;
+import takima.example.agregiokata.features.block.entity.Block;
+import takima.example.agregiokata.features.powerplant.entity.PowerPlant;
+import takima.example.agregiokata.features.powerplant.model.PowerPlantType;
+import takima.example.agregiokata.features.powerplant.repository.PowerPlantRepository;
+
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+@ExtendWith(MockitoExtension.class)
+class SupplyBlockServiceTest {
+
+    @Mock
+    private PowerPlantRepository powerPlantRepository;
+
+    @InjectMocks
+    private SupplyBlockService supplyBlockService;
+
+    private Block blockPlantless1;
+    private Block blockPlantless2;
+    private Block blockPlantless3;
+
+    private PowerPlant solarPlant;
+    private PowerPlant windPlant;
+
+    @BeforeEach
+    void setUp() {
+        blockPlantless1 = Block.builder()
+                .startTime(LocalTime.of(8, 0))
+                .endTime(LocalTime.of(9, 0))
+                .floorPrice(100.0)
+                .production(300.0)
+                .build();
+
+        blockPlantless2 = Block.builder()
+                .startTime(LocalTime.of(9, 0))
+                .endTime(LocalTime.of(12, 0))
+                .floorPrice(300.0)
+                .production(400.0)
+                .build();
+
+        blockPlantless3 = Block.builder()
+                .startTime(LocalTime.of(12, 0))
+                .endTime(LocalTime.of(15, 0))
+                .floorPrice(50.0)
+                .production(100.0)
+                .build();
+
+        solarPlant = PowerPlant.builder()
+                .id(1L)
+                .name("Solar Plant")
+                .type(PowerPlantType.SOLAR)
+                .capacityProduction(500.0)
+                .blocks(new ArrayList<>())
+                .build();
+
+        windPlant = PowerPlant.builder()
+                .id(2L)
+                .name("Wind Plant")
+                .type(PowerPlantType.WIND)
+                .capacityProduction(400.0)
+                .blocks(new ArrayList<>())
+                .build();
+    }
+
+    @Test
+    @DisplayName("Should attribute to all blocks a power plant when there is enough")
+    void plantsSupplyBlocks() {
+        var blocksToSupply = Arrays.asList(blockPlantless1, blockPlantless2, blockPlantless3);
+        var powerPlantsAvailable = Arrays.asList(solarPlant, windPlant);
+
+        when(powerPlantRepository.availablePowerPlant()).thenReturn(powerPlantsAvailable);
+
+        supplyBlockService.plantsSupplyBlocks(blocksToSupply);
+
+        assertEquals(windPlant, blocksToSupply.getFirst().getPlant());
+        assertEquals(1, windPlant.getBlocks().size());
+        assertEquals(solarPlant, blocksToSupply.get(1).getPlant());
+        assertEquals(2, solarPlant.getBlocks().size());
+        verify(powerPlantRepository, times(1)).availablePowerPlant();
+    }
+
+
+    @Test
+    @DisplayName("Should throw a bad request if no power plant are available")
+    void plantsSupplyBlocksNoPowerPlant() {
+        var blocksToSupply = Arrays.asList(blockPlantless1, blockPlantless2, blockPlantless3);
+        when(powerPlantRepository.availablePowerPlant()).thenReturn(List.of());
+
+        ErrorResponseException exception = assertThrows(ErrorResponseException.class, () -> {
+            supplyBlockService.plantsSupplyBlocks(blocksToSupply);
+        });
+
+        assertEquals("There is no or not enough power plants available for the offer", exception.getBody().getDetail());
+        assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
+        verify(powerPlantRepository, times(1)).availablePowerPlant();
+    }
+
+    @Test
+    @DisplayName("Should throw a bad request if power plant is insufficient during the supply")
+    void plantsSupplyBlocksInvalidPowerPlant() {
+        var blocksToSupply = Arrays.asList(blockPlantless1, blockPlantless2);
+        var powerPlantsAvailable = Collections.singletonList(solarPlant);
+
+        when(powerPlantRepository.availablePowerPlant()).thenReturn(powerPlantsAvailable);
+
+        ErrorResponseException exception = assertThrows(ErrorResponseException.class, () -> {
+            supplyBlockService.plantsSupplyBlocks(blocksToSupply);
+        });
+
+        assertEquals("There is no or not enough power plants available for the offer", exception.getBody().getDetail());
+        assertEquals(HttpStatus.BAD_REQUEST, exception.getStatusCode());
+        verify(powerPlantRepository, times(1)).availablePowerPlant();
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/takima/example/agregiokata/features/powerplant/repository/PowerPlantRepositoryTest.java b/src/test/java/takima/example/agregiokata/features/powerplant/repository/PowerPlantRepositoryTest.java
new file mode 100644
index 0000000..33bf50a
--- /dev/null
+++ b/src/test/java/takima/example/agregiokata/features/powerplant/repository/PowerPlantRepositoryTest.java
@@ -0,0 +1,145 @@
+package takima.example.agregiokata.features.powerplant.repository;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.jdbc.Sql;
+import org.testcontainers.utility.TestcontainersConfiguration;
+import takima.example.agregiokata.features.block.entity.Block;
+import takima.example.agregiokata.features.market.model.Market;
+import takima.example.agregiokata.features.offer.entity.Offer;
+import takima.example.agregiokata.features.offer.repository.OfferRepository;
+import takima.example.agregiokata.features.powerplant.entity.PowerPlant;
+import takima.example.agregiokata.features.powerplant.model.PowerPlantType;
+
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest
+@Import(TestcontainersConfiguration.class)
+@Sql(statements = "TRUNCATE TABLE powerplant, block, offer RESTART IDENTITY CASCADE; " +
+        "ALTER SEQUENCE powerplant_id_seq RESTART WITH 1; " +
+        "ALTER SEQUENCE block_id_seq RESTART WITH 1 ;" +
+        "ALTER SEQUENCE offer_id_seq RESTART WITH 1;", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
+class PowerPlantRepositoryTest {
+
+    @Autowired
+    private PowerPlantRepository powerPlantRepository;
+
+    @Autowired
+    private OfferRepository offerRepository;
+
+    private PowerPlant plantNotAvailable;
+    private PowerPlant hydroPlant;
+    private PowerPlant solarPlant;
+
+    private Block blockPlantless1;
+    private Block blockPlantless3;
+    private Block blockPlantless2;
+
+    private Offer offer;
+    private Offer offer2;
+
+
+    @BeforeEach
+    void setUp() {
+        offer = new Offer().toBuilder()
+                .market(Market.FAST_RESERVE)
+                .build();
+
+        offer2 = new Offer().toBuilder()
+                .market(Market.SECONDARY_RESERVE)
+                .build();
+
+        blockPlantless1 = Block.builder()
+                .startTime(LocalTime.of(8, 0))
+                .endTime(LocalTime.of(9, 0))
+                .floorPrice(100.0)
+                .production(300.0)
+                .offer(offer)
+                .build();
+
+        blockPlantless2 = Block.builder()
+                .startTime(LocalTime.of(9, 0))
+                .endTime(LocalTime.of(12, 0))
+                .floorPrice(300.0)
+                .production(400.0)
+                .offer(offer2)
+                .build();
+
+        blockPlantless3 = Block.builder()
+                .startTime(LocalTime.of(12, 0))
+                .endTime(LocalTime.of(15, 0))
+                .floorPrice(50.0)
+                .production(100.0)
+                .offer(offer)
+                .build();
+
+        hydroPlant = PowerPlant.builder()
+                .name("Hydro Plant")
+                .type(PowerPlantType.HYDRO)
+                .capacityProduction(500.0)
+                .blocks(new ArrayList<>())
+                .build();
+
+        solarPlant = PowerPlant.builder()
+                .name("Solar Plant")
+                .type(PowerPlantType.SOLAR)
+                .capacityProduction(400.0)
+                .blocks(List.of(blockPlantless2))
+                .build();
+
+        plantNotAvailable = PowerPlant.builder()
+                .name("Plant Not Available")
+                .type(PowerPlantType.SOLAR)
+                .capacityProduction(400.0)
+                .blocks(List.of(blockPlantless1, blockPlantless3))
+                .build();
+    }
+
+    @Test
+    @DisplayName("should return all plant that still that have not production completely supplied")
+    void availablePowerPlant() {
+        offerRepository.save(offer);
+        var plantAvailable = powerPlantRepository.save(hydroPlant);
+        plantNotAvailable.getBlocks().forEach(block -> {
+            block.setPlant(plantNotAvailable);
+        });
+        var plantAllSupplied = powerPlantRepository.save(plantNotAvailable);
+
+        List<PowerPlant> availablePlants = powerPlantRepository.availablePowerPlant();
+
+        assertThat(availablePlants).hasSize(1);
+        assertThat(availablePlants).extracting(PowerPlant::getName)
+                .containsExactly(hydroPlant.getName());
+    }
+
+    @Test
+    @DisplayName("should return all plant that still that have not production completely supplied")
+    void findAllByMarket() {
+        offerRepository.save(offer);
+        offerRepository.save(offer2);
+
+        solarPlant.getBlocks().forEach(block -> {
+            block.setPlant(solarPlant);
+        });
+        var solarPlantSaved = powerPlantRepository.save(solarPlant);
+
+        plantNotAvailable.getBlocks().forEach(block -> {
+            block.setPlant(plantNotAvailable);
+        });
+        var plantNotAvailableSaved = powerPlantRepository.save(plantNotAvailable);
+
+        List<PowerPlant> plantsByMarket = powerPlantRepository.findAllByMarket(Market.FAST_RESERVE);
+
+        assertThat(plantsByMarket).hasSize(1);
+        assertThat(plantsByMarket).extracting(PowerPlant::getName)
+                .containsExactly(plantNotAvailable.getName());
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml
new file mode 100644
index 0000000..621329a
--- /dev/null
+++ b/src/test/resources/application.yml
@@ -0,0 +1,5 @@
+spring:
+  datasource:
+    url: jdbc:tc:postgresql:15.7:///agregio-db
+    username: takima
+    password: takima
\ No newline at end of file
-- 
GitLab