1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 10:09:35 +01:00

fix(masterdata): MASTERDATA-Permissions und JSON-Serialisierung der REST-Responses

MASTERDATA_READ/WRITE fehlten im Permission-Enum und in den Rollen-Seed-Daten,
dadurch bekam der Admin bei allen Stammdaten-Schreiboperationen Access Denied.

Die Masterdata-Controller gaben Domain-Objekte direkt als JSON zurück, die von
Jackson nicht serialisiert werden konnten (method-style Accessors statt JavaBean-
Getter). Response-DTOs als Records eingeführt, die Domain-Objekte in flache
JSON-Strukturen mappen. Frontend-Mapping-Layer entfernt, da Backend-Responses
jetzt 1:1 die erwarteten Feldnamen liefern.
This commit is contained in:
Sebastian Frick 2026-02-18 22:13:23 +01:00
parent 3cccab1f4d
commit fbed3f899f
26 changed files with 481 additions and 364 deletions

View file

@ -79,6 +79,10 @@ public enum Permission {
LABEL_WRITE,
LABEL_PRINT,
// ==================== Master Data BC ====================
MASTERDATA_READ,
MASTERDATA_WRITE,
// ==================== Filiales BC ====================
BRANCH_READ,
BRANCH_WRITE,

View file

@ -7,8 +7,6 @@ import de.effigenix.domain.masterdata.ArticleError;
import de.effigenix.domain.masterdata.ArticleId;
import de.effigenix.domain.masterdata.ArticleStatus;
import de.effigenix.domain.masterdata.ProductCategoryId;
import de.effigenix.domain.masterdata.SalesUnitId;
import de.effigenix.domain.masterdata.SupplierId;
import de.effigenix.infrastructure.masterdata.web.dto.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
@ -73,7 +71,7 @@ public class ArticleController {
@PostMapping
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Article> createArticle(
public ResponseEntity<ArticleResponse> createArticle(
@Valid @RequestBody CreateArticleRequest request,
Authentication authentication
) {
@ -91,11 +89,11 @@ public class ArticleController {
}
logger.info("Article created: {}", request.articleNumber());
return ResponseEntity.status(HttpStatus.CREATED).body(result.unsafeGetValue());
return ResponseEntity.status(HttpStatus.CREATED).body(ArticleResponse.from(result.unsafeGetValue()));
}
@GetMapping
public ResponseEntity<List<Article>> listArticles(
public ResponseEntity<List<ArticleResponse>> listArticles(
@RequestParam(value = "categoryId", required = false) String categoryId,
@RequestParam(value = "status", required = false) ArticleStatus status,
Authentication authentication
@ -116,11 +114,12 @@ public class ArticleController {
throw new ArticleDomainErrorException(result.unsafeGetError());
}
return ResponseEntity.ok(result.unsafeGetValue());
var response = result.unsafeGetValue().stream().map(ArticleResponse::from).toList();
return ResponseEntity.ok(response);
}
@GetMapping("/{id}")
public ResponseEntity<Article> getArticle(
public ResponseEntity<ArticleResponse> getArticle(
@PathVariable("id") String articleId,
Authentication authentication
) {
@ -133,12 +132,12 @@ public class ArticleController {
throw new ArticleDomainErrorException(result.unsafeGetError());
}
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(ArticleResponse.from(result.unsafeGetValue()));
}
@PutMapping("/{id}")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Article> updateArticle(
public ResponseEntity<ArticleResponse> updateArticle(
@PathVariable("id") String articleId,
@Valid @RequestBody UpdateArticleRequest request,
Authentication authentication
@ -154,12 +153,12 @@ public class ArticleController {
}
logger.info("Article updated: {}", articleId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(ArticleResponse.from(result.unsafeGetValue()));
}
@PostMapping("/{id}/activate")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Article> activate(
public ResponseEntity<ArticleResponse> activate(
@PathVariable("id") String articleId,
Authentication authentication
) {
@ -173,12 +172,12 @@ public class ArticleController {
}
logger.info("Article activated: {}", articleId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(ArticleResponse.from(result.unsafeGetValue()));
}
@PostMapping("/{id}/deactivate")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Article> deactivate(
public ResponseEntity<ArticleResponse> deactivate(
@PathVariable("id") String articleId,
Authentication authentication
) {
@ -192,12 +191,12 @@ public class ArticleController {
}
logger.info("Article deactivated: {}", articleId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(ArticleResponse.from(result.unsafeGetValue()));
}
@PostMapping("/{id}/sales-units")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Article> addSalesUnit(
public ResponseEntity<ArticleResponse> addSalesUnit(
@PathVariable("id") String articleId,
@Valid @RequestBody AddSalesUnitRequest request,
Authentication authentication
@ -213,7 +212,7 @@ public class ArticleController {
}
logger.info("Sales unit added to article: {}", articleId);
return ResponseEntity.status(HttpStatus.CREATED).body(result.unsafeGetValue());
return ResponseEntity.status(HttpStatus.CREATED).body(ArticleResponse.from(result.unsafeGetValue()));
}
@DeleteMapping("/{id}/sales-units/{suId}")
@ -239,7 +238,7 @@ public class ArticleController {
@PutMapping("/{id}/sales-units/{suId}/price")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Article> updateSalesUnitPrice(
public ResponseEntity<ArticleResponse> updateSalesUnitPrice(
@PathVariable("id") String articleId,
@PathVariable("suId") String salesUnitId,
@Valid @RequestBody UpdateSalesUnitPriceRequest request,
@ -256,12 +255,12 @@ public class ArticleController {
}
logger.info("Sales unit price updated in article: {}", articleId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(ArticleResponse.from(result.unsafeGetValue()));
}
@PostMapping("/{id}/suppliers")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Article> assignSupplier(
public ResponseEntity<ArticleResponse> assignSupplier(
@PathVariable("id") String articleId,
@Valid @RequestBody AssignSupplierRequest request,
Authentication authentication
@ -277,7 +276,7 @@ public class ArticleController {
}
logger.info("Supplier assigned to article: {}", articleId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(ArticleResponse.from(result.unsafeGetValue()));
}
@DeleteMapping("/{id}/suppliers/{supplierId}")

View file

@ -72,7 +72,7 @@ public class CustomerController {
@PostMapping
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Customer> createCustomer(
public ResponseEntity<CustomerResponse> createCustomer(
@Valid @RequestBody CreateCustomerRequest request,
Authentication authentication
) {
@ -93,11 +93,11 @@ public class CustomerController {
}
logger.info("Customer created: {}", request.name());
return ResponseEntity.status(HttpStatus.CREATED).body(result.unsafeGetValue());
return ResponseEntity.status(HttpStatus.CREATED).body(CustomerResponse.from(result.unsafeGetValue()));
}
@GetMapping
public ResponseEntity<List<Customer>> listCustomers(
public ResponseEntity<List<CustomerResponse>> listCustomers(
@RequestParam(value = "type", required = false) CustomerType type,
@RequestParam(value = "status", required = false) CustomerStatus status,
Authentication authentication
@ -118,11 +118,12 @@ public class CustomerController {
throw new CustomerDomainErrorException(result.unsafeGetError());
}
return ResponseEntity.ok(result.unsafeGetValue());
var response = result.unsafeGetValue().stream().map(CustomerResponse::from).toList();
return ResponseEntity.ok(response);
}
@GetMapping("/{id}")
public ResponseEntity<Customer> getCustomer(
public ResponseEntity<CustomerResponse> getCustomer(
@PathVariable("id") String customerId,
Authentication authentication
) {
@ -135,12 +136,12 @@ public class CustomerController {
throw new CustomerDomainErrorException(result.unsafeGetError());
}
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(CustomerResponse.from(result.unsafeGetValue()));
}
@PutMapping("/{id}")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Customer> updateCustomer(
public ResponseEntity<CustomerResponse> updateCustomer(
@PathVariable("id") String customerId,
@Valid @RequestBody UpdateCustomerRequest request,
Authentication authentication
@ -163,12 +164,12 @@ public class CustomerController {
}
logger.info("Customer updated: {}", customerId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(CustomerResponse.from(result.unsafeGetValue()));
}
@PostMapping("/{id}/activate")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Customer> activate(
public ResponseEntity<CustomerResponse> activate(
@PathVariable("id") String customerId,
Authentication authentication
) {
@ -182,12 +183,12 @@ public class CustomerController {
}
logger.info("Customer activated: {}", customerId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(CustomerResponse.from(result.unsafeGetValue()));
}
@PostMapping("/{id}/deactivate")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Customer> deactivate(
public ResponseEntity<CustomerResponse> deactivate(
@PathVariable("id") String customerId,
Authentication authentication
) {
@ -201,12 +202,12 @@ public class CustomerController {
}
logger.info("Customer deactivated: {}", customerId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(CustomerResponse.from(result.unsafeGetValue()));
}
@PostMapping("/{id}/delivery-addresses")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Customer> addDeliveryAddress(
public ResponseEntity<CustomerResponse> addDeliveryAddress(
@PathVariable("id") String customerId,
@Valid @RequestBody AddDeliveryAddressRequest request,
Authentication authentication
@ -227,7 +228,7 @@ public class CustomerController {
}
logger.info("Delivery address added to customer: {}", customerId);
return ResponseEntity.status(HttpStatus.CREATED).body(result.unsafeGetValue());
return ResponseEntity.status(HttpStatus.CREATED).body(CustomerResponse.from(result.unsafeGetValue()));
}
@DeleteMapping("/{id}/delivery-addresses/{label}")
@ -253,7 +254,7 @@ public class CustomerController {
@PutMapping("/{id}/frame-contract")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Customer> setFrameContract(
public ResponseEntity<CustomerResponse> setFrameContract(
@PathVariable("id") String customerId,
@Valid @RequestBody SetFrameContractRequest request,
Authentication authentication
@ -277,7 +278,7 @@ public class CustomerController {
}
logger.info("Frame contract set for customer: {}", customerId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(CustomerResponse.from(result.unsafeGetValue()));
}
@DeleteMapping("/{id}/frame-contract")
@ -301,7 +302,7 @@ public class CustomerController {
@PutMapping("/{id}/preferences")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Customer> setPreferences(
public ResponseEntity<CustomerResponse> setPreferences(
@PathVariable("id") String customerId,
@Valid @RequestBody SetPreferencesRequest request,
Authentication authentication
@ -317,7 +318,7 @@ public class CustomerController {
}
logger.info("Preferences set for customer: {}", customerId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(CustomerResponse.from(result.unsafeGetValue()));
}
private ActorId extractActorId(Authentication authentication) {

View file

@ -6,11 +6,10 @@ import de.effigenix.application.masterdata.ListProductCategories;
import de.effigenix.application.masterdata.UpdateProductCategory;
import de.effigenix.application.masterdata.command.CreateProductCategoryCommand;
import de.effigenix.application.masterdata.command.UpdateProductCategoryCommand;
import de.effigenix.domain.masterdata.ProductCategory;
import de.effigenix.domain.masterdata.ProductCategoryId;
import de.effigenix.infrastructure.masterdata.web.dto.CreateProductCategoryRequest;
import de.effigenix.infrastructure.masterdata.web.dto.ProductCategoryResponse;
import de.effigenix.infrastructure.masterdata.web.dto.UpdateProductCategoryRequest;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -52,7 +51,7 @@ public class ProductCategoryController {
@PostMapping
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<ProductCategory> createCategory(
public ResponseEntity<ProductCategoryResponse> createCategory(
@Valid @RequestBody CreateProductCategoryRequest request,
Authentication authentication
) {
@ -67,11 +66,11 @@ public class ProductCategoryController {
}
logger.info("Product category created: {}", request.name());
return ResponseEntity.status(HttpStatus.CREATED).body(result.unsafeGetValue());
return ResponseEntity.status(HttpStatus.CREATED).body(ProductCategoryResponse.from(result.unsafeGetValue()));
}
@GetMapping
public ResponseEntity<List<ProductCategory>> listCategories(Authentication authentication) {
public ResponseEntity<List<ProductCategoryResponse>> listCategories(Authentication authentication) {
var actorId = extractActorId(authentication);
logger.info("Listing product categories by actor: {}", actorId.value());
@ -81,12 +80,13 @@ public class ProductCategoryController {
throw new ProductCategoryDomainErrorException(result.unsafeGetError());
}
return ResponseEntity.ok(result.unsafeGetValue());
var response = result.unsafeGetValue().stream().map(ProductCategoryResponse::from).toList();
return ResponseEntity.ok(response);
}
@PutMapping("/{id}")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<ProductCategory> updateCategory(
public ResponseEntity<ProductCategoryResponse> updateCategory(
@PathVariable("id") String categoryId,
@Valid @RequestBody UpdateProductCategoryRequest request,
Authentication authentication
@ -102,7 +102,7 @@ public class ProductCategoryController {
}
logger.info("Product category updated: {}", categoryId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(ProductCategoryResponse.from(result.unsafeGetValue()));
}
@DeleteMapping("/{id}")

View file

@ -64,7 +64,7 @@ public class SupplierController {
@PostMapping
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Supplier> createSupplier(
public ResponseEntity<SupplierResponse> createSupplier(
@Valid @RequestBody CreateSupplierRequest request,
Authentication authentication
) {
@ -84,11 +84,11 @@ public class SupplierController {
}
logger.info("Supplier created: {}", request.name());
return ResponseEntity.status(HttpStatus.CREATED).body(result.unsafeGetValue());
return ResponseEntity.status(HttpStatus.CREATED).body(SupplierResponse.from(result.unsafeGetValue()));
}
@GetMapping
public ResponseEntity<List<Supplier>> listSuppliers(
public ResponseEntity<List<SupplierResponse>> listSuppliers(
@RequestParam(value = "status", required = false) SupplierStatus status,
Authentication authentication
) {
@ -106,11 +106,12 @@ public class SupplierController {
throw new SupplierDomainErrorException(result.unsafeGetError());
}
return ResponseEntity.ok(result.unsafeGetValue());
var response = result.unsafeGetValue().stream().map(SupplierResponse::from).toList();
return ResponseEntity.ok(response);
}
@GetMapping("/{id}")
public ResponseEntity<Supplier> getSupplier(
public ResponseEntity<SupplierResponse> getSupplier(
@PathVariable("id") String supplierId,
Authentication authentication
) {
@ -123,12 +124,12 @@ public class SupplierController {
throw new SupplierDomainErrorException(result.unsafeGetError());
}
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(SupplierResponse.from(result.unsafeGetValue()));
}
@PutMapping("/{id}")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Supplier> updateSupplier(
public ResponseEntity<SupplierResponse> updateSupplier(
@PathVariable("id") String supplierId,
@Valid @RequestBody UpdateSupplierRequest request,
Authentication authentication
@ -150,12 +151,12 @@ public class SupplierController {
}
logger.info("Supplier updated: {}", supplierId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(SupplierResponse.from(result.unsafeGetValue()));
}
@PostMapping("/{id}/activate")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Supplier> activate(
public ResponseEntity<SupplierResponse> activate(
@PathVariable("id") String supplierId,
Authentication authentication
) {
@ -169,12 +170,12 @@ public class SupplierController {
}
logger.info("Supplier activated: {}", supplierId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(SupplierResponse.from(result.unsafeGetValue()));
}
@PostMapping("/{id}/deactivate")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Supplier> deactivate(
public ResponseEntity<SupplierResponse> deactivate(
@PathVariable("id") String supplierId,
Authentication authentication
) {
@ -188,12 +189,12 @@ public class SupplierController {
}
logger.info("Supplier deactivated: {}", supplierId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(SupplierResponse.from(result.unsafeGetValue()));
}
@PostMapping("/{id}/rating")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Supplier> rateSupplier(
public ResponseEntity<SupplierResponse> rateSupplier(
@PathVariable("id") String supplierId,
@Valid @RequestBody RateSupplierRequest request,
Authentication authentication
@ -211,12 +212,12 @@ public class SupplierController {
}
logger.info("Supplier rated: {}", supplierId);
return ResponseEntity.ok(result.unsafeGetValue());
return ResponseEntity.ok(SupplierResponse.from(result.unsafeGetValue()));
}
@PostMapping("/{id}/certificates")
@PreAuthorize("hasAuthority('MASTERDATA_WRITE')")
public ResponseEntity<Supplier> addCertificate(
public ResponseEntity<SupplierResponse> addCertificate(
@PathVariable("id") String supplierId,
@Valid @RequestBody AddCertificateRequest request,
Authentication authentication
@ -235,7 +236,7 @@ public class SupplierController {
}
logger.info("Certificate added to supplier: {}", supplierId);
return ResponseEntity.status(HttpStatus.CREATED).body(result.unsafeGetValue());
return ResponseEntity.status(HttpStatus.CREATED).body(SupplierResponse.from(result.unsafeGetValue()));
}
@DeleteMapping("/{id}/certificates")

View file

@ -0,0 +1,22 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.shared.common.Address;
public record AddressResponse(
String street,
String houseNumber,
String postalCode,
String city,
String country
) {
public static AddressResponse from(Address address) {
if (address == null) return null;
return new AddressResponse(
address.street(),
address.houseNumber(),
address.postalCode(),
address.city(),
address.country()
);
}
}

View file

@ -0,0 +1,33 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.domain.masterdata.Article;
import de.effigenix.domain.masterdata.SupplierId;
import java.time.LocalDateTime;
import java.util.List;
public record ArticleResponse(
String id,
String name,
String articleNumber,
String categoryId,
List<SalesUnitResponse> salesUnits,
String status,
List<String> supplierIds,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
public static ArticleResponse from(Article article) {
return new ArticleResponse(
article.id().value(),
article.name().value(),
article.articleNumber().value(),
article.categoryId().value(),
article.salesUnits().stream().map(SalesUnitResponse::from).toList(),
article.status().name(),
article.supplierReferences().stream().map(SupplierId::value).toList(),
article.createdAt(),
article.updatedAt()
);
}
}

View file

@ -0,0 +1,14 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.shared.common.ContactInfo;
public record ContactInfoResponse(
String phone,
String email,
String contactPerson
) {
public static ContactInfoResponse from(ContactInfo ci) {
if (ci == null) return null;
return new ContactInfoResponse(ci.phone(), ci.email(), ci.contactPerson());
}
}

View file

@ -0,0 +1,21 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.domain.masterdata.ContractLineItem;
import java.math.BigDecimal;
public record ContractLineItemResponse(
String articleId,
BigDecimal agreedPrice,
BigDecimal agreedQuantity,
String unit
) {
public static ContractLineItemResponse from(ContractLineItem item) {
return new ContractLineItemResponse(
item.articleId().value(),
item.agreedPrice().amount(),
item.agreedQuantity(),
item.unit().name()
);
}
}

View file

@ -0,0 +1,39 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.domain.masterdata.Customer;
import de.effigenix.domain.masterdata.CustomerPreference;
import java.time.LocalDateTime;
import java.util.List;
public record CustomerResponse(
String id,
String name,
String type,
AddressResponse billingAddress,
ContactInfoResponse contactInfo,
PaymentTermsResponse paymentTerms,
List<DeliveryAddressResponse> deliveryAddresses,
FrameContractResponse frameContract,
List<String> preferences,
String status,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
public static CustomerResponse from(Customer customer) {
return new CustomerResponse(
customer.id().value(),
customer.name().value(),
customer.type().name(),
AddressResponse.from(customer.billingAddress()),
ContactInfoResponse.from(customer.contactInfo()),
PaymentTermsResponse.from(customer.paymentTerms()),
customer.deliveryAddresses().stream().map(DeliveryAddressResponse::from).toList(),
FrameContractResponse.from(customer.frameContract()),
customer.preferences().stream().map(CustomerPreference::name).toList(),
customer.status().name(),
customer.createdAt(),
customer.updatedAt()
);
}
}

View file

@ -0,0 +1,19 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.domain.masterdata.DeliveryAddress;
public record DeliveryAddressResponse(
String label,
AddressResponse address,
String contactPerson,
String deliveryNotes
) {
public static DeliveryAddressResponse from(DeliveryAddress da) {
return new DeliveryAddressResponse(
da.label(),
AddressResponse.from(da.address()),
da.contactPerson(),
da.deliveryNotes()
);
}
}

View file

@ -0,0 +1,25 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.domain.masterdata.FrameContract;
import java.time.LocalDate;
import java.util.List;
public record FrameContractResponse(
String id,
LocalDate validFrom,
LocalDate validUntil,
String deliveryRhythm,
List<ContractLineItemResponse> lineItems
) {
public static FrameContractResponse from(FrameContract fc) {
if (fc == null) return null;
return new FrameContractResponse(
fc.id().value(),
fc.validFrom(),
fc.validUntil(),
fc.deliveryRhythm().name(),
fc.lineItems().stream().map(ContractLineItemResponse::from).toList()
);
}
}

View file

@ -0,0 +1,13 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.shared.common.PaymentTerms;
public record PaymentTermsResponse(
int paymentDueDays,
String paymentDescription
) {
public static PaymentTermsResponse from(PaymentTerms pt) {
if (pt == null) return null;
return new PaymentTermsResponse(pt.paymentDueDays(), pt.description());
}
}

View file

@ -0,0 +1,17 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.domain.masterdata.ProductCategory;
public record ProductCategoryResponse(
String id,
String name,
String description
) {
public static ProductCategoryResponse from(ProductCategory category) {
return new ProductCategoryResponse(
category.id().value(),
category.name().value(),
category.description()
);
}
}

View file

@ -0,0 +1,21 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.domain.masterdata.QualityCertificate;
import java.time.LocalDate;
public record QualityCertificateResponse(
String certificateType,
String issuer,
LocalDate validFrom,
LocalDate validUntil
) {
public static QualityCertificateResponse from(QualityCertificate cert) {
return new QualityCertificateResponse(
cert.certificateType(),
cert.issuer(),
cert.validFrom(),
cert.validUntil()
);
}
}

View file

@ -0,0 +1,21 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.domain.masterdata.SalesUnit;
import java.math.BigDecimal;
public record SalesUnitResponse(
String id,
String unit,
String priceModel,
BigDecimal price
) {
public static SalesUnitResponse from(SalesUnit su) {
return new SalesUnitResponse(
su.id().value(),
su.unit().name(),
su.priceModel().name(),
su.price().amount()
);
}
}

View file

@ -0,0 +1,18 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.domain.masterdata.SupplierRating;
public record SupplierRatingResponse(
int qualityScore,
int deliveryScore,
int priceScore
) {
public static SupplierRatingResponse from(SupplierRating rating) {
if (rating == null) return null;
return new SupplierRatingResponse(
rating.qualityScore(),
rating.deliveryScore(),
rating.priceScore()
);
}
}

View file

@ -0,0 +1,34 @@
package de.effigenix.infrastructure.masterdata.web.dto;
import de.effigenix.domain.masterdata.Supplier;
import java.time.LocalDateTime;
import java.util.List;
public record SupplierResponse(
String id,
String name,
AddressResponse address,
ContactInfoResponse contactInfo,
PaymentTermsResponse paymentTerms,
List<QualityCertificateResponse> certificates,
SupplierRatingResponse rating,
String status,
LocalDateTime createdAt,
LocalDateTime updatedAt
) {
public static SupplierResponse from(Supplier supplier) {
return new SupplierResponse(
supplier.id().value(),
supplier.name().value(),
AddressResponse.from(supplier.address()),
ContactInfoResponse.from(supplier.contactInfo()),
PaymentTermsResponse.from(supplier.paymentTerms()),
supplier.certificates().stream().map(QualityCertificateResponse::from).toList(),
SupplierRatingResponse.from(supplier.rating()),
supplier.status().name(),
supplier.createdAt(),
supplier.updatedAt()
);
}
}

View file

@ -0,0 +1,30 @@
-- Add MASTERDATA_READ and MASTERDATA_WRITE permissions to relevant roles.
-- These permissions are required by the Masterdata BC controllers
-- (ArticleController, ProductCategoryController, SupplierController, CustomerController).
-- ADMIN gets both READ and WRITE
INSERT INTO role_permissions (role_id, permission) VALUES
('c0a80121-0000-0000-0000-000000000001', 'MASTERDATA_READ'),
('c0a80121-0000-0000-0000-000000000001', 'MASTERDATA_WRITE');
-- PROCUREMENT_MANAGER gets both (manages suppliers, articles)
INSERT INTO role_permissions (role_id, permission) VALUES
('c0a80121-0000-0000-0000-000000000006', 'MASTERDATA_READ'),
('c0a80121-0000-0000-0000-000000000006', 'MASTERDATA_WRITE');
-- SALES_MANAGER gets both (manages customers)
INSERT INTO role_permissions (role_id, permission) VALUES
('c0a80121-0000-0000-0000-000000000008', 'MASTERDATA_READ'),
('c0a80121-0000-0000-0000-000000000008', 'MASTERDATA_WRITE');
-- PRODUCTION_MANAGER gets READ (needs to view articles, categories)
INSERT INTO role_permissions (role_id, permission) VALUES
('c0a80121-0000-0000-0000-000000000002', 'MASTERDATA_READ');
-- WAREHOUSE_WORKER gets READ (needs to view articles)
INSERT INTO role_permissions (role_id, permission) VALUES
('c0a80121-0000-0000-0000-000000000007', 'MASTERDATA_READ');
-- SALES_STAFF gets READ (needs to view articles, customers)
INSERT INTO role_permissions (role_id, permission) VALUES
('c0a80121-0000-0000-0000-000000000009', 'MASTERDATA_READ');

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet id="008-add-masterdata-permissions" author="effigenix">
<sqlFile path="db/changelog/changes/008-add-masterdata-permissions.sql"/>
</changeSet>
</databaseChangeLog>

View file

@ -12,5 +12,6 @@
<include file="db/changelog/changes/005-create-masterdata-schema.xml"/>
<include file="db/changelog/changes/006-create-supplier-schema.xml"/>
<include file="db/changelog/changes/007-create-customer-schema.xml"/>
<include file="db/changelog/changes/008-add-masterdata-permissions.xml"/>
</databaseChangeLog>