diff --git a/internal/entities/expense.go b/internal/entities/expense.go index a427582d..286eaf51 100644 --- a/internal/entities/expense.go +++ b/internal/entities/expense.go @@ -1,18 +1,30 @@ package entities import ( + "database/sql" "time" "gorm.io/gorm" ) type Expense struct { - Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:idx_name,where:deleted_at IS NULL"` - CreatedBy uint `gorm:"not null"` - CreatedAt time.Time `gorm:"autoCreateTime"` - UpdatedAt time.Time `gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + Id uint64 `gorm:"primaryKey;autoIncrement"` + ReferenceNumber *string `gorm:"type:varchar(50)"` + SupplierId *uint64 `gorm:""` + Category string `gorm:"type:varchar(50);not null"` + PoNumber string `gorm:"uniqueIndex;not null;type:varchar(50)"` + DocumentPath sql.NullString `gorm:"type:json"` + ExpenseDate time.Time `gorm:"type:date;not null"` + GrandTotal float64 `gorm:"type:numeric(15,3);default:0"` + Note *string `gorm:"type:text"` + CreatedBy *uint64 `gorm:""` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` - CreatedUser User `gorm:"foreignKey:CreatedBy;references:Id"` + // Relations + 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"` } diff --git a/internal/entities/expense_nonstock.go b/internal/entities/expense_nonstock.go new file mode 100644 index 00000000..eb27efef --- /dev/null +++ b/internal/entities/expense_nonstock.go @@ -0,0 +1,27 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +type ExpenseNonstock struct { + Id uint64 `gorm:"primaryKey;autoIncrement"` + ExpenseId *uint64 `gorm:""` + ProjectFlockKandangId *uint64 `gorm:""` + NonstockId *uint64 `gorm:""` + Qty 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"` + Note *string `gorm:"type:text"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + // Relations + Expense *Expense `gorm:"foreignKey:ExpenseId;references:Id"` + ProjectFlockKandang *ProjectFlockKandang `gorm:"foreignKey:ProjectFlockKandangId;references:Id"` + Nonstock *Nonstock `gorm:"foreignKey:NonstockId;references:Id"` + Realizations []ExpenseRealization `gorm:"foreignKey:ExpenseNonstockId;references:Id"` +} diff --git a/internal/entities/expense_realization.go b/internal/entities/expense_realization.go new file mode 100644 index 00000000..45a87602 --- /dev/null +++ b/internal/entities/expense_realization.go @@ -0,0 +1,25 @@ +package entities + +import ( + "time" + + "gorm.io/gorm" +) + +type ExpenseRealization struct { + Id uint64 `gorm:"primaryKey;autoIncrement"` + ExpenseNonstockId *uint64 `gorm:""` + RealizationQty float64 `gorm:"type:numeric(15,3);not null"` + RealizationUnitPrice float64 `gorm:"type:numeric(15,3);not null"` + RealizationTotalPrice float64 `gorm:"type:numeric(15,3);not null"` + RealizationDate time.Time `gorm:"type:date;not null"` + Note *string `gorm:"type:text"` + CreatedBy *uint64 `gorm:""` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + // Relations + ExpenseNonstock *ExpenseNonstock `gorm:"foreignKey:ExpenseNonstockId;references:Id"` + CreatedUser *User `gorm:"foreignKey:CreatedBy;references:Id"` +} diff --git a/internal/modules/expenses/dto/expense.dto.go b/internal/modules/expenses/dto/expense.dto.go index b7bd1b5f..b5fca80f 100644 --- a/internal/modules/expenses/dto/expense.dto.go +++ b/internal/modules/expenses/dto/expense.dto.go @@ -10,44 +10,52 @@ import ( // === DTO Structs === type ExpenseBaseDTO struct { - Id uint `json:"id"` - Name string `json:"name"` + Id uint64 `json:"id"` + PoNumber string `json:"po_number"` + ExpenseDate time.Time `json:"expense_date"` + GrandTotal float64 `json:"grand_total"` } type ExpenseListDTO struct { - Id uint `json:"id"` - Name string `json:"name"` - CreatedUser *userDTO.UserBaseDTO `json:"created_user"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` -} - -type ExpenseDetailDTO struct { - ExpenseListDTO + Id uint64 `json:"id"` + ReferenceNumber string `json:"reference_number"` + PoNumber string `json:"po_number"` + Category string `json:"category"` + ExpenseDate time.Time `json:"expense_date"` + GrandTotal float64 `json:"grand_total"` + CreatedUser *userDTO.UserBaseDTO `json:"created_user,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } // === Mapper Functions === func ToExpenseBaseDTO(e entity.Expense) ExpenseBaseDTO { return ExpenseBaseDTO{ - Id: e.Id, - Name: e.Name, + Id: e.Id, + PoNumber: e.PoNumber, + ExpenseDate: e.ExpenseDate, + GrandTotal: e.GrandTotal, } } func ToExpenseListDTO(e entity.Expense) ExpenseListDTO { var createdUser *userDTO.UserBaseDTO if e.CreatedUser.Id != 0 { - mapped := userDTO.ToUserBaseDTO(e.CreatedUser) + mapped := userDTO.ToUserBaseDTO(*e.CreatedUser) createdUser = &mapped } return ExpenseListDTO{ - Id: e.Id, - Name: e.Name, - CreatedAt: e.CreatedAt, - UpdatedAt: e.UpdatedAt, - CreatedUser: createdUser, + Id: e.Id, + ReferenceNumber: *e.ReferenceNumber, + PoNumber: e.PoNumber, + Category: e.Category, + ExpenseDate: e.ExpenseDate, + GrandTotal: e.GrandTotal, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + CreatedUser: createdUser, } } @@ -58,9 +66,3 @@ func ToExpenseListDTOs(e []entity.Expense) []ExpenseListDTO { } return result } - -func ToExpenseDetailDTO(e entity.Expense) ExpenseDetailDTO { - return ExpenseDetailDTO{ - ExpenseListDTO: ToExpenseListDTO(e), - } -} diff --git a/internal/modules/expenses/repositories/expense.repository.go b/internal/modules/expenses/repositories/expense.repository.go index 94712cd5..8cc580be 100644 --- a/internal/modules/expenses/repositories/expense.repository.go +++ b/internal/modules/expenses/repositories/expense.repository.go @@ -1,13 +1,16 @@ package repository import ( - entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "context" + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" "gorm.io/gorm" ) type ExpenseRepository interface { repository.BaseRepository[entity.Expense] + IdExists(ctx context.Context, id uint64) (bool, error) } type ExpenseRepositoryImpl struct { @@ -19,3 +22,7 @@ func NewExpenseRepository(db *gorm.DB) ExpenseRepository { BaseRepositoryImpl: repository.NewBaseRepository[entity.Expense](db), } } + +func (r *ExpenseRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) { + return repository.Exists[entity.Expense](ctx, r.DB(), uint(id)) +} diff --git a/internal/modules/expenses/repositories/expense_nonstock.repository.go b/internal/modules/expenses/repositories/expense_nonstock.repository.go new file mode 100644 index 00000000..257a3034 --- /dev/null +++ b/internal/modules/expenses/repositories/expense_nonstock.repository.go @@ -0,0 +1,28 @@ +package repository + +import ( + "context" + + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gorm.io/gorm" +) + +type ExpenseNonstockRepository interface { + repository.BaseRepository[entity.ExpenseNonstock] + IdExists(ctx context.Context, id uint64) (bool, error) +} + +type ExpenseNonstockRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.ExpenseNonstock] +} + +func NewExpenseNonstockRepository(db *gorm.DB) ExpenseNonstockRepository { + return &ExpenseNonstockRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.ExpenseNonstock](db), + } +} + +func (r *ExpenseNonstockRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) { + return repository.Exists[entity.ExpenseNonstock](ctx, r.DB(), uint(id)) +} diff --git a/internal/modules/expenses/repositories/expense_realization.repository.go b/internal/modules/expenses/repositories/expense_realization.repository.go new file mode 100644 index 00000000..0245f8a2 --- /dev/null +++ b/internal/modules/expenses/repositories/expense_realization.repository.go @@ -0,0 +1,28 @@ +package repository + +import ( + "context" + + "gitlab.com/mbugroup/lti-api.git/internal/common/repository" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + "gorm.io/gorm" +) + +type ExpenseRealizationRepository interface { + repository.BaseRepository[entity.ExpenseRealization] + IdExists(ctx context.Context, id uint64) (bool, error) +} + +type ExpenseRealizationRepositoryImpl struct { + *repository.BaseRepositoryImpl[entity.ExpenseRealization] +} + +func NewExpenseRealizationRepository(db *gorm.DB) ExpenseRealizationRepository { + return &ExpenseRealizationRepositoryImpl{ + BaseRepositoryImpl: repository.NewBaseRepository[entity.ExpenseRealization](db), + } +} + +func (r *ExpenseRealizationRepositoryImpl) IdExists(ctx context.Context, id uint64) (bool, error) { + return repository.Exists[entity.ExpenseRealization](ctx, r.DB(), uint(id)) +} diff --git a/internal/modules/expenses/services/expense.service.go b/internal/modules/expenses/services/expense.service.go index 1b57263e..6f794e54 100644 --- a/internal/modules/expenses/services/expense.service.go +++ b/internal/modules/expenses/services/expense.service.go @@ -79,8 +79,11 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, err } + createdBy := uint64(1) createBody := &entity.Expense{ - Name: req.Name, + PoNumber: req.PoNumber, + Category: req.Category, + CreatedBy: &createdBy, } if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { @@ -88,7 +91,7 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit return nil, err } - return s.GetOne(c, createBody.Id) + return s.GetOne(c, uint(createBody.Id)) } func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Expense, error) { @@ -98,8 +101,11 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) updateBody := make(map[string]any) - if req.Name != nil { - updateBody["name"] = *req.Name + if req.PoNumber != nil { + updateBody["po_number"] = *req.PoNumber + } + if req.Category != nil { + updateBody["category"] = *req.Category } if len(updateBody) == 0 { diff --git a/internal/modules/expenses/validations/expense.validation.go b/internal/modules/expenses/validations/expense.validation.go index 7d16d3ee..8155d76f 100644 --- a/internal/modules/expenses/validations/expense.validation.go +++ b/internal/modules/expenses/validations/expense.validation.go @@ -1,11 +1,13 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` + PoNumber string `json:"po_number" validate:"required,max=50"` + Category string `json:"category" validate:"required,max=50"` } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` + PoNumber *string `json:"po_number,omitempty" validate:"omitempty,max=50"` + Category *string `json:"category,omitempty" validate:"omitempty,max=50"` } type Query struct { diff --git a/internal/utils/constant.go b/internal/utils/constant.go index fc01a231..7b219a8e 100644 --- a/internal/utils/constant.go +++ b/internal/utils/constant.go @@ -241,6 +241,23 @@ var MarketingApprovalSteps = map[approvalutils.ApprovalStep]string{ MarketingDeliveryOrder: "Delivery Order", } +// ------------------------------------------------------------------- +// Expense Approval +// ------------------------------------------------------------------- + +const ( + ApprovalWorkflowExpense approvalutils.ApprovalWorkflowKey = approvalutils.ApprovalWorkflowKey("EXPENSES") + ExpenseStepPengajuan approvalutils.ApprovalStep = 1 + ExpenseStepManager approvalutils.ApprovalStep = 2 + ExpenseStepFinance approvalutils.ApprovalStep = 3 +) + +var ExpenseApprovalSteps = map[approvalutils.ApprovalStep]string{ + ExpenseStepPengajuan: "Pengajuan", + ExpenseStepManager: "Manager", + ExpenseStepFinance: "Finance", +} + // ------------------------------------------------------------------- // Validators // -------------------------------------------------------------------