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
@@ -103,11 +103,23 @@ func (u *ExpenseController) CreateOne(c *fiber.Ctx) error {
var singleCostPerKandang validation.CostPerKandang
if err := json.Unmarshal([]byte(costPerKandangJSON), &singleCostPerKandang); err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid cost_per_kandang JSON: %v", err))
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid cost_per_kandangs JSON: %v", err))
}
if singleCostPerKandang.KandangID == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Field KandangID is required")
}
req.CostPerKandangs = []validation.CostPerKandang{singleCostPerKandang}
} else {
for i, costPerKandang := range req.CostPerKandangs {
if costPerKandang.KandangID == 0 {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Field KandangID is required for cost_per_kandangs[%d]", i))
}
}
}
} else {
return fiber.NewError(fiber.StatusBadRequest, "Field cost_per_kandangs is required")
}
result, err := u.ExpenseService.CreateOne(c, req)
@@ -133,8 +145,30 @@ func (u *ExpenseController) UpdateOne(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
}
if err := c.BodyParser(req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
form, err := c.MultipartForm()
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
}
req.Documents = form.File["documents"]
if transactionDate := c.FormValue("transaction_date"); transactionDate != "" {
req.TransactionDate = &transactionDate
}
costPerKandangJSON := c.FormValue("cost_per_kandang")
if costPerKandangJSON != "" {
var costPerKandang []validation.CostPerKandang
if err := json.Unmarshal([]byte(costPerKandangJSON), &costPerKandang); err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid cost_per_kandang JSON: %v", err))
}
for i, costPerKandang := range costPerKandang {
if costPerKandang.KandangID == 0 {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Field KandangID is required for cost_per_kandang[%d]", i))
}
}
req.CostPerKandang = &costPerKandang
}
result, err := u.ExpenseService.UpdateOne(c, req, uint(id))
@@ -171,42 +205,45 @@ func (u *ExpenseController) DeleteOne(c *fiber.Ctx) error {
})
}
func (u *ExpenseController) ApproveExpense(c *fiber.Ctx) error {
expenseID := c.Params("id")
id, err := strconv.Atoi(expenseID)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
}
func (u *ExpenseController) Approval(c *fiber.Ctx) error {
req := new(validation.ApprovalRequest)
// Extract step from URL path (manager or finance)
path := c.Path()
var step string
if strings.Contains(path, "/approvals/manager") {
step = "Manager"
} else if strings.Contains(path, "/approvals/finance") {
step = "Finance"
} else {
return fiber.NewError(fiber.StatusBadRequest, "Invalid approval step")
}
// Parse approval request
var req validation.ApprovalRequest
if err := c.BodyParser(&req); err != nil {
if err := c.BodyParser(req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
// Approve expense
expense, err := u.ExpenseService.ApproveExpense(c, uint(id), step, req.Action, req.Notes)
path := c.Path()
approvalType := ""
if strings.Contains(path, "/approvals/manager") {
approvalType = "manager"
} else if strings.Contains(path, "/approvals/finance") {
approvalType = "finance"
} else {
return fiber.NewError(fiber.StatusBadRequest, "Invalid approval path")
}
results, err := u.ExpenseService.Approval(c, req, approvalType)
if err != nil {
return err
}
var (
data interface{}
message = "Submit expense approval successfully"
)
if len(results) == 1 {
data = results[0]
} else {
message = "Submit expense approvals successfully"
data = results
}
return c.Status(fiber.StatusOK).
JSON(response.Success{
Code: fiber.StatusOK,
Status: "success",
Message: "Approve expense successfully",
Data: expense,
Message: message,
Data: data,
})
}
@@ -224,8 +261,8 @@ func (u *ExpenseController) CreateRealization(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
}
req.Documents = form.File["documents"]
req.RealizationDate = c.FormValue("realization_date")
// Parse realizations JSON
realizationsJSON := c.FormValue("realizations")
if realizationsJSON != "" {
if err := json.Unmarshal([]byte(realizationsJSON), &req.Realizations); err != nil {
@@ -255,8 +292,21 @@ func (u *ExpenseController) UpdateRealization(c *fiber.Ctx) error {
}
var req validation.UpdateRealization
if err := c.BodyParser(&req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
form, err := c.MultipartForm()
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
}
req.Documents = form.File["documents"]
req.RealizationDate = c.FormValue("realization_date")
realizationsJSON := c.FormValue("realizations")
if realizationsJSON != "" {
if err := json.Unmarshal([]byte(realizationsJSON), &req.Realizations); err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid realizations JSON: %v", err))
}
}
expense, err := u.ExpenseService.UpdateRealization(c, uint(id), &req)
@@ -293,3 +343,49 @@ func (u *ExpenseController) CompleteExpense(c *fiber.Ctx) error {
Data: expense,
})
}
func (u *ExpenseController) DeleteDocument(c *fiber.Ctx) error {
expenseID, err := strconv.Atoi(c.Params("id"))
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
}
documentID, err := strconv.ParseUint(c.Params("documentId"), 10, 64)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid document ID")
}
if err := u.ExpenseService.DeleteDocument(c, uint(expenseID), documentID, false); err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Common{
Code: fiber.StatusOK,
Status: "success",
Message: "Delete document successfully",
})
}
func (u *ExpenseController) DeleteRealizationDocument(c *fiber.Ctx) error {
expenseID, err := strconv.Atoi(c.Params("id"))
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
}
documentID, err := strconv.ParseUint(c.Params("documentId"), 10, 64)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid document ID")
}
if err := u.ExpenseService.DeleteDocument(c, uint(expenseID), documentID, true); err != nil {
return err
}
return c.Status(fiber.StatusOK).
JSON(response.Common{
Code: fiber.StatusOK,
Status: "success",
Message: "Delete realization document successfully",
})
}
+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,
}
}
+4 -2
View File
@@ -25,9 +25,11 @@ func ExpenseRoutes(v1 fiber.Router, u user.UserService, s expense.ExpenseService
route.Get("/:id", ctrl.GetOne)
route.Patch("/:id", ctrl.UpdateOne)
route.Delete("/:id", ctrl.DeleteOne)
route.Post("/:id/approvals/manager", ctrl.ApproveExpense)
route.Post("/:id/approvals/finance", ctrl.ApproveExpense)
route.Post("/approvals/manager", ctrl.Approval)
route.Post("/approvals/finance", ctrl.Approval)
route.Post("/:id/realizations", ctrl.CreateRealization)
route.Patch("/:id/realizations", ctrl.UpdateRealization)
route.Post("/:id/complete", ctrl.CompleteExpense)
route.Delete("/:id/documents/:documentId", ctrl.DeleteDocument)
route.Delete("/:id/realization-documents/:documentId", ctrl.DeleteRealizationDocument)
}
File diff suppressed because it is too large Load Diff
@@ -4,39 +4,31 @@ import (
"mime/multipart"
)
// ApprovalRequest is used for expense approval endpoints
type ApprovalRequest struct {
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"`
Notes *string `json:"notes"`
}
type Create struct {
PoNumber *string `form:"po_number" validate:"omitempty,max=50"`
TransactionDate string `form:"transaction_date" validate:"required,datetime=2006-01-02"`
Category string `form:"category" validate:"required,oneof=BOP NON-BOP"`
SupplierID uint64 `form:"supplier_id" validate:"required,gt=0"`
Documents []*multipart.FileHeader `form:"documents" validate:"omitempty,dive"`
CostPerKandangs []CostPerKandang `form:"cost_per_kandangs" validate:"required,min=1,dive"`
PoNumber string `form:"po_number" json:"po_number" validate:"omitempty,max=50"`
TransactionDate string `form:"transaction_date" json:"transaction_date" validate:"required,datetime=2006-01-02"`
Category string `form:"category" json:"category" validate:"required,oneof=BOP NON-BOP"`
SupplierID uint64 `form:"supplier_id" json:"supplier_id" validate:"required,gt=0"`
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
CostPerKandangs []CostPerKandang `form:"cost_per_kandangs" json:"cost_per_kandangs" validate:"required,min=1,dive"`
}
type CostPerKandang struct {
KandangID uint64 `json:"kandang_id" form:"kandang_id" validate:"required,gt=0"`
CostItems []CostItem `json:"cost_items" form:"cost_items" validate:"required,min=1,dive"`
KandangID uint64 `form:"kandang_id" json:"kandang_id" validate:"required,gt=0"`
CostItems []CostItem `form:"cost_items" json:"cost_items" validate:"required,min=1,dive"`
}
type CostItem struct {
NonstockID uint64 `json:"nonstock_id" form:"nonstock_id" validate:"required,gt=0"`
Quantity float64 `json:"quantity" form:"quantity" validate:"required,gt=0"`
TotalCost float64 `json:"total_cost" form:"total_cost" validate:"required,gt=0"`
Notes string `json:"notes" form:"notes" validate:"required,max=500"`
NonstockID uint64 `form:"nonstock_id" json:"nonstock_id" validate:"required,gt=0"`
Quantity float64 `form:"quantity" json:"quantity" validate:"required,gt=0"`
TotalCost float64 `form:"total_cost" json:"total_cost" validate:"required,gt=0"`
Notes string `form:"notes" json:"notes" validate:"required,max=500"`
}
type Update struct {
PoNumber *string `json:"po_number,omitempty" validate:"omitempty,max=50"`
TransactionDate *string `json:"transaction_date,omitempty" validate:"omitempty,datetime=2006-01-02"`
SupplierID *uint64 `json:"supplier_id,omitempty" validate:"omitempty,gt=0"`
Documents *[]string `json:"documents,omitempty" validate:"omitempty,dive,max=255"`
CostPerKandang *[]CostPerKandang `json:"cost_per_kandang,omitempty" validate:"omitempty,min=1,dive"`
TransactionDate *string `form:"transaction_date" json:"transaction_date" validate:"omitempty,datetime=2006-01-02"`
CostPerKandang *[]CostPerKandang `form:"cost_per_kandang" json:"cost_per_kandang" validate:"omitempty,min=1,dive"`
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
}
type Query struct {
@@ -46,21 +38,27 @@ type Query struct {
}
type CreateRealization struct {
Documents []*multipart.FileHeader `form:"documents" validate:"omitempty,dive"`
Realizations []RealizationItem `json:"realizations" form:"realizations" validate:"required,min=1,dive"`
}
type RealizationItem struct {
ExpenseNonstockID uint64 `json:"expense_nonstock_id" form:"expense_nonstock_id" validate:"required,gt=0"`
Qty float64 `json:"qty" form:"qty" validate:"required,gt=0"`
UnitPrice float64 `json:"unit_price" form:"unit_price" validate:"required,gt=0"`
TotalPrice float64 `json:"total_price" form:"total_price" validate:"required,gt=0"`
Notes *string `json:"notes" form:"notes" validate:"omitempty,max=500"`
}
type CompleteExpense struct {
RealizationDate string `form:"realization_date" json:"realization_date" validate:"required,datetime=2006-01-02"`
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
Realizations []RealizationItem `form:"realizations" json:"realizations" validate:"required,min=1,dive"`
}
type UpdateRealization struct {
Realizations []RealizationItem `json:"realizations" validate:"required,min=1,dive"`
RealizationDate string `form:"realization_date" json:"realization_date" validate:"omitempty,datetime=2006-01-02"`
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
Realizations []RealizationItem `form:"realizations" json:"realizations" validate:"required,min=1,dive"`
}
type RealizationItem struct {
ExpenseNonstockID uint64 `form:"expense_nonstock_id" json:"expense_nonstock_id" validate:"required,gt=0"`
Qty float64 `form:"qty" json:"qty" validate:"required,gt=0"`
UnitPrice float64 `form:"unit_price" json:"unit_price" validate:"required,gt=0"`
TotalPrice float64 `form:"total_price" json:"total_price" validate:"required,gt=0"`
Notes *string `form:"notes" json:"notes" validate:"omitempty,max=500"`
}
type ApprovalRequest struct {
Action string `json:"action" form:"action" validate:"required,oneof=APPROVED REJECTED"`
ApprovableIds []uint `json:"approvable_ids" validate:"required,min=1,dive,gt=0"`
Notes *string `json:"notes" form:"notes"`
}