Complete implementation of the Goa Government e-Licensing platform with: Backend: - NestJS API with JWT authentication - PostgreSQL database with Knex ORM - Redis caching and session management - MinIO document storage - Hyperledger Besu blockchain integration - Multi-department workflow system - Comprehensive API tests (266/282 passing) Frontend: - Angular 21 with standalone components - Angular Material + TailwindCSS UI - Visual workflow builder - Document upload with progress tracking - Blockchain explorer integration - Role-based dashboards (Admin, Department, Citizen) - E2E tests with Playwright (37 tests) Infrastructure: - Docker Compose orchestration - Blockscout blockchain explorer - Development and production configurations
183 lines
5.4 KiB
Solidity
183 lines
5.4 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
|
|
/**
|
|
* @title ApprovalManager
|
|
* @notice Manages and records approval actions on the blockchain
|
|
* @dev Provides immutable audit trail for license approvals
|
|
*/
|
|
contract ApprovalManager is Ownable {
|
|
enum ApprovalStatus {
|
|
PENDING,
|
|
APPROVED,
|
|
REJECTED,
|
|
CHANGES_REQUESTED,
|
|
INVALIDATED
|
|
}
|
|
|
|
struct Approval {
|
|
bytes32 id;
|
|
string requestId;
|
|
address departmentAddress;
|
|
ApprovalStatus status;
|
|
string remarksHash;
|
|
string[] documentHashes;
|
|
uint256 timestamp;
|
|
bool isValid;
|
|
}
|
|
|
|
// Mapping from approval ID to Approval struct
|
|
mapping(bytes32 => Approval) private _approvals;
|
|
|
|
// Mapping from request ID to approval IDs
|
|
mapping(string => bytes32[]) private _requestApprovals;
|
|
|
|
// Counter for generating unique approval IDs
|
|
uint256 private _approvalCounter;
|
|
|
|
// Events
|
|
event ApprovalRecorded(
|
|
bytes32 indexed approvalId,
|
|
string indexed requestId,
|
|
address indexed departmentAddress,
|
|
ApprovalStatus status,
|
|
uint256 timestamp
|
|
);
|
|
|
|
event ApprovalInvalidated(
|
|
bytes32 indexed approvalId,
|
|
string reason
|
|
);
|
|
|
|
constructor() Ownable(msg.sender) {}
|
|
|
|
/**
|
|
* @notice Record an approval action
|
|
* @param requestId The license request ID
|
|
* @param departmentAddress The address of the approving department
|
|
* @param status The approval status
|
|
* @param remarksHash Hash of the approval remarks
|
|
* @param documentHashes Array of document hashes that were reviewed
|
|
* @return approvalId The unique ID of the recorded approval
|
|
*/
|
|
function recordApproval(
|
|
string calldata requestId,
|
|
address departmentAddress,
|
|
ApprovalStatus status,
|
|
string calldata remarksHash,
|
|
string[] calldata documentHashes
|
|
) public onlyOwner returns (bytes32) {
|
|
require(bytes(requestId).length > 0, "Request ID required");
|
|
require(departmentAddress != address(0), "Invalid department address");
|
|
|
|
_approvalCounter++;
|
|
bytes32 approvalId = keccak256(
|
|
abi.encodePacked(requestId, departmentAddress, block.timestamp, _approvalCounter)
|
|
);
|
|
|
|
Approval storage approval = _approvals[approvalId];
|
|
approval.id = approvalId;
|
|
approval.requestId = requestId;
|
|
approval.departmentAddress = departmentAddress;
|
|
approval.status = status;
|
|
approval.remarksHash = remarksHash;
|
|
approval.timestamp = block.timestamp;
|
|
approval.isValid = true;
|
|
|
|
// Copy document hashes using loop (calldata to storage)
|
|
for (uint256 i = 0; i < documentHashes.length; i++) {
|
|
approval.documentHashes.push(documentHashes[i]);
|
|
}
|
|
|
|
_requestApprovals[requestId].push(approvalId);
|
|
|
|
emit ApprovalRecorded(
|
|
approvalId,
|
|
requestId,
|
|
departmentAddress,
|
|
status,
|
|
block.timestamp
|
|
);
|
|
|
|
return approvalId;
|
|
}
|
|
|
|
/**
|
|
* @notice Get all approvals for a request
|
|
* @param requestId The license request ID
|
|
* @return Array of Approval structs
|
|
*/
|
|
function getRequestApprovals(string calldata requestId)
|
|
public
|
|
view
|
|
returns (Approval[] memory)
|
|
{
|
|
bytes32[] memory approvalIds = _requestApprovals[requestId];
|
|
Approval[] memory approvals = new Approval[](approvalIds.length);
|
|
|
|
for (uint256 i = 0; i < approvalIds.length; i++) {
|
|
approvals[i] = _approvals[approvalIds[i]];
|
|
}
|
|
|
|
return approvals;
|
|
}
|
|
|
|
/**
|
|
* @notice Invalidate an existing approval
|
|
* @param approvalId The approval ID to invalidate
|
|
* @param reason The reason for invalidation
|
|
*/
|
|
function invalidateApproval(bytes32 approvalId, string calldata reason)
|
|
public
|
|
onlyOwner
|
|
{
|
|
require(_approvals[approvalId].isValid, "Approval not found or already invalid");
|
|
|
|
_approvals[approvalId].isValid = false;
|
|
_approvals[approvalId].status = ApprovalStatus.INVALIDATED;
|
|
|
|
emit ApprovalInvalidated(approvalId, reason);
|
|
}
|
|
|
|
/**
|
|
* @notice Verify if approval remarks match the stored hash
|
|
* @param approvalId The approval ID
|
|
* @param remarksHash The hash to verify
|
|
* @return True if the hashes match
|
|
*/
|
|
function verifyApproval(bytes32 approvalId, string calldata remarksHash)
|
|
public
|
|
view
|
|
returns (bool)
|
|
{
|
|
Approval memory approval = _approvals[approvalId];
|
|
return approval.isValid &&
|
|
keccak256(bytes(approval.remarksHash)) == keccak256(bytes(remarksHash));
|
|
}
|
|
|
|
/**
|
|
* @notice Get details of a specific approval
|
|
* @param approvalId The approval ID
|
|
* @return The Approval struct
|
|
*/
|
|
function getApprovalDetails(bytes32 approvalId)
|
|
public
|
|
view
|
|
returns (Approval memory)
|
|
{
|
|
require(_approvals[approvalId].timestamp > 0, "Approval not found");
|
|
return _approvals[approvalId];
|
|
}
|
|
|
|
/**
|
|
* @notice Get the count of approvals for a request
|
|
* @param requestId The license request ID
|
|
* @return The number of approvals
|
|
*/
|
|
function getApprovalCount(string calldata requestId) public view returns (uint256) {
|
|
return _requestApprovals[requestId].length;
|
|
}
|
|
}
|