package dto import ( "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" "gitlab.com/mbugroup/lti-api.git/internal/utils" ) // === DTO Structs === type ExpenseRelationDTO struct { Id uint64 `json:"id"` PoNumber string `json:"po_number"` TransactionDate time.Time `json:"transaction_date"` } type ExpenseBaseDTO struct { Id uint64 `json:"id"` ReferenceNumber string `json:"reference_number"` PoNumber string `json:"po_number"` Category string `json:"category"` Supplier *supplierDTO.SupplierRelationDTO `json:"supplier,omitempty"` RealizationDate *time.Time `json:"realization_date,omitempty"` TransactionDate time.Time `json:"transaction_date"` Location *locationDTO.LocationRelationDTO `json:"location,omitempty"` IsPaid bool `json:"is_paid"` } type ExpenseListDTO struct { ExpenseBaseDTO GrandTotal float64 `json:"grand_total"` CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval,omitempty"` } type ExpenseDetailDTO struct { ExpenseBaseDTO Documents []DocumentDTO `json:"documents"` RealizationDocs []DocumentDTO `json:"realization_docs"` 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"` CreatedUser *userDTO.UserRelationDTO `json:"created_user,omitempty"` LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval,omitempty"` } type ExpenseNonstockDTO struct { Id uint64 `json:"id"` ExpenseId *uint64 `json:"expense_id,omitempty"` ProjectFlockKandangId *uint64 `json:"project_flock_kandang_id,omitempty"` KandangId *uint64 `json:"kandang_id,omitempty"` NonstockId *uint64 `json:"nonstock_id,omitempty"` Qty float64 `json:"qty"` Price float64 `json:"price"` Notes string `json:"notes"` Nonstock *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"` CreatedAt time.Time `json:"created_at"` } type ExpenseRealizationDTO struct { Id uint64 `json:"id"` ExpenseNonstockId *uint64 `json:"expense_nonstock_id,omitempty"` Qty float64 `json:"qty"` Price float64 `json:"price"` Notes string `json:"notes"` Nonstock *nonstockDTO.NonstockRelationDTO `json:"nonstock,omitempty"` CreatedAt time.Time `json:"created_at"` } type KandangGroupDTO struct { Id uint64 `json:"id"` Name string `json:"name,omitempty"` Pengajuans []ExpenseNonstockDTO `json:"pengajuans,omitempty"` Realisasi []ExpenseRealizationDTO `json:"realisasi,omitempty"` } type DocumentDTO struct { ID uint64 `json:"id"` Path string `json:"path"` Name string `json:"name"` } // === MAPPERS === func ToExpenseRelationDTO(e entity.Expense) ExpenseRelationDTO { return ExpenseRelationDTO{ Id: e.Id, PoNumber: e.PoNumber, TransactionDate: e.TransactionDate, } } func ToExpenseBaseDTO(e *entity.Expense) ExpenseBaseDTO { var location *locationDTO.LocationRelationDTO var supplier *supplierDTO.SupplierRelationDTO var realizationDate *time.Time if !e.RealizationDate.IsZero() { realizationDate = &e.RealizationDate } if e.Location != nil && e.Location.Id != 0 { mapped := locationDTO.ToLocationRelationDTO(*e.Location) location = &mapped } if e.Supplier != nil && e.Supplier.Id != 0 { mapped := supplierDTO.ToSupplierRelationDTO(*e.Supplier) supplier = &mapped } return ExpenseBaseDTO{ Id: e.Id, ReferenceNumber: e.ReferenceNumber, PoNumber: e.PoNumber, Category: e.Category, Supplier: supplier, RealizationDate: realizationDate, TransactionDate: e.TransactionDate, Location: location, IsPaid: e.IsPaid, } } func ToExpenseListDTO(e entity.Expense) ExpenseListDTO { var createdUser *userDTO.UserRelationDTO if e.CreatedUser.Id != 0 { mapped := userDTO.ToUserRelationDTO(*e.CreatedUser) createdUser = &mapped } var latestApproval *approvalDTO.ApprovalRelationDTO if e.LatestApproval != nil { mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval) latestApproval = &mapped } grandTotal := calculateGrandTotal(&e) return ExpenseListDTO{ ExpenseBaseDTO: ToExpenseBaseDTO(&e), GrandTotal: grandTotal, 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 documents []DocumentDTO var realizationDocs []DocumentDTO var createdUser *userDTO.UserRelationDTO if e.CreatedUser != nil && e.CreatedUser.Id != 0 { mapped := userDTO.ToUserRelationDTO(*e.CreatedUser) createdUser = &mapped } var latestApproval *approvalDTO.ApprovalRelationDTO if e.LatestApproval != nil { mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval) latestApproval = &mapped } var pengajuans []ExpenseNonstockDTO var realisasi []ExpenseRealizationDTO for _, doc := range e.Documents { documents = append(documents, DocumentDTO{ ID: uint64(doc.Id), Path: doc.Path, Name: doc.Name, }) } for _, doc := range e.RealizationDocuments { realizationDocs = append(realizationDocs, DocumentDTO{ ID: uint64(doc.Id), Path: doc.Path, Name: doc.Name, }) } if len(e.Nonstocks) > 0 { pengajuans = make([]ExpenseNonstockDTO, 0) realisasi = make([]ExpenseRealizationDTO, 0) for _, ns := range e.Nonstocks { pengajuanDTO := ToExpenseNonstockDTO(ns) pengajuans = append(pengajuans, pengajuanDTO) if ns.Realization != nil { var nonstock *nonstockDTO.NonstockRelationDTO if ns.Nonstock != nil && ns.Nonstock.Id != 0 { mapped := nonstockDTO.ToNonstockRelationDTO(*ns.Nonstock) nonstock = &mapped } realisasiDTO := ExpenseRealizationDTO{ Id: ns.Realization.Id, ExpenseNonstockId: ns.Realization.ExpenseNonstockId, Qty: ns.Realization.Qty, Price: ns.Realization.Price, Notes: ns.Realization.Notes, Nonstock: nonstock, CreatedAt: ns.Realization.CreatedAt, } realisasi = append(realisasi, realisasiDTO) } } } var totalPengajuan float64 for _, p := range pengajuans { totalPengajuan += p.Qty * p.Price } var totalRealisasi float64 for _, r := range realisasi { totalRealisasi += r.Qty * r.Price } kandangs := ToKandangGroupDTO(pengajuans, realisasi, e.Nonstocks) return ExpenseDetailDTO{ ExpenseBaseDTO: ToExpenseBaseDTO(e), Documents: documents, RealizationDocs: realizationDocs, 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.NonstockRelationDTO if ns.Nonstock != nil && ns.Nonstock.Id != 0 { mapped := nonstockDTO.ToNonstockRelationDTO(*ns.Nonstock) nonstock = &mapped } return ExpenseNonstockDTO{ Id: ns.Id, ExpenseId: ns.ExpenseId, ProjectFlockKandangId: ns.ProjectFlockKandangId, KandangId: ns.KandangId, NonstockId: ns.NonstockId, Qty: ns.Qty, Price: ns.Price, Notes: ns.Notes, Nonstock: nonstock, CreatedAt: ns.CreatedAt, } } func ToKandangGroupDTO(pengajuans []ExpenseNonstockDTO, realisasi []ExpenseRealizationDTO, nonstocks []entity.ExpenseNonstock) []KandangGroupDTO { kandangMap := make(map[uint64]*KandangGroupDTO) var directPengajuans []ExpenseNonstockDTO var directRealisasi []ExpenseRealizationDTO for _, p := range pengajuans { var kandangId uint64 var kandangName string if p.KandangId != nil { kandangId = *p.KandangId for _, ns := range nonstocks { if ns.Id == p.Id && ns.Kandang != nil { kandangName = ns.Kandang.Name break } } } if kandangId > 0 { if kandangMap[kandangId] == nil { kandangMap[kandangId] = &KandangGroupDTO{ Id: kandangId, Name: kandangName, Pengajuans: []ExpenseNonstockDTO{}, Realisasi: []ExpenseRealizationDTO{}, } } kandangMap[kandangId].Pengajuans = append(kandangMap[kandangId].Pengajuans, p) } else { directPengajuans = append(directPengajuans, p) } } for _, r := range realisasi { var kandangId uint64 var kandangName string for _, ns := range nonstocks { if ns.Realization != nil && ns.Realization.Id == r.Id && ns.Kandang != nil { kandangId = uint64(ns.Kandang.Id) kandangName = ns.Kandang.Name break } } if kandangId > 0 { if kandangMap[kandangId] == nil { kandangMap[kandangId] = &KandangGroupDTO{ Id: kandangId, Name: kandangName, Pengajuans: []ExpenseNonstockDTO{}, Realisasi: []ExpenseRealizationDTO{}, } } kandangMap[kandangId].Realisasi = append(kandangMap[kandangId].Realisasi, r) } else { directRealisasi = append(directRealisasi, r) } } // If there are direct expenses (without kandang), add them as a special entry with id=0 if len(directPengajuans) > 0 || len(directRealisasi) > 0 { kandangMap[0] = &KandangGroupDTO{ Id: 0, Name: "", Pengajuans: directPengajuans, Realisasi: directRealisasi, } } var kandangs []KandangGroupDTO for _, k := range kandangMap { kandangs = append(kandangs, *k) } return kandangs } func calculateGrandTotal(expense *entity.Expense) float64 { useRealization := expense.LatestApproval != nil && expense.LatestApproval.StepNumber >= uint16(utils.ExpenseStepRealisasi) if useRealization { var total float64 for _, ns := range expense.Nonstocks { if ns.Realization != nil { total += ns.Realization.Qty * ns.Realization.Price } } return total } var total float64 for _, ns := range expense.Nonstocks { total += ns.Qty * ns.Price } return total }