Fixed sales invoice issues in Ecommerce

This commit is contained in:
2026-09-03 08:34:23 +05:30
parent b4772ccf19
commit b123a34e8f
18 changed files with 2275 additions and 453 deletions

View File

@@ -0,0 +1,54 @@
package com.kifi.api.controller.sales;
import com.kifi.api.entity.sales.SalesChannel;
import com.kifi.api.service.sales.SalesChannelService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/api/kifi-v2/sales-channels")
@RequiredArgsConstructor
public class SalesChannelController {
private final SalesChannelService salesChannelService;
@GetMapping
public Flux<SalesChannel> getSalesChannels(Authentication authentication) {
Long userId = Long.valueOf(authentication.getDetails().toString());
return salesChannelService.getActiveChannels(userId);
}
@GetMapping("/all")
public Flux<SalesChannel> getAllSalesChannels(Authentication authentication) {
Long userId = Long.valueOf(authentication.getDetails().toString());
return salesChannelService.getAllChannels(userId);
}
@PostMapping
public Mono<SalesChannel> createSalesChannel(
Authentication authentication,
@RequestBody SalesChannel channel) {
Long userId = Long.valueOf(authentication.getDetails().toString());
return salesChannelService.createChannel(userId, channel);
}
@PutMapping("/{id}")
public Mono<SalesChannel> updateSalesChannel(
@PathVariable Long id,
Authentication authentication,
@RequestBody SalesChannel channel) {
Long userId = Long.valueOf(authentication.getDetails().toString());
return salesChannelService.updateChannel(userId, id, channel);
}
@DeleteMapping("/{id}")
public Mono<Void> deleteSalesChannel(
@PathVariable Long id,
Authentication authentication) {
Long userId = Long.valueOf(authentication.getDetails().toString());
return salesChannelService.deleteChannel(userId, id);
}
}

View File

@@ -33,6 +33,9 @@ public class BusinessFeature {
@Column("barcode_source")
private String barcodeSource;
@Column("enable_ecommerce_channels")
private Boolean enableEcommerceChannels;
@Column("created_at")
private LocalDateTime createdAt;
}

View File

@@ -80,6 +80,21 @@ public class Invoice {
@Column("emi_start_date")
private LocalDate emiStartDate;
@Column("sales_channel_id")
private Long salesChannelId;
@Column("sales_channel")
private String salesChannel;
@Column("marketplace_order_id")
private String marketplaceOrderId;
@Column("place_of_supply_state_id")
private Long placeOfSupplyStateId;
@Column("place_of_supply")
private String placeOfSupply;
@Column("created_at")
private LocalDateTime createdAt;

View File

@@ -0,0 +1,43 @@
package com.kifi.api.entity.sales;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table("sales_channels")
public class SalesChannel {
@Id
private Long id;
@Column("user_id")
private Long userId;
private String name;
private String code;
private String icon;
@Column("default_ledger_id")
private Long defaultLedgerId;
@Column("is_active")
@Builder.Default
private Boolean isActive = true;
@Column("created_at")
private LocalDateTime createdAt;
@Column("updated_at")
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,18 @@
package com.kifi.api.repository.sales;
import com.kifi.api.entity.sales.SalesChannel;
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 SalesChannelRepository extends ReactiveCrudRepository<SalesChannel, Long> {
@Query("SELECT * FROM sales_channels WHERE (user_id = :userId OR user_id IS NULL) AND is_active = true ORDER BY id ASC")
Flux<SalesChannel> findActiveChannelsForUser(Long userId);
@Query("SELECT * FROM sales_channels WHERE (user_id = :userId OR user_id IS NULL) ORDER BY id ASC")
Flux<SalesChannel> findAllChannelsForUser(Long userId);
Mono<SalesChannel> findByIdAndUserId(Long id, Long userId);
}

View File

@@ -37,6 +37,7 @@ public class BusinessService {
.multiLocation(false)
.bomReductionStrategy("COMPONENTS_ONLY")
.stockDeductionOnInvoice(true)
.enableEcommerceChannels(false)
.createdAt(LocalDateTime.now())
.build());
}
@@ -51,6 +52,7 @@ public class BusinessService {
if (feature.getBomReductionStrategy() != null) existing.setBomReductionStrategy(feature.getBomReductionStrategy());
if (feature.getStockDeductionOnInvoice() != null) existing.setStockDeductionOnInvoice(feature.getStockDeductionOnInvoice());
if (feature.getBarcodeSource() != null) existing.setBarcodeSource(feature.getBarcodeSource());
if (feature.getEnableEcommerceChannels() != null) existing.setEnableEcommerceChannels(feature.getEnableEcommerceChannels());
return businessFeatureRepository.save(existing);
})
.switchIfEmpty(Mono.defer(() -> {

View File

@@ -225,6 +225,16 @@ public class InvoiceService {
if (availItems.isEmpty()) {
return Mono.just(BigDecimal.ZERO);
}
// Strict FIFO: Sort by purchase/creation date & time in ASCENDING order
availItems.sort((a, b) -> {
if (a.getCreatedAt() == null && b.getCreatedAt() == null) {
return Long.compare(a.getId() != null ? a.getId() : 0L, b.getId() != null ? b.getId() : 0L);
}
if (a.getCreatedAt() == null) return 1;
if (b.getCreatedAt() == null) return -1;
int cmp = a.getCreatedAt().compareTo(b.getCreatedAt());
return cmp != 0 ? cmp : Long.compare(a.getId() != null ? a.getId() : 0L, b.getId() != null ? b.getId() : 0L);
});
BigDecimal needed = soldQty;
java.util.List<Mono<Void>> saves = new java.util.ArrayList<>();
BigDecimal totalCost = BigDecimal.ZERO;

View File

@@ -0,0 +1,55 @@
package com.kifi.api.service.sales;
import com.kifi.api.entity.sales.SalesChannel;
import com.kifi.api.repository.sales.SalesChannelRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
@Service
@RequiredArgsConstructor
@Transactional
public class SalesChannelService {
private final SalesChannelRepository salesChannelRepository;
public Flux<SalesChannel> getActiveChannels(Long userId) {
return salesChannelRepository.findActiveChannelsForUser(userId);
}
public Flux<SalesChannel> getAllChannels(Long userId) {
return salesChannelRepository.findAllChannelsForUser(userId);
}
public Mono<SalesChannel> createChannel(Long userId, SalesChannel channel) {
channel.setUserId(userId);
channel.setCreatedAt(LocalDateTime.now());
channel.setUpdatedAt(LocalDateTime.now());
if (channel.getIsActive() == null) {
channel.setIsActive(true);
}
return salesChannelRepository.save(channel);
}
public Mono<SalesChannel> updateChannel(Long userId, Long id, SalesChannel updated) {
return salesChannelRepository.findByIdAndUserId(id, userId)
.flatMap(existing -> {
if (updated.getName() != null) existing.setName(updated.getName());
if (updated.getCode() != null) existing.setCode(updated.getCode());
if (updated.getIcon() != null) existing.setIcon(updated.getIcon());
if (updated.getDefaultLedgerId() != null) existing.setDefaultLedgerId(updated.getDefaultLedgerId());
if (updated.getIsActive() != null) existing.setIsActive(updated.getIsActive());
existing.setUpdatedAt(LocalDateTime.now());
return salesChannelRepository.save(existing);
});
}
public Mono<Void> deleteChannel(Long userId, Long id) {
return salesChannelRepository.findByIdAndUserId(id, userId)
.flatMap(salesChannelRepository::delete);
}
}