Added market place specific feature
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package com.kifi.api.controller.invoice;
|
||||
|
||||
import com.kifi.api.entity.invoice.CreditNote;
|
||||
import com.kifi.api.service.invoice.CreditNoteService;
|
||||
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")
|
||||
@RequiredArgsConstructor
|
||||
public class CreditNoteController {
|
||||
|
||||
private final CreditNoteService creditNoteService;
|
||||
|
||||
@GetMapping("/credit-notes")
|
||||
public Flux<CreditNote> getCreditNotes(Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return creditNoteService.getCreditNotes(userId);
|
||||
}
|
||||
|
||||
@GetMapping("/credit-notes/{id}")
|
||||
public Mono<CreditNote> getCreditNoteById(
|
||||
@PathVariable Long id,
|
||||
Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return creditNoteService.getCreditNoteById(id, userId);
|
||||
}
|
||||
|
||||
@GetMapping("/invoices/{invoiceId}/credit-notes")
|
||||
public Flux<CreditNote> getCreditNotesForInvoice(
|
||||
@PathVariable Long invoiceId,
|
||||
Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return creditNoteService.getCreditNotesForInvoice(invoiceId, userId);
|
||||
}
|
||||
|
||||
@PostMapping("/invoices/{invoiceId}/credit-notes")
|
||||
public Mono<CreditNote> createCreditNote(
|
||||
@PathVariable Long invoiceId,
|
||||
Authentication authentication,
|
||||
@RequestBody CreditNote creditNote) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return creditNoteService.createCreditNote(userId, invoiceId, creditNote);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.kifi.api.controller.invoice;
|
||||
|
||||
import com.kifi.api.entity.invoice.MarketplaceSettlement;
|
||||
import com.kifi.api.service.invoice.MarketplaceSettlementService;
|
||||
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")
|
||||
@RequiredArgsConstructor
|
||||
public class MarketplaceSettlementController {
|
||||
|
||||
private final MarketplaceSettlementService settlementService;
|
||||
|
||||
@GetMapping("/marketplace-settlements")
|
||||
public Flux<MarketplaceSettlement> getSettlements(Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return settlementService.getSettlements(userId);
|
||||
}
|
||||
|
||||
@GetMapping("/marketplace-settlements/{id}")
|
||||
public Mono<MarketplaceSettlement> getSettlementById(
|
||||
@PathVariable Long id,
|
||||
Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return settlementService.getSettlementById(id, userId);
|
||||
}
|
||||
|
||||
@GetMapping("/invoices/{invoiceId}/settlements")
|
||||
public Flux<MarketplaceSettlement> getSettlementsForInvoice(
|
||||
@PathVariable Long invoiceId,
|
||||
Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return settlementService.getSettlementsForInvoice(invoiceId, userId);
|
||||
}
|
||||
|
||||
@PostMapping("/invoices/{invoiceId}/settlements")
|
||||
public Mono<MarketplaceSettlement> createSettlement(
|
||||
@PathVariable Long invoiceId,
|
||||
Authentication authentication,
|
||||
@RequestBody MarketplaceSettlement settlement) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return settlementService.createSettlement(userId, invoiceId, settlement);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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.math.BigDecimal;
|
||||
@@ -58,6 +59,19 @@ public class Product {
|
||||
private Double makingCharges;
|
||||
private String makingChargesType;
|
||||
private Double wastagePercentage;
|
||||
|
||||
@Column("min_stock")
|
||||
private BigDecimal minStock;
|
||||
|
||||
@Column("reorder_level")
|
||||
private BigDecimal reorderLevel;
|
||||
|
||||
@Column("reorder_quantity")
|
||||
private BigDecimal reorderQuantity;
|
||||
|
||||
@Column("track_inventory")
|
||||
private Boolean trackInventory;
|
||||
|
||||
private Boolean isActive;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.kifi.api.entity.invoice;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.relational.core.mapping.Column;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("credit_notes")
|
||||
public class CreditNote {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@Column("user_id")
|
||||
private Long userId;
|
||||
|
||||
@Column("invoice_id")
|
||||
private Long invoiceId;
|
||||
|
||||
@Column("credit_note_number")
|
||||
private String creditNoteNumber;
|
||||
|
||||
@Column("customer_id")
|
||||
private Long customerId;
|
||||
|
||||
@Column("return_date")
|
||||
private LocalDate returnDate;
|
||||
|
||||
@Column("reason")
|
||||
private String reason; // CUSTOMER_RETURN, RTO_UNDELIVERED, DEFECTIVE, EXCHANGE, OTHER
|
||||
|
||||
@Column("restock_items")
|
||||
@Builder.Default
|
||||
private Boolean restockItems = true;
|
||||
|
||||
@Column("subtotal")
|
||||
private BigDecimal subtotal;
|
||||
|
||||
@Column("tax_total")
|
||||
private BigDecimal taxTotal;
|
||||
|
||||
@Column("cgst_total")
|
||||
private BigDecimal cgstTotal;
|
||||
|
||||
@Column("sgst_total")
|
||||
private BigDecimal sgstTotal;
|
||||
|
||||
@Column("igst_total")
|
||||
private BigDecimal igstTotal;
|
||||
|
||||
@Column("discount_total")
|
||||
private BigDecimal discountTotal;
|
||||
|
||||
@Column("total_amount")
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
@Column("refund_status")
|
||||
private String refundStatus; // ADJUSTED_TO_AR, REFUNDED_TO_WALLET, STORE_CREDIT, PENDING
|
||||
|
||||
@Column("refund_wallet_id")
|
||||
private Long refundWalletId;
|
||||
|
||||
@Column("notes")
|
||||
private String notes;
|
||||
|
||||
@Column("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Transient
|
||||
private List<CreditNoteItem> items;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.kifi.api.entity.invoice;
|
||||
|
||||
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.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("credit_note_items")
|
||||
public class CreditNoteItem {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@Column("credit_note_id")
|
||||
private Long creditNoteId;
|
||||
|
||||
@Column("invoice_item_id")
|
||||
private Long invoiceItemId;
|
||||
|
||||
@Column("product_id")
|
||||
private Long productId;
|
||||
|
||||
@Column("inventory_item_id")
|
||||
private Long inventoryItemId;
|
||||
|
||||
@Column("product_name")
|
||||
private String productName;
|
||||
|
||||
@Column("sku")
|
||||
private String sku;
|
||||
|
||||
@Column("huid")
|
||||
private String huid;
|
||||
|
||||
@Column("hsn_code")
|
||||
private String hsnCode;
|
||||
|
||||
@Column("quantity")
|
||||
private BigDecimal quantity;
|
||||
|
||||
@Column("weight")
|
||||
private BigDecimal weight;
|
||||
|
||||
@Column("unit_price")
|
||||
private BigDecimal unitPrice;
|
||||
|
||||
@Column("tax_rate")
|
||||
private BigDecimal taxRate;
|
||||
|
||||
@Column("cgst")
|
||||
private BigDecimal cgst;
|
||||
|
||||
@Column("sgst")
|
||||
private BigDecimal sgst;
|
||||
|
||||
@Column("igst")
|
||||
private BigDecimal igst;
|
||||
|
||||
@Column("discount")
|
||||
private BigDecimal discount;
|
||||
|
||||
@Column("making_charge")
|
||||
private BigDecimal makingCharge;
|
||||
|
||||
@Column("other_charges")
|
||||
private BigDecimal otherCharges;
|
||||
|
||||
@Column("total")
|
||||
private BigDecimal total;
|
||||
}
|
||||
@@ -89,6 +89,24 @@ public class Invoice {
|
||||
@Column("marketplace_order_id")
|
||||
private String marketplaceOrderId;
|
||||
|
||||
@Column("courier_partner")
|
||||
private String courierPartner;
|
||||
|
||||
@Column("tracking_number")
|
||||
private String trackingNumber;
|
||||
|
||||
@Column("dispatch_status")
|
||||
private String dispatchStatus; // PENDING, PACKED, SHIPPED, IN_TRANSIT, DELIVERED, RTO
|
||||
|
||||
@Column("shipped_at")
|
||||
private LocalDateTime shippedAt;
|
||||
|
||||
@Column("shipping_address")
|
||||
private String shippingAddress;
|
||||
|
||||
@Column("shipping_pincode")
|
||||
private String shippingPincode;
|
||||
|
||||
@Column("place_of_supply_state_id")
|
||||
private Long placeOfSupplyStateId;
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.kifi.api.entity.invoice;
|
||||
|
||||
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.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("marketplace_settlements")
|
||||
public class MarketplaceSettlement {
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
@Column("user_id")
|
||||
private Long userId;
|
||||
|
||||
@Column("invoice_id")
|
||||
private Long invoiceId;
|
||||
|
||||
@Column("sales_channel_id")
|
||||
private Long salesChannelId;
|
||||
|
||||
@Column("channel_name")
|
||||
private String channelName;
|
||||
|
||||
@Column("settlement_ref")
|
||||
private String settlementRef;
|
||||
|
||||
@Column("settlement_date")
|
||||
private LocalDate settlementDate;
|
||||
|
||||
@Column("gross_amount")
|
||||
private BigDecimal grossAmount;
|
||||
|
||||
@Column("commission_fee")
|
||||
@Builder.Default
|
||||
private BigDecimal commissionFee = BigDecimal.ZERO;
|
||||
|
||||
@Column("shipping_fee")
|
||||
@Builder.Default
|
||||
private BigDecimal shippingFee = BigDecimal.ZERO;
|
||||
|
||||
@Column("other_fees")
|
||||
@Builder.Default
|
||||
private BigDecimal otherFees = BigDecimal.ZERO;
|
||||
|
||||
@Column("fee_gst")
|
||||
@Builder.Default
|
||||
private BigDecimal feeGst = BigDecimal.ZERO;
|
||||
|
||||
@Column("tcs_gst_amount")
|
||||
@Builder.Default
|
||||
private BigDecimal tcsGstAmount = BigDecimal.ZERO;
|
||||
|
||||
@Column("tds_amount")
|
||||
@Builder.Default
|
||||
private BigDecimal tdsAmount = BigDecimal.ZERO;
|
||||
|
||||
@Column("total_deductions")
|
||||
private BigDecimal totalDeductions;
|
||||
|
||||
@Column("net_payout_amount")
|
||||
private BigDecimal netPayoutAmount;
|
||||
|
||||
@Column("payout_wallet_id")
|
||||
private Long payoutWalletId;
|
||||
|
||||
@Column("status")
|
||||
@Builder.Default
|
||||
private String status = "SETTLED";
|
||||
|
||||
@Column("notes")
|
||||
private String notes;
|
||||
|
||||
@Column("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository.invoice;
|
||||
|
||||
import com.kifi.api.entity.invoice.CreditNoteItem;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface CreditNoteItemRepository extends ReactiveCrudRepository<CreditNoteItem, Long> {
|
||||
Flux<CreditNoteItem> findByCreditNoteId(Long creditNoteId);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.kifi.api.repository.invoice;
|
||||
|
||||
import com.kifi.api.entity.invoice.CreditNote;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface CreditNoteRepository extends ReactiveCrudRepository<CreditNote, Long> {
|
||||
Flux<CreditNote> findByUserId(Long userId);
|
||||
Flux<CreditNote> findByUserIdAndInvoiceId(Long userId, Long invoiceId);
|
||||
Flux<CreditNote> findByUserIdAndCustomerId(Long userId, Long customerId);
|
||||
Mono<CreditNote> findByUserIdAndCreditNoteNumber(Long userId, String creditNoteNumber);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.kifi.api.repository.invoice;
|
||||
|
||||
import com.kifi.api.entity.invoice.MarketplaceSettlement;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface MarketplaceSettlementRepository extends ReactiveCrudRepository<MarketplaceSettlement, Long> {
|
||||
Flux<MarketplaceSettlement> findByUserId(Long userId);
|
||||
Flux<MarketplaceSettlement> findByUserIdAndInvoiceId(Long userId, Long invoiceId);
|
||||
Flux<MarketplaceSettlement> findByUserIdAndSalesChannelId(Long userId, Long salesChannelId);
|
||||
Mono<MarketplaceSettlement> findByUserIdAndSettlementRef(Long userId, String settlementRef);
|
||||
}
|
||||
@@ -127,6 +127,12 @@ public class ProductService {
|
||||
existingProduct.setMakingCharges(updatedProduct.getMakingCharges());
|
||||
existingProduct.setMakingChargesType(updatedProduct.getMakingChargesType());
|
||||
existingProduct.setWastagePercentage(updatedProduct.getWastagePercentage());
|
||||
|
||||
existingProduct.setMinStock(updatedProduct.getMinStock());
|
||||
existingProduct.setReorderLevel(updatedProduct.getReorderLevel());
|
||||
existingProduct.setReorderQuantity(updatedProduct.getReorderQuantity());
|
||||
existingProduct.setTrackInventory(updatedProduct.getTrackInventory());
|
||||
|
||||
existingProduct.setIsActive(updatedProduct.getIsActive());
|
||||
existingProduct.setUpdatedAt(LocalDateTime.now());
|
||||
return productRepository.save(existingProduct);
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
package com.kifi.api.service.invoice;
|
||||
|
||||
import com.kifi.api.entity.Transaction;
|
||||
import com.kifi.api.entity.inventory.InventoryItem;
|
||||
import com.kifi.api.entity.invoice.CreditNote;
|
||||
import com.kifi.api.entity.invoice.CreditNoteItem;
|
||||
import com.kifi.api.entity.invoice.Invoice;
|
||||
import com.kifi.api.repository.customer.CustomerRepository;
|
||||
import com.kifi.api.repository.inventory.InventoryItemRepository;
|
||||
import com.kifi.api.repository.invoice.CreditNoteItemRepository;
|
||||
import com.kifi.api.repository.invoice.CreditNoteRepository;
|
||||
import com.kifi.api.repository.invoice.InvoiceItemRepository;
|
||||
import com.kifi.api.repository.invoice.InvoiceRepository;
|
||||
import com.kifi.api.service.TransactionService;
|
||||
import com.kifi.api.service.accounting.LedgerService;
|
||||
import com.kifi.api.service.inventory.InventoryItemService;
|
||||
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.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class CreditNoteService {
|
||||
|
||||
private final CreditNoteRepository creditNoteRepository;
|
||||
private final CreditNoteItemRepository creditNoteItemRepository;
|
||||
private final InvoiceRepository invoiceRepository;
|
||||
private final InvoiceItemRepository invoiceItemRepository;
|
||||
private final InventoryItemRepository inventoryItemRepository;
|
||||
private final InventoryItemService inventoryItemService;
|
||||
private final LedgerService ledgerService;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final TransactionService transactionService;
|
||||
|
||||
public Flux<CreditNote> getCreditNotes(Long userId) {
|
||||
return creditNoteRepository.findByUserId(userId)
|
||||
.flatMap(this::populateItems);
|
||||
}
|
||||
|
||||
public Flux<CreditNote> getCreditNotesForInvoice(Long invoiceId, Long userId) {
|
||||
return creditNoteRepository.findByUserIdAndInvoiceId(userId, invoiceId)
|
||||
.flatMap(this::populateItems);
|
||||
}
|
||||
|
||||
public Mono<CreditNote> getCreditNoteById(Long id, Long userId) {
|
||||
return creditNoteRepository.findById(id)
|
||||
.filter(cn -> cn.getUserId().equals(userId))
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("Credit note not found or unauthorized")))
|
||||
.flatMap(this::populateItems);
|
||||
}
|
||||
|
||||
private Mono<CreditNote> populateItems(CreditNote cn) {
|
||||
return creditNoteItemRepository.findByCreditNoteId(cn.getId())
|
||||
.collectList()
|
||||
.map(items -> {
|
||||
cn.setItems(items);
|
||||
return cn;
|
||||
});
|
||||
}
|
||||
|
||||
public Mono<CreditNote> createCreditNote(Long userId, Long invoiceId, CreditNote creditNote) {
|
||||
return invoiceRepository.findById(invoiceId)
|
||||
.filter(inv -> inv.getUserId().equals(userId))
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("Invoice not found or unauthorized")))
|
||||
.flatMap(invoice -> {
|
||||
creditNote.setUserId(userId);
|
||||
creditNote.setInvoiceId(invoiceId);
|
||||
creditNote.setCustomerId(invoice.getCustomerId());
|
||||
|
||||
if (creditNote.getReturnDate() == null) {
|
||||
creditNote.setReturnDate(LocalDate.now());
|
||||
}
|
||||
if (creditNote.getCreditNoteNumber() == null || creditNote.getCreditNoteNumber().trim().isEmpty()) {
|
||||
creditNote.setCreditNoteNumber("CN-" + System.currentTimeMillis() % 1000000);
|
||||
}
|
||||
creditNote.setCreatedAt(LocalDateTime.now());
|
||||
creditNote.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
if (creditNote.getRefundStatus() == null || creditNote.getRefundStatus().trim().isEmpty()) {
|
||||
creditNote.setRefundStatus("ADJUSTED_TO_AR");
|
||||
}
|
||||
|
||||
// Calculate totals from items if not provided
|
||||
List<CreditNoteItem> items = creditNote.getItems() != null ? creditNote.getItems() : new ArrayList<>();
|
||||
BigDecimal subtotal = BigDecimal.ZERO;
|
||||
BigDecimal taxTotal = BigDecimal.ZERO;
|
||||
BigDecimal cgstTotal = BigDecimal.ZERO;
|
||||
BigDecimal sgstTotal = BigDecimal.ZERO;
|
||||
BigDecimal igstTotal = BigDecimal.ZERO;
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
|
||||
for (CreditNoteItem itm : items) {
|
||||
BigDecimal qty = itm.getWeight() != null && itm.getWeight().compareTo(BigDecimal.ZERO) > 0
|
||||
? itm.getWeight()
|
||||
: (itm.getQuantity() != null ? itm.getQuantity() : BigDecimal.ONE);
|
||||
BigDecimal unitPrice = itm.getUnitPrice() != null ? itm.getUnitPrice() : BigDecimal.ZERO;
|
||||
BigDecimal baseAmt = qty.multiply(unitPrice);
|
||||
BigDecimal making = itm.getMakingCharge() != null ? itm.getMakingCharge() : BigDecimal.ZERO;
|
||||
BigDecimal disc = itm.getDiscount() != null ? itm.getDiscount() : BigDecimal.ZERO;
|
||||
BigDecimal other = itm.getOtherCharges() != null ? itm.getOtherCharges() : BigDecimal.ZERO;
|
||||
|
||||
BigDecimal cgst = itm.getCgst() != null ? itm.getCgst() : BigDecimal.ZERO;
|
||||
BigDecimal sgst = itm.getSgst() != null ? itm.getSgst() : BigDecimal.ZERO;
|
||||
BigDecimal igst = itm.getIgst() != null ? itm.getIgst() : BigDecimal.ZERO;
|
||||
BigDecimal tax = cgst.add(sgst).add(igst);
|
||||
|
||||
BigDecimal lineTotal = baseAmt.add(making).add(other).subtract(disc).add(tax);
|
||||
itm.setTotal(lineTotal);
|
||||
|
||||
subtotal = subtotal.add(baseAmt);
|
||||
taxTotal = taxTotal.add(tax);
|
||||
cgstTotal = cgstTotal.add(cgst);
|
||||
sgstTotal = sgstTotal.add(sgst);
|
||||
igstTotal = igstTotal.add(igst);
|
||||
totalAmount = totalAmount.add(lineTotal);
|
||||
}
|
||||
|
||||
if (creditNote.getSubtotal() == null) creditNote.setSubtotal(subtotal);
|
||||
if (creditNote.getTaxTotal() == null) creditNote.setTaxTotal(taxTotal);
|
||||
if (creditNote.getCgstTotal() == null) creditNote.setCgstTotal(cgstTotal);
|
||||
if (creditNote.getSgstTotal() == null) creditNote.setSgstTotal(sgstTotal);
|
||||
if (creditNote.getIgstTotal() == null) creditNote.setIgstTotal(igstTotal);
|
||||
if (creditNote.getTotalAmount() == null) creditNote.setTotalAmount(totalAmount);
|
||||
|
||||
return creditNoteRepository.save(creditNote)
|
||||
.flatMap(savedCn -> {
|
||||
Mono<List<CreditNoteItem>> itemsMono = Flux.fromIterable(items)
|
||||
.flatMap(item -> {
|
||||
item.setCreditNoteId(savedCn.getId());
|
||||
return creditNoteItemRepository.save(item);
|
||||
})
|
||||
.collectList();
|
||||
|
||||
return itemsMono.flatMap(savedItems -> {
|
||||
savedCn.setItems(savedItems);
|
||||
|
||||
// 1. Stock Restoration (if restockItems is true)
|
||||
Mono<Void> restockMono = Mono.empty();
|
||||
if (Boolean.TRUE.equals(savedCn.getRestockItems())) {
|
||||
restockMono = Flux.fromIterable(savedItems)
|
||||
.flatMap(item -> {
|
||||
// Inward restock back into inventory
|
||||
if (item.getInventoryItemId() != null) {
|
||||
return inventoryItemRepository.findById(item.getInventoryItemId())
|
||||
.flatMap(invItem -> {
|
||||
invItem.setStatus("AVAILABLE");
|
||||
invItem.setSalesRef(null);
|
||||
invItem.setUpdatedAt(LocalDateTime.now());
|
||||
return inventoryItemRepository.save(invItem).then();
|
||||
});
|
||||
} else if (item.getProductId() != null) {
|
||||
BigDecimal restockQty = item.getWeight() != null && item.getWeight().compareTo(BigDecimal.ZERO) > 0
|
||||
? item.getWeight()
|
||||
: (item.getQuantity() != null ? item.getQuantity() : BigDecimal.ONE);
|
||||
InventoryItem restockRecord = InventoryItem.builder()
|
||||
.userId(userId)
|
||||
.productId(item.getProductId())
|
||||
.sku(item.getSku())
|
||||
.huid(item.getHuid())
|
||||
.grossWeight(restockQty)
|
||||
.netWeight(restockQty)
|
||||
.purchaseCost(item.getUnitPrice())
|
||||
.purchaseRef("RETURN-" + savedCn.getCreditNoteNumber())
|
||||
.status("AVAILABLE")
|
||||
.createdAt(LocalDateTime.now())
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
return inventoryItemRepository.save(restockRecord).then();
|
||||
}
|
||||
return Mono.empty();
|
||||
})
|
||||
.then();
|
||||
}
|
||||
|
||||
// 2. Financial / Ledger Accounting Adjustment
|
||||
Mono<Void> accountingMono = Mono.empty();
|
||||
if ("REFUNDED_TO_WALLET".equalsIgnoreCase(savedCn.getRefundStatus()) && savedCn.getRefundWalletId() != null) {
|
||||
Transaction refundTx = Transaction.builder()
|
||||
.userId(userId)
|
||||
.fromWalletId(savedCn.getRefundWalletId()) // Cash/Bank decreases
|
||||
.type("EXPENSE")
|
||||
.amount(savedCn.getTotalAmount())
|
||||
.date(savedCn.getReturnDate())
|
||||
.description("Refund for Return #" + savedCn.getCreditNoteNumber() + " (Invoice #" + invoice.getInvoiceNumber() + ")")
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
accountingMono = transactionService.addTransaction(userId, refundTx).then();
|
||||
} else {
|
||||
// Customer AR adjustment
|
||||
if (invoice.getCustomerId() != null) {
|
||||
accountingMono = customerRepository.findById(invoice.getCustomerId())
|
||||
.flatMap(customer -> Mono.zip(
|
||||
ledgerService.getCustomerLedger(userId, customer.getId(), customer.getName()),
|
||||
ledgerService.getSalesRevenueLedger(userId)
|
||||
).flatMap(ledgers -> {
|
||||
Transaction returnTx = Transaction.builder()
|
||||
.userId(userId)
|
||||
.fromWalletId(ledgers.getT1().getId()) // Customer AR (Asset decreases)
|
||||
.toWalletId(ledgers.getT2().getId()) // Sales Revenue (Income decreases)
|
||||
.type("SALES_RETURN")
|
||||
.amount(savedCn.getTotalAmount())
|
||||
.date(savedCn.getReturnDate())
|
||||
.description("Credit Note #" + savedCn.getCreditNoteNumber() + " for Invoice #" + invoice.getInvoiceNumber())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
return transactionService.addTransaction(userId, returnTx).then();
|
||||
})).onErrorResume(err -> {
|
||||
System.err.println("AR adjustment error: " + err.getMessage());
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Update Invoice Status
|
||||
invoice.setStatus(savedCn.getTotalAmount().compareTo(invoice.getTotalAmount()) >= 0 ? "RETURNED" : "PARTIALLY_RETURNED");
|
||||
if ("RTO_UNDELIVERED".equalsIgnoreCase(savedCn.getReason())) {
|
||||
invoice.setDispatchStatus("RTO");
|
||||
}
|
||||
invoice.setUpdatedAt(LocalDateTime.now());
|
||||
Mono<Invoice> updateInvoiceMono = invoiceRepository.save(invoice);
|
||||
|
||||
return restockMono
|
||||
.then(accountingMono)
|
||||
.then(updateInvoiceMono)
|
||||
.thenReturn(savedCn);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.kifi.api.service.invoice;
|
||||
|
||||
import com.kifi.api.entity.Transaction;
|
||||
import com.kifi.api.entity.invoice.Invoice;
|
||||
import com.kifi.api.entity.invoice.InvoicePayment;
|
||||
import com.kifi.api.entity.invoice.MarketplaceSettlement;
|
||||
import com.kifi.api.repository.customer.CustomerRepository;
|
||||
import com.kifi.api.repository.invoice.InvoicePaymentRepository;
|
||||
import com.kifi.api.repository.invoice.InvoiceRepository;
|
||||
import com.kifi.api.repository.invoice.MarketplaceSettlementRepository;
|
||||
import com.kifi.api.service.TransactionService;
|
||||
import com.kifi.api.service.accounting.LedgerService;
|
||||
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.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional
|
||||
public class MarketplaceSettlementService {
|
||||
|
||||
private final MarketplaceSettlementRepository settlementRepository;
|
||||
private final InvoiceRepository invoiceRepository;
|
||||
private final InvoicePaymentRepository invoicePaymentRepository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final LedgerService ledgerService;
|
||||
private final TransactionService transactionService;
|
||||
|
||||
public Flux<MarketplaceSettlement> getSettlements(Long userId) {
|
||||
return settlementRepository.findByUserId(userId);
|
||||
}
|
||||
|
||||
public Flux<MarketplaceSettlement> getSettlementsForInvoice(Long invoiceId, Long userId) {
|
||||
return settlementRepository.findByUserIdAndInvoiceId(userId, invoiceId);
|
||||
}
|
||||
|
||||
public Mono<MarketplaceSettlement> getSettlementById(Long id, Long userId) {
|
||||
return settlementRepository.findById(id)
|
||||
.filter(s -> s.getUserId().equals(userId))
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("Settlement not found or unauthorized")));
|
||||
}
|
||||
|
||||
public Mono<MarketplaceSettlement> createSettlement(Long userId, Long invoiceId, MarketplaceSettlement settlement) {
|
||||
return invoiceRepository.findById(invoiceId)
|
||||
.filter(inv -> inv.getUserId().equals(userId))
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("Invoice not found or unauthorized")))
|
||||
.flatMap(invoice -> {
|
||||
settlement.setUserId(userId);
|
||||
settlement.setInvoiceId(invoiceId);
|
||||
if (settlement.getSalesChannelId() == null) {
|
||||
settlement.setSalesChannelId(invoice.getSalesChannelId());
|
||||
}
|
||||
if (settlement.getChannelName() == null || settlement.getChannelName().trim().isEmpty()) {
|
||||
settlement.setChannelName(invoice.getSalesChannel() != null ? invoice.getSalesChannel() : "Marketplace");
|
||||
}
|
||||
if (settlement.getSettlementDate() == null) {
|
||||
settlement.setSettlementDate(LocalDate.now());
|
||||
}
|
||||
if (settlement.getGrossAmount() == null) {
|
||||
settlement.setGrossAmount(invoice.getTotalAmount());
|
||||
}
|
||||
if (settlement.getCommissionFee() == null) settlement.setCommissionFee(BigDecimal.ZERO);
|
||||
if (settlement.getShippingFee() == null) settlement.setShippingFee(BigDecimal.ZERO);
|
||||
if (settlement.getOtherFees() == null) settlement.setOtherFees(BigDecimal.ZERO);
|
||||
if (settlement.getFeeGst() == null) settlement.setFeeGst(BigDecimal.ZERO);
|
||||
if (settlement.getTcsGstAmount() == null) settlement.setTcsGstAmount(BigDecimal.ZERO);
|
||||
if (settlement.getTdsAmount() == null) settlement.setTdsAmount(BigDecimal.ZERO);
|
||||
|
||||
BigDecimal totalDeductions = settlement.getCommissionFee()
|
||||
.add(settlement.getShippingFee())
|
||||
.add(settlement.getOtherFees())
|
||||
.add(settlement.getFeeGst())
|
||||
.add(settlement.getTcsGstAmount())
|
||||
.add(settlement.getTdsAmount());
|
||||
settlement.setTotalDeductions(totalDeductions);
|
||||
|
||||
BigDecimal netPayout = settlement.getGrossAmount().subtract(totalDeductions);
|
||||
settlement.setNetPayoutAmount(netPayout);
|
||||
|
||||
settlement.setStatus("SETTLED");
|
||||
settlement.setCreatedAt(LocalDateTime.now());
|
||||
settlement.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
return settlementRepository.save(settlement)
|
||||
.flatMap(savedSettlement -> {
|
||||
// 1. Record Bank Inflow Transaction
|
||||
Mono<Void> txMono = Mono.empty();
|
||||
if (savedSettlement.getPayoutWalletId() != null && netPayout.compareTo(BigDecimal.ZERO) > 0) {
|
||||
Transaction netDepositTx = Transaction.builder()
|
||||
.userId(userId)
|
||||
.toWalletId(savedSettlement.getPayoutWalletId()) // Cash/Bank increases
|
||||
.type("INCOME")
|
||||
.amount(netPayout)
|
||||
.date(savedSettlement.getSettlementDate())
|
||||
.description("Payout for " + savedSettlement.getChannelName() + " Order #" + (invoice.getMarketplaceOrderId() != null ? invoice.getMarketplaceOrderId() : invoice.getInvoiceNumber()) + " (Ref: " + (savedSettlement.getSettlementRef() != null ? savedSettlement.getSettlementRef() : "Settlement") + ")")
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
txMono = transactionService.addTransaction(userId, netDepositTx).then();
|
||||
}
|
||||
|
||||
// 2. Record Invoice Payment (Clears Invoice Receivables)
|
||||
InvoicePayment payment = InvoicePayment.builder()
|
||||
.invoiceId(invoiceId)
|
||||
.amount(invoice.getTotalAmount())
|
||||
.paymentDate(savedSettlement.getSettlementDate())
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
Mono<InvoicePayment> paymentMono = invoicePaymentRepository.save(payment);
|
||||
|
||||
// 3. Mark Invoice as PAID / SETTLED
|
||||
invoice.setStatus("PAID");
|
||||
invoice.setUpdatedAt(LocalDateTime.now());
|
||||
Mono<Invoice> updateInvoiceMono = invoiceRepository.save(invoice);
|
||||
|
||||
return txMono
|
||||
.then(paymentMono)
|
||||
.then(updateInvoiceMono)
|
||||
.thenReturn(savedSettlement);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -272,6 +272,7 @@ CREATE TABLE IF NOT EXISTS products (
|
||||
wastage_percentage DECIMAL(5, 2) DEFAULT 0.0,
|
||||
min_stock DECIMAL(10, 2) DEFAULT 0,
|
||||
reorder_level DECIMAL(10, 2) DEFAULT 0,
|
||||
reorder_quantity DECIMAL(10, 2) DEFAULT 0,
|
||||
track_inventory BOOLEAN DEFAULT TRUE,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -495,6 +496,76 @@ CREATE TABLE IF NOT EXISTS invoice_payments (
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS credit_notes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
invoice_id INTEGER REFERENCES invoices(id) ON DELETE CASCADE,
|
||||
credit_note_number VARCHAR(100) NOT NULL,
|
||||
customer_id INTEGER REFERENCES customers(id) ON DELETE SET NULL,
|
||||
return_date DATE NOT NULL,
|
||||
reason VARCHAR(100) NOT NULL,
|
||||
restock_items BOOLEAN DEFAULT TRUE,
|
||||
subtotal DECIMAL(15, 2) NOT NULL,
|
||||
tax_total DECIMAL(15, 2) DEFAULT 0.0,
|
||||
cgst_total DECIMAL(15, 2) DEFAULT 0.0,
|
||||
sgst_total DECIMAL(15, 2) DEFAULT 0.0,
|
||||
igst_total DECIMAL(15, 2) DEFAULT 0.0,
|
||||
discount_total DECIMAL(15, 2) DEFAULT 0.0,
|
||||
total_amount DECIMAL(15, 2) NOT NULL,
|
||||
refund_status VARCHAR(50) DEFAULT 'ADJUSTED_TO_AR',
|
||||
refund_wallet_id INTEGER REFERENCES wallets(id) ON DELETE SET NULL,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS credit_note_items (
|
||||
id SERIAL PRIMARY KEY,
|
||||
credit_note_id INTEGER REFERENCES credit_notes(id) ON DELETE CASCADE,
|
||||
invoice_item_id INTEGER REFERENCES invoice_items(id) ON DELETE SET NULL,
|
||||
product_id INTEGER REFERENCES products(id) ON DELETE SET NULL,
|
||||
inventory_item_id INTEGER REFERENCES inventory_items(id) ON DELETE SET NULL,
|
||||
product_name VARCHAR(255),
|
||||
sku VARCHAR(100),
|
||||
huid VARCHAR(50),
|
||||
hsn_code VARCHAR(50),
|
||||
quantity DECIMAL(10, 3) NOT NULL,
|
||||
weight DECIMAL(10, 3),
|
||||
unit_price DECIMAL(15, 2) NOT NULL,
|
||||
tax_rate DECIMAL(5, 2) DEFAULT 0.0,
|
||||
cgst DECIMAL(15, 2) DEFAULT 0.0,
|
||||
sgst DECIMAL(15, 2) DEFAULT 0.0,
|
||||
igst DECIMAL(15, 2) DEFAULT 0.0,
|
||||
discount DECIMAL(15, 2) DEFAULT 0.0,
|
||||
making_charge DECIMAL(15, 2) DEFAULT 0.0,
|
||||
other_charges DECIMAL(15, 2) DEFAULT 0.0,
|
||||
total DECIMAL(15, 2) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS marketplace_settlements (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
invoice_id INTEGER REFERENCES invoices(id) ON DELETE CASCADE,
|
||||
sales_channel_id INTEGER REFERENCES sales_channels(id) ON DELETE SET NULL,
|
||||
channel_name VARCHAR(100),
|
||||
settlement_ref VARCHAR(100),
|
||||
settlement_date DATE NOT NULL,
|
||||
gross_amount DECIMAL(15, 2) NOT NULL,
|
||||
commission_fee DECIMAL(15, 2) DEFAULT 0.0,
|
||||
shipping_fee DECIMAL(15, 2) DEFAULT 0.0,
|
||||
other_fees DECIMAL(15, 2) DEFAULT 0.0,
|
||||
fee_gst DECIMAL(15, 2) DEFAULT 0.0,
|
||||
tcs_gst_amount DECIMAL(15, 2) DEFAULT 0.0,
|
||||
tds_amount DECIMAL(15, 2) DEFAULT 0.0,
|
||||
total_deductions DECIMAL(15, 2) NOT NULL,
|
||||
net_payout_amount DECIMAL(15, 2) NOT NULL,
|
||||
payout_wallet_id INTEGER REFERENCES wallets(id) ON DELETE SET NULL,
|
||||
status VARCHAR(50) DEFAULT 'SETTLED',
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 9. Projects & Task Management
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id SERIAL PRIMARY KEY,
|
||||
|
||||
Reference in New Issue
Block a user