feat(BE-47,48,49,50): implement inventory adjustment system

- Extend DB schema with product_warehouses and stock_logs tables
- Implement stock adjustment API (increase/decrease operations)
- Add comprehensive validation for all adjustment operations
- Implement audit log system for each adjustment with history tracking
- Include transaction handling, DTOs, seeders, and proper error handling
- Add adjustment history API with pagination and filtering

TODO: Integration testing pending
This commit is contained in:
aguhh18
2025-10-10 09:24:17 +07:00
parent a0bdc7b23c
commit 91b320d489
23 changed files with 719 additions and 502 deletions
@@ -26,7 +26,6 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error {
query := &validation.Query{
Page: c.QueryInt("page", 1),
Limit: c.QueryInt("limit", 10),
Search: c.Query("search", ""),
ProductId: uint(c.QueryInt("product_id", 0)),
WarehouseId: uint(c.QueryInt("warehouse_id", 0)),
}
@@ -13,7 +13,7 @@ type ProductWarehouseBaseDTO struct {
Id uint `json:"id"`
ProductId uint `json:"product_id"`
WarehouseId uint `json:"warehouse_id"`
Quantity int `json:"quantity"`
Quantity float64 `json:"quantity"`
}
type ProductWarehouseListDTO struct {
@@ -14,6 +14,7 @@ type ProductWarehouseRepository interface {
IsProductExist(ctx context.Context, productId uint) (bool, error)
IsWarehouseExist(ctx context.Context, warehouseId uint) (bool, error)
ExistsByID(ctx context.Context, id uint) (bool, error)
GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error)
}
type ProductWarehouseRepositoryImpl struct {
@@ -51,3 +52,11 @@ func (r *ProductWarehouseRepositoryImpl) IsWarehouseExist(ctx context.Context, w
func (r *ProductWarehouseRepositoryImpl) ExistsByID(ctx context.Context, id uint) (bool, error) {
return repository.Exists[entity.ProductWarehouse](ctx, r.db, id)
}
func (r *ProductWarehouseRepositoryImpl) GetProductWarehouseByProductAndWarehouseID(ctx context.Context, productId, warehouseId uint) (*entity.ProductWarehouse, error) {
var productWarehouse entity.ProductWarehouse
if err := r.DB().WithContext(ctx).Where("product_id = ? AND warehouse_id = ?", productId, warehouseId).First(&productWarehouse).Error; err != nil {
return nil, err
}
return &productWarehouse, nil
}
@@ -58,13 +58,6 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
db = db.Where("warehouse_id = ?", params.WarehouseId)
}
// Search in related product or warehouse names
if params.Search != "" {
db = db.Joins("LEFT JOIN products ON products.id = product_warehouse.product_id").
Joins("LEFT JOIN warehouses ON warehouses.id = product_warehouse.warehouse_id").
Where("products.name ILIKE ? OR warehouses.name ILIKE ?", "%"+params.Search+"%", "%"+params.Search+"%")
}
return db.Order("created_at DESC").Order("updated_at DESC")
})
@@ -1,21 +1,20 @@
package validation
type Create struct {
ProductId uint `json:"product_id" validate:"required,number,min=1"`
WarehouseId uint `json:"warehouse_id" validate:"required,number,min=1"`
Quantity int `json:"quantity" validate:"required,number,min=0"`
ProductId uint `json:"product_id" validate:"required,number,min=1"`
WarehouseId uint `json:"warehouse_id" validate:"required,number,min=1"`
Quantity float64 `json:"quantity" validate:"required,number,min=0"`
}
type Update struct {
ProductId *uint `json:"product_id,omitempty" validate:"omitempty,number,min=1"`
WarehouseId *uint `json:"warehouse_id,omitempty" validate:"omitempty,number,min=1"`
Quantity *int `json:"quantity,omitempty" validate:"omitempty,number,min=0"`
ProductId *uint `json:"product_id,omitempty" validate:"omitempty,number,min=1"`
WarehouseId *uint `json:"warehouse_id,omitempty" validate:"omitempty,number,min=1"`
Quantity *float64 `json:"quantity,omitempty" validate:"omitempty,number,min=0"`
}
type Query struct {
Page int `query:"page" validate:"omitempty,number,min=1"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
Search string `query:"search" validate:"omitempty,max=50"`
ProductId uint `query:"product_id" validate:"omitempty,number,min=1"`
WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"`
Page int `query:"page" validate:"omitempty,number,min=1"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
ProductId uint `query:"product_id" validate:"omitempty,number,min=1"`
WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"`
}