created models

This commit is contained in:
2026-06-12 15:58:45 +03:00
parent b423a09f90
commit 48422d3e62
11 changed files with 808 additions and 1 deletions
+42 -1
View File
@@ -13,4 +13,45 @@ out/
.DS_Store
logs/
*.log
*.log
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
### react ###
.DS_*
logs
**/*.backup.*
**/*.back.*
node_modules
bower_components
*.sublime*
psd
thumb
sketch
@@ -1,10 +1,17 @@
package com.andwati.orders;
import com.andwati.orders.config.FareProperties;
import com.andwati.orders.config.InventoryProperties;
import io.github.cdimascio.dotenv.Dotenv;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties({
FareProperties.class,
InventoryProperties.class
})
public class OrdersBackendApplication {
public static void main(String[] args) {
@@ -0,0 +1,129 @@
package com.andwati.orders.config;
import com.andwati.orders.model.InventoryItem;
import com.andwati.orders.model.OrderItem;
import com.andwati.orders.model.OrderStatus;
import com.andwati.orders.model.Product;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
@Component
public class DataSeeder implements CommandLineRunner {
private final ProductRepository productRepository;
private final InventoryItemRepository inventoryItemRepository;
private final OrderRepository orderRepository;
private final boolean seedData;
public DataSeeder(
ProductRepository productRepository,
InventoryItemRepository inventoryItemRepository,
OrderRepository orderRepository,
@Value("${app.seed-data:true}") boolean seedData
) {
this.productRepository = productRepository;
this.inventoryItemRepository = inventoryItemRepository;
this.orderRepository = orderRepository;
this.seedData = seedData;
}
@Override
@Transactional
public void run(String... args) {
if (!seedData || productRepository.count() > 0) {
return;
}
Product mouse = product("Wireless Mouse", "MOUSE-001", "1500.00");
Product keyboard = product("Keyboard", "KEYBOARD-001", "2500.00");
Product cable = product("USB-C Cable", "USB-C-001", "800.00");
Product stand = product("Laptop Stand", "STAND-001", "3500.00");
Product webcam = product("Webcam", "WEBCAM-001", "4500.00");
productRepository.saveAll(List.of(mouse, keyboard, cable, stand, webcam));
inventoryItemRepository.saveAll(List.of(
inventory(mouse, 12, 5),
inventory(keyboard, 3, 5),
inventory(cable, 0, 5),
inventory(stand, 7, 5),
inventory(webcam, 2, 5)
));
Order placedOrder = new Order();
placedOrder.setCustomerName("Demo Customer");
placedOrder.setStatus(OrderStatus.PLACED);
placedOrder.setCurrency("KES");
placedOrder.setCreatedAt(Instant.now().minusSeconds(3600));
OrderItem itemOne = new OrderItem();
itemOne.setOrder(placedOrder);
itemOne.setProduct(mouse);
itemOne.setProductName(mouse.getName());
itemOne.setQuantity(1);
itemOne.setUnitPrice(mouse.getPrice());
itemOne.setLineTotal(mouse.getPrice());
OrderItem itemTwo = new OrderItem();
itemTwo.setOrder(placedOrder);
itemTwo.setProduct(keyboard);
itemTwo.setProductName(keyboard.getName());
itemTwo.setQuantity(1);
itemTwo.setUnitPrice(keyboard.getPrice());
itemTwo.setLineTotal(keyboard.getPrice());
placedOrder.addItem(itemOne);
placedOrder.addItem(itemTwo);
placedOrder.recalculateTotal();
Order cancelledOrder = new Order();
cancelledOrder.setCustomerName("Cancelled Customer");
cancelledOrder.setStatus(OrderStatus.CANCELLED);
cancelledOrder.setCurrency("KES");
cancelledOrder.setCreatedAt(Instant.now().minusSeconds(86_400));
cancelledOrder.setCancelledAt(Instant.now().minusSeconds(80_000));
OrderItem cancelledItem = new OrderItem();
cancelledItem.setOrder(cancelledOrder);
cancelledItem.setProduct(webcam);
cancelledItem.setProductName(webcam.getName());
cancelledItem.setQuantity(1);
cancelledItem.setUnitPrice(webcam.getPrice());
cancelledItem.setLineTotal(webcam.getPrice());
cancelledOrder.addItem(cancelledItem);
cancelledOrder.recalculateTotal();
orderRepository.saveAll(List.of(placedOrder, cancelledOrder));
}
private Product product(String name, String sku, String price) {
Product product = new Product();
product.setName(name);
product.setSku(sku);
product.setPrice(new BigDecimal(price));
product.setCurrency("KES");
product.setActive(true);
product.setCreatedAt(Instant.now());
product.setUpdatedAt(Instant.now());
return product;
}
private InventoryItem inventory(Product product, int availableQuantity, int reorderLevel) {
InventoryItem inventoryItem = new InventoryItem();
inventoryItem.setProduct(product);
inventoryItem.setAvailableQuantity(availableQuantity);
inventoryItem.setReservedQuantity(0);
inventoryItem.setReorderLevel(reorderLevel);
inventoryItem.setUpdatedAt(Instant.now());
return inventoryItem;
}
}
@@ -0,0 +1,34 @@
package com.andwati.orders.config;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
@Validated
@ConfigurationProperties(prefix = "fare")
public record FareProperties(
@NotNull
@DecimalMin(value = "0.00")
BigDecimal baseFare,
@NotNull
@DecimalMin(value = "0.00", inclusive = false)
BigDecimal perKmRate,
@NotNull
@DecimalMin(value = "0.00")
BigDecimal minimumFare,
@NotNull
@DecimalMin(value = "1.00")
BigDecimal defaultSurgeMultiplier,
@NotBlank
String currency
) {
}
@@ -0,0 +1,15 @@
package com.andwati.orders.config;
import jakarta.validation.constraints.Min;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@Validated
@ConfigurationProperties(prefix = "inventory")
public record InventoryProperties(
@Min(0)
int lowStockThreshold
) {
}
@@ -0,0 +1,172 @@
package com.andwati.orders.model;
import jakarta.persistence.*;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(name = "inventory_items", uniqueConstraints = {
@UniqueConstraint(name = "uk_inventory_product", columnNames = "product_id")
})
public class InventoryItem {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "product_id", nullable = false)
private Product product;
@Column(nullable = false)
private int availableQuantity;
@Column(nullable = false)
private int reservedQuantity;
@Column(nullable = false)
private int reorderLevel;
@Column(nullable = false)
private Instant updatedAt;
@PrePersist
public void prePersist() {
if (updatedAt == null) {
updatedAt = Instant.now();
}
}
@PreUpdate
public void preUpdate() {
updatedAt = Instant.now();
}
public boolean hasEnoughStock(int requestedQuantity) {
return availableQuantity >= requestedQuantity;
}
public void deduct(int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException("Quantity to deduct must be greater than zero");
}
if (availableQuantity < quantity) {
throw new IllegalStateException("Insufficient stock");
}
availableQuantity -= quantity;
updatedAt = Instant.now();
}
public void restore(int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException("Quantity to deduct must be greater than zero");
}
availableQuantity += quantity;
updatedAt = Instant.now();
}
public InventoryStatus getStatus() {
if (availableQuantity == 0) {
return InventoryStatus.OUT_OF_STOCK;
}
if (availableQuantity <= reorderLevel) {
return InventoryStatus.LOW_STOCK;
}
return InventoryStatus.IN_STOCK;
}
public UUID getId() {
return id;
}
public InventoryItem setId(UUID id) {
this.id = id;
return this;
}
public Product getProduct() {
return product;
}
public InventoryItem setProduct(Product product) {
this.product = product;
return this;
}
public int getAvailableQuantity() {
return availableQuantity;
}
public InventoryItem setAvailableQuantity(int availableQuantity) {
this.availableQuantity = availableQuantity;
return this;
}
public int getReservedQuantity() {
return reservedQuantity;
}
public InventoryItem setReservedQuantity(int reservedQuantity) {
this.reservedQuantity = reservedQuantity;
return this;
}
public int getReorderLevel() {
return reorderLevel;
}
public InventoryItem setReorderLevel(int reorderLevel) {
this.reorderLevel = reorderLevel;
return this;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public InventoryItem setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
return this;
}
}
@@ -0,0 +1,7 @@
package com.andwati.orders.model;
public enum InventoryStatus {
IN_STOCK,
LOW_STOCK,
OUT_OF_STOCK,
}
@@ -0,0 +1,152 @@
package com.andwati.orders.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(nullable = false, length = 120)
private String customerName;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 30)
private OrderStatus status = OrderStatus.PLACED;
@Column(nullable = false, precision = 12, scale = 2)
private BigDecimal totalAmount = BigDecimal.ZERO;
@Column(nullable = false, length = 3)
private String currency = "KES";
@Column(nullable = false)
private Instant createdAt;
private Instant cancelledAt;
@OneToMany(
mappedBy = "order",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<OrderItem> items = new ArrayList<>();
@PrePersist
public void prePersist() {
if (createdAt == null) {
createdAt = Instant.now();
}
if (totalAmount == null) {
totalAmount = BigDecimal.ZERO;
}
if (status == null) {
status = OrderStatus.PLACED;
}
}
public void addItem(OrderItem item) {
items.add(item);
item.setOrder(this);
}
public void recalculateTotal() {
this.totalAmount = items.stream()
.map(OrderItem::getLineTotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
public boolean isCancellable() {
return status == OrderStatus.PLACED;
}
public void cancel() {
this.status = OrderStatus.CANCELLED;
this.cancelledAt = Instant.now();
}
public UUID getId() {
return id;
}
public Order setId(UUID id) {
this.id = id;
return this;
}
public String getCustomerName() {
return customerName;
}
public Order setCustomerName(String customerName) {
this.customerName = customerName;
return this;
}
public OrderStatus getStatus() {
return status;
}
public Order setStatus(OrderStatus status) {
this.status = status;
return this;
}
public BigDecimal getTotalAmount() {
return totalAmount;
}
public Order setTotalAmount(BigDecimal totalAmount) {
this.totalAmount = totalAmount;
return this;
}
public String getCurrency() {
return currency;
}
public Order setCurrency(String currency) {
this.currency = currency;
return this;
}
public Instant getCreatedAt() {
return createdAt;
}
public Order setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
return this;
}
public Instant getCancelledAt() {
return cancelledAt;
}
public Order setCancelledAt(Instant cancelledAt) {
this.cancelledAt = cancelledAt;
return this;
}
public List<OrderItem> getItems() {
return items;
}
public Order setItems(List<OrderItem> items) {
this.items = items;
return this;
}
}
@@ -0,0 +1,109 @@
package com.andwati.orders.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.util.UUID;
@Entity
@Table(name = "order_items")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "order_id", nullable = false)
private Order order;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "product_id", nullable = false)
private Product product;
@Column(nullable = false, length = 120)
private String productName;
@Column(nullable = false)
private int quantity;
@Column(nullable = false, precision = 12, scale = 2)
private BigDecimal unitPrice;
@Column(nullable = false, precision = 12, scale = 2)
private BigDecimal lineTotal;
public static OrderItem from(Product product, int quantity) {
OrderItem item = new OrderItem();
item.setProduct(product);
item.setProductName(product.getName());
item.setQuantity(quantity);
item.setUnitPrice(product.getPrice());
item.setLineTotal(product.getPrice().multiply(BigDecimal.valueOf(quantity)));
return item;
}
public UUID getId() {
return id;
}
public OrderItem setId(UUID id) {
this.id = id;
return this;
}
public Order getOrder() {
return order;
}
public OrderItem setOrder(Order order) {
this.order = order;
return this;
}
public Product getProduct() {
return product;
}
public OrderItem setProduct(Product product) {
this.product = product;
return this;
}
public String getProductName() {
return productName;
}
public OrderItem setProductName(String productName) {
this.productName = productName;
return this;
}
public int getQuantity() {
return quantity;
}
public OrderItem setQuantity(int quantity) {
this.quantity = quantity;
return this;
}
public BigDecimal getUnitPrice() {
return unitPrice;
}
public OrderItem setUnitPrice(BigDecimal unitPrice) {
this.unitPrice = unitPrice;
return this;
}
public BigDecimal getLineTotal() {
return lineTotal;
}
public OrderItem setLineTotal(BigDecimal lineTotal) {
this.lineTotal = lineTotal;
return this;
}
}
@@ -0,0 +1,7 @@
package com.andwati.orders.model;
public enum OrderStatus {
PLACED,
CANCELLED
}
@@ -0,0 +1,134 @@
package com.andwati.orders.model;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.UUID;
@Entity
@Table(
name = "products",
uniqueConstraints = {
@UniqueConstraint(name = "uk_products_sku", columnNames = "sku")
}
)
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
@Column(nullable = false, length = 120)
private String name;
@Column(nullable = false, length = 80)
private String sku;
@Column(nullable = false, precision = 12, scale = 2)
private BigDecimal price;
@Column(nullable = false, length = 3)
private String currency = "KES";
@Column(nullable = false)
private boolean active = true;
@Column(nullable = false)
private Instant createdAt;
@Column(nullable = false)
private Instant updatedAt;
@PrePersist
public void prePersist() {
Instant now = Instant.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
}
@PreUpdate
public void preUpdate() {
updatedAt = Instant.now();
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public Product setName(String name) {
this.name = name;
return this;
}
public String getSku() {
return sku;
}
public Product setSku(String sku) {
this.sku = sku;
return this;
}
public BigDecimal getPrice() {
return price;
}
public Product setPrice(BigDecimal price) {
this.price = price;
return this;
}
public String getCurrency() {
return currency;
}
public Product setCurrency(String currency) {
this.currency = currency;
return this;
}
public boolean isActive() {
return active;
}
public Product setActive(boolean active) {
this.active = active;
return this;
}
public Instant getCreatedAt() {
return createdAt;
}
public Product setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
return this;
}
public Instant getUpdatedAt() {
return updatedAt;
}
public Product setUpdatedAt(Instant updatedAt) {
this.updatedAt = updatedAt;
return this;
}
}