mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
[FIX/BE-US] feat adjustment location and area
This commit is contained in:
@@ -82,6 +82,18 @@ func ResolveLocationScope(c *fiber.Ctx, db *gorm.DB) (ScopeFilter, error) {
|
|||||||
return ScopeFilter{IDs: locationIDs, Restrict: true}, nil
|
return ScopeFilter{IDs: locationIDs, Restrict: true}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ResolveLocationAreaScopes(c *fiber.Ctx, db *gorm.DB) (ScopeFilter, ScopeFilter, error) {
|
||||||
|
locationScope, err := ResolveLocationScope(c, db)
|
||||||
|
if err != nil {
|
||||||
|
return ScopeFilter{}, ScopeFilter{}, err
|
||||||
|
}
|
||||||
|
areaScope, err := ResolveAreaScope(c, db)
|
||||||
|
if err != nil {
|
||||||
|
return ScopeFilter{}, ScopeFilter{}, err
|
||||||
|
}
|
||||||
|
return locationScope, areaScope, nil
|
||||||
|
}
|
||||||
|
|
||||||
func collectRoleScope(c *fiber.Ctx) (roleScope, error) {
|
func collectRoleScope(c *fiber.Ctx) (roleScope, error) {
|
||||||
ctx, ok := AuthDetails(c)
|
ctx, ok := AuthDetails(c)
|
||||||
if !ok || ctx == nil {
|
if !ok || ctx == nil {
|
||||||
@@ -212,6 +224,31 @@ func ApplyAreaScope(c *fiber.Ctx, db *gorm.DB, column string) (*gorm.DB, error)
|
|||||||
return ApplyScopeFilter(db, scope, column), nil
|
return ApplyScopeFilter(db, scope, column), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ApplyLocationAreaScope(c *fiber.Ctx, db *gorm.DB, locationColumn, areaColumn string) (*gorm.DB, error) {
|
||||||
|
scopeDB := db
|
||||||
|
if db != nil {
|
||||||
|
scopeDB = db.Session(&gorm.Session{NewDB: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
if locationColumn != "" {
|
||||||
|
locationScope, err := ResolveLocationScope(c, scopeDB)
|
||||||
|
if err != nil {
|
||||||
|
return db, err
|
||||||
|
}
|
||||||
|
db = ApplyScopeFilter(db, locationScope, locationColumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if areaColumn != "" {
|
||||||
|
areaScope, err := ResolveAreaScope(c, scopeDB)
|
||||||
|
if err != nil {
|
||||||
|
return db, err
|
||||||
|
}
|
||||||
|
db = ApplyScopeFilter(db, areaScope, areaColumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
func EnsureWarehouseAccess(c *fiber.Ctx, db *gorm.DB, warehouseID uint) error {
|
func EnsureWarehouseAccess(c *fiber.Ctx, db *gorm.DB, warehouseID uint) error {
|
||||||
if warehouseID == 0 {
|
if warehouseID == 0 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid warehouse id")
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid warehouse id")
|
||||||
@@ -486,7 +523,7 @@ func EnsureUniformityAccess(c *fiber.Ctx, db *gorm.DB, uniformityID uint) error
|
|||||||
|
|
||||||
var count int64
|
var count int64
|
||||||
q := db.WithContext(c.Context()).
|
q := db.WithContext(c.Context()).
|
||||||
Table("project_flock_kandang_uniformities u").
|
Table("project_flock_kandang_uniformity u").
|
||||||
Joins("JOIN project_flock_kandangs pfk ON pfk.id = u.project_flock_kandang_id").
|
Joins("JOIN project_flock_kandangs pfk ON pfk.id = u.project_flock_kandang_id").
|
||||||
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
||||||
Where("u.id = ?", uniformityID)
|
Where("u.id = ?", uniformityID)
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.Cl
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scope, err := m.ResolveLocationScope(c, s.Repository.DB())
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
statusFilter := ""
|
statusFilter := ""
|
||||||
@@ -113,6 +117,12 @@ func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.Cl
|
|||||||
|
|
||||||
closings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
closings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
db = s.withClosingRelations(db)
|
db = s.withClosingRelations(db)
|
||||||
|
if scope.Restrict {
|
||||||
|
if len(scope.IDs) == 0 {
|
||||||
|
return db.Where("1 = 0")
|
||||||
|
}
|
||||||
|
db = m.ApplyScopeFilter(db, scope, "project_flocks.location_id")
|
||||||
|
}
|
||||||
if params.LocationID != nil {
|
if params.LocationID != nil {
|
||||||
db = db.Where("location_id = ?", *params.LocationID)
|
db = db.Where("location_id = ?", *params.LocationID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,87 @@ func (s dailyChecklistService) withRelations(db *gorm.DB) *gorm.DB {
|
|||||||
return db.Preload("Kandang")
|
return db.Preload("Kandang")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s dailyChecklistService) ensureChecklistAccess(c *fiber.Ctx, checklistID uint) error {
|
||||||
|
if checklistID == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid checklist id")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := s.Repository.DB().WithContext(c.Context()).
|
||||||
|
Table("daily_checklists dc").
|
||||||
|
Joins("JOIN kandangs k ON k.id = dc.kandang_id").
|
||||||
|
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||||
|
Joins("JOIN areas a ON a.id = loc.area_id").
|
||||||
|
Where("dc.id = ?", checklistID)
|
||||||
|
|
||||||
|
scopedDB, err := m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
if err := scopedDB.Count(&count).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s dailyChecklistService) ensureKandangAccess(c *fiber.Ctx, kandangID uint) error {
|
||||||
|
if kandangID == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid kandang id")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := s.Repository.DB().WithContext(c.Context()).
|
||||||
|
Table("kandangs k").
|
||||||
|
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||||
|
Joins("JOIN areas a ON a.id = loc.area_id").
|
||||||
|
Where("k.id = ?", kandangID)
|
||||||
|
|
||||||
|
scopedDB, err := m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
if err := scopedDB.Count(&count).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Kandang not found")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s dailyChecklistService) ensureTaskAccess(c *fiber.Ctx, taskID uint) error {
|
||||||
|
if taskID == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid task id")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := s.Repository.DB().WithContext(c.Context()).
|
||||||
|
Table("daily_checklist_activity_tasks t").
|
||||||
|
Joins("JOIN daily_checklists dc ON dc.id = t.checklist_id").
|
||||||
|
Joins("JOIN kandangs k ON k.id = dc.kandang_id").
|
||||||
|
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||||
|
Joins("JOIN areas a ON a.id = loc.area_id").
|
||||||
|
Where("t.id = ?", taskID)
|
||||||
|
|
||||||
|
scopedDB, err := m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
if err := scopedDB.Count(&count).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Task not found")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([]DailyChecklistListItem, int64, error) {
|
func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([]DailyChecklistListItem, int64, error) {
|
||||||
if err := s.Validate.Struct(params); err != nil {
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
@@ -143,7 +224,15 @@ func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([
|
|||||||
|
|
||||||
db := s.Repository.DB().WithContext(c.Context()).
|
db := s.Repository.DB().WithContext(c.Context()).
|
||||||
Table("daily_checklists dc").
|
Table("daily_checklists dc").
|
||||||
Joins("JOIN kandangs k ON k.id = dc.kandang_id")
|
Joins("JOIN kandangs k ON k.id = dc.kandang_id").
|
||||||
|
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||||
|
Joins("JOIN areas a ON a.id = loc.area_id")
|
||||||
|
|
||||||
|
var scopeErr error
|
||||||
|
db, scopeErr = m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
|
||||||
|
if scopeErr != nil {
|
||||||
|
return nil, 0, scopeErr
|
||||||
|
}
|
||||||
|
|
||||||
if params.DateFrom != "" {
|
if params.DateFrom != "" {
|
||||||
dateFrom, err := time.Parse("2006-01-02", params.DateFrom)
|
dateFrom, err := time.Parse("2006-01-02", params.DateFrom)
|
||||||
@@ -294,6 +383,9 @@ func (s dailyChecklistService) GetAll(c *fiber.Ctx, params *validation.Query) ([
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s dailyChecklistService) GetOne(c *fiber.Ctx, id uint) (*entity.DailyChecklist, error) {
|
func (s dailyChecklistService) GetOne(c *fiber.Ctx, id uint) (*entity.DailyChecklist, error) {
|
||||||
|
if err := s.ensureChecklistAccess(c, id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
dailyChecklist, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
dailyChecklist, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
|
||||||
@@ -399,6 +491,9 @@ func (s *dailyChecklistService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := s.ensureKandangAccess(c, req.KandangId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
date, err := time.Parse("2006-01-02", req.Date)
|
date, err := time.Parse("2006-01-02", req.Date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -431,6 +526,9 @@ func (s dailyChecklistService) UpdateOne(c *fiber.Ctx, req *validation.Update, i
|
|||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := s.ensureChecklistAccess(c, id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
deletedIDs := make([]uint, 0)
|
deletedIDs := make([]uint, 0)
|
||||||
if req.DeletedDocumentIDs != nil {
|
if req.DeletedDocumentIDs != nil {
|
||||||
@@ -502,6 +600,9 @@ func (s dailyChecklistService) UpdateOne(c *fiber.Ctx, req *validation.Update, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s dailyChecklistService) DeleteOne(c *fiber.Ctx, id uint) error {
|
func (s dailyChecklistService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||||
|
if err := s.ensureChecklistAccess(c, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
|
return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
|
||||||
@@ -516,6 +617,9 @@ func (s dailyChecklistService) AssignPhases(c *fiber.Ctx, id uint, req *validati
|
|||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.ensureChecklistAccess(c, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := s.Repository.GetByID(c.Context(), id, nil); err != nil {
|
if _, err := s.Repository.GetByID(c.Context(), id, nil); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -597,6 +701,9 @@ func (s dailyChecklistService) AssignPhases(c *fiber.Ctx, id uint, req *validati
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s dailyChecklistService) RemoveAssignment(c *fiber.Ctx, id uint, employeeID uint) error {
|
func (s dailyChecklistService) RemoveAssignment(c *fiber.Ctx, id uint, employeeID uint) error {
|
||||||
|
if err := s.ensureChecklistAccess(c, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if _, err := s.Repository.GetByID(c.Context(), id, nil); err != nil {
|
if _, err := s.Repository.GetByID(c.Context(), id, nil); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
|
return fiber.NewError(fiber.StatusNotFound, "DailyChecklist not found")
|
||||||
@@ -634,6 +741,9 @@ func (s dailyChecklistService) GetTasks(c *fiber.Ctx, checklistID uint) ([]entit
|
|||||||
if checklistID == 0 {
|
if checklistID == 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "checklist_id is required")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "checklist_id is required")
|
||||||
}
|
}
|
||||||
|
if err := s.ensureChecklistAccess(c, checklistID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := s.Repository.GetByID(c.Context(), checklistID, nil); err != nil {
|
if _, err := s.Repository.GetByID(c.Context(), checklistID, nil); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -658,6 +768,9 @@ func (s dailyChecklistService) GetChecklistPhaseIDs(c *fiber.Ctx, checklistID ui
|
|||||||
if checklistID == 0 {
|
if checklistID == 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "checklist_id is required")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "checklist_id is required")
|
||||||
}
|
}
|
||||||
|
if err := s.ensureChecklistAccess(c, checklistID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := s.Repository.GetByID(c.Context(), checklistID, nil); err != nil {
|
if _, err := s.Repository.GetByID(c.Context(), checklistID, nil); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -687,6 +800,9 @@ func (s dailyChecklistService) UpdateAssignment(c *fiber.Ctx, req *validation.Up
|
|||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.ensureTaskAccess(c, req.TaskID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
task := new(entity.DailyChecklistActivityTask)
|
task := new(entity.DailyChecklistActivityTask)
|
||||||
if err := s.Repository.DB().WithContext(c.Context()).First(task, req.TaskID).Error; err != nil {
|
if err := s.Repository.DB().WithContext(c.Context()).First(task, req.TaskID).Error; err != nil {
|
||||||
@@ -808,6 +924,9 @@ func (s dailyChecklistService) AssignTasks(c *fiber.Ctx, id uint, req *validatio
|
|||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.ensureChecklistAccess(c, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
employeeIDs, err := parseIDs(req.EmployeeIDs)
|
employeeIDs, err := parseIDs(req.EmployeeIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -900,8 +1019,16 @@ func (s dailyChecklistService) GetSummary(c *fiber.Ctx, params *validation.Summa
|
|||||||
Joins("JOIN daily_checklists d ON d.id = t.checklist_id").
|
Joins("JOIN daily_checklists d ON d.id = t.checklist_id").
|
||||||
Joins("JOIN kandangs k ON k.id = d.kandang_id").
|
Joins("JOIN kandangs k ON k.id = d.kandang_id").
|
||||||
Joins("JOIN employees e ON e.id = a.employee_id").
|
Joins("JOIN employees e ON e.id = a.employee_id").
|
||||||
|
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||||
|
Joins("JOIN areas ar ON ar.id = loc.area_id").
|
||||||
Where("d.date BETWEEN ? AND ? AND d.status = ?", dateFrom, dateTo, "APPROVED")
|
Where("d.date BETWEEN ? AND ? AND d.status = ?", dateFrom, dateTo, "APPROVED")
|
||||||
|
|
||||||
|
var scopeErr error
|
||||||
|
db, scopeErr = m.ApplyLocationAreaScope(c, db, "loc.id", "ar.id")
|
||||||
|
if scopeErr != nil {
|
||||||
|
return nil, scopeErr
|
||||||
|
}
|
||||||
|
|
||||||
if params.Category != "" {
|
if params.Category != "" {
|
||||||
db = db.Where("d.category = ?", params.Category)
|
db = db.Where("d.category = ?", params.Category)
|
||||||
}
|
}
|
||||||
@@ -946,7 +1073,11 @@ func (s dailyChecklistService) GetReport(c *fiber.Ctx, params *validation.Report
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
scope, err := m.ResolveLocationScope(c, s.Repository.DB())
|
locationScope, err := m.ResolveLocationScope(c, s.Repository.DB())
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
areaScope, err := m.ResolveAreaScope(c, s.Repository.DB())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -967,7 +1098,8 @@ func (s dailyChecklistService) GetReport(c *fiber.Ctx, params *validation.Report
|
|||||||
Where("EXTRACT(YEAR FROM dc.date) = ?", params.Year).
|
Where("EXTRACT(YEAR FROM dc.date) = ?", params.Year).
|
||||||
Where("dc.status = ?", "APPROVED")
|
Where("dc.status = ?", "APPROVED")
|
||||||
|
|
||||||
db = m.ApplyScopeFilter(db, scope, "loc.id")
|
db = m.ApplyScopeFilter(db, locationScope, "loc.id")
|
||||||
|
db = m.ApplyScopeFilter(db, areaScope, "a.id")
|
||||||
|
|
||||||
if params.AreaID != nil {
|
if params.AreaID != nil {
|
||||||
db = db.Where("a.id = ?", *params.AreaID)
|
db = db.Where("a.id = ?", *params.AreaID)
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ func (s *adjustmentService) withRelations(db *gorm.DB) *gorm.DB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.AdjustmentStock, error) {
|
func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.AdjustmentStock, error) {
|
||||||
|
if err := m.EnsureStockLogAccess(c, s.StockLogsRepository.DB(), id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
adjustmentStock, err := s.AdjustmentStockRepository.GetByStockLogID(c.Context(), id)
|
adjustmentStock, err := s.AdjustmentStockRepository.GetByStockLogID(c.Context(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
@@ -95,6 +99,9 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := m.EnsureWarehouseAccess(c, s.WarehouseRepo.DB(), uint(req.WarehouseID)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := common.EnsureRelations(c.Context(),
|
if err := common.EnsureRelations(c.Context(),
|
||||||
common.RelationCheck{Name: "Product", ID: &req.ProductID, Exists: s.ProductRepo.IdExists},
|
common.RelationCheck{Name: "Product", ID: &req.ProductID, Exists: s.ProductRepo.IdExists},
|
||||||
common.RelationCheck{Name: "Warehouse", ID: &req.WarehouseID, Exists: s.WarehouseRepo.IdExists},
|
common.RelationCheck{Name: "Warehouse", ID: &req.WarehouseID, Exists: s.WarehouseRepo.IdExists},
|
||||||
@@ -304,6 +311,19 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu
|
|||||||
Preload("ProductWarehouse.Product").
|
Preload("ProductWarehouse.Product").
|
||||||
Preload("ProductWarehouse.Warehouse")
|
Preload("ProductWarehouse.Warehouse")
|
||||||
|
|
||||||
|
scope, scopeErr := m.ResolveLocationScope(c, s.AdjustmentStockRepository.DB())
|
||||||
|
if scopeErr != nil {
|
||||||
|
return nil, 0, scopeErr
|
||||||
|
}
|
||||||
|
if scope.Restrict {
|
||||||
|
if len(scope.IDs) == 0 {
|
||||||
|
return []*entity.AdjustmentStock{}, 0, nil
|
||||||
|
}
|
||||||
|
q = q.Joins("JOIN product_warehouses pw_scope ON pw_scope.id = adjustment_stocks.product_warehouse_id").
|
||||||
|
Joins("JOIN warehouses w_scope ON w_scope.id = pw_scope.warehouse_id")
|
||||||
|
q = m.ApplyScopeFilter(q, scope, "w_scope.location_id")
|
||||||
|
}
|
||||||
|
|
||||||
if query.ProductID > 0 {
|
if query.ProductID > 0 {
|
||||||
q = q.Joins("JOIN stock_logs ON stock_logs.id = adjustment_stocks.stock_log_id").
|
q = q.Joins("JOIN stock_logs ON stock_logs.id = adjustment_stocks.stock_log_id").
|
||||||
Joins("JOIN product_warehouses ON product_warehouses.id = stock_logs.product_warehouse_id").
|
Joins("JOIN product_warehouses ON product_warehouses.id = stock_logs.product_warehouse_id").
|
||||||
|
|||||||
@@ -37,19 +37,49 @@ func NewProductStockService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s productStockService) withRelations(db *gorm.DB) *gorm.DB {
|
func (s productStockService) withRelations(db *gorm.DB, locationScope, areaScope m.ScopeFilter) *gorm.DB {
|
||||||
|
warehouseScope := func(db *gorm.DB) *gorm.DB {
|
||||||
|
if locationScope.Restrict {
|
||||||
|
db = db.Where("warehouses.location_id IN ?", locationScope.IDs)
|
||||||
|
}
|
||||||
|
if areaScope.Restrict {
|
||||||
|
db = db.Where("warehouses.area_id IN ?", areaScope.IDs)
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
productWarehouseScope := func(db *gorm.DB) *gorm.DB {
|
||||||
|
db = db.Joins("JOIN warehouses w ON w.id = product_warehouses.warehouse_id")
|
||||||
|
if locationScope.Restrict {
|
||||||
|
db = db.Where("w.location_id IN ?", locationScope.IDs)
|
||||||
|
}
|
||||||
|
if areaScope.Restrict {
|
||||||
|
db = db.Where("w.area_id IN ?", areaScope.IDs)
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
stockLogScope := func(db *gorm.DB) *gorm.DB {
|
||||||
|
db = db.
|
||||||
|
Joins("JOIN product_warehouses pw ON pw.id = stock_logs.product_warehouse_id").
|
||||||
|
Joins("JOIN warehouses w ON w.id = pw.warehouse_id")
|
||||||
|
if locationScope.Restrict {
|
||||||
|
db = db.Where("w.location_id IN ?", locationScope.IDs)
|
||||||
|
}
|
||||||
|
if areaScope.Restrict {
|
||||||
|
db = db.Where("w.area_id IN ?", areaScope.IDs)
|
||||||
|
}
|
||||||
|
return db.Order("stock_logs.created_at ASC")
|
||||||
|
}
|
||||||
|
|
||||||
return db.
|
return db.
|
||||||
Preload("CreatedUser").
|
Preload("CreatedUser").
|
||||||
Preload("Uom").
|
Preload("Uom").
|
||||||
Preload("ProductCategory").
|
Preload("ProductCategory").
|
||||||
Preload("Flags").
|
Preload("Flags").
|
||||||
Preload("ProductWarehouses").
|
Preload("ProductWarehouses", productWarehouseScope).
|
||||||
Preload("ProductWarehouses.Warehouse").
|
Preload("ProductWarehouses.Warehouse", warehouseScope).
|
||||||
Preload("ProductWarehouses.Warehouse.Location").
|
Preload("ProductWarehouses.Warehouse.Location").
|
||||||
Preload("ProductWarehouses.Warehouse.Location.Area").
|
Preload("ProductWarehouses.Warehouse.Location.Area").
|
||||||
Preload("ProductWarehouses.StockLogs", func(db *gorm.DB) *gorm.DB {
|
Preload("ProductWarehouses.StockLogs", stockLogScope).
|
||||||
return db.Order("created_at ASC")
|
|
||||||
}).
|
|
||||||
Preload("ProductWarehouses.StockLogs.CreatedUser").
|
Preload("ProductWarehouses.StockLogs.CreatedUser").
|
||||||
Preload("ProductSuppliers").
|
Preload("ProductSuppliers").
|
||||||
Preload("ProductSuppliers.Supplier", func(db *gorm.DB) *gorm.DB {
|
Preload("ProductSuppliers.Supplier", func(db *gorm.DB) *gorm.DB {
|
||||||
@@ -62,7 +92,7 @@ func (s productStockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
scope, err := m.ResolveLocationScope(c, s.ProductRepository.DB())
|
locationScope, areaScope, err := m.ResolveLocationAreaScopes(c, s.ProductRepository.DB())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -70,8 +100,8 @@ func (s productStockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
|||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
productStocks, total, err := s.ProductRepository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
productStocks, total, err := s.ProductRepository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
if scope.Restrict {
|
if locationScope.Restrict || areaScope.Restrict {
|
||||||
if len(scope.IDs) == 0 {
|
if (locationScope.Restrict && len(locationScope.IDs) == 0) || (areaScope.Restrict && len(areaScope.IDs) == 0) {
|
||||||
return db.Where("1 = 0")
|
return db.Where("1 = 0")
|
||||||
}
|
}
|
||||||
db = db.Where(`EXISTS (
|
db = db.Where(`EXISTS (
|
||||||
@@ -80,8 +110,12 @@ func (s productStockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
|||||||
JOIN warehouses w ON w.id = pw.warehouse_id
|
JOIN warehouses w ON w.id = pw.warehouse_id
|
||||||
WHERE pw.product_id = products.id
|
WHERE pw.product_id = products.id
|
||||||
AND pw.qty > 0
|
AND pw.qty > 0
|
||||||
AND w.location_id IN ?
|
AND (? OR w.location_id IN ?)
|
||||||
)`, scope.IDs)
|
AND (? OR w.area_id IN ?)
|
||||||
|
)`,
|
||||||
|
!locationScope.Restrict, locationScope.IDs,
|
||||||
|
!areaScope.Restrict, areaScope.IDs,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
db = db.Where(`EXISTS (
|
db = db.Where(`EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
@@ -91,7 +125,7 @@ func (s productStockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
|||||||
)`)
|
)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
db = s.withRelations(db)
|
db = s.withRelations(db, locationScope, areaScope)
|
||||||
if params.Search != "" {
|
if params.Search != "" {
|
||||||
db = db.Where("products.name ILIKE ?", "%"+params.Search+"%")
|
db = db.Where("products.name ILIKE ?", "%"+params.Search+"%")
|
||||||
}
|
}
|
||||||
@@ -106,13 +140,13 @@ func (s productStockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s productStockService) GetOne(c *fiber.Ctx, id uint) (*entity.Product, error) {
|
func (s productStockService) GetOne(c *fiber.Ctx, id uint) (*entity.Product, error) {
|
||||||
scope, err := m.ResolveLocationScope(c, s.ProductRepository.DB())
|
locationScope, areaScope, err := m.ResolveLocationAreaScopes(c, s.ProductRepository.DB())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if scope.Restrict {
|
if locationScope.Restrict || areaScope.Restrict {
|
||||||
if len(scope.IDs) == 0 {
|
if (locationScope.Restrict && len(locationScope.IDs) == 0) || (areaScope.Restrict && len(areaScope.IDs) == 0) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Product not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Product not found")
|
||||||
}
|
}
|
||||||
var count int64
|
var count int64
|
||||||
@@ -121,7 +155,8 @@ func (s productStockService) GetOne(c *fiber.Ctx, id uint) (*entity.Product, err
|
|||||||
Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
|
Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
|
||||||
Where("pw.product_id = ?", id).
|
Where("pw.product_id = ?", id).
|
||||||
Where("pw.qty > 0").
|
Where("pw.qty > 0").
|
||||||
Where("w.location_id IN ?", scope.IDs).
|
Where("(? OR w.location_id IN ?)", !locationScope.Restrict, locationScope.IDs).
|
||||||
|
Where("(? OR w.area_id IN ?)", !areaScope.Restrict, areaScope.IDs).
|
||||||
Count(&count).Error; err != nil {
|
Count(&count).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -130,7 +165,9 @@ func (s productStockService) GetOne(c *fiber.Ctx, id uint) (*entity.Product, err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
product, err := s.ProductRepository.GetByID(c.Context(), id, s.withRelations)
|
product, err := s.ProductRepository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
||||||
|
return s.withRelations(db, locationScope, areaScope)
|
||||||
|
})
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Product not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Product not found")
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -8,6 +8,7 @@ import (
|
|||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/services"
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/services"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
@@ -30,6 +31,7 @@ func (u *ProductWarehouseController) GetAll(c *fiber.Ctx) error {
|
|||||||
WarehouseId: uint(c.QueryInt("warehouse_id", 0)),
|
WarehouseId: uint(c.QueryInt("warehouse_id", 0)),
|
||||||
Flags: c.Query("flags", ""),
|
Flags: c.Query("flags", ""),
|
||||||
KandangId: uint(c.QueryInt("kandang_id", 0)),
|
KandangId: uint(c.QueryInt("kandang_id", 0)),
|
||||||
|
TransferContext: c.Query(utils.TransferContextKey, ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
if query.Page < 1 || query.Limit < 1 {
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
|
|||||||
+29
-7
@@ -7,11 +7,11 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/validations"
|
||||||
kandangrepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
kandangrepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,10 +54,18 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
scope, err := m.ResolveLocationScope(c, s.Repository.DB())
|
applyScope := true
|
||||||
|
if params.TransferContext == utils.TransferContextInventoryTransfer {
|
||||||
|
applyScope = !m.HasPermission(c, m.P_TransferCreateOne)
|
||||||
|
}
|
||||||
|
var scope m.ScopeFilter
|
||||||
|
var err error
|
||||||
|
if applyScope {
|
||||||
|
scope, err = m.ResolveLocationScope(c, s.Repository.DB())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if params.ProductId > 0 {
|
if params.ProductId > 0 {
|
||||||
isProductExist, err := s.Repository.IsProductExist(c.Context(), params.ProductId)
|
isProductExist, err := s.Repository.IsProductExist(c.Context(), params.ProductId)
|
||||||
@@ -96,12 +104,15 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
|
|||||||
productWarehouses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
productWarehouses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
db = s.withRelations(db)
|
db = s.withRelations(db)
|
||||||
|
|
||||||
|
db = db.Joins("JOIN warehouses w_scope ON product_warehouses.warehouse_id = w_scope.id").
|
||||||
|
Where("w_scope.deleted_at IS NULL")
|
||||||
|
if applyScope {
|
||||||
if scope.Restrict {
|
if scope.Restrict {
|
||||||
if len(scope.IDs) == 0 {
|
if len(scope.IDs) == 0 {
|
||||||
return db.Where("1 = 0")
|
return db.Where("1 = 0")
|
||||||
}
|
}
|
||||||
db = db.Joins("JOIN warehouses w_scope ON product_warehouses.warehouse_id = w_scope.id").
|
db = db.Where("w_scope.location_id IN ?", scope.IDs)
|
||||||
Where("w_scope.location_id IN ?", scope.IDs)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.ProductId != 0 {
|
if params.ProductId != 0 {
|
||||||
@@ -130,19 +141,30 @@ func (s productWarehouseService) GetAll(c *fiber.Ctx, params *validation.Query)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s productWarehouseService) GetOne(c *fiber.Ctx, id uint) (*entity.ProductWarehouse, error) {
|
func (s productWarehouseService) GetOne(c *fiber.Ctx, id uint) (*entity.ProductWarehouse, error) {
|
||||||
scope, err := m.ResolveLocationScope(c, s.Repository.DB())
|
applyScope := true
|
||||||
|
if c.Query(utils.TransferContextKey, "") == utils.TransferContextInventoryTransfer {
|
||||||
|
applyScope = !m.HasPermission(c, m.P_TransferCreateOne)
|
||||||
|
}
|
||||||
|
var scope m.ScopeFilter
|
||||||
|
var err error
|
||||||
|
if applyScope {
|
||||||
|
scope, err = m.ResolveLocationScope(c, s.Repository.DB())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
productWarehouse, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
productWarehouse, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
||||||
db = s.withRelations(db)
|
db = s.withRelations(db)
|
||||||
|
db = db.Joins("JOIN warehouses w_scope ON product_warehouses.warehouse_id = w_scope.id").
|
||||||
|
Where("w_scope.deleted_at IS NULL")
|
||||||
|
if applyScope {
|
||||||
if scope.Restrict {
|
if scope.Restrict {
|
||||||
if len(scope.IDs) == 0 {
|
if len(scope.IDs) == 0 {
|
||||||
return db.Where("1 = 0")
|
return db.Where("1 = 0")
|
||||||
}
|
}
|
||||||
db = db.Joins("JOIN warehouses w_scope ON product_warehouses.warehouse_id = w_scope.id").
|
db = db.Where("w_scope.location_id IN ?", scope.IDs)
|
||||||
Where("w_scope.location_id IN ?", scope.IDs)
|
}
|
||||||
}
|
}
|
||||||
return db
|
return db
|
||||||
})
|
})
|
||||||
|
|||||||
+1
@@ -19,4 +19,5 @@ type Query struct {
|
|||||||
WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"`
|
WarehouseId uint `query:"warehouse_id" validate:"omitempty,number,min=1"`
|
||||||
Flags string `query:"flags" validate:"omitempty"`
|
Flags string `query:"flags" validate:"omitempty"`
|
||||||
KandangId uint `query:"kandang_id" validate:"omitempty,number,min=1"`
|
KandangId uint `query:"kandang_id" validate:"omitempty,number,min=1"`
|
||||||
|
TransferContext string `query:"transfer_context" validate:"omitempty,oneof=inventory_transfer"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
|
|||||||
Joins("JOIN marketings ON marketings.id = marketing_products.marketing_id").
|
Joins("JOIN marketings ON marketings.id = marketing_products.marketing_id").
|
||||||
Where("marketing_delivery_products.delivery_date IS NOT NULL")
|
Where("marketing_delivery_products.delivery_date IS NOT NULL")
|
||||||
|
|
||||||
if filters.ProductId > 0 || filters.WarehouseId > 0 || filters.AreaId > 0 || filters.LocationId > 0 || filters.Search != "" || filters.MarketingType != "" {
|
if filters.ProductId > 0 || filters.WarehouseId > 0 || filters.AreaId > 0 || filters.LocationId > 0 || filters.AllowedAreaIDs != nil || filters.AllowedLocationIDs != nil || filters.Search != "" || filters.MarketingType != "" {
|
||||||
db = db.Joins("LEFT JOIN product_warehouses ON product_warehouses.id = marketing_products.product_warehouse_id")
|
db = db.Joins("LEFT JOIN product_warehouses ON product_warehouses.id = marketing_products.product_warehouse_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
|
|||||||
db = db.Where("product_warehouses.warehouse_id = ?", filters.WarehouseId)
|
db = db.Where("product_warehouses.warehouse_id = ?", filters.WarehouseId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if filters.AreaId > 0 || filters.LocationId > 0 {
|
if filters.AreaId > 0 || filters.LocationId > 0 || filters.AllowedAreaIDs != nil || filters.AllowedLocationIDs != nil {
|
||||||
db = db.Joins("LEFT JOIN project_flock_kandangs ON project_flock_kandangs.id = product_warehouses.project_flock_kandang_id").
|
db = db.Joins("LEFT JOIN project_flock_kandangs ON project_flock_kandangs.id = product_warehouses.project_flock_kandang_id").
|
||||||
Joins("LEFT JOIN project_flocks ON project_flocks.id = project_flock_kandangs.project_flock_id")
|
Joins("LEFT JOIN project_flocks ON project_flocks.id = project_flock_kandangs.project_flock_id")
|
||||||
|
|
||||||
@@ -201,6 +201,22 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C
|
|||||||
if filters.LocationId > 0 {
|
if filters.LocationId > 0 {
|
||||||
db = db.Where("project_flocks.location_id = ?", filters.LocationId)
|
db = db.Where("project_flocks.location_id = ?", filters.LocationId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if filters.AllowedAreaIDs != nil {
|
||||||
|
if len(filters.AllowedAreaIDs) == 0 {
|
||||||
|
db = db.Where("1 = 0")
|
||||||
|
} else {
|
||||||
|
db = db.Where("project_flocks.area_id IN ?", filters.AllowedAreaIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.AllowedLocationIDs != nil {
|
||||||
|
if len(filters.AllowedLocationIDs) == 0 {
|
||||||
|
db = db.Where("1 = 0")
|
||||||
|
} else {
|
||||||
|
db = db.Where("project_flocks.location_id IN ?", filters.AllowedLocationIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if filters.MarketingType != "" {
|
if filters.MarketingType != "" {
|
||||||
|
|||||||
@@ -119,12 +119,17 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO
|
|||||||
if len(scope.IDs) == 0 {
|
if len(scope.IDs) == 0 {
|
||||||
return db.Where("1 = 0")
|
return db.Where("1 = 0")
|
||||||
}
|
}
|
||||||
db = db.
|
db = db.Where(
|
||||||
Joins("JOIN marketing_products mp ON mp.marketing_id = marketings.id").
|
`EXISTS (
|
||||||
Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id").
|
SELECT 1
|
||||||
Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
|
FROM marketing_products mp
|
||||||
Where("w.location_id IN ?", scope.IDs).
|
JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id
|
||||||
Distinct("marketings.*")
|
JOIN warehouses w ON w.id = pw.warehouse_id
|
||||||
|
WHERE mp.marketing_id = marketings.id
|
||||||
|
AND w.location_id IN ?
|
||||||
|
)`,
|
||||||
|
scope.IDs,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.MarketingId != 0 {
|
if params.MarketingId != 0 {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
||||||
|
m "gitlab.com/mbugroup/lti-api.git/internal/middleware"
|
||||||
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/repositories"
|
repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/repositories"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/employees/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
@@ -43,6 +44,61 @@ func (s employeesService) withRelations(db *gorm.DB) *gorm.DB {
|
|||||||
Where("employees.deleted_at IS NULL")
|
Where("employees.deleted_at IS NULL")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s employeesService) ensureEmployeeAccess(c *fiber.Ctx, employeeID uint) error {
|
||||||
|
if employeeID == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid employee id")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := s.Repository.DB().WithContext(c.Context()).
|
||||||
|
Table("employees e").
|
||||||
|
Joins("JOIN employee_kandangs ek ON ek.employee_id = e.id").
|
||||||
|
Joins("JOIN kandangs k ON k.id = ek.kandang_id").
|
||||||
|
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||||
|
Joins("JOIN areas a ON a.id = loc.area_id").
|
||||||
|
Where("e.id = ?", employeeID).
|
||||||
|
Where("e.deleted_at IS NULL")
|
||||||
|
|
||||||
|
scopedDB, err := m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
if err := scopedDB.Count(&count).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Employees not found")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s employeesService) ensureKandangIDsAccess(c *fiber.Ctx, kandangIDs []uint) error {
|
||||||
|
if len(kandangIDs) == 0 {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, "kandang_ids must contain at least one valid id")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := s.Repository.DB().WithContext(c.Context()).
|
||||||
|
Table("kandangs k").
|
||||||
|
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||||
|
Joins("JOIN areas a ON a.id = loc.area_id").
|
||||||
|
Where("k.id IN ?", kandangIDs)
|
||||||
|
|
||||||
|
scopedDB, err := m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
if err := scopedDB.Count(&count).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != int64(len(kandangIDs)) {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, "Kandang not found")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s employeesService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Employees, int64, error) {
|
func (s employeesService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Employees, int64, error) {
|
||||||
if err := s.Validate.Struct(params); err != nil {
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
@@ -52,17 +108,29 @@ func (s employeesService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
|
|
||||||
employeess, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
employeess, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
db = s.withRelations(db)
|
db = s.withRelations(db)
|
||||||
|
db = db.Joins("JOIN employee_kandangs ek ON ek.employee_id = employees.id").
|
||||||
|
Joins("JOIN kandangs k ON k.id = ek.kandang_id").
|
||||||
|
Joins("JOIN locations loc ON loc.id = k.location_id").
|
||||||
|
Joins("JOIN areas a ON a.id = loc.area_id")
|
||||||
|
var scopeErr error
|
||||||
|
db, scopeErr = m.ApplyLocationAreaScope(c, db, "loc.id", "a.id")
|
||||||
|
if scopeErr != nil {
|
||||||
|
return db.Where("1 = 0")
|
||||||
|
}
|
||||||
if params.Search != "" {
|
if params.Search != "" {
|
||||||
db = db.Where("employees.name ILIKE ?", "%"+params.Search+"%")
|
db = db.Where("employees.name ILIKE ?", "%"+params.Search+"%")
|
||||||
}
|
}
|
||||||
if params.KandangId != nil {
|
if params.KandangId != nil {
|
||||||
db = db.Joins("JOIN employee_kandangs ek ON ek.employee_id = employees.id").
|
db = db.Where("ek.kandang_id = ?", *params.KandangId)
|
||||||
Where("ek.kandang_id = ?", *params.KandangId)
|
|
||||||
}
|
}
|
||||||
if params.IsActive != nil {
|
if params.IsActive != nil {
|
||||||
db = db.Where("employees.is_active = ?", *params.IsActive)
|
db = db.Where("employees.is_active = ?", *params.IsActive)
|
||||||
}
|
}
|
||||||
return db.Order("employees.created_at DESC").Order("employees.updated_at DESC")
|
return db.
|
||||||
|
Select("employees.id, employees.name, employees.is_active, employees.created_at, employees.updated_at").
|
||||||
|
Group("employees.id, employees.name, employees.is_active, employees.created_at, employees.updated_at").
|
||||||
|
Order("employees.created_at DESC").
|
||||||
|
Order("employees.updated_at DESC")
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -73,6 +141,9 @@ func (s employeesService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s employeesService) GetOne(c *fiber.Ctx, id uint) (*entity.Employees, error) {
|
func (s employeesService) GetOne(c *fiber.Ctx, id uint) (*entity.Employees, error) {
|
||||||
|
if err := s.ensureEmployeeAccess(c, id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
employees, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
employees, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, fiber.NewError(fiber.StatusNotFound, "Employees not found")
|
return nil, fiber.NewError(fiber.StatusNotFound, "Employees not found")
|
||||||
@@ -98,6 +169,9 @@ func (s *employeesService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent
|
|||||||
if len(kandangIDs) == 0 {
|
if len(kandangIDs) == 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids must contain at least one valid id")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids must contain at least one valid id")
|
||||||
}
|
}
|
||||||
|
if err := s.ensureKandangIDsAccess(c, kandangIDs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := s.Repository.First(c.Context(), func(db *gorm.DB) *gorm.DB {
|
if _, err := s.Repository.First(c.Context(), func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Where("LOWER(name) = ?", strings.ToLower(name))
|
return db.Where("LOWER(name) = ?", strings.ToLower(name))
|
||||||
@@ -147,6 +221,9 @@ func (s employeesService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := s.ensureEmployeeAccess(c, id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
updateBody := make(map[string]any)
|
updateBody := make(map[string]any)
|
||||||
var (
|
var (
|
||||||
@@ -181,6 +258,9 @@ func (s employeesService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
if len(ids) == 0 {
|
if len(ids) == 0 {
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids must contain at least one valid id")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "kandang_ids must contain at least one valid id")
|
||||||
}
|
}
|
||||||
|
if err := s.ensureKandangIDsAccess(c, ids); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
kandangIDs = ids
|
kandangIDs = ids
|
||||||
needKandangUpdate = true
|
needKandangUpdate = true
|
||||||
@@ -234,6 +314,9 @@ func (s employeesService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s employeesService) DeleteOne(c *fiber.Ctx, id uint) error {
|
func (s employeesService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||||
|
if err := s.ensureEmployeeAccess(c, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return fiber.NewError(fiber.StatusNotFound, "Employees not found")
|
return fiber.NewError(fiber.StatusNotFound, "Employees not found")
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
"gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto"
|
||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/services"
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/services"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
@@ -23,6 +25,11 @@ func NewWarehouseController(warehouseService service.WarehouseService) *Warehous
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *WarehouseController) GetAll(c *fiber.Ctx) error {
|
func (u *WarehouseController) GetAll(c *fiber.Ctx) error {
|
||||||
|
excludeIDs, err := parseCommaSeparatedUint(c.Query("exclude_id", ""))
|
||||||
|
if err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
query := &validation.Query{
|
query := &validation.Query{
|
||||||
Page: c.QueryInt("page", 1),
|
Page: c.QueryInt("page", 1),
|
||||||
Limit: c.QueryInt("limit", 10),
|
Limit: c.QueryInt("limit", 10),
|
||||||
@@ -30,6 +37,8 @@ func (u *WarehouseController) GetAll(c *fiber.Ctx) error {
|
|||||||
AreaId: c.QueryInt("area_id", 0),
|
AreaId: c.QueryInt("area_id", 0),
|
||||||
LocationId: c.QueryInt("location_id", 0),
|
LocationId: c.QueryInt("location_id", 0),
|
||||||
ActiveProjectFlockOnly: c.QueryBool("active_project_flock", false),
|
ActiveProjectFlockOnly: c.QueryBool("active_project_flock", false),
|
||||||
|
TransferContext: c.Query(utils.TransferContextKey, ""),
|
||||||
|
ExcludeIDs: excludeIDs,
|
||||||
}
|
}
|
||||||
|
|
||||||
if query.Page < 1 || query.Limit < 1 {
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
@@ -56,6 +65,28 @@ func (u *WarehouseController) GetAll(c *fiber.Ctx) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseCommaSeparatedUint(raw string) ([]uint, error) {
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
|
if raw == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(raw, ",")
|
||||||
|
out := make([]uint, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
if part == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value, err := strconv.ParseUint(part, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fiber.NewError(fiber.StatusBadRequest, "invalid exclude_id")
|
||||||
|
}
|
||||||
|
out = append(out, uint(value))
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *WarehouseController) GetOne(c *fiber.Ctx) error {
|
func (u *WarehouseController) GetOne(c *fiber.Ctx) error {
|
||||||
param := c.Params("id")
|
param := c.Params("id")
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,14 @@ func (s warehouseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
|
|
||||||
warehouses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
warehouses, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
db = s.withRelations(db)
|
db = s.withRelations(db)
|
||||||
db, scopeErr = m.ApplyAreaScope(c, db, "warehouses.area_id")
|
applyScope := true
|
||||||
|
if params.TransferContext == utils.TransferContextInventoryTransfer {
|
||||||
|
applyScope = !m.HasPermission(c, m.P_TransferCreateOne)
|
||||||
|
}
|
||||||
|
|
||||||
|
if applyScope {
|
||||||
|
db, scopeErr = m.ApplyLocationAreaScope(c, db, "warehouses.location_id", "warehouses.area_id")
|
||||||
|
}
|
||||||
if params.Search != "" {
|
if params.Search != "" {
|
||||||
db = db.Where("warehouses.name ILIKE ?", "%"+params.Search+"%")
|
db = db.Where("warehouses.name ILIKE ?", "%"+params.Search+"%")
|
||||||
}
|
}
|
||||||
@@ -81,6 +88,9 @@ func (s warehouseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti
|
|||||||
)
|
)
|
||||||
`, "Aktif")
|
`, "Aktif")
|
||||||
}
|
}
|
||||||
|
if len(params.ExcludeIDs) > 0 {
|
||||||
|
db = db.Where("warehouses.id NOT IN ?", params.ExcludeIDs)
|
||||||
|
}
|
||||||
return db.Order("created_at DESC").Order("updated_at DESC")
|
return db.Order("created_at DESC").Order("updated_at DESC")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -99,7 +109,7 @@ func (s warehouseService) GetOne(c *fiber.Ctx, id uint) (*entity.Warehouse, erro
|
|||||||
|
|
||||||
warehouse, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
warehouse, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
||||||
db = s.withRelations(db)
|
db = s.withRelations(db)
|
||||||
db, scopeErr = m.ApplyAreaScope(c, db, "warehouses.area_id")
|
db, scopeErr = m.ApplyLocationAreaScope(c, db, "warehouses.location_id", "warehouses.area_id")
|
||||||
return db
|
return db
|
||||||
})
|
})
|
||||||
if scopeErr != nil {
|
if scopeErr != nil {
|
||||||
|
|||||||
@@ -23,4 +23,6 @@ type Query struct {
|
|||||||
AreaId int `query:"area_id" validate:"omitempty,number,gt=0"`
|
AreaId int `query:"area_id" validate:"omitempty,number,gt=0"`
|
||||||
LocationId int `query:"location_id" validate:"omitempty,number,gt=0"`
|
LocationId int `query:"location_id" validate:"omitempty,number,gt=0"`
|
||||||
ActiveProjectFlockOnly bool `query:"active_project_flock"`
|
ActiveProjectFlockOnly bool `query:"active_project_flock"`
|
||||||
|
ExcludeIDs []uint `query:"-" validate:"omitempty,dive,gt=0"`
|
||||||
|
TransferContext string `query:"transfer_context" validate:"omitempty,oneof=inventory_transfer"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
service "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services"
|
||||||
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/validations"
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
"gitlab.com/mbugroup/lti-api.git/internal/response"
|
||||||
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
@@ -76,6 +77,7 @@ func (u *ProjectflockController) GetAll(c *fiber.Ctx) error {
|
|||||||
query.Category = category
|
query.Category = category
|
||||||
|
|
||||||
}
|
}
|
||||||
|
query.TransferContext = c.Query(utils.TransferContextKey, "")
|
||||||
|
|
||||||
if kandangRaw := c.Query("kandang_id", c.Query("kandang_ids", "")); kandangRaw != "" {
|
if kandangRaw := c.Query("kandang_id", c.Query("kandang_ids", "")); kandangRaw != "" {
|
||||||
ids, err := parseUintList(kandangRaw)
|
ids, err := parseUintList(kandangRaw)
|
||||||
|
|||||||
@@ -121,6 +121,12 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, err
|
return nil, 0, nil, err
|
||||||
}
|
}
|
||||||
|
if params.TransferContext == utils.TransferContextTransferToLaying {
|
||||||
|
if m.HasPermission(c, m.P_TransferToLaying_CreateOne) || m.HasPermission(c, m.P_TransferToLaying_UpdateOne) {
|
||||||
|
scope.Restrict = false
|
||||||
|
scope.IDs = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ type Query struct {
|
|||||||
Period int `query:"period" validate:"omitempty,number,gt=0"`
|
Period int `query:"period" validate:"omitempty,number,gt=0"`
|
||||||
Category string `query:"category" validate:"omitempty"`
|
Category string `query:"category" validate:"omitempty"`
|
||||||
KandangIds []uint `query:"kandang_id" validate:"omitempty,dive,gt=0"`
|
KandangIds []uint `query:"kandang_id" validate:"omitempty,dive,gt=0"`
|
||||||
|
TransferContext string `query:"transfer_context" validate:"omitempty,oneof=transfer_to_laying"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Approve struct {
|
type Approve struct {
|
||||||
|
|||||||
@@ -106,15 +106,31 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scope, err := m.ResolveLocationScope(c, s.Repository.DB())
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
|
|
||||||
transferLayings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
transferLayings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||||
|
needFlockJoin := scope.Restrict || params.Search != ""
|
||||||
|
if needFlockJoin {
|
||||||
|
db = db.Joins("LEFT JOIN project_flocks AS pf_from ON laying_transfers.from_project_flock_id = pf_from.id").
|
||||||
|
Joins("LEFT JOIN project_flocks AS pf_to ON laying_transfers.to_project_flock_id = pf_to.id")
|
||||||
|
}
|
||||||
|
|
||||||
|
if scope.Restrict {
|
||||||
|
if len(scope.IDs) == 0 {
|
||||||
|
return db.Where("1 = 0")
|
||||||
|
}
|
||||||
|
db = db.Where("(pf_from.location_id IN ? OR pf_to.location_id IN ?)", scope.IDs, scope.IDs)
|
||||||
|
}
|
||||||
|
|
||||||
// Apply search and filters
|
// Apply search and filters
|
||||||
if params.Search != "" {
|
if params.Search != "" {
|
||||||
searchPattern := "%" + params.Search + "%"
|
searchPattern := "%" + params.Search + "%"
|
||||||
db = db.Joins("LEFT JOIN project_flocks AS pf_from ON laying_transfers.from_project_flock_id = pf_from.id").
|
db = db.Where("laying_transfers.transfer_number ILIKE ? OR laying_transfers.notes ILIKE ? OR pf_from.flock_name ILIKE ? OR pf_to.flock_name ILIKE ?",
|
||||||
Joins("LEFT JOIN project_flocks AS pf_to ON laying_transfers.to_project_flock_id = pf_to.id").
|
|
||||||
Where("laying_transfers.transfer_number ILIKE ? OR laying_transfers.notes ILIKE ? OR pf_from.flock_name ILIKE ? OR pf_to.flock_name ILIKE ?",
|
|
||||||
searchPattern, searchPattern, searchPattern, searchPattern)
|
searchPattern, searchPattern, searchPattern, searchPattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,12 +197,11 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create)
|
|||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), req.SourceProjectFlockId); err != nil {
|
if !m.HasPermission(c, m.P_TransferToLaying_CreateOne) {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), req.TargetProjectFlockId); err != nil {
|
if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), req.TargetProjectFlockId); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
actorID, err := m.ActorIDFromContext(c)
|
actorID, err := m.ActorIDFromContext(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -415,15 +430,11 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
|||||||
if err := s.Validate.Struct(req); err != nil {
|
if err := s.Validate.Struct(req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := m.EnsureLayingTransferAccess(c, s.Repository.DB(), id); err != nil {
|
if !m.HasPermission(c, m.P_TransferToLaying_UpdateOne) {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), req.SourceProjectFlockId); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), req.TargetProjectFlockId); err != nil {
|
if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), req.TargetProjectFlockId); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
existingTransfer, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
existingTransfer, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Preload("Sources.ProductWarehouse").Preload("Targets")
|
return db.Preload("Sources.ProductWarehouse").Preload("Targets")
|
||||||
@@ -599,6 +610,9 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
func (s transferLayingService) DeleteOne(c *fiber.Ctx, id uint) error {
|
||||||
|
if err := m.EnsureLayingTransferAccess(c, s.Repository.DB(), id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
_, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB {
|
||||||
return db.Preload("Sources.ProductWarehouse").Preload("Targets")
|
return db.Preload("Sources.ProductWarehouse").Preload("Targets")
|
||||||
@@ -669,6 +683,12 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) (
|
|||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id")
|
return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, approvableID := range approvableIDs {
|
||||||
|
if err := m.EnsureLayingTransferAccess(c, s.Repository.DB(), approvableID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
step := utils.TransferToLayingStepPengajuan
|
step := utils.TransferToLayingStepPengajuan
|
||||||
if action == entity.ApprovalActionApproved {
|
if action == entity.ApprovalActionApproved {
|
||||||
step = utils.TransferToLayingStepDisetujui
|
step = utils.TransferToLayingStepDisetujui
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func (s uniformityService) GetAll(c *fiber.Ctx, params *validation.Query) ([]ent
|
|||||||
offset := (params.Page - 1) * params.Limit
|
offset := (params.Page - 1) * params.Limit
|
||||||
uniformitys, total, err := s.Repository.GetAllWithFilters(c.Context(), offset, params.Limit, params, func(db *gorm.DB) *gorm.DB {
|
uniformitys, total, err := s.Repository.GetAllWithFilters(c.Context(), offset, params.Limit, params, func(db *gorm.DB) *gorm.DB {
|
||||||
db = db.
|
db = db.
|
||||||
Joins("JOIN project_flock_kandangs pfk ON pfk.id = project_flock_kandang_uniformities.project_flock_kandang_id").
|
Joins("JOIN project_flock_kandangs pfk ON pfk.id = project_flock_kandang_uniformity.project_flock_kandang_id").
|
||||||
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id")
|
Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id")
|
||||||
db, scopeErr = m.ApplyLocationScope(c, db, "pf.location_id")
|
db, scopeErr = m.ApplyLocationScope(c, db, "pf.location_id")
|
||||||
return db
|
return db
|
||||||
|
|||||||
@@ -112,6 +112,10 @@ func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
areaScope, err := m.ResolveAreaScope(ctx, c.RepportService.DB())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if locationScope.Restrict {
|
if locationScope.Restrict {
|
||||||
allowed := toInt64Slice(locationScope.IDs)
|
allowed := toInt64Slice(locationScope.IDs)
|
||||||
if len(allowed) == 0 {
|
if len(allowed) == 0 {
|
||||||
@@ -119,6 +123,13 @@ func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
query.AllowedLocationIDs = allowed
|
query.AllowedLocationIDs = allowed
|
||||||
}
|
}
|
||||||
|
if areaScope.Restrict {
|
||||||
|
allowed := toInt64Slice(areaScope.IDs)
|
||||||
|
if len(allowed) == 0 {
|
||||||
|
allowed = []int64{-1}
|
||||||
|
}
|
||||||
|
query.AllowedAreaIDs = allowed
|
||||||
|
}
|
||||||
|
|
||||||
if query.Page < 1 || query.Limit < 1 {
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
@@ -220,6 +231,21 @@ func (c *RepportController) GetDebtSupplier(ctx *fiber.Ctx) error {
|
|||||||
SortOrder: ctx.Query("sort_order", ""),
|
SortOrder: ctx.Query("sort_order", ""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
locationScope, err := m.ResolveLocationScope(ctx, c.RepportService.DB())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
areaScope, err := m.ResolveAreaScope(ctx, c.RepportService.DB())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if locationScope.Restrict {
|
||||||
|
query.AllowedLocationIDs = toInt64Slice(locationScope.IDs)
|
||||||
|
}
|
||||||
|
if areaScope.Restrict {
|
||||||
|
query.AllowedAreaIDs = toInt64Slice(areaScope.IDs)
|
||||||
|
}
|
||||||
|
|
||||||
if query.Page < 1 || query.Limit < 1 {
|
if query.Page < 1 || query.Limit < 1 {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type CustomerPaymentTransaction struct {
|
|||||||
type CustomerPaymentRepository interface {
|
type CustomerPaymentRepository interface {
|
||||||
GetCustomerPaymentTransactions(ctx context.Context, customerID *uint) ([]CustomerPaymentTransaction, error)
|
GetCustomerPaymentTransactions(ctx context.Context, customerID *uint) ([]CustomerPaymentTransaction, error)
|
||||||
GetInitialBalanceByCustomer(ctx context.Context, customerID uint) (float64, error)
|
GetInitialBalanceByCustomer(ctx context.Context, customerID uint) (float64, error)
|
||||||
GetCustomerIDsWithTransactions(ctx context.Context, limit, offset int) ([]uint, int64, error)
|
GetCustomerIDsWithTransactions(ctx context.Context, limit, offset int, allowedCustomerIDs []uint) ([]uint, int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type customerPaymentRepositoryImpl struct {
|
type customerPaymentRepositoryImpl struct {
|
||||||
@@ -146,7 +146,7 @@ func (r *customerPaymentRepositoryImpl) GetInitialBalanceByCustomer(ctx context.
|
|||||||
return result.Nominal, nil
|
return result.Nominal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *customerPaymentRepositoryImpl) GetCustomerIDsWithTransactions(ctx context.Context, limit, offset int) ([]uint, int64, error) {
|
func (r *customerPaymentRepositoryImpl) GetCustomerIDsWithTransactions(ctx context.Context, limit, offset int, allowedCustomerIDs []uint) ([]uint, int64, error) {
|
||||||
subQuery := r.db.WithContext(ctx).
|
subQuery := r.db.WithContext(ctx).
|
||||||
Table("(" +
|
Table("(" +
|
||||||
"SELECT DISTINCT c.id as customer_id FROM marketing_delivery_products mdp " +
|
"SELECT DISTINCT c.id as customer_id FROM marketing_delivery_products mdp " +
|
||||||
@@ -161,26 +161,36 @@ func (r *customerPaymentRepositoryImpl) GetCustomerIDsWithTransactions(ctx conte
|
|||||||
"AND p.transaction_type = 'PENJUALAN' AND p.deleted_at IS NULL AND c.deleted_at IS NULL" +
|
"AND p.transaction_type = 'PENJUALAN' AND p.deleted_at IS NULL AND c.deleted_at IS NULL" +
|
||||||
") as customer_ids")
|
") as customer_ids")
|
||||||
|
|
||||||
|
if len(allowedCustomerIDs) > 0 {
|
||||||
|
subQuery = subQuery.Where("customer_id IN ?", allowedCustomerIDs)
|
||||||
|
}
|
||||||
|
|
||||||
var total int64
|
var total int64
|
||||||
if err := subQuery.Count(&total).Error; err != nil {
|
if err := subQuery.Count(&total).Error; err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var customerIDs []uint
|
var customerIDs []uint
|
||||||
err := r.db.WithContext(ctx).
|
query := r.db.WithContext(ctx).
|
||||||
Table("("+
|
Table("(" +
|
||||||
"SELECT DISTINCT c.id as customer_id FROM marketing_delivery_products mdp "+
|
"SELECT DISTINCT c.id as customer_id FROM marketing_delivery_products mdp " +
|
||||||
"INNER JOIN marketing_products mp ON mp.id = mdp.marketing_product_id "+
|
"INNER JOIN marketing_products mp ON mp.id = mdp.marketing_product_id " +
|
||||||
"INNER JOIN marketings m ON m.id = mp.marketing_id "+
|
"INNER JOIN marketings m ON m.id = mp.marketing_id " +
|
||||||
"INNER JOIN customers c ON c.id = m.customer_id "+
|
"INNER JOIN customers c ON c.id = m.customer_id " +
|
||||||
"WHERE mdp.delivery_date IS NOT NULL AND m.deleted_at IS NULL AND c.deleted_at IS NULL "+
|
"WHERE mdp.delivery_date IS NOT NULL AND m.deleted_at IS NULL AND c.deleted_at IS NULL " +
|
||||||
"UNION "+
|
"UNION " +
|
||||||
"SELECT DISTINCT c.id as customer_id FROM payments p "+
|
"SELECT DISTINCT c.id as customer_id FROM payments p " +
|
||||||
"INNER JOIN customers c ON c.id = p.party_id "+
|
"INNER JOIN customers c ON c.id = p.party_id " +
|
||||||
"WHERE p.party_type = 'CUSTOMER' AND p.direction = 'IN' "+
|
"WHERE p.party_type = 'CUSTOMER' AND p.direction = 'IN' " +
|
||||||
"AND p.transaction_type = 'PENJUALAN' AND p.deleted_at IS NULL AND c.deleted_at IS NULL"+
|
"AND p.transaction_type = 'PENJUALAN' AND p.deleted_at IS NULL AND c.deleted_at IS NULL" +
|
||||||
") as customer_ids").
|
") as customer_ids").
|
||||||
Select("customer_id").
|
Select("customer_id")
|
||||||
|
|
||||||
|
if len(allowedCustomerIDs) > 0 {
|
||||||
|
query = query.Where("customer_id IN ?", allowedCustomerIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.
|
||||||
Order("customer_id ASC").
|
Order("customer_id ASC").
|
||||||
Limit(limit).
|
Limit(limit).
|
||||||
Offset(offset).
|
Offset(offset).
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ func (r *debtSupplierRepositoryImpl) baseSupplierQuery(ctx context.Context, filt
|
|||||||
Model(&entity.Supplier{}).
|
Model(&entity.Supplier{}).
|
||||||
Joins("JOIN purchases ON purchases.supplier_id = suppliers.id").
|
Joins("JOIN purchases ON purchases.supplier_id = suppliers.id").
|
||||||
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id").
|
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id").
|
||||||
|
Joins("JOIN warehouses w ON w.id = purchase_items.warehouse_id").
|
||||||
Joins("JOIN (?) AS la ON la.approvable_id = purchases.id", r.latestPurchaseApproval(ctx)).
|
Joins("JOIN (?) AS la ON la.approvable_id = purchases.id", r.latestPurchaseApproval(ctx)).
|
||||||
Where("la.step_number >= ?", uint16(utils.PurchaseStepReceiving)).
|
Where("la.step_number >= ?", uint16(utils.PurchaseStepReceiving)).
|
||||||
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
||||||
@@ -79,6 +80,22 @@ func (r *debtSupplierRepositoryImpl) baseSupplierQuery(ctx context.Context, filt
|
|||||||
db = db.Where("suppliers.id IN ?", filters.SupplierIDs)
|
db = db.Where("suppliers.id IN ?", filters.SupplierIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if filters.AllowedAreaIDs != nil {
|
||||||
|
if len(filters.AllowedAreaIDs) == 0 {
|
||||||
|
db = db.Where("1 = 0")
|
||||||
|
} else {
|
||||||
|
db = db.Where("w.area_id IN ?", filters.AllowedAreaIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.AllowedLocationIDs != nil {
|
||||||
|
if len(filters.AllowedLocationIDs) == 0 {
|
||||||
|
db = db.Where("1 = 0")
|
||||||
|
} else {
|
||||||
|
db = db.Where("w.location_id IN ?", filters.AllowedLocationIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if filters.StartDate != "" {
|
if filters.StartDate != "" {
|
||||||
if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil {
|
if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil {
|
||||||
db = db.Where(fmt.Sprintf("DATE(%s) >= ?", dateColumn), dateFrom)
|
db = db.Where(fmt.Sprintf("DATE(%s) >= ?", dateColumn), dateFrom)
|
||||||
@@ -226,12 +243,29 @@ func (r *debtSupplierRepositoryImpl) getPurchaseIDs(ctx context.Context, supplie
|
|||||||
Table("purchases").
|
Table("purchases").
|
||||||
Select("DISTINCT purchases.id").
|
Select("DISTINCT purchases.id").
|
||||||
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id").
|
Joins("JOIN purchase_items ON purchase_items.purchase_id = purchases.id").
|
||||||
|
Joins("JOIN warehouses w ON w.id = purchase_items.warehouse_id").
|
||||||
Joins("JOIN (?) AS la ON la.approvable_id = purchases.id", r.latestPurchaseApproval(ctx)).
|
Joins("JOIN (?) AS la ON la.approvable_id = purchases.id", r.latestPurchaseApproval(ctx)).
|
||||||
Where("purchases.supplier_id IN ?", supplierIDs).
|
Where("purchases.supplier_id IN ?", supplierIDs).
|
||||||
Where("la.step_number >= ?", uint16(utils.PurchaseStepReceiving)).
|
Where("la.step_number >= ?", uint16(utils.PurchaseStepReceiving)).
|
||||||
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
Where("(la.action IS NULL OR la.action != ?)", string(entity.ApprovalActionRejected)).
|
||||||
Where("purchase_items.received_date IS NOT NULL")
|
Where("purchase_items.received_date IS NOT NULL")
|
||||||
|
|
||||||
|
if filters.AllowedAreaIDs != nil {
|
||||||
|
if len(filters.AllowedAreaIDs) == 0 {
|
||||||
|
db = db.Where("1 = 0")
|
||||||
|
} else {
|
||||||
|
db = db.Where("w.area_id IN ?", filters.AllowedAreaIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if filters.AllowedLocationIDs != nil {
|
||||||
|
if len(filters.AllowedLocationIDs) == 0 {
|
||||||
|
db = db.Where("1 = 0")
|
||||||
|
} else {
|
||||||
|
db = db.Where("w.location_id IN ?", filters.AllowedLocationIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if filters.StartDate != "" {
|
if filters.StartDate != "" {
|
||||||
if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil {
|
if dateFrom, err := utils.ParseDateString(filters.StartDate); err == nil {
|
||||||
db = db.Where(fmt.Sprintf("DATE(%s) >= ?", dateColumn), dateFrom)
|
db = db.Where(fmt.Sprintf("DATE(%s) >= ?", dateColumn), dateFrom)
|
||||||
|
|||||||
@@ -46,12 +46,13 @@ type RepportService interface {
|
|||||||
GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangResponseData, *dto.HppPerKandangMetaDTO, error)
|
GetHppPerKandang(ctx *fiber.Ctx) (*dto.HppPerKandangResponseData, *dto.HppPerKandangMetaDTO, error)
|
||||||
GetProductionResult(ctx *fiber.Ctx, params *validation.ProductionResultQuery) ([]dto.ProductionResultDTO, int64, error)
|
GetProductionResult(ctx *fiber.Ctx, params *validation.ProductionResultQuery) ([]dto.ProductionResultDTO, int64, error)
|
||||||
GetCustomerPayment(ctx *fiber.Ctx, params *validation.CustomerPaymentQuery) ([]dto.CustomerPaymentReportItem, int64, error)
|
GetCustomerPayment(ctx *fiber.Ctx, params *validation.CustomerPaymentQuery) ([]dto.CustomerPaymentReportItem, int64, error)
|
||||||
|
DB() *gorm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
type repportService struct {
|
type repportService struct {
|
||||||
Log *logrus.Logger
|
Log *logrus.Logger
|
||||||
Validate *validator.Validate
|
Validate *validator.Validate
|
||||||
DB *gorm.DB
|
db *gorm.DB
|
||||||
ExpenseRealizationRepo expenseRepo.ExpenseRealizationRepository
|
ExpenseRealizationRepo expenseRepo.ExpenseRealizationRepository
|
||||||
MarketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository
|
MarketingDeliveryRepo marketingRepo.MarketingDeliveryProductRepository
|
||||||
PurchaseRepo purchaseRepo.PurchaseRepository
|
PurchaseRepo purchaseRepo.PurchaseRepository
|
||||||
@@ -100,7 +101,7 @@ func NewRepportService(
|
|||||||
return &repportService{
|
return &repportService{
|
||||||
Log: utils.Log,
|
Log: utils.Log,
|
||||||
Validate: validate,
|
Validate: validate,
|
||||||
DB: db,
|
db: db,
|
||||||
ExpenseRealizationRepo: expenseRealizationRepo,
|
ExpenseRealizationRepo: expenseRealizationRepo,
|
||||||
MarketingDeliveryRepo: marketingDeliveryRepo,
|
MarketingDeliveryRepo: marketingDeliveryRepo,
|
||||||
PurchaseRepo: purchaseRepo,
|
PurchaseRepo: purchaseRepo,
|
||||||
@@ -119,6 +120,9 @@ func NewRepportService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *repportService) DB() *gorm.DB {
|
||||||
|
return s.db
|
||||||
|
}
|
||||||
|
|
||||||
func (s *repportService) GetExpense(c *fiber.Ctx, params *validation.ExpenseQuery) ([]dto.RepportExpenseListDTO, int64, error) {
|
func (s *repportService) GetExpense(c *fiber.Ctx, params *validation.ExpenseQuery) ([]dto.RepportExpenseListDTO, int64, error) {
|
||||||
if err := s.Validate.Struct(params); err != nil {
|
if err := s.Validate.Struct(params); err != nil {
|
||||||
@@ -407,11 +411,38 @@ func (s *repportService) GetCustomerPayment(ctx *fiber.Ctx, params *validation.C
|
|||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
locationScope, err := m.ResolveLocationScope(ctx, s.DB())
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
areaScope, err := m.ResolveAreaScope(ctx, s.DB())
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
restrictScope := locationScope.Restrict || areaScope.Restrict
|
||||||
|
var allowedCustomerIDs []uint
|
||||||
|
if restrictScope {
|
||||||
|
if (locationScope.Restrict && len(locationScope.IDs) == 0) || (areaScope.Restrict && len(areaScope.IDs) == 0) {
|
||||||
|
return []dto.CustomerPaymentReportItem{}, 0, nil
|
||||||
|
}
|
||||||
|
allowedCustomerIDs, err = s.getCustomerIDsByScope(ctx.Context(), locationScope.IDs, areaScope.IDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if len(allowedCustomerIDs) == 0 {
|
||||||
|
return []dto.CustomerPaymentReportItem{}, 0, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var customerIDs []uint
|
var customerIDs []uint
|
||||||
var totalCustomers int64
|
var totalCustomers int64
|
||||||
|
|
||||||
if len(params.CustomerIDs) > 0 {
|
if len(params.CustomerIDs) > 0 {
|
||||||
customerIDs = params.CustomerIDs
|
customerIDs = params.CustomerIDs
|
||||||
|
if restrictScope {
|
||||||
|
customerIDs = intersectUint(customerIDs, allowedCustomerIDs)
|
||||||
|
}
|
||||||
totalCustomers = int64(len(customerIDs))
|
totalCustomers = int64(len(customerIDs))
|
||||||
|
|
||||||
if len(customerIDs) == 0 {
|
if len(customerIDs) == 0 {
|
||||||
@@ -430,7 +461,7 @@ func (s *repportService) GetCustomerPayment(ctx *fiber.Ctx, params *validation.C
|
|||||||
offset := (page - 1) * limit
|
offset := (page - 1) * limit
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
customerIDs, totalCustomers, err = s.CustomerPaymentRepo.GetCustomerIDsWithTransactions(ctx.Context(), limit, offset)
|
customerIDs, totalCustomers, err = s.CustomerPaymentRepo.GetCustomerIDsWithTransactions(ctx.Context(), limit, offset, allowedCustomerIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
@@ -456,6 +487,37 @@ func (s *repportService) GetCustomerPayment(ctx *fiber.Ctx, params *validation.C
|
|||||||
return result, totalCustomers, nil
|
return result, totalCustomers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *repportService) getCustomerIDsByScope(ctx context.Context, locationIDs, areaIDs []uint) ([]uint, error) {
|
||||||
|
if len(locationIDs) == 0 && len(areaIDs) == 0 {
|
||||||
|
return []uint{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
db := s.db.WithContext(ctx).
|
||||||
|
Table("customers c").
|
||||||
|
Select("DISTINCT c.id").
|
||||||
|
Joins("JOIN marketings m ON m.customer_id = c.id").
|
||||||
|
Joins("JOIN marketing_products mp ON mp.marketing_id = m.id").
|
||||||
|
Joins("JOIN marketing_delivery_products mdp ON mdp.marketing_product_id = mp.id").
|
||||||
|
Joins("JOIN product_warehouses pw ON pw.id = mdp.product_warehouse_id").
|
||||||
|
Joins("JOIN warehouses w ON w.id = pw.warehouse_id").
|
||||||
|
Where("mdp.delivery_date IS NOT NULL").
|
||||||
|
Where("m.deleted_at IS NULL").
|
||||||
|
Where("c.deleted_at IS NULL")
|
||||||
|
|
||||||
|
if len(locationIDs) > 0 {
|
||||||
|
db = db.Where("w.location_id IN ?", locationIDs)
|
||||||
|
}
|
||||||
|
if len(areaIDs) > 0 {
|
||||||
|
db = db.Where("w.area_id IN ?", areaIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
var customerIDs []uint
|
||||||
|
if err := db.Pluck("c.id", &customerIDs).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return customerIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *repportService) processCustomerPayment(ctx context.Context, customerID uint, params *validation.CustomerPaymentQuery) (dto.CustomerPaymentReportItem, error) {
|
func (s *repportService) processCustomerPayment(ctx context.Context, customerID uint, params *validation.CustomerPaymentQuery) (dto.CustomerPaymentReportItem, error) {
|
||||||
|
|
||||||
customer, err := s.CustomerRepo.GetByID(ctx, customerID, nil)
|
customer, err := s.CustomerRepo.GetByID(ctx, customerID, nil)
|
||||||
@@ -803,7 +865,7 @@ func (s *repportService) getUniformityByWeek(ctx context.Context, projectFlockKa
|
|||||||
}
|
}
|
||||||
|
|
||||||
var rows []entity.ProjectFlockKandangUniformity
|
var rows []entity.ProjectFlockKandangUniformity
|
||||||
if err := s.DB.WithContext(ctx).
|
if err := s.db.WithContext(ctx).
|
||||||
Model(&entity.ProjectFlockKandangUniformity{}).
|
Model(&entity.ProjectFlockKandangUniformity{}).
|
||||||
Select("week, uniformity, uniform_date, id, chart_data").
|
Select("week, uniformity, uniform_date, id, chart_data").
|
||||||
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
||||||
@@ -2007,6 +2069,23 @@ func intersectInt64(a, b []int64) []int64 {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func intersectUint(a, b []uint) []uint {
|
||||||
|
if len(a) == 0 || len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
set := make(map[uint]struct{}, len(b))
|
||||||
|
for _, id := range b {
|
||||||
|
set[id] = struct{}{}
|
||||||
|
}
|
||||||
|
out := make([]uint, 0, len(a))
|
||||||
|
for _, id := range a {
|
||||||
|
if _, ok := set[id]; ok {
|
||||||
|
out = append(out, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
func parseOptionalFloat64(raw string) (*float64, error) {
|
func parseOptionalFloat64(raw string) (*float64, error) {
|
||||||
raw = strings.TrimSpace(raw)
|
raw = strings.TrimSpace(raw)
|
||||||
if raw == "" {
|
if raw == "" {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ type MarketingQuery struct {
|
|||||||
EndDate string `query:"end_date" validate:"omitempty,datetime=2006-01-02"`
|
EndDate string `query:"end_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
SortBy string `query:"sort_by" validate:"omitempty,oneof=so_date realization_date customer warehouse product sales_person vehicle_number sales_amount hpp_amount qty average_weight total_weight sales_price hpp_price aging_days"`
|
SortBy string `query:"sort_by" validate:"omitempty,oneof=so_date realization_date customer warehouse product sales_person vehicle_number sales_amount hpp_amount qty average_weight total_weight sales_price hpp_price aging_days"`
|
||||||
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
|
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
|
||||||
|
AllowedAreaIDs []int64 `query:"-"`
|
||||||
AllowedLocationIDs []int64 `query:"-"`
|
AllowedLocationIDs []int64 `query:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +59,8 @@ type DebtSupplierQuery struct {
|
|||||||
EndDate string `query:"end_date" validate:"omitempty,datetime=2006-01-02"`
|
EndDate string `query:"end_date" validate:"omitempty,datetime=2006-01-02"`
|
||||||
FilterBy string `query:"filter_by" validate:"omitempty,oneof=received_date po_date"`
|
FilterBy string `query:"filter_by" validate:"omitempty,oneof=received_date po_date"`
|
||||||
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
|
SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"`
|
||||||
|
AllowedAreaIDs []int64 `query:"-"`
|
||||||
|
AllowedLocationIDs []int64 `query:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HppPerKandangQuery struct {
|
type HppPerKandangQuery struct {
|
||||||
|
|||||||
@@ -117,6 +117,16 @@ const (
|
|||||||
StockLogTypeRecording StockLogType = "RECORDING"
|
StockLogTypeRecording StockLogType = "RECORDING"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
// Transfer context
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
const (
|
||||||
|
TransferContextKey = "transfer_context"
|
||||||
|
TransferContextInventoryTransfer = "inventory_transfer"
|
||||||
|
TransferContextTransferToLaying = "transfer_to_laying"
|
||||||
|
)
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// WarehouseType
|
// WarehouseType
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,446 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
|
|
||||||
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
|
||||||
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
|
|
||||||
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
||||||
rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories"
|
|
||||||
recordingRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories"
|
|
||||||
servicePkg "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/services"
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
|
||||||
"gitlab.com/mbugroup/lti-api.git/internal/utils/fifo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRecordingFIFO_CreatePendingWithoutStock(t *testing.T) {
|
|
||||||
db, svc, _, _ := setupRecordingFIFOTableTest(t)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
recordingID := uint(1)
|
|
||||||
productWarehouse := createProductWarehouseRow(t, db, 0)
|
|
||||||
stock := createRecordingStockRow(t, db, recordingID, productWarehouse.Id, 10)
|
|
||||||
|
|
||||||
if err := svc.ConsumeRecordingStocks(ctx, db, []entity.RecordingStock{stock}); err != nil {
|
|
||||||
t.Fatalf("consumeRecordingStocks (pending) failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
updated := fetchRecordingStock(t, db, stock.Id)
|
|
||||||
assertFloatEqual(t, 0, updated.UsageQty, "usage_qty should remain zero when no stock is available")
|
|
||||||
assertFloatEqual(t, 10, updated.PendingQty, "pending_qty should capture the entire request")
|
|
||||||
assertWarehouseQuantity(t, db, productWarehouse.Id, 0)
|
|
||||||
assertAllocationCount(t, db, 0)
|
|
||||||
|
|
||||||
assertAllocationCount(t, db, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecordingFIFO_EditReallocatesUsage(t *testing.T) {
|
|
||||||
db, svc, fifoSvc, stockableKey := setupRecordingFIFOTableTest(t)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
recordingID := uint(1)
|
|
||||||
productWarehouse := createProductWarehouseRow(t, db, 0)
|
|
||||||
stock := createRecordingStockRow(t, db, recordingID, productWarehouse.Id, 10)
|
|
||||||
lot := createStockLot(t, db, productWarehouse.Id)
|
|
||||||
|
|
||||||
if _, err := fifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
|
||||||
StockableKey: stockableKey,
|
|
||||||
StockableID: lot.Id,
|
|
||||||
ProductWarehouseID: productWarehouse.Id,
|
|
||||||
Quantity: 12,
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("replenish failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := svc.ConsumeRecordingStocks(ctx, db, []entity.RecordingStock{stock}); err != nil {
|
|
||||||
t.Fatalf("consumeRecordingStocks (initial) failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertWarehouseQuantity(t, db, productWarehouse.Id, 2)
|
|
||||||
|
|
||||||
desired := 4.0
|
|
||||||
stock.UsageQty = &desired
|
|
||||||
|
|
||||||
if err := svc.ConsumeRecordingStocks(ctx, db, []entity.RecordingStock{stock}); err != nil {
|
|
||||||
t.Fatalf("consumeRecordingStocks (edit) failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
updated := fetchRecordingStock(t, db, stock.Id)
|
|
||||||
assertFloatEqual(t, 4, updated.UsageQty, "usage_qty should reflect edited request")
|
|
||||||
assertFloatEqual(t, 0, updated.PendingQty, "pending_qty should remain zero after downsize")
|
|
||||||
assertWarehouseQuantity(t, db, productWarehouse.Id, 8)
|
|
||||||
|
|
||||||
alloc := fetchSingleAllocation(t, db, stock.Id)
|
|
||||||
if alloc.Status != entity.StockAllocationStatusActive {
|
|
||||||
t.Fatalf("expected ACTIVE allocation, got %s", alloc.Status)
|
|
||||||
}
|
|
||||||
if mathAbs(alloc.Qty-4) > 1e-6 {
|
|
||||||
t.Fatalf("expected allocation qty 4, got %.3f", alloc.Qty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRecordingFIFO_DeleteReleasesStock(t *testing.T) {
|
|
||||||
db, svc, fifoSvc, stockableKey := setupRecordingFIFOTableTest(t)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
recordingID := uint(1)
|
|
||||||
productWarehouse := createProductWarehouseRow(t, db, 0)
|
|
||||||
stock := createRecordingStockRow(t, db, recordingID, productWarehouse.Id, 10)
|
|
||||||
lot := createStockLot(t, db, productWarehouse.Id)
|
|
||||||
|
|
||||||
if _, err := fifoSvc.Replenish(ctx, commonSvc.StockReplenishRequest{
|
|
||||||
StockableKey: stockableKey,
|
|
||||||
StockableID: lot.Id,
|
|
||||||
ProductWarehouseID: productWarehouse.Id,
|
|
||||||
Quantity: 10,
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("replenish failed: %v", err)
|
|
||||||
}
|
|
||||||
if err := svc.ConsumeRecordingStocks(ctx, db, []entity.RecordingStock{stock}); err != nil {
|
|
||||||
t.Fatalf("consumeRecordingStocks failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := svc.ReleaseRecordingStocks(ctx, db, []entity.RecordingStock{stock}); err != nil {
|
|
||||||
t.Fatalf("releaseRecordingStocks failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
updated := fetchRecordingStock(t, db, stock.Id)
|
|
||||||
assertFloatEqual(t, 0, updated.UsageQty, "usage_qty should be cleared after delete")
|
|
||||||
assertFloatEqual(t, 0, updated.PendingQty, "pending_qty should be cleared after delete")
|
|
||||||
assertWarehouseQuantity(t, db, productWarehouse.Id, 10)
|
|
||||||
|
|
||||||
alloc := fetchSingleAllocation(t, db, stock.Id)
|
|
||||||
if alloc.Status != entity.StockAllocationStatusReleased {
|
|
||||||
t.Fatalf("expected allocation to be released, got %s", alloc.Status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- helpers ----------------------------------------------------------------
|
|
||||||
|
|
||||||
type recordingStockTable struct {
|
|
||||||
Id uint `gorm:"primaryKey"`
|
|
||||||
RecordingId uint `gorm:"column:recording_id;not null"`
|
|
||||||
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"`
|
|
||||||
UsageQty *float64 `gorm:"column:usage_qty"`
|
|
||||||
PendingQty *float64 `gorm:"column:pending_qty"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (recordingStockTable) TableName() string { return "recording_stocks" }
|
|
||||||
|
|
||||||
type productWarehouseTable struct {
|
|
||||||
Id uint `gorm:"primaryKey"`
|
|
||||||
ProductId uint `gorm:"column:product_id"`
|
|
||||||
WarehouseId uint `gorm:"column:warehouse_id"`
|
|
||||||
Quantity float64 `gorm:"column:quantity"`
|
|
||||||
CreatedBy uint `gorm:"column:created_by"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (productWarehouseTable) TableName() string { return "product_warehouses" }
|
|
||||||
|
|
||||||
type stockAllocationTable struct {
|
|
||||||
Id uint `gorm:"primaryKey"`
|
|
||||||
ProductWarehouseId uint `gorm:"not null"`
|
|
||||||
StockableType string `gorm:"size:100"`
|
|
||||||
StockableId uint
|
|
||||||
UsableType string `gorm:"size:100"`
|
|
||||||
UsableId uint
|
|
||||||
Qty float64 `gorm:"column:qty"`
|
|
||||||
Status string `gorm:"size:20"`
|
|
||||||
Note *string `gorm:"type:text"`
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
ReleasedAt *time.Time
|
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (stockAllocationTable) TableName() string { return "stock_allocations" }
|
|
||||||
|
|
||||||
type testStockSource struct {
|
|
||||||
Id uint `gorm:"primaryKey"`
|
|
||||||
ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null"`
|
|
||||||
TotalQty float64 `gorm:"column:total_qty"`
|
|
||||||
TotalUsedQty float64 `gorm:"column:total_used_qty"`
|
|
||||||
CreatedAt time.Time `gorm:"column:created_at"`
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (testStockSource) TableName() string { return "test_fifo_stockables" }
|
|
||||||
|
|
||||||
func setupRecordingFIFOTableTest(t *testing.T) (*gorm.DB, servicePkg.RecordingFIFOIntegrationService, commonSvc.FifoService, fifo.StockableKey) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{
|
|
||||||
Logger: logger.Default.LogMode(logger.Silent),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("open sqlite: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.AutoMigrate(
|
|
||||||
&recordingStockTable{},
|
|
||||||
&productWarehouseTable{},
|
|
||||||
&stockAllocationTable{},
|
|
||||||
&testStockSource{},
|
|
||||||
); err != nil {
|
|
||||||
t.Fatalf("auto migrate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.AutoMigrate(
|
|
||||||
&entity.ProductWarehouse{},
|
|
||||||
&entity.StockAllocation{},
|
|
||||||
&entity.RecordingStock{},
|
|
||||||
); err != nil {
|
|
||||||
t.Fatalf("auto migrate entities: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stockAllocRepo := newFifoTestStockAllocationRepo(db)
|
|
||||||
productWarehouseRepo := rProductWarehouse.NewProductWarehouseRepository(db)
|
|
||||||
fifoSvc := commonSvc.NewFifoService(db, stockAllocRepo, productWarehouseRepo, utils.Log)
|
|
||||||
|
|
||||||
registerRecordingUsable(t, fifoSvc)
|
|
||||||
|
|
||||||
key := fifo.StockableKey(fmt.Sprintf("TEST_STOCKABLE_%s_%d", sanitizeKey(t.Name()), time.Now().UnixNano()))
|
|
||||||
if err := fifoSvc.RegisterStockable(fifo.StockableConfig{
|
|
||||||
Key: key,
|
|
||||||
Table: "test_fifo_stockables",
|
|
||||||
Columns: fifo.StockableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
TotalQuantity: "total_qty",
|
|
||||||
TotalUsedQuantity: "total_used_qty",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("register stockable: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
svc := servicePkg.NewRecordingFIFOIntegrationService(
|
|
||||||
recordingRepo.NewRecordingRepository(db),
|
|
||||||
productWarehouseRepo,
|
|
||||||
fifoSvc,
|
|
||||||
)
|
|
||||||
|
|
||||||
return db, svc, fifoSvc, key
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerRecordingUsable(t *testing.T, fifoSvc commonSvc.FifoService) {
|
|
||||||
t.Helper()
|
|
||||||
err := fifoSvc.RegisterUsable(fifo.UsableConfig{
|
|
||||||
Key: fifo.UsableKeyRecordingStock,
|
|
||||||
Table: "recording_stocks",
|
|
||||||
Columns: fifo.UsableColumns{
|
|
||||||
ID: "id",
|
|
||||||
ProductWarehouseID: "product_warehouse_id",
|
|
||||||
UsageQuantity: "usage_qty",
|
|
||||||
PendingQuantity: "pending_qty",
|
|
||||||
CreatedAt: "created_at",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil && !strings.Contains(strings.ToLower(err.Error()), "already registered") {
|
|
||||||
t.Fatalf("register usable: %v", err)
|
|
||||||
}
|
|
||||||
if _, ok := fifo.Usable(fifo.UsableKeyRecordingStock); !ok {
|
|
||||||
t.Fatal("recording stock usable key not registered")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createProductWarehouseRow(t *testing.T, db *gorm.DB, qty float64) entity.ProductWarehouse {
|
|
||||||
t.Helper()
|
|
||||||
pw := entity.ProductWarehouse{
|
|
||||||
ProductId: 1,
|
|
||||||
WarehouseId: 1,
|
|
||||||
Quantity: qty,
|
|
||||||
// CreatedBy: 1,
|
|
||||||
}
|
|
||||||
if err := db.Create(&pw).Error; err != nil {
|
|
||||||
t.Fatalf("create product warehouse: %v", err)
|
|
||||||
}
|
|
||||||
return pw
|
|
||||||
}
|
|
||||||
|
|
||||||
func createRecordingStockRow(t *testing.T, db *gorm.DB, recordingID, productWarehouseID uint, desired float64) entity.RecordingStock {
|
|
||||||
t.Helper()
|
|
||||||
stock := entity.RecordingStock{
|
|
||||||
RecordingId: recordingID,
|
|
||||||
ProductWarehouseId: productWarehouseID,
|
|
||||||
UsageQty: floatPtr(0),
|
|
||||||
PendingQty: floatPtr(0),
|
|
||||||
}
|
|
||||||
if err := db.Create(&stock).Error; err != nil {
|
|
||||||
t.Fatalf("create recording stock: %v", err)
|
|
||||||
}
|
|
||||||
stock.UsageQty = floatPtr(desired)
|
|
||||||
return stock
|
|
||||||
}
|
|
||||||
|
|
||||||
func createStockLot(t *testing.T, db *gorm.DB, productWarehouseID uint) testStockSource {
|
|
||||||
t.Helper()
|
|
||||||
lot := testStockSource{
|
|
||||||
ProductWarehouseId: productWarehouseID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}
|
|
||||||
if err := db.Create(&lot).Error; err != nil {
|
|
||||||
t.Fatalf("create stock lot: %v", err)
|
|
||||||
}
|
|
||||||
return lot
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchRecordingStock(t *testing.T, db *gorm.DB, id uint) entity.RecordingStock {
|
|
||||||
t.Helper()
|
|
||||||
var stock entity.RecordingStock
|
|
||||||
if err := db.First(&stock, id).Error; err != nil {
|
|
||||||
t.Fatalf("fetch recording stock: %v", err)
|
|
||||||
}
|
|
||||||
return stock
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchSingleAllocation(t *testing.T, db *gorm.DB, usableID uint) entity.StockAllocation {
|
|
||||||
t.Helper()
|
|
||||||
var alloc entity.StockAllocation
|
|
||||||
if err := db.Where("usable_id = ?", usableID).Order("created_at ASC").First(&alloc).Error; err != nil {
|
|
||||||
t.Fatalf("fetch allocation: %v", err)
|
|
||||||
}
|
|
||||||
return alloc
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertAllocationCount(t *testing.T, db *gorm.DB, expected int64) {
|
|
||||||
t.Helper()
|
|
||||||
var count int64
|
|
||||||
if err := db.Model(&entity.StockAllocation{}).Count(&count).Error; err != nil {
|
|
||||||
t.Fatalf("count allocations: %v", err)
|
|
||||||
}
|
|
||||||
if count != expected {
|
|
||||||
t.Fatalf("expected %d allocations, got %d", expected, count)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertWarehouseQuantity(t *testing.T, db *gorm.DB, id uint, expected float64) {
|
|
||||||
t.Helper()
|
|
||||||
var pw entity.ProductWarehouse
|
|
||||||
if err := db.First(&pw, id).Error; err != nil {
|
|
||||||
t.Fatalf("fetch product warehouse: %v", err)
|
|
||||||
}
|
|
||||||
if mathAbs(pw.Quantity-expected) > 1e-6 {
|
|
||||||
t.Fatalf("expected warehouse quantity %.3f, got %.3f", expected, pw.Quantity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertFloatEqual(t *testing.T, expected float64, value *float64, msg string) {
|
|
||||||
t.Helper()
|
|
||||||
if value == nil {
|
|
||||||
t.Fatalf("expected %s %.3f, got nil", msg, expected)
|
|
||||||
}
|
|
||||||
if mathAbs(*value-expected) > 1e-6 {
|
|
||||||
t.Fatalf("%s: expected %.3f, got %.3f", msg, expected, *value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func floatPtr(v float64) *float64 {
|
|
||||||
p := new(float64)
|
|
||||||
*p = v
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func mathAbs(v float64) float64 {
|
|
||||||
if v < 0 {
|
|
||||||
return -v
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeKey(name string) string {
|
|
||||||
if name == "" {
|
|
||||||
return "CASE"
|
|
||||||
}
|
|
||||||
clean := strings.Map(func(r rune) rune {
|
|
||||||
if (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
if r >= 'a' && r <= 'z' {
|
|
||||||
return r - 32
|
|
||||||
}
|
|
||||||
return '_'
|
|
||||||
}, name)
|
|
||||||
return clean
|
|
||||||
}
|
|
||||||
|
|
||||||
type fifoTestStockAllocationRepo struct {
|
|
||||||
commonRepo.StockAllocationRepository
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func newFifoTestStockAllocationRepo(db *gorm.DB) commonRepo.StockAllocationRepository {
|
|
||||||
return &fifoTestStockAllocationRepo{
|
|
||||||
StockAllocationRepository: commonRepo.NewStockAllocationRepository(db),
|
|
||||||
db: db,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *fifoTestStockAllocationRepo) PatchOne(
|
|
||||||
ctx context.Context,
|
|
||||||
id uint,
|
|
||||||
updates map[string]any,
|
|
||||||
modifier func(*gorm.DB) *gorm.DB,
|
|
||||||
) error {
|
|
||||||
base := r.db
|
|
||||||
|
|
||||||
setClauses := make([]string, 0, len(updates))
|
|
||||||
args := make([]any, 0, len(updates)+1)
|
|
||||||
for column, value := range updates {
|
|
||||||
colName := column
|
|
||||||
if strings.EqualFold(column, "quantity") {
|
|
||||||
colName = "qty"
|
|
||||||
}
|
|
||||||
setClauses = append(setClauses, fmt.Sprintf("%s = ?", colName))
|
|
||||||
args = append(args, value)
|
|
||||||
}
|
|
||||||
args = append(args, id)
|
|
||||||
sql := fmt.Sprintf("UPDATE stock_allocations SET %s WHERE id = ?", strings.Join(setClauses, ", "))
|
|
||||||
|
|
||||||
result := base.Exec(sql, args...)
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return gorm.ErrRecordNotFound
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *fifoTestStockAllocationRepo) ReleaseByUsable(
|
|
||||||
ctx context.Context,
|
|
||||||
usableType string,
|
|
||||||
usableID uint,
|
|
||||||
note *string,
|
|
||||||
modifier func(*gorm.DB) *gorm.DB,
|
|
||||||
) error {
|
|
||||||
base := r.db
|
|
||||||
|
|
||||||
setClause := "status = ?, released_at = ?"
|
|
||||||
args := []any{entity.StockAllocationStatusReleased, time.Now()}
|
|
||||||
if note != nil {
|
|
||||||
setClause += ", note = ?"
|
|
||||||
args = append(args, *note)
|
|
||||||
}
|
|
||||||
args = append(args, usableType, usableID, entity.StockAllocationStatusActive)
|
|
||||||
sql := fmt.Sprintf(
|
|
||||||
"UPDATE stock_allocations SET %s WHERE usable_type = ? AND usable_id = ? AND status = ?",
|
|
||||||
setClause,
|
|
||||||
)
|
|
||||||
|
|
||||||
result := base.Exec(sql, args...)
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user