Repository: folio-org/mod-mosaic
Pull request #5: [MODMO-8] Return FOLIO POL number and error messages when needed
Author: login: Saba-Zedginidze-EPAM
Created: 2025-04-10T16:08:03Z | Merged: 2025-04-14T17:42:00Z

### **Purpose**
[[MODMO-8] Return FOLIO POL number and error messages when needed](https://folio-org.atlassian.net/browse/MODMO-8)

### **Approach**
- Add basic necessary dependencies
- Add openapi with spring docs generator
- Add raml-utils and acq-models submodule
- Set up plugin for API generation
- Add necessary schemas and API spec file
- Add Orders controller, service and client (Needs mapping logic for mosaic orders)
- Add controller advice for exception handling
- Add and setup tests to run during jenkins build 

### **Pre-Review Checklist**
- [x] **Self-reviewed Code** — Reviewed code for issues, unnecessary parts, and overall quality.
- [ ] **Change Notes** — NEWS.md updated with clear description and issue key.
- [ ] **Testing** — Confirmed changes were tested locally or on dev environment.
- [ ] **Breaking Changes** — Handled all required actions if changes affect API, DB, or interface versions.
  - [ ] API/schema changes
  - [ ] Interface version updates
  - [ ] DB schema changes / migration scripts
- [ ] **New Properties / Environment Variables** — Updated README.md if new configs were added.
- [ ] **Environment Recreation Test (if needed)** — Verified that environment can be recreated successfully.



Diff:
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..53c3f6d
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,7 @@
+[submodule "ramls/acq-models"]
+	path = ramls/acq-models
+	url = https://github.com/folio-org/acq-models
+[submodule "ramls/raml-util"]
+	path = ramls/raml-util
+	url = https://github.com/folio-org/raml
+	branch = raml1.0
diff --git a/descriptors/ModuleDescriptor-template.json b/descriptors/ModuleDescriptor-template.json
index bef4d8f..f4852ca 100644
--- a/descriptors/ModuleDescriptor-template.json
+++ b/descriptors/ModuleDescriptor-template.json
@@ -1,8 +1,45 @@
 {
   "id": "@artifactId@-@version@",
   "name": "Folio mod-mosaic module",
-  "requires": [],
+  "requires": [
+    {
+      "id": "orders",
+      "version": "12.0"
+    },
+    {
+      "id": "order-lines",
+      "version": "3.0"
+    },
+    {
+      "id": "organizations.organizations",
+      "version": "1.1"
+    },
+    {
+      "id": "finance.expense-classes",
+      "version": "3.0"
+    },
+    {
+      "id": "finance.funds",
+      "version": "3.0"
+    }
+  ],
   "provides": [
+    {
+      "id": "mosaic",
+      "version": "1.0",
+      "handlers": [
+        {
+          "methods": ["POST"],
+          "pathPattern": "/orders",
+          "permissionsRequired": [
+            "mosaic.orders.item.post"
+          ],
+          "modulePermissions": [
+            "orders.item.post"
+          ]
+        }
+      ]
+    },
     {
       "id": "_tenant",
       "version": "2.0",
@@ -20,7 +57,21 @@
       ]
     }
   ],
-  "permissionSets": [],
+  "permissionSets": [
+    {
+      "permissionName": "mosaic.all",
+      "displayName": "Mosaic API module - all permissions",
+      "description": "All permissions for mosaic module",
+      "subPermissions": [
+        "mosaic.orders.item.post"
+      ]
+    },
+    {
+      "permissionName": "mosaic.orders.item.post",
+      "displayName": "post mosaic orders",
+      "description": "Create Mosaic orders"
+    }
+  ],
   "launchDescriptor": {
     "dockerImage": "@artifactId@:@version@",
     "dockerPull": false,
diff --git a/pom.xml b/pom.xml
index aed9fb7..97f2de5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,15 +37,33 @@
     </sonar.exclusions>
     <argLine />
 
-    <!--Folio dependencies properties-->
+    <!-- Other properties -->
+    <generated-sources.dir>${project.build.directory}/generated-sources</generated-sources.dir>
+
+    <!-- Jsonschema2pojo -->
+    <jsonschema2pojo.output>${generated-sources.dir}/src/main/java</jsonschema2pojo.output>
+    <jsonschema2pojo.version>1.2.2</jsonschema2pojo.version>
+
+    <!-- OpenAPI generator -->
+    <openapi-generator.file>${project.basedir}/src/main/resources/swagger.api/mosaic.yaml</openapi-generator.file>
+    <openapi-generator.output>${generated-sources.dir}</openapi-generator.output>
+    <openapi-generator.version>7.12.0</openapi-generator.version>
+    <springdoc-openapi-ui.version>1.8.0</springdoc-openapi-ui.version>
+
+    <!-- Folio dependencies properties -->
     <folio-module-descriptor-validator.version>1.0.1</folio-module-descriptor-validator.version>
     <folio-spring-base.version>9.0.1</folio-spring-base.version>
 
-    <!--Maven plugin dependencies-->
+    <!-- Other dependencies -->
+    <lombok.version>1.18.38</lombok.version>
+
+    <!-- Maven plugin dependencies -->
     <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
     <maven-release-plugin.version>3.1.1</maven-release-plugin.version>
     <maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
+    <maven-enforcer-plugin.version>3.5.0</maven-enforcer-plugin.version>
     <copy-rename-maven-plugin.version>1.0.1</copy-rename-maven-plugin.version>
+    <exec-maven-plugin.version>3.5.0</exec-maven-plugin.version>
   </properties>
 
   <dependencies>
@@ -54,6 +72,7 @@
       <artifactId>folio-spring-base</artifactId>
       <version>${folio-spring-base.version}</version>
     </dependency>
+
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
@@ -64,15 +83,29 @@
         </exclusion>
       </exclusions>
     </dependency>
+
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-actuator</artifactId>
-      <exclusions>
-        <exclusion>
-          <groupId>org.springframework.boot</groupId>
-          <artifactId>spring-boot-starter-logging</artifactId>
-        </exclusion>
-      </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>org.springdoc</groupId>
+      <artifactId>springdoc-openapi-ui</artifactId>
+      <version>${springdoc-openapi-ui.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.projectlombok</groupId>
+      <artifactId>lombok</artifactId>
+      <version>${lombok.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <!-- Test dependencies -->
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-test</artifactId>
     </dependency>
   </dependencies>
 
@@ -91,6 +124,7 @@
           </execution>
         </executions>
       </plugin>
+
       <plugin>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-maven-plugin</artifactId>
@@ -125,6 +159,7 @@
           </execution>
         </executions>
       </plugin>
+
       <plugin>
         <groupId>com.coderplus.maven.plugins</groupId>
         <artifactId>copy-rename-maven-plugin</artifactId>
@@ -163,7 +198,8 @@
             </goals>
             <configuration>
               <sources>
-                <source>${project.build.directory}/generated-sources</source>
+                <!-- Includes both OpenAPI and jsonschema2pojo generated sources -->
+                <source>${jsonschema2pojo.output}</source>
               </sources>
             </configuration>
           </execution>
@@ -176,7 +212,6 @@
         <version>${maven-surefire-plugin.version}</version>
         <configuration>
           <useSystemClassLoader>false</useSystemClassLoader>
-          <groups>unit</groups>
         </configuration>
       </plugin>
 
@@ -191,6 +226,165 @@
           <localCheckout>true</localCheckout>
         </configuration>
       </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>${exec-maven-plugin.version}</version>
+        <executions>
+          <execution>
+            <id>git submodule update</id>
+            <phase>initialize</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <configuration>
+              <executable>git</executable>
+              <arguments>
+                <argument>submodule</argument>
+                <argument>update</argument>
+                <argument>--init</argument>
+                <argument>--recursive</argument>
+              </arguments>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.openapitools</groupId>
+        <artifactId>openapi-generator-maven-plugin</artifactId>
+        <version>${openapi-generator.version}</version>
+        <executions>
+          <execution>
+            <id>mosaic</id>
+            <goals>
+              <goal>generate</goal>
+            </goals>
+            <configuration>
+              <inputSpec>${openapi-generator.file}</inputSpec>
+              <output>${openapi-generator.output}</output>
+              <generatorName>spring</generatorName>
+              <modelPackage>${project.groupId}.mosaic.domain.dto</modelPackage>
+              <apiPackage>${project.groupId}.mosaic.rest.resource</apiPackage>
+              <generateApis>true</generateApis>
+              <generateApiTests>true</generateApiTests>
+              <generateApiDocumentation>true</generateApiDocumentation>
+              <generateModels>true</generateModels>
+              <generateModelTests>false</generateModelTests>
+              <generateSupportingFiles>true</generateSupportingFiles>
+              <supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate>
+              <generateModelDocumentation>true</generateModelDocumentation>
+              <schemaMappings>
+                <schemaMapping>MosaicOrder=org.folio.rest.acq.model.orders.CompositePurchaseOrder</schemaMapping>
+              </schemaMappings>
+              <configOptions>
+                <dateLibrary>java</dateLibrary>
+                <interfaceOnly>true</interfaceOnly>
+                <useSpringBoot3>true</useSpringBoot3>
+                <serializableModel>true</serializableModel>
+              </configOptions>
+            </configuration>
+          </execution>
+          <execution>
+            <goals>
+              <goal>generate</goal>
+            </goals>
+            <configuration>
+              <inputSpec>${openapi-generator.file}</inputSpec>
+              <output>${project.build.directory}/docs/mod-mosaic</output>
+              <generatorName>html2</generatorName>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.jsonschema2pojo</groupId>
+        <artifactId>jsonschema2pojo-maven-plugin</artifactId>
+        <version>${jsonschema2pojo.version}</version>
+        <configuration>
+          <includes>
+            <include>*.json</include>
+          </includes>
+          <outputDirectory>${jsonschema2pojo.output}</outputDirectory>
+          <generateBuilders>true</generateBuilders>
+        </configuration>
+        <executions>
+          <execution>
+            <id>common</id>
+            <goals>
+              <goal>generate</goal>
+            </goals>
+            <configuration>
+              <sourceDirectory>${basedir}/ramls/acq-models/common/schemas</sourceDirectory>
+              <targetPackage>org.folio.rest.acq.model</targetPackage>
+            </configuration>
+          </execution>
+          <execution>
+            <id>orders</id>
+            <goals>
+              <goal>generate</goal>
+            </goals>
+            <configuration>
+              <sourceDirectory>${basedir}/ramls/acq-models/mod-orders/schemas</sourceDirectory>
+              <targetPackage>org.folio.rest.acq.model.orders</targetPackage>
+            </configuration>
+          </execution>
+          <execution>
+            <id>orders-storage</id>
+            <goals>
+              <goal>generate</goal>
+            </goals>
+            <configuration>
+              <sourceDirectory>${basedir}/ramls/acq-models/mod-orders-storage/schemas</sourceDirectory>
+              <targetPackage>org.folio.rest.acq.model.orders</targetPackage>
+            </configuration>
+          </execution>
+          <execution>
+            <id>mod-orgs</id>
+            <goals>
+              <goal>generate</goal>
+            </goals>
+            <configuration>
+              <sourceDirectory>${basedir}/ramls/acq-models/mod-orgs/schemas</sourceDirectory>
+              <targetPackage>org.folio.rest.acq.model.orgs</targetPackage>
+            </configuration>
+          </execution>
+          <execution>
+            <id>mod-finance</id>
+            <goals>
+              <goal>generate</goal>
+            </goals>
+            <configuration>
+              <sourceDirectory>${basedir}/ramls/acq-models/mod-finance/schemas</sourceDirectory>
+              <targetPackage>org.folio.rest.acq.model.finance</targetPackage>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-enforcer-plugin</artifactId>
+        <version>${maven-enforcer-plugin.version}</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>enforce</goal>
+            </goals>
+            <configuration>
+              <rules>
+                <bannedDependencies>
+                  <excludes>
+                    <exclude>org.springframework.boot:spring-boot-starter-logging</exclude>
+                  </excludes>
+                </bannedDependencies>
+              </rules>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
     </plugins>
   </build>
 
diff --git a/ramls/acq-models b/ramls/acq-models
new file mode 160000
index 0000000..c4f15de
--- /dev/null
+++ b/ramls/acq-models
@@ -0,0 +1 @@
+Subproject commit c4f15de92c35fd230e4d29dced2cb80b0b9c8486
diff --git a/ramls/raml-util b/ramls/raml-util
new file mode 160000
index 0000000..f48a63f
--- /dev/null
+++ b/ramls/raml-util
@@ -0,0 +1 @@
+Subproject commit f48a63f3e45b9a3b437d21c3a61ed10b8ceb5f25
diff --git a/src/main/java/org/folio/mosaic/FolioMosaicApplication.java b/src/main/java/org/folio/mosaic/FolioMosaicApplication.java
index 2df2cae..69e6e4a 100644
--- a/src/main/java/org/folio/mosaic/FolioMosaicApplication.java
+++ b/src/main/java/org/folio/mosaic/FolioMosaicApplication.java
@@ -2,11 +2,14 @@
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
 
+@EnableFeignClients
 @SpringBootApplication
 public class FolioMosaicApplication {
 
   public static void main(String[] args) {
     SpringApplication.run(FolioMosaicApplication.class, args);
   }
+
 }
diff --git a/src/main/java/org/folio/mosaic/client/OrdersClient.java b/src/main/java/org/folio/mosaic/client/OrdersClient.java
new file mode 100644
index 0000000..8f815c0
--- /dev/null
+++ b/src/main/java/org/folio/mosaic/client/OrdersClient.java
@@ -0,0 +1,14 @@
+package org.folio.mosaic.client;
+
+import org.folio.rest.acq.model.orders.CompositePurchaseOrder;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+@FeignClient("orders")
+public interface OrdersClient {
+
+  @PostMapping(value = "/orders")
+  CompositePurchaseOrder createOrder(@RequestBody CompositePurchaseOrder poLine);
+
+}
diff --git a/src/main/java/org/folio/mosaic/controller/OrdersController.java b/src/main/java/org/folio/mosaic/controller/OrdersController.java
new file mode 100644
index 0000000..a6c632a
--- /dev/null
+++ b/src/main/java/org/folio/mosaic/controller/OrdersController.java
@@ -0,0 +1,28 @@
+package org.folio.mosaic.controller;
+
+import org.folio.mosaic.rest.resource.OrdersApi;
+import org.folio.mosaic.service.OrdersService;
+import org.folio.rest.acq.model.orders.CompositePurchaseOrder;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+
+@RestController
+@RequestMapping("/mosaic")
+@RequiredArgsConstructor
+@Log4j2
+public class OrdersController implements OrdersApi {
+
+  private final OrdersService ordersService;
+
+  @Override
+  public ResponseEntity<String> createMosaicOrder(CompositePurchaseOrder mosaicOrder) {
+    return ResponseEntity.status(HttpStatus.CREATED)
+      .body(ordersService.createOrder(mosaicOrder));
+  }
+
+}
diff --git a/src/main/java/org/folio/mosaic/controller/exception/ControllerExceptionHandler.java b/src/main/java/org/folio/mosaic/controller/exception/ControllerExceptionHandler.java
new file mode 100644
index 0000000..e141439
--- /dev/null
+++ b/src/main/java/org/folio/mosaic/controller/exception/ControllerExceptionHandler.java
@@ -0,0 +1,61 @@
+package org.folio.mosaic.controller.exception;
+
+import org.folio.mosaic.domain.dto.Errors;
+import org.folio.mosaic.util.ErrorUtils;
+import org.folio.mosaic.util.error.ErrorCode;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import feign.FeignException;
+import lombok.extern.log4j.Log4j2;
+
+@RestControllerAdvice
+@Log4j2
+public class ControllerExceptionHandler {
+
+  @ResponseStatus(HttpStatus.NOT_FOUND)
+  @ExceptionHandler(FeignException.NotFound.class)
+  public Errors handleNotFoundException(FeignException.NotFound e) {
+    logException(e);
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.NOT_FOUND_ERROR);
+  }
+
+  @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
+  @ExceptionHandler(FeignException.UnprocessableEntity.class)
+  public Errors handleValidationException(FeignException.UnprocessableEntity e) {
+    logException(e);
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.VALIDATION_ERROR);
+  }
+
+  @ResponseStatus(HttpStatus.BAD_REQUEST)
+  @ExceptionHandler(FeignException.FeignClientException.class)
+  public Errors handleClientException(FeignException.FeignClientException e) {
+    logException(e);
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.BAD_REQUEST_ERROR);
+  }
+
+  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+  @ExceptionHandler(FeignException.FeignServerException.class)
+  public Errors handleServerException(FeignException.FeignServerException e) {
+    logException(e);
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.INTERNAL_ERROR);
+  }
+
+  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+  @ExceptionHandler(Exception.class)
+  public Errors handleGenericException(Exception e) {
+    logException(e);
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.UNKNOWN_ERROR);
+  }
+
+  public void logException(FeignException e) {
+    log.error("Feign exception occurred with status code {}", e.status(), e);
+  }
+
+  public void logException(Exception e) {
+    log.error("Generic exception occurred", e);
+  }
+
+}
diff --git a/src/main/java/org/folio/mosaic/service/OrdersService.java b/src/main/java/org/folio/mosaic/service/OrdersService.java
new file mode 100644
index 0000000..2df9f05
--- /dev/null
+++ b/src/main/java/org/folio/mosaic/service/OrdersService.java
@@ -0,0 +1,24 @@
+package org.folio.mosaic.service;
+
+import org.folio.mosaic.client.OrdersClient;
+import org.folio.rest.acq.model.orders.CompositePurchaseOrder;
+import org.springframework.stereotype.Service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+import lombok.val;
+
+@Service
+@RequiredArgsConstructor
+@Log4j2
+public class OrdersService {
+
+  private final OrdersClient ordersClient;
+
+  public String createOrder(CompositePurchaseOrder compositePurchaseOrder) {
+    log.info("createOrder:: Creating mosaic order with number: {}", compositePurchaseOrder.getPoNumber());
+    val createdOrder = ordersClient.createOrder(compositePurchaseOrder);
+    return createdOrder.getCompositePoLines().getFirst().getPoLineNumber();
+  }
+
+}
diff --git a/src/main/java/org/folio/mosaic/util/ErrorUtils.java b/src/main/java/org/folio/mosaic/util/ErrorUtils.java
new file mode 100644
index 0000000..b2598f8
--- /dev/null
+++ b/src/main/java/org/folio/mosaic/util/ErrorUtils.java
@@ -0,0 +1,22 @@
+package org.folio.mosaic.util;
+
+import java.util.List;
+
+import org.folio.mosaic.domain.dto.Error;
+import org.folio.mosaic.domain.dto.Errors;
+import org.folio.mosaic.util.error.ErrorCode;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class ErrorUtils {
+
+  public static Errors getErrors(String message, ErrorCode errorCode) {
+    return getErrors(errorCode.toError().message(message));
+  }
+
+  public static Errors getErrors(Error... errors) {
+    return new Errors().errors(List.of(errors)).totalRecords(errors.length);
+  }
+
+}
diff --git a/src/main/java/org/folio/mosaic/util/error/ErrorCode.java b/src/main/java/org/folio/mosaic/util/error/ErrorCode.java
new file mode 100644
index 0000000..efbfaff
--- /dev/null
+++ b/src/main/java/org/folio/mosaic/util/error/ErrorCode.java
@@ -0,0 +1,24 @@
+package org.folio.mosaic.util.error;
+
+import org.folio.mosaic.domain.dto.Error;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum ErrorCode {
+
+  VALIDATION_ERROR("validationError", "Validation error"),
+  NOT_FOUND_ERROR("notFoundError", "Resource not found"),
+  INTERNAL_ERROR("internalError", "Internal error occurred"),
+  BAD_REQUEST_ERROR("badRequestError", "Bad request sent by the client"),
+  UNKNOWN_ERROR("unknownError", "Unknown error occurred");
+
+  private final String code;
+  private final String description;
+
+  public Error toError() {
+    return new Error().code(code).message(description);
+  }
+
+}
diff --git a/src/main/resources/swagger.api/mosaic.yaml b/src/main/resources/swagger.api/mosaic.yaml
new file mode 100644
index 0000000..105c15f
--- /dev/null
+++ b/src/main/resources/swagger.api/mosaic.yaml
@@ -0,0 +1,59 @@
+openapi: 3.0.0
+info:
+  title: Mosaic integration API
+  description: "Mosaic integration API"
+  version: 0.0.1
+servers:
+  - url: /mosaic
+paths:
+  /orders:
+    description: Orders API
+    post:
+      summary: Create a new order in FOLIO
+      operationId: createMosaicOrder
+      requestBody:
+        description: Mosaic Order object
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/MosaicOrder"
+      responses:
+        '201':
+          description: Order successfully created, returns the POL number
+          content:
+            text/plain:
+              schema:
+                type: string
+              example: "10001-1"
+        '400':
+          description:
+            Bad request, e.g. malformed request body or query parameter.
+            Details of the error (e.g. name of the parameter or line/character number
+            with malformed data) provided in the response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/errors"
+        '500':
+          description: Internal server error, e.g. due to misconfiguration
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/errors"
+components:
+  schemas:
+    MosaicOrder:
+      type: object
+      properties:
+        name:
+          $ref: "MosaicOrder"
+    errors:
+      $ref: "./schemas/common.yaml#/Errors"
+    error:
+      $ref: "./schemas/common.yaml#/Error"
+    parameters:
+      $ref: "./schemas/common.yaml#/Parameters"
+    parameter:
+      $ref: "./schemas/common.yaml#/Parameter"
+
diff --git a/src/main/resources/swagger.api/schemas/common.yaml b/src/main/resources/swagger.api/schemas/common.yaml
new file mode 100644
index 0000000..e55cc0b
--- /dev/null
+++ b/src/main/resources/swagger.api/schemas/common.yaml
@@ -0,0 +1,53 @@
+Error:
+  description: "An error"
+  type: object
+  properties:
+    message:
+      type: string
+      minLength: 1
+      description: "Error message text"
+    type:
+      type: string
+      description: "Error message type"
+    code:
+      type: string
+      description: "Error message code"
+    parameters:
+      description: "Error message parameters"
+      $ref: "#/Parameters"
+  additionalProperties: false
+  required:
+    - message
+
+Errors:
+  description: "A set of errors"
+  type: object
+  properties:
+    errors:
+      description: "List of errors"
+      type: array
+      items:
+        $ref: "#/Error"
+    total_records:
+      description: "Total number of errors"
+      type: integer
+  additionalProperties: false
+
+Parameter:
+  description: "List of key/value parameters of an error"
+  type: object
+  properties:
+    key:
+      type: string
+      minLength: 1
+    value:
+      type: string
+  additionalProperties: false
+  required:
+    - key
+
+Parameters:
+  description: "List of key/value parameters of an error"
+  type: array
+  items:
+    $ref: "#/Parameter"
diff --git a/src/test/java/org/folio/mosaic/CopilotGenerated.java b/src/test/java/org/folio/mosaic/CopilotGenerated.java
new file mode 100644
index 0000000..e64d868
--- /dev/null
+++ b/src/test/java/org/folio/mosaic/CopilotGenerated.java
@@ -0,0 +1,26 @@
+package org.folio.mosaic;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Annotation to indicate that test(s) in the class are generated by GitHub Copilot.
+ * <br><br>
+ * Set <code>value</code> or <code>partiallyGenerated</code> attribute to <code>true</code>
+ * if the generated test(s) were significantly modified/altered by the developer.
+ */
+@Target({ElementType.TYPE, ElementType.METHOD})
+@Retention(RetentionPolicy.SOURCE)
+public @interface CopilotGenerated {
+
+  @AliasFor("partiallyGenerated")
+  boolean value() default false;
+
+  @AliasFor("value")
+  boolean partiallyGenerated() default false;
+
+}
diff --git a/src/test/java/org/folio/mosaic/FolioMosaicApplicationTest.java b/src/test/java/org/folio/mosaic/FolioMosaicApplicationTest.java
new file mode 100644
index 0000000..c6ef39a
--- /dev/null
+++ b/src/test/java/org/folio/mosaic/FolioMosaicApplicationTest.java
@@ -0,0 +1,15 @@
+package org.folio.mosaic;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class FolioMosaicApplicationTest {
+
+  @Test
+  void contextLoads() {
+    // This test is used to check if the Spring application context loads successfully.
+    // No implementation is needed here.
+  }
+
+}
diff --git a/src/test/java/org/folio/mosaic/controller/OrdersControllerTest.java b/src/test/java/org/folio/mosaic/controller/OrdersControllerTest.java
new file mode 100644
index 0000000..cd7faa7
--- /dev/null
+++ b/src/test/java/org/folio/mosaic/controller/OrdersControllerTest.java
@@ -0,0 +1,95 @@
+package org.folio.mosaic.controller;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.folio.mosaic.service.OrdersService;
+import org.folio.mosaic.util.ErrorUtils;
+import org.folio.mosaic.util.error.ErrorCode;
+import org.folio.rest.acq.model.orders.CompositePoLine;
+import org.folio.rest.acq.model.orders.CompositePurchaseOrder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+import org.springframework.test.web.servlet.MockMvc;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import feign.FeignException;
+import feign.Request;
+import lombok.val;
+
+@WebMvcTest(OrdersController.class)
+class OrdersControllerTest {
+
+  @MockitoBean
+  private OrdersService ordersService;
+
+  @Autowired
+  private MockMvc mockMvc;
+  @Autowired
+  private ObjectMapper objectMapper;
+
+  @Test
+  void testCreateOrder() throws Exception {
+    val poLineNumber = "12345";
+    val mosaicOrder = new CompositePurchaseOrder().withCompositePoLines(List.of(new CompositePoLine().withPoLineNumber(poLineNumber)));
+
+    when(ordersService.createOrder(mosaicOrder)).thenReturn(poLineNumber);
+
+    mockMvc.perform(post("/mosaic/orders")
+        .contentType(MediaType.APPLICATION_JSON)
+        .content(objectMapper.writeValueAsString(mosaicOrder)))
+      .andExpect(status().isCreated())
+      .andExpect(content().string(poLineNumber));
+
+    verify(ordersService).createOrder(mosaicOrder);
+  }
+
+  @ParameterizedTest
+  @MethodSource("createOrderExceptionProvider")
+  void testCreateOrderThrowsException(int statusCode, ErrorCode errorCode, Throwable throwable) throws Exception {
+    val mosaicOrder = new CompositePurchaseOrder();
+    val expectedError = ErrorUtils.getErrors(errorCode.toError());
+
+    when(ordersService.createOrder(mosaicOrder)).thenThrow(throwable);
+
+    mockMvc.perform(post("/mosaic/orders")
+        .contentType(MediaType.APPLICATION_JSON)
+        .content(objectMapper.writeValueAsString(mosaicOrder)))
+      .andExpect(status().is(statusCode))
+      .andExpect(content().json(objectMapper.writeValueAsString(expectedError)));
+
+    verify(ordersService).createOrder(mosaicOrder);
+  }
+
+  private static Stream<Arguments> createOrderExceptionProvider() {
+    val req = mock(Request.class);
+    return Stream.of(
+      Arguments.of(HttpStatus.NOT_FOUND.value(), ErrorCode.NOT_FOUND_ERROR,
+        new FeignException.NotFound(ErrorCode.NOT_FOUND_ERROR.getDescription(), req, null, null)),
+      Arguments.of(HttpStatus.UNPROCESSABLE_ENTITY.value(), ErrorCode.VALIDATION_ERROR,
+        new FeignException.UnprocessableEntity(ErrorCode.VALIDATION_ERROR.getDescription(), req, null, null)),
+      Arguments.of(HttpStatus.BAD_REQUEST.value(), ErrorCode.BAD_REQUEST_ERROR,
+        new FeignException.FeignClientException(HttpStatus.BAD_REQUEST.value(), ErrorCode.BAD_REQUEST_ERROR.getDescription(), req, null, null)),
+      Arguments.of(HttpStatus.INTERNAL_SERVER_ERROR.value(), ErrorCode.INTERNAL_ERROR,
+        new FeignException.FeignServerException(HttpStatus.INTERNAL_SERVER_ERROR.value(), ErrorCode.INTERNAL_ERROR.getDescription(), req, null, null)),
+      Arguments.of(HttpStatus.INTERNAL_SERVER_ERROR.value(), ErrorCode.UNKNOWN_ERROR,
+        new RuntimeException(ErrorCode.UNKNOWN_ERROR.getDescription()))
+    );
+  }
+
+}
diff --git a/src/test/java/org/folio/mosaic/service/OrdersServiceTest.java b/src/test/java/org/folio/mosaic/service/OrdersServiceTest.java
new file mode 100644
index 0000000..284a762
--- /dev/null
+++ b/src/test/java/org/folio/mosaic/service/OrdersServiceTest.java
@@ -0,0 +1,57 @@
+package org.folio.mosaic.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+
+import org.folio.mosaic.CopilotGenerated;
+import org.folio.mosaic.client.OrdersClient;
+import org.folio.rest.acq.model.orders.CompositePoLine;
+import org.folio.rest.acq.model.orders.CompositePurchaseOrder;
+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;
+
+@CopilotGenerated
+@ExtendWith(MockitoExtension.class)
+class OrdersServiceTest {
+
+  @Mock
+  private OrdersClient ordersClient;
+
+  @InjectMocks
+  private OrdersService ordersService;
+
+  @Test
+  void testCreateOrder() {
+    CompositePurchaseOrder compositePurchaseOrder = new CompositePurchaseOrder();
+    compositePurchaseOrder.setPoNumber("PO12345");
+
+    CompositePurchaseOrder createdOrder = new CompositePurchaseOrder();
+    createdOrder.setCompositePoLines(List.of(new CompositePoLine().withPoLineNumber("POL12345")));
+
+    when(ordersClient.createOrder(compositePurchaseOrder)).thenReturn(createdOrder);
+
+    String result = ordersService.createOrder(compositePurchaseOrder);
+
+    assertEquals("POL12345", result);
+    verify(ordersClient).createOrder(compositePurchaseOrder);
+  }
+
+  @Test
+  void testCreateOrderClientException() {
+    CompositePurchaseOrder compositePurchaseOrder = new CompositePurchaseOrder();
+    compositePurchaseOrder.setPoNumber("PO12345");
+
+    when(ordersClient.createOrder(compositePurchaseOrder)).thenThrow(new RuntimeException("Order creation failed"));
+
+    assertThrows(RuntimeException.class, () -> ordersService.createOrder(compositePurchaseOrder));
+    verify(ordersClient).createOrder(compositePurchaseOrder);
+  }
+
+}
diff --git a/src/test/java/org/folio/mosaic/util/ErrorUtilsTest.java b/src/test/java/org/folio/mosaic/util/ErrorUtilsTest.java
new file mode 100644
index 0000000..9d5f35f
--- /dev/null
+++ b/src/test/java/org/folio/mosaic/util/ErrorUtilsTest.java
@@ -0,0 +1,45 @@
+package org.folio.mosaic.util;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.folio.mosaic.CopilotGenerated;
+import org.folio.mosaic.util.error.ErrorCode;
+import org.junit.jupiter.api.Test;
+
+import lombok.val;
+
+@CopilotGenerated(partiallyGenerated = true)
+class ErrorUtilsTest {
+
+  @Test
+  void testGetErrors() {
+    val errors = ErrorUtils.getErrors(ErrorCode.VALIDATION_ERROR.toError(), ErrorCode.BAD_REQUEST_ERROR.toError());
+
+    assertThat(errors).isNotNull();
+    assertThat(errors.getErrors()).isNotNull().isNotEmpty().hasSize(2);
+    assertThat(errors.getTotalRecords()).isEqualTo(2);
+    assertThat(errors.getErrors().get(0).getMessage()).isEqualTo(ErrorCode.VALIDATION_ERROR.getDescription());
+    assertThat(errors.getErrors().get(1).getMessage()).isEqualTo(ErrorCode.BAD_REQUEST_ERROR.getDescription());
+  }
+
+  @Test
+  void testGetErrorsEmpty() {
+    val errors = ErrorUtils.getErrors();
+
+    assertThat(errors).isNotNull();
+    assertThat(errors.getErrors()).isNotNull().isEmpty();
+    assertThat(errors.getTotalRecords()).isZero();
+  }
+
+  @Test
+  void testGetErrorsWithMessage() {
+    val errorMessage = "Custom message";
+
+    val errors = ErrorUtils.getErrors(errorMessage, ErrorCode.UNKNOWN_ERROR);
+
+    assertThat(errors).isNotNull();
+    assertThat(errors.getErrors()).isNotNull().isNotEmpty().hasSize(1);
+    assertThat(errors.getTotalRecords()).isEqualTo(1);
+    assertThat(errors.getErrors().getFirst().getMessage()).isEqualTo(errorMessage);
+  }
+}


Reviews:
user: login: Saba-Zedginidze-EPAM
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T09:52:38Z
user: login: Saba-Zedginidze-EPAM
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T09:53:55Z
user: login: SerhiiNosko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T10:35:50Z
user: login: SerhiiNosko
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-11T10:37:06Z
user: login: Saba-Zedginidze-EPAM
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T14:16:36Z
user: login: azizbekxm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T14:23:11Z
user: login: azizbekxm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T14:27:25Z
user: login: SerhiiNosko
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T18:47:01Z

Review comments:
diff_hunk: @@ -191,6 +226,165 @@
           <localCheckout>true</localCheckout>
         </configuration>
       </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>${exec-maven-plugin.version}</version>
+        <executions>
+          <execution>
+            <id>git submodule update</id>
+            <phase>initialize</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <configuration>
+              <executable>git</executable>
+              <arguments>
+                <argument>submodule</argument>
+                <argument>update</argument>
+                <argument>--init</argument>
+                <argument>--recursive</argument>
+              </arguments>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.openapitools</groupId>
+        <artifactId>openapi-generator-maven-plugin</artifactId>
+        <version>${openapi-generator.version}</version>
+        <executions>
+          <execution>
+            <id>mosaic</id>
+            <goals>
+              <goal>generate</goal>
+            </goals>
+            <configuration>
+              <inputSpec>${openapi-generator.file}</inputSpec>
+              <output>${openapi-generator.output}</output>
+              <generatorName>spring</generatorName>
+              <modelPackage>${project.groupId}.mosaic.domain.dto</modelPackage>
+              <apiPackage>${project.groupId}.mosaic.rest.resource</apiPackage>
+              <generateApis>true</generateApis>
+              <generateApiTests>true</generateApiTests>
+              <generateApiDocumentation>true</generateApiDocumentation>
+              <generateModels>true</generateModels>
+              <generateModelTests>false</generateModelTests>
+              <generateSupportingFiles>true</generateSupportingFiles>
+              <supportingFilesToGenerate>ApiUtil.java</supportingFilesToGenerate>
+              <generateModelDocumentation>true</generateModelDocumentation>
+              <schemaMappings>
+                <schemaMapping>MosaicOrder=org.folio.rest.acq.model.orders.CompositePurchaseOrder</schemaMapping>
+              </schemaMappings>
path: pom.xml
user: login: Saba-Zedginidze-EPAM
body: api-lint error is caused because of this logic. Temporarily, until mosaic order mapping logic is implemented, the POST endpoint will accept `CompositePurchaseOrder` object, which is generated from `acq-models` and not `openapi generator`, so in order to reuse the already generated class instead of copy-pasting for openapi to generate it again, a mapping is created in `pom.xml` here to create an alias as `MosaicOrder` to point to the  `CompositePurchaseOrder`. 
created_at: 2025-04-11T09:52:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+openapi: 3.0.0
+info:
+  title: Mosaic integration API
+  description: "Mosaic integration API"
+  version: 0.0.1
+servers:
+  - url: /mosaic
+paths:
+  /orders:
+    description: Orders API
+    post:
+      summary: Create a new order in FOLIO
+      operationId: createMosaicOrder
+      requestBody:
+        description: Mosaic Order object
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/MosaicOrder"
+      responses:
+        '201':
+          description: Order successfully created, returns the POL number
+          content:
+            text/plain:
+              schema:
+                type: string
+              example: "10001-1"
+        '400':
+          description:
+            Bad request, e.g. malformed request body or query parameter.
+            Details of the error (e.g. name of the parameter or line/character number
+            with malformed data) provided in the response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/errors"
+        '500':
+          description: Internal server error, e.g. due to misconfiguration
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/errors"
+components:
+  schemas:
+    MosaicOrder:
+      type: object
+      properties:
+        name:
+          $ref: "MosaicOrder"
path: src/main/resources/swagger.api/mosaic.yaml
user: login: Saba-Zedginidze-EPAM
body: This ensures that a schema mapping from 'pom.xml' is used while generating API interface 
created_at: 2025-04-11T09:53:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+package org.folio.mosaic.controller.exception;
+
+import org.folio.mosaic.domain.dto.Errors;
+import org.folio.mosaic.util.ErrorUtils;
+import org.folio.mosaic.util.error.ErrorCode;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import feign.FeignException;
+import lombok.extern.log4j.Log4j2;
+
+@RestControllerAdvice
+@Log4j2
+public class ExceptionHandlerController {
+
+  @ResponseStatus(HttpStatus.NOT_FOUND)
+  @ExceptionHandler(FeignException.NotFound.class)
+  public Errors handleNotFoundException(FeignException.NotFound e) {
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.NOT_FOUND_ERROR);
+  }
+
+  @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
+  @ExceptionHandler(FeignException.UnprocessableEntity.class)
+  public Errors handleNotFoundException(FeignException.UnprocessableEntity e) {
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.VALIDATION_ERROR);
+  }
+
+  @ResponseStatus(HttpStatus.BAD_REQUEST)
+  @ExceptionHandler(FeignException.FeignClientException.class)
+  public Errors handleNotFoundException(FeignException.FeignClientException e) {
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.BAD_REQUEST_ERROR);
+  }
+
+  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+  @ExceptionHandler(FeignException.FeignServerException.class)
+  public Errors handleNotFoundException(FeignException.FeignServerException e) {
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.INTERNAL_ERROR);
path: src/main/java/org/folio/mosaic/controller/exception/ExceptionHandlerController.java
user: login: SerhiiNosko
body: bettet to add logs to all handlers, espessionaly for 500
created_at: 2025-04-11T10:35:50Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,48 @@
+package org.folio.mosaic.controller.exception;
+
+import org.folio.mosaic.domain.dto.Errors;
+import org.folio.mosaic.util.ErrorUtils;
+import org.folio.mosaic.util.error.ErrorCode;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import feign.FeignException;
+import lombok.extern.log4j.Log4j2;
+
+@RestControllerAdvice
+@Log4j2
+public class ExceptionHandlerController {
+
+  @ResponseStatus(HttpStatus.NOT_FOUND)
+  @ExceptionHandler(FeignException.NotFound.class)
+  public Errors handleNotFoundException(FeignException.NotFound e) {
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.NOT_FOUND_ERROR);
+  }
+
+  @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
+  @ExceptionHandler(FeignException.UnprocessableEntity.class)
+  public Errors handleNotFoundException(FeignException.UnprocessableEntity e) {
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.VALIDATION_ERROR);
+  }
+
+  @ResponseStatus(HttpStatus.BAD_REQUEST)
+  @ExceptionHandler(FeignException.FeignClientException.class)
+  public Errors handleNotFoundException(FeignException.FeignClientException e) {
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.BAD_REQUEST_ERROR);
+  }
+
+  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
+  @ExceptionHandler(FeignException.FeignServerException.class)
+  public Errors handleNotFoundException(FeignException.FeignServerException e) {
+    return ErrorUtils.getErrors(e.getMessage(), ErrorCode.INTERNAL_ERROR);
path: src/main/java/org/folio/mosaic/controller/exception/ExceptionHandlerController.java
user: login: Saba-Zedginidze-EPAM
body: All Feign exceptions are better logged as they most likely will be unhandled/unexpected, as well as generic Exception. However, the logs can be limited in the future together with different logic added 
created_at: 2025-04-11T14:16:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,24 @@
+package org.folio.mosaic.service;
+
+import org.folio.mosaic.client.OrdersClient;
+import org.folio.rest.acq.model.orders.CompositePurchaseOrder;
+import org.springframework.stereotype.Service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.log4j.Log4j2;
+import lombok.val;
+
+@Service
+@RequiredArgsConstructor
+@Log4j2
+public class OrdersService {
+
+  private final OrdersClient ordersClient;
+
+  public String createOrder(CompositePurchaseOrder compositePurchaseOrder) {
+    log.info("createOrder:: Creating mosaic order with number: {}", compositePurchaseOrder.getPoNumber());
+    val createdOrder = ordersClient.createOrder(compositePurchaseOrder);
path: src/main/java/org/folio/mosaic/service/OrdersService.java
user: login: azizbekxm
body: is it supped to be var?
created_at: 2025-04-11T14:23:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+openapi: 3.0.0
+info:
+  title: Mosaic integration API
+  description: "Mosaic integration API"
+  version: 0.0.1
+servers:
+  - url: /mosaic
+paths:
+  /orders:
+    description: Orders API
+    post:
+      summary: Create a new order in FOLIO
+      operationId: createMosaicOrder
+      requestBody:
+        description: Mosaic Order object
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/MosaicOrder"
+      responses:
+        '201':
+          description: Order successfully created, returns the POL number
+          content:
+            text/plain:
+              schema:
+                type: string
+              example: "10001-1"
+        '400':
+          description:
+            Bad request, e.g. malformed request body or query parameter.
+            Details of the error (e.g. name of the parameter or line/character number
+            with malformed data) provided in the response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/errors"
+        '500':
+          description: Internal server error, e.g. due to misconfiguration
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/errors"
path: src/main/resources/swagger.api/mosaic.yaml
user: login: azizbekxm
body: small suggestion to make them reusable
here is example
https://github.com/folio-org/mod-consortia/blob/d101eefdcf2d1f560f21c12a995c4170b682e0f0/src/main/resources/swagger.api/user_tenants.yaml#L111

```
      responses:
        "200":
          $ref: "#/components/responses/UserTenantCollection"
        "400":
          $ref: "#/components/responses/BadRequest"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalServerError"
```
created_at: 2025-04-11T14:26:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,59 @@
+openapi: 3.0.0
+info:
+  title: Mosaic integration API
+  description: "Mosaic integration API"
+  version: 0.0.1
+servers:
+  - url: /mosaic
+paths:
+  /orders:
+    description: Orders API
+    post:
+      summary: Create a new order in FOLIO
+      operationId: createMosaicOrder
+      requestBody:
+        description: Mosaic Order object
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/MosaicOrder"
+      responses:
+        '201':
+          description: Order successfully created, returns the POL number
+          content:
+            text/plain:
+              schema:
+                type: string
+              example: "10001-1"
+        '400':
+          description:
+            Bad request, e.g. malformed request body or query parameter.
+            Details of the error (e.g. name of the parameter or line/character number
+            with malformed data) provided in the response.
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/errors"
+        '500':
+          description: Internal server error, e.g. due to misconfiguration
+          content:
+            application/json:
+              schema:
+                $ref: "#/components/schemas/errors"
path: src/main/resources/swagger.api/mosaic.yaml
user: login: SerhiiNosko
body: will be addressed in subsequent pr, because we were needed to merge this PR urgently to fix build issue
created_at: 2025-04-14T18:47:01Z
author_association: COLLABORATOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-11T14:17:09Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=org.folio%3Amod-mosaic&pullRequest=5) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=org.folio%3Amod-mosaic&pullRequest=5&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=org.folio%3Amod-mosaic&pullRequest=5&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=org.folio%3Amod-mosaic&pullRequest=5&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=org.folio%3Amod-mosaic&pullRequest=5&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=org.folio%3Amod-mosaic&pullRequest=5&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=org.folio%3Amod-mosaic&pullRequest=5)


author_association: NONE