mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 14:09:34 +01:00
feat: TUI-Screens für Inventar und Produktion + API-Client Typ-Migration
Neue TUI-Features:
- Inventar: Lageorte auflisten, anlegen, bearbeiten, (de-)aktivieren
- Produktion: Rezepte auflisten, anlegen, Detail-Ansicht
- Navigation erweitert (Hauptmenü, Routing)
API-Client auf generierte OpenAPI-Typen umgestellt:
- 6 neue Alias-Dateien in @effigenix/types (supplier, category, article,
customer, inventory, production)
- api-client Re-Exports direkt von @effigenix/types statt via Resources
- Backend: @Schema(requiredProperties) auf 16 Response-Records
- Backend: OpenApiCustomizer für application-layer DTOs (UserDTO, RoleDTO)
Hinweis: Backend-Endpoints für GET /api/recipes und
GET /api/inventory/storage-locations/{id} fehlen noch (separate Issues).
This commit is contained in:
parent
bee3f28b5f
commit
c26d72fbe7
48 changed files with 2090 additions and 474 deletions
|
|
@ -1,14 +1,16 @@
|
|||
package de.effigenix.infrastructure.inventory.web.dto;
|
||||
|
||||
import de.effigenix.domain.inventory.StorageLocation;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(requiredProperties = {"id", "name", "storageType", "active"})
|
||||
public record StorageLocationResponse(
|
||||
String id,
|
||||
String name,
|
||||
String storageType,
|
||||
TemperatureRangeResponse temperatureRange,
|
||||
@Schema(nullable = true) TemperatureRangeResponse temperatureRange,
|
||||
boolean active
|
||||
) {
|
||||
|
||||
|
|
@ -30,5 +32,6 @@ public record StorageLocationResponse(
|
|||
);
|
||||
}
|
||||
|
||||
@Schema(requiredProperties = {"minTemperature", "maxTemperature"})
|
||||
public record TemperatureRangeResponse(BigDecimal minTemperature, BigDecimal maxTemperature) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package de.effigenix.infrastructure.masterdata.web.dto;
|
||||
|
||||
import de.effigenix.shared.common.Address;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(requiredProperties = {"street", "houseNumber", "postalCode", "city", "country"})
|
||||
public record AddressResponse(
|
||||
String street,
|
||||
String houseNumber,
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ package de.effigenix.infrastructure.masterdata.web.dto;
|
|||
|
||||
import de.effigenix.domain.masterdata.Article;
|
||||
import de.effigenix.domain.masterdata.SupplierId;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(requiredProperties = {"id", "name", "articleNumber", "categoryId", "salesUnits", "status", "supplierIds", "createdAt", "updatedAt"})
|
||||
public record ArticleResponse(
|
||||
String id,
|
||||
String name,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package de.effigenix.infrastructure.masterdata.web.dto;
|
||||
|
||||
import de.effigenix.shared.common.ContactInfo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(requiredProperties = {"phone", "email", "contactPerson"})
|
||||
public record ContactInfoResponse(
|
||||
String phone,
|
||||
String email,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package de.effigenix.infrastructure.masterdata.web.dto;
|
||||
|
||||
import de.effigenix.domain.masterdata.ContractLineItem;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(requiredProperties = {"articleId", "agreedPrice", "agreedQuantity", "unit"})
|
||||
public record ContractLineItemResponse(
|
||||
String articleId,
|
||||
BigDecimal agreedPrice,
|
||||
|
|
|
|||
|
|
@ -2,19 +2,21 @@ package de.effigenix.infrastructure.masterdata.web.dto;
|
|||
|
||||
import de.effigenix.domain.masterdata.Customer;
|
||||
import de.effigenix.domain.masterdata.CustomerPreference;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(requiredProperties = {"id", "name", "type", "billingAddress", "contactInfo", "deliveryAddresses", "preferences", "status", "createdAt", "updatedAt"})
|
||||
public record CustomerResponse(
|
||||
String id,
|
||||
String name,
|
||||
String type,
|
||||
AddressResponse billingAddress,
|
||||
ContactInfoResponse contactInfo,
|
||||
PaymentTermsResponse paymentTerms,
|
||||
@Schema(nullable = true) PaymentTermsResponse paymentTerms,
|
||||
List<DeliveryAddressResponse> deliveryAddresses,
|
||||
FrameContractResponse frameContract,
|
||||
@Schema(nullable = true) FrameContractResponse frameContract,
|
||||
List<String> preferences,
|
||||
String status,
|
||||
LocalDateTime createdAt,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package de.effigenix.infrastructure.masterdata.web.dto;
|
||||
|
||||
import de.effigenix.domain.masterdata.DeliveryAddress;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(requiredProperties = {"label", "address", "contactPerson", "deliveryNotes"})
|
||||
public record DeliveryAddressResponse(
|
||||
String label,
|
||||
AddressResponse address,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package de.effigenix.infrastructure.masterdata.web.dto;
|
||||
|
||||
import de.effigenix.domain.masterdata.FrameContract;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(requiredProperties = {"id", "validFrom", "validUntil", "deliveryRhythm", "lineItems"})
|
||||
public record FrameContractResponse(
|
||||
String id,
|
||||
LocalDate validFrom,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package de.effigenix.infrastructure.masterdata.web.dto;
|
||||
|
||||
import de.effigenix.shared.common.PaymentTerms;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(requiredProperties = {"paymentDueDays", "paymentDescription"})
|
||||
public record PaymentTermsResponse(
|
||||
int paymentDueDays,
|
||||
String paymentDescription
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package de.effigenix.infrastructure.masterdata.web.dto;
|
||||
|
||||
import de.effigenix.domain.masterdata.ProductCategory;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(requiredProperties = {"id", "name", "description"})
|
||||
public record ProductCategoryResponse(
|
||||
String id,
|
||||
String name,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package de.effigenix.infrastructure.masterdata.web.dto;
|
||||
|
||||
import de.effigenix.domain.masterdata.QualityCertificate;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Schema(requiredProperties = {"certificateType", "issuer", "validFrom", "validUntil"})
|
||||
public record QualityCertificateResponse(
|
||||
String certificateType,
|
||||
String issuer,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package de.effigenix.infrastructure.masterdata.web.dto;
|
||||
|
||||
import de.effigenix.domain.masterdata.SalesUnit;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Schema(requiredProperties = {"id", "unit", "priceModel", "price"})
|
||||
public record SalesUnitResponse(
|
||||
String id,
|
||||
String unit,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package de.effigenix.infrastructure.masterdata.web.dto;
|
||||
|
||||
import de.effigenix.domain.masterdata.SupplierRating;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(requiredProperties = {"qualityScore", "deliveryScore", "priceScore"})
|
||||
public record SupplierRatingResponse(
|
||||
int qualityScore,
|
||||
int deliveryScore,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
package de.effigenix.infrastructure.masterdata.web.dto;
|
||||
|
||||
import de.effigenix.domain.masterdata.Supplier;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(requiredProperties = {"id", "name", "contactInfo", "certificates", "status", "createdAt", "updatedAt"})
|
||||
public record SupplierResponse(
|
||||
String id,
|
||||
String name,
|
||||
AddressResponse address,
|
||||
@Schema(nullable = true) AddressResponse address,
|
||||
ContactInfoResponse contactInfo,
|
||||
PaymentTermsResponse paymentTerms,
|
||||
@Schema(nullable = true) PaymentTermsResponse paymentTerms,
|
||||
List<QualityCertificateResponse> certificates,
|
||||
SupplierRatingResponse rating,
|
||||
@Schema(nullable = true) SupplierRatingResponse rating,
|
||||
String status,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
package de.effigenix.infrastructure.production.web.dto;
|
||||
|
||||
import de.effigenix.domain.production.Ingredient;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(requiredProperties = {"id", "position", "articleId", "quantity", "uom", "substitutable"})
|
||||
public record IngredientResponse(
|
||||
String id,
|
||||
int position,
|
||||
String articleId,
|
||||
String quantity,
|
||||
String uom,
|
||||
String subRecipeId,
|
||||
@Schema(nullable = true) String subRecipeId,
|
||||
boolean substitutable
|
||||
) {
|
||||
public static IngredientResponse from(Ingredient ingredient) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package de.effigenix.infrastructure.production.web.dto;
|
||||
|
||||
import de.effigenix.domain.production.Recipe;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(requiredProperties = {"id", "name", "version", "type", "description", "yieldPercentage", "outputQuantity", "outputUom", "status", "ingredients", "createdAt", "updatedAt"})
|
||||
public record RecipeResponse(
|
||||
String id,
|
||||
String name,
|
||||
|
|
@ -12,7 +14,7 @@ public record RecipeResponse(
|
|||
String type,
|
||||
String description,
|
||||
int yieldPercentage,
|
||||
Integer shelfLifeDays,
|
||||
@Schema(nullable = true) Integer shelfLifeDays,
|
||||
String outputQuantity,
|
||||
String outputUom,
|
||||
String status,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import java.time.LocalDateTime;
|
|||
* Client should store the access token and send it in Authorization header
|
||||
* for subsequent requests.
|
||||
*/
|
||||
@Schema(description = "Login response with JWT tokens")
|
||||
@Schema(description = "Login response with JWT tokens",
|
||||
requiredProperties = {"accessToken", "tokenType", "expiresIn", "expiresAt", "refreshToken"})
|
||||
public record LoginResponse(
|
||||
@Schema(description = "JWT access token", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
||||
String accessToken,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,15 @@ import io.swagger.v3.oas.annotations.info.Info;
|
|||
import io.swagger.v3.oas.annotations.info.License;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
||||
import io.swagger.v3.oas.annotations.servers.Server;
|
||||
import org.springdoc.core.customizers.OpenApiCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* OpenAPI/Swagger Configuration.
|
||||
*
|
||||
|
|
@ -111,6 +118,35 @@ import org.springframework.context.annotation.Configuration;
|
|||
"""
|
||||
)
|
||||
public class OpenApiConfig {
|
||||
// Configuration is done via annotations
|
||||
// No additional beans needed
|
||||
|
||||
/**
|
||||
* Marks fields as required for application-layer DTOs that cannot carry
|
||||
* {@code @Schema} annotations (to preserve the clean architecture boundary).
|
||||
*/
|
||||
@Bean
|
||||
public OpenApiCustomizer applicationDtoRequiredFieldsCustomizer() {
|
||||
// Application-layer DTOs → all required except explicitly nullable
|
||||
Map<String, Set<String>> nullableFields = Map.of(
|
||||
"UserDTO", Set.of("branchId", "lastLogin"),
|
||||
"RoleDTO", Set.of()
|
||||
);
|
||||
|
||||
return openApi -> {
|
||||
var schemas = openApi.getComponents().getSchemas();
|
||||
if (schemas == null) return;
|
||||
|
||||
nullableFields.forEach((schemaName, nullable) -> {
|
||||
var schema = schemas.get(schemaName);
|
||||
if (schema == null || schema.getProperties() == null) return;
|
||||
|
||||
List<String> required = new ArrayList<>();
|
||||
schema.getProperties().keySet().forEach(prop -> {
|
||||
if (!nullable.contains(prop)) {
|
||||
required.add((String) prop);
|
||||
}
|
||||
});
|
||||
schema.setRequired(required);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue