Vendor Order Stacking made functional.
This commit is contained in:
@@ -183,36 +183,74 @@ public class ProductController {
|
|||||||
public ResponseEntity<Product> publishProduct(@PathVariable Long id, @RequestBody Product productDetails) {
|
public ResponseEntity<Product> publishProduct(@PathVariable Long id, @RequestBody Product productDetails) {
|
||||||
return productRepository.findById(id)
|
return productRepository.findById(id)
|
||||||
.map(product -> {
|
.map(product -> {
|
||||||
// Generate new PRD ID
|
String finalProductId = productDetails.getProductId();
|
||||||
String newProductId = "PRD-" + String.format("%04d", new java.util.Random().nextInt(10000));
|
|
||||||
|
|
||||||
// Update all details first
|
// If it's a draft ID or blank, try to find an existing live product by name first
|
||||||
product.setName(productDetails.getName());
|
if (finalProductId == null || finalProductId.startsWith("DRAFT-") || finalProductId.isEmpty()) {
|
||||||
product.setProductId(newProductId);
|
List<Product> nameMatches = productRepository.findByNameRobust(productDetails.getName());
|
||||||
product.setCategory(productDetails.getCategory());
|
if (!nameMatches.isEmpty()) {
|
||||||
product.setDescription(productDetails.getDescription());
|
finalProductId = nameMatches.get(0).getProductId();
|
||||||
product.setBasePrice(productDetails.getBasePrice());
|
}
|
||||||
product.setPrice(productDetails.getPrice());
|
|
||||||
product.setOfferPrice(productDetails.getOfferPrice());
|
|
||||||
product.setCounter(productDetails.getCounter());
|
|
||||||
product.setTag(productDetails.getTag());
|
|
||||||
product.setBarcode(productDetails.getBarcode());
|
|
||||||
product.setImageData(productDetails.getImageData());
|
|
||||||
product.setStock(productDetails.getStock());
|
|
||||||
|
|
||||||
product.getSessions().clear();
|
|
||||||
if (productDetails.getSessions() != null) {
|
|
||||||
product.getSessions().addAll(productDetails.getSessions());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalize
|
// If still a draft ID or blank, generate a new PRD ID
|
||||||
product.setDraft(false);
|
if (finalProductId == null || finalProductId.startsWith("DRAFT-") || finalProductId.isEmpty()) {
|
||||||
product.setActive(true);
|
finalProductId = "PRD-" + String.format("%04d", new java.util.Random().nextInt(10000));
|
||||||
|
}
|
||||||
Product updated = productRepository.save(product);
|
|
||||||
updateStallAssociations(updated, productDetails.getStalls());
|
// Check if a live product with this ID already exists (to merge)
|
||||||
|
final String targetId = finalProductId;
|
||||||
return ResponseEntity.ok(productRepository.findById(updated.getId()).orElse(updated));
|
java.util.Optional<Product> existingLive = productRepository.findByProductIdRobust(targetId);
|
||||||
|
|
||||||
|
if (existingLive.isPresent()) {
|
||||||
|
Product live = existingLive.get();
|
||||||
|
// MERGE: Update stock and other details
|
||||||
|
live.setStock((live.getStock() != null ? live.getStock() : 0) + (productDetails.getStock() != null ? productDetails.getStock() : 0));
|
||||||
|
live.setName(productDetails.getName());
|
||||||
|
live.setCategory(productDetails.getCategory());
|
||||||
|
live.setDescription(productDetails.getDescription());
|
||||||
|
live.setBasePrice(productDetails.getBasePrice());
|
||||||
|
live.setPrice(productDetails.getPrice());
|
||||||
|
live.setOfferPrice(productDetails.getOfferPrice());
|
||||||
|
live.setCounter(productDetails.getCounter());
|
||||||
|
live.setTag(productDetails.getTag());
|
||||||
|
live.setImageData(productDetails.getImageData());
|
||||||
|
live.setDraft(false);
|
||||||
|
live.setActive(true);
|
||||||
|
|
||||||
|
Product savedLive = productRepository.save(live);
|
||||||
|
productRepository.delete(product); // Delete the draft
|
||||||
|
updateStallAssociations(savedLive, productDetails.getStalls());
|
||||||
|
return ResponseEntity.ok(productRepository.findById(savedLive.getId()).orElse(savedLive));
|
||||||
|
} else {
|
||||||
|
// CREATE/UPDATE as new live product
|
||||||
|
product.setName(productDetails.getName());
|
||||||
|
product.setProductId(finalProductId);
|
||||||
|
product.setCategory(productDetails.getCategory());
|
||||||
|
product.setDescription(productDetails.getDescription());
|
||||||
|
product.setBasePrice(productDetails.getBasePrice());
|
||||||
|
product.setPrice(productDetails.getPrice());
|
||||||
|
product.setOfferPrice(productDetails.getOfferPrice());
|
||||||
|
product.setCounter(productDetails.getCounter());
|
||||||
|
product.setTag(productDetails.getTag());
|
||||||
|
product.setBarcode(productDetails.getBarcode());
|
||||||
|
product.setImageData(productDetails.getImageData());
|
||||||
|
product.setStock(productDetails.getStock());
|
||||||
|
|
||||||
|
product.getSessions().clear();
|
||||||
|
if (productDetails.getSessions() != null) {
|
||||||
|
product.getSessions().addAll(productDetails.getSessions());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize
|
||||||
|
product.setDraft(false);
|
||||||
|
product.setActive(true);
|
||||||
|
|
||||||
|
Product updated = productRepository.save(product);
|
||||||
|
updateStallAssociations(updated, productDetails.getStalls());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(productRepository.findById(updated.getId()).orElse(updated));
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.orElse(ResponseEntity.notFound().build());
|
.orElse(ResponseEntity.notFound().build());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ public interface ProductRepository extends JpaRepository<Product, Long> {
|
|||||||
List<String> findDistinctCategories();
|
List<String> findDistinctCategories();
|
||||||
|
|
||||||
List<Product> findByIsDraftTrue();
|
List<Product> findByIsDraftTrue();
|
||||||
|
|
||||||
|
@Query("SELECT p FROM Product p WHERE LOWER(TRIM(p.name)) = LOWER(TRIM(:name)) AND (p.isDraft = false OR p.isDraft IS NULL)")
|
||||||
|
List<Product> findByNameRobust(@org.springframework.data.repository.query.Param("name") String name);
|
||||||
|
|
||||||
|
@Query("SELECT p FROM Product p WHERE p.productId = :productId AND (p.isDraft = false OR p.isDraft IS NULL)")
|
||||||
|
java.util.Optional<Product> findByProductIdRobust(@org.springframework.data.repository.query.Param("productId") String productId);
|
||||||
|
|
||||||
boolean existsByNameAndCategory(String name, String category);
|
boolean existsByNameAndCategory(String name, String category);
|
||||||
@org.springframework.data.jpa.repository.Modifying
|
@org.springframework.data.jpa.repository.Modifying
|
||||||
|
|||||||
@@ -154,13 +154,31 @@ public class PurchaseService {
|
|||||||
for (PurchaseOrderItem item : order.getItems()) {
|
for (PurchaseOrderItem item : order.getItems()) {
|
||||||
Product draftProduct = new Product();
|
Product draftProduct = new Product();
|
||||||
draftProduct.setName(item.getProductName());
|
draftProduct.setName(item.getProductName());
|
||||||
|
|
||||||
|
// Try to find an existing live product with the same name to copy metadata
|
||||||
|
String itemName = item.getProductName();
|
||||||
|
List<Product> existingProducts = productRepository.findByNameRobust(itemName);
|
||||||
|
if (!existingProducts.isEmpty()) {
|
||||||
|
Product existing = existingProducts.get(0);
|
||||||
|
draftProduct.setCategory(existing.getCategory());
|
||||||
|
draftProduct.setImageData(existing.getImageData());
|
||||||
|
draftProduct.setDescription(existing.getDescription());
|
||||||
|
draftProduct.setTag(existing.getTag());
|
||||||
|
draftProduct.setCounter(existing.getCounter());
|
||||||
|
draftProduct.setPrice(existing.getPrice());
|
||||||
|
}
|
||||||
|
|
||||||
draftProduct.setStock(item.getQuantity() != null ? item.getQuantity().intValue() : 0);
|
draftProduct.setStock(item.getQuantity() != null ? item.getQuantity().intValue() : 0);
|
||||||
draftProduct.setBasePrice(item.getRate());
|
draftProduct.setBasePrice(item.getRate());
|
||||||
draftProduct.setDraft(true);
|
draftProduct.setDraft(true);
|
||||||
draftProduct.setActive(false); // Not live yet
|
draftProduct.setActive(false); // Not live yet
|
||||||
|
|
||||||
// Set a unique product ID if possible
|
// Set a unique product ID if possible
|
||||||
draftProduct.setProductId("DRAFT-" + String.format("%04d", new java.util.Random().nextInt(10000)) + "-" + item.getId());
|
if (!existingProducts.isEmpty()) {
|
||||||
|
draftProduct.setProductId(existingProducts.get(0).getProductId());
|
||||||
|
} else {
|
||||||
|
draftProduct.setProductId("DRAFT-" + String.format("%04d", new java.util.Random().nextInt(10000)) + "-" + item.getId());
|
||||||
|
}
|
||||||
|
|
||||||
productRepository.save(draftProduct);
|
productRepository.save(draftProduct);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,13 +109,29 @@ const Purchases: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleItemChange = (index: number, field: keyof PurchaseOrderItem, value: string | number) => {
|
const handleItemChange = (index: number, field: keyof PurchaseOrderItem, value: string | number) => {
|
||||||
const updatedItems = [...newOrder.items];
|
setNewOrder(prev => {
|
||||||
const item = { ...updatedItems[index], [field]: value };
|
const updatedItems = [...prev.items];
|
||||||
if (field === 'quantity' || field === 'rate') {
|
const item = { ...updatedItems[index], [field]: value };
|
||||||
item.total = Number(item.quantity) * Number(item.rate);
|
if (field === 'quantity' || field === 'rate' || field === 'productName') {
|
||||||
}
|
item.total = Number(item.quantity) * Number(item.rate);
|
||||||
updatedItems[index] = item;
|
}
|
||||||
setNewOrder({ ...newOrder, items: updatedItems });
|
updatedItems[index] = item;
|
||||||
|
return { ...prev, items: updatedItems };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleProductSelect = (index: number, product: any) => {
|
||||||
|
setNewOrder(prev => {
|
||||||
|
const updatedItems = [...prev.items];
|
||||||
|
updatedItems[index] = {
|
||||||
|
...updatedItems[index],
|
||||||
|
productName: product.name,
|
||||||
|
rate: product.basePrice || 0,
|
||||||
|
total: Number(updatedItems[index].quantity) * Number(product.basePrice || 0)
|
||||||
|
};
|
||||||
|
return { ...prev, items: updatedItems };
|
||||||
|
});
|
||||||
|
setActiveSearchIdx(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateTotal = () => {
|
const calculateTotal = () => {
|
||||||
@@ -442,10 +458,9 @@ const Purchases: React.FC = () => {
|
|||||||
<button
|
<button
|
||||||
key={p.id}
|
key={p.id}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onMouseDown={(e) => {
|
||||||
handleItemChange(idx, 'productName', p.name);
|
e.preventDefault(); // Prevent focus loss
|
||||||
handleItemChange(idx, 'rate', p.basePrice || 0);
|
handleProductSelect(idx, p);
|
||||||
setActiveSearchIdx(null);
|
|
||||||
}}
|
}}
|
||||||
className="w-full text-left px-4 py-3 hover:bg-indigo-50 flex flex-col transition-colors border-b border-[#f1f5f9] last:border-0"
|
className="w-full text-left px-4 py-3 hover:bg-indigo-50 flex flex-col transition-colors border-b border-[#f1f5f9] last:border-0"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user