Feat[BE-296]: adjust schema db and entity products, product_warehouse, stock_logs

This commit is contained in:
giovanni-ce
2025-12-03 18:43:03 +07:00
parent fa5609c183
commit 94fc9219af
17 changed files with 258 additions and 94 deletions
@@ -0,0 +1,2 @@
ALTER TABLE products
DROP COLUMN IF EXISTS is_visible;
@@ -0,0 +1,2 @@
ALTER TABLE products
ADD COLUMN IF NOT EXISTS is_visible BOOLEAN NOT NULL DEFAULT TRUE;
@@ -0,0 +1,35 @@
BEGIN;
-- Drop new indexes and FK
DROP INDEX IF EXISTS idx_product_warehouses_project_flock_kandang_id;
DROP INDEX IF EXISTS idx_product_warehouses_unique;
ALTER TABLE product_warehouses
DROP CONSTRAINT IF EXISTS fk_product_warehouses_project_flock_kandang_id,
ALTER COLUMN project_flock_kandang_id DROP NOT NULL,
DROP COLUMN IF EXISTS project_flock_kandang_id;
-- Revert qty to integer quantity
ALTER TABLE product_warehouses
RENAME COLUMN qty TO quantity;
ALTER TABLE product_warehouses
ALTER COLUMN quantity TYPE INTEGER USING quantity::integer,
ALTER COLUMN quantity SET DEFAULT 0,
ALTER COLUMN quantity SET NOT NULL;
-- Restore audit/soft-delete columns
ALTER TABLE product_warehouses
ADD COLUMN IF NOT EXISTS created_by BIGINT NOT NULL REFERENCES users (id),
ADD COLUMN IF NOT EXISTS created_at TIMESTAMPTZ DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
-- Recreate prior indexes
CREATE INDEX IF NOT EXISTS idx_product_warehouses_deleted_at ON product_warehouses (deleted_at);
CREATE UNIQUE INDEX IF NOT EXISTS idx_product_warehouses_unique
ON product_warehouses (product_id, warehouse_id)
WHERE deleted_at IS NULL;
COMMIT;
@@ -0,0 +1,41 @@
BEGIN;
-- Drop indexes that depend on deleted_at or old uniqueness
DROP INDEX IF EXISTS idx_product_warehouses_deleted_at;
DROP INDEX IF EXISTS idx_product_warehouses_unique;
-- Add new relation and adjust quantity column
ALTER TABLE product_warehouses
ADD COLUMN IF NOT EXISTS project_flock_kandang_id BIGINT;
ALTER TABLE product_warehouses
RENAME COLUMN quantity TO qty;
-- Enforce numeric quantity with precision and default
ALTER TABLE product_warehouses
ALTER COLUMN qty TYPE NUMERIC(15, 3) USING qty::numeric(15, 3),
ALTER COLUMN qty SET DEFAULT 0,
ALTER COLUMN qty SET NOT NULL;
-- Remove audit/soft-delete columns no longer used
ALTER TABLE product_warehouses
DROP COLUMN IF EXISTS created_by,
DROP COLUMN IF EXISTS created_at,
DROP COLUMN IF EXISTS updated_at,
DROP COLUMN IF EXISTS deleted_at;
-- Enforce FK and not-null for project_flock_kandang_id
ALTER TABLE product_warehouses
ADD CONSTRAINT fk_product_warehouses_project_flock_kandang_id
FOREIGN KEY (project_flock_kandang_id)
REFERENCES project_flock_kandangs (id)
ON DELETE RESTRICT ON UPDATE CASCADE;
-- New indexes
CREATE INDEX IF NOT EXISTS idx_product_warehouses_project_flock_kandang_id
ON product_warehouses (project_flock_kandang_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_product_warehouses_unique
ON product_warehouses (product_id, warehouse_id, project_flock_kandang_id);
COMMIT;
@@ -0,0 +1,44 @@
BEGIN;
-- Drop new indexes
DROP INDEX IF EXISTS stock_logs_loggable_type_loggable_id_idx;
DROP INDEX IF EXISTS stock_logs_product_warehouse_id_idx;
DROP INDEX IF EXISTS stock_logs_created_by_idx;
DROP INDEX IF EXISTS stock_logs_created_at_idx;
-- Restore obsolete columns
ALTER TABLE stock_logs
ADD COLUMN IF NOT EXISTS transaction_type VARCHAR(20) DEFAULT '' NOT NULL,
ADD COLUMN IF NOT EXISTS quantity NUMERIC(15, 3) DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS before_quantity NUMERIC(15, 3) DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS after_quantity NUMERIC(15, 3) DEFAULT 0 NOT NULL,
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
-- Rename columns back
ALTER TABLE stock_logs
RENAME COLUMN loggable_type TO log_type;
ALTER TABLE stock_logs
RENAME COLUMN loggable_id TO log_id;
ALTER TABLE stock_logs
RENAME COLUMN notes TO note;
-- Drop new columns
ALTER TABLE stock_logs
DROP COLUMN IF EXISTS increase,
DROP COLUMN IF EXISTS decrease;
-- Restore indexes for old structure
CREATE INDEX IF NOT EXISTS stock_logs_product_warehouse_id_idx ON stock_logs (product_warehouse_id);
CREATE INDEX IF NOT EXISTS stock_logs_log_type_log_id_idx ON stock_logs (log_type, log_id);
CREATE INDEX IF NOT EXISTS stock_logs_created_by_idx ON stock_logs (created_by);
CREATE INDEX IF NOT EXISTS stock_logs_created_at_idx ON stock_logs (created_at);
CREATE INDEX IF NOT EXISTS stock_logs_deleted_at_idx ON stock_logs (deleted_at);
COMMIT;
@@ -0,0 +1,50 @@
BEGIN;
-- Drop old indexes tied to removed columns
DROP INDEX IF EXISTS stock_logs_log_type_log_id_idx;
DROP INDEX IF EXISTS stock_logs_deleted_at_idx;
-- Rename columns to new naming
ALTER TABLE stock_logs
RENAME COLUMN log_type TO loggable_type;
ALTER TABLE stock_logs
RENAME COLUMN log_id TO loggable_id;
ALTER TABLE stock_logs
RENAME COLUMN note TO notes;
-- Add new increase/decrease columns
ALTER TABLE stock_logs
ADD COLUMN IF NOT EXISTS increase NUMERIC(15, 3) DEFAULT 0,
ADD COLUMN IF NOT EXISTS decrease NUMERIC(15, 3) DEFAULT 0;
-- Adjust column definitions
ALTER TABLE stock_logs
ALTER COLUMN loggable_type TYPE VARCHAR(50),
ALTER COLUMN loggable_type SET NOT NULL,
ALTER COLUMN loggable_id SET NOT NULL,
ALTER COLUMN increase SET DEFAULT 0,
ALTER COLUMN increase SET NOT NULL,
ALTER COLUMN decrease SET DEFAULT 0,
ALTER COLUMN decrease SET NOT NULL;
-- Remove obsolete columns
ALTER TABLE stock_logs
DROP COLUMN IF EXISTS transaction_type,
DROP COLUMN IF EXISTS quantity,
DROP COLUMN IF EXISTS before_quantity,
DROP COLUMN IF EXISTS after_quantity,
DROP COLUMN IF EXISTS updated_at,
DROP COLUMN IF EXISTS deleted_at;
-- Recreate indexes for new structure
CREATE INDEX IF NOT EXISTS stock_logs_product_warehouse_id_idx ON stock_logs (product_warehouse_id);
CREATE INDEX IF NOT EXISTS stock_logs_loggable_type_loggable_id_idx ON stock_logs (loggable_type, loggable_id);
CREATE INDEX IF NOT EXISTS stock_logs_created_by_idx ON stock_logs (created_by);
CREATE INDEX IF NOT EXISTS stock_logs_created_at_idx ON stock_logs (created_at);
COMMIT;
+1 -1
View File
@@ -910,7 +910,7 @@ func seedProductWarehouse(tx *gorm.DB, createdBy uint) error {
ProductId: product.Id,
WarehouseId: warehouse.Id,
Quantity: seed.Quantity,
CreatedBy: createdBy,
// CreatedBy: createdBy,
}
if err := tx.Create(&productWarehouse).Error; err != nil {
return err
+7 -5
View File
@@ -21,10 +21,12 @@ type Product struct {
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
IsVisible bool `gorm:"column:is_visible;default:true"`
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
Uom Uom `gorm:"foreignKey:UomId;references:Id"`
ProductCategory ProductCategory `gorm:"foreignKey:ProductCategoryId;references:Id"`
ProductSuppliers []ProductSupplier `gorm:"foreignKey:ProductId;references:Id"`
Flags []Flag `gorm:"polymorphic:Flagable;polymorphicValue:products"`
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
Uom Uom `gorm:"foreignKey:UomId;references:Id"`
ProductCategory ProductCategory `gorm:"foreignKey:ProductCategoryId;references:Id"`
ProductSuppliers []ProductSupplier `gorm:"foreignKey:ProductId;references:Id"`
Flags []Flag `gorm:"polymorphic:Flagable;polymorphicValue:products"`
ProductWarehouses []ProductWarehouse `gorm:"foreignKey:ProductId;references:Id"`
}
+8 -17
View File
@@ -1,23 +1,14 @@
package entities
import (
"time"
"gorm.io/gorm"
)
type ProductWarehouse struct {
Id uint `gorm:"primaryKey;autoIncrement"`
ProductId uint `gorm:"not null"`
WarehouseId uint `gorm:"not null"`
Quantity float64 `gorm:"default:0"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
CreatedBy uint `gorm:"not null"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
Id uint `gorm:"primaryKey;column:id"`
ProductId uint `gorm:"column:product_id;not null"`
WarehouseId uint `gorm:"column:warehouse_id;not null"`
ProjectFlockKandangId *uint `gorm:"column:project_flock_kandang_id"`
Quantity float64 `gorm:"column:qty;type:numeric(15,3);default:0"`
// Relations
Product Product `gorm:"foreignKey:ProductId;references:Id"`
Warehouse Warehouse `gorm:"foreignKey:WarehouseId;references:Id"`
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
Product Product `gorm:"foreignKey:ProductId;references:Id"`
Warehouse Warehouse `gorm:"foreignKey:WarehouseId;references:Id"`
StockLogs []StockLog `gorm:"foreignKey:ProductWarehouseId;references:Id"`
}
+13 -18
View File
@@ -1,10 +1,6 @@
package entities
import (
"time"
"gorm.io/gorm"
)
import "time"
const (
LogTypeAdjustment = "ADJUSTMENT"
@@ -17,19 +13,18 @@ const (
)
type StockLog struct {
Id uint `gorm:"primaryKey;column:id"`
TransactionType string `gorm:"type:varchar(20);not null"`
Quantity float64 `gorm:"type:numeric(15,3);not null"`
BeforeQuantity float64 `gorm:"type:numeric(15,3);not null"`
AfterQuantity float64 `gorm:"type:numeric(15,3);not null"`
LogType string `gorm:"type:varchar(50);not null;index:stock_logs_flaggable_lookup,priority:1"`
LogId uint `gorm:"not null;index:stock_logs_flaggable_lookup,priority:2"`
Note string `gorm:"type:text"`
ProductWarehouseId uint `gorm:"not null;index"`
CreatedBy uint `gorm:"index"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
Id uint `gorm:"primaryKey;column:id"`
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null;index"`
CreatedBy uint `gorm:"column:created_by;not null;index"`
Increase float64 `gorm:"column:increase;type:numeric(15,3);default:0"`
Decrease float64 `gorm:"column:decrease;type:numeric(15,3);default:0"`
LoggableType string `gorm:"column:loggable_type;type:varchar(50);not null"`
LoggableId uint `gorm:"column:loggable_id;not null"`
Notes string `gorm:"column:notes;type:text"`
CreatedAt time.Time `gorm:"column:created_at;autoCreateTime"`
ProductWarehouse *ProductWarehouse `json:"product_warehouse,omitempty" gorm:"foreignKey:ProductWarehouseId;references:Id"`
CreatedUser *User `json:"created_user,omitempty" gorm:"foreignKey:CreatedBy;references:Id"`
@@ -104,12 +104,12 @@ func ToProductWarehouseDTO(e *entity.ProductWarehouse) *ProductWarehouseDTO {
func ToAdjustmentRelationDTO(e *entity.StockLog) AdjustmentRelationDTO {
return AdjustmentRelationDTO{
Id: e.Id,
TransactionType: e.TransactionType,
Quantity: e.Quantity,
BeforeQuantity: e.BeforeQuantity,
AfterQuantity: e.AfterQuantity,
Note: e.Note,
Id: e.Id,
// TransactionType: e.LoggableType,
// Quantity: e.Q,
// BeforeQuantity: e.BeforeQuantity,
// AfterQuantity: e.AfterQuantity,
Note: e.Notes,
ProductWarehouseId: e.ProductWarehouseId,
ProductWarehouse: ToProductWarehouseDTO(e.ProductWarehouse),
}
@@ -136,6 +136,6 @@ func ToAdjustmentListDTO(e *entity.StockLog) AdjustmentListDTO {
func ToAdjustmentDetailDTO(e *entity.StockLog) AdjustmentDetailDTO {
return AdjustmentDetailDTO{
AdjustmentListDTO: ToAdjustmentListDTO(e),
UpdatedAt: e.UpdatedAt,
// UpdatedAt: e.UpdatedAt,
}
}
@@ -66,7 +66,7 @@ func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.StockLog, err
return nil, err
}
if stockLog.LogType != entity.LogTypeAdjustment {
if stockLog.LoggableType != entity.LogTypeAdjustment {
return nil, fiber.NewError(fiber.StatusNotFound, "Adjustment not found")
}
@@ -107,7 +107,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
ProductId: uint(req.ProductID),
WarehouseId: uint(req.WarehouseID),
Quantity: 0,
CreatedBy: 1, // TODO: should Get from auth middleware
// CreatedBy: 1, // TODO: should Get from auth middleware
}
if err := s.ProductWarehouseRepo.CreateOne(ctx, newPW, nil); err != nil {
@@ -125,25 +125,23 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
}
afterQuantity := productWarehouse.Quantity
newLog := &entity.StockLog{
// TransactionType: transactionType,
LoggableType: entity.LogTypeAdjustment,
LoggableId: 0,
Notes: req.Note,
ProductWarehouseId: productWarehouse.Id,
CreatedBy: 1, // TODO: should Get from auth middleware
}
if transactionType == entity.TransactionTypeIncrease {
afterQuantity += req.Quantity
newLog.Increase = afterQuantity
} else {
if productWarehouse.Quantity < req.Quantity {
return fiber.NewError(fiber.StatusBadRequest, "Insufficient stock for adjustment")
}
afterQuantity -= req.Quantity
}
newLog := &entity.StockLog{
TransactionType: transactionType,
Quantity: req.Quantity,
BeforeQuantity: productWarehouse.Quantity,
AfterQuantity: afterQuantity,
LogType: entity.LogTypeAdjustment,
LogId: 0,
Note: req.Note,
ProductWarehouseId: productWarehouse.Id,
CreatedBy: 1, // TODO: should Get from auth middleware
newLog.Decrease = afterQuantity
}
if err := s.StockLogsRepository.WithTx(tx).CreateOne(ctx, newLog, nil); err != nil {
@@ -98,8 +98,8 @@ func ToProductWarehouseNestedDTO(e entity.ProductWarehouse) ProductWarehousNeste
func ToProductWarehouseListDTO(e entity.ProductWarehouse) ProductWarehouseListDTO {
dto := ProductWarehouseListDTO{
ProductWarehouseRelationDTO: ToProductWarehouseRelationDTO(e),
CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt,
// CreatedAt: e.CreatedAt,
// UpdatedAt: e.UpdatedAt,
}
// Map Product relation jika ada
@@ -140,13 +140,13 @@ func ToProductWarehouseListDTO(e entity.ProductWarehouse) ProductWarehouseListDT
}
// Map CreatedUser relation jika ada
if e.CreatedUser.Id != 0 {
user := UserRelationDTO{
Id: e.CreatedUser.Id,
Username: e.CreatedUser.Name,
}
dto.CreatedUser = &user
}
// if e.CreatedUser.Id != 0 {
// user := UserRelationDTO{
// Id: e.CreatedUser.Id,
// Username: e.CreatedUser.Name,
// }
// dto.CreatedUser = &user
// }
return dto
}
@@ -213,11 +213,11 @@ func (r *ProductWarehouseRepositoryImpl) EnsureProductWarehouse(
ProductId: productID,
WarehouseId: warehouseID,
Quantity: 0,
CreatedBy: uint(createdBy),
}
if entity.CreatedBy == 0 {
entity.CreatedBy = 1
// CreatedBy: uint(createdBy),
}
// if entity.CreatedBy == 0 {
// entity.CreatedBy = 1
// }
if err := r.CreateOne(ctx, entity, nil); err != nil {
return 0, err
@@ -267,15 +267,18 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
s.Log.Infof("Source product warehouse updated: %+v", sourcePW.Id)
// create stock log for decrease (source)
beforeQty := sourcePW.Quantity + product.ProductQty // sourcePW already decreased
// beforeQty := sourcePW.Quantity + product.ProductQty // sourcePW already decreased
decreaseLog := &entity.StockLog{
TransactionType: entity.TransactionTypeDecrease,
Quantity: product.ProductQty,
BeforeQuantity: beforeQty,
AfterQuantity: sourcePW.Quantity,
LogType: entity.LogTypeTransfer,
LogId: uint(entityTransfer.Id),
Note: "",
// TransactionType: entity.TransactionTypeDecrease,
// Quantity: product.ProductQty,
// BeforeQuantity: beforeQty,
// AfterQuantity: sourcePW.Qty,
// LogType: entity.LogTypeTransfer,
// LogId: uint(entityTransfer.Id),
Decrease: product.ProductQty,
Notes: "",
LoggableType: entity.LogTypeTransfer,
LoggableId: uint(entityTransfer.Id),
ProductWarehouseId: sourcePW.Id,
CreatedBy: 1,
}
@@ -298,7 +301,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
ProductId: uint(product.ProductID),
WarehouseId: uint(req.DestinationWarehouseID),
Quantity: 0,
CreatedBy: 1, // TODO: should Get from auth middleware
// CreatedBy: 1, // TODO: should Get from auth middleware
}
if err := s.ProductWarehouseRepo.WithTx(tx).CreateOne(c.Context(), destPW, nil); err != nil {
s.Log.Errorf("Failed to create destination product warehouse: %+v", err)
@@ -315,15 +318,16 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques
s.Log.Infof("Destination product warehouse updated: %+v", destPW.Id)
// create stock log for increase (destination)
beforeDestQty := destPW.Quantity - product.ProductQty
// beforeDestQty := destPW.Quantity - product.ProductQty
increaseLog := &entity.StockLog{
TransactionType: entity.TransactionTypeIncrease,
Quantity: product.ProductQty,
BeforeQuantity: beforeDestQty,
AfterQuantity: destPW.Quantity,
LogType: entity.LogTypeTransfer,
LogId: uint(entityTransfer.Id),
Note: "",
// TransactionType: entity.TransactionTypeIncrease,
// Quantity: product.ProductQty,
// BeforeQuantity: beforeDestQty,
// AfterQuantity: destPW.Qty,
Increase: product.ProductQty,
LoggableType: entity.LogTypeTransfer,
LoggableId: uint(entityTransfer.Id),
Notes: "",
ProductWarehouseId: destPW.Id,
CreatedBy: 1,
}
@@ -549,7 +549,7 @@ func (s *chickinService) getOrCreateProductWarehouse(ctx *fiber.Ctx, warehouseId
ProductId: product.Id,
WarehouseId: warehouseId,
Quantity: 0,
CreatedBy: actorID,
// CreatedBy: actorID,
}
if err := s.ProductWarehouseRepo.WithTx(dbTransaction).CreateOne(ctx.Context(), newPW, nil); err != nil {
@@ -768,7 +768,7 @@ func (s *transferLayingService) getOrCreateProductWarehouse(ctx context.Context,
ProductId: productID,
WarehouseId: warehouseID,
Quantity: quantity,
CreatedBy: actorID,
// CreatedBy: actorID,
}
if err := productWarehouseRepoTx.CreateOne(ctx, newWarehouse, nil); err != nil {