Add finding service layer
Introduce FindingService with create, update, delete, get, and list operations including audit association management. Update the probo service orchestration, actions, and policies to use findings. Remove the old NonconformityService and ContinualImprovementService. Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -242,12 +242,14 @@ const (
|
||||
ActionReportGetReportUrl = "core:report:get-report-url"
|
||||
ActionReportDownloadUrlGet = "core:report:get-download-url"
|
||||
|
||||
// Nonconformity actions
|
||||
ActionNonconformityGet = "core:nonconformity:get"
|
||||
ActionNonconformityList = "core:nonconformity:list"
|
||||
ActionNonconformityCreate = "core:nonconformity:create"
|
||||
ActionNonconformityUpdate = "core:nonconformity:update"
|
||||
ActionNonconformityDelete = "core:nonconformity:delete"
|
||||
// Finding actions
|
||||
ActionFindingGet = "core:finding:get"
|
||||
ActionFindingList = "core:finding:list"
|
||||
ActionFindingCreate = "core:finding:create"
|
||||
ActionFindingUpdate = "core:finding:update"
|
||||
ActionFindingDelete = "core:finding:delete"
|
||||
ActionFindingAuditMappingCreate = "core:finding:create-audit-mapping"
|
||||
ActionFindingAuditMappingDelete = "core:finding:delete-audit-mapping"
|
||||
|
||||
// Obligation actions
|
||||
ActionObligationGet = "core:obligation:get"
|
||||
@@ -256,13 +258,6 @@ const (
|
||||
ActionObligationUpdate = "core:obligation:update"
|
||||
ActionObligationDelete = "core:obligation:delete"
|
||||
|
||||
// ContinualImprovement actions
|
||||
ActionContinualImprovementGet = "core:continual-improvement:get"
|
||||
ActionContinualImprovementList = "core:continual-improvement:list"
|
||||
ActionContinualImprovementCreate = "core:continual-improvement:create"
|
||||
ActionContinualImprovementUpdate = "core:continual-improvement:update"
|
||||
ActionContinualImprovementDelete = "core:continual-improvement:delete"
|
||||
|
||||
// ProcessingActivity actions
|
||||
ActionProcessingActivityList = "core:processing-activity:list"
|
||||
ActionProcessingActivityGet = "core:processing-activity:get"
|
||||
|
||||
@@ -485,3 +485,86 @@ func (s AuditService) ListForControlID(
|
||||
|
||||
return page.NewPage(audits, cursor), nil
|
||||
}
|
||||
|
||||
func (s AuditService) CountForControlID(
|
||||
ctx context.Context,
|
||||
controlID gid.GID,
|
||||
) (int, error) {
|
||||
var count int
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
audits := coredata.Audits{}
|
||||
count, err = audits.CountByControlID(ctx, conn, s.svc.scope, controlID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count audits: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s AuditService) CountForFindingID(
|
||||
ctx context.Context,
|
||||
findingID gid.GID,
|
||||
) (int, error) {
|
||||
var count int
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
audits := coredata.Audits{}
|
||||
count, err = audits.CountByFindingID(ctx, conn, s.svc.scope, findingID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count audits: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s AuditService) ListForFindingID(
|
||||
ctx context.Context,
|
||||
findingID gid.GID,
|
||||
cursor *page.Cursor[coredata.AuditOrderField],
|
||||
) (*page.Page[*coredata.Audit, coredata.AuditOrderField], error) {
|
||||
var audits coredata.Audits
|
||||
finding := &coredata.Finding{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := finding.LoadByID(ctx, conn, s.svc.scope, findingID); err != nil {
|
||||
return fmt.Errorf("cannot load finding: %w", err)
|
||||
}
|
||||
|
||||
err := audits.LoadByFindingID(ctx, conn, s.svc.scope, finding.ID, cursor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load audits: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(audits, cursor), nil
|
||||
}
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package probo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/validator"
|
||||
)
|
||||
|
||||
type ContinualImprovementService struct {
|
||||
svc *TenantService
|
||||
}
|
||||
|
||||
type (
|
||||
CreateContinualImprovementRequest struct {
|
||||
OrganizationID gid.GID
|
||||
ReferenceID string
|
||||
Description *string
|
||||
Source *string
|
||||
OwnerID gid.GID
|
||||
TargetDate *time.Time
|
||||
Status *coredata.ContinualImprovementStatus
|
||||
Priority *coredata.ContinualImprovementPriority
|
||||
}
|
||||
|
||||
UpdateContinualImprovementRequest struct {
|
||||
ID gid.GID
|
||||
ReferenceID *string
|
||||
Description **string
|
||||
Source **string
|
||||
OwnerID *gid.GID
|
||||
TargetDate **time.Time
|
||||
Status *coredata.ContinualImprovementStatus
|
||||
Priority *coredata.ContinualImprovementPriority
|
||||
}
|
||||
)
|
||||
|
||||
func (ccir *CreateContinualImprovementRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(ccir.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||
v.Check(ccir.ReferenceID, "reference_id", validator.SafeText(NameMaxLength))
|
||||
v.Check(ccir.Description, "description", validator.SafeText(ContentMaxLength))
|
||||
v.Check(ccir.Source, "source", validator.SafeText(ContentMaxLength))
|
||||
v.Check(ccir.OwnerID, "owner_id", validator.Required(), validator.GID(coredata.MembershipProfileEntityType))
|
||||
v.Check(ccir.Status, "status", validator.OneOfSlice(coredata.ContinualImprovementStatuses()))
|
||||
v.Check(ccir.Priority, "priority", validator.OneOfSlice(coredata.ContinualImprovementPriorities()))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (ucir *UpdateContinualImprovementRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(ucir.ID, "id", validator.Required(), validator.GID(coredata.ContinualImprovementEntityType))
|
||||
v.Check(ucir.ReferenceID, "reference_id", validator.SafeText(NameMaxLength))
|
||||
v.Check(ucir.Description, "description", validator.SafeText(ContentMaxLength))
|
||||
v.Check(ucir.Source, "source", validator.SafeText(ContentMaxLength))
|
||||
v.Check(ucir.OwnerID, "owner_id", validator.GID(coredata.MembershipProfileEntityType))
|
||||
v.Check(ucir.Status, "status", validator.OneOfSlice(coredata.ContinualImprovementStatuses()))
|
||||
v.Check(ucir.Priority, "priority", validator.OneOfSlice(coredata.ContinualImprovementPriorities()))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (s ContinualImprovementService) Get(
|
||||
ctx context.Context,
|
||||
continualImprovementID gid.GID,
|
||||
) (*coredata.ContinualImprovement, error) {
|
||||
improvement := &coredata.ContinualImprovement{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := improvement.LoadByID(ctx, conn, s.svc.scope, continualImprovementID); err != nil {
|
||||
return fmt.Errorf("cannot load continual improvement: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return improvement, nil
|
||||
}
|
||||
|
||||
func (s *ContinualImprovementService) Create(
|
||||
ctx context.Context,
|
||||
req *CreateContinualImprovementRequest,
|
||||
) (*coredata.ContinualImprovement, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
improvement := &coredata.ContinualImprovement{
|
||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.ContinualImprovementEntityType),
|
||||
OrganizationID: req.OrganizationID,
|
||||
ReferenceID: req.ReferenceID,
|
||||
Description: req.Description,
|
||||
Source: req.Source,
|
||||
OwnerID: req.OwnerID,
|
||||
TargetDate: req.TargetDate,
|
||||
Status: *req.Status,
|
||||
Priority: *req.Priority,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
organization := &coredata.Organization{}
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, req.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
owner := &coredata.MembershipProfile{}
|
||||
if err := owner.LoadByID(ctx, conn, s.svc.scope, req.OwnerID); err != nil {
|
||||
return fmt.Errorf("cannot load owner profile: %w", err)
|
||||
}
|
||||
|
||||
if err := improvement.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert continual improvement: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return improvement, nil
|
||||
}
|
||||
|
||||
func (s *ContinualImprovementService) Update(
|
||||
ctx context.Context,
|
||||
req *UpdateContinualImprovementRequest,
|
||||
) (*coredata.ContinualImprovement, error) {
|
||||
improvement := &coredata.ContinualImprovement{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := improvement.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load continual improvement: %w", err)
|
||||
}
|
||||
|
||||
if req.ReferenceID != nil {
|
||||
improvement.ReferenceID = *req.ReferenceID
|
||||
}
|
||||
|
||||
if req.Description != nil {
|
||||
improvement.Description = *req.Description
|
||||
}
|
||||
|
||||
if req.Source != nil {
|
||||
improvement.Source = *req.Source
|
||||
}
|
||||
|
||||
if req.OwnerID != nil {
|
||||
owner := &coredata.MembershipProfile{}
|
||||
if err := owner.LoadByID(ctx, conn, s.svc.scope, *req.OwnerID); err != nil {
|
||||
return fmt.Errorf("cannot load owner profile: %w", err)
|
||||
}
|
||||
improvement.OwnerID = *req.OwnerID
|
||||
}
|
||||
|
||||
if req.TargetDate != nil {
|
||||
improvement.TargetDate = *req.TargetDate
|
||||
}
|
||||
|
||||
if req.Status != nil {
|
||||
improvement.Status = *req.Status
|
||||
}
|
||||
|
||||
if req.Priority != nil {
|
||||
improvement.Priority = *req.Priority
|
||||
}
|
||||
|
||||
improvement.UpdatedAt = time.Now()
|
||||
|
||||
if err := improvement.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update continual improvement: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return improvement, nil
|
||||
}
|
||||
|
||||
func (s *ContinualImprovementService) Delete(
|
||||
ctx context.Context,
|
||||
continualImprovementID gid.GID,
|
||||
) error {
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
improvement := &coredata.ContinualImprovement{}
|
||||
if err := improvement.LoadByID(ctx, conn, s.svc.scope, continualImprovementID); err != nil {
|
||||
return fmt.Errorf("cannot load continual improvement: %w", err)
|
||||
}
|
||||
|
||||
if err := improvement.Delete(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot delete continual improvement: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s ContinualImprovementService) CountByOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
filter *coredata.ContinualImprovementFilter,
|
||||
) (int, error) {
|
||||
var count int
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
improvements := coredata.ContinualImprovements{}
|
||||
count, err = improvements.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count continual improvements: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s ContinualImprovementService) ListForOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[coredata.ContinualImprovementOrderField],
|
||||
filter *coredata.ContinualImprovementFilter,
|
||||
) (*page.Page[*coredata.ContinualImprovement, coredata.ContinualImprovementOrderField], error) {
|
||||
var improvements coredata.ContinualImprovements
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := improvements.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load continual improvements: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(improvements, cursor), nil
|
||||
}
|
||||
476
pkg/probo/finding_service.go
Normal file
476
pkg/probo/finding_service.go
Normal file
@@ -0,0 +1,476 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package probo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/validator"
|
||||
)
|
||||
|
||||
type FindingService struct {
|
||||
svc *TenantService
|
||||
}
|
||||
|
||||
type (
|
||||
CreateFindingRequest struct {
|
||||
OrganizationID gid.GID
|
||||
Kind coredata.FindingKind
|
||||
Description *string
|
||||
Source *string
|
||||
IdentifiedOn *time.Time
|
||||
RootCause *string
|
||||
CorrectiveAction *string
|
||||
OwnerID *gid.GID
|
||||
DueDate *time.Time
|
||||
Status *coredata.FindingStatus
|
||||
Priority *coredata.FindingPriority
|
||||
RiskID *gid.GID
|
||||
EffectivenessCheck *string
|
||||
}
|
||||
|
||||
UpdateFindingRequest struct {
|
||||
ID gid.GID
|
||||
Description **string
|
||||
Source **string
|
||||
IdentifiedOn **time.Time
|
||||
RootCause **string
|
||||
CorrectiveAction **string
|
||||
OwnerID *gid.GID
|
||||
DueDate **time.Time
|
||||
Status *coredata.FindingStatus
|
||||
Priority *coredata.FindingPriority
|
||||
RiskID **gid.GID
|
||||
EffectivenessCheck **string
|
||||
}
|
||||
)
|
||||
|
||||
func (r *CreateFindingRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(r.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||
v.Check(r.Kind, "kind", validator.Required(), validator.OneOfSlice(coredata.FindingKinds()))
|
||||
v.Check(r.Description, "description", validator.SafeText(ContentMaxLength))
|
||||
v.Check(r.Source, "source", validator.SafeText(ContentMaxLength))
|
||||
v.Check(r.RootCause, "root_cause", validator.SafeText(ContentMaxLength))
|
||||
v.Check(r.CorrectiveAction, "corrective_action", validator.SafeText(ContentMaxLength))
|
||||
v.Check(r.OwnerID, "owner_id", validator.GID(coredata.MembershipProfileEntityType))
|
||||
v.Check(r.Status, "status", validator.OneOfSlice(coredata.FindingStatuses()))
|
||||
v.Check(r.Priority, "priority", validator.OneOfSlice(coredata.FindingPriorities()))
|
||||
v.Check(r.RiskID, "risk_id", validator.GID(coredata.RiskEntityType))
|
||||
v.Check(r.EffectivenessCheck, "effectiveness_check", validator.SafeText(ContentMaxLength))
|
||||
|
||||
if r.Status != nil && *r.Status == coredata.FindingStatusRiskAccepted {
|
||||
v.Check(r.RiskID, "risk_id", validator.Required())
|
||||
}
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (r *UpdateFindingRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(r.ID, "id", validator.Required(), validator.GID(coredata.FindingEntityType))
|
||||
v.Check(r.Description, "description", validator.SafeText(ContentMaxLength))
|
||||
v.Check(r.Source, "source", validator.SafeText(ContentMaxLength))
|
||||
v.Check(r.RootCause, "root_cause", validator.SafeText(ContentMaxLength))
|
||||
v.Check(r.CorrectiveAction, "corrective_action", validator.SafeText(ContentMaxLength))
|
||||
v.Check(r.OwnerID, "owner_id", validator.GID(coredata.MembershipProfileEntityType))
|
||||
v.Check(r.Status, "status", validator.OneOfSlice(coredata.FindingStatuses()))
|
||||
v.Check(r.Priority, "priority", validator.OneOfSlice(coredata.FindingPriorities()))
|
||||
v.Check(r.RiskID, "risk_id", validator.GID(coredata.RiskEntityType))
|
||||
v.Check(r.EffectivenessCheck, "effectiveness_check", validator.SafeText(ContentMaxLength))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (s FindingService) Get(
|
||||
ctx context.Context,
|
||||
findingID gid.GID,
|
||||
) (*coredata.Finding, error) {
|
||||
finding := &coredata.Finding{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return finding.LoadByID(ctx, conn, s.svc.scope, findingID)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get finding: %w", err)
|
||||
}
|
||||
|
||||
return finding, nil
|
||||
}
|
||||
|
||||
func (s *FindingService) Create(
|
||||
ctx context.Context,
|
||||
req *CreateFindingRequest,
|
||||
) (*coredata.Finding, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
finding := &coredata.Finding{
|
||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.FindingEntityType),
|
||||
OrganizationID: req.OrganizationID,
|
||||
Kind: req.Kind,
|
||||
Description: req.Description,
|
||||
Source: req.Source,
|
||||
IdentifiedOn: req.IdentifiedOn,
|
||||
RootCause: req.RootCause,
|
||||
CorrectiveAction: req.CorrectiveAction,
|
||||
OwnerID: req.OwnerID,
|
||||
DueDate: req.DueDate,
|
||||
Status: coredata.FindingStatusOpen,
|
||||
Priority: coredata.FindingPriorityMedium,
|
||||
RiskID: req.RiskID,
|
||||
EffectivenessCheck: req.EffectivenessCheck,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if req.Status != nil {
|
||||
finding.Status = *req.Status
|
||||
}
|
||||
|
||||
if req.Priority != nil {
|
||||
finding.Priority = *req.Priority
|
||||
}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
organization := &coredata.Organization{}
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, req.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
if req.OwnerID != nil {
|
||||
owner := &coredata.MembershipProfile{}
|
||||
if err := owner.LoadByID(ctx, conn, s.svc.scope, *req.OwnerID); err != nil {
|
||||
return fmt.Errorf("cannot load owner profile: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := finding.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert finding: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return finding, nil
|
||||
}
|
||||
|
||||
func (s *FindingService) Update(
|
||||
ctx context.Context,
|
||||
req *UpdateFindingRequest,
|
||||
) (*coredata.Finding, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
finding := &coredata.Finding{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := finding.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load finding: %w", err)
|
||||
}
|
||||
|
||||
if req.Description != nil {
|
||||
finding.Description = *req.Description
|
||||
}
|
||||
if req.Source != nil {
|
||||
finding.Source = *req.Source
|
||||
}
|
||||
if req.IdentifiedOn != nil {
|
||||
finding.IdentifiedOn = *req.IdentifiedOn
|
||||
}
|
||||
if req.RootCause != nil {
|
||||
finding.RootCause = *req.RootCause
|
||||
}
|
||||
if req.CorrectiveAction != nil {
|
||||
finding.CorrectiveAction = *req.CorrectiveAction
|
||||
}
|
||||
if req.OwnerID != nil {
|
||||
owner := &coredata.MembershipProfile{}
|
||||
if err := owner.LoadByID(ctx, conn, s.svc.scope, *req.OwnerID); err != nil {
|
||||
return fmt.Errorf("cannot load owner profile: %w", err)
|
||||
}
|
||||
finding.OwnerID = req.OwnerID
|
||||
}
|
||||
if req.DueDate != nil {
|
||||
finding.DueDate = *req.DueDate
|
||||
}
|
||||
if req.Status != nil {
|
||||
finding.Status = *req.Status
|
||||
}
|
||||
if req.Priority != nil {
|
||||
finding.Priority = *req.Priority
|
||||
}
|
||||
if req.RiskID != nil {
|
||||
finding.RiskID = *req.RiskID
|
||||
}
|
||||
if req.EffectivenessCheck != nil {
|
||||
finding.EffectivenessCheck = *req.EffectivenessCheck
|
||||
}
|
||||
|
||||
if finding.Status == coredata.FindingStatusRiskAccepted && finding.RiskID == nil {
|
||||
return fmt.Errorf("cannot update finding: risk_id is required when status is RISK_ACCEPTED")
|
||||
}
|
||||
|
||||
finding.UpdatedAt = time.Now()
|
||||
|
||||
if err := finding.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update finding: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return finding, nil
|
||||
}
|
||||
|
||||
func (s FindingService) Delete(
|
||||
ctx context.Context,
|
||||
findingID gid.GID,
|
||||
) error {
|
||||
finding := coredata.Finding{ID: findingID}
|
||||
return s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := finding.Delete(ctx, conn, s.svc.scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete finding: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s FindingService) ListForOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[coredata.FindingOrderField],
|
||||
filter *coredata.FindingFilter,
|
||||
) (*page.Page[*coredata.Finding, coredata.FindingOrderField], error) {
|
||||
var findings coredata.Findings
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := findings.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load findings: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(findings, cursor), nil
|
||||
}
|
||||
|
||||
func (s FindingService) CountForOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
filter *coredata.FindingFilter,
|
||||
) (int, error) {
|
||||
var count int
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
findings := coredata.Findings{}
|
||||
count, err = findings.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count findings: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (s FindingService) CreateAuditMapping(
|
||||
ctx context.Context,
|
||||
findingID gid.GID,
|
||||
auditID gid.GID,
|
||||
referenceID string,
|
||||
) (*coredata.Finding, *coredata.Audit, error) {
|
||||
finding := &coredata.Finding{}
|
||||
audit := &coredata.Audit{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := finding.LoadByID(ctx, conn, s.svc.scope, findingID); err != nil {
|
||||
return fmt.Errorf("cannot load finding: %w", err)
|
||||
}
|
||||
|
||||
if err := audit.LoadByID(ctx, conn, s.svc.scope, auditID); err != nil {
|
||||
return fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
if finding.OrganizationID != audit.OrganizationID {
|
||||
return fmt.Errorf("cannot create finding audit mapping: finding and audit belong to different organizations")
|
||||
}
|
||||
|
||||
findingAudit := &coredata.FindingAudit{
|
||||
FindingID: findingID,
|
||||
AuditID: auditID,
|
||||
ReferenceID: referenceID,
|
||||
OrganizationID: finding.OrganizationID,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := findingAudit.Upsert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot create finding audit mapping: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return finding, audit, nil
|
||||
}
|
||||
|
||||
func (s FindingService) DeleteAuditMapping(
|
||||
ctx context.Context,
|
||||
findingID gid.GID,
|
||||
auditID gid.GID,
|
||||
) (*coredata.Finding, *coredata.Audit, error) {
|
||||
finding := &coredata.Finding{}
|
||||
audit := &coredata.Audit{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := finding.LoadByID(ctx, conn, s.svc.scope, findingID); err != nil {
|
||||
return fmt.Errorf("cannot load finding: %w", err)
|
||||
}
|
||||
|
||||
if err := audit.LoadByID(ctx, conn, s.svc.scope, auditID); err != nil {
|
||||
return fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
findingAudit := &coredata.FindingAudit{}
|
||||
if err := findingAudit.Delete(ctx, conn, s.svc.scope, finding.ID, audit.ID); err != nil {
|
||||
return fmt.Errorf("cannot delete finding audit mapping: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot delete finding audit mapping: %w", err)
|
||||
}
|
||||
|
||||
return finding, audit, nil
|
||||
}
|
||||
|
||||
func (s FindingService) ListForAuditID(
|
||||
ctx context.Context,
|
||||
auditID gid.GID,
|
||||
cursor *page.Cursor[coredata.FindingOrderField],
|
||||
filter *coredata.FindingFilter,
|
||||
) (*page.Page[*coredata.Finding, coredata.FindingOrderField], error) {
|
||||
var findings coredata.Findings
|
||||
audit := &coredata.Audit{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := audit.LoadByID(ctx, conn, s.svc.scope, auditID); err != nil {
|
||||
return fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
if err := findings.LoadByAuditID(ctx, conn, s.svc.scope, auditID, cursor, filter); err != nil {
|
||||
return fmt.Errorf("cannot load findings: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage([]*coredata.Finding(findings), cursor), nil
|
||||
}
|
||||
|
||||
func (s FindingService) CountForAuditID(
|
||||
ctx context.Context,
|
||||
auditID gid.GID,
|
||||
filter *coredata.FindingFilter,
|
||||
) (int, error) {
|
||||
var count int
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
findings := coredata.Findings{}
|
||||
count, err = findings.CountByAuditID(ctx, conn, s.svc.scope, auditID, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count findings: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
@@ -1,323 +0,0 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package probo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/validator"
|
||||
)
|
||||
|
||||
type NonconformityService struct {
|
||||
svc *TenantService
|
||||
}
|
||||
|
||||
type (
|
||||
CreateNonconformityRequest struct {
|
||||
OrganizationID gid.GID
|
||||
ReferenceID string
|
||||
Description *string
|
||||
AuditID *gid.GID
|
||||
DateIdentified *time.Time
|
||||
RootCause string
|
||||
CorrectiveAction *string
|
||||
OwnerID gid.GID
|
||||
DueDate *time.Time
|
||||
Status *coredata.NonconformityStatus
|
||||
EffectivenessCheck *string
|
||||
}
|
||||
|
||||
UpdateNonconformityRequest struct {
|
||||
ID gid.GID
|
||||
ReferenceID *string
|
||||
Description **string
|
||||
DateIdentified **time.Time
|
||||
RootCause *string
|
||||
CorrectiveAction **string
|
||||
OwnerID *gid.GID
|
||||
AuditID **gid.GID
|
||||
DueDate **time.Time
|
||||
Status *coredata.NonconformityStatus
|
||||
EffectivenessCheck **string
|
||||
}
|
||||
)
|
||||
|
||||
func (cnr *CreateNonconformityRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(cnr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||
v.Check(cnr.ReferenceID, "reference_id", validator.Required(), validator.SafeText(NameMaxLength))
|
||||
v.Check(cnr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||
v.Check(cnr.AuditID, "audit_id", validator.GID(coredata.AuditEntityType))
|
||||
v.Check(cnr.RootCause, "root_cause", validator.Required(), validator.SafeText(ContentMaxLength))
|
||||
v.Check(cnr.CorrectiveAction, "corrective_action", validator.SafeText(ContentMaxLength))
|
||||
v.Check(cnr.OwnerID, "owner_id", validator.Required(), validator.GID(coredata.MembershipProfileEntityType))
|
||||
v.Check(cnr.Status, "status", validator.OneOfSlice(coredata.NonconformityStatuses()))
|
||||
v.Check(cnr.EffectivenessCheck, "effectiveness_check", validator.SafeText(ContentMaxLength))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (unr *UpdateNonconformityRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(unr.ID, "id", validator.Required(), validator.GID(coredata.NonconformityEntityType))
|
||||
v.Check(unr.ReferenceID, "reference_id", validator.SafeText(NameMaxLength))
|
||||
v.Check(unr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||
v.Check(unr.RootCause, "root_cause", validator.SafeText(ContentMaxLength))
|
||||
v.Check(unr.CorrectiveAction, "corrective_action", validator.SafeText(ContentMaxLength))
|
||||
v.Check(unr.OwnerID, "owner_id", validator.GID(coredata.MembershipProfileEntityType))
|
||||
v.Check(unr.Status, "status", validator.OneOfSlice(coredata.NonconformityStatuses()))
|
||||
v.Check(unr.EffectivenessCheck, "effectiveness_check", validator.SafeText(ContentMaxLength))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
func (s NonconformityService) Get(
|
||||
ctx context.Context,
|
||||
nonconformityID gid.GID,
|
||||
) (*coredata.Nonconformity, error) {
|
||||
nonconformity := &coredata.Nonconformity{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return nonconformity.LoadByID(ctx, conn, s.svc.scope, nonconformityID)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nonconformity, nil
|
||||
}
|
||||
|
||||
func (s *NonconformityService) Create(
|
||||
ctx context.Context,
|
||||
req *CreateNonconformityRequest,
|
||||
) (*coredata.Nonconformity, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
nonconformity := &coredata.Nonconformity{
|
||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.NonconformityEntityType),
|
||||
OrganizationID: req.OrganizationID,
|
||||
ReferenceID: req.ReferenceID,
|
||||
Description: req.Description,
|
||||
AuditID: req.AuditID,
|
||||
DateIdentified: req.DateIdentified,
|
||||
RootCause: req.RootCause,
|
||||
CorrectiveAction: req.CorrectiveAction,
|
||||
OwnerID: req.OwnerID,
|
||||
DueDate: req.DueDate,
|
||||
Status: coredata.NonconformityStatusOpen,
|
||||
EffectivenessCheck: req.EffectivenessCheck,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if req.Status != nil {
|
||||
nonconformity.Status = *req.Status
|
||||
}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
organization := &coredata.Organization{}
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, req.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
if req.AuditID != nil {
|
||||
audit := &coredata.Audit{}
|
||||
if err := audit.LoadByID(ctx, conn, s.svc.scope, *req.AuditID); err != nil {
|
||||
return fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
owner := &coredata.MembershipProfile{}
|
||||
if err := owner.LoadByID(ctx, conn, s.svc.scope, req.OwnerID); err != nil {
|
||||
return fmt.Errorf("cannot load owner profile: %w", err)
|
||||
}
|
||||
|
||||
if err := nonconformity.Insert(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot insert nonconformity: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nonconformity, nil
|
||||
}
|
||||
|
||||
func (s *NonconformityService) Update(
|
||||
ctx context.Context,
|
||||
req *UpdateNonconformityRequest,
|
||||
) (*coredata.Nonconformity, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid request: %w", err)
|
||||
}
|
||||
|
||||
nonconformity := &coredata.Nonconformity{}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
if err := nonconformity.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load nonconformity: %w", err)
|
||||
}
|
||||
|
||||
if req.ReferenceID != nil {
|
||||
nonconformity.ReferenceID = *req.ReferenceID
|
||||
}
|
||||
if req.Description != nil {
|
||||
nonconformity.Description = *req.Description
|
||||
}
|
||||
if req.DateIdentified != nil {
|
||||
nonconformity.DateIdentified = *req.DateIdentified
|
||||
}
|
||||
if req.RootCause != nil {
|
||||
nonconformity.RootCause = *req.RootCause
|
||||
}
|
||||
if req.CorrectiveAction != nil {
|
||||
nonconformity.CorrectiveAction = *req.CorrectiveAction
|
||||
}
|
||||
if req.OwnerID != nil {
|
||||
owner := &coredata.MembershipProfile{}
|
||||
if err := owner.LoadByID(ctx, conn, s.svc.scope, *req.OwnerID); err != nil {
|
||||
return fmt.Errorf("cannot load owner profile: %w", err)
|
||||
}
|
||||
nonconformity.OwnerID = *req.OwnerID
|
||||
}
|
||||
if req.AuditID != nil {
|
||||
if *req.AuditID != nil {
|
||||
audit := &coredata.Audit{}
|
||||
if err := audit.LoadByID(ctx, conn, s.svc.scope, **req.AuditID); err != nil {
|
||||
return fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
}
|
||||
nonconformity.AuditID = *req.AuditID
|
||||
}
|
||||
if req.DueDate != nil {
|
||||
nonconformity.DueDate = *req.DueDate
|
||||
}
|
||||
if req.Status != nil {
|
||||
nonconformity.Status = *req.Status
|
||||
}
|
||||
if req.EffectivenessCheck != nil {
|
||||
nonconformity.EffectivenessCheck = *req.EffectivenessCheck
|
||||
}
|
||||
|
||||
nonconformity.UpdatedAt = time.Now()
|
||||
|
||||
if err := nonconformity.Update(ctx, conn, s.svc.scope); err != nil {
|
||||
return fmt.Errorf("cannot update nonconformity: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nonconformity, nil
|
||||
}
|
||||
|
||||
func (s NonconformityService) Delete(
|
||||
ctx context.Context,
|
||||
nonconformityID gid.GID,
|
||||
) error {
|
||||
nonconformity := coredata.Nonconformity{ID: nonconformityID}
|
||||
return s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := nonconformity.Delete(ctx, conn, s.svc.scope)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete nonconformity: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s NonconformityService) ListForOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[coredata.NonconformityOrderField],
|
||||
filter *coredata.NonconformityFilter,
|
||||
) (*page.Page[*coredata.Nonconformity, coredata.NonconformityOrderField], error) {
|
||||
var nonconformities coredata.Nonconformities
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := nonconformities.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load nonconformities: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(nonconformities, cursor), nil
|
||||
}
|
||||
|
||||
func (s NonconformityService) CountForOrganizationID(
|
||||
ctx context.Context,
|
||||
organizationID gid.GID,
|
||||
filter *coredata.NonconformityFilter,
|
||||
) (int, error) {
|
||||
var count int
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
nonconformities := coredata.Nonconformities{}
|
||||
count, err = nonconformities.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID, filter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count nonconformities: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -66,9 +66,8 @@ var ViewerPolicy = policy.NewPolicy(
|
||||
ActionDatumGet, ActionDatumList,
|
||||
ActionAuditGet, ActionAuditList,
|
||||
ActionReportGet, ActionReportGetReportUrl, ActionReportDownloadUrlGet,
|
||||
ActionNonconformityGet, ActionNonconformityList,
|
||||
ActionFindingGet, ActionFindingList,
|
||||
ActionObligationGet, ActionObligationList,
|
||||
ActionContinualImprovementGet, ActionContinualImprovementList,
|
||||
ActionProcessingActivityGet, ActionProcessingActivityList,
|
||||
ActionDataProtectionImpactAssessmentGet, ActionDataProtectionImpactAssessmentList,
|
||||
ActionTransferImpactAssessmentGet, ActionTransferImpactAssessmentList,
|
||||
@@ -134,9 +133,8 @@ var AuditorPolicy = policy.NewPolicy(
|
||||
ActionDatumGet, ActionDatumList,
|
||||
ActionAuditGet, ActionAuditList,
|
||||
ActionReportGet, ActionReportGetReportUrl, ActionReportDownloadUrlGet,
|
||||
ActionNonconformityGet, ActionNonconformityList,
|
||||
ActionFindingGet, ActionFindingList,
|
||||
ActionObligationGet, ActionObligationList,
|
||||
ActionContinualImprovementGet, ActionContinualImprovementList,
|
||||
ActionProcessingActivityGet, ActionProcessingActivityList,
|
||||
ActionDataProtectionImpactAssessmentGet,
|
||||
ActionTransferImpactAssessmentGet, ActionTransferImpactAssessmentList,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -107,10 +107,9 @@ type (
|
||||
TrustCenterFiles *TrustCenterFileService
|
||||
ComplianceFrameworks *ComplianceFrameworkService
|
||||
ComplianceExternalURLs *ComplianceExternalURLService
|
||||
Nonconformities *NonconformityService
|
||||
Findings *FindingService
|
||||
Obligations *ObligationService
|
||||
Snapshots *SnapshotService
|
||||
ContinualImprovements *ContinualImprovementService
|
||||
RightsRequests *RightsRequestService
|
||||
ProcessingActivities *ProcessingActivityService
|
||||
DataProtectionImpactAssessments *DataProtectionImpactAssessmentService
|
||||
@@ -257,10 +256,9 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
|
||||
filevalidation.WithMaxFileSize(10*1024*1024), // 10MB
|
||||
),
|
||||
}
|
||||
tenantService.Nonconformities = &NonconformityService{svc: tenantService}
|
||||
tenantService.Findings = &FindingService{svc: tenantService}
|
||||
tenantService.Obligations = &ObligationService{svc: tenantService}
|
||||
tenantService.Snapshots = &SnapshotService{svc: tenantService}
|
||||
tenantService.ContinualImprovements = &ContinualImprovementService{svc: tenantService}
|
||||
tenantService.RightsRequests = &RightsRequestService{svc: tenantService}
|
||||
tenantService.ProcessingActivities = &ProcessingActivityService{
|
||||
svc: tenantService,
|
||||
|
||||
Reference in New Issue
Block a user