Files
docs-piam/pr-approval-admin-guide.md
2025-11-10 13:55:48 +07:00

26 KiB

Purchase Requisition Approval & Immutability - System Administrator Guide

Document Information

  • Document Type: Technical Implementation Guide
  • Audience: System Administrators, DevOps, Technical Support
  • Last Updated: 2025-10-30
  • Version: 1.0

Table of Contents

  1. Architecture Overview
  2. Implementation Details
  3. Database Schema
  4. API Endpoints
  5. Configuration
  6. Security Considerations
  7. Monitoring and Logging
  8. Troubleshooting
  9. Maintenance Procedures
  10. Testing and Validation

Architecture Overview

System Components

┌─────────────┐      ┌─────────────┐      ┌──────────────┐
│   Angular   │──────│  ASP.NET    │──────│  PostgreSQL  │
│   Frontend  │ HTTP │  Core API   │ EF   │   Database   │
└─────────────┘      └─────────────┘      └──────────────┘
     (UI)            (Business Logic)       (Data Store)

Immutability Implementation Layers

  1. Frontend Layer (purchase-requisition-form.component.ts)

    • UI/UX protection
    • Visual indicators
    • Client-side validation
    • User experience
  2. API Layer (PurchaseRequisitionsController.cs)

    • HTTP endpoint protection
    • Request validation
    • Error handling
    • Audit logging trigger
  3. Service Layer (PurchaseRequisitionService.cs)

    • Business logic
    • Exception propagation
  4. Repository Layer (PurchaseRequisitionRepository.cs)

    • Data access
    • Status validation
    • Exception throwing
  5. Database Layer (PostgreSQL)

    • Data persistence
    • Audit log storage
    • Transaction management

Status Flow

Draft (Editable)
  ↓ Submit
PendingApproval (Locked)
  ↓ Approve          ↓ Reject
Approved (Locked)    → Back to Draft (Editable)
  ↓ Convert
ConvertedToRfq/PO (Locked)
  ↓ Complete
Closed (Locked)

Implementation Details

Backend Implementation

Custom Exception Class

File: /piam-api/src/PiamMasterData.Domain/Exceptions/ImmutableDocumentException.cs

public class ImmutableDocumentException : Exception
{
    public string DocumentType { get; }
    public string DocumentId { get; }
    public string? CurrentStatus { get; }
    public string? AllowedStatus { get; }

    public ImmutableDocumentException(
        string documentType,
        string documentId,
        string? currentStatus = null,
        string? allowedStatus = null)
        : base($"Cannot modify {documentType} in '{currentStatus}' status. " +
               $"Only '{allowedStatus}' status allows modifications.")
    {
        DocumentType = documentType;
        DocumentId = documentId;
        CurrentStatus = currentStatus;
        AllowedStatus = allowedStatus;
    }
}

Repository Validation

File: /piam-api/src/PiamMasterData.Infrastructure/Repositories/PurchaseRequisitionRepository.cs

Lines: 304-311

if (requisition.Status != PurchaseRequisitionStatus.Draft)
{
    throw new ImmutableDocumentException(
        documentType: "Purchase Requisition",
        documentId: id.ToString(),
        currentStatus: requisition.Status.ToString(),
        allowedStatus: nameof(PurchaseRequisitionStatus.Draft)
    );
}

Controller Error Handling

File: /piam-api/src/PiamMasterData.Api/Controllers/PurchaseRequisitionsController.cs

Lines: 120-143

catch (ImmutableDocumentException ex)
{
    // Log the modification attempt for audit purposes
    await LogModificationAttemptAsync(
        id,
        "UPDATE_ATTEMPT",
        ex.CurrentStatus ?? "UNKNOWN",
        false,
        dto,
        ex.Message,
        cancellationToken);

    return StatusCode(403, new
    {
        error = ex.Message,
        errorCode = "PR_IMMUTABLE_STATUS",
        details = new
        {
            documentType = ex.DocumentType,
            documentId = ex.DocumentId,
            currentStatus = ex.CurrentStatus,
            allowedStatus = ex.AllowedStatus
        }
    });
}

Frontend Implementation

Component Logic

File: /piam-web/src/app/features/procurement/components/purchase-requisition-form/purchase-requisition-form.component.ts

Lines: 186-203

// Check if document is immutable (cannot be edited due to status)
get isImmutable(): boolean {
  const immutableStatuses: PurchaseRequisitionStatus[] = [
    'Approved',
    'PendingApproval',
    'ConvertedToRfq',
    'ConvertedToPurchaseOrder',
    'Closed',
  ];
  return this.detail ? immutableStatuses.includes(this.detail.status) : false;
}

// Determine if user can edit (save/submit buttons should show)
get canEdit(): boolean {
  if (!this.detail) return false;
  return this.detail.status === 'Draft' && !this.isReadOnly;
}

Template Visual Indicators

File: /piam-web/src/app/features/procurement/components/purchase-requisition-form/purchase-requisition-form.component.html

Key Elements:

  1. Lock icon in header (lines 12-19)
  2. Enhanced status badge (lines 22-31)
  3. Informational banner (lines 62-75)
  4. Conditional button visibility (lines 40-55)
  5. View Only badge (lines 33-36)

Database Schema

Purchase Requisition Table

Table: purchase_requisitions

Key Columns:

id                    UUID PRIMARY KEY
requisition_number    VARCHAR(50) UNIQUE NOT NULL
status                VARCHAR(50) NOT NULL
approved_by           VARCHAR(255)
approved_at_utc       TIMESTAMP
created_at_utc        TIMESTAMP NOT NULL
updated_at_utc        TIMESTAMP NOT NULL

Status Values:

  • Draft - Editable
  • PendingApproval - Locked
  • Approved - Locked
  • Rejected - Editable
  • ConvertedToRfq - Locked
  • ConvertedToPurchaseOrder - Locked
  • Closed - Locked

Audit Log Table

Table: purchase_requisition_audit_logs

Schema:

CREATE TABLE purchase_requisition_audit_logs (
    id                      UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    purchase_requisition_id UUID NOT NULL,
    action                  VARCHAR(50) NOT NULL,
    user_id                 VARCHAR(255),
    user_name               VARCHAR(255) NOT NULL,
    attempted_changes       TEXT,
    status_at_attempt       VARCHAR(50) NOT NULL,
    was_successful          BOOLEAN NOT NULL,
    failure_reason          TEXT,
    ip_address              VARCHAR(45),
    user_agent              TEXT,
    created_at_utc          TIMESTAMP NOT NULL DEFAULT now(),

    FOREIGN KEY (purchase_requisition_id)
        REFERENCES purchase_requisitions(id) ON DELETE CASCADE
);

CREATE INDEX ix_pr_audit_logs_pr_id
    ON purchase_requisition_audit_logs(purchase_requisition_id);

CREATE INDEX ix_pr_audit_logs_created_at
    ON purchase_requisition_audit_logs(created_at_utc DESC);

CREATE INDEX ix_pr_audit_logs_pr_id_created_at
    ON purchase_requisition_audit_logs(purchase_requisition_id, created_at_utc DESC);

Action Types:

  • UPDATE_ATTEMPT - Attempted to modify locked PR
  • STATUS_CHANGE - Status transition
  • APPROVAL - PR approved
  • REJECTION - PR rejected
  • CONVERSION - Converted to RFQ/PO

Migration

File: /piam-api/src/PiamMasterData.Infrastructure/Migrations/20251029204955_AddPurchaseRequisitionAuditLog.cs

Commands:

# Create migration
cd /piam-api/src/PiamMasterData.Infrastructure
dotnet ef migrations add AddPurchaseRequisitionAuditLog

# Apply migration
dotnet ef database update

# Verify
psql -h localhost -U [user] -d [database] -c "\d purchase_requisition_audit_logs"

API Endpoints

Update Purchase Requisition

Endpoint: PUT /api/purchase-requisitions/{id}

Request:

PUT /api/purchase-requisitions/a1b2c3d4-5678-90ab-cdef-123456789012
Content-Type: application/json

{
  "departmentCode": "IT",
  "budgetCode": "OPEX-2025-IT-001",
  "purpose": "Updated purpose",
  "lines": [...]
}

Success Response (200 OK):

{
  "id": "a1b2c3d4-5678-90ab-cdef-123456789012",
  "requisitionNumber": "PR-2025-001234",
  "status": "Draft",
  ...
}

Error Response (403 Forbidden):

{
  "error": "Cannot modify Purchase Requisition in 'Approved' status. Only 'Draft' status allows modifications.",
  "errorCode": "PR_IMMUTABLE_STATUS",
  "details": {
    "documentType": "Purchase Requisition",
    "documentId": "a1b2c3d4-5678-90ab-cdef-123456789012",
    "currentStatus": "Approved",
    "allowedStatus": "Draft"
  }
}

Get Audit Logs

Endpoint: GET /api/purchase-requisitions/{id}/audit-logs

Request:

GET /api/purchase-requisitions/a1b2c3d4-5678-90ab-cdef-123456789012/audit-logs?pageNumber=1&pageSize=25

Response (200 OK):

{
  "items": [
    {
      "id": "log-uuid-1",
      "purchaseRequisitionId": "a1b2c3d4-5678-90ab-cdef-123456789012",
      "action": "UPDATE_ATTEMPT",
      "userId": "user-123",
      "userName": "john.doe",
      "attemptedChanges": "{\"purpose\":\"New purpose\"}",
      "statusAtAttempt": "Approved",
      "wasSuccessful": false,
      "failureReason": "Cannot modify PR in Approved status",
      "ipAddress": "192.168.1.100",
      "userAgent": "Mozilla/5.0...",
      "createdAtUtc": "2025-10-30T14:23:45Z"
    }
  ],
  "totalCount": 1,
  "pageNumber": 1,
  "pageSize": 25
}

Approve Purchase Requisition

Endpoint: POST /api/purchase-requisitions/{id}/approve

Important: ⚠️ This endpoint currently has NO authorization checks

Request:

POST /api/purchase-requisitions/a1b2c3d4-5678-90ab-cdef-123456789012/approve
Content-Type: application/json

{
  "remarks": "Approved for procurement"
}

Response (200 OK):

{
  "id": "a1b2c3d4-5678-90ab-cdef-123456789012",
  "status": "Approved",
  "approvedBy": "approver.name",
  "approvedAtUtc": "2025-10-30T15:00:00Z",
  ...
}

Configuration

Dependency Injection

File: /piam-api/src/PiamMasterData.Infrastructure/DependencyInjection.cs

Line: 90

services.AddScoped<IPurchaseRequisitionAuditService, PurchaseRequisitionAuditService>();

Service Registration

All required services are registered in DependencyInjection.cs:

services.AddHttpContextAccessor(); // Line 21 - Required for audit logging
services.AddScoped<IPurchaseRequisitionRepository, PurchaseRequisitionRepository>();
services.AddScoped<IPurchaseRequisitionAuditService, PurchaseRequisitionAuditService>();

Connection String

File: appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Database=PiamMasterData;Username=user;Password=pass"
  }
}

Frontend Environment

File: /piam-web/src/environments/environment.ts

export const environment = {
  production: false,
  apiUrl: 'http://localhost:5200/api'
};

Security Considerations

Current Implementation

Implemented Security Features

  1. Status-Based Access Control

    • Repository enforces status checks
    • Cannot bypass via API calls
    • Consistent across all endpoints
  2. Audit Logging

    • All modification attempts logged
    • User context captured
    • IP address and user agent recorded
  3. Exception Handling

    • Proper HTTP status codes
    • Structured error responses
    • No sensitive data leakage
  4. Frontend Protection

    • UI prevents unauthorized actions
    • Visual indicators for locked documents
    • Form validation

Missing Security Features

  1. NO Authorization Attributes
    • No [Authorize] on controller actions
    • Anyone with API access can approve
    • No role-based access control

Recommended Fix:

/// <summary>
/// Approves a purchase requisition
/// </summary>
[Authorize(Policy = "Procurement.PurchaseRequisitions.Approve")]
[HttpPost("{id:guid}/approve")]
public async Task<ActionResult<PurchaseRequisitionDetailDto>> ApproveRequisition(...)
  1. No Permission Checks
    • Backend doesn't verify user permissions
    • No distinction between creator/approver

Recommended Implementation:

// Check if user has permission to approve
if (!User.HasPermission("Procurement.PurchaseRequisitions.Approve"))
{
    return Forbidden();
}
  1. No CSRF Protection
    • Consider adding anti-forgery tokens
    • Especially for state-changing operations

Threat Model

Threat Risk Level Mitigation
Direct API bypass High Enforced at repository level
Unauthorized approval High Add authorization attributes
Audit log tampering Medium Database permissions, indexes
Session hijacking Medium Implement proper authentication
CSRF attacks Medium Add anti-forgery tokens
SQL injection Low Using EF Core with parameters
  1. Implement Authorization

    # Add authorization attributes to all endpoints
    # Define policies in Startup.cs
    # Implement permission checks
    
  2. Add API Rate Limiting

    services.AddRateLimiting(options => { ... });
    
  3. Enable CORS Properly

    services.AddCors(options =>
    {
        options.AddPolicy("PiamPolicy", builder =>
        {
            builder.WithOrigins("https://yourdomain.com")
                   .AllowAnyMethod()
                   .AllowAnyHeader();
        });
    });
    
  4. Add Request Validation

    [ValidateAntiForgeryToken]
    [ValidateModel]
    

Monitoring and Logging

Application Logs

Configuration: appsettings.json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.EntityFrameworkCore": "Warning",
      "PiamMasterData.Infrastructure.Services": "Information"
    }
  }
}

Audit Log Service

File: /piam-api/src/PiamMasterData.Infrastructure/Services/PurchaseRequisitionAuditService.cs

Key Method: LogModificationAttemptAsync

Logs:

  • User name and ID
  • Action attempted
  • Status at time of attempt
  • IP address and user agent
  • Attempted changes (JSON)
  • Success/failure status
  • Failure reason

Monitoring Queries

Recent Modification Attempts

SELECT
    pr.requisition_number,
    pal.action,
    pal.user_name,
    pal.status_at_attempt,
    pal.was_successful,
    pal.failure_reason,
    pal.created_at_utc
FROM purchase_requisition_audit_logs pal
JOIN purchase_requisitions pr ON pal.purchase_requisition_id = pr.id
WHERE pal.was_successful = false
  AND pal.created_at_utc > NOW() - INTERVAL '7 days'
ORDER BY pal.created_at_utc DESC;

Failed Modification Attempts by User

SELECT
    user_name,
    COUNT(*) as attempt_count,
    MAX(created_at_utc) as last_attempt
FROM purchase_requisition_audit_logs
WHERE was_successful = false
  AND action = 'UPDATE_ATTEMPT'
  AND created_at_utc > NOW() - INTERVAL '30 days'
GROUP BY user_name
ORDER BY attempt_count DESC;

Approval Activity

SELECT
    pr.requisition_number,
    pr.status,
    pr.approved_by,
    pr.approved_at_utc,
    pr.created_at_utc,
    EXTRACT(EPOCH FROM (pr.approved_at_utc - pr.created_at_utc))/3600 as hours_to_approval
FROM purchase_requisitions pr
WHERE pr.approved_at_utc IS NOT NULL
  AND pr.approved_at_utc > NOW() - INTERVAL '30 days'
ORDER BY pr.approved_at_utc DESC;

Performance Monitoring

Key Metrics:

  1. Average time to approval
  2. Number of modification attempts
  3. Failed approval attempts
  4. API response times
  5. Database query performance

Tools:

  • Application Insights (if using Azure)
  • ELK Stack (Elasticsearch, Logstash, Kibana)
  • Prometheus + Grafana
  • PostgreSQL pg_stat_statements

Troubleshooting

Common Issues

Issue: Users Report "Cannot Edit" But PR is in Draft

Diagnosis:

-- Check PR status
SELECT id, requisition_number, status, updated_at_utc
FROM purchase_requisitions
WHERE requisition_number = 'PR-2025-001234';

-- Check for status anomalies
SELECT status, COUNT(*)
FROM purchase_requisitions
GROUP BY status;

Possible Causes:

  1. Browser cache showing old status
  2. Frontend/backend version mismatch
  3. Database update didn't complete

Solutions:

  1. Clear browser cache (Ctrl+Shift+Delete)
  2. Verify backend and frontend versions
  3. Check database directly
  4. Review application logs

Issue: Audit Logs Not Being Created

Diagnosis:

# Check if table exists
psql -h localhost -U user -d database -c "\d purchase_requisition_audit_logs"

# Check for recent logs
psql -h localhost -U user -d database -c "SELECT COUNT(*) FROM purchase_requisition_audit_logs;"

# Check service registration
grep -r "PurchaseRequisitionAuditService" src/

Possible Causes:

  1. Migration not applied
  2. Service not registered in DI
  3. Exception in audit logging (silently caught)
  4. Database permissions issue

Solutions:

  1. Run migrations: dotnet ef database update
  2. Verify DI registration in DependencyInjection.cs:90
  3. Check application logs for exceptions
  4. Verify database user has INSERT permissions

Issue: 403 Errors on Draft PRs

Diagnosis:

-- Verify PR status
SELECT id, requisition_number, status
FROM purchase_requisitions
WHERE id = 'a1b2c3d4-5678-90ab-cdef-123456789012';

Possible Causes:

  1. Status field corrupted
  2. Status enum mismatch
  3. Code deployment issue

Solutions:

  1. Verify database value matches enum
  2. Check for recent code deployments
  3. Review repository code
  4. Check for custom status values

Issue: Approved PRs Being Modified

Critical Security Issue

Immediate Actions:

  1. Review audit logs immediately
  2. Identify affected PRs
  3. Notify security team
  4. Check for unauthorized access

Investigation:

-- Find modified approved PRs
SELECT
    pr.requisition_number,
    pr.status,
    pr.updated_at_utc,
    pr.approved_at_utc
FROM purchase_requisitions pr
WHERE pr.status != 'Draft'
  AND pr.updated_at_utc > pr.approved_at_utc;

-- Check audit logs
SELECT *
FROM purchase_requisition_audit_logs
WHERE action = 'UPDATE_ATTEMPT'
  AND was_successful = true;

Root Cause Analysis:

  • Repository validation bypass?
  • Direct database modification?
  • Code deployment introduced bug?
  • Migration script error?

Maintenance Procedures

Database Maintenance

Audit Log Retention

Policy: Retain 7 years per audit requirements

Archive Old Logs:

-- Create archive table (one-time)
CREATE TABLE purchase_requisition_audit_logs_archive (
    LIKE purchase_requisition_audit_logs INCLUDING ALL
);

-- Archive logs older than 5 years
INSERT INTO purchase_requisition_audit_logs_archive
SELECT * FROM purchase_requisition_audit_logs
WHERE created_at_utc < NOW() - INTERVAL '5 years';

-- Optional: Delete archived records from main table
-- DELETE FROM purchase_requisition_audit_logs
-- WHERE created_at_utc < NOW() - INTERVAL '5 years';

Index Maintenance

-- Reindex for performance
REINDEX TABLE purchase_requisition_audit_logs;
REINDEX TABLE purchase_requisitions;

-- Vacuum to reclaim space
VACUUM ANALYZE purchase_requisition_audit_logs;
VACUUM ANALYZE purchase_requisitions;

-- Check index usage
SELECT
    schemaname,
    tablename,
    indexname,
    idx_scan,
    idx_tup_read,
    idx_tup_fetch
FROM pg_stat_user_indexes
WHERE tablename IN ('purchase_requisition_audit_logs', 'purchase_requisitions')
ORDER BY idx_scan;

Backup Procedures

Database Backup

# Full backup
pg_dump -h localhost -U user -d database -F c -f piam_backup_$(date +%Y%m%d).backup

# Backup specific tables
pg_dump -h localhost -U user -d database -t purchase_requisitions -t purchase_requisition_audit_logs -F c -f pr_tables_$(date +%Y%m%d).backup

# Restore
pg_restore -h localhost -U user -d database -c piam_backup_20251030.backup

Schedule Backups

# Add to crontab
# Daily backup at 2 AM
0 2 * * * /usr/local/bin/backup-piam-db.sh

# Weekly full backup on Sunday at 1 AM
0 1 * * 0 /usr/local/bin/backup-piam-db-full.sh

Application Updates

Deployment Checklist

  1. Before Deployment

    • Review all code changes
    • Run unit tests
    • Run integration tests
    • Backup database
    • Notify users of maintenance window
  2. During Deployment

    • Stop application
    • Apply database migrations
    • Deploy new code
    • Update configuration if needed
    • Start application
  3. After Deployment

    • Verify application starts
    • Test critical paths
    • Check logs for errors
    • Verify database connections
    • Monitor for issues

Migration Commands

# List migrations
dotnet ef migrations list

# Add migration
dotnet ef migrations add MigrationName

# Apply migrations
dotnet ef database update

# Rollback to specific migration
dotnet ef database update PreviousMigrationName

# Generate SQL script (for review)
dotnet ef migrations script > migration.sql

Testing and Validation

Manual Testing

Test Case 1: Update Draft PR

# Should succeed
curl -X PUT "http://localhost:5200/api/purchase-requisitions/{draft-id}" \
  -H "Content-Type: application/json" \
  -d '{
    "departmentCode": "IT",
    "purpose": "Updated purpose",
    "lines": [...]
  }'

# Expected: 200 OK

Test Case 2: Update Approved PR

# Should fail
curl -X PUT "http://localhost:5200/api/purchase-requisitions/{approved-id}" \
  -H "Content-Type: application/json" \
  -d '{
    "purpose": "Trying to change approved PR"
  }'

# Expected: 403 Forbidden with PR_IMMUTABLE_STATUS error code

Test Case 3: Verify Audit Logging

# Attempt to modify approved PR (should fail)
curl -X PUT "http://localhost:5200/api/purchase-requisitions/{approved-id}" \
  -H "Content-Type: application/json" \
  -d '{"purpose": "Test"}'

# Check audit logs
curl "http://localhost:5200/api/purchase-requisitions/{approved-id}/audit-logs"

# Verify log entry exists with:
# - action: "UPDATE_ATTEMPT"
# - wasSuccessful: false
# - statusAtAttempt: "Approved"

Automated Testing

Unit Test Example

[Fact]
public async Task UpdateRequisitionAsync_WhenStatusIsApproved_ThrowsImmutableDocumentException()
{
    // Arrange
    var approvedPR = new PurchaseRequisition
    {
        Id = Guid.NewGuid(),
        Status = PurchaseRequisitionStatus.Approved
    };
    _mockRepository.Setup(r => r.GetByIdAsync(It.IsAny<Guid>()))
        .ReturnsAsync(approvedPR);

    // Act & Assert
    await Assert.ThrowsAsync<ImmutableDocumentException>(
        () => _service.UpdateRequisitionAsync(approvedPR.Id, new UpdatePurchaseRequisitionDto())
    );
}

Integration Test Example

[Fact]
public async Task PUT_ApprovedPR_Returns403Forbidden()
{
    // Arrange
    var approvedPR = await CreateApprovedPRAsync();
    var updateDto = new UpdatePurchaseRequisitionDto
    {
        Purpose = "Trying to update"
    };

    // Act
    var response = await _client.PutAsJsonAsync(
        $"/api/purchase-requisitions/{approvedPR.Id}",
        updateDto
    );

    // Assert
    Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);

    var error = await response.Content.ReadFromJsonAsync<ErrorResponse>();
    Assert.Equal("PR_IMMUTABLE_STATUS", error.ErrorCode);
}

Performance Testing

# Load test with Apache Bench
ab -n 1000 -c 10 -p pr-update.json -T application/json \
  http://localhost:5200/api/purchase-requisitions/{id}

# Monitor response times
# Expected: < 100ms for status validation
# Expected: < 200ms for full update operation

Appendix

File Reference

Backend:

  • /piam-api/src/PiamMasterData.Domain/Exceptions/ImmutableDocumentException.cs
  • /piam-api/src/PiamMasterData.Api/Controllers/PurchaseRequisitionsController.cs:120-143
  • /piam-api/src/PiamMasterData.Infrastructure/Repositories/PurchaseRequisitionRepository.cs:304-311
  • /piam-api/src/PiamMasterData.Infrastructure/Services/PurchaseRequisitionAuditService.cs
  • /piam-api/src/PiamMasterData.Infrastructure/DependencyInjection.cs:90

Frontend:

  • /piam-web/src/app/features/procurement/components/purchase-requisition-form/purchase-requisition-form.component.ts:186-203
  • /piam-web/src/app/features/procurement/components/purchase-requisition-form/purchase-requisition-form.component.html:12-75

Database:

  • /piam-api/src/PiamMasterData.Infrastructure/Migrations/20251029204955_AddPurchaseRequisitionAuditLog.cs
  • User Guide: /docs/user-guide-pr-approval.md
  • Process Guide: /docs/pr-after-approval-guide.md
  • FAQ: /docs/pr-approval-faq.md
  • Requirements: /docs/pr-approval-immutability-requirement.md

Useful Commands

# Check application version
dotnet --version

# Check database version
psql --version

# View running processes
dotnet run --no-build &

# Check database connection
psql -h localhost -U user -d database -c "SELECT version();"

# View application logs
tail -f /var/log/piam-api/application.log

# Check disk space
df -h

# Check database size
psql -h localhost -U user -d database -c "SELECT pg_size_pretty(pg_database_size('database'));"

Support and Escalation

L1 Support (Help Desk)

  • User cannot edit Draft PR → Clear cache
  • Page won't load → Refresh browser
  • Buttons missing → Check PR status

L2 Support (System Administrator)

  • Audit logs not appearing → Check migration
  • Performance issues → Check database indexes
  • Configuration problems → Review settings

L3 Support (Development Team)

  • Logic bugs → Code review
  • Security issues → Immediate escalation
  • Data integrity issues → Database investigation

Critical Issues (Immediate Escalation)

  • Approved PRs being modified
  • Audit log tampering
  • Security breaches
  • Data corruption

Document End

For operational procedures and user assistance, refer to the User Guide and FAQ documents.