-
Notifications
You must be signed in to change notification settings - Fork 114
Expand file tree
/
Copy pathSalesService.java
More file actions
157 lines (134 loc) · 7.62 KB
/
SalesService.java
File metadata and controls
157 lines (134 loc) · 7.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package com.borsibaar.service;
import com.borsibaar.dto.*;
import com.borsibaar.entity.*;
import com.borsibaar.repository.BarStationRepository;
import com.borsibaar.repository.InventoryRepository;
import com.borsibaar.repository.InventoryTransactionRepository;
import com.borsibaar.repository.ProductRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class SalesService {
private final InventoryRepository inventoryRepository;
private final InventoryTransactionRepository inventoryTransactionRepository;
private final ProductRepository productRepository;
private final BarStationRepository barStationRepository;
@Transactional
public SaleResponseDto processSale(SaleRequestDto request, UUID userId, Long organizationId) {
// Generate unique sale reference ID
String saleId = "SALE-" + System.currentTimeMillis();
List<SaleItemResponseDto> saleItems = new ArrayList<>();
BigDecimal totalAmount = BigDecimal.ZERO;
BarStation station = barStationRepository
.findByOrganizationIdAndId(organizationId, request.barStationId())
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.FORBIDDEN,
"Bar station does not belong to your organization"));
// Process each item in the sale
for (SaleItemRequestDto item : request.items()) {
SaleItemResponseDto saleItem = processSaleItem(item, userId, organizationId, saleId,
station.getId());
saleItems.add(saleItem);
totalAmount = totalAmount.add(saleItem.totalPrice());
}
return new SaleResponseDto(
saleId,
saleItems,
totalAmount,
request.notes(),
OffsetDateTime.now());
}
private SaleItemResponseDto processSaleItem(SaleItemRequestDto item, UUID userId, Long organizationId,
String saleId, Long barStationId) {
// Verify product exists and belongs to organization
Product product = productRepository.findById(item.productId())
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND, "Product not found: " + item.productId()));
if (!product.getOrganizationId().equals(organizationId)) {
throw new ResponseStatusException(
HttpStatus.FORBIDDEN, "Product does not belong to your organization");
}
if (!product.isActive()) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "Product is not active: " + product.getName());
}
// Get inventory for this product
/*
* Inventory inventory = inventoryRepository
* .findByOrganizationIdAndProductId(organizationId, item.productId())
* .orElseThrow(() -> new ResponseStatusException(
* HttpStatus.NOT_FOUND, "No inventory found for product: " +
* product.getName()));
*/
Inventory inventory = Optional.ofNullable(product.getInventory())
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND,
"No inventory found for product: " + product.getName()));
// Check stock availability
BigDecimal oldQuantity = inventory.getQuantity();
BigDecimal newQuantity = oldQuantity.subtract(item.quantity());
if (newQuantity.compareTo(BigDecimal.ZERO) < 0) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Insufficient stock for " + product.getName() +
". Available: " + oldQuantity + ", Requested: "
+ item.quantity());
}
// Calculate pricing
BigDecimal priceBeforeSale = Optional.ofNullable(inventory.getAdjustedPrice())
.orElse(product.getBasePrice());
BigDecimal totalPrice = priceBeforeSale.multiply(item.quantity());
BigDecimal priceAfterSale = priceBeforeSale;
Category category = product.getCategory();
if (category != null && category.isDynamicPricing()) {
priceAfterSale = priceBeforeSale.add(product.getOrganization().getPriceIncreaseStep());
if (product.getMaxPrice() != null && priceAfterSale.compareTo(product.getMaxPrice()) > 0) {
priceAfterSale = product.getMaxPrice();
}
}
// Update inventory
inventory.setQuantity(newQuantity);
inventory.setUpdatedAt(OffsetDateTime.now());
inventory.setAdjustedPrice(priceAfterSale);
inventory = inventoryRepository.save(inventory);
// Create sale transaction
createSaleTransaction(inventory, item.quantity(),
oldQuantity, newQuantity, priceBeforeSale, priceAfterSale,
saleId, userId, barStationId);
return new SaleItemResponseDto(
item.productId(),
product.getName(),
item.quantity(),
priceBeforeSale,
totalPrice);
}
private void createSaleTransaction(Inventory inventory, BigDecimal quantity,
BigDecimal quantityBefore, BigDecimal quantityAfter,
BigDecimal priceBefore, BigDecimal priceAfter,
String saleId, UUID userId, Long barStationId) {
InventoryTransaction transaction = new InventoryTransaction();
transaction.setInventory(inventory);
transaction.setTransactionType("SALE");
transaction.setQuantityChange(quantity.negate()); // Negative for sales
transaction.setQuantityBefore(quantityBefore);
transaction.setQuantityAfter(quantityAfter);
transaction.setPriceBefore(priceBefore);
transaction.setPriceAfter(priceAfter);
transaction.setReferenceId(saleId);
transaction.setNotes("POS Sale");
transaction.setCreatedBy(userId);
transaction.setBarStationId(barStationId);
transaction.setCreatedAt(OffsetDateTime.now());
inventoryTransactionRepository.save(transaction);
}
}