Tuned ui to support ecommerce product catalogue and purchases.
This commit is contained in:
@@ -55,6 +55,7 @@ public class ProductCategoryController {
|
||||
existing.setDefaultMakingCharge(category.getDefaultMakingCharge());
|
||||
existing.setMakingChargeType(category.getMakingChargeType());
|
||||
existing.setBaseUnit(category.getBaseUnit());
|
||||
existing.setDefaultLengthUom(category.getDefaultLengthUom());
|
||||
existing.setIsActive(category.getIsActive());
|
||||
existing.setSortOrder(category.getSortOrder());
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ public class InventoryItem {
|
||||
private Long vendorId;
|
||||
private Long branchId;
|
||||
private String purchaseRef;
|
||||
private String salesRef;
|
||||
private BigDecimal saleRate;
|
||||
private String photoUrl;
|
||||
@Builder.Default
|
||||
private String status = "AVAILABLE";
|
||||
|
||||
@@ -33,6 +33,26 @@ public class Product {
|
||||
private String dimensions;
|
||||
private String color;
|
||||
private String size;
|
||||
|
||||
private Double itemLength;
|
||||
private Double itemWidth;
|
||||
private Double itemHeight;
|
||||
private String dimensionUom;
|
||||
|
||||
private Double packageLength;
|
||||
private Double packageWidth;
|
||||
private Double packageHeight;
|
||||
private String packageDimensionUom;
|
||||
|
||||
private String manufacturerCode;
|
||||
private String material;
|
||||
private String brandName;
|
||||
private String countryOfOrigin;
|
||||
|
||||
private Double weightInG;
|
||||
private Double packageWeightInG;
|
||||
private Double volumetricWeightInKg;
|
||||
|
||||
private String priceCalcRule;
|
||||
private Double purityFactor;
|
||||
private Double makingCharges;
|
||||
|
||||
@@ -34,6 +34,8 @@ public class ProductCategory {
|
||||
private String makingChargeType;
|
||||
|
||||
private String baseUnit;
|
||||
@Builder.Default
|
||||
private String defaultLengthUom = "cm";
|
||||
|
||||
@Builder.Default
|
||||
private Boolean isActive = true;
|
||||
|
||||
@@ -102,6 +102,26 @@ public class ProductService {
|
||||
existingProduct.setColor(updatedProduct.getColor());
|
||||
existingProduct.setSize(updatedProduct.getSize());
|
||||
existingProduct.setDimensions(updatedProduct.getDimensions());
|
||||
|
||||
existingProduct.setItemLength(updatedProduct.getItemLength());
|
||||
existingProduct.setItemWidth(updatedProduct.getItemWidth());
|
||||
existingProduct.setItemHeight(updatedProduct.getItemHeight());
|
||||
existingProduct.setDimensionUom(updatedProduct.getDimensionUom());
|
||||
|
||||
existingProduct.setPackageLength(updatedProduct.getPackageLength());
|
||||
existingProduct.setPackageWidth(updatedProduct.getPackageWidth());
|
||||
existingProduct.setPackageHeight(updatedProduct.getPackageHeight());
|
||||
existingProduct.setPackageDimensionUom(updatedProduct.getPackageDimensionUom());
|
||||
|
||||
existingProduct.setManufacturerCode(updatedProduct.getManufacturerCode());
|
||||
existingProduct.setMaterial(updatedProduct.getMaterial());
|
||||
existingProduct.setBrandName(updatedProduct.getBrandName());
|
||||
existingProduct.setCountryOfOrigin(updatedProduct.getCountryOfOrigin());
|
||||
|
||||
existingProduct.setWeightInG(updatedProduct.getWeightInG());
|
||||
existingProduct.setPackageWeightInG(updatedProduct.getPackageWeightInG());
|
||||
existingProduct.setVolumetricWeightInKg(updatedProduct.getVolumetricWeightInKg());
|
||||
|
||||
existingProduct.setPriceCalcRule(updatedProduct.getPriceCalcRule());
|
||||
existingProduct.setPurityFactor(normalizePurity(updatedProduct.getPurityFactor()));
|
||||
existingProduct.setMakingCharges(updatedProduct.getMakingCharges());
|
||||
|
||||
@@ -146,15 +146,64 @@ public class InvoiceService {
|
||||
if (shouldDeduct && invoice.getItems() != null && !invoice.getItems().isEmpty()) {
|
||||
return Flux.fromIterable(invoice.getItems())
|
||||
.concatMap(item -> {
|
||||
BigDecimal soldQty = (item.getWeight() != null && item.getWeight().compareTo(BigDecimal.ZERO) > 0)
|
||||
? item.getWeight()
|
||||
: (item.getQuantity() != null && item.getQuantity().compareTo(BigDecimal.ZERO) > 0 ? item.getQuantity() : BigDecimal.ONE);
|
||||
BigDecimal soldRate = item.getUnitPrice() != null ? item.getUnitPrice() : BigDecimal.ZERO;
|
||||
|
||||
if (item.getInventoryItemId() != null) {
|
||||
return inventoryItemRepository.findById(item.getInventoryItemId())
|
||||
.flatMap(invItem -> {
|
||||
invItem.setStatus("SOLD");
|
||||
invItem.setUpdatedAt(LocalDateTime.now());
|
||||
BigDecimal cost = invItem.getPurchaseCost() != null ? invItem.getPurchaseCost() : BigDecimal.ZERO;
|
||||
return inventoryItemRepository.save(invItem).thenReturn(cost);
|
||||
BigDecimal currentWeight = (invItem.getGrossWeight() != null && invItem.getGrossWeight().compareTo(BigDecimal.ZERO) > 0)
|
||||
? invItem.getGrossWeight()
|
||||
: ((invItem.getNetWeight() != null && invItem.getNetWeight().compareTo(BigDecimal.ZERO) > 0)
|
||||
? invItem.getNetWeight()
|
||||
: BigDecimal.ONE);
|
||||
|
||||
BigDecimal unitCost = invItem.getPurchaseCost() != null ? invItem.getPurchaseCost() : BigDecimal.ZERO;
|
||||
BigDecimal cost = unitCost.multiply(soldQty);
|
||||
|
||||
if (currentWeight.compareTo(soldQty) > 0) {
|
||||
// Partial sale: Reduce existing available item weight
|
||||
BigDecimal remaining = currentWeight.subtract(soldQty);
|
||||
invItem.setGrossWeight(remaining);
|
||||
invItem.setNetWeight(remaining);
|
||||
invItem.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
// Create new SOLD item record
|
||||
com.kifi.api.entity.inventory.InventoryItem soldItem = com.kifi.api.entity.inventory.InventoryItem.builder()
|
||||
.userId(userId)
|
||||
.productId(invItem.getProductId())
|
||||
.vendorId(invItem.getVendorId())
|
||||
.purchaseRef(invItem.getPurchaseRef())
|
||||
.salesRef(invoice.getInvoiceNumber())
|
||||
.purchaseCost(invItem.getPurchaseCost())
|
||||
.saleRate(soldRate)
|
||||
.makingCharges(invItem.getMakingCharges())
|
||||
.sku(invItem.getSku())
|
||||
.huid(invItem.getHuid())
|
||||
.grossWeight(soldQty)
|
||||
.netWeight(soldQty)
|
||||
.photoUrl(invItem.getPhotoUrl())
|
||||
.status("SOLD")
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
return inventoryItemRepository.save(invItem)
|
||||
.then(inventoryItemRepository.save(soldItem))
|
||||
.thenReturn(cost);
|
||||
} else {
|
||||
// Full sale
|
||||
invItem.setStatus("SOLD");
|
||||
invItem.setSalesRef(invoice.getInvoiceNumber());
|
||||
invItem.setSaleRate(soldRate);
|
||||
invItem.setUpdatedAt(LocalDateTime.now());
|
||||
return inventoryItemRepository.save(invItem).thenReturn(cost);
|
||||
}
|
||||
}).defaultIfEmpty(BigDecimal.ZERO);
|
||||
} else if (item.getProductId() != null) {
|
||||
// Product sale without explicit inventory item: adjust product stock & FIFO deduct inventory items
|
||||
InventoryMovement movement = InventoryMovement.builder()
|
||||
.userId(userId)
|
||||
.type("REDUCTION")
|
||||
@@ -167,8 +216,72 @@ public class InvoiceService {
|
||||
movementItem.setQuantity(item.getQuantity());
|
||||
movement.setItems(java.util.Collections.singletonList(movementItem));
|
||||
|
||||
return productService.adjustStock(userId, item.getProductId(), movement)
|
||||
.thenReturn(BigDecimal.ZERO);
|
||||
Mono<Void> adjustMono = productService.adjustStock(userId, item.getProductId(), movement).then();
|
||||
|
||||
Mono<BigDecimal> fifoMono = inventoryItemRepository.findByUserIdAndProductId(userId, item.getProductId())
|
||||
.filter(i -> "AVAILABLE".equalsIgnoreCase(i.getStatus()))
|
||||
.collectList()
|
||||
.flatMap(availItems -> {
|
||||
if (availItems.isEmpty()) {
|
||||
return Mono.just(BigDecimal.ZERO);
|
||||
}
|
||||
BigDecimal needed = soldQty;
|
||||
java.util.List<Mono<Void>> saves = new java.util.ArrayList<>();
|
||||
BigDecimal totalCost = BigDecimal.ZERO;
|
||||
|
||||
for (com.kifi.api.entity.inventory.InventoryItem invItem : availItems) {
|
||||
if (needed.compareTo(BigDecimal.ZERO) <= 0) break;
|
||||
BigDecimal itemWeight = (invItem.getGrossWeight() != null && invItem.getGrossWeight().compareTo(BigDecimal.ZERO) > 0)
|
||||
? invItem.getGrossWeight()
|
||||
: ((invItem.getNetWeight() != null && invItem.getNetWeight().compareTo(BigDecimal.ZERO) > 0)
|
||||
? invItem.getNetWeight()
|
||||
: BigDecimal.ONE);
|
||||
|
||||
BigDecimal take = needed.min(itemWeight);
|
||||
needed = needed.subtract(take);
|
||||
BigDecimal unitPurchase = invItem.getPurchaseCost() != null ? invItem.getPurchaseCost() : BigDecimal.ZERO;
|
||||
totalCost = totalCost.add(unitPurchase.multiply(take));
|
||||
|
||||
if (itemWeight.compareTo(take) > 0) {
|
||||
BigDecimal remaining = itemWeight.subtract(take);
|
||||
invItem.setGrossWeight(remaining);
|
||||
invItem.setNetWeight(remaining);
|
||||
invItem.setUpdatedAt(LocalDateTime.now());
|
||||
saves.add(inventoryItemRepository.save(invItem).then());
|
||||
|
||||
com.kifi.api.entity.inventory.InventoryItem soldRecord = com.kifi.api.entity.inventory.InventoryItem.builder()
|
||||
.userId(userId)
|
||||
.productId(invItem.getProductId())
|
||||
.vendorId(invItem.getVendorId())
|
||||
.purchaseRef(invItem.getPurchaseRef())
|
||||
.salesRef(invoice.getInvoiceNumber())
|
||||
.purchaseCost(invItem.getPurchaseCost())
|
||||
.saleRate(soldRate)
|
||||
.makingCharges(invItem.getMakingCharges())
|
||||
.sku(invItem.getSku())
|
||||
.huid(invItem.getHuid())
|
||||
.grossWeight(take)
|
||||
.netWeight(take)
|
||||
.photoUrl(invItem.getPhotoUrl())
|
||||
.status("SOLD")
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
saves.add(inventoryItemRepository.save(soldRecord).then());
|
||||
} else {
|
||||
invItem.setStatus("SOLD");
|
||||
invItem.setSalesRef(invoice.getInvoiceNumber());
|
||||
invItem.setSaleRate(soldRate);
|
||||
invItem.setUpdatedAt(LocalDateTime.now());
|
||||
saves.add(inventoryItemRepository.save(invItem).then());
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal finalCost = totalCost;
|
||||
return Flux.concat(saves).then(Mono.just(finalCost));
|
||||
});
|
||||
|
||||
return adjustMono.then(fifoMono);
|
||||
}
|
||||
return Mono.just(BigDecimal.ZERO);
|
||||
})
|
||||
|
||||
@@ -208,6 +208,7 @@ CREATE TABLE IF NOT EXISTS product_categories (
|
||||
commodity_code VARCHAR(10),
|
||||
purity_factor DECIMAL(5, 4) DEFAULT 1.0,
|
||||
base_unit VARCHAR(20) DEFAULT 'pcs',
|
||||
default_length_uom VARCHAR(20) DEFAULT 'cm',
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
@@ -249,6 +250,21 @@ CREATE TABLE IF NOT EXISTS products (
|
||||
weight DECIMAL(10, 3),
|
||||
color VARCHAR(50),
|
||||
size VARCHAR(50),
|
||||
item_length DECIMAL(10, 2),
|
||||
item_width DECIMAL(10, 2),
|
||||
item_height DECIMAL(10, 2),
|
||||
dimension_uom VARCHAR(20) DEFAULT 'cm',
|
||||
package_length DECIMAL(10, 2),
|
||||
package_width DECIMAL(10, 2),
|
||||
package_height DECIMAL(10, 2),
|
||||
package_dimension_uom VARCHAR(20) DEFAULT 'cm',
|
||||
manufacturer_code VARCHAR(100),
|
||||
material VARCHAR(100),
|
||||
brand_name VARCHAR(100),
|
||||
country_of_origin VARCHAR(100),
|
||||
weight_in_g DECIMAL(10, 3),
|
||||
package_weight_in_g DECIMAL(10, 3),
|
||||
volumetric_weight_in_kg DECIMAL(10, 3),
|
||||
price_calc_rule VARCHAR(50) DEFAULT 'MANUAL',
|
||||
purity_factor DECIMAL(5, 4),
|
||||
making_charges DECIMAL(15, 2) DEFAULT 0.0,
|
||||
@@ -383,6 +399,8 @@ CREATE TABLE IF NOT EXISTS inventory_items (
|
||||
vendor_id INTEGER REFERENCES vendors(id) ON DELETE SET NULL,
|
||||
branch_id INTEGER,
|
||||
purchase_ref VARCHAR(100),
|
||||
sales_ref VARCHAR(100),
|
||||
sale_rate DECIMAL(15, 2),
|
||||
photo_url VARCHAR(1024),
|
||||
status VARCHAR(50) DEFAULT 'AVAILABLE',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
Reference in New Issue
Block a user