feat(back-end & ): removed Abstract repository
Some checks failed
Build, Test and Deploy / test-backend (push) Successful in 1m12s
Build, Test and Deploy / build-and-push (push) Successful in 38s
Build, Test and Deploy / deploy (push) Has been cancelled

This commit is contained in:
2026-02-12 16:00:03 +01:00
parent 96ae9bb609
commit 9c3d5fae12
11 changed files with 742 additions and 29 deletions

View File

@@ -0,0 +1,131 @@
package com.printcalculator.controller;
import com.printcalculator.entity.CustomQuoteRequest;
import com.printcalculator.entity.CustomQuoteRequestAttachment;
import com.printcalculator.repository.CustomQuoteRequestAttachmentRepository;
import com.printcalculator.repository.CustomQuoteRequestRepository;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/custom-quote-requests")
@CrossOrigin(origins = "*")
public class CustomQuoteRequestController {
private final CustomQuoteRequestRepository requestRepo;
private final CustomQuoteRequestAttachmentRepository attachmentRepo;
// TODO: Inject Storage Service
private static final String STORAGE_ROOT = "storage_requests";
public CustomQuoteRequestController(CustomQuoteRequestRepository requestRepo,
CustomQuoteRequestAttachmentRepository attachmentRepo) {
this.requestRepo = requestRepo;
this.attachmentRepo = attachmentRepo;
}
// 1. Create Custom Quote Request
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Transactional
public ResponseEntity<CustomQuoteRequest> createCustomQuoteRequest(
// Form fields
@RequestParam("requestType") String requestType,
@RequestParam("customerType") String customerType,
@RequestParam("email") String email,
@RequestParam(value = "phone", required = false) String phone,
@RequestParam(value = "name", required = false) String name,
@RequestParam(value = "companyName", required = false) String companyName,
@RequestParam(value = "contactPerson", required = false) String contactPerson,
@RequestParam("message") String message,
// Files (Max 15)
@RequestParam(value = "files", required = false) List<MultipartFile> files
) throws IOException {
// 1. Create Request
CustomQuoteRequest request = new CustomQuoteRequest();
request.setRequestType(requestType);
request.setCustomerType(customerType);
request.setEmail(email);
request.setPhone(phone);
request.setName(name);
request.setCompanyName(companyName);
request.setContactPerson(contactPerson);
request.setMessage(message);
request.setStatus("PENDING");
request.setCreatedAt(OffsetDateTime.now());
request.setUpdatedAt(OffsetDateTime.now());
request = requestRepo.save(request);
// 2. Handle Attachments
if (files != null && !files.isEmpty()) {
if (files.size() > 15) {
throw new IOException("Too many files. Max 15 allowed.");
}
for (MultipartFile file : files) {
if (file.isEmpty()) continue;
CustomQuoteRequestAttachment attachment = new CustomQuoteRequestAttachment();
attachment.setRequest(request);
attachment.setOriginalFilename(file.getOriginalFilename());
attachment.setMimeType(file.getContentType());
attachment.setFileSizeBytes(file.getSize());
attachment.setCreatedAt(OffsetDateTime.now());
// Generate path
UUID fileUuid = UUID.randomUUID();
String ext = getExtension(file.getOriginalFilename());
String storedFilename = fileUuid.toString() + "." + ext;
// Note: We don't have attachment ID yet.
// We'll save attachment first to get ID.
attachment.setStoredFilename(storedFilename);
attachment.setStoredRelativePath("PENDING");
attachment = attachmentRepo.save(attachment);
String relativePath = "quote-requests/" + request.getId() + "/attachments/" + attachment.getId() + "/" + storedFilename;
attachment.setStoredRelativePath(relativePath);
attachmentRepo.save(attachment);
// Save file to disk
Path absolutePath = Paths.get(STORAGE_ROOT, relativePath);
Files.createDirectories(absolutePath.getParent());
Files.copy(file.getInputStream(), absolutePath);
}
}
return ResponseEntity.ok(request);
}
// 2. Get Request
@GetMapping("/{id}")
public ResponseEntity<CustomQuoteRequest> getCustomQuoteRequest(@PathVariable UUID id) {
return requestRepo.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// Helper
private String getExtension(String filename) {
if (filename == null) return "dat";
int i = filename.lastIndexOf('.');
if (i > 0) {
return filename.substring(i + 1);
}
return "dat";
}
}

View File

@@ -0,0 +1,285 @@
package com.printcalculator.controller;
import com.printcalculator.entity.*;
import com.printcalculator.repository.*;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;
import java.util.Optional;
@RestController
@RequestMapping("/api/orders")
@CrossOrigin(origins = "*")
public class OrderController {
private final OrderRepository orderRepo;
private final OrderItemRepository orderItemRepo;
private final QuoteSessionRepository quoteSessionRepo;
private final QuoteLineItemRepository quoteLineItemRepo;
private final CustomerRepository customerRepo;
// TODO: Inject Storage Service or use a base path property
private static final String STORAGE_ROOT = "storage_orders";
public OrderController(OrderRepository orderRepo,
OrderItemRepository orderItemRepo,
QuoteSessionRepository quoteSessionRepo,
QuoteLineItemRepository quoteLineItemRepo,
CustomerRepository customerRepo) {
this.orderRepo = orderRepo;
this.orderItemRepo = orderItemRepo;
this.quoteSessionRepo = quoteSessionRepo;
this.quoteLineItemRepo = quoteLineItemRepo;
this.customerRepo = customerRepo;
}
// DTOs
public static class CreateOrderRequest {
public CustomerDto customer;
public AddressDto billingAddress;
public AddressDto shippingAddress;
public boolean shippingSameAsBilling;
}
public static class CustomerDto {
public String email;
public String phone;
public String customerType; // "PRIVATE", "BUSINESS"
}
public static class AddressDto {
public String firstName;
public String lastName;
public String companyName;
public String contactPerson;
public String addressLine1;
public String addressLine2;
public String zip;
public String city;
public String countryCode;
}
// 1. Create Order from Quote
@PostMapping("/from-quote/{quoteSessionId}")
@Transactional
public ResponseEntity<Order> createOrderFromQuote(
@PathVariable UUID quoteSessionId,
@RequestBody CreateOrderRequest request
) {
// 1. Fetch Quote Session
QuoteSession session = quoteSessionRepo.findById(quoteSessionId)
.orElseThrow(() -> new RuntimeException("Quote Session not found"));
if (!"ACTIVE".equals(session.getStatus())) {
// Allow converting only active sessions? Or check if not already converted?
// checking convertedOrderId might be better
}
if (session.getConvertedOrderId() != null) {
return ResponseEntity.badRequest().body(null); // Already converted
}
// 2. Handle Customer (Find or Create)
Customer customer = customerRepo.findByEmail(request.customer.email)
.orElseGet(() -> {
Customer newC = new Customer();
newC.setEmail(request.customer.email);
newC.setCreatedAt(OffsetDateTime.now());
return customerRepo.save(newC);
});
// Update customer details?
customer.setPhone(request.customer.phone);
customer.setCustomerType(request.customer.customerType);
customer.setUpdatedAt(OffsetDateTime.now());
customerRepo.save(customer);
// 3. Create Order
Order order = new Order();
order.setSourceQuoteSession(session);
order.setCustomer(customer);
order.setCustomerEmail(request.customer.email);
order.setCustomerPhone(request.customer.phone);
order.setStatus("PENDING_PAYMENT");
order.setCreatedAt(OffsetDateTime.now());
order.setUpdatedAt(OffsetDateTime.now());
order.setCurrency("CHF");
// Billing
order.setBillingCustomerType(request.customer.customerType);
if (request.billingAddress != null) {
order.setBillingFirstName(request.billingAddress.firstName);
order.setBillingLastName(request.billingAddress.lastName);
order.setBillingCompanyName(request.billingAddress.companyName);
order.setBillingContactPerson(request.billingAddress.contactPerson);
order.setBillingAddressLine1(request.billingAddress.addressLine1);
order.setBillingAddressLine2(request.billingAddress.addressLine2);
order.setBillingZip(request.billingAddress.zip);
order.setBillingCity(request.billingAddress.city);
order.setBillingCountryCode(request.billingAddress.countryCode != null ? request.billingAddress.countryCode : "CH");
}
// Shipping
order.setShippingSameAsBilling(request.shippingSameAsBilling);
if (!request.shippingSameAsBilling && request.shippingAddress != null) {
order.setShippingFirstName(request.shippingAddress.firstName);
order.setShippingLastName(request.shippingAddress.lastName);
order.setShippingCompanyName(request.shippingAddress.companyName);
order.setShippingContactPerson(request.shippingAddress.contactPerson);
order.setShippingAddressLine1(request.shippingAddress.addressLine1);
order.setShippingAddressLine2(request.shippingAddress.addressLine2);
order.setShippingZip(request.shippingAddress.zip);
order.setShippingCity(request.shippingAddress.city);
order.setShippingCountryCode(request.shippingAddress.countryCode != null ? request.shippingAddress.countryCode : "CH");
} else {
// Copy billing to shipping? Or leave empty and rely on flag?
// Usually explicit copy is safer for queries
order.setShippingFirstName(order.getBillingFirstName());
order.setShippingLastName(order.getBillingLastName());
order.setShippingCompanyName(order.getBillingCompanyName());
order.setShippingContactPerson(order.getBillingContactPerson());
order.setShippingAddressLine1(order.getBillingAddressLine1());
order.setShippingAddressLine2(order.getBillingAddressLine2());
order.setShippingZip(order.getBillingZip());
order.setShippingCity(order.getBillingCity());
order.setShippingCountryCode(order.getBillingCountryCode());
}
// Financials from Session (Assuming mocked/calculated in session)
// We re-calculate totals from line items to be safe
List<QuoteLineItem> quoteItems = quoteLineItemRepo.findByQuoteSessionId(quoteSessionId);
BigDecimal subtotal = BigDecimal.ZERO;
// Save Order first to get ID
order = orderRepo.save(order);
// 4. Create Order Items
for (QuoteLineItem qItem : quoteItems) {
OrderItem oItem = new OrderItem();
oItem.setOrder(order);
oItem.setOriginalFilename(qItem.getOriginalFilename());
oItem.setQuantity(qItem.getQuantity());
oItem.setColorCode(qItem.getColorCode());
oItem.setMaterialCode(session.getMaterialCode()); // Or per item if supported
// Pricing
oItem.setUnitPriceChf(qItem.getUnitPriceChf());
oItem.setLineTotalChf(qItem.getUnitPriceChf().multiply(BigDecimal.valueOf(qItem.getQuantity())));
oItem.setPrintTimeSeconds(qItem.getPrintTimeSeconds());
oItem.setMaterialGrams(qItem.getMaterialGrams());
// File Handling Check
// "orders/{orderId}/3d-files/{orderItemId}/{uuid}.{ext}"
UUID fileUuid = UUID.randomUUID();
String ext = getExtension(qItem.getOriginalFilename());
String storedFilename = fileUuid.toString() + "." + ext;
// Note: We don't have the orderItemId yet because we haven't saved it.
// We can pre-generate ID or save order item then update path?
// GeneratedValue strategy AUTO might not let us set ID easily?
// Let's save item first with temporary path, then update?
// OR use a path structure that doesn't depend on ItemId? "orders/{orderId}/3d-files/{uuid}.ext" is also fine?
// User requested: "orders/{orderId}/3d-files/{orderItemId}/{uuid}.{ext}"
// So we need OrderItemId.
oItem.setStoredFilename(storedFilename);
oItem.setStoredRelativePath("PENDING"); // Placeholder
oItem.setMimeType("application/octet-stream"); // specific type if known
oItem = orderItemRepo.save(oItem);
// Update Path now that we have ID
String relativePath = "orders/" + order.getId() + "/3d-files/" + oItem.getId() + "/" + storedFilename;
oItem.setStoredRelativePath(relativePath);
orderItemRepo.save(oItem);
subtotal = subtotal.add(oItem.getLineTotalChf());
}
// Update Order Totals
order.setSubtotalChf(subtotal);
order.setSetupCostChf(session.getSetupCostChf());
order.setShippingCostChf(BigDecimal.valueOf(9.00)); // Default shipping? or 0?
// TODO: Calc implementation for shipping
BigDecimal total = subtotal.add(order.getSetupCostChf()).add(order.getShippingCostChf()).subtract(order.getDiscountChf() != null ? order.getDiscountChf() : BigDecimal.ZERO);
order.setTotalChf(total);
// Link session
session.setConvertedOrderId(order.getId());
session.setStatus("CONVERTED"); // or CLOSED
quoteSessionRepo.save(session);
return ResponseEntity.ok(orderRepo.save(order));
}
// 2. Upload file for Order Item
@PostMapping(value = "/{orderId}/items/{orderItemId}/file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Transactional
public ResponseEntity<Void> uploadOrderItemFile(
@PathVariable UUID orderId,
@PathVariable UUID orderItemId,
@RequestParam("file") MultipartFile file
) throws IOException {
OrderItem item = orderItemRepo.findById(orderItemId)
.orElseThrow(() -> new RuntimeException("OrderItem not found"));
if (!item.getOrder().getId().equals(orderId)) {
return ResponseEntity.badRequest().build();
}
// Ensure path logic
String relativePath = item.getStoredRelativePath();
if (relativePath == null || relativePath.equals("PENDING")) {
// Should verify consistency
// If we used the logic above, it should have a path.
// If it's "PENDING", regen it.
String ext = getExtension(file.getOriginalFilename());
String storedFilename = UUID.randomUUID().toString() + "." + ext;
relativePath = "orders/" + orderId + "/3d-files/" + orderItemId + "/" + storedFilename;
item.setStoredRelativePath(relativePath);
item.setStoredFilename(storedFilename);
// Update item
}
// Save file to disk
Path absolutePath = Paths.get(STORAGE_ROOT, relativePath);
Files.createDirectories(absolutePath.getParent());
if (Files.exists(absolutePath)) {
Files.delete(absolutePath); // Overwrite?
}
Files.copy(file.getInputStream(), absolutePath);
item.setFileSizeBytes(file.getSize());
item.setMimeType(file.getContentType());
// Calculate SHA256? (Optional)
orderItemRepo.save(item);
return ResponseEntity.ok().build();
}
private String getExtension(String filename) {
if (filename == null) return "stl";
int i = filename.lastIndexOf('.');
if (i > 0) {
return filename.substring(i + 1);
}
return "stl";
}
}

View File

@@ -0,0 +1,226 @@
package com.printcalculator.controller;
import com.printcalculator.entity.PrinterMachine;
import com.printcalculator.entity.QuoteLineItem;
import com.printcalculator.entity.QuoteSession;
import com.printcalculator.model.PrintStats;
import com.printcalculator.model.QuoteResult;
import com.printcalculator.repository.PrinterMachineRepository;
import com.printcalculator.repository.QuoteLineItemRepository;
import com.printcalculator.repository.QuoteSessionRepository;
import com.printcalculator.service.QuoteCalculator;
import com.printcalculator.service.SlicerService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api/quote-sessions")
@CrossOrigin(origins = "*") // Allow CORS for dev
public class QuoteSessionController {
private final QuoteSessionRepository sessionRepo;
private final QuoteLineItemRepository lineItemRepo;
private final SlicerService slicerService;
private final QuoteCalculator quoteCalculator;
private final PrinterMachineRepository machineRepo;
// Defaults
private static final String DEFAULT_FILAMENT = "pla_basic";
private static final String DEFAULT_PROCESS = "standard";
public QuoteSessionController(QuoteSessionRepository sessionRepo,
QuoteLineItemRepository lineItemRepo,
SlicerService slicerService,
QuoteCalculator quoteCalculator,
PrinterMachineRepository machineRepo) {
this.sessionRepo = sessionRepo;
this.lineItemRepo = lineItemRepo;
this.slicerService = slicerService;
this.quoteCalculator = quoteCalculator;
this.machineRepo = machineRepo;
}
// 1. Start a new session with a file
@PostMapping(value = "/line-items", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Transactional
public ResponseEntity<QuoteSession> createSessionAndAddItem(
@RequestParam("file") MultipartFile file
) throws IOException {
// Create new session
QuoteSession session = new QuoteSession();
session.setStatus("ACTIVE");
session.setPricingVersion("v1"); // Placeholder
session.setMaterialCode(DEFAULT_FILAMENT); // Default for session
session.setSupportsEnabled(false);
session.setCreatedAt(OffsetDateTime.now());
session.setExpiresAt(OffsetDateTime.now().plusDays(30));
// Set defaults
session.setSetupCostChf(BigDecimal.ZERO);
session = sessionRepo.save(session);
// Process file and add item
addItemToSession(session, file);
// Refresh session to return updated data (if we added list fetching to repo, otherwise manually fetch items if needed for response)
// For now, let's just return the session. The client might need to fetch items separately or we can return a DTO.
// User request: "ritorna sessione + line items + total"
// Since QuoteSession entity doesn't have a @OneToMany list of items (it has OneToMany usually but mapped by item),
// we might need a DTO or just rely on the fact that we might add the list to the entity if valid.
// Looking at QuoteSession.java, it does NOT have a list of items.
// So we should probably return a DTO or just return the Session and Client calls GET /quote-sessions/{id} immediately?
// User request: "ritorna quoteSessionId" (actually implies just ID, but likely wants full object).
// "ritorna sessione + line items + total (usa view o calcolo service)" refers to GET /quote-sessions/{id}
// Let's return the full session details including items in a DTO/Map/wrapper?
// Or just the session for now. The user said "ritorna quoteSessionId" for this specific endpoint.
// Let's return the Session entity for now.
return ResponseEntity.ok(session);
}
// 2. Add item to existing session
@PostMapping(value = "/{id}/line-items", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Transactional
public ResponseEntity<QuoteLineItem> addItemToExistingSession(
@PathVariable UUID id,
@RequestParam("file") MultipartFile file
) throws IOException {
QuoteSession session = sessionRepo.findById(id)
.orElseThrow(() -> new RuntimeException("Session not found"));
QuoteLineItem item = addItemToSession(session, file);
return ResponseEntity.ok(item);
}
// Helper to add item
private QuoteLineItem addItemToSession(QuoteSession session, MultipartFile file) throws IOException {
if (file.isEmpty()) throw new IOException("File is empty");
// 1. Save file temporarily
Path tempInput = Files.createTempFile("upload_", "_" + file.getOriginalFilename());
file.transferTo(tempInput.toFile());
try {
// 2. Mock Calc or Real Calc
// The user said: "per ora calcolo mock" (mock calculation) but we have SlicerService.
// "Nota: il calcolo può essere stub: set print_time_seconds/material_grams/unit_price_chf a valori placeholder."
// However, since we have the SlicerService, we CAN try to use it if we want, OR just use stub as requested to be fast?
// "avvia calcolo (per ora calcolo mock)" -> I will use a simple Stub to satisfy the requirement immediately.
// But I will also implement the structure to swap to Real Calc.
// STUB CALCULATION as requested
int printTime = 3600; // 1 hour
BigDecimal materialGrams = new BigDecimal("50.00");
BigDecimal unitPrice = new BigDecimal("15.00");
// 3. Create Line Item
QuoteLineItem item = new QuoteLineItem();
item.setQuoteSession(session);
item.setOriginalFilename(file.getOriginalFilename());
item.setQuantity(1);
item.setColorCode("#FFFFFF"); // Default
item.setStatus("CALCULATED");
item.setPrintTimeSeconds(printTime);
item.setMaterialGrams(materialGrams);
item.setUnitPriceChf(unitPrice);
item.setPricingBreakdown(Map.of("mock", true));
// Set simple bounding box
item.setBoundingBoxXMm(BigDecimal.valueOf(100));
item.setBoundingBoxYMm(BigDecimal.valueOf(100));
item.setBoundingBoxZMm(BigDecimal.valueOf(20));
item.setCreatedAt(OffsetDateTime.now());
item.setUpdatedAt(OffsetDateTime.now());
return lineItemRepo.save(item);
} finally {
Files.deleteIfExists(tempInput);
}
}
// 3. Update Line Item
@PatchMapping("/line-items/{lineItemId}")
@Transactional
public ResponseEntity<QuoteLineItem> updateLineItem(
@PathVariable UUID lineItemId,
@RequestBody Map<String, Object> updates
) {
QuoteLineItem item = lineItemRepo.findById(lineItemId)
.orElseThrow(() -> new RuntimeException("Item not found"));
if (updates.containsKey("quantity")) {
item.setQuantity((Integer) updates.get("quantity"));
}
if (updates.containsKey("color_code")) {
item.setColorCode((String) updates.get("color_code"));
}
// Recalculate price if needed?
// For now, unit price is fixed in mock. Total is calculated on GET.
item.setUpdatedAt(OffsetDateTime.now());
return ResponseEntity.ok(lineItemRepo.save(item));
}
// 4. Delete Line Item
@DeleteMapping("/{sessionId}/line-items/{lineItemId}")
@Transactional
public ResponseEntity<Void> deleteLineItem(
@PathVariable UUID sessionId,
@PathVariable UUID lineItemId
) {
// Verify item belongs to session?
QuoteLineItem item = lineItemRepo.findById(lineItemId)
.orElseThrow(() -> new RuntimeException("Item not found"));
if (!item.getQuoteSession().getId().equals(sessionId)) {
return ResponseEntity.badRequest().build();
}
lineItemRepo.delete(item);
return ResponseEntity.noContent().build();
}
// 5. Get Session (Session + Items + Total)
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getQuoteSession(@PathVariable UUID id) {
QuoteSession session = sessionRepo.findById(id)
.orElseThrow(() -> new RuntimeException("Session not found"));
List<QuoteLineItem> items = lineItemRepo.findByQuoteSessionId(id);
// Calculate Totals
BigDecimal itemsTotal = BigDecimal.ZERO;
for (QuoteLineItem item : items) {
BigDecimal lineTotal = item.getUnitPriceChf().multiply(BigDecimal.valueOf(item.getQuantity()));
itemsTotal = itemsTotal.add(lineTotal);
}
BigDecimal setupFee = session.getSetupCostChf() != null ? session.getSetupCostChf() : BigDecimal.ZERO;
BigDecimal grandTotal = itemsTotal.add(setupFee);
Map<String, Object> response = new HashMap<>();
response.put("session", session);
response.put("items", items);
response.put("itemsTotalChf", itemsTotal);
response.put("grandTotalChf", grandTotal);
return ResponseEntity.ok(response);
}
}

View File

@@ -116,4 +116,6 @@ public class CustomQuoteRequestAttachment {
this.createdAt = createdAt;
}
public void setCustomQuoteRequest(CustomQuoteRequest request) {
}
}

View File

@@ -1,29 +0,0 @@
package com.printcalculator.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import org.hibernate.annotations.Immutable;
import java.math.BigDecimal;
import java.util.UUID;
@Entity
@Immutable
@Table(name = "quote_session_totals")
public class QuoteSessionTotal {
@Column(name = "quote_session_id")
private UUID quoteSessionId;
@Column(name = "total_chf")
private BigDecimal totalChf;
public UUID getQuoteSessionId() {
return quoteSessionId;
}
public BigDecimal getTotalChf() {
return totalChf;
}
}

View File

@@ -3,7 +3,9 @@ package com.printcalculator.repository;
import com.printcalculator.entity.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
import java.util.UUID;
public interface CustomerRepository extends JpaRepository<Customer, UUID> {
Optional<Customer> findByEmail(String email);
}

View File

@@ -3,7 +3,9 @@ package com.printcalculator.repository;
import com.printcalculator.entity.QuoteLineItem;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
import java.util.UUID;
public interface QuoteLineItemRepository extends JpaRepository<QuoteLineItem, UUID> {
List<QuoteLineItem> findByQuoteSessionId(UUID quoteSessionId);
}