Files

183 lines
5.4 KiB
Solidity
Raw Permalink Normal View History

// 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;
}
}