diff --git a/internal/entities/project_budget.go b/internal/entities/project_budget.go index 23c521ac..1c339bd5 100644 --- a/internal/entities/project_budget.go +++ b/internal/entities/project_budget.go @@ -5,10 +5,12 @@ import ( ) type ProjectBudget struct { - Id uint `gorm:"primaryKey"` - Qty float64 `gorm:"type:numeric(15,3);not null"` - Price float64 `gorm:"type:numeric(15,3);not null"` - CreatedAt time.Time `gorm:"autoCreateTime"` + Id uint `gorm:"primaryKey"` + ProjectFlockId uint `gorm:"not null"` + NonstockId uint `gorm:"not null"` + Qty float64 `gorm:"type:numeric(15,3);not null"` + Price float64 `gorm:"type:numeric(15,3);not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` Nonstock *Nonstock `gorm:"foreignKey:Id;references:Id"` ProjectFlock *ProjectFlock `gorm:"foreignKey:Id;references:Id"` diff --git a/internal/entities/projectflock.go b/internal/entities/projectflock.go index e8745455..0a92b54b 100644 --- a/internal/entities/projectflock.go +++ b/internal/entities/projectflock.go @@ -24,6 +24,7 @@ type ProjectFlock struct { CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` Kandangs []Kandang `gorm:"many2many:project_flock_kandangs;joinTableForeignKey:project_flock_id;joinTableReferences:kandang_id" json:"kandangs,omitempty"` KandangHistory []ProjectFlockKandang `gorm:"foreignKey:ProjectFlockId;references:Id" json:"-"` + Budgets []ProjectBudget `gorm:"foreignKey:ProjectFlockId;references:Id" json:"-"` LatestApproval *Approval `gorm:"-" json:"-"` } diff --git a/internal/modules/production/project_flocks/dto/projectflock.dto.go b/internal/modules/production/project_flocks/dto/projectflock.dto.go index 8324dd71..0922b160 100644 --- a/internal/modules/production/project_flocks/dto/projectflock.dto.go +++ b/internal/modules/production/project_flocks/dto/projectflock.dto.go @@ -9,6 +9,7 @@ import ( fcrDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/fcrs/dto" kandangDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/dto" locationDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/dto" + nonstockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/nonstocks/dto" // pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" @@ -24,15 +25,16 @@ type ProjectFlockRelationDTO struct { type ProjectFlockListDTO struct { ProjectFlockRelationDTO - Area *areaDTO.AreaRelationDTO `json:"area,omitempty"` - Category string `json:"category"` - Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"` - Location *locationDTO.LocationRelationDTO `json:"location,omitempty"` - Kandangs []KandangWithProjectFlockIdDTO `json:"kandangs,omitempty"` - CreatedUser *userDTO.UserRelationDTO `json:"created_user"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Approval approvalDTO.ApprovalRelationDTO `json:"approval"` + Area *areaDTO.AreaRelationDTO `json:"area,omitempty"` + Category string `json:"category"` + Fcr *fcrDTO.FcrRelationDTO `json:"fcr,omitempty"` + Location *locationDTO.LocationRelationDTO `json:"location,omitempty"` + Kandangs []KandangWithProjectFlockIdDTO `json:"kandangs,omitempty"` + ProjectBudgets []ProjectBudgetDTO `json:"project_budgets,omitempty"` + CreatedUser *userDTO.UserRelationDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Approval approvalDTO.ApprovalRelationDTO `json:"approval"` } type KandangWithProjectFlockIdDTO struct { @@ -51,6 +53,13 @@ type KandangPeriodSummaryDTO struct { Period int `json:"period"` } +type ProjectBudgetDTO struct { + Id uint `json:"id"` + Qty float64 `json:"qty"` + Price float64 `json:"price"` + Nonstock *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"` +} + func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectFlockListDTO { var createdUser *userDTO.UserRelationDTO if e.CreatedUser.Id != 0 { @@ -110,6 +119,7 @@ func ToProjectFlockListDTOWithPeriod(e entity.ProjectFlock, period int) ProjectF ProjectFlockRelationDTO: createProjectFlockRelationDTO(e, period), Area: areaSummary, Kandangs: kandangSummaries, + ProjectBudgets: ToProjectBudgetDTOs(e.Budgets), Category: e.Category, Fcr: fcrSummary, Location: locationSummary, @@ -184,3 +194,26 @@ func createProjectFlockRelationDTO(e entity.ProjectFlock, period int) ProjectFlo FlockName: e.FlockName, } } + +func ToProjectBudgetDTO(e entity.ProjectBudget) ProjectBudgetDTO { + var nonstockRef *nonstockDTO.NonstockRelationDTO + if e.Nonstock != nil && e.Nonstock.Id != 0 { + mapped := nonstockDTO.ToNonstockRelationDTO(*e.Nonstock) + nonstockRef = &mapped + } + + return ProjectBudgetDTO{ + Id: e.Id, + Qty: e.Qty, + Price: e.Price, + Nonstock: nonstockRef, + } +} + +func ToProjectBudgetDTOs(e []entity.ProjectBudget) []ProjectBudgetDTO { + result := make([]ProjectBudgetDTO, len(e)) + for i, r := range e { + result[i] = ToProjectBudgetDTO(r) + } + return result +} diff --git a/internal/modules/production/project_flocks/module.go b/internal/modules/production/project_flocks/module.go index 4fd932a4..631aef58 100644 --- a/internal/modules/production/project_flocks/module.go +++ b/internal/modules/production/project_flocks/module.go @@ -13,6 +13,7 @@ import ( rFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/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" + rProjectBudget "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" rProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" sProjectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services" @@ -31,6 +32,7 @@ func (ProjectflockModule) RegisterRoutes(router fiber.Router, db *gorm.DB, valid projectflockKandangRepo := rProjectflock.NewProjectFlockKandangRepository(db) warehouseRepo := rWarehouse.NewWarehouseRepository(db) productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db) + projectBudgetRepo := rProjectBudget.NewProjectBudgetRepository(db) userRepo := rUser.NewUserRepository(db) approvalRepo := commonRepo.NewApprovalRepository(db) @@ -39,7 +41,7 @@ func (ProjectflockModule) RegisterRoutes(router fiber.Router, db *gorm.DB, valid panic(fmt.Sprintf("failed to register project flock approval workflow: %v", err)) } - projectflockService := sProjectflock.NewProjectflockService(projectflockRepo, flockRepo, kandangRepo, projectflockKandangRepo, warehouseRepo, productWarehouseRepo, approvalService, validate) + projectflockService := sProjectflock.NewProjectflockService(projectflockRepo, flockRepo, kandangRepo, projectflockKandangRepo, warehouseRepo, productWarehouseRepo, projectBudgetRepo, approvalService, validate) userService := sUser.NewUserService(userRepo, validate) ProjectflockRoutes(router, userService, projectflockService) diff --git a/internal/modules/production/project_flocks/repositories/projectflock.repository.go b/internal/modules/production/project_flocks/repositories/projectflock.repository.go index eede3638..b46cdb5c 100644 --- a/internal/modules/production/project_flocks/repositories/projectflock.repository.go +++ b/internal/modules/production/project_flocks/repositories/projectflock.repository.go @@ -54,7 +54,9 @@ func (r *ProjectflockRepositoryImpl) WithDefaultRelations() func(*gorm.DB) *gorm Preload("Location"). Preload("Kandangs"). Preload("KandangHistory"). - Preload("KandangHistory.Kandang") + Preload("KandangHistory.Kandang"). + Preload("Budgets"). + Preload("Budgets.Nonstock") } } diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index 827e5b19..26e4fdc6 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -16,6 +16,7 @@ import ( flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories" kandangRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" warehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" + projectBudgetRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" pfutils "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/utils" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations" @@ -49,6 +50,7 @@ type projectflockService struct { KandangRepo kandangRepository.KandangRepository WarehouseRepo warehouseRepository.WarehouseRepository ProductWarehouseRepo productWarehouseRepository.ProductWarehouseRepository + ProjectBudgetRepo projectBudgetRepository.ProjectBudgetRepository PivotRepo repository.ProjectFlockKandangRepository ApprovalSvc commonSvc.ApprovalService approvalWorkflow approvalutils.ApprovalWorkflowKey @@ -67,8 +69,10 @@ func NewProjectflockService( pivotRepo repository.ProjectFlockKandangRepository, warehouseRepo warehouseRepository.WarehouseRepository, productWarehouseRepo productWarehouseRepository.ProductWarehouseRepository, + projectBudgetRepo projectBudgetRepository.ProjectBudgetRepository, approvalSvc commonSvc.ApprovalService, validate *validator.Validate, + ) ProjectflockService { return &projectflockService{ Log: utils.Log, @@ -289,7 +293,6 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (* err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { projectRepo := repository.NewProjectflockRepository(dbTransaction) - // Generate unique flock name (sequential per base name, starting from 1) generatedName, _, err := s.generateSequentialFlockName(c.Context(), projectRepo, canonicalBase, 1, nil) if err != nil { return err @@ -300,7 +303,6 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (* return err } - // Compute period per kandang so every kandang maintains its own cycle history. periods, err := projectRepo.GetNextPeriodsForKandangs(c.Context(), kandangIDs) if err != nil { return err @@ -309,6 +311,10 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (* return err } + if err := s.UpsertProjectBudget(c.Context(), dbTransaction, createBody.Id, req.ProjectBudgets); err != nil { + return err + } + action := entity.ApprovalActionCreated approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction)) _, err = approvalSvcTx.CreateApproval( @@ -1044,3 +1050,35 @@ func (s projectflockService) kandangRepoWithTx(tx *gorm.DB) kandangRepository.Ka } return kandangRepository.NewKandangRepository(s.Repository.DB()) } + +func (s projectflockService) UpsertProjectBudget(ctx context.Context, dbTransaction *gorm.DB, projectFlockID uint, budgets []validation.ProjectBudget) error { + + if len(budgets) == 0 { + return nil + } + + budgetRepo := projectBudgetRepository.NewProjectBudgetRepository(dbTransaction) + + if err := budgetRepo.DeleteMany(ctx, func(q *gorm.DB) *gorm.DB { + return q.Where("project_flock_id = ?", projectFlockID) + }); err != nil && err != gorm.ErrRecordNotFound { + return err + } + + records := make([]*entity.ProjectBudget, 0, len(budgets)) + for _, b := range budgets { + records = append(records, &entity.ProjectBudget{ + ProjectFlockId: projectFlockID, + NonstockId: b.NonstockId, + Price: b.Price, + Qty: b.Qty, + }) + } + + if err := budgetRepo.CreateMany(ctx, records, nil); err != nil { + return err + } + + return nil + +} diff --git a/internal/modules/production/project_flocks/validations/projectflock.validation.go b/internal/modules/production/project_flocks/validations/projectflock.validation.go index 33f20725..bd2c3231 100644 --- a/internal/modules/production/project_flocks/validations/projectflock.validation.go +++ b/internal/modules/production/project_flocks/validations/projectflock.validation.go @@ -1,12 +1,13 @@ package validation type Create struct { - FlockName string `json:"flock_name" validate:"required_strict"` - AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"` - Category string `json:"category" validate:"required_strict"` - FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"` - LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"` - KandangIds []uint `json:"kandang_ids" validate:"required,min=1,dive,gt=0"` + FlockName string `json:"flock_name" validate:"required_strict"` + AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"` + Category string `json:"category" validate:"required_strict"` + FcrId uint `json:"fcr_id" validate:"required_strict,number,gt=0"` + LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"` + KandangIds []uint `json:"kandang_ids" validate:"required,min=1,dive,gt=0"` + ProjectBudgets []ProjectBudget `json:"project_budgets" validate:"required,min=1,dive"` } type Update struct { @@ -36,3 +37,9 @@ type Approve struct { ApprovableIds []uint `json:"approvable_ids" validate:"required_strict,min=1,dive,gt=0"` Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` } + +type ProjectBudget struct { + NonstockId uint `json:"nonstock_id" validate:"required_strict,number,gt=0"` + Price float64 `json:"price" validate:"required_strict,number,gt=0"` + Qty float64 `json:"qty" validate:"required_strict,number,gt=0"` +}