mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Feat[BE]: refactored Chickin createone and implement approvals and add more needed constant
This commit is contained in:
+136
-136
@@ -93,9 +93,9 @@ func Run(db *gorm.DB) error {
|
|||||||
if err := seedTransferStock(tx, adminID); err != nil {
|
if err := seedTransferStock(tx, adminID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := seedChickin(tx, adminID); err != nil {
|
// if err := seedChickin(tx, adminID); err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
fmt.Println("✅ Master data seeding completed")
|
fmt.Println("✅ Master data seeding completed")
|
||||||
return nil
|
return nil
|
||||||
@@ -1134,151 +1134,151 @@ func seedTransferStock(tx *gorm.DB, createdBy uint) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func seedChickin(tx *gorm.DB, createdBy uint) error {
|
// func seedChickin(tx *gorm.DB, createdBy uint) error {
|
||||||
seeds := []struct {
|
// seeds := []struct {
|
||||||
ProjectFlockKandangId uint
|
// ProjectFlockKandangId uint
|
||||||
ChickInDate string
|
// ChickInDate string
|
||||||
Quantity float64
|
// Quantity float64
|
||||||
Note string
|
// Note string
|
||||||
}{
|
// }{
|
||||||
{ProjectFlockKandangId: 1, ChickInDate: "2025-10-20", Quantity: 100, Note: "Seeder chickin 1"},
|
// {ProjectFlockKandangId: 1, ChickInDate: "2025-10-20", Quantity: 100, Note: "Seeder chickin 1"},
|
||||||
{ProjectFlockKandangId: 2, ChickInDate: "2025-10-21", Quantity: 200, Note: "Seeder chickin 2"},
|
// {ProjectFlockKandangId: 2, ChickInDate: "2025-10-21", Quantity: 200, Note: "Seeder chickin 2"},
|
||||||
}
|
// }
|
||||||
|
|
||||||
for _, seed := range seeds {
|
// for _, seed := range seeds {
|
||||||
chickinDate, err := time.Parse("2006-01-02", seed.ChickInDate)
|
// chickinDate, err := time.Parse("2006-01-02", seed.ChickInDate)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Insert ProjectChickin jika belum ada
|
// // Insert ProjectChickin jika belum ada
|
||||||
var chickin entity.ProjectChickin
|
// var chickin entity.ProjectChickin
|
||||||
err = tx.Where("project_flock_kandang_id = ? AND chick_in_date = ?", seed.ProjectFlockKandangId, chickinDate).
|
// err = tx.Where("project_flock_kandang_id = ? AND chick_in_date = ?", seed.ProjectFlockKandangId, chickinDate).
|
||||||
First(&chickin).Error
|
// First(&chickin).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
// if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
chickin = entity.ProjectChickin{
|
// chickin = entity.ProjectChickin{
|
||||||
ProjectFlockKandangId: seed.ProjectFlockKandangId,
|
// ProjectFlockKandangId: seed.ProjectFlockKandangId,
|
||||||
ChickInDate: chickinDate,
|
// ChickInDate: chickinDate,
|
||||||
Quantity: seed.Quantity,
|
// Quantity: seed.Quantity,
|
||||||
Note: seed.Note,
|
// Note: seed.Note,
|
||||||
CreatedBy: createdBy,
|
// CreatedBy: createdBy,
|
||||||
}
|
// }
|
||||||
if err := tx.Create(&chickin).Error; err != nil {
|
// if err := tx.Create(&chickin).Error; err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
} else if err != nil {
|
// } else if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
var population entity.ProjectFlockPopulation
|
// var population entity.ProjectFlockPopulation
|
||||||
err = tx.Where("project_flock_kandang_id = ?", seed.ProjectFlockKandangId).First(&population).Error
|
// err = tx.Where("project_flock_kandang_id = ?", seed.ProjectFlockKandangId).First(&population).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
// if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
population = entity.ProjectFlockPopulation{
|
// population = entity.ProjectFlockPopulation{
|
||||||
ProjectFlockKandangId: seed.ProjectFlockKandangId,
|
// ProjectFlockKandangId: seed.ProjectFlockKandangId,
|
||||||
InitialQuantity: seed.Quantity,
|
// InitialQuantity: seed.Quantity,
|
||||||
CurrentQuantity: seed.Quantity,
|
// CurrentQuantity: seed.Quantity,
|
||||||
ReservedQuantity: 0,
|
// ReservedQuantity: 0,
|
||||||
CreatedBy: createdBy,
|
// CreatedBy: createdBy,
|
||||||
}
|
// }
|
||||||
if err := tx.Create(&population).Error; err != nil {
|
// if err := tx.Create(&population).Error; err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
} else if err != nil {
|
// } else if err != nil {
|
||||||
return err
|
// return err
|
||||||
} else {
|
// } else {
|
||||||
// Update population quantities
|
// // Update population quantities
|
||||||
if err := tx.Model(&entity.ProjectFlockPopulation{}).
|
// if err := tx.Model(&entity.ProjectFlockPopulation{}).
|
||||||
Where("id = ?", population.Id).
|
// Where("id = ?", population.Id).
|
||||||
Updates(map[string]any{
|
// Updates(map[string]any{
|
||||||
"initial_quantity": population.InitialQuantity + seed.Quantity,
|
// "initial_quantity": population.InitialQuantity + seed.Quantity,
|
||||||
"current_quantity": population.CurrentQuantity + seed.Quantity,
|
// "current_quantity": population.CurrentQuantity + seed.Quantity,
|
||||||
"reserved_quantity": 0,
|
// "reserved_quantity": 0,
|
||||||
}).Error; err != nil {
|
// }).Error; err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
var pfk entity.ProjectFlockKandang
|
// var pfk entity.ProjectFlockKandang
|
||||||
if err := tx.Where("id = ?", seed.ProjectFlockKandangId).First(&pfk).Error; err != nil {
|
// if err := tx.Where("id = ?", seed.ProjectFlockKandangId).First(&pfk).Error; err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
// if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
// no pivot found; skip creating details
|
// // no pivot found; skip creating details
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
var warehouse entity.Warehouse
|
// var warehouse entity.Warehouse
|
||||||
if err := tx.Where("kandang_id = ?", pfk.KandangId).First(&warehouse).Error; err != nil {
|
// if err := tx.Where("kandang_id = ?", pfk.KandangId).First(&warehouse).Error; err != nil {
|
||||||
// if warehouse not found, cannot create details
|
// // if warehouse not found, cannot create details
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
// if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
var productWarehouses []entity.ProductWarehouse
|
// var productWarehouses []entity.ProductWarehouse
|
||||||
err = tx.Table("product_warehouses").
|
// err = tx.Table("product_warehouses").
|
||||||
Select("product_warehouses.*").
|
// Select("product_warehouses.*").
|
||||||
Joins("JOIN products ON products.id = product_warehouses.product_id").
|
// Joins("JOIN products ON products.id = product_warehouses.product_id").
|
||||||
Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
|
// Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
|
||||||
Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id).
|
// Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id).
|
||||||
Order("product_warehouses.created_at DESC").
|
// Order("product_warehouses.created_at DESC").
|
||||||
Find(&productWarehouses).Error
|
// Find(&productWarehouses).Error
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// If no product warehouses found, keep existing chickin.Quantity and skip details
|
// // If no product warehouses found, keep existing chickin.Quantity and skip details
|
||||||
if len(productWarehouses) == 0 {
|
// if len(productWarehouses) == 0 {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
// sum all pw quantities and set chickin.Quantity to that total (mimic CreateOne)
|
// // sum all pw quantities and set chickin.Quantity to that total (mimic CreateOne)
|
||||||
totalQty := 0.0
|
// totalQty := 0.0
|
||||||
for _, pw := range productWarehouses {
|
// for _, pw := range productWarehouses {
|
||||||
totalQty += pw.Quantity
|
// totalQty += pw.Quantity
|
||||||
}
|
// }
|
||||||
|
|
||||||
if chickin.Quantity != totalQty {
|
// if chickin.Quantity != totalQty {
|
||||||
if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Update("quantity", totalQty).Error; err != nil {
|
// if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Update("quantity", totalQty).Error; err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
chickin.Quantity = totalQty
|
// chickin.Quantity = totalQty
|
||||||
}
|
// }
|
||||||
|
|
||||||
for _, pw := range productWarehouses {
|
// for _, pw := range productWarehouses {
|
||||||
// ensure detail exists or create it with full pw.Quantity
|
// // ensure detail exists or create it with full pw.Quantity
|
||||||
var detail entity.ProjectChickinDetail
|
// var detail entity.ProjectChickinDetail
|
||||||
err = tx.Where("project_chickin_id = ? AND product_warehouse_id = ?", chickin.Id, pw.Id).First(&detail).Error
|
// err = tx.Where("project_chickin_id = ? AND product_warehouse_id = ?", chickin.Id, pw.Id).First(&detail).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
// if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
detail = entity.ProjectChickinDetail{
|
// detail = entity.ProjectChickinDetail{
|
||||||
ProjectChickinId: chickin.Id,
|
// ProjectChickinId: chickin.Id,
|
||||||
ProductWarehouseId: pw.Id,
|
// ProductWarehouseId: pw.Id,
|
||||||
Quantity: pw.Quantity,
|
// Quantity: pw.Quantity,
|
||||||
CreatedBy: createdBy,
|
// CreatedBy: createdBy,
|
||||||
}
|
// }
|
||||||
if err := tx.Create(&detail).Error; err != nil {
|
// if err := tx.Create(&detail).Error; err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
} else if err != nil {
|
// } else if err != nil {
|
||||||
return err
|
// return err
|
||||||
} else {
|
// } else {
|
||||||
if detail.Quantity != pw.Quantity {
|
// if detail.Quantity != pw.Quantity {
|
||||||
if err := tx.Model(&entity.ProjectChickinDetail{}).Where("id = ?", detail.Id).Update("quantity", pw.Quantity).Error; err != nil {
|
// if err := tx.Model(&entity.ProjectChickinDetail{}).Where("id = ?", detail.Id).Update("quantity", pw.Quantity).Error; err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// zero out pw quantity
|
// // zero out pw quantity
|
||||||
if err := tx.Model(&entity.ProductWarehouse{}).Where("id = ?", pw.Id).Update("quantity", 0).Error; err != nil {
|
// if err := tx.Model(&entity.ProductWarehouse{}).Where("id = ?", pw.Id).Update("quantity", 0).Error; err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func ptr[T any](v T) *T {
|
func ptr[T any](v T) *T {
|
||||||
return &v
|
return &v
|
||||||
|
|||||||
@@ -12,13 +12,16 @@ type ProjectChickin struct {
|
|||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
ProjectFlockKandangId uint `gorm:"not null"`
|
ProjectFlockKandangId uint `gorm:"not null"`
|
||||||
ChickInDate time.Time `gorm:"not null"`
|
ChickInDate time.Time `gorm:"not null"`
|
||||||
Quantity float64 `gorm:"not null"`
|
ProductWarehouseId uint `gorm:"not null"`
|
||||||
Note string `gorm:"type:text"`
|
UsageQty float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
PendingUsageQty float64 `gorm:"type:numeric(15,3);default:0"`
|
||||||
|
Notes string `gorm:"type:text"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
ProjectFlockKandang ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||||
CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"`
|
ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||||
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,17 @@ import (
|
|||||||
|
|
||||||
type ProjectFlockPopulation struct {
|
type ProjectFlockPopulation struct {
|
||||||
Id uint `gorm:"primaryKey"`
|
Id uint `gorm:"primaryKey"`
|
||||||
ProjectFlockKandangId uint `gorm:"not null"`
|
ProjectChickinId uint `gorm:"not null"`
|
||||||
InitialQuantity float64 `gorm:"type:numeric(15,3);not null"`
|
ProductWarehouseId uint `gorm:"not null"`
|
||||||
CurrentQuantity float64 `gorm:"type:numeric(15,3);not null"`
|
TotalQty float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
ReservedQuantity float64 `gorm:"type:numeric(15,3)"`
|
TotalUsedQty float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
|
Notes string `gorm:"type:text"`
|
||||||
CreatedBy uint `gorm:"not null"`
|
CreatedBy uint `gorm:"not null"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
|
||||||
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
ProjectChickin *ProjectChickin `gorm:"foreignKey:ProjectChickinId;references:Id"`
|
||||||
|
ProductWarehouse *ProductWarehouse `gorm:"foreignKey:ProductWarehouseId;references:Id"`
|
||||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,3 +28,4 @@ type ProjectFlock struct {
|
|||||||
KandangHistory []ProjectFlockKandang `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
KandangHistory []ProjectFlockKandang `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
||||||
LatestApproval *Approval `gorm:"-" json:"-"`
|
LatestApproval *Approval `gorm:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ type ProjectFlockKandang struct {
|
|||||||
ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"`
|
ProjectFlockId uint `gorm:"not null;index:idx_project_flock_kandangs_project;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||||
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
KandangId uint `gorm:"not null;index:idx_project_flock_kandangs_kandang;uniqueIndex:idx_project_flock_kandangs_unique"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
|
||||||
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
ProjectFlock ProjectFlock `gorm:"foreignKey:ProjectFlockId;references:Id"`
|
||||||
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
Kandang Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
||||||
|
Chickins []ProjectChickin `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ func (r *ConstantRepositoryImpl) GetConstants() map[string]interface{} {
|
|||||||
"LOKASI",
|
"LOKASI",
|
||||||
"KANDANG",
|
"KANDANG",
|
||||||
},
|
},
|
||||||
|
"stock_log": map[string][]string{
|
||||||
|
"log_types": []string{"TRANSFER", "ADJUSTMENT"},
|
||||||
|
"transaction_types": []string{"INCREASE", "DECREASE"},
|
||||||
|
},
|
||||||
"supplier_categories": []string{
|
"supplier_categories": []string{
|
||||||
"BOP",
|
"BOP",
|
||||||
"SAPRONAK",
|
"SAPRONAK",
|
||||||
|
|||||||
@@ -139,23 +139,33 @@ func (u *ChickinController) DeleteOne(c *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *ChickinController) Approve(c *fiber.Ctx) error {
|
func (u *ChickinController) Approval(c *fiber.Ctx) error {
|
||||||
param := c.Params("id")
|
req := new(validation.Approve)
|
||||||
|
if err := c.BodyParser(req); err != nil {
|
||||||
id, err := strconv.Atoi(param)
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
if err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := u.ChickinService.Approve(c, uint(id)); err != nil {
|
results, err := u.ChickinService.Approval(c, req)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
data interface{}
|
||||||
|
message = "Submit chickin approval successfully"
|
||||||
|
)
|
||||||
|
if len(results) == 1 {
|
||||||
|
data = dto.ToChickinListDTO(results[0])
|
||||||
|
} else {
|
||||||
|
message = "Submit chickin approvals successfully"
|
||||||
|
data = dto.ToChickinListDTOs(results)
|
||||||
|
}
|
||||||
|
|
||||||
return c.Status(fiber.StatusOK).
|
return c.Status(fiber.StatusOK).
|
||||||
JSON(response.Success{
|
JSON(response.Success{
|
||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Approve chickin successfully",
|
Message: message,
|
||||||
Data: nil,
|
Data: data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ type ChickinBaseDTO struct {
|
|||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProjectFlockKandang *ProjectFlockKandangDTO `json:"project_flock_kandang"`
|
ProjectFlockKandang *ProjectFlockKandangDTO `json:"project_flock_kandang"`
|
||||||
ChickInDate time.Time `json:"chick_in_date"`
|
ChickInDate time.Time `json:"chick_in_date"`
|
||||||
Quantity float64 `json:"quantity"`
|
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||||
Note string `json:"note"`
|
UsageQty float64 `json:"usage_qty"`
|
||||||
|
PendingUsageQty float64 `json:"pending_usage_qty"`
|
||||||
|
Notes string `json:"notes"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectFlockDTO struct {
|
type ProjectFlockDTO struct {
|
||||||
@@ -44,8 +46,10 @@ type ChickinSimpleDTO struct {
|
|||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id"`
|
||||||
ChickInDate time.Time `json:"chick_in_date"`
|
ChickInDate time.Time `json:"chick_in_date"`
|
||||||
Quantity float64 `json:"quantity"`
|
ProductWarehouseId uint `json:"product_warehouse_id"`
|
||||||
Note string `json:"note"`
|
UsageQty float64 `json:"usage_qty"`
|
||||||
|
PendingUsageQty float64 `json:"pending_usage_qty"`
|
||||||
|
Notes string `json:"notes"`
|
||||||
CreatedBy uint `json:"created_by"`
|
CreatedBy uint `json:"created_by"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,16 +142,18 @@ func ToProjectFlockKandangDTO(e entity.ProjectFlockKandang) ProjectFlockKandangD
|
|||||||
|
|
||||||
func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO {
|
func ToChickinBaseDTO(e entity.ProjectChickin) ChickinBaseDTO {
|
||||||
var pfk *ProjectFlockKandangDTO
|
var pfk *ProjectFlockKandangDTO
|
||||||
if e.ProjectFlockKandang.Id != 0 {
|
if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.Id != 0 {
|
||||||
mapped := ToProjectFlockKandangDTO(e.ProjectFlockKandang)
|
mapped := ToProjectFlockKandangDTO(*e.ProjectFlockKandang)
|
||||||
pfk = &mapped
|
pfk = &mapped
|
||||||
}
|
}
|
||||||
return ChickinBaseDTO{
|
return ChickinBaseDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
ProjectFlockKandang: pfk,
|
ProjectFlockKandang: pfk,
|
||||||
ChickInDate: e.ChickInDate,
|
ChickInDate: e.ChickInDate,
|
||||||
Quantity: e.Quantity,
|
ProductWarehouseId: e.ProductWarehouseId,
|
||||||
Note: e.Note,
|
UsageQty: e.UsageQty,
|
||||||
|
PendingUsageQty: e.PendingUsageQty,
|
||||||
|
Notes: e.Notes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,21 +162,23 @@ func ToChickinSimpleDTO(e entity.ProjectChickin) ChickinSimpleDTO {
|
|||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
ProjectFlockKandangId: e.ProjectFlockKandangId,
|
ProjectFlockKandangId: e.ProjectFlockKandangId,
|
||||||
ChickInDate: e.ChickInDate,
|
ChickInDate: e.ChickInDate,
|
||||||
Quantity: e.Quantity,
|
ProductWarehouseId: e.ProductWarehouseId,
|
||||||
Note: e.Note,
|
UsageQty: e.UsageQty,
|
||||||
|
PendingUsageQty: e.PendingUsageQty,
|
||||||
|
Notes: e.Notes,
|
||||||
CreatedBy: e.CreatedBy,
|
CreatedBy: e.CreatedBy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToChickinListDTO(e entity.ProjectChickin) ChickinListDTO {
|
func ToChickinListDTO(e entity.ProjectChickin) ChickinListDTO {
|
||||||
var createdUser *userBaseDTO.UserBaseDTO
|
var createdUser *userBaseDTO.UserBaseDTO
|
||||||
if e.CreatedUser.Id != 0 {
|
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
||||||
mapped := userBaseDTO.ToUserBaseDTO(e.CreatedUser)
|
mapped := userBaseDTO.ToUserBaseDTO(*e.CreatedUser)
|
||||||
createdUser = &mapped
|
createdUser = &mapped
|
||||||
}
|
}
|
||||||
var pfk *ProjectFlockKandangDTO
|
var pfk *ProjectFlockKandangDTO
|
||||||
if e.ProjectFlockKandang.Id != 0 {
|
if e.ProjectFlockKandang != nil && e.ProjectFlockKandang.Id != 0 {
|
||||||
mapped := ToProjectFlockKandangDTO(e.ProjectFlockKandang)
|
mapped := ToProjectFlockKandangDTO(*e.ProjectFlockKandang)
|
||||||
pfk = &mapped
|
pfk = &mapped
|
||||||
}
|
}
|
||||||
return ChickinListDTO{
|
return ChickinListDTO{
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package chickins
|
package chickins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
|
|
||||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
rKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||||
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories"
|
||||||
@@ -15,6 +20,8 @@ import (
|
|||||||
|
|
||||||
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
|
||||||
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
sUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
|
||||||
|
|
||||||
|
utils "gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ChickinModule struct{}
|
type ChickinModule struct{}
|
||||||
@@ -32,6 +39,12 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
|||||||
|
|
||||||
userRepo := rUser.NewUserRepository(db)
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
|
||||||
|
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||||
|
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||||
|
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowProjectFlockKandang, utils.ProjectFlockKandangApprovalSteps); err != nil {
|
||||||
|
panic(fmt.Sprintf("failed to register project flock kandang approval workflow: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, projectflockkandangrepo, projectflockpopulationrepo, chickinDetailRepo, validate)
|
chickinService := sChickin.NewChickinService(chickinRepo, kandangRepo, warehouseRepo, productWarehouseRepo, projectFlockRepo, projectflockkandangrepo, projectflockpopulationrepo, chickinDetailRepo, validate)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
|
|||||||
@@ -25,5 +25,5 @@ func ChickinRoutes(v1 fiber.Router, u user.UserService, s chickin.ChickinService
|
|||||||
route.Get("/:id", ctrl.GetOne)
|
route.Get("/:id", ctrl.GetOne)
|
||||||
route.Patch("/:id", ctrl.UpdateOne)
|
route.Patch("/:id", ctrl.UpdateOne)
|
||||||
route.Delete("/:id", ctrl.DeleteOne)
|
route.Delete("/:id", ctrl.DeleteOne)
|
||||||
route.Post("/:id/approve", ctrl.Approve)
|
route.Post("/approvals", ctrl.Approval)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
|
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
KandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
KandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||||
@@ -24,7 +28,7 @@ type ChickinService interface {
|
|||||||
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error)
|
CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.ProjectChickin, error)
|
||||||
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error)
|
UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error)
|
||||||
DeleteOne(ctx *fiber.Ctx, id uint) error
|
DeleteOne(ctx *fiber.Ctx, id uint) error
|
||||||
Approve(ctx *fiber.Ctx, id uint) error
|
Approval(ctx *fiber.Ctx, req *validation.Approve) ([]entity.ProjectChickin, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type chickinService struct {
|
type chickinService struct {
|
||||||
@@ -110,107 +114,98 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
projectflockkandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), req.ProjectFlockKandangId)
|
projectFlockKandang, err := s.ProjectflockKandangRepo.GetByID(c.Context(), req.ProjectFlockKandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to get projectflock kandang: %+v", err)
|
return nil, fiber.NewError(fiber.StatusNotFound, "Project Flock Kandang not found")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), projectflockkandang.KandangId)
|
warehouse, err := s.WarehouseRepo.GetByKandangID(c.Context(), projectFlockKandang.KandangId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to get warehouse: %+v", err)
|
return nil, fiber.NewError(fiber.StatusNotFound, "Warehouse for Kandang not found")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// move complex DB query into repository for cleaner service
|
var productWarehouses []entity.ProductWarehouse
|
||||||
productWarehouses, err := s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(c.Context(), "DOC", warehouse.Id)
|
|
||||||
if err != nil {
|
|
||||||
s.Log.Errorf("Failed to get product warehouses: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(productWarehouses) == 0 {
|
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse")
|
|
||||||
}
|
|
||||||
totalQuantity := 0.0
|
|
||||||
for _, pw := range productWarehouses {
|
|
||||||
totalQuantity += pw.Quantity
|
|
||||||
}
|
|
||||||
|
|
||||||
if totalQuantity < 1 {
|
if strings.ToUpper(strings.TrimSpace(projectFlockKandang.ProjectFlock.Category)) == string(utils.ProjectFlockCategoryGrowing) {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Insufficient quantity in Product Warehouses")
|
|
||||||
|
productWarehouses, err = s.ProductWarehouseRepo.GetByCategoryCodeAndWarehouseID(c.Context(), "DOC", warehouse.Id)
|
||||||
|
if err != nil || len(productWarehouses) == 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusNotFound, "Product for growing category in the Kandang's warehouse not found")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chickinDate, err := utils.ParseDateString(req.ChickInDate)
|
chickinDate, err := utils.ParseDateString(req.ChickInDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.Log.Errorf("Failed to parse chickin date: %+v", err)
|
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid ChickInDate format")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid ChickInDate format")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actorID := uint(1) // todo nanti ambil dari auth context
|
||||||
|
newChikins := make([]*entity.ProjectChickin, 0)
|
||||||
|
for _, productWarehouse := range productWarehouses {
|
||||||
|
|
||||||
|
if productWarehouse.Quantity > 0 {
|
||||||
newChickin := &entity.ProjectChickin{
|
newChickin := &entity.ProjectChickin{
|
||||||
ProjectFlockKandangId: projectflockkandang.Id,
|
|
||||||
ChickInDate: chickinDate,
|
|
||||||
Quantity: totalQuantity,
|
|
||||||
Note: req.Note,
|
|
||||||
CreatedBy: 1, //todo: ganti dengan user login
|
|
||||||
}
|
|
||||||
err = s.Repository.CreateOne(c.Context(), newChickin, nil)
|
|
||||||
if err != nil {
|
|
||||||
s.Log.Errorf("Failed to create chickin: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update semua product warehouse: set quantity jadi 0
|
|
||||||
for _, pw := range productWarehouses {
|
|
||||||
err = s.ProductWarehouseRepo.PatchOne(c.Context(), pw.Id, map[string]any{
|
|
||||||
"quantity": 0,
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
|
||||||
s.Log.Errorf("Failed to update product warehouse quantity: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newChickinDetail := &entity.ProjectChickinDetail{
|
|
||||||
ProjectChickinId: newChickin.Id,
|
|
||||||
ProductWarehouseId: pw.Id,
|
|
||||||
Quantity: pw.Quantity,
|
|
||||||
CreatedBy: 1, // todo: ganti dengan user login
|
|
||||||
}
|
|
||||||
err = s.ProjectChickinDetailRepo.CreateOne(c.Context(), newChickinDetail, nil)
|
|
||||||
if err != nil {
|
|
||||||
s.Log.Errorf("Failed to create chickin detail: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
existingPopulation, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), req.ProjectFlockKandangId)
|
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
s.Log.Errorf("Failed to get project flock population: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if existingPopulation != nil {
|
|
||||||
|
|
||||||
err = s.ProjectflockPopulationRepo.PatchOne(c.Context(), existingPopulation.Id, map[string]any{
|
|
||||||
"reserved_quantity": newChickin.Quantity + existingPopulation.ReservedQuantity,
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
|
||||||
s.Log.Errorf("Failed to update project flock population: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newPopulation := &entity.ProjectFlockPopulation{
|
|
||||||
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
ProjectFlockKandangId: req.ProjectFlockKandangId,
|
||||||
InitialQuantity: 0,
|
ChickInDate: chickinDate,
|
||||||
CurrentQuantity: 0,
|
UsageQty: 0,
|
||||||
ReservedQuantity: newChickin.Quantity,
|
PendingUsageQty: productWarehouse.Quantity,
|
||||||
CreatedBy: 1, // todo: ganti dengan user login
|
ProductWarehouseId: productWarehouse.Id,
|
||||||
|
Notes: req.Note,
|
||||||
|
CreatedBy: actorID,
|
||||||
}
|
}
|
||||||
err = s.ProjectflockPopulationRepo.CreateOne(c.Context(), newPopulation, nil)
|
|
||||||
if err != nil {
|
newChikins = append(newChikins, newChickin)
|
||||||
s.Log.Errorf("Failed to create project flock population: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.GetOne(c, newChickin.Id)
|
if len(newChikins) == 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "No chickins to create")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
|
|
||||||
|
approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
|
productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction)
|
||||||
|
|
||||||
|
if err := s.Repository.WithTx(dbTransaction).CreateMany(c.Context(), newChikins, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
latest, err := approvalSvcTx.LatestByTarget(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, projectFlockKandang.Id, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chickin := range newChikins {
|
||||||
|
|
||||||
|
updates := map[string]any{"quantity": 0}
|
||||||
|
|
||||||
|
if err := productWarehouseTx.PatchOne(c.Context(), chickin.ProductWarehouseId, updates, nil); err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fmt.Errorf("failed to update product warehouse quantity for id %d", chickin.ProductWarehouseId)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if latest == nil {
|
||||||
|
|
||||||
|
action := entity.ApprovalActionCreated
|
||||||
|
if _, err := approvalSvcTx.CreateApproval(c.Context(), utils.ApprovalWorkflowProjectFlockKandang, projectFlockKandang.Id, utils.ProjectFlockKandangStepPengajuan, &action, actorID, nil); err != nil {
|
||||||
|
lower := strings.ToLower(err.Error())
|
||||||
|
if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return newChikins[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) {
|
func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.ProjectChickin, error) {
|
||||||
@@ -224,7 +219,8 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
updateBody["chick_in_date"] = req.ChickInDate
|
updateBody["chick_in_date"] = req.ChickInDate
|
||||||
}
|
}
|
||||||
if req.Note != "" {
|
if req.Note != "" {
|
||||||
updateBody["note"] = req.Note
|
// entity uses `Notes` => column `notes`
|
||||||
|
updateBody["notes"] = req.Note
|
||||||
}
|
}
|
||||||
if len(updateBody) == 0 {
|
if len(updateBody) == 0 {
|
||||||
return s.GetOne(c, id)
|
return s.GetOne(c, id)
|
||||||
@@ -243,160 +239,140 @@ func (s chickinService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
|||||||
|
|
||||||
func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||||
|
|
||||||
|
// Simplified delete: directly call repository delete. Complex restore logic removed for now.
|
||||||
|
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entity.ProjectChickin, error) {
|
||||||
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
actorID := uint(1) // todo nanti ambil dari auth context
|
||||||
|
|
||||||
|
var action entity.ApprovalAction
|
||||||
|
switch strings.ToUpper(strings.TrimSpace(req.Action)) {
|
||||||
|
case string(entity.ApprovalActionRejected):
|
||||||
|
action = entity.ApprovalActionRejected
|
||||||
|
case string(entity.ApprovalActionApproved):
|
||||||
|
action = entity.ApprovalActionApproved
|
||||||
|
default:
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "action must be APPROVED or REJECTED")
|
||||||
|
}
|
||||||
|
|
||||||
|
approvableIDs := uniqueUintSlice(req.ApprovableIds)
|
||||||
|
if len(approvableIDs) == 0 {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id")
|
||||||
|
}
|
||||||
|
|
||||||
|
step := utils.ProjectFlockKandangStepPengajuan
|
||||||
|
if action == entity.ApprovalActionApproved {
|
||||||
|
step = utils.ProjectFlockKandangStepDisetujui
|
||||||
|
}
|
||||||
|
|
||||||
err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error {
|
||||||
|
approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction))
|
||||||
|
ProjectFlockPopulationRepotx := s.ProjectflockPopulationRepo.WithTx(dbTransaction)
|
||||||
|
chickinRepoTx := s.Repository.WithTx(dbTransaction)
|
||||||
|
|
||||||
chickinRepo := repository.NewChickinRepository(dbTransaction)
|
for _, approvableID := range approvableIDs {
|
||||||
projectFlockKandangRepo := s.ProjectflockKandangRepo.WithTx(dbTransaction)
|
|
||||||
productWarehouseRepo := s.ProductWarehouseRepo.WithTxRepo(dbTransaction)
|
|
||||||
projectFlockPopulationRepo := s.ProjectflockPopulationRepo.WithTx(dbTransaction)
|
|
||||||
projectChickinDetailRepo := s.ProjectChickinDetailRepo.WithTxRepo(dbTransaction)
|
|
||||||
warehouseRepoTx := rWarehouse.NewWarehouseRepository(dbTransaction)
|
|
||||||
|
|
||||||
chickin, err := chickinRepo.GetByID(c.Context(), id, nil)
|
exists, err := s.ProjectflockKandangRepo.WithTx(dbTransaction).IdExists(c.Context(), approvableID)
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
population, err := projectFlockPopulationRepo.GetByProjectFlockKandangID(c.Context(), chickin.ProjectFlockKandangId)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Project flock population not found")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newReserved := population.ReservedQuantity - chickin.Quantity
|
|
||||||
if newReserved < 0 {
|
|
||||||
newReserved = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
err = projectFlockPopulationRepo.PatchOne(c.Context(), population.Id, map[string]any{
|
|
||||||
"reserved_quantity": newReserved,
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !exists {
|
||||||
restoreFromDetails := func() (bool, error) {
|
return fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("ProjectFlockKandang %d not found", approvableID))
|
||||||
|
|
||||||
details, err := projectChickinDetailRepo.GetByProjectChickinID(c.Context(), chickin.Id)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range details {
|
if _, err := approvalSvc.CreateApproval(
|
||||||
productWarehouse, err := productWarehouseRepo.GetByID(c.Context(), d.ProductWarehouseId, nil)
|
c.Context(),
|
||||||
if err != nil {
|
utils.ApprovalWorkflowProjectFlockKandang,
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
approvableID,
|
||||||
continue
|
step,
|
||||||
|
&action,
|
||||||
|
actorID,
|
||||||
|
req.Notes,
|
||||||
|
); err != nil {
|
||||||
|
|
||||||
|
lower := strings.ToLower(err.Error())
|
||||||
|
if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return false, err
|
s.Log.Infof("ignored duplicate approval for kandang %d: %v", approvableID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedQuantity := productWarehouse.Quantity + d.Quantity
|
if action == entity.ApprovalActionApproved {
|
||||||
if err := productWarehouseRepo.PatchOne(c.Context(), productWarehouse.Id, map[string]any{"quantity": updatedQuantity}, nil); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := projectChickinDetailRepo.DeleteMany(c.Context(), func(db *gorm.DB) *gorm.DB {
|
var chickins []entity.ProjectChickin
|
||||||
return db.Where("project_chickin_id = ?", chickin.Id)
|
if err := chickinRepoTx.DB().WithContext(c.Context()).Where("project_flock_kandang_id = ?", approvableID).Find(&chickins).Error; err != nil {
|
||||||
}); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
restored, err := restoreFromDetails()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !restored {
|
for _, chickin := range chickins {
|
||||||
projectflockkandang, err := projectFlockKandangRepo.GetByID(c.Context(), population.ProjectFlockKandangId)
|
population := &entity.ProjectFlockPopulation{
|
||||||
if err != nil {
|
ProjectChickinId: chickin.Id,
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
ProductWarehouseId: chickin.ProductWarehouseId,
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Project flock kandang not found")
|
TotalQty: chickin.PendingUsageQty,
|
||||||
|
TotalUsedQty: 0,
|
||||||
|
Notes: chickin.Notes,
|
||||||
|
CreatedBy: actorID,
|
||||||
}
|
}
|
||||||
|
if err := ProjectFlockPopulationRepotx.CreateOne(c.Context(), population, nil); err != nil {
|
||||||
|
lower := strings.ToLower(err.Error())
|
||||||
|
if !(strings.Contains(lower, "duplicate") || strings.Contains(lower, "unique constraint") || strings.Contains(lower, "23505")) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
s.Log.Infof("ignored duplicate population for chickin %d: %v", chickin.Id, err)
|
||||||
warehouse, err := warehouseRepoTx.GetByKandangID(c.Context(), projectflockkandang.KandangId)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Warehouse not found for kandang")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
productWarehouse, err := productWarehouseRepo.GetLatestByCategoryCodeAndWarehouseID(c.Context(), "DOC", warehouse.Id)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Product Warehouse not found for the given Project Flock and Warehouse")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedQuantity := productWarehouse.Quantity + chickin.Quantity
|
|
||||||
|
|
||||||
if err := productWarehouseRepo.PatchOne(c.Context(), productWarehouse.Id, map[string]any{"quantity": updatedQuantity}, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := chickinRepo.DeleteOne(c.Context(), id); err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ferr, ok := err.(*fiber.Error); ok {
|
if fiberErr, ok := err.(*fiber.Error); ok {
|
||||||
return ferr
|
return nil, fiberErr
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *chickinService) Approve(c *fiber.Ctx, id uint) error {
|
|
||||||
|
|
||||||
// todo: ini contoh akhir jika sudah approved
|
|
||||||
|
|
||||||
chickin, err := s.Repository.GetByID(c.Context(), id, nil)
|
|
||||||
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Chickin not found")
|
||||||
}
|
}
|
||||||
if err != nil {
|
s.Log.Errorf("Failed to record approval for chickins %+v: %+v", approvableIDs, err)
|
||||||
s.Log.Errorf("Failed get chickin by id: %+v", err)
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record approval")
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
population, err := s.ProjectflockPopulationRepo.GetByProjectFlockKandangID(c.Context(), chickin.ProjectFlockKandangId)
|
updated := make([]entity.ProjectChickin, 0)
|
||||||
if err != nil {
|
for _, kandangID := range approvableIDs {
|
||||||
s.Log.Errorf("Failed to get project flock population: %+v", err)
|
var chickins []entity.ProjectChickin
|
||||||
return err
|
if err := s.Repository.DB().WithContext(c.Context()).Where("project_flock_kandang_id = ?", kandangID).Find(&chickins).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
updated = append(updated, chickins...)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.ProjectflockPopulationRepo.PatchOne(c.Context(), population.Id, map[string]any{
|
return updated, nil
|
||||||
"reserved_quantity": population.ReservedQuantity - chickin.Quantity,
|
|
||||||
"initial_quantity": population.InitialQuantity + chickin.Quantity,
|
|
||||||
"current_quantity": population.CurrentQuantity + chickin.Quantity,
|
|
||||||
}, nil)
|
|
||||||
if err != nil {
|
|
||||||
s.Log.Errorf("Failed to update project flock population: %+v", err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
func uniqueUintSlice(values []uint) []uint {
|
||||||
|
seen := make(map[uint]struct{}, len(values))
|
||||||
|
result := make([]uint, 0, len(values))
|
||||||
|
for _, v := range values {
|
||||||
|
if _, ok := seen[v]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[v] = struct{}{}
|
||||||
|
result = append(result, v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package validation
|
|||||||
type Create struct {
|
type Create struct {
|
||||||
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"`
|
ProjectFlockKandangId uint `json:"project_flock_kandang_id" validate:"required,number,min=1"`
|
||||||
ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"`
|
ChickInDate string `json:"chick_in_date" validate:"required,datetime=2006-01-02"`
|
||||||
Note string `json:"note" validate:"omitempty`
|
Note string `json:"note" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
@@ -16,3 +16,9 @@ type Query struct {
|
|||||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
|
||||||
ProjectFlockKandangId uint `query:"project_flock_kandang_id" validate:"omitempty,number,min=1"`
|
ProjectFlockKandangId uint `query:"project_flock_kandang_id" validate:"omitempty,number,min=1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Approve struct {
|
||||||
|
Action string `json:"action" validate:"required_strict"`
|
||||||
|
ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"`
|
||||||
|
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type ProjectflockRepository interface {
|
|||||||
GetActiveByFlock(ctx context.Context, flockID uint) (*entity.ProjectFlock, error)
|
GetActiveByFlock(ctx context.Context, flockID uint) (*entity.ProjectFlock, error)
|
||||||
GetMaxPeriodByFlock(ctx context.Context, flockID uint) (int, error)
|
GetMaxPeriodByFlock(ctx context.Context, flockID uint) (int, error)
|
||||||
GetNextPeriodForFlock(ctx context.Context, flockID uint) (int, error)
|
GetNextPeriodForFlock(ctx context.Context, flockID uint) (int, error)
|
||||||
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectflockRepositoryImpl struct {
|
type ProjectflockRepositoryImpl struct {
|
||||||
@@ -28,6 +29,10 @@ func NewProjectflockRepository(db *gorm.DB) ProjectflockRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ProjectflockRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.ProjectFlock](ctx, r.DB(), id)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ProjectflockRepositoryImpl) GetAllByFlock(ctx context.Context, flockID uint) ([]entity.ProjectFlock, error) {
|
func (r *ProjectflockRepositoryImpl) GetAllByFlock(ctx context.Context, flockID uint) ([]entity.ProjectFlock, error) {
|
||||||
var records []entity.ProjectFlock
|
var records []entity.ProjectFlock
|
||||||
if err := r.DB().WithContext(ctx).
|
if err := r.DB().WithContext(ctx).
|
||||||
|
|||||||
+5
@@ -3,6 +3,7 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -14,6 +15,7 @@ type ProjectFlockKandangRepository interface {
|
|||||||
DeleteMany(ctx context.Context, projectFlockID uint, kandangIDs []uint) error
|
DeleteMany(ctx context.Context, projectFlockID uint, kandangIDs []uint) error
|
||||||
GetAll(ctx context.Context) ([]entity.ProjectFlockKandang, error)
|
GetAll(ctx context.Context) ([]entity.ProjectFlockKandang, error)
|
||||||
WithTx(tx *gorm.DB) ProjectFlockKandangRepository
|
WithTx(tx *gorm.DB) ProjectFlockKandangRepository
|
||||||
|
IdExists(ctx context.Context, id uint) (bool, error)
|
||||||
DB() *gorm.DB
|
DB() *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +69,9 @@ func (r *projectFlockKandangRepositoryImpl) WithTx(tx *gorm.DB) ProjectFlockKand
|
|||||||
func (r *projectFlockKandangRepositoryImpl) DB() *gorm.DB {
|
func (r *projectFlockKandangRepositoryImpl) DB() *gorm.DB {
|
||||||
return r.db
|
return r.db
|
||||||
}
|
}
|
||||||
|
func (r *projectFlockKandangRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
||||||
|
return repository.Exists[entity.ProjectFlockKandang](ctx, r.db, id)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error) {
|
func (r *projectFlockKandangRepositoryImpl) GetByID(ctx context.Context, id uint) (*entity.ProjectFlockKandang, error) {
|
||||||
record := new(entity.ProjectFlockKandang)
|
record := new(entity.ProjectFlockKandang)
|
||||||
|
|||||||
@@ -670,18 +670,19 @@ func (s *recordingService) getPreviousRecording(tx *gorm.DB, projectFlockKandang
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) getTotalChick(tx *gorm.DB, projectFlockKandangId uint) (int64, error) {
|
func (s *recordingService) getTotalChick(tx *gorm.DB, projectFlockKandangId uint) (int64, error) {
|
||||||
var population entity.ProjectFlockPopulation
|
// var population entity.ProjectFlockPopulation
|
||||||
err := tx.
|
// err := tx.
|
||||||
Where("project_flock_kandang_id = ?", projectFlockKandangId).
|
// Where("project_flock_kandang_id = ?", projectFlockKandangId).
|
||||||
Order("created_at DESC").
|
// Order("created_at DESC").
|
||||||
First(&population).Error
|
// First(&population).Error
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
// if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return 0, nil
|
// return 0, nil
|
||||||
}
|
// }
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return 0, err
|
// return 0, err
|
||||||
}
|
// }
|
||||||
return int64(math.Round(population.InitialQuantity)), nil
|
//todo : nanti ganti lagi mas saya hardcode dulu
|
||||||
|
return int64(math.Round(1000)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *recordingService) getAverageBodyWeight(tx *gorm.DB, recordingID uint) (float64, error) {
|
func (s *recordingService) getAverageBodyWeight(tx *gorm.DB, recordingID uint) (float64, error) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
|
||||||
rTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
|
rTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/repositories"
|
||||||
sTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
|
sTransferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services"
|
||||||
|
|
||||||
@@ -17,10 +18,11 @@ type TransferLayingModule struct{}
|
|||||||
func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
func (TransferLayingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *validator.Validate) {
|
||||||
transferLayingRepo := rTransferLaying.NewTransferLayingRepository(db)
|
transferLayingRepo := rTransferLaying.NewTransferLayingRepository(db)
|
||||||
userRepo := rUser.NewUserRepository(db)
|
userRepo := rUser.NewUserRepository(db)
|
||||||
|
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
|
||||||
|
projectFlockKandangRepo := rProjectFlock.NewProjectFlockKandangRepository(db)
|
||||||
|
|
||||||
transferLayingService := sTransferLaying.NewTransferLayingService(transferLayingRepo, validate)
|
transferLayingService := sTransferLaying.NewTransferLayingService(transferLayingRepo, projectFlockRepo, projectFlockKandangRepo, validate)
|
||||||
userService := sUser.NewUserService(userRepo, validate)
|
userService := sUser.NewUserService(userRepo, validate)
|
||||||
|
|
||||||
TransferLayingRoutes(router, userService, transferLayingService)
|
TransferLayingRoutes(router, userService, transferLayingService)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,24 @@ const (
|
|||||||
WarehouseTypeKandang WarehouseType = "KANDANG"
|
WarehouseTypeKandang WarehouseType = "KANDANG"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// Stock log
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
type StockLogTransactionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StockLogTransactionTypeIncrease StockLogTransactionType = "INCREASE"
|
||||||
|
StockLogTransactionTypeDecrease StockLogTransactionType = "DECREASE"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StockLogType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
StockLogTypeAdjustment StockLogType = "ADJUSTMENT"
|
||||||
|
StockLogTypeTransfer StockLogType = "TRANSFER"
|
||||||
|
)
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// WarehouseType
|
// WarehouseType
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
@@ -140,6 +158,20 @@ var ProjectFlockApprovalSteps = map[approvalutils.ApprovalStep]string{
|
|||||||
ProjectFlockStepAktif: "Aktif",
|
ProjectFlockStepAktif: "Aktif",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// Project Flock Kandang Approval
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
const (
|
||||||
|
ApprovalWorkflowProjectFlockKandang approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("PROJECT_FLOCK_KANDANGS")
|
||||||
|
ProjectFlockKandangStepPengajuan approvalutils.ApprovalStep = 1
|
||||||
|
ProjectFlockKandangStepDisetujui approvalutils.ApprovalStep = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var ProjectFlockKandangApprovalSteps = map[approvalutils.ApprovalStep]string{
|
||||||
|
ProjectFlockKandangStepPengajuan: "Pengajuan",
|
||||||
|
ProjectFlockKandangStepDisetujui: "Disetujui",
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// Validators
|
// Validators
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user