Fixed upto purchases and rate sync process

This commit is contained in:
2026-08-28 19:45:19 +05:30
parent 189f49ed07
commit eacca6aaea
21 changed files with 1203 additions and 401 deletions

View File

@@ -9,20 +9,27 @@ import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Map;
@RestController
@RequestMapping("/api/kifi-v2/inventory/commodity-rates")
@RequiredArgsConstructor
public class CommodityRateController {
private final CommodityRateService rateService;
@GetMapping("/{commodityName}")
public Flux<CommodityRateHistory> getRateHistory(@PathVariable String commodityName) {
return rateService.getRateHistory(commodityName);
@GetMapping("/latest")
public Flux<CommodityRateHistory> getAllLatestRates() {
return rateService.getAllLatestRates();
}
@GetMapping("/{commodityName}/latest")
public Mono<ResponseEntity<CommodityRateHistory>> getLatestRate(@PathVariable String commodityName) {
return rateService.getLatestRate(commodityName)
@GetMapping("/{commodityCode}")
public Flux<CommodityRateHistory> getRateHistory(@PathVariable String commodityCode) {
return rateService.getRateHistory(commodityCode);
}
@GetMapping("/{commodityCode}/latest")
public Mono<ResponseEntity<CommodityRateHistory>> getLatestRate(@PathVariable String commodityCode) {
return rateService.getLatestRate(commodityCode)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
@@ -32,12 +39,28 @@ public class CommodityRateController {
Authentication authentication,
@RequestBody CommodityRateHistory rateHistory) {
Long userId = Long.valueOf(authentication.getDetails().toString());
String code = rateHistory.getCommodityCode() != null ? rateHistory.getCommodityCode() : rateHistory.getCommodityName();
String name = rateHistory.getCommodityName() != null ? rateHistory.getCommodityName() : code;
return rateService.addRate(
rateHistory.getCommodityName(),
code,
name,
rateHistory.getPurity(),
rateHistory.getRate(),
rateHistory.getSource() != null ? rateHistory.getSource() : "MANUAL",
userId
).map(ResponseEntity::ok);
}
@PostMapping("/{commodityCode}/sync")
public Mono<ResponseEntity<Map<String, Object>>> syncRatesToProducts(
Authentication authentication,
@PathVariable String commodityCode) {
Long userId = Long.valueOf(authentication.getDetails().toString());
return rateService.syncRatesToProducts(commodityCode, userId)
.map(count -> ResponseEntity.ok(Map.<String, Object>of(
"commodityCode", commodityCode,
"updatedCount", count,
"message", "Successfully synced rates for " + count + " products"
)));
}
}

View File

@@ -47,6 +47,7 @@ public class ProductCategoryController {
existing.setParentCategoryId(category.getParentCategoryId());
existing.setHasChild(category.getHasChild());
existing.setCommodityCode(category.getCommodityCode());
existing.setPurityFactor(category.getPurityFactor() != null ? category.getPurityFactor() : 1.0);
existing.setDefaultHsn(category.getDefaultHsn());
existing.setDefaultGst(category.getDefaultGst());
existing.setHuidRequired(category.getHuidRequired());

View File

@@ -18,6 +18,7 @@ import java.time.LocalDateTime;
public class CommodityRateHistory {
@Id
private Long id;
private String commodityCode;
private String commodityName;
private String purity;
private BigDecimal rate;

View File

@@ -24,6 +24,8 @@ public class ProductCategory {
@Builder.Default
private Boolean hasChild = false;
private String commodityCode;
@Builder.Default
private Double purityFactor = 1.0;
private String defaultHsn;
private java.math.BigDecimal defaultGst;
@Builder.Default

View File

@@ -4,8 +4,10 @@ import com.kifi.api.entity.TransactionItem;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Repository
public interface TransactionItemRepository extends ReactiveCrudRepository<TransactionItem, Long> {
Flux<TransactionItem> findByTransactionId(Long transactionId);
Mono<Void> deleteByTransactionId(Long transactionId);
}

View File

@@ -1,10 +1,15 @@
package com.kifi.api.repository.inventory;
import com.kifi.api.entity.inventory.CommodityRateHistory;
import org.springframework.data.r2dbc.repository.Query;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface CommodityRateHistoryRepository extends ReactiveCrudRepository<CommodityRateHistory, Long> {
Flux<CommodityRateHistory> findByCommodityCodeOrderByEffectiveAtDesc(String commodityCode);
Flux<CommodityRateHistory> findByCommodityNameOrderByEffectiveAtDesc(String commodityName);
@Query("SELECT DISTINCT ON (commodity_code) * FROM commodity_rate_history WHERE commodity_code IS NOT NULL ORDER BY commodity_code, effective_at DESC")
Flux<CommodityRateHistory> findLatestRatesForAllCommodities();
}

View File

@@ -6,4 +6,5 @@ import reactor.core.publisher.Flux;
public interface ProductCategoryRepository extends ReactiveCrudRepository<ProductCategory, Long> {
Flux<ProductCategory> findByUserId(Long userId);
Flux<ProductCategory> findByUserIdAndCommodityCode(Long userId, String commodityCode);
}

View File

@@ -137,6 +137,7 @@ public class TransactionService {
public Mono<Transaction> updateTransaction(Long id, Long userId, Transaction updatedTransaction) {
return transactionRepository.findById(id)
.filter(t -> t.getUserId().equals(userId))
.switchIfEmpty(Mono.error(new org.springframework.web.server.ResponseStatusException(org.springframework.http.HttpStatus.NOT_FOUND, "Transaction not found")))
.flatMap(t -> {
// Revert old balances
Mono<Void> revertBalances = updateWalletBalances(t.getToWalletId(), t.getFromWalletId(), t.getAmount());
@@ -152,9 +153,26 @@ public class TransactionService {
t.setAmount(updatedTransaction.getAmount());
t.setDate(updatedTransaction.getDate());
t.setDescription(updatedTransaction.getDescription());
t.setDueDate(updatedTransaction.getDueDate());
t.setAlertSchedule(updatedTransaction.getAlertSchedule());
t.setAlertTime(updatedTransaction.getAlertTime());
return revertBalances.then(applyNewBalances).then(transactionRepository.save(t));
}).flatMap(this::populateItemsAndAttachments);
})
.flatMap(savedTx -> {
Mono<Void> deleteOldItems = transactionItemRepository.deleteByTransactionId(savedTx.getId());
Mono<Void> saveNewItems = Mono.empty();
if (updatedTransaction.getItems() != null && !updatedTransaction.getItems().isEmpty()) {
for (TransactionItem item : updatedTransaction.getItems()) {
item.setId(null);
item.setTransactionId(savedTx.getId());
item.setCreatedAt(LocalDateTime.now());
}
saveNewItems = transactionItemRepository.saveAll(updatedTransaction.getItems()).then();
}
return deleteOldItems.then(saveNewItems).thenReturn(savedTx);
})
.flatMap(this::populateItemsAndAttachments);
}
@Transactional

View File

@@ -1,38 +1,82 @@
package com.kifi.api.service.inventory;
import com.kifi.api.entity.inventory.CommodityRateHistory;
import com.kifi.api.entity.inventory.Product;
import com.kifi.api.entity.inventory.ProductCategory;
import com.kifi.api.repository.inventory.CommodityRateHistoryRepository;
import com.kifi.api.repository.inventory.ProductCategoryRepository;
import com.kifi.api.repository.inventory.ProductRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
@Service
@RequiredArgsConstructor
public class CommodityRateService {
private final CommodityRateHistoryRepository rateHistoryRepository;
private final ProductCategoryRepository productCategoryRepository;
private final ProductRepository productRepository;
public Mono<CommodityRateHistory> addRate(String commodityName, String purity, BigDecimal rate, String source, Long userId) {
public Mono<CommodityRateHistory> addRate(String commodityCode, String commodityName, String purity, BigDecimal rate, String source, Long userId) {
CommodityRateHistory history = CommodityRateHistory.builder()
.commodityName(commodityName)
.commodityCode(commodityCode)
.commodityName(commodityName != null && !commodityName.isEmpty() ? commodityName : commodityCode)
.purity(purity)
.rate(rate)
.effectiveAt(LocalDateTime.now())
.source(source)
.source(source != null ? source : "MANUAL")
.createdBy(userId)
.createdAt(LocalDateTime.now())
.build();
return rateHistoryRepository.save(history);
}
public Flux<CommodityRateHistory> getRateHistory(String commodityName) {
return rateHistoryRepository.findByCommodityNameOrderByEffectiveAtDesc(commodityName);
public Flux<CommodityRateHistory> getRateHistory(String commodityCode) {
return rateHistoryRepository.findByCommodityCodeOrderByEffectiveAtDesc(commodityCode)
.switchIfEmpty(rateHistoryRepository.findByCommodityNameOrderByEffectiveAtDesc(commodityCode));
}
public Mono<CommodityRateHistory> getLatestRate(String commodityName) {
return getRateHistory(commodityName).next();
public Mono<CommodityRateHistory> getLatestRate(String commodityCode) {
return getRateHistory(commodityCode).next();
}
public Flux<CommodityRateHistory> getAllLatestRates() {
return rateHistoryRepository.findLatestRatesForAllCommodities();
}
public Mono<Integer> syncRatesToProducts(String commodityCode, Long userId) {
return getLatestRate(commodityCode)
.flatMap(latestRate ->
productCategoryRepository.findByUserIdAndCommodityCode(userId, commodityCode)
.flatMap(category -> {
double categoryPurity = category.getPurityFactor() != null ? category.getPurityFactor() : 1.0;
return productRepository.findByCategoryId(category.getId())
.flatMap(product -> {
double productPurity = product.getPurityFactor() != null ? product.getPurityFactor() : categoryPurity;
BigDecimal baseRate = latestRate.getRate().multiply(BigDecimal.valueOf(productPurity));
if ("WEIGHT_BASED".equalsIgnoreCase(product.getPriceCalcRule())) {
BigDecimal making = product.getMakingCharges() != null ? BigDecimal.valueOf(product.getMakingCharges()) : BigDecimal.ZERO;
BigDecimal newPrice = baseRate;
if ("PERCENTAGE".equalsIgnoreCase(product.getMakingChargesType())) {
newPrice = baseRate.add(baseRate.multiply(making).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
} else {
newPrice = baseRate.add(making);
}
product.setSellingPrice(newPrice.setScale(2, RoundingMode.HALF_UP));
}
product.setUpdatedAt(LocalDateTime.now());
return productRepository.save(product);
});
})
.collectList()
.map(list -> list.size())
)
.defaultIfEmpty(0);
}
}

View File

@@ -201,7 +201,8 @@ CREATE TABLE IF NOT EXISTS product_categories (
default_gst DECIMAL(5, 2),
huid_required BOOLEAN DEFAULT FALSE,
default_making_charge DECIMAL(15, 2),
making_charge_type VARCHAR(50),
commodity_code VARCHAR(10),
purity_factor DECIMAL(5, 4) DEFAULT 1.0,
base_unit VARCHAR(20) DEFAULT 'pcs',
is_active BOOLEAN DEFAULT TRUE,
sort_order INTEGER DEFAULT 0,
@@ -210,7 +211,8 @@ CREATE TABLE IF NOT EXISTS product_categories (
CREATE TABLE IF NOT EXISTS commodity_rate_history (
id SERIAL PRIMARY KEY,
commodity_name VARCHAR(100) NOT NULL,
commodity_code VARCHAR(100) NOT NULL,
commodity_name VARCHAR(100),
purity VARCHAR(50),
rate DECIMAL(15, 2) NOT NULL,
effective_at TIMESTAMP NOT NULL,