feat(inventory_request): Implement inventory request management with CRUD operations and line handling
Deploy Application / deploy (push) Successful in 25s Details

- Added InventoryRequestController for handling requests
- Created InventoryRequestService for business logic
- Developed InventoryRequestRepository and InventoryRequestLineRepository for data access
- Introduced DTOs for request and response structures
- Implemented filtering for inventory requests
- Registered routes for inventory requests in the router
- Updated core dependency injection to include new services and controllers
This commit is contained in:
Habib Fatkhul Rohman 2025-11-17 11:59:39 +07:00
parent 9fa1d4ec14
commit 28f2a93f6a
13 changed files with 919 additions and 17 deletions

View File

@ -14,7 +14,8 @@ import (
"github.com/Caknoooo/go-gin-clean-starter/modules/auth"
"github.com/Caknoooo/go-gin-clean-starter/modules/category"
"github.com/Caknoooo/go-gin-clean-starter/modules/client"
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt"
inventoryreceipt "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_receipt"
inventoryrequest "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request"
maintenancegroup "github.com/Caknoooo/go-gin-clean-starter/modules/maintenance_group"
"github.com/Caknoooo/go-gin-clean-starter/modules/menu"
"github.com/Caknoooo/go-gin-clean-starter/modules/monitoring"
@ -163,8 +164,9 @@ func main() {
warehouse.RegisterRoutes(server, injector)
zona.RegisterRoutes(server, injector)
aisle.RegisterRoutes(server, injector)
inventory_receipt.RegisterRoutes(server, injector)
inventoryreceipt.RegisterRoutes(server, injector)
assignment.RegisterRoutes(server, injector)
inventoryrequest.RegisterRoutes(server, injector)
// register swagger route
server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))

View File

@ -1,9 +1,12 @@
package entities
import (
"fmt"
"strings"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type TInventoryRequestEntity struct {
@ -13,11 +16,13 @@ type TInventoryRequestEntity struct {
DueDate time.Time `gorm:"type:timestamp;" json:"due_date"`
RequestType string `gorm:"type:varchar(50);" json:"request_type"`
Note string `gorm:"type:text;" json:"note"`
Status string `gorm:"type:varchar(50);" json:"status"`
Status string `gorm:"type:varchar(50);default:'draft'" json:"status"`
ClientID uuid.UUID `gorm:"type:uuid;index;" json:"client_id"`
ClientID uuid.UUID `gorm:"type:uuid;index;" json:"client_id"`
Client M_Client `gorm:"foreignKey:ClientID;references:ID"`
RequestLines []TInventoryRequestLineEntity `gorm:"foreignKey:InvRequestID;references:ID"`
Assignment TAssignmentEntity `gorm:"foreignKey:DocumentID;references:ID"`
Client M_Client `gorm:"foreignKey:ClientID;references:ID"`
FullAuditTrail
}
@ -25,3 +30,44 @@ type TInventoryRequestEntity struct {
func (TInventoryRequestEntity) TableName() string {
return "t_inventory_requests"
}
// GenerateDocumentNumber generates a new document number for a client
func GenerateDocumentNumberInventoryRequest(db *gorm.DB, clientId string) (string, error) {
prefix := "RQST"
// Ambil nama client berdasarkan clientId
var client struct {
Name string
}
if err := db.Table("m_clients").Select("name").Where("id = ?", clientId).First(&client).Error; err != nil {
return "", fmt.Errorf("client not found")
}
if client.Name == "" {
return "", fmt.Errorf("client name is empty")
}
words := strings.Fields(client.Name)
initials := ""
for _, w := range words {
if len(w) > 0 {
initials += strings.ToUpper(string(w[0]))
}
}
// Cari document number terakhir untuk client ini
var lastRequest TInventoryRequestEntity
err := db.
Where("client_id = ?", clientId).
Order("document_number DESC").
First(&lastRequest).Error
seq := 1
if err == nil && lastRequest.DocumentNumber != "" {
parts := strings.Split(lastRequest.DocumentNumber, "-")
if len(parts) == 3 {
fmt.Sscanf(parts[2], "%d", &seq)
seq++
}
}
docNum := fmt.Sprintf("%s-%s-%04d", prefix, initials, seq)
return docNum, nil
}

View File

@ -32,8 +32,8 @@ func Migrate(db *gorm.DB) error {
&entities.TAssignmentUserEntity{},
&entities.TInventoryReceiptEntity{},
&entities.TInventoryReceiptLineEntity{},
// &entities.TInventoryRequestEntity{},
// &entities.TInventoryRequestLineEntity{},
&entities.TInventoryRequestEntity{},
&entities.TInventoryRequestLineEntity{},
// &entities.TInventoryIssueEntity{},
// &entities.TInventoryIssueLineEntity{},
// &entities.InventoryTransactionEntity{},
@ -68,13 +68,13 @@ func MigrateFresh(db *gorm.DB) error {
// &entities.MCrossReferenceEntity{},
// &entities.MWarehouseEntity{},
// &entities.MZonaEntity{},
// &entities.MAisleEntity{},
&entities.TAssignmentEntity{},
&entities.TAssignmentUserEntity{},
&entities.TInventoryReceiptEntity{},
&entities.TInventoryReceiptLineEntity{},
// &entities.TInventoryRequestEntity{},
// &entities.TInventoryRequestLineEntity{},
&entities.MAisleEntity{},
// &entities.TAssignmentEntity{},
// &entities.TAssignmentUserEntity{},
// &entities.TInventoryReceiptEntity{},
// &entities.TInventoryReceiptLineEntity{},
&entities.TInventoryRequestEntity{},
&entities.TInventoryRequestLineEntity{},
// &entities.TInventoryIssueEntity{},
// &entities.TInventoryIssueLineEntity{},
// &entities.InventoryTransactionEntity{},

View File

@ -8,4 +8,4 @@ COPY . .
RUN go mod tidy
CMD go run cmd/main.go --migrate && air
CMD go run cmd/main.go --fresh && air

View File

@ -1,4 +1,4 @@
package inventory_receipt
package inventoryreceipt
import (
"github.com/Caknoooo/go-gin-clean-starter/middlewares"

View File

@ -0,0 +1,179 @@
package controller
import (
"net/http"
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/dto"
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/query"
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/service"
"github.com/Caknoooo/go-gin-clean-starter/pkg/constants"
"github.com/Caknoooo/go-gin-clean-starter/pkg/utils"
"github.com/gin-gonic/gin"
"github.com/samber/do"
"gorm.io/gorm"
)
type InventoryRequestController interface {
Create(ctx *gin.Context)
Update(ctx *gin.Context)
Delete(ctx *gin.Context)
GetById(ctx *gin.Context)
GetAll(ctx *gin.Context)
CreateLine(ctx *gin.Context)
UpdateLine(ctx *gin.Context)
DeleteLine(ctx *gin.Context)
}
type inventoryRequestController struct {
requestService service.InventoryRequestService
db *gorm.DB
}
func (c *inventoryRequestController) DeleteLine(ctx *gin.Context) {
id := ctx.Param("id")
if err := c.requestService.DeleteLine(ctx, id); err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_INVENTORY_REQUEST_LINE, err.Error(), nil)
ctx.JSON(http.StatusInternalServerError, res)
return
}
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_INVENTORY_REQUEST_LINE, nil)
ctx.JSON(http.StatusOK, res)
}
func (c *inventoryRequestController) UpdateLine(ctx *gin.Context) {
id := ctx.Param("id")
var req dto.InventoryRequestLineUpdateRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
updated, err := c.requestService.UpdateLine(ctx, id, req)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_INVENTORY_REQUEST_LINE, err.Error(), nil)
ctx.JSON(http.StatusInternalServerError, res)
return
}
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_INVENTORY_REQUEST_LINE, updated)
ctx.JSON(http.StatusOK, res)
}
func (c *inventoryRequestController) CreateLine(ctx *gin.Context) {
id := ctx.Param("id")
var req dto.InventoryRequestLineCreateRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
created, err := c.requestService.CreateLine(ctx, id, req)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_INVENTORY_REQUEST_LINE, err.Error(), nil)
ctx.JSON(http.StatusInternalServerError, res)
return
}
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_INVENTORY_REQUEST_LINE, created)
ctx.JSON(http.StatusOK, res)
}
func (c *inventoryRequestController) Create(ctx *gin.Context) {
var req dto.InventoryRequestCreateRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
created, err := c.requestService.Create(ctx, req)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_CREATE_INVENTORY_REQUEST, err.Error(), nil)
ctx.JSON(http.StatusInternalServerError, res)
return
}
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_CREATE_INVENTORY_REQUEST, created)
ctx.JSON(http.StatusOK, res)
}
func (c *inventoryRequestController) Update(ctx *gin.Context) {
id := ctx.Param("id")
var req dto.InventoryRequestUpdateRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
updated, err := c.requestService.Update(ctx, req, id)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_INVENTORY_REQUEST, err.Error(), nil)
ctx.JSON(http.StatusInternalServerError, res)
return
}
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_INVENTORY_REQUEST, updated)
ctx.JSON(http.StatusOK, res)
}
func (c *inventoryRequestController) Delete(ctx *gin.Context) {
id := ctx.Param("id")
if err := c.requestService.Delete(ctx, id); err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_INVENTORY_REQUEST, err.Error(), nil)
ctx.JSON(http.StatusInternalServerError, res)
return
}
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_INVENTORY_REQUEST, nil)
ctx.JSON(http.StatusOK, res)
}
func (c *inventoryRequestController) GetById(ctx *gin.Context) {
id := ctx.Param("id")
request, err := c.requestService.GetById(ctx, id)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_REQUEST, err.Error(), nil)
ctx.JSON(http.StatusNotFound, res)
return
}
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_INVENTORY_REQUEST, request)
ctx.JSON(http.StatusOK, res)
}
func (c *inventoryRequestController) GetAll(ctx *gin.Context) {
clientId := ctx.DefaultQuery("client_id", "")
var filter query.InventoryRequestFilter
filter.ClientID = clientId
if err := ctx.ShouldBindQuery(&filter); err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_REQUEST, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
getAll := ctx.Query("get_all")
if getAll != "" {
requests, _, err := c.requestService.GetAll(ctx, filter)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_REQUEST, err.Error(), nil)
ctx.JSON(http.StatusBadRequest, res)
return
}
response := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_INVENTORY_REQUEST, requests)
ctx.JSON(http.StatusOK, response)
return
}
perPage := utils.ParseInt(ctx.DefaultQuery("per_page", "10"))
page := utils.ParseInt(ctx.DefaultQuery("page", "1"))
filter.PerPage = perPage
filter.Page = (page - 1) * perPage
requests, total, err := c.requestService.GetAll(ctx, filter)
if err != nil {
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_INVENTORY_REQUEST, err.Error(), nil)
ctx.JSON(http.StatusInternalServerError, res)
return
}
paginationResponse := utils.BuildPaginationResponse(perPage, page, total)
res := utils.BuildResponseSuccessWithPagination(http.StatusOK, dto.MESSAGE_SUCCESS_GET_INVENTORY_REQUEST, requests, paginationResponse)
ctx.JSON(http.StatusOK, res)
}
func NewInventoryRequestController(i *do.Injector, requestService service.InventoryRequestService) InventoryRequestController {
db := do.MustInvokeNamed[*gorm.DB](i, constants.DB)
return &inventoryRequestController{
requestService: requestService,
db: db,
}
}

View File

@ -0,0 +1,94 @@
package dto
import pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto"
const (
MESSAGE_FAILED_CREATE_INVENTORY_REQUEST = "failed create inventory request"
MESSAGE_FAILED_CREATE_INVENTORY_REQUEST_LINE = "failed create inventory request line"
MESSAGE_SUCCESS_CREATE_INVENTORY_REQUEST = "success create inventory request"
MESSAGE_SUCCESS_CREATE_INVENTORY_REQUEST_LINE = "success create inventory request line"
MESSAGE_FAILED_GET_INVENTORY_REQUEST = "failed get inventory request"
MESSAGE_SUCCESS_GET_INVENTORY_REQUEST = "success get inventory request"
MESSAGE_FAILED_UPDATE_INVENTORY_REQUEST = "failed update inventory request"
MESSAGE_FAILED_UPDATE_INVENTORY_REQUEST_LINE = "failed update inventory request line"
MESSAGE_SUCCESS_UPDATE_INVENTORY_REQUEST = "success update inventory request"
MESSAGE_SUCCESS_UPDATE_INVENTORY_REQUEST_LINE = "success update inventory request line"
MESSAGE_FAILED_DELETE_INVENTORY_REQUEST = "failed delete inventory request"
MESSAGE_FAILED_DELETE_INVENTORY_REQUEST_LINE = "failed delete inventory request line"
MESSAGE_SUCCESS_DELETE_INVENTORY_REQUEST = "success delete inventory request"
MESSAGE_SUCCESS_DELETE_INVENTORY_REQUEST_LINE = "success delete inventory request line"
MESSAGE_FAILED_GET_DATA_FROM_BODY = "failed get data from body"
)
type InventoryRequestCreateRequest struct {
ReferenceNumber string `json:"reference_number"`
// DocumentNumber string `json:"document_number"`
DueDate string `json:"due_date"`
RequestType string `json:"request_type"`
Note string `json:"note"`
ClientID string `json:"client_id" binding:"required"`
Status string `json:"status" binding:"required"`
RequestLines []InventoryRequestLineCreateRequest `json:"request_lines,omitempty" binding:"dive"`
}
type InventoryRequestLineCreateRequest struct {
Quantity float64 `json:"quantity"`
ProductID string `json:"product_id"`
ClientID string `json:"client_id"`
}
type InventoryRequestUpdateRequest struct {
ReferenceNumber string `json:"reference_number"`
DocumentNumber string `json:"document_number"`
DueDate string `json:"due_date"`
RequestType string `json:"request_type"`
Note string `json:"note"`
Status string `json:"status"`
}
type InventoryRequestResponse struct {
ID string `json:"id"`
ReferenceNumber string `json:"reference_number"`
DocumentNumber string `json:"document_number"`
DueDate string `json:"due_date"`
RequestType string `json:"request_type"`
Note string `json:"note"`
Status string `json:"status"`
Client pkgdto.IdNameResponse `json:"client"`
RequestLines []InventoryRequestLineResponse `json:"request_lines"`
LineCount int `json:"line_count"`
Assignment AssignmentResponse `json:"assignment,omitempty"`
}
type AssignmentResponse struct {
ID string `json:"id"`
DocumentType string `json:"document_type"`
DocumentID string `json:"document_id"`
AssignmentUsers []AssignmentUserResponse `json:"assignment_users"`
}
type AssignmentUserResponse struct {
ID string `json:"id"`
TaskType string `json:"task_type"`
User pkgdto.IdNameResponse `json:"user"`
Role pkgdto.IdNameResponse `json:"role"`
}
type InventoryRequestLineResponse struct {
ID string `json:"id"`
Quantity float64 `json:"quantity"`
Product InventoryRequestLineProductResponse `json:"product"`
ClientID string `json:"client_id"`
}
type InventoryRequestLineProductResponse struct {
ID string `json:"id"`
Name string `json:"name"`
RefNumber string `json:"ref_number"`
Uom pkgdto.IdNameResponse `json:"uom"`
}
type InventoryRequestLineUpdateRequest struct {
Quantity *float64 `json:"quantity"`
ProductID *string `json:"product_id"`
}

View File

@ -0,0 +1,22 @@
package query
import (
"gorm.io/gorm"
)
type InventoryRequestFilter struct {
ClientID string `form:"client_id"`
RequestType string `form:"request_type"`
PerPage int `form:"per_page"`
Page int `form:"page"`
}
func ApplyInventoryRequestFilters(db *gorm.DB, filter InventoryRequestFilter) *gorm.DB {
if filter.ClientID != "" {
db = db.Where("client_id = ?", filter.ClientID)
}
if filter.RequestType != "" {
db = db.Where("request_type ILIKE ?", "%"+filter.RequestType+"%")
}
return db
}

View File

@ -0,0 +1,89 @@
package repository
import (
"context"
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
"gorm.io/gorm"
)
type InventoryRequestLineRepository interface {
Create(ctx context.Context, tx *gorm.DB, line entities.TInventoryRequestLineEntity) (entities.TInventoryRequestLineEntity, error)
GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryRequestLineEntity, error)
GetAllByRequestId(ctx context.Context, requestId string) ([]entities.TInventoryRequestLineEntity, error)
Update(ctx context.Context, tx *gorm.DB, line entities.TInventoryRequestLineEntity) (entities.TInventoryRequestLineEntity, error)
Delete(ctx context.Context, tx *gorm.DB, id string) error
BulkCreate(ctx context.Context, tx *gorm.DB, lines []entities.TInventoryRequestLineEntity) error
DeleteByRequestId(ctx context.Context, tx *gorm.DB, requestId string) error
}
type inventoryRequestLineRepository struct {
db *gorm.DB
}
func NewInventoryRequestLineRepository(db *gorm.DB) InventoryRequestLineRepository {
return &inventoryRequestLineRepository{db: db}
}
func (r *inventoryRequestLineRepository) Create(ctx context.Context, tx *gorm.DB, line entities.TInventoryRequestLineEntity) (entities.TInventoryRequestLineEntity, error) {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Create(&line).Error; err != nil {
return line, err
}
return line, nil
}
func (r *inventoryRequestLineRepository) GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryRequestLineEntity, error) {
if tx == nil {
tx = r.db
}
var line entities.TInventoryRequestLineEntity
if err := tx.WithContext(ctx).Preload("Product").Preload("InvRequest").Preload("Client").First(&line, "id = ?", id).Error; err != nil {
return line, err
}
return line, nil
}
func (r *inventoryRequestLineRepository) GetAllByRequestId(ctx context.Context, requestId string) ([]entities.TInventoryRequestLineEntity, error) {
var lines []entities.TInventoryRequestLineEntity
if err := r.db.WithContext(ctx).Where("inv_request_id = ?", requestId).Preload("Product").Preload("InvRequest").Preload("Client").Find(&lines).Error; err != nil {
return lines, err
}
return lines, nil
}
func (r *inventoryRequestLineRepository) Update(ctx context.Context, tx *gorm.DB, line entities.TInventoryRequestLineEntity) (entities.TInventoryRequestLineEntity, error) {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Model(&entities.TInventoryRequestLineEntity{}).Where("id = ?", line.ID).Select("*").Updates(&line).Error; err != nil {
return line, err
}
return line, nil
}
func (r *inventoryRequestLineRepository) Delete(ctx context.Context, tx *gorm.DB, id string) error {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Delete(&entities.TInventoryRequestLineEntity{}, "id = ?", id).Error; err != nil {
return err
}
return nil
}
func (r *inventoryRequestLineRepository) BulkCreate(ctx context.Context, tx *gorm.DB, lines []entities.TInventoryRequestLineEntity) error {
if tx == nil {
tx = r.db
}
return tx.WithContext(ctx).Create(&lines).Error
}
func (r *inventoryRequestLineRepository) DeleteByRequestId(ctx context.Context, tx *gorm.DB, requestId string) error {
if tx == nil {
tx = r.db
}
return tx.WithContext(ctx).Where("inv_request_id = ?", requestId).Delete(&entities.TInventoryRequestLineEntity{}).Error
}

View File

@ -0,0 +1,88 @@
package repository
import (
"context"
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/query"
"gorm.io/gorm"
)
type InventoryRequestRepository interface {
Create(ctx context.Context, tx *gorm.DB, request entities.TInventoryRequestEntity) (entities.TInventoryRequestEntity, error)
GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryRequestEntity, error)
GetAll(ctx context.Context, filter query.InventoryRequestFilter) ([]entities.TInventoryRequestEntity, int64, error)
Update(ctx context.Context, tx *gorm.DB, request entities.TInventoryRequestEntity) (entities.TInventoryRequestEntity, error)
Delete(ctx context.Context, tx *gorm.DB, id string) error
}
type inventoryRequestRepository struct {
db *gorm.DB
}
func NewInventoryRequestRepository(db *gorm.DB) InventoryRequestRepository {
return &inventoryRequestRepository{db: db}
}
func (r *inventoryRequestRepository) Create(ctx context.Context, tx *gorm.DB, request entities.TInventoryRequestEntity) (entities.TInventoryRequestEntity, error) {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Create(&request).Error; err != nil {
return request, err
}
return request, nil
}
func (r *inventoryRequestRepository) GetById(ctx context.Context, tx *gorm.DB, id string) (entities.TInventoryRequestEntity, error) {
if tx == nil {
tx = r.db
}
var request entities.TInventoryRequestEntity
if err := tx.WithContext(ctx).
Preload("Client").
Preload("RequestLines").
Preload("RequestLines.Product").
Preload("Assignment").
Preload("Assignment.AssignmentUsers").
Preload("Assignment.AssignmentUsers.User").
Preload("Assignment.AssignmentUsers.Role").
First(&request, "id = ?", id).Error; err != nil {
return request, err
}
return request, nil
}
func (r *inventoryRequestRepository) GetAll(ctx context.Context, filter query.InventoryRequestFilter) ([]entities.TInventoryRequestEntity, int64, error) {
var requests []entities.TInventoryRequestEntity
var total int64
db := query.ApplyInventoryRequestFilters(r.db, filter)
db.Model(&entities.TInventoryRequestEntity{}).Count(&total)
if filter.PerPage > 0 && filter.Page > 0 {
db = db.Offset((filter.Page - 1) * filter.PerPage).Limit(filter.PerPage)
}
if err := db.Preload("Client").Find(&requests).Error; err != nil {
return requests, total, err
}
return requests, total, nil
}
func (r *inventoryRequestRepository) Update(ctx context.Context, tx *gorm.DB, request entities.TInventoryRequestEntity) (entities.TInventoryRequestEntity, error) {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Model(&entities.TInventoryRequestEntity{}).Where("id = ?", request.ID).Select("*").Updates(&request).Error; err != nil {
return request, err
}
return request, nil
}
func (r *inventoryRequestRepository) Delete(ctx context.Context, tx *gorm.DB, id string) error {
if tx == nil {
tx = r.db
}
if err := tx.WithContext(ctx).Delete(&entities.TInventoryRequestEntity{}, "id = ?", id).Error; err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,27 @@
package inventoryrequest
import (
"github.com/Caknoooo/go-gin-clean-starter/middlewares"
"github.com/Caknoooo/go-gin-clean-starter/modules/auth/service"
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/controller"
"github.com/Caknoooo/go-gin-clean-starter/pkg/constants"
"github.com/gin-gonic/gin"
"github.com/samber/do"
)
func RegisterRoutes(server *gin.Engine, injector *do.Injector) {
requestController := do.MustInvoke[controller.InventoryRequestController](injector)
jwtService := do.MustInvokeNamed[service.JWTService](injector, constants.JWTService)
requestRoutes := server.Group("/api/v1/inventory-requests")
{
requestRoutes.POST("", middlewares.Authenticate(jwtService), requestController.Create)
requestRoutes.GET(":id", middlewares.Authenticate(jwtService), requestController.GetById)
requestRoutes.PUT(":id", middlewares.Authenticate(jwtService), requestController.Update)
requestRoutes.DELETE(":id", middlewares.Authenticate(jwtService), requestController.Delete)
requestRoutes.GET("", middlewares.Authenticate(jwtService), requestController.GetAll)
requestRoutes.POST(":id/lines", middlewares.Authenticate(jwtService), requestController.CreateLine)
requestRoutes.PUT("lines/:id", middlewares.Authenticate(jwtService), requestController.UpdateLine)
requestRoutes.DELETE("lines/:id", middlewares.Authenticate(jwtService), requestController.DeleteLine)
}
}

View File

@ -0,0 +1,342 @@
package service
import (
"context"
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
dtodomain "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/dto"
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/query"
"github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/repository"
pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto"
"github.com/Caknoooo/go-gin-clean-starter/pkg/utils"
"github.com/google/uuid"
"gorm.io/gorm"
)
type InventoryRequestService interface {
Create(ctx context.Context, req dtodomain.InventoryRequestCreateRequest) (dtodomain.InventoryRequestResponse, error)
GetById(ctx context.Context, id string) (dtodomain.InventoryRequestResponse, error)
GetAll(ctx context.Context, filter query.InventoryRequestFilter) ([]dtodomain.InventoryRequestResponse, int64, error)
Update(ctx context.Context, req dtodomain.InventoryRequestUpdateRequest, id string) (dtodomain.InventoryRequestResponse, error)
Delete(ctx context.Context, id string) error
CreateLine(ctx context.Context, requestId string, req dtodomain.InventoryRequestLineCreateRequest) (dtodomain.InventoryRequestLineResponse, error)
UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryRequestLineUpdateRequest) (dtodomain.InventoryRequestLineResponse, error)
DeleteLine(ctx context.Context, lineId string) error
}
type inventoryRequestService struct {
db *gorm.DB
requestRepo repository.InventoryRequestRepository
requestLineRepo repository.InventoryRequestLineRepository
}
func (s *inventoryRequestService) DeleteLine(ctx context.Context, lineId string) error {
return s.requestLineRepo.Delete(ctx, nil, lineId)
}
func (s *inventoryRequestService) UpdateLine(ctx context.Context, lineId string, req dtodomain.InventoryRequestLineUpdateRequest) (dtodomain.InventoryRequestLineResponse, error) {
line, err := s.requestLineRepo.GetById(ctx, nil, lineId)
if err != nil {
return dtodomain.InventoryRequestLineResponse{}, err
}
if req.Quantity != nil {
line.Quantity = *req.Quantity
}
if req.ProductID != nil {
if *req.ProductID != "" {
tmp, err := uuid.Parse(*req.ProductID)
if err != nil {
return dtodomain.InventoryRequestLineResponse{}, err
}
line.ProductID = tmp
} else {
line.ProductID = uuid.Nil
}
}
updated, err := s.requestLineRepo.Update(ctx, nil, line)
if err != nil {
return dtodomain.InventoryRequestLineResponse{}, err
}
product := dtodomain.InventoryRequestLineProductResponse{}
if updated.Product.ID != uuid.Nil {
product = dtodomain.InventoryRequestLineProductResponse{
ID: updated.Product.ID.String(),
Name: updated.Product.Name,
RefNumber: updated.Product.RefNumber,
Uom: pkgdto.IdNameResponse{
ID: updated.Product.Uom.ID.String(),
Name: updated.Product.Uom.Name,
},
}
}
return dtodomain.InventoryRequestLineResponse{
ID: updated.ID.String(),
Quantity: updated.Quantity,
Product: product,
ClientID: updated.ClientID.String(),
}, nil
}
func toAssignmentResponse(e entities.TAssignmentEntity) dtodomain.AssignmentResponse {
users := make([]dtodomain.AssignmentUserResponse, 0)
for _, user := range e.AssignmentUsers {
userResp := dtodomain.AssignmentUserResponse{
ID: user.ID.String(),
TaskType: user.TaskType,
User: pkgdto.IdNameResponse{ID: user.User.ID.String(), Name: user.User.Name},
Role: pkgdto.IdNameResponse{ID: user.Role.ID.String(), Name: user.Role.Name},
}
users = append(users, userResp)
}
return dtodomain.AssignmentResponse{
ID: e.ID.String(),
DocumentType: e.DocumentType,
DocumentID: e.DocumentID.String(),
AssignmentUsers: users,
}
}
func toInventoryRequestResponse(e entities.TInventoryRequestEntity) dtodomain.InventoryRequestResponse {
client := pkgdto.IdNameResponse{}
if e.Client.ID != uuid.Nil {
client = pkgdto.IdNameResponse{
ID: e.Client.ID.String(),
Name: e.Client.Name,
}
}
lines := make([]dtodomain.InventoryRequestLineResponse, 0)
for _, line := range e.RequestLines {
product := dtodomain.InventoryRequestLineProductResponse{}
if line.Product.ID != uuid.Nil {
product = dtodomain.InventoryRequestLineProductResponse{
ID: line.Product.ID.String(),
Name: line.Product.Name,
RefNumber: line.Product.RefNumber,
Uom: pkgdto.IdNameResponse{
ID: line.Product.Uom.ID.String(),
Name: line.Product.Uom.Name,
},
}
}
lines = append(lines, dtodomain.InventoryRequestLineResponse{
ID: line.ID.String(),
Quantity: line.Quantity,
Product: product,
ClientID: line.ClientID.String(),
})
}
assignment := dtodomain.AssignmentResponse{}
if e.Assignment.ID != uuid.Nil {
assignment = toAssignmentResponse(e.Assignment)
}
return dtodomain.InventoryRequestResponse{
ID: e.ID.String(),
ReferenceNumber: e.ReferenceNumber,
DocumentNumber: e.DocumentNumber,
DueDate: utils.DateTimeToString(e.DueDate),
RequestType: e.RequestType,
Note: e.Note,
Status: e.Status,
Client: client,
LineCount: len(lines),
RequestLines: lines,
Assignment: assignment,
}
}
func (s *inventoryRequestService) Create(ctx context.Context, req dtodomain.InventoryRequestCreateRequest) (dtodomain.InventoryRequestResponse, error) {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
clientUUID, err := uuid.Parse(req.ClientID)
if err != nil {
tx.Rollback()
return dtodomain.InventoryRequestResponse{}, err
}
docNum, err := entities.GenerateDocumentNumberInventoryRequest(s.db, req.ClientID)
if err != nil {
tx.Rollback()
return dtodomain.InventoryRequestResponse{}, err
}
request := entities.TInventoryRequestEntity{
ReferenceNumber: req.ReferenceNumber,
DocumentNumber: docNum,
DueDate: utils.StringToDateTime(req.DueDate),
RequestType: req.RequestType,
Note: req.Note,
ClientID: clientUUID,
Status: req.Status,
}
created, err := s.requestRepo.Create(ctx, tx, request)
if err != nil {
tx.Rollback()
return dtodomain.InventoryRequestResponse{}, err
}
var lines []entities.TInventoryRequestLineEntity
for _, lineReq := range req.RequestLines {
var productUUID uuid.UUID
if lineReq.ProductID != "" {
productUUID, err = uuid.Parse(lineReq.ProductID)
if err != nil {
tx.Rollback()
return dtodomain.InventoryRequestResponse{}, err
}
} else {
productUUID = uuid.Nil
}
clientLineUUID, err := uuid.Parse(lineReq.ClientID)
if err != nil {
tx.Rollback()
return dtodomain.InventoryRequestResponse{}, err
}
lines = append(lines, entities.TInventoryRequestLineEntity{
Quantity: lineReq.Quantity,
InvRequestID: created.ID,
ProductID: productUUID,
ClientID: clientLineUUID,
})
}
if len(lines) > 0 {
err = s.requestLineRepo.BulkCreate(ctx, tx, lines)
if err != nil {
tx.Rollback()
return dtodomain.InventoryRequestResponse{}, err
}
}
tx.Commit()
result, err := s.requestRepo.GetById(ctx, nil, created.ID.String())
if err != nil {
return dtodomain.InventoryRequestResponse{}, err
}
return toInventoryRequestResponse(result), nil
}
func (s *inventoryRequestService) GetById(ctx context.Context, id string) (dtodomain.InventoryRequestResponse, error) {
request, err := s.requestRepo.GetById(ctx, nil, id)
if err != nil {
return dtodomain.InventoryRequestResponse{}, err
}
return toInventoryRequestResponse(request), nil
}
func (s *inventoryRequestService) GetAll(ctx context.Context, filter query.InventoryRequestFilter) ([]dtodomain.InventoryRequestResponse, int64, error) {
requests, total, err := s.requestRepo.GetAll(ctx, filter)
if err != nil {
return nil, 0, err
}
var responses []dtodomain.InventoryRequestResponse
for _, e := range requests {
responses = append(responses, toInventoryRequestResponse(e))
}
if responses == nil {
responses = make([]dtodomain.InventoryRequestResponse, 0)
}
return responses, total, nil
}
func (s *inventoryRequestService) Update(ctx context.Context, req dtodomain.InventoryRequestUpdateRequest, id string) (dtodomain.InventoryRequestResponse, error) {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
request, err := s.requestRepo.GetById(ctx, tx, id)
if err != nil {
tx.Rollback()
return dtodomain.InventoryRequestResponse{}, err
}
if req.ReferenceNumber != "" {
request.ReferenceNumber = req.ReferenceNumber
}
request.DocumentNumber = req.DocumentNumber
request.DueDate = utils.StringToDateTime(req.DueDate)
request.RequestType = req.RequestType
request.Note = req.Note
request.Status = req.Status
updated, err := s.requestRepo.Update(ctx, tx, request)
if err != nil {
tx.Rollback()
return dtodomain.InventoryRequestResponse{}, err
}
tx.Commit()
result, err := s.requestRepo.GetById(ctx, nil, updated.ID.String())
if err != nil {
return dtodomain.InventoryRequestResponse{}, err
}
return toInventoryRequestResponse(result), nil
}
func (s *inventoryRequestService) Delete(ctx context.Context, id string) error {
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
if err := s.requestRepo.Delete(ctx, tx, id); err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
}
func (s *inventoryRequestService) CreateLine(ctx context.Context, requestId string, req dtodomain.InventoryRequestLineCreateRequest) (dtodomain.InventoryRequestLineResponse, error) {
requestUUID, err := uuid.Parse(requestId)
if err != nil {
return dtodomain.InventoryRequestLineResponse{}, err
}
var productUUID uuid.UUID
if req.ProductID != "" {
productUUID, err = uuid.Parse(req.ProductID)
if err != nil {
return dtodomain.InventoryRequestLineResponse{}, err
}
} else {
productUUID = uuid.Nil
}
clientLineUUID, err := uuid.Parse(req.ClientID)
if err != nil {
return dtodomain.InventoryRequestLineResponse{}, err
}
line := entities.TInventoryRequestLineEntity{
Quantity: req.Quantity,
InvRequestID: requestUUID,
ProductID: productUUID,
ClientID: clientLineUUID,
}
created, err := s.requestLineRepo.Create(ctx, nil, line)
if err != nil {
return dtodomain.InventoryRequestLineResponse{}, err
}
product := dtodomain.InventoryRequestLineProductResponse{}
if created.Product.ID != uuid.Nil {
product = dtodomain.InventoryRequestLineProductResponse{
ID: created.Product.ID.String(),
Name: created.Product.Name,
RefNumber: created.Product.RefNumber,
Uom: pkgdto.IdNameResponse{
ID: created.Product.Uom.ID.String(),
Name: created.Product.Uom.Name,
},
}
}
return dtodomain.InventoryRequestLineResponse{
ID: created.ID.String(),
Quantity: created.Quantity,
Product: product,
ClientID: created.ClientID.String(),
}, nil
}
func NewInventoryRequestService(db *gorm.DB, requestRepo repository.InventoryRequestRepository, requestLineRepo repository.InventoryRequestLineRepository) InventoryRequestService {
return &inventoryRequestService{
db: db,
requestRepo: requestRepo,
requestLineRepo: requestLineRepo,
}
}

View File

@ -68,6 +68,11 @@ import (
assignmentUserRepo "github.com/Caknoooo/go-gin-clean-starter/modules/assignment/repository"
assignmentService "github.com/Caknoooo/go-gin-clean-starter/modules/assignment/service"
inventoryRequestController "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/controller"
inventoryRequestLineRepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/repository"
inventoryRequestRepo "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/repository"
inventoryRequestService "github.com/Caknoooo/go-gin-clean-starter/modules/inventory_request/service"
"github.com/Caknoooo/go-gin-clean-starter/modules/user/controller"
"github.com/Caknoooo/go-gin-clean-starter/modules/user/repository"
userService "github.com/Caknoooo/go-gin-clean-starter/modules/user/service"
@ -121,6 +126,8 @@ func RegisterDependencies(injector *do.Injector) {
inventoryReceiptLineRepository := inventoryReceiptLineRepo.NewInventoryReceiptLineRepository(db)
assignmentRepository := assignmentRepo.NewAssignmentRepository(db)
assignmentUserRepository := assignmentUserRepo.NewAssignmentUserRepository(db)
inventoryRequestRepository := inventoryRequestRepo.NewInventoryRequestRepository(db)
inventoryRequestLineRepository := inventoryRequestLineRepo.NewInventoryRequestLineRepository(db)
// Service
userServ := userService.NewUserService(userRepository, refreshTokenRepository, jwtService, db)
@ -138,6 +145,7 @@ func RegisterDependencies(injector *do.Injector) {
aisleServ := aisleService.NewAisleService(aisleRepository, db)
inventoryReceiptServ := inventoryReceiptService.NewInventoryReceiptService(db, inventoryReceiptRepository, inventoryReceiptLineRepository)
assignmentServ := assignmentService.NewAssignmentService(db, assignmentRepository, assignmentUserRepository)
inventoryRequestServ := inventoryRequestService.NewInventoryRequestService(db, inventoryRequestRepository, inventoryRequestLineRepository)
// Controller
do.Provide(
@ -225,4 +233,9 @@ func RegisterDependencies(injector *do.Injector) {
return assignmentController.NewAssignmentController(i, assignmentServ), nil
},
)
do.Provide(
injector, func(i *do.Injector) (inventoryRequestController.InventoryRequestController, error) {
return inventoryRequestController.NewInventoryRequestController(i, inventoryRequestServ), nil
},
)
}