Feat[BE-261]: creating multiple Approval API, Update API, Delete API and some logic Adjustment

This commit is contained in:
aguhh18
2025-11-21 00:55:02 +07:00
parent b502751b4e
commit b8d1268dfa
8 changed files with 797 additions and 550 deletions
+120 -170
View File
@@ -2,7 +2,6 @@ package dto
import (
"encoding/json"
"fmt"
"time"
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
@@ -13,21 +12,18 @@ import (
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"`
Id uint64 `json:"id"`
ReferenceNumber string `json:"reference_number"`
PoNumber string `json:"po_number"`
Category string `json:"category"`
Supplier *supplierDTO.SupplierBaseDTO `json:"supplier,omitempty"`
RealizationDate *time.Time `json:"realization_date,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"`
@@ -36,95 +32,59 @@ type ExpenseListDTO struct {
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"`
Documents []DocumentDTO `json:"documents,omitempty"`
RealizationDocs []DocumentDTO `json:"realization_docs,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"`
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"`
}
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"`
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"`
}
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"`
Id uint64 `json:"id"`
KandangId uint64 `json:"kandang_id"`
Name string `json:"name,omitempty"`
Pengajuans []ExpenseNonstockDTO `json:"pengajuans,omitempty"`
Realisasi []ExpenseRealizationDTO `json:"realisasi,omitempty"`
}
// === Helper Functions ===
func getStringValue(s *string) string {
if s == nil {
return ""
}
return *s
type DocumentDTO struct {
ID uint64 `json:"id"`
Path string `json:"path"`
}
// === Mapper Functions ===
func ToExpenseBaseDTO(e *entity.Expense) ExpenseBaseDTO {
var documents []string
var location *locationDTO.LocationBaseDTO
var supplier *supplierDTO.SupplierBaseDTO
// 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
}
var realizationDate *time.Time
if !e.RealizationDate.IsZero() {
realizationDate = &e.RealizationDate
}
// 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)
@@ -132,12 +92,18 @@ func ToExpenseBaseDTO(e *entity.Expense) ExpenseBaseDTO {
}
}
if e.Supplier != nil && e.Supplier.Id != 0 {
mapped := supplierDTO.ToSupplierBaseDTO(*e.Supplier)
supplier = &mapped
}
return ExpenseBaseDTO{
Id: e.Id,
ReferenceNumber: getStringValue(e.ReferenceNumber),
PoNumber: e.PoNumber, // Keep as pointer to allow null in JSON
ReferenceNumber: e.ReferenceNumber,
PoNumber: e.PoNumber,
Category: e.Category,
Documents: documents,
Supplier: supplier,
RealizationDate: realizationDate,
ExpenseDate: e.ExpenseDate,
GrandTotal: e.GrandTotal,
Location: location,
@@ -175,18 +141,14 @@ func ToExpenseListDTOs(expenses []entity.Expense) []ExpenseListDTO {
}
func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
var documents []DocumentDTO
var realizationDocs []DocumentDTO
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)
@@ -194,51 +156,45 @@ func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
}
var pengajuans []ExpenseNonstockDTO
var realisasi []RealizationOnlyDTO
var realisasi []ExpenseRealizationDTO
if e.DocumentPath.Valid && e.DocumentPath.String != "" {
json.Unmarshal([]byte(e.DocumentPath.String), &documents)
}
if e.RealizationDocumentPath.Valid && e.RealizationDocumentPath.String != "" {
json.Unmarshal([]byte(e.RealizationDocumentPath.String), &realizationDocs)
}
if len(e.Nonstocks) > 0 {
pengajuans = make([]ExpenseNonstockDTO, 0)
realisasi = make([]RealizationOnlyDTO, 0)
realisasi = make([]ExpenseRealizationDTO, 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,
realisasiDTO := ExpenseRealizationDTO{
Id: ns.Realization.Id,
Qty: ns.Realization.RealizationQty,
UnitPrice: ns.Realization.RealizationUnitPrice,
TotalPrice: ns.Realization.RealizationTotalPrice,
Note: ns.Realization.Note,
Nonstock: nonstock,
}
realisasi = append(realisasi, realisasiDTO)
}
}
}
// Calculate total pengajuan and realisasi
var totalPengajuan float64
for _, p := range pengajuans {
totalPengajuan += p.TotalPrice
@@ -249,55 +205,76 @@ func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
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
var kandangId uint64
var kandangName string
for _, ns := range e.Nonstocks {
if ns.Id == p.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: p.ProjectFlockKandang.Id,
KandangId: kandangId,
Name: fmt.Sprintf("Kandang %d", kandangId),
Id: kandangId,
KandangId: kandangId,
Name: kandangName,
Pengajuans: []ExpenseNonstockDTO{},
Realisasi: []ExpenseRealizationDTO{},
}
}
kandangMap[kandangId].Pengajuans = append(kandangMap[kandangId].Pengajuans, p)
}
}
// Process realisasi
for _, r := range realisasi {
if r.ProjectFlockKandang != nil {
kandangId := r.ProjectFlockKandang.KandangId
var kandangId uint64
var kandangName string
for _, ns := range e.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: r.ProjectFlockKandang.Id,
KandangId: kandangId,
Name: fmt.Sprintf("Kandang %d", kandangId),
Id: kandangId,
KandangId: kandangId,
Name: kandangName,
Pengajuans: []ExpenseNonstockDTO{},
Realisasi: []ExpenseRealizationDTO{},
}
}
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,
ExpenseBaseDTO: ToExpenseBaseDTO(e),
Documents: documents,
RealizationDocs: realizationDocs,
CreatedUser: createdUser,
Kandangs: kandangs,
TotalPengajuan: totalPengajuan,
TotalRealisasi: totalRealisasi,
CreatedAt: e.CreatedAt,
UpdatedAt: e.UpdatedAt,
LatestApproval: latestApproval,
}
}
@@ -308,39 +285,12 @@ func ToExpenseNonstockDTO(ns entity.ExpenseNonstock) ExpenseNonstockDTO {
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,
Id: ns.Id,
Qty: ns.Qty,
UnitPrice: ns.UnitPrice,
TotalPrice: ns.TotalPrice,
Note: &ns.Note,
Nonstock: nonstock,
}
}