fix: next period,purchase before bop, integration auth module,fix validation-master data

This commit is contained in:
ragilap
2025-11-25 10:32:15 +07:00
parent 8881be2a22
commit c02f72c5e5
63 changed files with 838 additions and 864 deletions
@@ -23,21 +23,19 @@ func NewPurchaseController(s service.PurchaseService) *PurchaseController {
}
func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error {
query := &validation.PurchaseQuery{
query := &validation.Query{
Page: c.QueryInt("page", 1),
Limit: c.QueryInt("limit", 10),
Search: strings.TrimSpace(c.Query("search")),
PrNumber: strings.TrimSpace(c.Query("pr_number")),
CreatedFrom: strings.TrimSpace(c.Query("created_from")),
CreatedTo: strings.TrimSpace(c.Query("created_to")),
SupplierID: uint(c.QueryInt("supplier_id", 0)),
AreaID: uint(c.QueryInt("area_id", 0)),
LocationID: uint(c.QueryInt("location_id", 0)),
ProductCategoryID: uint(c.QueryInt("product_category_id", 0)),
}
if supplierID := c.QueryInt("supplier_id", 0); supplierID > 0 {
query.SupplierID = uint(supplierID)
}
if status := strings.TrimSpace(c.Query("status")); status != "" {
query.Status = strings.ToUpper(status)
if query.Page < 1 || query.Limit < 1 {
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
}
results, total, err := ctrl.service.GetAll(c, query)
@@ -45,24 +43,15 @@ func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error {
return err
}
limit := query.Limit
if limit <= 0 {
limit = 10
}
page := query.Page
if page <= 0 {
page = 1
}
return c.Status(fiber.StatusOK).
JSON(response.SuccessWithPaginate[dto.PurchaseListItemDTO]{
JSON(response.SuccessWithPaginate[dto.PurchaseListDTO]{
Code: fiber.StatusOK,
Status: "success",
Message: "Purchase fetched successfully",
Meta: response.Meta{
Page: page,
Limit: limit,
TotalPages: int64(math.Ceil(float64(total) / float64(limit))),
Page: query.Page,
Limit: query.Limit,
TotalPages: int64(math.Ceil(float64(total) / float64(query.Limit))),
TotalResults: total,
},
Data: dto.ToPurchaseListDTOs(results),
@@ -71,12 +60,13 @@ func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error {
func (ctrl *PurchaseController) GetOne(c *fiber.Ctx) error {
param := c.Params("id")
id, err := strconv.ParseUint(param, 10, 64)
id, err := strconv.Atoi(param)
if err != nil || id == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
}
result, err := ctrl.service.GetOne(c, id)
result, err := ctrl.service.GetOne(c, uint(id))
if err != nil {
return err
}
@@ -96,7 +86,7 @@ func (ctrl *PurchaseController) CreateOne(c *fiber.Ctx) error {
if err := c.BodyParser(req); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
result, err := ctrl.service.CreateOne(c, req)
if err != nil {
return err
@@ -113,7 +103,7 @@ func (ctrl *PurchaseController) CreateOne(c *fiber.Ctx) error {
func (ctrl *PurchaseController) ApproveStaffPurchase(c *fiber.Ctx) error {
param := c.Params("id")
id, err := strconv.ParseUint(param, 10, 64)
id, err := strconv.Atoi(param)
if err != nil || id == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
}
@@ -123,7 +113,7 @@ func (ctrl *PurchaseController) ApproveStaffPurchase(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid request body: %v", err))
}
result, err := ctrl.service.ApproveStaffPurchase(c, id, req)
result, err := ctrl.service.ApproveStaffPurchase(c, uint(id), req)
if err != nil {
return err
}
@@ -137,10 +127,9 @@ func (ctrl *PurchaseController) ApproveStaffPurchase(c *fiber.Ctx) error {
})
}
func (ctrl *PurchaseController) ApproveManagerPurchase(c *fiber.Ctx) error {
param := c.Params("id")
id, err := strconv.ParseUint(param, 10, 64)
id, err := strconv.Atoi(param)
if err != nil || id == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
}
@@ -150,7 +139,7 @@ func (ctrl *PurchaseController) ApproveManagerPurchase(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
result, err := ctrl.service.ApproveManagerPurchase(c, id, req)
result, err := ctrl.service.ApproveManagerPurchase(c, uint(id), req)
if err != nil {
return err
}
@@ -166,7 +155,7 @@ func (ctrl *PurchaseController) ApproveManagerPurchase(c *fiber.Ctx) error {
func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error {
param := c.Params("id")
id, err := strconv.ParseUint(param, 10, 64)
id, err := strconv.Atoi(param)
if err != nil || id == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
}
@@ -176,7 +165,7 @@ func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
result, err := ctrl.service.ReceiveProducts(c, id, req)
result, err := ctrl.service.ReceiveProducts(c, uint(id), req)
if err != nil {
return err
}
@@ -192,7 +181,7 @@ func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error {
func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error {
param := c.Params("id")
id, err := strconv.ParseUint(param, 10, 64)
id, err := strconv.Atoi(param)
if err != nil || id == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
}
@@ -202,7 +191,7 @@ func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
}
result, err := ctrl.service.DeleteItems(c, id, req)
result, err := ctrl.service.DeleteItems(c, uint(id), req)
if err != nil {
return err
}
@@ -218,12 +207,12 @@ func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error {
func (ctrl *PurchaseController) DeletePurchase(c *fiber.Ctx) error {
param := c.Params("id")
id, err := strconv.ParseUint(param, 10, 64)
id, err := strconv.Atoi(param)
if err != nil || id == 0 {
return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id")
}
if err := ctrl.service.DeletePurchase(c, id); err != nil {
if err := ctrl.service.DeletePurchase(c, uint(id)); err != nil {
return err
}
+111 -84
View File
@@ -10,46 +10,51 @@ import (
productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto"
supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto"
warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto"
)
type PurchaseListItemDTO struct {
Id uint64 `json:"id"`
PrNumber string `json:"pr_number"`
PoNumber *string `json:"po_number"`
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
CreditTerm *int `json:"credit_term"`
DueDate *time.Time `json:"due_date"`
PoDate *time.Time `json:"po_date"`
GrandTotal float64 `json:"grand_total"`
Notes *string `json:"notes"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Approval *approvalDTO.ApprovalRelationDTO `json:"approval"`
type PurchaseRelationDTO struct {
Id uint `json:"id"`
PrNumber string `json:"pr_number"`
PoNumber *string `json:"po_number"`
PoDate *time.Time `json:"po_date"`
Notes *string `json:"notes"`
}
type PurchaseListDTO struct {
PurchaseRelationDTO
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
CreditTerm *int `json:"credit_term"`
DueDate *time.Time `json:"due_date"`
GrandTotal float64 `json:"grand_total"`
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
}
type PurchaseDetailDTO struct {
Id uint64 `json:"id"`
PrNumber string `json:"pr_number"`
PoNumber *string `json:"po_number"`
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
CreditTerm *int `json:"credit_term"`
DueDate *time.Time `json:"due_date"`
PoDate *time.Time `json:"po_date"`
GrandTotal float64 `json:"grand_total"`
Notes *string `json:"notes"`
Items []PurchaseItemDTO `json:"items"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Approval *approvalDTO.ApprovalRelationDTO `json:"approval"`
PurchaseRelationDTO
Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"`
CreditTerm *int `json:"credit_term"`
DueDate *time.Time `json:"due_date"`
GrandTotal float64 `json:"grand_total"`
Items []PurchaseItemDTO `json:"items"`
CreatedUser *userDTO.UserRelationDTO `json:"created_user"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"`
}
type PurchaseItemDTO struct {
Id uint64 `json:"id"`
ProductID uint64 `json:"product_id"`
Id uint `json:"id"`
ProductID uint `json:"product_id"`
Product *productDTO.ProductRelationDTO `json:"product"`
WarehouseID uint64 `json:"warehouse_id"`
WarehouseID uint `json:"warehouse_id"`
Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse"`
ProductWarehouseID *uint64 `json:"product_warehouse_id"`
ProductWarehouseID *uint `json:"product_warehouse_id"`
SubQty float64 `json:"sub_qty"`
TotalQty float64 `json:"total_qty"`
TotalUsed float64 `json:"total_used"`
@@ -61,6 +66,17 @@ type PurchaseItemDTO struct {
VehicleNumber *string `json:"vehicle_number"`
}
func ToPurchaseRelationDTO(p *entity.Purchase) PurchaseRelationDTO {
return PurchaseRelationDTO{
Id: p.Id,
PrNumber: p.PrNumber,
PoNumber: p.PoNumber,
PoDate: p.PoDate,
Notes: p.Notes,
}
}
func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO {
dto := PurchaseItemDTO{
Id: item.Id,
@@ -77,10 +93,12 @@ func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO {
TravelDocumentPath: item.TravelNumberDocs,
VehicleNumber: item.VehicleNumber,
}
if item.Product != nil && item.Product.Id != 0 {
summary := productDTO.ToProductRelationDTO(*item.Product)
dto.Product = &summary
}
if item.Warehouse != nil && item.Warehouse.Id != 0 {
summary := warehouseDTO.ToWarehouseRelationDTO(*item.Warehouse)
if item.Warehouse.Area.Id != 0 {
@@ -93,6 +111,7 @@ func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO {
}
dto.Warehouse = &summary
}
return dto
}
@@ -104,70 +123,78 @@ func ToPurchaseItemDTOs(items []entity.PurchaseItem) []PurchaseItemDTO {
return result
}
func ToPurchaseDetailDTO(p entity.Purchase) PurchaseDetailDTO {
dto := PurchaseDetailDTO{
Id: p.Id,
PrNumber: p.PrNumber,
PoNumber: p.PoNumber,
Supplier: mapSupplier(p.Supplier),
CreditTerm: p.CreditTerm,
DueDate: p.DueDate,
PoDate: p.PoDate,
GrandTotal: p.GrandTotal,
Notes: p.Notes,
Items: ToPurchaseItemDTOs(p.Items),
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO {
var supplier *supplierDTO.SupplierRelationDTO
if p.Supplier.Id != 0 {
mapped := supplierDTO.ToSupplierRelationDTO(p.Supplier)
supplier = &mapped
}
if approval := toPurchaseApprovalDTO(p); approval != nil {
dto.Approval = approval
var createdUser *userDTO.UserRelationDTO
if p.CreatedUser.Id != 0 {
mapped := userDTO.ToUserRelationDTO(p.CreatedUser)
createdUser = &mapped
}
var latestApproval *approvalDTO.ApprovalRelationDTO
if p.LatestApproval != nil && p.LatestApproval.Id != 0 {
mapped := approvalDTO.ToApprovalDTO(*p.LatestApproval)
latestApproval = &mapped
}
return PurchaseListDTO{
PurchaseRelationDTO: ToPurchaseRelationDTO(&p),
Supplier: supplier,
CreditTerm: p.CreditTerm,
DueDate: p.DueDate,
GrandTotal: p.GrandTotal,
CreatedUser: createdUser,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
LatestApproval: latestApproval,
}
return dto
}
func ToPurchaseListDTO(p entity.Purchase) PurchaseListItemDTO {
dto := PurchaseListItemDTO{
Id: p.Id,
PrNumber: p.PrNumber,
PoNumber: p.PoNumber,
Supplier: mapSupplier(p.Supplier),
CreditTerm: p.CreditTerm,
DueDate: p.DueDate,
PoDate: p.PoDate,
GrandTotal: p.GrandTotal,
Notes: p.Notes,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
}
if approval := toPurchaseApprovalDTO(p); approval != nil {
dto.Approval = approval
}
return dto
}
func mapSupplier(s entity.Supplier) *supplierDTO.SupplierRelationDTO {
if s.Id == 0 {
return nil
}
summary := supplierDTO.ToSupplierRelationDTO(s)
return &summary
}
func ToPurchaseListDTOs(items []entity.Purchase) []PurchaseListItemDTO {
func ToPurchaseListDTOs(items []entity.Purchase) []PurchaseListDTO {
if len(items) == 0 {
return nil
return make([]PurchaseListDTO, 0)
}
result := make([]PurchaseListItemDTO, len(items))
result := make([]PurchaseListDTO, len(items))
for i, item := range items {
result[i] = ToPurchaseListDTO(item)
}
return result
}
func toPurchaseApprovalDTO(p entity.Purchase) *approvalDTO.ApprovalRelationDTO {
if p.LatestApproval == nil || p.LatestApproval.Id == 0 {
return nil
func ToPurchaseDetailDTO(p entity.Purchase) PurchaseDetailDTO {
var supplier *supplierDTO.SupplierRelationDTO
if p.Supplier.Id != 0 {
mapped := supplierDTO.ToSupplierRelationDTO(p.Supplier)
supplier = &mapped
}
mapped := approvalDTO.ToApprovalDTO(*p.LatestApproval)
return &mapped
}
var createdUser *userDTO.UserRelationDTO
if p.CreatedUser.Id != 0 {
mapped := userDTO.ToUserRelationDTO(p.CreatedUser)
createdUser = &mapped
}
var latestApproval *approvalDTO.ApprovalRelationDTO
if p.LatestApproval != nil && p.LatestApproval.Id != 0 {
mapped := approvalDTO.ToApprovalDTO(*p.LatestApproval)
latestApproval = &mapped
}
return PurchaseDetailDTO{
PurchaseRelationDTO: ToPurchaseRelationDTO(&p),
Supplier: supplier,
CreditTerm: p.CreditTerm,
DueDate: p.DueDate,
GrandTotal: p.GrandTotal,
Items: ToPurchaseItemDTOs(p.Items),
CreatedUser: createdUser,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
LatestApproval: latestApproval,
}
}
-1
View File
@@ -43,7 +43,6 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate
warehouseRepo,
supplierRepo,
productWarehouseRepo,
approvalRepo,
approvalService,
expenseBridge,
)
@@ -18,14 +18,11 @@ import (
type PurchaseRepository interface {
repository.BaseRepository[entity.Purchase]
CreateWithItems(ctx context.Context, purchase *entity.Purchase, items []*entity.PurchaseItem) error
CreateItems(ctx context.Context, purchaseID uint64, items []*entity.PurchaseItem) error
GetByIDWithRelations(ctx context.Context, id uint64) (*entity.Purchase, error)
GetAllWithFilters(ctx context.Context, offset, limit int, filter *PurchaseListFilter) ([]entity.Purchase, int64, error)
UpdatePricing(ctx context.Context, purchaseID uint64, updates []PurchasePricingUpdate, grandTotal float64) error
UpdateReceivingDetails(ctx context.Context, purchaseID uint64, updates []PurchaseReceivingUpdate) error
DeleteItems(ctx context.Context, purchaseID uint64, itemIDs []uint64) error
WithListRelations() func(*gorm.DB) *gorm.DB
UpdateGrandTotal(ctx context.Context, purchaseID uint64, grandTotal float64) error
CreateItems(ctx context.Context, purchaseID uint, items []*entity.PurchaseItem) error
UpdatePricing(ctx context.Context, purchaseID uint, updates []PurchasePricingUpdate, grandTotal float64) error
UpdateReceivingDetails(ctx context.Context, purchaseID uint, updates []PurchaseReceivingUpdate) error
DeleteItems(ctx context.Context, purchaseID uint, itemIDs []uint) error
UpdateGrandTotal(ctx context.Context, purchaseID uint, grandTotal float64) error
NextPrNumber(ctx context.Context, tx *gorm.DB) (string, error)
NextPoNumber(ctx context.Context, tx *gorm.DB) (string, error)
}
@@ -40,19 +37,10 @@ func NewPurchaseRepository(db *gorm.DB) PurchaseRepository {
}
}
type PurchaseListFilter struct {
SupplierID uint
Search string
PrNumber string
CreatedFrom *time.Time
CreatedTo *time.Time
Status *entity.ApprovalAction
CompletedOnly bool
}
func (r *PurchaseRepositoryImpl) CreateWithItems(ctx context.Context, purchase *entity.Purchase, items []*entity.PurchaseItem) error {
db := r.DB().WithContext(ctx)
//ambil dari base repository
if err := db.Create(purchase).Error; err != nil {
return err
}
@@ -71,7 +59,7 @@ func (r *PurchaseRepositoryImpl) CreateWithItems(ctx context.Context, purchase *
return nil
}
func (r *PurchaseRepositoryImpl) CreateItems(ctx context.Context, purchaseID uint64, items []*entity.PurchaseItem) error {
func (r *PurchaseRepositoryImpl) CreateItems(ctx context.Context, purchaseID uint, items []*entity.PurchaseItem) error {
if len(items) == 0 {
return nil
}
@@ -86,52 +74,9 @@ func (r *PurchaseRepositoryImpl) CreateItems(ctx context.Context, purchaseID uin
return r.DB().WithContext(ctx).Create(&items).Error
}
func (r *PurchaseRepositoryImpl) GetByIDWithRelations(ctx context.Context, id uint64) (*entity.Purchase, error) {
var purchase entity.Purchase
err := r.DB().WithContext(ctx).
Scopes(r.withDetailRelations).
First(&purchase, id).Error
if err != nil {
return nil, err
}
return &purchase, nil
}
func (r *PurchaseRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, filter *PurchaseListFilter) ([]entity.Purchase, int64, error) {
return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB {
db = r.withListRelations(db)
return r.applyListFilters(db, filter)
})
}
func (r *PurchaseRepositoryImpl) WithListRelations() func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return r.withListRelations(db)
}
}
func (r *PurchaseRepositoryImpl) withDetailRelations(db *gorm.DB) *gorm.DB {
return db.
Preload("Supplier").
Preload("Items", func(db *gorm.DB) *gorm.DB {
return db.Order("id ASC")
}).
Preload("Items.Product").
Preload("Items.Warehouse").
Preload("Items.Warehouse.Area").
Preload("Items.Warehouse.Location").
Preload("Items.ProductWarehouse")
}
func (r *PurchaseRepositoryImpl) WithDetailRelations() func(*gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return r.withDetailRelations(db)
}
}
type PurchasePricingUpdate struct {
ItemID uint64
ProductID *uint64
ItemID uint
ProductID *uint
Price float64
TotalPrice float64
Quantity *float64
@@ -139,7 +84,7 @@ type PurchasePricingUpdate struct {
}
type PurchaseReceivingUpdate struct {
ItemID uint64
ItemID uint
ReceivedDate *time.Time
TravelNumber *string
TravelDocumentPath *string
@@ -152,7 +97,7 @@ type PurchaseReceivingUpdate struct {
func (r *PurchaseRepositoryImpl) UpdatePricing(
ctx context.Context,
purchaseID uint64,
purchaseID uint,
updates []PurchasePricingUpdate,
grandTotal float64,
) error {
@@ -192,7 +137,6 @@ func (r *PurchaseRepositoryImpl) UpdatePricing(
Where("id = ?", purchaseID).
Updates(map[string]interface{}{
"grand_total": grandTotal,
"updated_at": gorm.Expr("NOW()"),
}).Error; err != nil {
return err
}
@@ -202,7 +146,7 @@ func (r *PurchaseRepositoryImpl) UpdatePricing(
func (r *PurchaseRepositoryImpl) UpdateReceivingDetails(
ctx context.Context,
purchaseID uint64,
purchaseID uint,
updates []PurchaseReceivingUpdate,
) error {
if len(updates) == 0 {
@@ -259,7 +203,7 @@ func (r *PurchaseRepositoryImpl) UpdateReceivingDetails(
func (r *PurchaseRepositoryImpl) UpdateGrandTotal(
ctx context.Context,
purchaseID uint64,
purchaseID uint,
grandTotal float64,
) error {
return r.DB().WithContext(ctx).
@@ -271,7 +215,7 @@ func (r *PurchaseRepositoryImpl) UpdateGrandTotal(
}).Error
}
func (r *PurchaseRepositoryImpl) DeleteItems(ctx context.Context, purchaseID uint64, itemIDs []uint64) error {
func (r *PurchaseRepositoryImpl) DeleteItems(ctx context.Context, purchaseID uint, itemIDs []uint) error {
if len(itemIDs) == 0 {
return errors.New("itemIDs cannot be empty")
}
@@ -361,63 +305,3 @@ func parseNumericSuffix(value, prefix string) (int, bool) {
}
return number, true
}
func (r *PurchaseRepositoryImpl) withListRelations(db *gorm.DB) *gorm.DB {
return db.Preload("Supplier")
}
func (r *PurchaseRepositoryImpl) applyListFilters(db *gorm.DB, filter *PurchaseListFilter) *gorm.DB {
if filter == nil {
return db
}
if filter.SupplierID > 0 {
db = db.Where("purchases.supplier_id = ?", filter.SupplierID)
}
if search := strings.ToLower(strings.TrimSpace(filter.Search)); search != "" {
like := "%" + search + "%"
db = db.Where("(LOWER(purchases.pr_number) LIKE ? OR LOWER(COALESCE(purchases.notes, '')) LIKE ?)", like, like)
}
if pr := strings.TrimSpace(filter.PrNumber); pr != "" {
db = db.Where("purchases.pr_number ILIKE ?", "%"+pr+"%")
}
if filter.CreatedFrom != nil {
db = db.Where("purchases.created_at >= ?", *filter.CreatedFrom)
}
if filter.CreatedTo != nil {
db = db.Where("purchases.created_at < ?", *filter.CreatedTo)
}
if filter.CompletedOnly {
step := uint16(utils.PurchaseStepCompleted)
db = r.applyLatestApprovalFilter(db, entity.ApprovalActionApproved, &step)
} else if filter.Status != nil {
db = r.applyLatestApprovalFilter(db, *filter.Status, nil)
}
return db.Order("purchases.created_at DESC").Order("purchases.id DESC")
}
func (r *PurchaseRepositoryImpl) applyLatestApprovalFilter(db *gorm.DB, action entity.ApprovalAction, minStep *uint16) *gorm.DB {
latestSub := r.DB().
Model(&entity.Approval{}).
Select("approvable_id, MAX(action_at) AS latest_action_at").
Where("approvable_type = ?", utils.ApprovalWorkflowPurchase.String()).
Group("approvable_id")
db = db.
Joins("LEFT JOIN (?) AS latest_purchase_approvals ON latest_purchase_approvals.approvable_id = purchases.id", latestSub).
Joins(
"LEFT JOIN approvals ON approvals.approvable_id = purchases.id AND approvals.approvable_type = ? AND approvals.action_at = latest_purchase_approvals.latest_action_at",
utils.ApprovalWorkflowPurchase.String(),
).
Where("approvals.action = ?", string(action))
if minStep != nil {
db = db.Where("approvals.step_number >= ?", *minStep)
}
return db
}
+2 -2
View File
@@ -3,7 +3,7 @@ package purchases
import (
"github.com/gofiber/fiber/v2"
middleware "gitlab.com/mbugroup/lti-api.git/internal/middleware"
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
controller "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/controllers"
service "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/services"
user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services"
@@ -13,7 +13,7 @@ func Routes(router fiber.Router, purchaseService service.PurchaseService, userSe
ctrl := controller.NewPurchaseController(purchaseService)
route := router.Group("/purchases")
route.Use(middleware.Auth(userService))
route.Use(m.Auth(userService))
route.Get("/", ctrl.GetAll)
route.Get("/:id", ctrl.GetOne)
@@ -9,16 +9,16 @@ import (
// PurchaseExpenseBridge defines hooks that allow purchase flows to stay in sync with expense data once it exists.
type PurchaseExpenseBridge interface {
OnItemsCreated(ctx context.Context, purchaseID uint64, items []entity.PurchaseItem) error
OnItemsDeleted(ctx context.Context, purchaseID uint64, itemIDs []uint64) error
OnItemsReceived(ctx context.Context, purchaseID uint64, updates []ExpenseReceivingPayload) error
OnItemsCreated(ctx context.Context, purchaseID uint, items []entity.PurchaseItem) error
OnItemsDeleted(ctx context.Context, purchaseID uint, itemIDs []uint) error
OnItemsReceived(ctx context.Context, purchaseID uint, updates []ExpenseReceivingPayload) error
}
// ExpenseReceivingPayload captures the minimum data expense integration will need once available.
type ExpenseReceivingPayload struct {
PurchaseItemID uint64
ProductID uint64
WarehouseID uint64
PurchaseItemID uint
ProductID uint
WarehouseID uint
ReceivedQty float64
ReceivedDate *time.Time
}
@@ -30,14 +30,14 @@ func NewNoopPurchaseExpenseBridge() PurchaseExpenseBridge {
return &noopPurchaseExpenseBridge{}
}
func (n *noopPurchaseExpenseBridge) OnItemsCreated(_ context.Context, _ uint64, _ []entity.PurchaseItem) error {
func (n *noopPurchaseExpenseBridge) OnItemsCreated(_ context.Context, _ uint, _ []entity.PurchaseItem) error {
return nil
}
func (n *noopPurchaseExpenseBridge) OnItemsDeleted(_ context.Context, _ uint64, _ []uint64) error {
func (n *noopPurchaseExpenseBridge) OnItemsDeleted(_ context.Context, _ uint, _ []uint) error {
return nil
}
func (n *noopPurchaseExpenseBridge) OnItemsReceived(_ context.Context, _ uint64, _ []ExpenseReceivingPayload) error {
func (n *noopPurchaseExpenseBridge) OnItemsReceived(_ context.Context, _ uint, _ []ExpenseReceivingPayload) error {
return nil
}
File diff suppressed because it is too large Load Diff
@@ -14,26 +14,28 @@ type CreatePurchaseRequest struct {
}
type StaffPurchaseApprovalItem struct {
PurchaseItemID uint64 `json:"purchase_item_id,omitempty" validate:"omitempty,gt=0"`
PurchaseItemID uint `json:"purchase_item_id,omitempty" validate:"omitempty,gt=0"`
// For new items (no purchase_item_id), product_id is required.
ProductID uint64 `json:"product_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
WarehouseID uint64 `json:"warehouse_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
ProductID uint `json:"product_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
WarehouseID uint `json:"warehouse_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
Qty *float64 `json:"qty,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"`
Price float64 `json:"price" validate:"required,gt=0"`
TotalPrice float64 `json:"total_price" validate:"required,gt=0"`
}
type ApproveStaffPurchaseRequest struct {
Items []StaffPurchaseApprovalItem `json:"items" validate:"required,min=1,dive"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"`
Items []StaffPurchaseApprovalItem `json:"items,omitempty" validate:"omitempty,min=1,dive"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
}
type ApproveManagerPurchaseRequest struct {
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
}
type ReceivePurchaseItemRequest struct {
PurchaseItemID uint64 `json:"purchase_item_id" validate:"required,gt=0"`
PurchaseItemID uint `json:"purchase_item_id" validate:"required,gt=0"`
WarehouseID *uint `json:"warehouse_id" validate:"omitempty,gt=0"`
ReceivedDate string `json:"received_date" validate:"required,datetime=2006-01-02"`
TravelNumber *string `json:"travel_number" validate:"omitempty,max=100"`
@@ -43,21 +45,23 @@ type ReceivePurchaseItemRequest struct {
}
type ReceivePurchaseRequest struct {
Items []ReceivePurchaseItemRequest `json:"items" validate:"required,min=1,dive"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"`
Items []ReceivePurchaseItemRequest `json:"items,omitempty" validate:"omitempty,min=1,dive"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
}
type DeletePurchaseItemsRequest struct {
ItemIDs []uint64 `json:"item_ids" validate:"required,min=1,dive,gt=0"`
ItemIDs []uint `json:"item_ids" validate:"required,min=1,dive,gt=0"`
}
type PurchaseQuery struct {
Page int `query:"page" validate:"omitempty,number,min=1"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
SupplierID uint `query:"supplier_id" validate:"omitempty,gt=0"`
Search string `query:"search" validate:"omitempty,max=100"`
PrNumber string `query:"pr_number" validate:"omitempty,max=50"`
CreatedFrom string `query:"created_from" validate:"omitempty,datetime=2006-01-02"`
CreatedTo string `query:"created_to" validate:"omitempty,datetime=2006-01-02"`
Status string `query:"status" validate:"omitempty,oneof=CREATED UPDATED APPROVED REJECTED COMPLETED"`
type Query struct {
Page int `query:"page" validate:"omitempty,number,min=1"`
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"`
SupplierID uint `query:"supplier_id" validate:"omitempty,gt=0"`
AreaID uint `query:"area_id" validate:"omitempty,gt=0"`
LocationID uint `query:"location_id" validate:"omitempty,gt=0"`
ProductCategoryID uint `query:"product_category_id" validate:"omitempty,gt=0"`
Search string `query:"search" validate:"omitempty,max=100"`
CreatedFrom string `query:"created_from" validate:"omitempty,datetime=2006-01-02"`
CreatedTo string `query:"created_to" validate:"omitempty,datetime=2006-01-02"`
}