mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Feat[BE-261]: creating multiple Approval API, Update API, Delete API and some logic Adjustment
This commit is contained in:
@@ -7,7 +7,9 @@ CREATE TABLE expenses (
|
|||||||
),
|
),
|
||||||
po_number VARCHAR(50) NULL,
|
po_number VARCHAR(50) NULL,
|
||||||
document_path JSON,
|
document_path JSON,
|
||||||
|
realization_document_path JSON,
|
||||||
expense_date DATE NOT NULL,
|
expense_date DATE NOT NULL,
|
||||||
|
realization_date DATE,
|
||||||
grand_total NUMERIC(15, 3) DEFAULT 0,
|
grand_total NUMERIC(15, 3) DEFAULT 0,
|
||||||
note TEXT,
|
note TEXT,
|
||||||
created_by BIGINT,
|
created_by BIGINT,
|
||||||
|
|||||||
@@ -8,19 +8,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Expense struct {
|
type Expense struct {
|
||||||
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
ReferenceNumber *string `gorm:"type:varchar(50);uniqueIndex"`
|
ReferenceNumber string `gorm:"type:varchar(50);uniqueIndex"`
|
||||||
SupplierId *uint64 `gorm:""`
|
SupplierId uint64 `gorm:""`
|
||||||
Category string `gorm:"type:varchar(50);not null"`
|
Category string `gorm:"type:varchar(50);not null"`
|
||||||
PoNumber *string `gorm:"type:varchar(50)"`
|
PoNumber string `gorm:"type:varchar(50)"`
|
||||||
DocumentPath sql.NullString `gorm:"type:json"`
|
DocumentPath sql.NullString `gorm:"type:json"` // Dokumen pengajuan
|
||||||
ExpenseDate time.Time `gorm:"type:date;not null"`
|
RealizationDocumentPath sql.NullString `gorm:"type:json;column:realization_document_path"` // Dokumen realisasi
|
||||||
GrandTotal float64 `gorm:"type:numeric(15,3);default:0"`
|
RealizationDate time.Time `gorm:"type:date;column:realization_date"` // Tanggal realisasi
|
||||||
Note *string `gorm:"type:text"`
|
ExpenseDate time.Time `gorm:"type:date;not null"`
|
||||||
CreatedBy *uint64 `gorm:""`
|
GrandTotal float64 `gorm:"type:numeric(15,3);default:0"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
Note string `gorm:"type:text"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
CreatedBy uint64 `gorm:""`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Supplier *Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
Supplier *Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ type ExpenseNonstock struct {
|
|||||||
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
Id uint64 `gorm:"primaryKey;autoIncrement"`
|
||||||
ExpenseId *uint64 `gorm:""`
|
ExpenseId *uint64 `gorm:""`
|
||||||
ProjectFlockKandangId *uint64 `gorm:""`
|
ProjectFlockKandangId *uint64 `gorm:""`
|
||||||
|
KandangId *uint64 `gorm:""`
|
||||||
NonstockId *uint64 `gorm:""`
|
NonstockId *uint64 `gorm:""`
|
||||||
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
Qty float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
UnitPrice float64 `gorm:"type:numeric(15,3);not null"`
|
UnitPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
TotalPrice float64 `gorm:"type:numeric(15,3);not null"`
|
TotalPrice float64 `gorm:"type:numeric(15,3);not null"`
|
||||||
Note *string `gorm:"type:text"`
|
Note string `gorm:"type:text"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
@@ -22,6 +23,7 @@ type ExpenseNonstock struct {
|
|||||||
// Relations
|
// Relations
|
||||||
Expense *Expense `gorm:"foreignKey:ExpenseId;references:Id"`
|
Expense *Expense `gorm:"foreignKey:ExpenseId;references:Id"`
|
||||||
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"`
|
||||||
|
Kandang *Kandang `gorm:"foreignKey:KandangId;references:Id"`
|
||||||
Nonstock *Nonstock `gorm:"foreignKey:NonstockId;references:Id"`
|
Nonstock *Nonstock `gorm:"foreignKey:NonstockId;references:Id"`
|
||||||
Realization *ExpenseRealization `gorm:"foreignKey:ExpenseNonstockId;references:Id"`
|
Realization *ExpenseRealization `gorm:"foreignKey:ExpenseNonstockId;references:Id"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,11 +103,23 @@ func (u *ExpenseController) CreateOne(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
var singleCostPerKandang validation.CostPerKandang
|
var singleCostPerKandang validation.CostPerKandang
|
||||||
if err := json.Unmarshal([]byte(costPerKandangJSON), &singleCostPerKandang); err != nil {
|
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}
|
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)
|
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")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid Id")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.BodyParser(req); err != nil {
|
form, err := c.MultipartForm()
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
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))
|
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 {
|
func (u *ExpenseController) Approval(c *fiber.Ctx) error {
|
||||||
expenseID := c.Params("id")
|
req := new(validation.ApprovalRequest)
|
||||||
id, err := strconv.Atoi(expenseID)
|
|
||||||
if err != nil {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid expense ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract step from URL path (manager or finance)
|
if err := c.BodyParser(req); err != nil {
|
||||||
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 {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Approve expense
|
path := c.Path()
|
||||||
expense, err := u.ExpenseService.ApproveExpense(c, uint(id), step, req.Action, req.Notes)
|
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 {
|
if err != nil {
|
||||||
return err
|
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).
|
return c.Status(fiber.StatusOK).
|
||||||
JSON(response.Success{
|
JSON(response.Success{
|
||||||
Code: fiber.StatusOK,
|
Code: fiber.StatusOK,
|
||||||
Status: "success",
|
Status: "success",
|
||||||
Message: "Approve expense successfully",
|
Message: message,
|
||||||
Data: expense,
|
Data: data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,8 +261,8 @@ func (u *ExpenseController) CreateRealization(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid multipart form")
|
||||||
}
|
}
|
||||||
req.Documents = form.File["documents"]
|
req.Documents = form.File["documents"]
|
||||||
|
req.RealizationDate = c.FormValue("realization_date")
|
||||||
|
|
||||||
// Parse realizations JSON
|
|
||||||
realizationsJSON := c.FormValue("realizations")
|
realizationsJSON := c.FormValue("realizations")
|
||||||
if realizationsJSON != "" {
|
if realizationsJSON != "" {
|
||||||
if err := json.Unmarshal([]byte(realizationsJSON), &req.Realizations); err != nil {
|
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
|
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)
|
expense, err := u.ExpenseService.UpdateRealization(c, uint(id), &req)
|
||||||
@@ -293,3 +343,49 @@ func (u *ExpenseController) CompleteExpense(c *fiber.Ctx) error {
|
|||||||
Data: expense,
|
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",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package dto
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
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"
|
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// === Base DTO ===
|
|
||||||
|
|
||||||
type ExpenseBaseDTO struct {
|
type ExpenseBaseDTO struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
ReferenceNumber string `json:"reference_number"`
|
ReferenceNumber string `json:"reference_number"`
|
||||||
PoNumber *string `json:"po_number"`
|
PoNumber string `json:"po_number"`
|
||||||
Category string `json:"category"`
|
Category string `json:"category"`
|
||||||
Documents []string `json:"documents,omitempty"`
|
Supplier *supplierDTO.SupplierBaseDTO `json:"supplier,omitempty"`
|
||||||
ExpenseDate time.Time `json:"expense_date"`
|
RealizationDate *time.Time `json:"realization_date,omitempty"`
|
||||||
GrandTotal float64 `json:"grand_total"`
|
ExpenseDate time.Time `json:"expense_date"`
|
||||||
|
GrandTotal float64 `json:"grand_total"`
|
||||||
Location *locationDTO.LocationBaseDTO `json:"location,omitempty"`
|
Location *locationDTO.LocationBaseDTO `json:"location,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// === List DTO (untuk GetAll) ===
|
|
||||||
|
|
||||||
type ExpenseListDTO struct {
|
type ExpenseListDTO struct {
|
||||||
ExpenseBaseDTO
|
ExpenseBaseDTO
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
|
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
|
||||||
@@ -36,95 +32,59 @@ type ExpenseListDTO struct {
|
|||||||
LatestApproval *approvalDTO.ApprovalBaseDTO `json:"latest_approval,omitempty"`
|
LatestApproval *approvalDTO.ApprovalBaseDTO `json:"latest_approval,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Detail DTO (untuk GetOne) ===
|
|
||||||
|
|
||||||
type ExpenseDetailDTO struct {
|
type ExpenseDetailDTO struct {
|
||||||
ExpenseBaseDTO
|
ExpenseBaseDTO
|
||||||
Supplier *supplierDTO.SupplierBaseDTO `json:"supplier,omitempty"`
|
Documents []DocumentDTO `json:"documents,omitempty"`
|
||||||
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
|
RealizationDocs []DocumentDTO `json:"realization_docs,omitempty"`
|
||||||
Kandangs []KandangGroupDTO `json:"kandangs,omitempty"`
|
CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"`
|
||||||
TotalPengajuan float64 `json:"total_pengajuan"`
|
Kandangs []KandangGroupDTO `json:"kandangs,omitempty"`
|
||||||
TotalRealisasi float64 `json:"total_realisasi"`
|
TotalPengajuan float64 `json:"total_pengajuan"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
TotalRealisasi float64 `json:"total_realisasi"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
LatestApproval *approvalDTO.ApprovalBaseDTO `json:"latest_approval,omitempty"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
LatestApproval *approvalDTO.ApprovalBaseDTO `json:"latest_approval,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Nested DTO ===
|
|
||||||
|
|
||||||
type ExpenseNonstockDTO struct {
|
type ExpenseNonstockDTO struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
Qty float64 `json:"qty"`
|
Qty float64 `json:"qty"`
|
||||||
UnitPrice float64 `json:"unit_price"`
|
UnitPrice float64 `json:"unit_price"`
|
||||||
TotalPrice float64 `json:"total_price"`
|
TotalPrice float64 `json:"total_price"`
|
||||||
Note *string `json:"note,omitempty"`
|
Note *string `json:"note,omitempty"`
|
||||||
Nonstock *nonstockDTO.NonstockBaseDTO `json:"nonstock,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 {
|
type ExpenseRealizationDTO struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
Qty float64 `json:"qty"`
|
Qty float64 `json:"qty"`
|
||||||
UnitPrice float64 `json:"unit_price"`
|
UnitPrice float64 `json:"unit_price"`
|
||||||
TotalPrice float64 `json:"total_price"`
|
TotalPrice float64 `json:"total_price"`
|
||||||
Date time.Time `json:"date"`
|
Note *string `json:"note,omitempty"`
|
||||||
Note *string `json:"note,omitempty"`
|
Nonstock *nonstockDTO.NonstockBaseDTO `json:"nonstock,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 {
|
type KandangGroupDTO struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
KandangId uint64 `json:"kandang_id"`
|
KandangId uint64 `json:"kandang_id"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Pengajuans []ExpenseNonstockDTO `json:"pengajuans,omitempty"`
|
Pengajuans []ExpenseNonstockDTO `json:"pengajuans,omitempty"`
|
||||||
Realisasi []RealizationOnlyDTO `json:"realisasi,omitempty"`
|
Realisasi []ExpenseRealizationDTO `json:"realisasi,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Helper Functions ===
|
type DocumentDTO struct {
|
||||||
|
ID uint64 `json:"id"`
|
||||||
func getStringValue(s *string) string {
|
Path string `json:"path"`
|
||||||
if s == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return *s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Mapper Functions ===
|
|
||||||
|
|
||||||
func ToExpenseBaseDTO(e *entity.Expense) ExpenseBaseDTO {
|
func ToExpenseBaseDTO(e *entity.Expense) ExpenseBaseDTO {
|
||||||
var documents []string
|
|
||||||
var location *locationDTO.LocationBaseDTO
|
var location *locationDTO.LocationBaseDTO
|
||||||
|
var supplier *supplierDTO.SupplierBaseDTO
|
||||||
|
|
||||||
// Parse document paths from JSON if available
|
var realizationDate *time.Time
|
||||||
if e.DocumentPath.Valid && e.DocumentPath.String != "" {
|
if !e.RealizationDate.IsZero() {
|
||||||
if err := json.Unmarshal([]byte(e.DocumentPath.String), &documents); err == nil {
|
realizationDate = &e.RealizationDate
|
||||||
// Successfully parsed documents
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get location from the first kandang if available
|
|
||||||
if len(e.Nonstocks) > 0 && e.Nonstocks[0].ProjectFlockKandang != nil {
|
if len(e.Nonstocks) > 0 && e.Nonstocks[0].ProjectFlockKandang != nil {
|
||||||
if e.Nonstocks[0].ProjectFlockKandang.Kandang.Location.Id != 0 {
|
if e.Nonstocks[0].ProjectFlockKandang.Kandang.Location.Id != 0 {
|
||||||
mapped := locationDTO.ToLocationBaseDTO(e.Nonstocks[0].ProjectFlockKandang.Kandang.Location)
|
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{
|
return ExpenseBaseDTO{
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
ReferenceNumber: getStringValue(e.ReferenceNumber),
|
ReferenceNumber: e.ReferenceNumber,
|
||||||
PoNumber: e.PoNumber, // Keep as pointer to allow null in JSON
|
PoNumber: e.PoNumber,
|
||||||
Category: e.Category,
|
Category: e.Category,
|
||||||
Documents: documents,
|
Supplier: supplier,
|
||||||
|
RealizationDate: realizationDate,
|
||||||
ExpenseDate: e.ExpenseDate,
|
ExpenseDate: e.ExpenseDate,
|
||||||
GrandTotal: e.GrandTotal,
|
GrandTotal: e.GrandTotal,
|
||||||
Location: location,
|
Location: location,
|
||||||
@@ -175,18 +141,14 @@ func ToExpenseListDTOs(expenses []entity.Expense) []ExpenseListDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
|
func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
|
||||||
|
var documents []DocumentDTO
|
||||||
|
var realizationDocs []DocumentDTO
|
||||||
var createdUser *userDTO.UserBaseDTO
|
var createdUser *userDTO.UserBaseDTO
|
||||||
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
if e.CreatedUser != nil && e.CreatedUser.Id != 0 {
|
||||||
mapped := userDTO.ToUserBaseDTO(*e.CreatedUser)
|
mapped := userDTO.ToUserBaseDTO(*e.CreatedUser)
|
||||||
createdUser = &mapped
|
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
|
var latestApproval *approvalDTO.ApprovalBaseDTO
|
||||||
if e.LatestApproval != nil {
|
if e.LatestApproval != nil {
|
||||||
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
|
mapped := approvalDTO.ToApprovalDTO(*e.LatestApproval)
|
||||||
@@ -194,51 +156,45 @@ func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var pengajuans []ExpenseNonstockDTO
|
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 {
|
if len(e.Nonstocks) > 0 {
|
||||||
pengajuans = make([]ExpenseNonstockDTO, 0)
|
pengajuans = make([]ExpenseNonstockDTO, 0)
|
||||||
realisasi = make([]RealizationOnlyDTO, 0)
|
realisasi = make([]ExpenseRealizationDTO, 0)
|
||||||
|
|
||||||
for _, ns := range e.Nonstocks {
|
for _, ns := range e.Nonstocks {
|
||||||
// Create DTO without realization for pengajuans
|
|
||||||
pengajuanDTO := ToExpenseNonstockDTO(ns)
|
pengajuanDTO := ToExpenseNonstockDTO(ns)
|
||||||
pengajuanDTO.Realization = nil // Remove realization from pengajuan
|
|
||||||
pengajuans = append(pengajuans, pengajuanDTO)
|
pengajuans = append(pengajuans, pengajuanDTO)
|
||||||
|
|
||||||
// Create separate DTO with realization data if it exists
|
|
||||||
if ns.Realization != nil && ns.Realization.Id != 0 {
|
if ns.Realization != nil && ns.Realization.Id != 0 {
|
||||||
// Create realization DTO with only realization data
|
|
||||||
var nonstock *nonstockDTO.NonstockBaseDTO
|
var nonstock *nonstockDTO.NonstockBaseDTO
|
||||||
if ns.Nonstock != nil && ns.Nonstock.Id != 0 {
|
if ns.Nonstock != nil && ns.Nonstock.Id != 0 {
|
||||||
mapped := nonstockDTO.ToNonstockBaseDTO(*ns.Nonstock)
|
mapped := nonstockDTO.ToNonstockBaseDTO(*ns.Nonstock)
|
||||||
nonstock = &mapped
|
nonstock = &mapped
|
||||||
}
|
}
|
||||||
|
|
||||||
var projectFlockKandang *ProjectFlockKandangNestedDTO
|
realisasiDTO := ExpenseRealizationDTO{
|
||||||
if ns.ProjectFlockKandang != nil && ns.ProjectFlockKandang.Id != 0 {
|
Id: ns.Realization.Id,
|
||||||
projectFlockKandang = &ProjectFlockKandangNestedDTO{
|
Qty: ns.Realization.RealizationQty,
|
||||||
Id: uint64(ns.ProjectFlockKandang.Id),
|
UnitPrice: ns.Realization.RealizationUnitPrice,
|
||||||
KandangId: uint64(ns.ProjectFlockKandang.KandangId),
|
TotalPrice: ns.Realization.RealizationTotalPrice,
|
||||||
}
|
Note: ns.Realization.Note,
|
||||||
}
|
Nonstock: nonstock,
|
||||||
|
|
||||||
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)
|
realisasi = append(realisasi, realisasiDTO)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate total pengajuan and realisasi
|
|
||||||
var totalPengajuan float64
|
var totalPengajuan float64
|
||||||
for _, p := range pengajuans {
|
for _, p := range pengajuans {
|
||||||
totalPengajuan += p.TotalPrice
|
totalPengajuan += p.TotalPrice
|
||||||
@@ -249,55 +205,76 @@ func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
|
|||||||
totalRealisasi += r.TotalPrice
|
totalRealisasi += r.TotalPrice
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group pengajuans and realisasi by kandang
|
|
||||||
kandangMap := make(map[uint64]*KandangGroupDTO)
|
kandangMap := make(map[uint64]*KandangGroupDTO)
|
||||||
|
|
||||||
// Process pengajuans
|
|
||||||
for _, p := range pengajuans {
|
for _, p := range pengajuans {
|
||||||
if p.ProjectFlockKandang != nil {
|
var kandangId uint64
|
||||||
kandangId := p.ProjectFlockKandang.KandangId
|
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 {
|
if kandangMap[kandangId] == nil {
|
||||||
kandangMap[kandangId] = &KandangGroupDTO{
|
kandangMap[kandangId] = &KandangGroupDTO{
|
||||||
Id: p.ProjectFlockKandang.Id,
|
Id: kandangId,
|
||||||
KandangId: kandangId,
|
KandangId: kandangId,
|
||||||
Name: fmt.Sprintf("Kandang %d", kandangId),
|
Name: kandangName,
|
||||||
|
Pengajuans: []ExpenseNonstockDTO{},
|
||||||
|
Realisasi: []ExpenseRealizationDTO{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kandangMap[kandangId].Pengajuans = append(kandangMap[kandangId].Pengajuans, p)
|
kandangMap[kandangId].Pengajuans = append(kandangMap[kandangId].Pengajuans, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process realisasi
|
|
||||||
for _, r := range realisasi {
|
for _, r := range realisasi {
|
||||||
if r.ProjectFlockKandang != nil {
|
var kandangId uint64
|
||||||
kandangId := r.ProjectFlockKandang.KandangId
|
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 {
|
if kandangMap[kandangId] == nil {
|
||||||
kandangMap[kandangId] = &KandangGroupDTO{
|
kandangMap[kandangId] = &KandangGroupDTO{
|
||||||
Id: r.ProjectFlockKandang.Id,
|
Id: kandangId,
|
||||||
KandangId: kandangId,
|
KandangId: kandangId,
|
||||||
Name: fmt.Sprintf("Kandang %d", kandangId),
|
Name: kandangName,
|
||||||
|
Pengajuans: []ExpenseNonstockDTO{},
|
||||||
|
Realisasi: []ExpenseRealizationDTO{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kandangMap[kandangId].Realisasi = append(kandangMap[kandangId].Realisasi, r)
|
kandangMap[kandangId].Realisasi = append(kandangMap[kandangId].Realisasi, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert map to slice
|
|
||||||
var kandangs []KandangGroupDTO
|
var kandangs []KandangGroupDTO
|
||||||
for _, k := range kandangMap {
|
for _, k := range kandangMap {
|
||||||
kandangs = append(kandangs, *k)
|
kandangs = append(kandangs, *k)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ExpenseDetailDTO{
|
return ExpenseDetailDTO{
|
||||||
ExpenseBaseDTO: ToExpenseBaseDTO(e),
|
ExpenseBaseDTO: ToExpenseBaseDTO(e),
|
||||||
Supplier: supplier,
|
Documents: documents,
|
||||||
CreatedUser: createdUser,
|
RealizationDocs: realizationDocs,
|
||||||
Kandangs: kandangs,
|
CreatedUser: createdUser,
|
||||||
TotalPengajuan: totalPengajuan,
|
Kandangs: kandangs,
|
||||||
TotalRealisasi: totalRealisasi,
|
TotalPengajuan: totalPengajuan,
|
||||||
CreatedAt: e.CreatedAt,
|
TotalRealisasi: totalRealisasi,
|
||||||
UpdatedAt: e.UpdatedAt,
|
CreatedAt: e.CreatedAt,
|
||||||
LatestApproval: latestApproval,
|
UpdatedAt: e.UpdatedAt,
|
||||||
|
LatestApproval: latestApproval,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,39 +285,12 @@ func ToExpenseNonstockDTO(ns entity.ExpenseNonstock) ExpenseNonstockDTO {
|
|||||||
nonstock = &mapped
|
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{
|
return ExpenseNonstockDTO{
|
||||||
Id: ns.Id,
|
Id: ns.Id,
|
||||||
Qty: ns.Qty,
|
Qty: ns.Qty,
|
||||||
UnitPrice: ns.UnitPrice,
|
UnitPrice: ns.UnitPrice,
|
||||||
TotalPrice: ns.TotalPrice,
|
TotalPrice: ns.TotalPrice,
|
||||||
Note: ns.Note,
|
Note: &ns.Note,
|
||||||
Nonstock: nonstock,
|
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,11 @@ func ExpenseRoutes(v1 fiber.Router, u user.UserService, s expense.ExpenseService
|
|||||||
route.Get("/:id", ctrl.GetOne)
|
route.Get("/:id", ctrl.GetOne)
|
||||||
route.Patch("/:id", ctrl.UpdateOne)
|
route.Patch("/:id", ctrl.UpdateOne)
|
||||||
route.Delete("/:id", ctrl.DeleteOne)
|
route.Delete("/:id", ctrl.DeleteOne)
|
||||||
route.Post("/:id/approvals/manager", ctrl.ApproveExpense)
|
route.Post("/approvals/manager", ctrl.Approval)
|
||||||
route.Post("/:id/approvals/finance", ctrl.ApproveExpense)
|
route.Post("/approvals/finance", ctrl.Approval)
|
||||||
route.Post("/:id/realizations", ctrl.CreateRealization)
|
route.Post("/:id/realizations", ctrl.CreateRealization)
|
||||||
route.Patch("/:id/realizations", ctrl.UpdateRealization)
|
route.Patch("/:id/realizations", ctrl.UpdateRealization)
|
||||||
route.Post("/:id/complete", ctrl.CompleteExpense)
|
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"
|
"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 {
|
type Create struct {
|
||||||
PoNumber *string `form:"po_number" validate:"omitempty,max=50"`
|
PoNumber string `form:"po_number" json:"po_number" validate:"omitempty,max=50"`
|
||||||
TransactionDate string `form:"transaction_date" validate:"required,datetime=2006-01-02"`
|
TransactionDate string `form:"transaction_date" json:"transaction_date" validate:"required,datetime=2006-01-02"`
|
||||||
Category string `form:"category" validate:"required,oneof=BOP NON-BOP"`
|
Category string `form:"category" json:"category" validate:"required,oneof=BOP NON-BOP"`
|
||||||
SupplierID uint64 `form:"supplier_id" validate:"required,gt=0"`
|
SupplierID uint64 `form:"supplier_id" json:"supplier_id" validate:"required,gt=0"`
|
||||||
Documents []*multipart.FileHeader `form:"documents" validate:"omitempty,dive"`
|
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
||||||
CostPerKandangs []CostPerKandang `form:"cost_per_kandangs" validate:"required,min=1,dive"`
|
CostPerKandangs []CostPerKandang `form:"cost_per_kandangs" json:"cost_per_kandangs" validate:"required,min=1,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CostPerKandang struct {
|
type CostPerKandang struct {
|
||||||
KandangID uint64 `json:"kandang_id" form:"kandang_id" validate:"required,gt=0"`
|
KandangID uint64 `form:"kandang_id" json:"kandang_id" validate:"required,gt=0"`
|
||||||
CostItems []CostItem `json:"cost_items" form:"cost_items" validate:"required,min=1,dive"`
|
CostItems []CostItem `form:"cost_items" json:"cost_items" validate:"required,min=1,dive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CostItem struct {
|
type CostItem struct {
|
||||||
NonstockID uint64 `json:"nonstock_id" form:"nonstock_id" validate:"required,gt=0"`
|
NonstockID uint64 `form:"nonstock_id" json:"nonstock_id" validate:"required,gt=0"`
|
||||||
Quantity float64 `json:"quantity" form:"quantity" validate:"required,gt=0"`
|
Quantity float64 `form:"quantity" json:"quantity" validate:"required,gt=0"`
|
||||||
TotalCost float64 `json:"total_cost" form:"total_cost" validate:"required,gt=0"`
|
TotalCost float64 `form:"total_cost" json:"total_cost" validate:"required,gt=0"`
|
||||||
Notes string `json:"notes" form:"notes" validate:"required,max=500"`
|
Notes string `form:"notes" json:"notes" validate:"required,max=500"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
PoNumber *string `json:"po_number,omitempty" validate:"omitempty,max=50"`
|
TransactionDate *string `form:"transaction_date" json:"transaction_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
TransactionDate *string `json:"transaction_date,omitempty" validate:"omitempty,datetime=2006-01-02"`
|
CostPerKandang *[]CostPerKandang `form:"cost_per_kandang" json:"cost_per_kandang" validate:"omitempty,min=1,dive"`
|
||||||
SupplierID *uint64 `json:"supplier_id,omitempty" validate:"omitempty,gt=0"`
|
Documents []*multipart.FileHeader `form:"documents" json:"documents" validate:"omitempty,dive"`
|
||||||
Documents *[]string `json:"documents,omitempty" validate:"omitempty,dive,max=255"`
|
|
||||||
CostPerKandang *[]CostPerKandang `json:"cost_per_kandang,omitempty" validate:"omitempty,min=1,dive"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
@@ -46,21 +38,27 @@ type Query struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CreateRealization struct {
|
type CreateRealization struct {
|
||||||
Documents []*multipart.FileHeader `form:"documents" validate:"omitempty,dive"`
|
RealizationDate string `form:"realization_date" json:"realization_date" validate:"required,datetime=2006-01-02"`
|
||||||
Realizations []RealizationItem `json:"realizations" form:"realizations" validate:"required,min=1,dive"`
|
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 `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 {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateRealization struct {
|
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"`
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user