# 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](#architecture-overview) 2. [Implementation Details](#implementation-details) 3. [Database Schema](#database-schema) 4. [API Endpoints](#api-endpoints) 5. [Configuration](#configuration) 6. [Security Considerations](#security-considerations) 7. [Monitoring and Logging](#monitoring-and-logging) 8. [Troubleshooting](#troubleshooting) 9. [Maintenance Procedures](#maintenance-procedures) 10. [Testing and Validation](#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` ```csharp 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 ```csharp 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 ```csharp 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 ```typescript // 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**: ```sql 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**: ```sql 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**: ```bash # 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**: ```http 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): ```json { "id": "a1b2c3d4-5678-90ab-cdef-123456789012", "requisitionNumber": "PR-2025-001234", "status": "Draft", ... } ``` **Error Response** (403 Forbidden): ```json { "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**: ```http GET /api/purchase-requisitions/a1b2c3d4-5678-90ab-cdef-123456789012/audit-logs?pageNumber=1&pageSize=25 ``` **Response** (200 OK): ```json { "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**: ```http POST /api/purchase-requisitions/a1b2c3d4-5678-90ab-cdef-123456789012/approve Content-Type: application/json { "remarks": "Approved for procurement" } ``` **Response** (200 OK): ```json { "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 ```csharp services.AddScoped(); ``` ### Service Registration All required services are registered in `DependencyInjection.cs`: ```csharp services.AddHttpContextAccessor(); // Line 21 - Required for audit logging services.AddScoped(); services.AddScoped(); ``` ### Connection String **File**: `appsettings.json` ```json { "ConnectionStrings": { "DefaultConnection": "Host=localhost;Database=PiamMasterData;Username=user;Password=pass" } } ``` ### Frontend Environment **File**: `/piam-web/src/environments/environment.ts` ```typescript 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**: ```csharp /// /// Approves a purchase requisition /// [Authorize(Policy = "Procurement.PurchaseRequisitions.Approve")] [HttpPost("{id:guid}/approve")] public async Task> ApproveRequisition(...) ``` 2. **No Permission Checks** - Backend doesn't verify user permissions - No distinction between creator/approver **Recommended Implementation**: ```csharp // Check if user has permission to approve if (!User.HasPermission("Procurement.PurchaseRequisitions.Approve")) { return Forbidden(); } ``` 3. **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 | ### Recommended Security Enhancements 1. **Implement Authorization** ```bash # Add authorization attributes to all endpoints # Define policies in Startup.cs # Implement permission checks ``` 2. **Add API Rate Limiting** ```csharp services.AddRateLimiting(options => { ... }); ``` 3. **Enable CORS Properly** ```csharp services.AddCors(options => { options.AddPolicy("PiamPolicy", builder => { builder.WithOrigins("https://yourdomain.com") .AllowAnyMethod() .AllowAnyHeader(); }); }); ``` 4. **Add Request Validation** ```csharp [ValidateAntiForgeryToken] [ValidateModel] ``` --- ## Monitoring and Logging ### Application Logs **Configuration**: `appsettings.json` ```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 ```sql 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 ```sql 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 ```sql 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**: ```sql -- 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**: ```bash # 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**: ```sql -- 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**: ```sql -- 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**: ```sql -- 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 ```sql -- 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```bash # 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 ```csharp [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())) .ReturnsAsync(approvedPR); // Act & Assert await Assert.ThrowsAsync( () => _service.UpdateRequisitionAsync(approvedPR.Id, new UpdatePurchaseRequisitionDto()) ); } ``` #### Integration Test Example ```csharp [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(); Assert.Equal("PR_IMMUTABLE_STATUS", error.ErrorCode); } ``` ### Performance Testing ```bash # 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` ### Related Documentation - **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 ```bash # 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.*