package dto import ( "encoding/json" "fmt" "time" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" approvalDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/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" supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto" userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" ) // === Base DTO === type ExpenseBaseDTO struct { Id uint64 `json:"id"` ReferenceNumber string `json:"reference_number"` PoNumber *string `json:"po_number"` Category string `json:"category"` Documents []string `json:"documents,omitempty"` ExpenseDate time.Time `json:"expense_date"` GrandTotal float64 `json:"grand_total"` Location *locationDTO.LocationBaseDTO `json:"location,omitempty"` } // === List DTO (untuk GetAll) === type ExpenseListDTO struct { ExpenseBaseDTO CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` LatestApproval *approvalDTO.ApprovalBaseDTO `json:"latest_approval,omitempty"` } // === Detail DTO (untuk GetOne) === type ExpenseDetailDTO struct { ExpenseBaseDTO Supplier *supplierDTO.SupplierBaseDTO `json:"supplier,omitempty"` CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"` Kandangs []KandangGroupDTO `json:"kandangs,omitempty"` TotalPengajuan float64 `json:"total_pengajuan"` TotalRealisasi float64 `json:"total_realisasi"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` LatestApproval *approvalDTO.ApprovalBaseDTO `json:"latest_approval,omitempty"` } // === Nested DTO === type ExpenseNonstockDTO struct { Id uint64 `json:"id"` Qty float64 `json:"qty"` UnitPrice float64 `json:"unit_price"` TotalPrice float64 `json:"total_price"` Note *string `json:"note,omitempty"` Nonstock *nonstockDTO.NonstockBaseDTO `json:"nonstock,omitempty"` ProjectFlockKandang *ProjectFlockKandangNestedDTO `json:"project_flock_kandang,omitempty"` Realization *ExpenseRealizationDTO `json:"realization,omitempty"` } type ProjectFlockKandangNestedDTO struct { Id uint64 `json:"id"` KandangId uint64 `json:"kandang_id"` } type ExpenseRealizationDTO struct { Id uint64 `json:"id"` Qty float64 `json:"qty"` UnitPrice float64 `json:"unit_price"` TotalPrice float64 `json:"total_price"` Date time.Time `json:"date"` Note *string `json:"note,omitempty"` } type RealizationOnlyDTO struct { Id uint64 `json:"id"` Qty float64 `json:"qty"` UnitPrice float64 `json:"unit_price"` TotalPrice float64 `json:"total_price"` Date time.Time `json:"date"` Note *string `json:"note,omitempty"` Nonstock *nonstockDTO.NonstockBaseDTO `json:"nonstock,omitempty"` ProjectFlockKandang *ProjectFlockKandangNestedDTO `json:"project_flock_kandang,omitempty"` } type KandangDTO struct { Id uint64 `json:"id"` KandangId uint64 `json:"kandang_id"` Name string `json:"name,omitempty"` } type KandangGroupDTO struct { Id uint64 `json:"id"` KandangId uint64 `json:"kandang_id"` Name string `json:"name,omitempty"` Pengajuans []ExpenseNonstockDTO `json:"pengajuans,omitempty"` Realisasi []RealizationOnlyDTO `json:"realisasi,omitempty"` } // === Helper Functions === func getStringValue(s *string) string { if s == nil { return "" } return *s } // === Mapper Functions === func ToExpenseBaseDTO(e *entity.Expense) ExpenseBaseDTO { var documents []string var location *locationDTO.LocationBaseDTO // Parse document paths from JSON if available if e.DocumentPath.Valid && e.DocumentPath.String != "" { if err := json.Unmarshal([]byte(e.DocumentPath.String), &documents); err == nil { // Successfully parsed documents } } // Get location from the first kandang if available if len(e.Nonstocks) > 0 && e.Nonstocks[0].ProjectFlockKandang != nil { if e.Nonstocks[0].ProjectFlockKandang.Kandang.Location.Id != 0 { mapped := locationDTO.ToLocationBaseDTO(e.Nonstocks[0].ProjectFlockKandang.Kandang.Location) location = &mapped } } return ExpenseBaseDTO{ Id: e.Id, ReferenceNumber: getStringValue(e.ReferenceNumber), PoNumber: e.PoNumber, // Keep as pointer to allow null in JSON Category: e.Category, Documents: documents, ExpenseDate: e.ExpenseDate, GrandTotal: e.GrandTotal, Location: location, } } func ToExpenseListDTO(e *entity.Expense) ExpenseListDTO { var createdUser *userDTO.UserBaseDTO if e.CreatedUser != nil && e.CreatedUser.Id != 0 { mapped := userDTO.ToUserBaseDTO(*e.CreatedUser) createdUser = &mapped } var latestApproval *approvalDTO.ApprovalBaseDTO if e.LatestApproval != nil { mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval) latestApproval = &mapped } return ExpenseListDTO{ ExpenseBaseDTO: ToExpenseBaseDTO(e), CreatedUser: createdUser, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt, LatestApproval: latestApproval, } } func ToExpenseListDTOs(expenses []entity.Expense) []ExpenseListDTO { result := make([]ExpenseListDTO, len(expenses)) for i, expense := range expenses { result[i] = ToExpenseListDTO(&expense) } return result } func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO { var createdUser *userDTO.UserBaseDTO if e.CreatedUser != nil && e.CreatedUser.Id != 0 { mapped := userDTO.ToUserBaseDTO(*e.CreatedUser) createdUser = &mapped } var supplier *supplierDTO.SupplierBaseDTO if e.Supplier != nil && e.Supplier.Id != 0 { mapped := supplierDTO.ToSupplierBaseDTO(*e.Supplier) supplier = &mapped } var latestApproval *approvalDTO.ApprovalBaseDTO if e.LatestApproval != nil { mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval) latestApproval = &mapped } var pengajuans []ExpenseNonstockDTO var realisasi []RealizationOnlyDTO if len(e.Nonstocks) > 0 { pengajuans = make([]ExpenseNonstockDTO, 0) realisasi = make([]RealizationOnlyDTO, 0) for _, ns := range e.Nonstocks { // Create DTO without realization for pengajuans pengajuanDTO := ToExpenseNonstockDTO(ns) pengajuanDTO.Realization = nil // Remove realization from pengajuan pengajuans = append(pengajuans, pengajuanDTO) // Create separate DTO with realization data if it exists if ns.Realization != nil && ns.Realization.Id != 0 { // Create realization DTO with only realization data var nonstock *nonstockDTO.NonstockBaseDTO if ns.Nonstock != nil && ns.Nonstock.Id != 0 { mapped := nonstockDTO.ToNonstockBaseDTO(*ns.Nonstock) nonstock = &mapped } var projectFlockKandang *ProjectFlockKandangNestedDTO if ns.ProjectFlockKandang != nil && ns.ProjectFlockKandang.Id != 0 { projectFlockKandang = &ProjectFlockKandangNestedDTO{ Id: uint64(ns.ProjectFlockKandang.Id), KandangId: uint64(ns.ProjectFlockKandang.KandangId), } } realisasiDTO := RealizationOnlyDTO{ Id: ns.Realization.Id, Qty: ns.Realization.RealizationQty, UnitPrice: ns.Realization.RealizationUnitPrice, TotalPrice: ns.Realization.RealizationTotalPrice, Date: ns.Realization.RealizationDate, Note: ns.Realization.Note, Nonstock: nonstock, ProjectFlockKandang: projectFlockKandang, } realisasi = append(realisasi, realisasiDTO) } } } // Calculate total pengajuan and realisasi var totalPengajuan float64 for _, p := range pengajuans { totalPengajuan += p.TotalPrice } var totalRealisasi float64 for _, r := range realisasi { totalRealisasi += r.TotalPrice } // Group pengajuans and realisasi by kandang kandangMap := make(map[uint64]*KandangGroupDTO) // Process pengajuans for _, p := range pengajuans { if p.ProjectFlockKandang != nil { kandangId := p.ProjectFlockKandang.KandangId if kandangMap[kandangId] == nil { kandangMap[kandangId] = &KandangGroupDTO{ Id: p.ProjectFlockKandang.Id, KandangId: kandangId, Name: fmt.Sprintf("Kandang %d", kandangId), } } kandangMap[kandangId].Pengajuans = append(kandangMap[kandangId].Pengajuans, p) } } // Process realisasi for _, r := range realisasi { if r.ProjectFlockKandang != nil { kandangId := r.ProjectFlockKandang.KandangId if kandangMap[kandangId] == nil { kandangMap[kandangId] = &KandangGroupDTO{ Id: r.ProjectFlockKandang.Id, KandangId: kandangId, Name: fmt.Sprintf("Kandang %d", kandangId), } } kandangMap[kandangId].Realisasi = append(kandangMap[kandangId].Realisasi, r) } } // Convert map to slice var kandangs []KandangGroupDTO for _, k := range kandangMap { kandangs = append(kandangs, *k) } return ExpenseDetailDTO{ ExpenseBaseDTO: ToExpenseBaseDTO(e), Supplier: supplier, CreatedUser: createdUser, Kandangs: kandangs, TotalPengajuan: totalPengajuan, TotalRealisasi: totalRealisasi, CreatedAt: e.CreatedAt, UpdatedAt: e.UpdatedAt, LatestApproval: latestApproval, } } func ToExpenseNonstockDTO(ns entity.ExpenseNonstock) ExpenseNonstockDTO { var nonstock *nonstockDTO.NonstockBaseDTO if ns.Nonstock != nil && ns.Nonstock.Id != 0 { mapped := nonstockDTO.ToNonstockBaseDTO(*ns.Nonstock) nonstock = &mapped } var projectFlockKandang *ProjectFlockKandangNestedDTO if ns.ProjectFlockKandang != nil && ns.ProjectFlockKandang.Id != 0 { projectFlockKandang = &ProjectFlockKandangNestedDTO{ Id: uint64(ns.ProjectFlockKandang.Id), KandangId: uint64(ns.ProjectFlockKandang.KandangId), } } var realization *ExpenseRealizationDTO if ns.Realization != nil && ns.Realization.Id != 0 { mapped := ToExpenseRealizationDTO(*ns.Realization) realization = &mapped } return ExpenseNonstockDTO{ Id: ns.Id, Qty: ns.Qty, UnitPrice: ns.UnitPrice, TotalPrice: ns.TotalPrice, Note: ns.Note, Nonstock: nonstock, ProjectFlockKandang: projectFlockKandang, Realization: realization, } } func ToExpenseRealizationDTO(r entity.ExpenseRealization) ExpenseRealizationDTO { return ExpenseRealizationDTO{ Id: r.Id, Qty: r.RealizationQty, UnitPrice: r.RealizationUnitPrice, TotalPrice: r.RealizationTotalPrice, Date: r.RealizationDate, Note: r.Note, } }