From a3e9017e29219b58bf3e84aec18c6a31a813e3b5 Mon Sep 17 00:00:00 2001 From: ragilap Date: Thu, 15 Jan 2026 14:43:59 +0700 Subject: [PATCH] [FIX/BE-US] changes role to user and query --- internal/middleware/auth.go | 32 +- internal/middleware/role_scope.go | 501 ++++++++++++++---- .../closings/services/closing.service.go | 57 +- .../services/adjustment.service.go | 28 +- .../salesorder_delivery_product.repository.go | 9 + .../services/deliveryorder.service.go | 32 ++ .../marketing/services/salesorder.service.go | 34 +- .../kandangs/services/kandang.service.go | 15 + .../warehouses/services/warehouse.service.go | 32 ++ .../recordings/services/recording.service.go | 40 ++ .../services/transfer_laying.service.go | 34 ++ .../repositories/uniformity.repository.go | 12 +- .../services/uniformity.service.go | 37 +- .../controllers/repport.controller.go | 16 + .../validations/repport.validation.go | 1 + internal/modules/sso/verifier/profile.go | 72 ++- 16 files changed, 805 insertions(+), 147 deletions(-) diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 1b670c14..b7229382 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -24,6 +24,10 @@ type AuthContext struct { User *entity.User Roles []sso.Role Permissions map[string]struct{} + UserAreaIDs []uint + UserLocationIDs []uint + UserAllArea bool + UserAllLocation bool } // Auth validates the incoming request against the central SSO access token and @@ -67,15 +71,19 @@ func Auth(userService service.UserService, requiredScopes ...string) fiber.Handl var roles []sso.Role permissions := make(map[string]struct{}) + var profile *sso.UserProfile if verification.UserID != 0 { - if profile, err := sso.FetchProfile(c.Context(), token, verification); err != nil { + if p, err := sso.FetchProfile(c.Context(), token, verification); err != nil { utils.Log.WithError(err).Warn("auth: failed to fetch sso profile") - } else if profile != nil { - roles = profile.Roles - for _, perm := range profile.PermissionNames() { - if perm != "" { - permissions[perm] = struct{}{} - } + } else { + profile = p + } + } + if profile != nil { + roles = profile.Roles + for _, perm := range profile.PermissionNames() { + if perm != "" { + permissions[perm] = struct{}{} } } } @@ -86,6 +94,16 @@ func Auth(userService service.UserService, requiredScopes ...string) fiber.Handl User: user, Roles: roles, Permissions: permissions, + UserAreaIDs: nil, + UserLocationIDs: nil, + UserAllArea: false, + UserAllLocation: false, + } + if profile != nil { + ctx.UserAreaIDs = profile.AreaIDs + ctx.UserLocationIDs = profile.LocationIDs + ctx.UserAllArea = profile.AllArea + ctx.UserAllLocation = profile.AllLocation } c.Locals(authContextLocalsKey, ctx) diff --git a/internal/middleware/role_scope.go b/internal/middleware/role_scope.go index f00e12d9..155d2e6a 100644 --- a/internal/middleware/role_scope.go +++ b/internal/middleware/role_scope.go @@ -2,12 +2,10 @@ package middleware import ( "errors" - "strings" "github.com/gofiber/fiber/v2" "gorm.io/gorm" - "gitlab.com/mbugroup/lti-api.git/internal/config" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" ) @@ -86,48 +84,24 @@ func ResolveLocationScope(c *fiber.Ctx, db *gorm.DB) (ScopeFilter, error) { func collectRoleScope(c *fiber.Ctx) (roleScope, error) { ctx, ok := AuthDetails(c) - if !ok || ctx == nil || len(ctx.Roles) == 0 { + if !ok || ctx == nil { return roleScope{}, nil } - clientAlias := resolveClientAlias(ctx) - - scope := roleScope{} - areaSet := make(map[uint]struct{}) - locationSet := make(map[uint]struct{}) - - for _, role := range ctx.Roles { - if clientAlias != "" && !strings.EqualFold(strings.TrimSpace(role.ClientAlias), clientAlias) { - continue - } - scope.hasAnyScopes = true - if role.AllArea { - scope.allArea = true - } - if role.AllLocation { - scope.allLocation = true - } - for _, id := range role.AreaIDs { - if id == 0 { - continue - } - areaSet[id] = struct{}{} - } - for _, id := range role.LocationIDs { - if id == 0 { - continue - } - locationSet[id] = struct{}{} - } + userAreaIDs := uniqueUint(ctx.UserAreaIDs) + userLocationIDs := uniqueUint(ctx.UserLocationIDs) + userScope := roleScope{ + allArea: ctx.UserAllArea, + allLocation: ctx.UserAllLocation, + areaIDs: userAreaIDs, + locationIDs: userLocationIDs, + hasAnyScopes: ctx.UserAllArea || ctx.UserAllLocation || len(userAreaIDs) > 0 || len(userLocationIDs) > 0, + } + if userScope.hasAnyScopes { + return userScope, nil } - scope.areaIDs = keysUint(areaSet) - scope.locationIDs = keysUint(locationSet) - - scope.hasAnyScopes = scope.hasAnyScopes && - (scope.allArea || scope.allLocation || len(scope.areaIDs) > 0 || len(scope.locationIDs) > 0) - - return scope, nil + return roleScope{}, nil } func areaIDsByLocationIDs(db *gorm.DB, locationIDs []uint) ([]uint, error) { @@ -204,70 +178,7 @@ func uniqueUint(ids []uint) []uint { return result } -func keysUint(set map[uint]struct{}) []uint { - if len(set) == 0 { - return nil - } - out := make([]uint, 0, len(set)) - for id := range set { - out = append(out, id) - } - return out -} -func resolveClientAlias(ctx *AuthContext) string { - if ctx == nil || ctx.Verification == nil || ctx.Verification.Claims == nil { - return "" - } - - scopes := ctx.Verification.Claims.Scopes() - if len(scopes) == 0 { - return "" - } - - seen := make(map[string]struct{}) - for _, scope := range scopes { - scope = strings.ToLower(strings.TrimSpace(scope)) - if scope == "" { - continue - } - prefix := scope - if idx := strings.IndexAny(prefix, ".:"); idx > 0 { - prefix = prefix[:idx] - } - prefix = strings.TrimSpace(prefix) - if prefix == "" { - continue - } - if alias := matchAlias(prefix); alias != "" { - seen[alias] = struct{}{} - } - } - - if len(seen) != 1 { - return "" - } - for alias := range seen { - return alias - } - return "" -} - -func matchAlias(alias string) string { - alias = strings.ToLower(strings.TrimSpace(alias)) - if alias == "" { - return "" - } - if _, ok := config.SSOClients[alias]; ok { - return alias - } - for key := range config.SSOClients { - if strings.EqualFold(key, alias) { - return strings.ToLower(strings.TrimSpace(key)) - } - } - return "" -} func ApplyScopeFilter(db *gorm.DB, scope ScopeFilter, column string) *gorm.DB { if db == nil || !scope.Restrict { @@ -278,3 +189,389 @@ func ApplyScopeFilter(db *gorm.DB, scope ScopeFilter, column string) *gorm.DB { } return db.Where(column+" IN ?", scope.IDs) } + +func EnsureWarehouseAccess(c *fiber.Ctx, db *gorm.DB, warehouseID uint) error { + if warehouseID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid warehouse id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveLocationScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Warehouse not found") + } + + var count int64 + if err := ApplyScopeFilter( + db.WithContext(c.Context()). + Model(&entity.Warehouse{}). + Where("id = ?", warehouseID), + scope, + "warehouses.location_id", + ).Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Warehouse not found") + } + return nil +} + +func EnsureAreaAccess(c *fiber.Ctx, db *gorm.DB, areaID uint) error { + if areaID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid area id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveAreaScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Area not found") + } + + var count int64 + if err := ApplyScopeFilter( + db.WithContext(c.Context()). + Model(&entity.Area{}). + Where("id = ?", areaID), + scope, + "areas.id", + ).Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Area not found") + } + return nil +} + +func EnsureLocationAccess(c *fiber.Ctx, db *gorm.DB, locationID uint) error { + if locationID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid location id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveLocationScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Location not found") + } + + var count int64 + if err := ApplyScopeFilter( + db.WithContext(c.Context()). + Model(&entity.Location{}). + Where("id = ?", locationID), + scope, + "locations.id", + ).Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Location not found") + } + return nil +} + +func EnsureKandangAccess(c *fiber.Ctx, db *gorm.DB, kandangID uint) error { + if kandangID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid kandang id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveLocationScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Kandang not found") + } + + var count int64 + if err := ApplyScopeFilter( + db.WithContext(c.Context()). + Model(&entity.Kandang{}). + Where("id = ?", kandangID), + scope, + "kandangs.location_id", + ).Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Kandang not found") + } + return nil +} + +func EnsureProductWarehouseAccess(c *fiber.Ctx, db *gorm.DB, productWarehouseID uint) error { + if productWarehouseID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid product warehouse id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveLocationScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Product warehouse not found") + } + + var count int64 + q := db.WithContext(c.Context()). + Table("product_warehouses pw"). + Joins("JOIN warehouses w ON w.id = pw.warehouse_id"). + Where("pw.id = ?", productWarehouseID) + q = ApplyScopeFilter(q, scope, "w.location_id") + if err := q.Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Product warehouse not found") + } + return nil +} + +func EnsureStockLogAccess(c *fiber.Ctx, db *gorm.DB, stockLogID uint) error { + if stockLogID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid stock log id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveLocationScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Stock log not found") + } + + var count int64 + q := db.WithContext(c.Context()). + Table("stock_logs sl"). + Joins("JOIN product_warehouses pw ON pw.id = sl.product_warehouse_id"). + Joins("JOIN warehouses w ON w.id = pw.warehouse_id"). + Where("sl.id = ?", stockLogID) + q = ApplyScopeFilter(q, scope, "w.location_id") + if err := q.Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Stock log not found") + } + return nil +} + +func EnsureMarketingAccess(c *fiber.Ctx, db *gorm.DB, marketingID uint) error { + if marketingID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid marketing id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveLocationScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Marketing not found") + } + + var count int64 + q := db.WithContext(c.Context()). + Table("marketings m"). + Joins("JOIN marketing_products mp ON mp.marketing_id = m.id"). + Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id"). + Joins("JOIN warehouses w ON w.id = pw.warehouse_id"). + Where("m.id = ?", marketingID) + q = ApplyScopeFilter(q, scope, "w.location_id") + if err := q.Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Marketing not found") + } + return nil +} + +func EnsureRecordingAccess(c *fiber.Ctx, db *gorm.DB, recordingID uint) error { + if recordingID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid recording id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveLocationScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Recording not found") + } + + var count int64 + q := db.WithContext(c.Context()). + Table("recordings r"). + Joins("JOIN project_flock_kandangs pfk ON pfk.id = r.project_flock_kandangs_id"). + Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id"). + Where("r.id = ?", recordingID) + q = ApplyScopeFilter(q, scope, "pf.location_id") + if err := q.Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Recording not found") + } + return nil +} + +func EnsureUniformityAccess(c *fiber.Ctx, db *gorm.DB, uniformityID uint) error { + if uniformityID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid uniformity id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveLocationScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Uniformity not found") + } + + var count int64 + q := db.WithContext(c.Context()). + Table("project_flock_kandang_uniformities u"). + 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"). + Where("u.id = ?", uniformityID) + q = ApplyScopeFilter(q, scope, "pf.location_id") + if err := q.Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Uniformity not found") + } + return nil +} + +func EnsureLayingTransferAccess(c *fiber.Ctx, db *gorm.DB, transferID uint) error { + if transferID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid transfer id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveLocationScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Transfer not found") + } + + var count int64 + q := db.WithContext(c.Context()). + Table("laying_transfers lt"). + Joins("JOIN project_flocks pf_from ON pf_from.id = lt.from_project_flock_id"). + Joins("JOIN project_flocks pf_to ON pf_to.id = lt.to_project_flock_id"). + Where("lt.id = ?", transferID). + Where("(pf_from.location_id IN ? OR pf_to.location_id IN ?)", scope.IDs, scope.IDs) + if err := q.Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Transfer not found") + } + return nil +} + +func EnsureProjectFlockAccess(c *fiber.Ctx, db *gorm.DB, projectFlockID uint) error { + if projectFlockID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveLocationScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Project Flock not found") + } + + var count int64 + if err := ApplyScopeFilter( + db.WithContext(c.Context()). + Model(&entity.ProjectFlock{}). + Where("id = ?", projectFlockID), + scope, + "project_flocks.location_id", + ).Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Project Flock not found") + } + return nil +} + +func EnsureProjectFlockKandangAccess(c *fiber.Ctx, db *gorm.DB, projectFlockID, projectFlockKandangID uint) error { + if projectFlockKandangID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Invalid project flock kandang id") + } + if db == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Database not configured") + } + + scope, err := ResolveLocationScope(c, db) + if err != nil || !scope.Restrict { + return err + } + if len(scope.IDs) == 0 { + return fiber.NewError(fiber.StatusNotFound, "Project Flock Kandang not found") + } + + var count int64 + q := db.WithContext(c.Context()). + Table("project_flock_kandangs"). + Joins("JOIN project_flocks ON project_flocks.id = project_flock_kandangs.project_flock_id"). + Where("project_flock_kandangs.id = ?", projectFlockKandangID) + if projectFlockID > 0 { + q = q.Where("project_flock_kandangs.project_flock_id = ?", projectFlockID) + } + q = ApplyScopeFilter(q, scope, "project_flocks.location_id") + if err := q.Count(&count).Error; err != nil { + return err + } + if count == 0 { + return fiber.NewError(fiber.StatusNotFound, "Project Flock Kandang not found") + } + return nil +} diff --git a/internal/modules/closings/services/closing.service.go b/internal/modules/closings/services/closing.service.go index 38529b0d..16a889b5 100644 --- a/internal/modules/closings/services/closing.service.go +++ b/internal/modules/closings/services/closing.service.go @@ -12,6 +12,7 @@ import ( commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/dto" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations" @@ -98,10 +99,16 @@ func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.Cl 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 closings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withClosingRelations(db) + db = m.ApplyScopeFilter(db, scope, "project_flocks.location_id") if params.Search != "" { return db.Where("flock_name ILIKE ?", "%"+params.Search+"%") } @@ -128,6 +135,10 @@ func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.Cl } func (s closingService) GetProjectFlockByID(c *fiber.Ctx, id uint) (*entity.ProjectFlock, error) { + if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), id); err != nil { + return nil, err + } + projectFlock, err := s.ProjectFlockRepo.GetByID(c.Context(), id, s.withRelations) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Project Flock not found") @@ -139,6 +150,13 @@ func (s closingService) GetProjectFlockByID(c *fiber.Ctx, id uint) (*entity.Proj } func (s closingService) GetPenjualan(c *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) ([]entity.MarketingDeliveryProduct, error) { + if projectFlockKandangID != nil { + if err := m.EnsureProjectFlockKandangAccess(c, s.Repository.DB(), projectFlockID, *projectFlockKandangID); err != nil { + return nil, err + } + } else if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil { + return nil, err + } realisasi, err := s.MarketingDeliveryProductRepo.GetClosingPenjualan(c.Context(), projectFlockID, projectFlockKandangID) if err != nil { @@ -152,8 +170,8 @@ func (s closingService) GetPenjualan(c *fiber.Ctx, projectFlockID uint, projectF } func (s closingService) GetClosingSummary(c *fiber.Ctx, projectFlockID uint, kandangID *uint) (any, error) { - if projectFlockID == 0 { - return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id") + if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil { + return nil, err } if kandangID != nil { @@ -299,8 +317,8 @@ func (s closingService) getClosingSummaryByKandang(ctx context.Context, projectF } func (s closingService) GetClosingSapronak(c *fiber.Ctx, projectFlockID uint, params *validation.ClosingSapronakQuery) ([]dto.ClosingSapronakItemDTO, int64, error) { - if projectFlockID == 0 { - return nil, 0, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id") + if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil { + return nil, 0, err } if params == nil { @@ -322,14 +340,6 @@ func (s closingService) GetClosingSapronak(c *fiber.Ctx, projectFlockID uint, pa return nil, 0, fiber.NewError(fiber.StatusBadRequest, "type must be either incoming or outgoing") } - if _, err := s.Repository.GetByID(c.Context(), projectFlockID, nil); err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, 0, fiber.NewError(fiber.StatusNotFound, "Project flock tidak ditemukan") - } - s.Log.Errorf("Failed get project flock %d for sapronak closing: %+v", projectFlockID, err) - return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch project flock") - } - warehouseIDs, err := s.getWarehouseIDsByProjectFlock(c.Context(), projectFlockID) if err != nil { s.Log.Errorf("Failed to fetch warehouses for project flock %d: %+v", projectFlockID, err) @@ -490,6 +500,14 @@ func (s closingService) getApprovalStatuses(ctx context.Context, projectFlockID } func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) (*dto.OverheadListDTO, error) { + if projectFlockKandangID != nil { + if err := m.EnsureProjectFlockKandangAccess(c, s.Repository.DB(), projectFlockID, *projectFlockKandangID); err != nil { + return nil, err + } + } else if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil { + return nil, err + } + budgets, err := s.ProjectBudgetRepo.GetByProjectFlockID(c.Context(), projectFlockID) if err != nil { return nil, err @@ -578,6 +596,9 @@ func (s closingService) GetOverhead(c *fiber.Ctx, projectFlockID uint, projectFl } func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (*dto.ReportResponse, error) { + if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil { + return nil, err + } if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "Project Flock", ID: &projectFlockID, Exists: s.ProjectFlockRepo.IdExists}, @@ -654,8 +675,12 @@ func (s closingService) GetClosingKeuangan(c *fiber.Ctx, projectFlockID uint) (* } func (s closingService) GetExpeditionHPP(c *fiber.Ctx, projectFlockID uint, projectFlockKandangID *uint) (*dto.ExpeditionHPPDTO, error) { - if projectFlockID == 0 { - return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id") + if projectFlockKandangID != nil { + if err := m.EnsureProjectFlockKandangAccess(c, s.Repository.DB(), projectFlockID, *projectFlockKandangID); err != nil { + return nil, err + } + } else if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil { + return nil, err } rows, err := s.Repository.GetExpeditionHPP(c.Context(), projectFlockID, projectFlockKandangID) @@ -686,8 +711,8 @@ func (s closingService) GetExpeditionHPP(c *fiber.Ctx, projectFlockID uint, proj } func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint, kandangID *uint) (*dto.ClosingProductionReportDTO, error) { - if projectFlockID == 0 { - return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project flock id") + if err := m.EnsureProjectFlockAccess(c, s.Repository.DB(), projectFlockID); err != nil { + return nil, err } projectFlockKandangIDs, err := s.getProjectFlockKandangIDs(c.Context(), projectFlockID, kandangID) diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index 71b985c2..4347fd6c 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -74,6 +74,10 @@ func (s *adjustmentService) withRelations(db *gorm.DB) *gorm.DB { } func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.StockLog, error) { + if err := m.EnsureStockLogAccess(c, s.StockLogsRepository.DB(), id); err != nil { + return nil, err + } + stockLog, err := s.StockLogsRepository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { return s.withRelations(db).Preload("ProductWarehouse.Product.ProductCategory") }) @@ -101,6 +105,11 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e if err != nil { 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(), common.RelationCheck{Name: "Product", ID: &req.ProductID, Exists: s.ProductRepo.IdExists}, common.RelationCheck{Name: "Warehouse", ID: &req.WarehouseID, Exists: s.WarehouseRepo.IdExists}, @@ -279,6 +288,11 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu if query.WarehouseID > 0 && !isWarehousesExist { return nil, 0, fiber.NewError(fiber.StatusNotFound, "Warehouse not found") } + if query.WarehouseID > 0 { + if err := m.EnsureWarehouseAccess(c, s.WarehouseRepo.DB(), uint(query.WarehouseID)); err != nil { + return nil, 0, err + } + } isProductsExist, err := s.ProductRepo.IdExists(c.Context(), uint(query.ProductID)) if err != nil { @@ -290,7 +304,19 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu } stockLogs, total, err := s.StockLogsRepository.GetAll(c.Context(), offset, query.Limit, func(db *gorm.DB) *gorm.DB { - + scope, err := m.ResolveLocationScope(c, s.StockLogsRepository.DB()) + if err != nil { + return db.Where("1 = 0") + } + if scope.Restrict { + if len(scope.IDs) == 0 { + return db.Where("1 = 0") + } + 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") + db = m.ApplyScopeFilter(db, scope, "w.location_id") + } db = s.withRelations(db) db = db.Where("loggable_type = ?", string(utils.StockLogTypeAdjustment)) diff --git a/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go b/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go index f14988b1..4998f82b 100644 --- a/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go +++ b/internal/modules/marketing/repositories/salesorder_delivery_product.repository.go @@ -140,6 +140,15 @@ func (r *MarketingDeliveryProductRepositoryImpl) GetAllWithFilters(ctx context.C Joins("JOIN marketings ON marketings.id = marketing_products.marketing_id"). Where("marketing_delivery_products.delivery_date IS NOT NULL") + if len(filters.AllowedLocationIDs) > 0 { + if !containsJoin(db, "product_warehouses") { + db = db.Joins("JOIN product_warehouses ON product_warehouses.id = marketing_products.product_warehouse_id") + } + db = db.Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = product_warehouses.project_flock_kandang_id"). + Joins("JOIN project_flocks ON project_flocks.id = project_flock_kandangs.project_flock_id"). + Where("project_flocks.location_id IN ?", filters.AllowedLocationIDs) + } + if filters.ProductId > 0 || filters.WarehouseId > 0 || filters.Search != "" || filters.MarketingType != "" { db = db.Joins("LEFT JOIN product_warehouses ON product_warehouses.id = marketing_products.product_warehouse_id") } diff --git a/internal/modules/marketing/services/deliveryorder.service.go b/internal/modules/marketing/services/deliveryorder.service.go index a1f4e1dd..9c197234 100644 --- a/internal/modules/marketing/services/deliveryorder.service.go +++ b/internal/modules/marketing/services/deliveryorder.service.go @@ -67,6 +67,10 @@ func (s deliveryOrdersService) withRelations(db *gorm.DB) *gorm.DB { } func (s deliveryOrdersService) getMarketingWithDeliveries(c *fiber.Ctx, marketingId uint) (*dto.MarketingDetailDTO, error) { + if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), marketingId); err != nil { + return nil, err + } + marketing, err := s.MarketingRepo.GetByID(c.Context(), marketingId, s.withRelations) if err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to fetch marketing") @@ -91,6 +95,11 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO return nil, 0, err } + scope, err := m.ResolveLocationScope(c, s.MarketingRepo.DB()) + if err != nil { + return nil, 0, err + } + offset := (params.Page - 1) * params.Limit marketings, total, err := s.MarketingRepo.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { @@ -102,6 +111,18 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO Preload("Products.ProductWarehouse.Warehouse"). Preload("Products.DeliveryProduct") + if scope.Restrict { + if len(scope.IDs) == 0 { + return db.Where("1 = 0") + } + db = db. + Joins("JOIN marketing_products mp ON mp.marketing_id = marketings.id"). + Joins("JOIN product_warehouses pw ON pw.id = mp.product_warehouse_id"). + Joins("JOIN warehouses w ON w.id = pw.warehouse_id"). + Where("w.location_id IN ?", scope.IDs). + Distinct("marketings.*") + } + if params.MarketingId != 0 { return db.Where("id = ?", params.MarketingId) } @@ -130,6 +151,9 @@ func (s deliveryOrdersService) GetAll(c *fiber.Ctx, params *validation.DeliveryO } func (s deliveryOrdersService) GetOne(c *fiber.Ctx, id uint) (*dto.MarketingDetailDTO, error) { + if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil { + return nil, err + } marketing, err := s.MarketingRepo.GetByID(c.Context(), id, s.withRelations) if errors.Is(err, gorm.ErrRecordNotFound) { @@ -169,6 +193,10 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Delivery return nil, err } + if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), req.MarketingId); err != nil { + return nil, err + } + if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "Marketing", ID: &req.MarketingId, Exists: s.MarketingRepo.IdExists}, ); err != nil { @@ -303,6 +331,10 @@ func (s deliveryOrdersService) UpdateOne(c *fiber.Ctx, req *validation.DeliveryO return nil, err } + if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil { + return nil, err + } + if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "Marketing", ID: &id, Exists: s.MarketingRepo.IdExists}, ); err != nil { diff --git a/internal/modules/marketing/services/salesorder.service.go b/internal/modules/marketing/services/salesorder.service.go index d57b323e..e65fa9bb 100644 --- a/internal/modules/marketing/services/salesorder.service.go +++ b/internal/modules/marketing/services/salesorder.service.go @@ -70,6 +70,10 @@ func (s salesOrdersService) withRelations(db *gorm.DB) *gorm.DB { } func (s salesOrdersService) getOne(c *fiber.Ctx, id uint) (*entity.Marketing, error) { + if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil { + return nil, err + } + marketing, err := s.MarketingRepo.GetByID(c.Context(), id, s.withRelations) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "SalesOrders not found") @@ -108,6 +112,9 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e } for _, item := range req.MarketingProducts { + if err := m.EnsureProductWarehouseAccess(c, s.MarketingRepo.DB(), item.ProductWarehouseId); err != nil { + return nil, err + } if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "ProductWarehouse", ID: &item.ProductWarehouseId, Exists: s.ProductWarehouseRepo.IdExists}, ); err != nil { @@ -197,6 +204,10 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u return nil, err } + if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil { + return nil, err + } + actorID, err := m.ActorIDFromContext(c) if err != nil { return nil, err @@ -219,11 +230,14 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u } if len(req.MarketingProducts) > 0 { - for _, item := range req.MarketingProducts { - if err := commonSvc.EnsureRelations(c.Context(), - commonSvc.RelationCheck{Name: "ProductWarehouse", ID: &item.ProductWarehouseId, Exists: s.ProductWarehouseRepo.IdExists}, - ); err != nil { - return nil, err + for _, item := range req.MarketingProducts { + if err := m.EnsureProductWarehouseAccess(c, s.MarketingRepo.DB(), item.ProductWarehouseId); err != nil { + return nil, err + } + if err := commonSvc.EnsureRelations(c.Context(), + commonSvc.RelationCheck{Name: "ProductWarehouse", ID: &item.ProductWarehouseId, Exists: s.ProductWarehouseRepo.IdExists}, + ); err != nil { + return nil, err } } } @@ -391,6 +405,10 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u } func (s salesOrdersService) DeleteOne(c *fiber.Ctx, id uint) error { + if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil { + return err + } + marketing, err := s.MarketingRepo.GetByID(c.Context(), id, s.withRelations) if errors.Is(err, gorm.ErrRecordNotFound) { @@ -455,6 +473,12 @@ func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]e return nil, err } + for _, id := range req.ApprovableIds { + if err := m.EnsureMarketingAccess(c, s.MarketingRepo.DB(), id); err != nil { + return nil, err + } + } + actorID, err := m.ActorIDFromContext(c) if err != nil { return nil, err diff --git a/internal/modules/master/kandangs/services/kandang.service.go b/internal/modules/master/kandangs/services/kandang.service.go index b70577d6..d5b297e5 100644 --- a/internal/modules/master/kandangs/services/kandang.service.go +++ b/internal/modules/master/kandangs/services/kandang.service.go @@ -103,6 +103,9 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit if err := s.Validate.Struct(req); err != nil { return nil, err } + if err := m.EnsureLocationAccess(c, s.Repository.DB(), req.LocationId); err != nil { + return nil, err + } if exists, err := s.Repository.NameExists(c.Context(), req.Name, nil); err != nil { s.Log.Errorf("Failed to check kandang name: %+v", err) @@ -177,6 +180,14 @@ func (s kandangService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) if err := s.Validate.Struct(req); err != nil { return nil, err } + if err := m.EnsureKandangAccess(c, s.Repository.DB(), id); err != nil { + return nil, err + } + if req.LocationId != nil { + if err := m.EnsureLocationAccess(c, s.Repository.DB(), *req.LocationId); err != nil { + return nil, err + } + } existing, err := s.Repository.GetByID(c.Context(), id, nil) if errors.Is(err, gorm.ErrRecordNotFound) { @@ -268,6 +279,10 @@ func (s kandangService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) } func (s kandangService) DeleteOne(c *fiber.Ctx, id uint) error { + if err := m.EnsureKandangAccess(c, s.Repository.DB(), id); err != nil { + return err + } + if err := s.Repository.DeleteOne(c.Context(), id); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "Kandang not found") diff --git a/internal/modules/master/warehouses/services/warehouse.service.go b/internal/modules/master/warehouses/services/warehouse.service.go index aaa5ca7e..29cf402f 100644 --- a/internal/modules/master/warehouses/services/warehouse.service.go +++ b/internal/modules/master/warehouses/services/warehouse.service.go @@ -128,6 +128,19 @@ func (s *warehouseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent if err := validateWarehouseTypeRequirements(typ, &req.AreaId, req.LocationId, req.KandangId); err != nil { return nil, err } + if err := m.EnsureAreaAccess(c, s.Repository.DB(), req.AreaId); err != nil { + return nil, err + } + if req.LocationId != nil { + if err := m.EnsureLocationAccess(c, s.Repository.DB(), *req.LocationId); err != nil { + return nil, err + } + } + if req.KandangId != nil { + if err := m.EnsureKandangAccess(c, s.Repository.DB(), *req.KandangId); err != nil { + return nil, err + } + } //? Check relation area, location, and kandang if err := common.EnsureRelations(c.Context(), @@ -166,6 +179,21 @@ func (s warehouseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin if err := s.Validate.Struct(req); err != nil { return nil, err } + if req.AreaId != nil { + if err := m.EnsureAreaAccess(c, s.Repository.DB(), *req.AreaId); err != nil { + return nil, err + } + } + if req.LocationId != nil { + if err := m.EnsureLocationAccess(c, s.Repository.DB(), *req.LocationId); err != nil { + return nil, err + } + } + if req.KandangId != nil { + if err := m.EnsureKandangAccess(c, s.Repository.DB(), *req.KandangId); err != nil { + return nil, err + } + } existing, err := s.GetOne(c, id) if err != nil { @@ -243,6 +271,10 @@ func (s warehouseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin } func (s warehouseService) DeleteOne(c *fiber.Ctx, id uint) error { + if err := m.EnsureWarehouseAccess(c, s.Repository.DB(), id); err != nil { + return err + } + if err := s.Repository.DeleteOne(c.Context(), id); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "Warehouse not found") diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index 88ed4cf7..e52aaddc 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -101,6 +101,16 @@ func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti return nil, 0, err } + scope, err := m.ResolveLocationScope(c, s.Repository.DB()) + if err != nil { + return nil, 0, err + } + if params.ProjectFlockKandangId != 0 { + if err := m.EnsureProjectFlockKandangAccess(c, s.Repository.DB(), 0, params.ProjectFlockKandangId); err != nil { + return nil, 0, err + } + } + limit := params.Limit if limit == 0 { limit = 10 @@ -113,6 +123,15 @@ func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti recordings, total, err := s.Repository.GetAll(c.Context(), offset, limit, func(db *gorm.DB) *gorm.DB { db = s.Repository.WithRelations(db) + if scope.Restrict { + if len(scope.IDs) == 0 { + return db.Where("1 = 0") + } + db = db. + Joins("JOIN project_flock_kandangs pfk ON pfk.id = recordings.project_flock_kandangs_id"). + Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id") + db = m.ApplyScopeFilter(db, scope, "pf.location_id") + } if params.ProjectFlockKandangId != 0 { db = db.Where("project_flock_kandangs_id = ?", params.ProjectFlockKandangId) } @@ -133,6 +152,10 @@ func (s recordingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]enti } func (s recordingService) GetOne(c *fiber.Ctx, id uint) (*entity.Recording, error) { + if err := m.EnsureRecordingAccess(c, s.Repository.DB(), id); err != nil { + return nil, err + } + recording, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { return s.Repository.WithRelations(db) }) @@ -156,6 +179,9 @@ func (s recordingService) GetNextDay(c *fiber.Ctx, projectFlockKandangId uint) ( if projectFlockKandangId == 0 { return 0, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id is required") } + if err := m.EnsureProjectFlockKandangAccess(c, s.Repository.DB(), 0, projectFlockKandangId); err != nil { + return 0, err + } db := s.Repository.DB().WithContext(c.Context()) next, err := s.Repository.GenerateNextDay(db, projectFlockKandangId) @@ -171,6 +197,9 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent if err := s.Validate.Struct(req); err != nil { return nil, err } + if err := m.EnsureProjectFlockKandangAccess(c, s.Repository.DB(), 0, req.ProjectFlockKandangId); err != nil { + return nil, err + } ctx := c.Context() recordTime := time.Now().UTC() @@ -320,6 +349,9 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin if err := s.Validate.Struct(req); err != nil { return nil, err } + if err := m.EnsureRecordingAccess(c, s.Repository.DB(), id); err != nil { + return nil, err + } if req.Stocks == nil && req.Depletions == nil && req.Eggs == nil { return s.GetOne(c, id) @@ -535,6 +567,11 @@ func (s recordingService) Approval(c *fiber.Ctx, req *validation.Approve) ([]ent if err := s.Validate.Struct(req); err != nil { return nil, err } + for _, id := range req.ApprovableIds { + if err := m.EnsureRecordingAccess(c, s.Repository.DB(), id); err != nil { + return nil, err + } + } actionValue := strings.ToUpper(strings.TrimSpace(req.Action)) var action entity.ApprovalAction @@ -612,6 +649,9 @@ func (s recordingService) Approval(c *fiber.Ctx, req *validation.Approve) ([]ent } func (s recordingService) DeleteOne(c *fiber.Ctx, id uint) error { + if err := m.EnsureRecordingAccess(c, s.Repository.DB(), id); err != nil { + return err + } ctx := c.Context() return s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { diff --git a/internal/modules/production/transfer_layings/services/transfer_laying.service.go b/internal/modules/production/transfer_layings/services/transfer_laying.service.go index 9732ad75..bcafd489 100644 --- a/internal/modules/production/transfer_layings/services/transfer_laying.service.go +++ b/internal/modules/production/transfer_layings/services/transfer_laying.service.go @@ -107,10 +107,25 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([ 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 transferLayings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { db = s.withRelations(db) + if scope.Restrict { + if len(scope.IDs) == 0 { + return db.Where("1 = 0") + } + db = db. + Joins("JOIN project_flocks pf_from ON pf_from.id = laying_transfers.from_project_flock_id"). + Joins("JOIN project_flocks pf_to ON pf_to.id = laying_transfers.to_project_flock_id"). + Where("(pf_from.location_id IN ? OR pf_to.location_id IN ?)", scope.IDs, scope.IDs). + Distinct("laying_transfers.*") + } db = db.Order("created_at DESC") return db }) @@ -134,6 +149,10 @@ func (s transferLayingService) GetAll(c *fiber.Ctx, params *validation.Query) ([ } func (s transferLayingService) GetOne(c *fiber.Ctx, id uint) (*entity.LayingTransfer, error) { + if err := m.EnsureLayingTransferAccess(c, s.Repository.DB(), id); err != nil { + return nil, err + } + transferLaying, err := s.Repository.GetByID(c.Context(), id, s.withRelations) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "TransferLaying not found") @@ -167,6 +186,12 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create) if err := s.Validate.Struct(req); err != nil { 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 { + return nil, err + } actorID, err := m.ActorIDFromContext(c) if err != nil { @@ -375,6 +400,15 @@ func (s *transferLayingService) UpdateOne(c *fiber.Ctx, req *validation.Update, if err := s.Validate.Struct(req); err != nil { return nil, err } + if err := m.EnsureLayingTransferAccess(c, s.Repository.DB(), id); err != nil { + 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 { + return nil, err + } existingTransfer, err := s.Repository.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { return db.Preload("Sources.ProductWarehouse").Preload("Targets") diff --git a/internal/modules/production/uniformities/repositories/uniformity.repository.go b/internal/modules/production/uniformities/repositories/uniformity.repository.go index 9641c650..8453bf84 100644 --- a/internal/modules/production/uniformities/repositories/uniformity.repository.go +++ b/internal/modules/production/uniformities/repositories/uniformity.repository.go @@ -12,7 +12,7 @@ import ( type UniformityRepository interface { repository.BaseRepository[entity.ProjectFlockKandangUniformity] - GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlockKandangUniformity, int64, error) + GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query, modifiers ...func(*gorm.DB) *gorm.DB) ([]entity.ProjectFlockKandangUniformity, int64, error) WithDefaultRelations() func(*gorm.DB) *gorm.DB DeleteByProjectFlockKandangIDs(ctx context.Context, projectFlockKandangIDs []uint) error } @@ -27,9 +27,15 @@ func NewUniformityRepository(db *gorm.DB) UniformityRepository { } } -func (r *UniformityRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlockKandangUniformity, int64, error) { +func (r *UniformityRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query, modifiers ...func(*gorm.DB) *gorm.DB) ([]entity.ProjectFlockKandangUniformity, int64, error) { return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB { - return r.applyQueryFilters(r.WithDefaultRelations()(db), params) + db = r.applyQueryFilters(r.WithDefaultRelations()(db), params) + for _, modifier := range modifiers { + if modifier != nil { + db = modifier(db) + } + } + return db }) } diff --git a/internal/modules/production/uniformities/services/uniformity.service.go b/internal/modules/production/uniformities/services/uniformity.service.go index 92db84a3..51ea29a7 100644 --- a/internal/modules/production/uniformities/services/uniformity.service.go +++ b/internal/modules/production/uniformities/services/uniformity.service.go @@ -87,8 +87,24 @@ func (s uniformityService) GetAll(c *fiber.Ctx, params *validation.Query) ([]ent 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 - uniformitys, total, err := s.Repository.GetAllWithFilters(c.Context(), offset, params.Limit, params) + uniformitys, total, err := s.Repository.GetAllWithFilters(c.Context(), offset, params.Limit, params, func(db *gorm.DB) *gorm.DB { + if scope.Restrict { + if len(scope.IDs) == 0 { + return db.Where("1 = 0") + } + db = db. + Joins("JOIN project_flock_kandangs pfk ON pfk.id = project_flock_kandang_uniformities.project_flock_kandang_id"). + Joins("JOIN project_flocks pf ON pf.id = pfk.project_flock_id") + db = m.ApplyScopeFilter(db, scope, "pf.location_id") + } + return db + }) if err != nil { s.Log.Errorf("Failed to get uniformitys: %+v", err) @@ -101,6 +117,10 @@ func (s uniformityService) GetAll(c *fiber.Ctx, params *validation.Query) ([]ent } func (s uniformityService) GetOne(c *fiber.Ctx, id uint) (*entity.ProjectFlockKandangUniformity, error) { + if err := m.EnsureUniformityAccess(c, s.Repository.DB(), id); err != nil { + return nil, err + } + uniformity, err := s.Repository.GetByID(c.Context(), id, s.Repository.WithDefaultRelations()) if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Uniformity not found") @@ -326,6 +346,9 @@ func (s *uniformityService) CreateOne(c *fiber.Ctx, req *validation.Create, file if err := s.Validate.Struct(req); err != nil { return nil, err } + if err := m.EnsureProjectFlockKandangAccess(c, s.Repository.DB(), 0, req.ProjectFlockKandangId); err != nil { + return nil, err + } if s.ProjectFlockKandangRepo == nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "Project flock kandang repository not available") } @@ -439,6 +462,9 @@ func (s uniformityService) UpdateOne(c *fiber.Ctx, req *validation.Update, id ui if err := s.Validate.Struct(req); err != nil { return nil, err } + if err := m.EnsureUniformityAccess(c, s.Repository.DB(), id); err != nil { + return nil, err + } updateBody := make(map[string]any) var uniformDate *time.Time @@ -627,6 +653,10 @@ func (s *uniformityService) ensureUniqueUniformity(ctx context.Context, id uint, } func (s uniformityService) DeleteOne(c *fiber.Ctx, id uint) error { + if err := m.EnsureUniformityAccess(c, s.Repository.DB(), id); err != nil { + return err + } + if err := s.Repository.DeleteOne(c.Context(), id); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "Uniformity not found") @@ -657,6 +687,11 @@ func (s uniformityService) Approval(c *fiber.Ctx, req *validation.Approve) ([]en if len(ids) == 0 { return nil, fiber.NewError(fiber.StatusBadRequest, "approvable_ids must contain at least one id") } + for _, id := range ids { + if err := m.EnsureUniformityAccess(c, s.Repository.DB(), id); err != nil { + return nil, err + } + } step := utils.UniformityStepPengajuan if action == entity.ApprovalActionApproved { diff --git a/internal/modules/repports/controllers/repport.controller.go b/internal/modules/repports/controllers/repport.controller.go index 4ed7a855..3d0468d4 100644 --- a/internal/modules/repports/controllers/repport.controller.go +++ b/internal/modules/repports/controllers/repport.controller.go @@ -106,6 +106,18 @@ func (c *RepportController) GetMarketing(ctx *fiber.Ctx) error { SortOrder: ctx.Query("sort_order", ""), } + locationScope, err := m.ResolveLocationScope(ctx, c.RepportService.DB()) + if err != nil { + return err + } + if locationScope.Restrict { + allowed := toInt64Slice(locationScope.IDs) + if len(allowed) == 0 { + allowed = []int64{-1} + } + query.AllowedLocationIDs = allowed + } + if query.Page < 1 || query.Limit < 1 { return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") } @@ -283,6 +295,10 @@ func (c *RepportController) GetProductionResult(ctx *fiber.Ctx) error { ProjectFlockKandangID: uint(projectFlockKandangID), } + if err := m.EnsureProjectFlockKandangAccess(ctx, c.RepportService.DB(), 0, query.ProjectFlockKandangID); err != nil { + return err + } + if query.Page < 1 || query.Limit < 1 { return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") } diff --git a/internal/modules/repports/validations/repport.validation.go b/internal/modules/repports/validations/repport.validation.go index 4c1d7356..c5572edb 100644 --- a/internal/modules/repports/validations/repport.validation.go +++ b/internal/modules/repports/validations/repport.validation.go @@ -31,6 +31,7 @@ type MarketingQuery struct { 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"` SortOrder string `query:"sort_order" validate:"omitempty,oneof=asc desc"` + AllowedLocationIDs []int64 `query:"-"` } type PurchaseSupplierQuery struct { diff --git a/internal/modules/sso/verifier/profile.go b/internal/modules/sso/verifier/profile.go index 52b7ef5a..e3cd40ca 100644 --- a/internal/modules/sso/verifier/profile.go +++ b/internal/modules/sso/verifier/profile.go @@ -39,6 +39,10 @@ type UserProfile struct { UserID uint Roles []Role Permissions []Permission + AreaIDs []uint + LocationIDs []uint + AllArea bool + AllLocation bool } // Role describes a role assignment from the SSO profile response. @@ -49,10 +53,6 @@ type Role struct { ClientID uint ClientAlias string ClientName string - AllArea bool - AllLocation bool - AreaIDs []uint - LocationIDs []uint Permissions []Permission RawReference json.RawMessage `json:"-"` } @@ -149,6 +149,10 @@ func fetchProfileFromSSO(ctx context.Context, token string) (*UserProfile, error } roles := envelope.getRoles() + areaIDs := envelope.getAreaIDs() + locationIDs := envelope.getLocationIDs() + allArea := envelope.getAllArea() + allLocation := envelope.getAllLocation() profile := &UserProfile{} // Attempt to infer user id if provided. @@ -166,10 +170,6 @@ func fetchProfileFromSSO(ctx context.Context, token string) (*UserProfile, error ClientAlias: strings.TrimSpace(r.Client.Alias), ClientName: strings.TrimSpace(r.Client.Name), ClientID: uint(r.Client.ID), - AllArea: r.AllArea, - AllLocation: r.AllLocation, - AreaIDs: r.AreaIDs, - LocationIDs: r.LocationIDs, } rolePerms := make([]Permission, 0, len(r.Permissions)) for _, p := range r.Permissions { @@ -191,6 +191,10 @@ func fetchProfileFromSSO(ctx context.Context, token string) (*UserProfile, error } profile.Roles = convertedRoles profile.Permissions = perms + profile.AreaIDs = areaIDs + profile.LocationIDs = locationIDs + profile.AllArea = allArea + profile.AllLocation = allLocation return profile, nil } @@ -268,9 +272,17 @@ func canonicalPermissionName(name string) string { // userInfoEnvelope handles the varying shapes returned by the SSO userinfo endpoint. type userInfoEnvelope struct { Roles []userInfoRole `json:"roles"` + AreaIDs []uint `json:"area_ids"` + LocationIDs []uint `json:"location_ids"` + AllArea bool `json:"all_area"` + AllLocation bool `json:"all_location"` Data *struct { ID int64 `json:"id"` Roles []userInfoRole `json:"roles"` + AreaIDs []uint `json:"area_ids"` + LocationIDs []uint `json:"location_ids"` + AllArea bool `json:"all_area"` + AllLocation bool `json:"all_location"` } `json:"data"` User *struct { ID int64 `json:"id"` @@ -292,14 +304,50 @@ func (e *userInfoEnvelope) getRoles() []userInfoRole { return nil } +func (e *userInfoEnvelope) getAreaIDs() []uint { + if len(e.AreaIDs) > 0 { + return e.AreaIDs + } + if e.Data != nil && len(e.Data.AreaIDs) > 0 { + return e.Data.AreaIDs + } + return nil +} + +func (e *userInfoEnvelope) getLocationIDs() []uint { + if len(e.LocationIDs) > 0 { + return e.LocationIDs + } + if e.Data != nil && len(e.Data.LocationIDs) > 0 { + return e.Data.LocationIDs + } + return nil +} + +func (e *userInfoEnvelope) getAllArea() bool { + if e.AllArea { + return true + } + if e.Data != nil && e.Data.AllArea { + return true + } + return false +} + +func (e *userInfoEnvelope) getAllLocation() bool { + if e.AllLocation { + return true + } + if e.Data != nil && e.Data.AllLocation { + return true + } + return false +} + type userInfoRole struct { ID int64 `json:"id"` Key string `json:"key"` Name string `json:"name"` - AllArea bool `json:"all_area"` - AllLocation bool `json:"all_location"` - AreaIDs []uint `json:"area_ids"` - LocationIDs []uint `json:"location_ids"` Client userInfoClient `json:"client"` Permissions []userInfoPermRaw `json:"permissions"` }