Product Add Done
This commit is contained in:
20
kifi-api/build_n_push_v2.sh
Executable file
20
kifi-api/build_n_push_v2.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
REGISTRY="hub.technobeesolutions.in"
|
||||
USERNAME="technobee_admin"
|
||||
PASSWORD='M@tr!x#149@dm!N'
|
||||
IMAGE_NAME="kifi-api"
|
||||
TAG="kifi-one"
|
||||
FULL_IMAGE_NAME="$REGISTRY/$IMAGE_NAME:$TAG"
|
||||
|
||||
# Stop on any error
|
||||
set -e
|
||||
|
||||
echo "Logging into Docker registry: $REGISTRY..."
|
||||
echo "$PASSWORD" | docker login "$REGISTRY" -u "$USERNAME" --password-stdin
|
||||
|
||||
echo "Building and pushing Docker image for linux/amd64: $FULL_IMAGE_NAME..."
|
||||
docker buildx build --platform linux/amd64 -t "$FULL_IMAGE_NAME" --push .
|
||||
|
||||
echo "Done! Image built and pushed successfully."
|
||||
@@ -45,8 +45,8 @@ public class SecurityConfig {
|
||||
.securityContextRepository(securityContextRepository)
|
||||
.authorizeExchange(exchange -> exchange
|
||||
.pathMatchers(HttpMethod.OPTIONS).permitAll()
|
||||
.pathMatchers("/api/kifi/auth/**").permitAll()
|
||||
.pathMatchers("/api/kifi/health/**").permitAll()
|
||||
.pathMatchers("/api/kifi-v2/auth/**").permitAll()
|
||||
.pathMatchers("/api/kifi-v2/health/**").permitAll()
|
||||
.anyExchange().authenticated()
|
||||
)
|
||||
.build();
|
||||
|
||||
@@ -13,7 +13,7 @@ import reactor.core.publisher.Mono;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi/auth")
|
||||
@RequestMapping("/api/kifi-v2/auth")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthController {
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import reactor.core.publisher.Mono;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi/budgets")
|
||||
@RequestMapping("/api/kifi-v2/budgets")
|
||||
@RequiredArgsConstructor
|
||||
public class BudgetController {
|
||||
private final BudgetService budgetService;
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi/categories")
|
||||
@RequestMapping("/api/kifi-v2/categories")
|
||||
@RequiredArgsConstructor
|
||||
public class CategoryController {
|
||||
private final CategoryService categoryService;
|
||||
|
||||
@@ -8,7 +8,7 @@ import reactor.core.publisher.Mono;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi/health")
|
||||
@RequestMapping("/api/kifi-v2/health")
|
||||
public class HealthController {
|
||||
|
||||
@GetMapping
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.List;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi/recurring-transactions")
|
||||
@RequestMapping("/api/kifi-v2/recurring-transactions")
|
||||
@RequiredArgsConstructor
|
||||
public class RecurringTransactionController {
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi/reports")
|
||||
@RequestMapping("/api/kifi-v2/reports")
|
||||
@RequiredArgsConstructor
|
||||
public class ReportController {
|
||||
private final ReportService reportService;
|
||||
|
||||
@@ -15,7 +15,7 @@ import java.util.List;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi/transactions")
|
||||
@RequestMapping("/api/kifi-v2/transactions")
|
||||
@RequiredArgsConstructor
|
||||
public class TransactionController {
|
||||
private final TransactionService transactionService;
|
||||
|
||||
@@ -13,7 +13,7 @@ import reactor.core.publisher.Mono;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi/wallets")
|
||||
@RequestMapping("/api/kifi-v2/wallets")
|
||||
@RequiredArgsConstructor
|
||||
public class WalletController {
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.kifi.api.controller.business;
|
||||
|
||||
import com.kifi.api.entity.business.BusinessProfile;
|
||||
import com.kifi.api.service.business.BusinessService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi-v2/business")
|
||||
@RequiredArgsConstructor
|
||||
public class BusinessController {
|
||||
private final BusinessService businessService;
|
||||
|
||||
// TODO: Obtain user ID from auth context, using hardcoded for now or parameter
|
||||
@GetMapping("/profile")
|
||||
public Mono<ResponseEntity<BusinessProfile>> getProfile(Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return businessService.getProfileByUserId(userId)
|
||||
.map(ResponseEntity::ok)
|
||||
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping("/profile")
|
||||
public Mono<ResponseEntity<BusinessProfile>> saveProfile(Authentication authentication, @RequestBody BusinessProfile profile) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return businessService.saveProfile(userId, profile)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.kifi.api.controller.inventory;
|
||||
|
||||
import com.kifi.api.dto.inventory.MovementRequestDto;
|
||||
import com.kifi.api.entity.inventory.InventoryMovement;
|
||||
import com.kifi.api.service.inventory.InventoryMovementService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi-v2/inventory/movements")
|
||||
@RequiredArgsConstructor
|
||||
public class InventoryMovementController {
|
||||
private final InventoryMovementService movementService;
|
||||
|
||||
@PostMapping
|
||||
public Mono<ResponseEntity<InventoryMovement>> recordMovement(Authentication authentication, @RequestBody MovementRequestDto request) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return movementService.recordMovement(userId, request.getMovement(), request.getItems())
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.kifi.api.controller.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.ProductCategory;
|
||||
import com.kifi.api.repository.inventory.ProductCategoryRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi-v2/inventory/categories")
|
||||
@RequiredArgsConstructor
|
||||
public class ProductCategoryController {
|
||||
private final ProductCategoryRepository categoryRepository;
|
||||
|
||||
@GetMapping
|
||||
public Flux<ProductCategory> getCategories(Authentication authentication) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return categoryRepository.findByUserId(userId);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Mono<ResponseEntity<ProductCategory>> createCategory(Authentication authentication, @RequestBody ProductCategory category) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
category.setUserId(userId);
|
||||
category.setCreatedAt(LocalDateTime.now());
|
||||
return categoryRepository.save(category)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public Mono<ResponseEntity<ProductCategory>> updateCategory(@PathVariable Long id, @RequestBody ProductCategory category) {
|
||||
return categoryRepository.findById(id)
|
||||
.flatMap(existing -> {
|
||||
existing.setName(category.getName());
|
||||
existing.setParentCategoryId(category.getParentCategoryId());
|
||||
existing.setIsCommodity(category.getIsCommodity());
|
||||
existing.setDailyRate(category.getDailyRate());
|
||||
return categoryRepository.save(existing);
|
||||
})
|
||||
.map(ResponseEntity::ok)
|
||||
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.kifi.api.controller.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.Product;
|
||||
import com.kifi.api.entity.inventory.ProductImage;
|
||||
import com.kifi.api.service.inventory.ProductService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import org.springframework.http.codec.multipart.FilePart;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import java.util.Base64;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/kifi-v2/inventory/products")
|
||||
@RequiredArgsConstructor
|
||||
public class ProductController {
|
||||
private final ProductService productService;
|
||||
|
||||
@GetMapping
|
||||
public Flux<Product> getProducts(
|
||||
Authentication authentication,
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size
|
||||
) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return productService.getProductsByUserId(userId, page, size);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public Mono<ResponseEntity<Product>> createProduct(Authentication authentication, @RequestBody Product product) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return productService.createProduct(userId, product)
|
||||
.map(ResponseEntity::ok);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public Mono<ResponseEntity<Product>> updateProduct(
|
||||
Authentication authentication,
|
||||
@PathVariable Long id,
|
||||
@RequestBody Product product
|
||||
) {
|
||||
Long userId = Long.valueOf(authentication.getDetails().toString());
|
||||
return productService.updateProduct(userId, id, product)
|
||||
.map(ResponseEntity::ok)
|
||||
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||
}
|
||||
|
||||
@PostMapping(value = "/{id}/images", consumes = org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public Mono<ProductImage> addImage(@PathVariable Long id, @RequestPart("file") FilePart filePart) {
|
||||
return DataBufferUtils.join(filePart.content())
|
||||
.flatMap(dataBuffer -> {
|
||||
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(bytes);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
String base64Content = Base64.getEncoder().encodeToString(bytes);
|
||||
String fileName = filePart.filename();
|
||||
String contentType = filePart.headers().getContentType() != null ? filePart.headers().getContentType().toString() : "application/octet-stream";
|
||||
return productService.addProductImage(id, fileName, contentType, base64Content);
|
||||
});
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/images/{imageId}/content")
|
||||
public Mono<ResponseEntity<byte[]>> downloadImage(@PathVariable Long id, @PathVariable Long imageId) {
|
||||
return productService.downloadProductImage(imageId)
|
||||
.map(response -> {
|
||||
byte[] decodedBytes = Base64.getDecoder().decode(response.getBase64Content());
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_TYPE, "image/jpeg")
|
||||
.body(decodedBytes);
|
||||
});
|
||||
}
|
||||
|
||||
@DeleteMapping("/images/{imageId}")
|
||||
public Mono<Void> deleteImage(@PathVariable Long imageId) {
|
||||
return productService.deleteProductImage(imageId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.kifi.api.dto.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.InventoryMovement;
|
||||
import com.kifi.api.entity.inventory.InventoryMovementItem;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class MovementRequestDto {
|
||||
private InventoryMovement movement;
|
||||
private List<InventoryMovementItem> items;
|
||||
}
|
||||
@@ -21,6 +21,8 @@ public class User {
|
||||
private String password;
|
||||
@Builder.Default
|
||||
private Boolean enabled = false;
|
||||
@Builder.Default
|
||||
private String profileType = "INDIVIDUAL";
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kifi.api.entity.business;
|
||||
|
||||
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.Table;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("business_features")
|
||||
public class BusinessFeature {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Boolean inventoryManagement;
|
||||
private Boolean salesManagement;
|
||||
private Boolean multiLocation;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.kifi.api.entity.business;
|
||||
|
||||
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.Table;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("business_profiles")
|
||||
public class BusinessProfile {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private String businessName;
|
||||
private String industry;
|
||||
private String taxNumber;
|
||||
private String currency;
|
||||
private Boolean taxIncludedInPrice;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kifi.api.entity.inventory;
|
||||
|
||||
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.Table;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("inventory_balances")
|
||||
public class InventoryBalance {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long productId;
|
||||
private Long locationId;
|
||||
private BigDecimal quantity;
|
||||
private LocalDateTime lastUpdated;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kifi.api.entity.inventory;
|
||||
|
||||
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.Table;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("inventory_locations")
|
||||
public class InventoryLocation {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private String name;
|
||||
private String address;
|
||||
private Boolean isPrimary;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.kifi.api.entity.inventory;
|
||||
|
||||
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.Table;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("inventory_movements")
|
||||
public class InventoryMovement {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Long locationId;
|
||||
private String type;
|
||||
private Long referenceTransactionId;
|
||||
private String notes;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.kifi.api.entity.inventory;
|
||||
|
||||
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.Table;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("inventory_movement_items")
|
||||
public class InventoryMovementItem {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long movementId;
|
||||
private Long productId;
|
||||
private BigDecimal quantity;
|
||||
private BigDecimal unitPrice;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.kifi.api.entity.inventory;
|
||||
|
||||
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.Table;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("products")
|
||||
public class Product {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private Long categoryId;
|
||||
private Long uomId;
|
||||
private String name;
|
||||
private String sku;
|
||||
private String barcode;
|
||||
private String description;
|
||||
private BigDecimal purchasePrice;
|
||||
private BigDecimal sellingPrice;
|
||||
private BigDecimal gstRate;
|
||||
private String dimensions;
|
||||
private BigDecimal weight;
|
||||
private String color;
|
||||
private String size;
|
||||
private String priceCalcRule;
|
||||
private Boolean autoCalculatePrice;
|
||||
private Double purityFactor;
|
||||
private Double makingCharges;
|
||||
private String makingChargesType;
|
||||
private Double wastagePercentage;
|
||||
private BigDecimal minStock;
|
||||
private BigDecimal reorderLevel;
|
||||
private Boolean trackInventory;
|
||||
private Boolean isActive;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@Transient
|
||||
private List<ProductImage> images;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kifi.api.entity.inventory;
|
||||
|
||||
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.Table;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("product_bom")
|
||||
public class ProductBom {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long parentProductId;
|
||||
private Long componentProductId;
|
||||
private BigDecimal quantity;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.kifi.api.entity.inventory;
|
||||
|
||||
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.Table;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("product_categories")
|
||||
public class ProductCategory {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private String name;
|
||||
private Long parentCategoryId;
|
||||
private Boolean isCommodity;
|
||||
private String calculationMethod;
|
||||
private String baseUnit;
|
||||
private Double dailyRate;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.kifi.api.entity.inventory;
|
||||
|
||||
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.Table;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("product_images")
|
||||
public class ProductImage {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long productId;
|
||||
private String fileName;
|
||||
private String filePath;
|
||||
private String contentType;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.kifi.api.entity.inventory;
|
||||
|
||||
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.Table;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table("units_of_measure")
|
||||
public class UnitOfMeasure {
|
||||
@Id
|
||||
private Long id;
|
||||
private Long userId;
|
||||
private String name;
|
||||
private String abbreviation;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository.business;
|
||||
|
||||
import com.kifi.api.entity.business.BusinessFeature;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface BusinessFeatureRepository extends ReactiveCrudRepository<BusinessFeature, Long> {
|
||||
Mono<BusinessFeature> findByUserId(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository.business;
|
||||
|
||||
import com.kifi.api.entity.business.BusinessProfile;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public interface BusinessProfileRepository extends ReactiveCrudRepository<BusinessProfile, Long> {
|
||||
Mono<BusinessProfile> findByUserId(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.kifi.api.repository.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.InventoryBalance;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface InventoryBalanceRepository extends ReactiveCrudRepository<InventoryBalance, Long> {
|
||||
Mono<InventoryBalance> findByProductIdAndLocationId(Long productId, Long locationId);
|
||||
Flux<InventoryBalance> findByLocationId(Long locationId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.InventoryLocation;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface InventoryLocationRepository extends ReactiveCrudRepository<InventoryLocation, Long> {
|
||||
Flux<InventoryLocation> findByUserId(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.InventoryMovementItem;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface InventoryMovementItemRepository extends ReactiveCrudRepository<InventoryMovementItem, Long> {
|
||||
Flux<InventoryMovementItem> findByMovementId(Long movementId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.InventoryMovement;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface InventoryMovementRepository extends ReactiveCrudRepository<InventoryMovement, Long> {
|
||||
Flux<InventoryMovement> findByUserId(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.ProductBom;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface ProductBomRepository extends ReactiveCrudRepository<ProductBom, Long> {
|
||||
Flux<ProductBom> findByParentProductId(Long parentProductId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.ProductCategory;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface ProductCategoryRepository extends ReactiveCrudRepository<ProductCategory, Long> {
|
||||
Flux<ProductCategory> findByUserId(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.ProductImage;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface ProductImageRepository extends ReactiveCrudRepository<ProductImage, Long> {
|
||||
Flux<ProductImage> findByProductId(Long productId);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kifi.api.repository.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.Product;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface ProductRepository extends ReactiveCrudRepository<Product, Long> {
|
||||
Flux<Product> findByUserId(Long userId, Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.kifi.api.repository.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.UnitOfMeasure;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
public interface UnitOfMeasureRepository extends ReactiveCrudRepository<UnitOfMeasure, Long> {
|
||||
Flux<UnitOfMeasure> findByUserId(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.kifi.api.service.business;
|
||||
|
||||
import com.kifi.api.entity.business.BusinessProfile;
|
||||
import com.kifi.api.repository.business.BusinessProfileRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class BusinessService {
|
||||
private final BusinessProfileRepository businessProfileRepository;
|
||||
|
||||
public Mono<BusinessProfile> getProfileByUserId(Long userId) {
|
||||
return businessProfileRepository.findByUserId(userId);
|
||||
}
|
||||
|
||||
public Mono<BusinessProfile> saveProfile(Long userId, BusinessProfile profile) {
|
||||
return businessProfileRepository.findByUserId(userId)
|
||||
.flatMap(existing -> {
|
||||
existing.setBusinessName(profile.getBusinessName());
|
||||
existing.setIndustry(profile.getIndustry());
|
||||
existing.setTaxNumber(profile.getTaxNumber());
|
||||
existing.setCurrency(profile.getCurrency() != null ? profile.getCurrency() : existing.getCurrency());
|
||||
existing.setUpdatedAt(LocalDateTime.now());
|
||||
return businessProfileRepository.save(existing);
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
profile.setUserId(userId);
|
||||
profile.setCreatedAt(LocalDateTime.now());
|
||||
profile.setUpdatedAt(LocalDateTime.now());
|
||||
return businessProfileRepository.save(profile);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.kifi.api.service.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.InventoryBalance;
|
||||
import com.kifi.api.entity.inventory.InventoryMovement;
|
||||
import com.kifi.api.entity.inventory.InventoryMovementItem;
|
||||
import com.kifi.api.repository.inventory.InventoryBalanceRepository;
|
||||
import com.kifi.api.repository.inventory.InventoryMovementItemRepository;
|
||||
import com.kifi.api.repository.inventory.InventoryMovementRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class InventoryMovementService {
|
||||
private final InventoryMovementRepository movementRepository;
|
||||
private final InventoryMovementItemRepository itemRepository;
|
||||
private final InventoryBalanceRepository balanceRepository;
|
||||
|
||||
@Transactional
|
||||
public Mono<InventoryMovement> recordMovement(Long userId, InventoryMovement movement, List<InventoryMovementItem> items) {
|
||||
movement.setUserId(userId);
|
||||
movement.setCreatedAt(LocalDateTime.now());
|
||||
|
||||
return movementRepository.save(movement)
|
||||
.flatMap(savedMovement -> {
|
||||
return reactor.core.publisher.Flux.fromIterable(items)
|
||||
.flatMap(item -> {
|
||||
item.setMovementId(savedMovement.getId());
|
||||
item.setCreatedAt(LocalDateTime.now());
|
||||
return itemRepository.save(item)
|
||||
.then(updateBalance(item.getProductId(), savedMovement.getLocationId(), item.getQuantity(), savedMovement.getType()));
|
||||
})
|
||||
.then(Mono.just(savedMovement));
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<InventoryBalance> updateBalance(Long productId, Long locationId, BigDecimal quantity, String type) {
|
||||
BigDecimal signedQuantity = quantity;
|
||||
if ("REDUCTION".equals(type) || "ADJUSTMENT".equals(type)) { // Assuming adjustment is absolute or negative handled elsewhere, for now treat reduction as negative
|
||||
// we'll refine this later
|
||||
if("REDUCTION".equals(type)) {
|
||||
signedQuantity = quantity.negate();
|
||||
}
|
||||
}
|
||||
|
||||
final BigDecimal finalQty = signedQuantity;
|
||||
|
||||
return balanceRepository.findByProductIdAndLocationId(productId, locationId)
|
||||
.flatMap(balance -> {
|
||||
balance.setQuantity(balance.getQuantity().add(finalQty));
|
||||
balance.setLastUpdated(LocalDateTime.now());
|
||||
return balanceRepository.save(balance);
|
||||
})
|
||||
.switchIfEmpty(Mono.defer(() -> {
|
||||
InventoryBalance newBalance = new InventoryBalance();
|
||||
newBalance.setProductId(productId);
|
||||
newBalance.setLocationId(locationId);
|
||||
newBalance.setQuantity(finalQty);
|
||||
newBalance.setLastUpdated(LocalDateTime.now());
|
||||
return balanceRepository.save(newBalance);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.kifi.api.service.inventory;
|
||||
|
||||
import com.kifi.api.entity.inventory.Product;
|
||||
import com.kifi.api.entity.inventory.ProductImage;
|
||||
import com.kifi.api.repository.inventory.ProductRepository;
|
||||
import com.kifi.api.repository.inventory.ProductImageRepository;
|
||||
import com.kifi.api.service.MinioServiceClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import java.util.UUID;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ProductService {
|
||||
private final ProductRepository productRepository;
|
||||
private final ProductImageRepository productImageRepository;
|
||||
private final MinioServiceClient minioServiceClient;
|
||||
|
||||
public Flux<Product> getProductsByUserId(Long userId, int page, int size) {
|
||||
return productRepository.findByUserId(userId, PageRequest.of(page, size))
|
||||
.flatMap(product -> productImageRepository.findByProductId(product.getId()).collectList()
|
||||
.map(images -> {
|
||||
product.setImages(images);
|
||||
return product;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public Mono<Product> createProduct(Long userId, Product product) {
|
||||
product.setUserId(userId);
|
||||
product.setCreatedAt(LocalDateTime.now());
|
||||
product.setUpdatedAt(LocalDateTime.now());
|
||||
if (product.getIsActive() == null) product.setIsActive(true);
|
||||
if (product.getTrackInventory() == null) product.setTrackInventory(true);
|
||||
if (product.getPriceCalcRule() == null) product.setPriceCalcRule("MANUAL");
|
||||
if (product.getAutoCalculatePrice() == null) product.setAutoCalculatePrice(false);
|
||||
return productRepository.save(product);
|
||||
}
|
||||
|
||||
public Mono<Product> updateProduct(Long userId, Long productId, Product updatedProduct) {
|
||||
return productRepository.findById(productId)
|
||||
.filter(p -> p.getUserId().equals(userId))
|
||||
.flatMap(existingProduct -> {
|
||||
existingProduct.setName(updatedProduct.getName());
|
||||
existingProduct.setSku(updatedProduct.getSku());
|
||||
existingProduct.setCategoryId(updatedProduct.getCategoryId());
|
||||
existingProduct.setPurchasePrice(updatedProduct.getPurchasePrice());
|
||||
existingProduct.setSellingPrice(updatedProduct.getSellingPrice());
|
||||
existingProduct.setGstRate(updatedProduct.getGstRate());
|
||||
existingProduct.setWeight(updatedProduct.getWeight());
|
||||
existingProduct.setColor(updatedProduct.getColor());
|
||||
existingProduct.setSize(updatedProduct.getSize());
|
||||
existingProduct.setDimensions(updatedProduct.getDimensions());
|
||||
existingProduct.setPriceCalcRule(updatedProduct.getPriceCalcRule());
|
||||
existingProduct.setAutoCalculatePrice(updatedProduct.getAutoCalculatePrice());
|
||||
existingProduct.setPurityFactor(updatedProduct.getPurityFactor());
|
||||
existingProduct.setMakingCharges(updatedProduct.getMakingCharges());
|
||||
existingProduct.setMakingChargesType(updatedProduct.getMakingChargesType());
|
||||
existingProduct.setWastagePercentage(updatedProduct.getWastagePercentage());
|
||||
existingProduct.setMinStock(updatedProduct.getMinStock());
|
||||
existingProduct.setReorderLevel(updatedProduct.getReorderLevel());
|
||||
existingProduct.setTrackInventory(updatedProduct.getTrackInventory());
|
||||
existingProduct.setIsActive(updatedProduct.getIsActive());
|
||||
existingProduct.setUpdatedAt(LocalDateTime.now());
|
||||
return productRepository.save(existingProduct);
|
||||
});
|
||||
}
|
||||
|
||||
public Mono<ProductImage> addProductImage(Long productId, String fileName, String contentType, String base64Content) {
|
||||
String uniqueFileName = UUID.randomUUID().toString() + "_" + fileName;
|
||||
String directoryPath = "products/" + productId;
|
||||
|
||||
return minioServiceClient.uploadFile(directoryPath, contentType, uniqueFileName, base64Content)
|
||||
.flatMap(minioResponse -> {
|
||||
if (minioResponse.isSuccess()) {
|
||||
ProductImage image = ProductImage.builder()
|
||||
.productId(productId)
|
||||
.fileName(fileName)
|
||||
.filePath(minioResponse.getFilePath())
|
||||
.contentType(contentType)
|
||||
.createdAt(LocalDateTime.now())
|
||||
.build();
|
||||
return productImageRepository.save(image);
|
||||
} else {
|
||||
return Mono.error(new RuntimeException("Failed to upload product image to MinIO"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Mono<Void> deleteProductImage(Long imageId) {
|
||||
return productImageRepository.findById(imageId)
|
||||
.flatMap(image ->
|
||||
minioServiceClient.deleteFile(image.getContentType(), image.getFilePath())
|
||||
.then(productImageRepository.delete(image))
|
||||
);
|
||||
}
|
||||
|
||||
public Mono<MinioServiceClient.MinioDownloadResponse> downloadProductImage(Long imageId) {
|
||||
return productImageRepository.findById(imageId)
|
||||
.switchIfEmpty(Mono.error(new RuntimeException("Product image not found")))
|
||||
.flatMap(image ->
|
||||
minioServiceClient.downloadFile(image.getContentType(), image.getFilePath())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ spring:
|
||||
name: kifi-api
|
||||
|
||||
r2dbc:
|
||||
url: r2dbc:postgresql://103.125.129.116:5333/kifi
|
||||
url: r2dbc:postgresql://103.125.129.116:5333/kifi-v2
|
||||
username: postgres
|
||||
password: M@triXPostgr3s@6202
|
||||
pool:
|
||||
|
||||
@@ -132,3 +132,134 @@ CREATE TABLE IF NOT EXISTS wallet_invitations (
|
||||
status VARCHAR(20) DEFAULT 'PENDING',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- KIFI V2 PHASE 1 MIGRATION: INVENTORY MANAGEMENT --
|
||||
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS profile_type VARCHAR(20) DEFAULT 'INDIVIDUAL';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS business_profiles (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
business_name VARCHAR(255) NOT NULL,
|
||||
industry VARCHAR(100),
|
||||
tax_number VARCHAR(100),
|
||||
currency VARCHAR(10) DEFAULT 'INR',
|
||||
tax_included_in_price BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS business_features (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
inventory_management BOOLEAN DEFAULT FALSE,
|
||||
sales_management BOOLEAN DEFAULT FALSE,
|
||||
multi_location BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS product_categories (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
parent_category_id INTEGER REFERENCES product_categories(id),
|
||||
is_commodity BOOLEAN DEFAULT FALSE,
|
||||
calculation_method VARCHAR(50) DEFAULT 'UNIT',
|
||||
base_unit VARCHAR(50) DEFAULT 'pcs',
|
||||
daily_rate DECIMAL(15, 2),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS units_of_measure (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
abbreviation VARCHAR(20),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS products (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
category_id INTEGER REFERENCES product_categories(id),
|
||||
uom_id INTEGER REFERENCES units_of_measure(id),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
sku VARCHAR(100),
|
||||
barcode VARCHAR(100),
|
||||
description TEXT,
|
||||
purchase_price DECIMAL(15, 2),
|
||||
selling_price DECIMAL(15, 2),
|
||||
gst_rate DECIMAL(5, 2),
|
||||
dimensions VARCHAR(100),
|
||||
weight DECIMAL(10, 3),
|
||||
color VARCHAR(50),
|
||||
size VARCHAR(50),
|
||||
price_calc_rule VARCHAR(50) DEFAULT 'MANUAL',
|
||||
auto_calculate_price BOOLEAN DEFAULT FALSE,
|
||||
purity_factor DECIMAL(5, 4) DEFAULT 1.0,
|
||||
making_charges DECIMAL(15, 2) DEFAULT 0.0,
|
||||
making_charges_type VARCHAR(50) DEFAULT 'FLAT',
|
||||
wastage_percentage DECIMAL(5, 2) DEFAULT 0.0,
|
||||
min_stock DECIMAL(10, 2) DEFAULT 0,
|
||||
reorder_level DECIMAL(10, 2) DEFAULT 0,
|
||||
track_inventory BOOLEAN DEFAULT TRUE,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS product_images (
|
||||
id SERIAL PRIMARY KEY,
|
||||
product_id INTEGER REFERENCES products(id) ON DELETE CASCADE,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
file_path VARCHAR(1024) NOT NULL,
|
||||
content_type VARCHAR(100),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS product_bom (
|
||||
id SERIAL PRIMARY KEY,
|
||||
parent_product_id INTEGER REFERENCES products(id) ON DELETE CASCADE,
|
||||
component_product_id INTEGER REFERENCES products(id) ON DELETE RESTRICT,
|
||||
quantity DECIMAL(10, 3) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inventory_locations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
address TEXT,
|
||||
is_primary BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inventory_movements (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||||
location_id INTEGER REFERENCES inventory_locations(id),
|
||||
type VARCHAR(50) NOT NULL, -- ADDITION, REDUCTION, TRANSFER, ADJUSTMENT
|
||||
reference_transaction_id INTEGER REFERENCES transactions(id),
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inventory_movement_items (
|
||||
id SERIAL PRIMARY KEY,
|
||||
movement_id INTEGER REFERENCES inventory_movements(id) ON DELETE CASCADE,
|
||||
product_id INTEGER REFERENCES products(id),
|
||||
quantity DECIMAL(10, 2) NOT NULL,
|
||||
unit_price DECIMAL(10, 2),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inventory_balances (
|
||||
id SERIAL PRIMARY KEY,
|
||||
product_id INTEGER REFERENCES products(id) ON DELETE CASCADE,
|
||||
location_id INTEGER REFERENCES inventory_locations(id) ON DELETE CASCADE,
|
||||
quantity DECIMAL(10, 2) DEFAULT 0,
|
||||
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(product_id, location_id)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user