mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Feat[BE]: integrate document service into expense module and update related DTOs for document handling
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -13,8 +12,6 @@ type Expense struct {
|
||||
SupplierId uint64 `gorm:""`
|
||||
Category string `gorm:"type:varchar(50);not null"`
|
||||
PoNumber string `gorm:"type:varchar(50)"`
|
||||
DocumentPath sql.NullString `gorm:"type:json"`
|
||||
RealizationDocumentPath sql.NullString `gorm:"type:json;column:realization_document_path"`
|
||||
RealizationDate time.Time `gorm:"type:date;column:realization_date"`
|
||||
TransactionDate time.Time `gorm:"type:date;not null"`
|
||||
Notes string `gorm:"type:text;column:notes"`
|
||||
@@ -23,8 +20,10 @@ type Expense struct {
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||
|
||||
Supplier *Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
Nonstocks []ExpenseNonstock `gorm:"foreignKey:ExpenseId;references:Id"`
|
||||
LatestApproval *Approval `gorm:"-" json:"latest_approval,omitempty"`
|
||||
Supplier *Supplier `gorm:"foreignKey:SupplierId;references:Id"`
|
||||
CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"`
|
||||
Nonstocks []ExpenseNonstock `gorm:"foreignKey:ExpenseId;references:Id"`
|
||||
Documents []Document `gorm:"foreignKey:DocumentableId;references:Id"`
|
||||
RealizationDocuments []Document `gorm:"foreignKey:DocumentableId;references:Id"`
|
||||
LatestApproval *Approval `gorm:"-" json:"latest_approval,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||
@@ -41,8 +40,8 @@ type ExpenseListDTO struct {
|
||||
|
||||
type ExpenseDetailDTO struct {
|
||||
ExpenseBaseDTO
|
||||
Documents []DocumentDTO `json:"documents,omitempty"`
|
||||
RealizationDocs []DocumentDTO `json:"realization_docs,omitempty"`
|
||||
Documents []DocumentDTO `json:"documents"`
|
||||
RealizationDocs []DocumentDTO `json:"realization_docs"`
|
||||
Kandangs []KandangGroupDTO `json:"kandangs,omitempty"`
|
||||
TotalPengajuan float64 `json:"total_pengajuan"`
|
||||
TotalRealisasi float64 `json:"total_realisasi"`
|
||||
@@ -179,12 +178,20 @@ func ToExpenseDetailDTO(e *entity.Expense) ExpenseDetailDTO {
|
||||
var pengajuans []ExpenseNonstockDTO
|
||||
var realisasi []ExpenseRealizationDTO
|
||||
|
||||
if e.DocumentPath.Valid && e.DocumentPath.String != "" {
|
||||
json.Unmarshal([]byte(e.DocumentPath.String), &documents)
|
||||
// Map documents from Document service
|
||||
for _, doc := range e.Documents {
|
||||
documents = append(documents, DocumentDTO{
|
||||
ID: uint64(doc.Id),
|
||||
Path: doc.Path,
|
||||
})
|
||||
}
|
||||
|
||||
if e.RealizationDocumentPath.Valid && e.RealizationDocumentPath.String != "" {
|
||||
json.Unmarshal([]byte(e.RealizationDocumentPath.String), &realizationDocs)
|
||||
// Map realization documents from Document service
|
||||
for _, doc := range e.RealizationDocuments {
|
||||
realizationDocs = append(realizationDocs, DocumentDTO{
|
||||
ID: uint64(doc.Id),
|
||||
Path: doc.Path,
|
||||
})
|
||||
}
|
||||
|
||||
if len(e.Nonstocks) > 0 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package expenses
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
@@ -31,15 +32,20 @@ func (ExpenseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate *
|
||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||
realizationRepo := rExpense.NewExpenseRealizationRepository(db)
|
||||
projectFlockKandangRepo := rProjectFlockKandang.NewProjectFlockKandangRepository(db)
|
||||
documentRepo := commonRepo.NewDocumentRepository(db)
|
||||
|
||||
approvalSvc := commonSvc.NewApprovalService(approvalRepo)
|
||||
documentSvc, err := commonSvc.NewDocumentServiceFromConfig(context.Background(), documentRepo)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create document service: %v", err))
|
||||
}
|
||||
|
||||
// Register workflow steps for EXPENSES approval
|
||||
if err := approvalSvc.RegisterWorkflowSteps(utils.ApprovalWorkflowExpense, utils.ExpenseApprovalSteps); err != nil {
|
||||
panic(fmt.Sprintf("failed to register expense approval workflow: %v", err))
|
||||
}
|
||||
|
||||
expenseService := sExpense.NewExpenseService(expenseRepo, supplierRepo, nonstockRepo, approvalSvc, realizationRepo, projectFlockKandangRepo, validate)
|
||||
expenseService := sExpense.NewExpenseService(expenseRepo, supplierRepo, nonstockRepo, approvalSvc, realizationRepo, projectFlockKandangRepo, documentSvc, validate)
|
||||
userService := sUser.NewUserService(userRepo, validate)
|
||||
|
||||
ExpenseRoutes(router, userService, expenseService)
|
||||
|
||||
@@ -2,11 +2,8 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
|
||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
||||
@@ -49,9 +46,10 @@ type expenseService struct {
|
||||
ApprovalSvc commonSvc.ApprovalService
|
||||
RealizationRepository repository.ExpenseRealizationRepository
|
||||
ProjectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository
|
||||
DocumentSvc commonSvc.DocumentService
|
||||
}
|
||||
|
||||
func NewExpenseService(repo repository.ExpenseRepository, supplierRepo supplierRepo.SupplierRepository, nonstockRepo nonstockRepo.NonstockRepository, approvalSvc commonSvc.ApprovalService, realizationRepo repository.ExpenseRealizationRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, validate *validator.Validate) ExpenseService {
|
||||
func NewExpenseService(repo repository.ExpenseRepository, supplierRepo supplierRepo.SupplierRepository, nonstockRepo nonstockRepo.NonstockRepository, approvalSvc commonSvc.ApprovalService, realizationRepo repository.ExpenseRealizationRepository, projectFlockKandangRepo projectFlockKandangRepo.ProjectFlockKandangRepository, documentSvc commonSvc.DocumentService, validate *validator.Validate) ExpenseService {
|
||||
return &expenseService{
|
||||
Log: utils.Log,
|
||||
Validate: validate,
|
||||
@@ -61,6 +59,7 @@ func NewExpenseService(repo repository.ExpenseRepository, supplierRepo supplierR
|
||||
ApprovalSvc: approvalSvc,
|
||||
RealizationRepository: realizationRepo,
|
||||
ProjectFlockKandangRepo: projectFlockKandangRepo,
|
||||
DocumentSvc: documentSvc,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +71,13 @@ func (s expenseService) withRelations(db *gorm.DB) *gorm.DB {
|
||||
Preload("Nonstocks.Realization").
|
||||
Preload("Nonstocks.ProjectFlockKandang.Kandang.Location").
|
||||
Preload("Nonstocks.Kandang").
|
||||
Preload("Nonstocks.Kandang.Location")
|
||||
Preload("Nonstocks.Kandang.Location").
|
||||
Preload("Documents", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("documentable_type = ?", string(utils.DocumentableTypeExpense))
|
||||
}).
|
||||
Preload("RealizationDocuments", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("documentable_type = ?", string(utils.DocumentableTypeExpenseRealization))
|
||||
})
|
||||
}
|
||||
|
||||
func (s expenseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]expenseDto.ExpenseListDTO, int64, error) {
|
||||
@@ -269,9 +274,23 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create initial approval")
|
||||
}
|
||||
|
||||
if len(req.Documents) > 0 {
|
||||
if err := s.processDocuments(c, expenseRepoTx, uint(expense.Id), req.Documents, false); err != nil {
|
||||
return err
|
||||
if s.DocumentSvc != nil && len(req.Documents) > 0 {
|
||||
documentFiles := make([]commonSvc.DocumentFile, 0, len(req.Documents))
|
||||
for idx, file := range req.Documents {
|
||||
documentFiles = append(documentFiles, commonSvc.DocumentFile{
|
||||
File: file,
|
||||
Type: string(utils.DocumentTypeExpense),
|
||||
Index: &idx,
|
||||
})
|
||||
}
|
||||
_, err := s.DocumentSvc.UploadDocuments(c.Context(), commonSvc.DocumentUploadRequest{
|
||||
DocumentableType: string(utils.DocumentableTypeExpense),
|
||||
DocumentableID: expense.Id,
|
||||
CreatedBy: &createdByUint,
|
||||
Files: documentFiles,
|
||||
})
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to upload expense documents")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,9 +546,23 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
|
||||
}
|
||||
}
|
||||
|
||||
if len(req.Documents) > 0 {
|
||||
if err := s.processDocuments(c, expenseRepoTx, id, req.Documents, false); err != nil {
|
||||
return err
|
||||
if s.DocumentSvc != nil && len(req.Documents) > 0 {
|
||||
documentFiles := make([]commonSvc.DocumentFile, 0, len(req.Documents))
|
||||
for idx, file := range req.Documents {
|
||||
documentFiles = append(documentFiles, commonSvc.DocumentFile{
|
||||
File: file,
|
||||
Type: string(utils.DocumentTypeExpense),
|
||||
Index: &idx,
|
||||
})
|
||||
}
|
||||
_, err := s.DocumentSvc.UploadDocuments(c.Context(), commonSvc.DocumentUploadRequest{
|
||||
DocumentableType: string(utils.DocumentableTypeExpense),
|
||||
DocumentableID: uint64(id),
|
||||
CreatedBy: &actorID,
|
||||
Files: documentFiles,
|
||||
})
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to upload expense documents")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,9 +691,24 @@ func (s *expenseService) CreateRealization(c *fiber.Ctx, expenseID uint, req *va
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to update realization date")
|
||||
}
|
||||
|
||||
if len(req.Documents) > 0 {
|
||||
if err := s.processDocuments(c, expenseRepoTx, expenseID, req.Documents, true); err != nil {
|
||||
return err
|
||||
if s.DocumentSvc != nil && len(req.Documents) > 0 {
|
||||
documentFiles := make([]commonSvc.DocumentFile, 0, len(req.Documents))
|
||||
for idx, file := range req.Documents {
|
||||
documentFiles = append(documentFiles, commonSvc.DocumentFile{
|
||||
File: file,
|
||||
Type: string(utils.DocumentTypeExpenseRealization),
|
||||
Index: &idx,
|
||||
})
|
||||
}
|
||||
actorID := uint(1) // TODO: replace with authenticated user id
|
||||
_, err := s.DocumentSvc.UploadDocuments(c.Context(), commonSvc.DocumentUploadRequest{
|
||||
DocumentableType: string(utils.DocumentableTypeExpenseRealization),
|
||||
DocumentableID: uint64(expenseID),
|
||||
CreatedBy: &actorID,
|
||||
Files: documentFiles,
|
||||
})
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to upload expense realization documents")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -833,9 +881,24 @@ func (s *expenseService) UpdateRealization(c *fiber.Ctx, expenseID uint, req *va
|
||||
}
|
||||
}
|
||||
|
||||
if len(req.Documents) > 0 {
|
||||
if err := s.processDocuments(c, expenseRepoTx, expenseID, req.Documents, true); err != nil {
|
||||
return err
|
||||
if s.DocumentSvc != nil && len(req.Documents) > 0 {
|
||||
documentFiles := make([]commonSvc.DocumentFile, 0, len(req.Documents))
|
||||
for idx, file := range req.Documents {
|
||||
documentFiles = append(documentFiles, commonSvc.DocumentFile{
|
||||
File: file,
|
||||
Type: string(utils.DocumentTypeExpenseRealization),
|
||||
Index: &idx,
|
||||
})
|
||||
}
|
||||
actorID := uint(1) // TODO: replace with authenticated user id
|
||||
_, err := s.DocumentSvc.UploadDocuments(c.Context(), commonSvc.DocumentUploadRequest{
|
||||
DocumentableType: string(utils.DocumentableTypeExpenseRealization),
|
||||
DocumentableID: uint64(expenseID),
|
||||
CreatedBy: &actorID,
|
||||
Files: documentFiles,
|
||||
})
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to upload expense realization documents")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,79 +933,6 @@ func (s *expenseService) UpdateRealization(c *fiber.Ctx, expenseID uint, req *va
|
||||
return responseDTO, nil
|
||||
}
|
||||
|
||||
func (s *expenseService) processDocuments(ctx *fiber.Ctx, expenseRepoTx repository.ExpenseRepository, expenseID uint, documents []*multipart.FileHeader, isRealization bool) error {
|
||||
|
||||
if len(documents) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var existingDocuments []expenseDto.DocumentDTO
|
||||
var fieldName string
|
||||
|
||||
if isRealization {
|
||||
fieldName = "realization_document_path"
|
||||
} else {
|
||||
fieldName = "document_path"
|
||||
}
|
||||
|
||||
expense, err := expenseRepoTx.GetByID(ctx.Context(), expenseID, nil)
|
||||
if err != nil {
|
||||
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get expense for document processing")
|
||||
}
|
||||
} else {
|
||||
|
||||
var documentField sql.NullString
|
||||
if isRealization {
|
||||
documentField = expense.RealizationDocumentPath
|
||||
} else {
|
||||
documentField = expense.DocumentPath
|
||||
}
|
||||
|
||||
if documentField.Valid && documentField.String != "" {
|
||||
if err := json.Unmarshal([]byte(documentField.String), &existingDocuments); err != nil {
|
||||
existingDocuments = []expenseDto.DocumentDTO{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var startID uint64 = 1
|
||||
if len(existingDocuments) > 0 {
|
||||
|
||||
maxID := uint64(0)
|
||||
for _, doc := range existingDocuments {
|
||||
if doc.ID > maxID {
|
||||
maxID = doc.ID
|
||||
}
|
||||
}
|
||||
startID = maxID + 1
|
||||
}
|
||||
|
||||
for i, doc := range documents {
|
||||
documentPath := doc.Filename
|
||||
|
||||
document := expenseDto.DocumentDTO{
|
||||
ID: startID + uint64(i),
|
||||
Path: documentPath,
|
||||
}
|
||||
existingDocuments = append(existingDocuments, document)
|
||||
}
|
||||
|
||||
documentJSON, err := json.Marshal(existingDocuments)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to save documents")
|
||||
}
|
||||
|
||||
if err := expenseRepoTx.PatchOne(ctx.Context(), expenseID, map[string]interface{}{
|
||||
fieldName: string(documentJSON),
|
||||
}, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to save documents")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *expenseService) DeleteDocument(ctx *fiber.Ctx, expenseID uint, documentID uint64, isRealization bool) error {
|
||||
|
||||
if err := commonSvc.EnsureRelations(ctx.Context(),
|
||||
@@ -951,62 +941,40 @@ func (s *expenseService) DeleteDocument(ctx *fiber.Ctx, expenseID uint, document
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Repository.DB().WithContext(ctx.Context()).Transaction(func(tx *gorm.DB) error {
|
||||
expenseRepoTx := repository.NewExpenseRepository(tx)
|
||||
if s.DocumentSvc == nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Document service not available")
|
||||
}
|
||||
|
||||
expense, err := expenseRepoTx.GetByID(ctx.Context(), expenseID, nil)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get expense for document deletion")
|
||||
// Verify document exists and belongs to the expense
|
||||
var documentableType string
|
||||
if isRealization {
|
||||
documentableType = string(utils.DocumentableTypeExpenseRealization)
|
||||
} else {
|
||||
documentableType = string(utils.DocumentableTypeExpense)
|
||||
}
|
||||
|
||||
documents, err := s.DocumentSvc.ListByTarget(ctx.Context(), documentableType, uint64(expenseID))
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve documents")
|
||||
}
|
||||
|
||||
documentFound := false
|
||||
var documentIDsToDelete []uint
|
||||
for _, doc := range documents {
|
||||
if uint64(doc.Id) == documentID {
|
||||
documentFound = true
|
||||
documentIDsToDelete = append(documentIDsToDelete, doc.Id)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var existingDocuments []expenseDto.DocumentDTO
|
||||
var fieldName string
|
||||
if !documentFound {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Document not found")
|
||||
}
|
||||
|
||||
if isRealization {
|
||||
fieldName = "realization_document_path"
|
||||
if expense.RealizationDocumentPath.Valid && expense.RealizationDocumentPath.String != "" {
|
||||
if err := json.Unmarshal([]byte(expense.RealizationDocumentPath.String), &existingDocuments); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to parse existing realization documents")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fieldName = "document_path"
|
||||
if expense.DocumentPath.Valid && expense.DocumentPath.String != "" {
|
||||
if err := json.Unmarshal([]byte(expense.DocumentPath.String), &existingDocuments); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to parse existing documents")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updatedDocuments []expenseDto.DocumentDTO
|
||||
documentFound := false
|
||||
|
||||
for _, doc := range existingDocuments {
|
||||
if doc.ID == documentID {
|
||||
documentFound = true
|
||||
continue
|
||||
}
|
||||
updatedDocuments = append(updatedDocuments, doc)
|
||||
}
|
||||
|
||||
if !documentFound {
|
||||
return fiber.NewError(fiber.StatusNotFound, "Document not found")
|
||||
}
|
||||
|
||||
documentJSON, err := json.Marshal(updatedDocuments)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to save documents")
|
||||
}
|
||||
|
||||
if err := expenseRepoTx.PatchOne(ctx.Context(), expenseID, map[string]interface{}{
|
||||
fieldName: string(documentJSON),
|
||||
}, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to save documents")
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
// Delete document from database and storage
|
||||
if err := s.DocumentSvc.DeleteDocuments(ctx.Context(), documentIDsToDelete, true); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete document")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package purchases
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
@@ -41,6 +42,11 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
||||
|
||||
approvalRepo := commonRepo.NewApprovalRepository(db)
|
||||
approvalService := commonSvc.NewApprovalService(approvalRepo)
|
||||
documentRepo := commonRepo.NewDocumentRepository(db)
|
||||
documentSvc, err := commonSvc.NewDocumentServiceFromConfig(context.Background(), documentRepo)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to create document service: %v", err))
|
||||
}
|
||||
if err := approvalService.RegisterWorkflowSteps(utils.ApprovalWorkflowPurchase, utils.PurchaseApprovalSteps); err != nil {
|
||||
panic(fmt.Sprintf("failed to register purchase approval workflow: %v", err))
|
||||
}
|
||||
@@ -54,6 +60,7 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
|
||||
approvalService,
|
||||
expenseRealizationRepo,
|
||||
projectFlockKandangRepository,
|
||||
documentSvc,
|
||||
validate,
|
||||
)
|
||||
expenseBridge := service.NewExpenseBridge(
|
||||
|
||||
@@ -408,9 +408,13 @@ type DocumentType string
|
||||
type DocumentableType string
|
||||
|
||||
const (
|
||||
DocumentTypeTransfer DocumentType = "STOCK_TRANSFER_DOCUMENT"
|
||||
DocumentTypeTransfer DocumentType = "STOCK_TRANSFER_DOCUMENT"
|
||||
DocumentTypeExpense DocumentType = "EXPENSE_DOCUMENT"
|
||||
DocumentTypeExpenseRealization DocumentType = "EXPENSE_REALIZATION_DOCUMENT"
|
||||
|
||||
DocumentableTypeTransfer DocumentableType = "STOCK_TRANSFER"
|
||||
DocumentableTypeTransfer DocumentableType = "STOCK_TRANSFER"
|
||||
DocumentableTypeExpense DocumentableType = "EXPENSE"
|
||||
DocumentableTypeExpenseRealization DocumentableType = "EXPENSE_REALIZATION"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user