feat[BE]: create GetOverhead API, and fixing chickin use newest productwarehouse schema

This commit is contained in:
aguhh18
2025-12-09 15:32:11 +07:00
parent 89b23b0653
commit 511e5501bb
12 changed files with 309 additions and 21 deletions
@@ -123,3 +123,25 @@ func (u *ClosingController) GetPenjualan(c *fiber.Ctx) error {
Data: dto.ToPenjualanRealisasiResponseDTO(projectFlock.Category, uint(projectFlockID), result),
})
}
func (u *ClosingController) GetOverhead(c *fiber.Ctx) error {
param := c.Params("project_flock_id")
projectFlockID, err := strconv.Atoi(param)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid Project Flock Id")
}
result, err := u.ClosingService.GetOverhead(c, uint(projectFlockID))
if err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Success{
Code: fiber.StatusOK,
Status: "success",
Message: "Get overhead successfully",
Data: result,
})
}
@@ -11,7 +11,6 @@ import (
)
// === Response DTO ===
type SalesDTO struct {
Id uint `json:"id"`
RealizationDate time.Time `json:"realization_date"`
@@ -0,0 +1,175 @@
package dto
import (
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
)
// === DTO Structs ===
type OverheadDTO struct {
ItemName string `json:"item_name"`
UOMName string `json:"uom_name"`
BudgetQuantity float64 `json:"budget_quantity"`
BudgetUnitPrice float64 `json:"budget_unit_price"`
BudgetTotalAmount float64 `json:"budget_total_amount"`
ActualDate string `json:"actual_date"`
ActualQuantity float64 `json:"actual_quantity"`
ActualUnitPrice float64 `json:"actual_unit_price"`
ActualTotalAmount float64 `json:"actual_total_amount"`
CostPerBird float64 `json:"cost_per_bird"`
}
type TotalDTO struct {
BudgetQuantity float64 `json:"budget_quantity"`
BudgetTotalAmount float64 `json:"budget_total_amount"`
ActualQuantity float64 `json:"actual_quantity"`
ActualTotalAmount float64 `json:"actual_total_amount"`
CostPerBird float64 `json:"cost_per_bird"`
}
type OverheadListDTO struct {
Total TotalDTO `json:"total"`
Overheads []OverheadDTO `json:"overheads"`
}
// === Mapper Functions ===
func ToOverheadDTO(budget *entity.ProjectBudget, realization *entity.ExpenseRealization) OverheadDTO {
if budget == nil && realization == nil {
return OverheadDTO{}
}
var itemName, itemUOM string
if budget != nil {
itemName, itemUOM = getItemInfo(budget.Nonstock)
}
if itemName == "" && realization != nil && realization.ExpenseNonstock != nil {
itemName, itemUOM = getItemInfo(realization.ExpenseNonstock.Nonstock)
}
dto := OverheadDTO{
ItemName: itemName,
UOMName: itemUOM,
}
if budget != nil {
dto.BudgetQuantity = budget.Qty
dto.BudgetUnitPrice = budget.Price
dto.BudgetTotalAmount = calculateTotal(budget.Qty, budget.Price)
}
if realization != nil {
dto.ActualQuantity = realization.Qty
dto.ActualUnitPrice = realization.Price
dto.ActualTotalAmount = calculateTotal(realization.Qty, realization.Price)
dto.ActualDate = formatRealizationDate(realization)
}
return dto
}
func ToOverheadListDTOs(budgets []entity.ProjectBudget, realizations []entity.ExpenseRealization, totalChickinQty float64) OverheadListDTO {
overheadsByNonstockID := make(map[uint]*OverheadDTO)
latestDateByNonstockID := make(map[uint]string)
for i := range budgets {
nonstockID := budgets[i].NonstockId
if overheadsByNonstockID[nonstockID] == nil {
overheadsByNonstockID[nonstockID] = &OverheadDTO{}
}
itemName, itemUOM := getItemInfo(budgets[i].Nonstock)
overheadsByNonstockID[nonstockID].ItemName = itemName
overheadsByNonstockID[nonstockID].UOMName = itemUOM
overheadsByNonstockID[nonstockID].BudgetQuantity = budgets[i].Qty
overheadsByNonstockID[nonstockID].BudgetUnitPrice = budgets[i].Price
overheadsByNonstockID[nonstockID].BudgetTotalAmount = calculateTotal(budgets[i].Qty, budgets[i].Price)
}
for i := range realizations {
if realizations[i].ExpenseNonstock == nil || realizations[i].ExpenseNonstock.NonstockId == nil {
continue
}
nonstockID := uint(*realizations[i].ExpenseNonstock.NonstockId)
if overheadsByNonstockID[nonstockID] == nil {
overheadsByNonstockID[nonstockID] = &OverheadDTO{}
}
overheadsByNonstockID[nonstockID].ActualQuantity += realizations[i].Qty
overheadsByNonstockID[nonstockID].ActualTotalAmount += calculateTotal(realizations[i].Qty, realizations[i].Price)
if overheadsByNonstockID[nonstockID].ItemName == "" {
itemName, itemUOM := getItemInfo(realizations[i].ExpenseNonstock.Nonstock)
overheadsByNonstockID[nonstockID].ItemName = itemName
overheadsByNonstockID[nonstockID].UOMName = itemUOM
}
realizationDateStr := formatRealizationDate(&realizations[i])
if realizationDateStr != "" {
if latestDateByNonstockID[nonstockID] == "" || realizationDateStr > latestDateByNonstockID[nonstockID] {
latestDateByNonstockID[nonstockID] = realizationDateStr
}
}
}
var totalBudgetQuantity, totalBudgetAmount, totalActualQuantity, totalActualAmount float64
overheadItems := make([]OverheadDTO, 0, len(overheadsByNonstockID))
for nonstockID, overhead := range overheadsByNonstockID {
overhead.ActualDate = latestDateByNonstockID[nonstockID]
overhead.CostPerBird = calculateCostPerBird(overhead.ActualTotalAmount, totalChickinQty)
if overhead.ActualQuantity > 0 {
overhead.ActualUnitPrice = overhead.ActualTotalAmount / overhead.ActualQuantity
}
totalBudgetQuantity += overhead.BudgetQuantity
totalBudgetAmount += overhead.BudgetTotalAmount
totalActualQuantity += overhead.ActualQuantity
totalActualAmount += overhead.ActualTotalAmount
overheadItems = append(overheadItems, *overhead)
}
return OverheadListDTO{
Total: TotalDTO{
BudgetQuantity: totalBudgetQuantity,
BudgetTotalAmount: totalBudgetAmount,
ActualQuantity: totalActualQuantity,
ActualTotalAmount: totalActualAmount,
CostPerBird: calculateCostPerBird(totalActualAmount, totalChickinQty),
},
Overheads: overheadItems,
}
}
// === Helper Functions ===
func getItemInfo(nonstock *entity.Nonstock) (string, string) {
if nonstock != nil && nonstock.Id != 0 {
return nonstock.Name, nonstock.Uom.Name
}
return "", ""
}
func calculateTotal(qty, price float64) float64 {
return qty * price
}
func calculateCostPerBird(totalPrice, totalChickinQty float64) float64 {
if totalChickinQty > 0 {
return totalPrice / totalChickinQty
}
return 0
}
func formatRealizationDate(realization *entity.ExpenseRealization) string {
if realization != nil && realization.ExpenseNonstock != nil && realization.ExpenseNonstock.Expense != nil {
if !realization.ExpenseNonstock.Expense.RealizationDate.IsZero() {
return realization.ExpenseNonstock.Expense.RealizationDate.Format("2006-01-02T15:04:05Z07:00")
}
}
return ""
}
+6 -1
View File
@@ -9,7 +9,9 @@ import (
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
rClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories"
sClosing "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/services"
rExpenseRealization "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
rMarketings "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
rChickin "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
rUser "gitlab.com/mbugroup/lti-api.git/internal/modules/users/repositories"
@@ -22,12 +24,15 @@ func (ClosingModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
closingRepo := rClosing.NewClosingRepository(db)
userRepo := rUser.NewUserRepository(db)
projectFlockRepo := rProjectFlock.NewProjectflockRepository(db)
projectBudgetRepo := rProjectFlock.NewProjectBudgetRepository(db)
marketingRepo := rMarketings.NewMarketingRepository(db)
marketingDeliveryProductRepo := rMarketings.NewMarketingDeliveryProductRepository(db)
expenseRealizationRepo := rExpenseRealization.NewExpenseRealizationRepository(db)
chickinRepo := rChickin.NewChickinRepository(db)
approvalRepo := commonRepo.NewApprovalRepository(db)
approvalService := commonSvc.NewApprovalService(approvalRepo)
closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, validate)
closingService := sClosing.NewClosingService(closingRepo, projectFlockRepo, marketingRepo, marketingDeliveryProductRepo, approvalService, expenseRealizationRepo, projectBudgetRepo, chickinRepo, validate)
userService := sUser.NewUserService(userRepo, validate)
ClosingRoutes(router, userService, closingService)
+2
View File
@@ -22,5 +22,7 @@ func ClosingRoutes(v1 fiber.Router, u user.UserService, s closing.ClosingService
route.Get("/", ctrl.GetAll)
route.Get("/:project_flock_id/penjualan", ctrl.GetPenjualan)
route.Get("/:project_flock_id/overhead", ctrl.GetOverhead)
route.Get("/:projectFlockId", ctrl.GetClosingSummary)
}
@@ -9,8 +9,10 @@ import (
"gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto"
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories"
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations"
expenseRealizationRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/repositories"
marketingDeliveryProductRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
marketingRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories"
chickinRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories"
projectflockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories"
"gitlab.com/mbugroup/lti-api.git/internal/utils"
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
@@ -26,6 +28,7 @@ type ClosingService interface {
GetProjectFlockByID(ctx *fiber.Ctx, id uint) (*entity.ProjectFlock, error)
GetPenjualan(ctx *fiber.Ctx, projectFlockID uint) ([]entity.MarketingDeliveryProduct, error)
GetClosingSummary(ctx *fiber.Ctx, projectFlockID uint) (*dto.ClosingSummaryDTO, error)
GetOverhead(ctx *fiber.Ctx, projectFlockID uint) (*dto.OverheadListDTO, error)
}
type closingService struct {
@@ -36,9 +39,12 @@ type closingService struct {
MarketingRepo marketingRepository.MarketingRepository
MarketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository
ApprovalSvc commonSvc.ApprovalService
ExpenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository
ProjectBudgetRepo projectflockRepository.ProjectBudgetRepository
ChickinRepo chickinRepository.ProjectChickinRepository
}
func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, validate *validator.Validate) ClosingService {
func NewClosingService(repo repository.ClosingRepository, projectFlockRepo projectflockRepository.ProjectflockRepository, marketingRepo marketingRepository.MarketingRepository, marketingDeliveryProductRepo marketingDeliveryProductRepository.MarketingDeliveryProductRepository, approvalSvc commonSvc.ApprovalService, expenseRealizationRepo expenseRealizationRepository.ExpenseRealizationRepository, projectBudgetRepo projectflockRepository.ProjectBudgetRepository, chickinRepo chickinRepository.ProjectChickinRepository, validate *validator.Validate) ClosingService {
return &closingService{
Log: utils.Log,
Validate: validate,
@@ -47,6 +53,9 @@ func NewClosingService(repo repository.ClosingRepository, projectFlockRepo proje
MarketingRepo: marketingRepo,
MarketingDeliveryProductRepo: marketingDeliveryProductRepo,
ApprovalSvc: approvalSvc,
ExpenseRealizationRepo: expenseRealizationRepo,
ProjectBudgetRepo: projectBudgetRepo,
ChickinRepo: chickinRepo,
}
}
@@ -188,3 +197,29 @@ func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID
return statusProject, statusClosing, nil
}
func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint) (*dto.OverheadListDTO, error) {
budgets, err := s.ProjectBudgetRepo.GetByProjectFlockID(c.Context(), projectFlockID)
if err != nil {
return nil, err
}
realizations, err := s.ExpenseRealizationRepo.GetByProjectFlockID(c.Context(), projectFlockID)
if err != nil {
return nil, err
}
chickins, err := s.ChickinRepo.GetByProjectFlockID(c.Context(), projectFlockID)
if err != nil {
return nil, err
}
var totalChickinQty float64
for _, chickin := range chickins {
totalChickinQty += chickin.UsageQty
}
result := dto.ToOverheadListDTOs(budgets, realizations, totalChickinQty)
return &result, nil
}