diff --git a/apps/workspace-api/Doc.txt b/apps/workspace-api/Doc.txt new file mode 100644 index 0000000..e69de29 diff --git a/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryController.java b/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryController.java new file mode 100644 index 0000000..8b005db --- /dev/null +++ b/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryController.java @@ -0,0 +1,37 @@ +package com.voyage.workspace.inventory; + +import com.voyage.workspace.products.SkuRepository; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/inventory") +public class InventoryController { + + private final InventoryMovementRepository movementRepo; + private final SkuRepository skuRepo; + + public InventoryController(InventoryMovementRepository movementRepo, SkuRepository skuRepo) { + this.movementRepo = movementRepo; + this.skuRepo = skuRepo; + } + + @PostMapping("/movements") + public ResponseEntity createMovement(@RequestBody InventoryMovementCreateRequest req) { + var skuOpt = skuRepo.findById(req.skuId()); + if (skuOpt.isEmpty()) return ResponseEntity.badRequest().body("Unknown skuId: " + req.skuId()); + + InventoryMovement m = new InventoryMovement(); + m.setSku(skuOpt.get()); + m.setDelta(req.delta()); + m.setReason(req.reason()); + m.setReference(req.reference()); + + return ResponseEntity.ok(movementRepo.save(m)); + } + + @GetMapping("/stock/{skuId}") + public int getCurrentStock(@PathVariable Long skuId) { + return movementRepo.currentStock(skuId); + } +} \ No newline at end of file diff --git a/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryMovement.java b/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryMovement.java new file mode 100644 index 0000000..f2bff6f --- /dev/null +++ b/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryMovement.java @@ -0,0 +1,44 @@ +package com.voyage.workspace.inventory; + +import com.voyage.workspace.products.Sku; +import jakarta.persistence.*; + +import java.time.Instant; + +@Entity +@Table(name = "inventory_movements") +public class InventoryMovement { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(optional = false, fetch = FetchType.LAZY) + @JoinColumn(name = "sku_id", nullable = false) + private Sku sku; + + @Column(nullable = false) + private int delta; + + @Column(nullable = false) + private String reason; // PRODUCTION, SALE, RETURN, ADJUSTMENT + + private String reference; + + @Column(nullable=false, updatable=false) + private Instant createdAt = Instant.now(); + + public InventoryMovement() {} + + public Long getId() { return id; } + public Sku getSku() { return sku; } + public int getDelta() { return delta; } + public String getReason() { return reason; } + public String getReference() { return reference; } + public Instant getCreatedAt() { return createdAt; } + + public void setSku(Sku sku) { this.sku = sku; } + public void setDelta(int delta) { this.delta = delta; } + public void setReason(String reason) { this.reason = reason; } + public void setReference(String reference) { this.reference = reference; } +} diff --git a/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryMovementCreateRequest.java b/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryMovementCreateRequest.java new file mode 100644 index 0000000..74869d7 --- /dev/null +++ b/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryMovementCreateRequest.java @@ -0,0 +1,8 @@ +package com.voyage.workspace.inventory; + +public record InventoryMovementCreateRequest( + Long skuId, + int delta, + String reason, + String reference +) {} \ No newline at end of file diff --git a/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryMovementRepository.java b/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryMovementRepository.java new file mode 100644 index 0000000..05bd07e --- /dev/null +++ b/apps/workspace-api/src/main/java/com/voyage/workspace/inventory/InventoryMovementRepository.java @@ -0,0 +1,11 @@ +package com.voyage.workspace.inventory; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface InventoryMovementRepository extends JpaRepository { + + @Query("select coalesce(sum(m.delta), 0) from InventoryMovement m where m.sku.id = :skuId") + int currentStock(@Param("skuId") Long skuId); +} \ No newline at end of file diff --git a/apps/workspace-api/src/main/java/com/voyage/workspace/products/Sku.java b/apps/workspace-api/src/main/java/com/voyage/workspace/products/Sku.java new file mode 100644 index 0000000..f3bdf24 --- /dev/null +++ b/apps/workspace-api/src/main/java/com/voyage/workspace/products/Sku.java @@ -0,0 +1,46 @@ +package com.voyage.workspace.products; + +import jakarta.persistence.*; +import java.math.BigDecimal; + +@Entity +@Table(name = "skus") +public class Sku { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(optional = false, fetch = FetchType.LAZY) + @JoinColumn(name = "product_id", nullable = false) + private Product product; + + @Column(nullable = false, unique = true) + private String skuCode; + + private String size; + private String color; + + @Column(nullable = false) + private BigDecimal price; + + @Column(nullable = false) + private boolean active = true; + + public Sku() {} + + public Long getId() { return id; } + public Product getProduct() { return product; } + public String getSkuCode() { return skuCode; } + public String getSize() { return size; } + public String getColor() { return color; } + public BigDecimal getPrice() { return price; } + public boolean isActive() { return active; } + + public void setProduct(Product product) { this.product = product; } + public void setSkuCode(String skuCode) { this.skuCode = skuCode; } + public void setSize(String size) { this.size = size; } + public void setColor(String color) { this.color = color; } + public void setPrice(BigDecimal price) { this.price = price; } + public void setActive(boolean active) { this.active = active; } +} \ No newline at end of file diff --git a/apps/workspace-api/src/main/java/com/voyage/workspace/products/SkuController.java b/apps/workspace-api/src/main/java/com/voyage/workspace/products/SkuController.java new file mode 100644 index 0000000..a0d2747 --- /dev/null +++ b/apps/workspace-api/src/main/java/com/voyage/workspace/products/SkuController.java @@ -0,0 +1,42 @@ +package com.voyage.workspace.products; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/skus") +public class SkuController { + + private final SkuRepository skuRepo; + private final ProductRepository productRepo; + + public SkuController(SkuRepository skuRepo, ProductRepository productRepo) { + this.skuRepo = skuRepo; + this.productRepo = productRepo; + } + + @GetMapping + public List list(@RequestParam(required = false) Long productId) { + if (productId != null) return skuRepo.findByProductId(productId); + return skuRepo.findAll(); + } + + @PostMapping + public ResponseEntity create(@RequestBody SkuCreateRequest req) { + var productOpt = productRepo.findById(req.productId()); + if (productOpt.isEmpty()) { + return ResponseEntity.badRequest().body("Unknown productId: " + req.productId()); + } + + Sku sku = new Sku(); + sku.setProduct(productOpt.get()); + sku.setSkuCode(req.skuCode()); + sku.setSize(req.size()); + sku.setColor(req.color()); + sku.setPrice(req.price()); + + return ResponseEntity.ok(skuRepo.save(sku)); + } +} \ No newline at end of file diff --git a/apps/workspace-api/src/main/java/com/voyage/workspace/products/SkuCreateRequest.java b/apps/workspace-api/src/main/java/com/voyage/workspace/products/SkuCreateRequest.java new file mode 100644 index 0000000..05f7dfd --- /dev/null +++ b/apps/workspace-api/src/main/java/com/voyage/workspace/products/SkuCreateRequest.java @@ -0,0 +1,11 @@ +package com.voyage.workspace.products; + +import java.math.BigDecimal; + +public record SkuCreateRequest( + Long productId, + String skuCode, + String size, + String color, + BigDecimal price +) {} \ No newline at end of file diff --git a/apps/workspace-api/src/main/java/com/voyage/workspace/products/SkuRepository.java b/apps/workspace-api/src/main/java/com/voyage/workspace/products/SkuRepository.java new file mode 100644 index 0000000..807c326 --- /dev/null +++ b/apps/workspace-api/src/main/java/com/voyage/workspace/products/SkuRepository.java @@ -0,0 +1,11 @@ +package com.voyage.workspace.products; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface SkuRepository extends JpaRepository { + List findByProductId(Long productId); + Optional findBySkuCode(String skuCode); +} \ No newline at end of file