Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions apps/customer-portal/backend/modules/entity/entity.bal
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public isolated function createCase(string idToken, CaseCreatePayload payload) r
}

# Update an existing case.
#
#
# + idToken - ID token for authorization
# + caseId - Unique ID of the case to be updated
# + payload - Case update payload
Expand Down Expand Up @@ -187,7 +187,7 @@ public isolated function createDeployedProduct(string idToken, DeployedProductCr
}

# Update a deployed product.
#
#
# + idToken - ID token for authorization
# + deployedProductId - ID of the deployed product to update
# + payload - Deployed product update payload
Expand Down Expand Up @@ -325,3 +325,14 @@ public isolated function searchProductVersions(string idToken, string productId,

return csEntityClient->/products/[productId]/versions/search.post(payload, generateHeaders(idToken));
}

# Search time cards.
#
# + idToken - ID token for authorization
# + payload - Payload containing search criteria for time cards
# + return - Response containing matching time cards or error
public isolated function searchTimeCards(string idToken, TimeCardSearchPayload payload)
returns TimeCardsResponse|error {

return csEntityClient->/time\-cards/search.post(payload, generateHeaders(idToken));
}
71 changes: 66 additions & 5 deletions apps/customer-portal/backend/modules/entity/types.bal
Original file line number Diff line number Diff line change
Expand Up @@ -918,14 +918,14 @@ public type CallRequestsResponse record {|
json...;
|};

# Date Constraint.
# Date-time (UTC).
@constraint:String {
pattern: {
value: re `^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):[0-5]\d(:[0-5]\d(\.\d{1,9})?)?(Z|[+-]([01]\d|2[0-3]):?[0-5]\d)$`,
message: "Invalid date provided. Please provide a valid date value."
}
}
public type Date string;
public type DateTime string;

# Request payload for creating a call request.
public type CallRequestCreatePayload record {|
Expand All @@ -935,7 +935,7 @@ public type CallRequestCreatePayload record {|
string reason;
# Preferred UTC times for the call
@constraint:Array {minLength: 1}
Date[] utcTimes;
DateTime[] utcTimes;
# Duration in minutes
@constraint:Int {minValue: 1}
int durationInMinutes;
Expand Down Expand Up @@ -968,9 +968,9 @@ public type CallRequestUpdatePayload record {|
# State key
int stateKey;
# Reason for the update
string? reason;
string reason?;
# New preferred UTC times for the call (mandatory when stateKey is 2)
Date[] utcTimes?;
DateTime[] utcTimes?;
Comment thread
Rashmika998 marked this conversation as resolved.
|};

# Updated call request details.
Expand Down Expand Up @@ -1077,3 +1077,64 @@ public type ProductVersionsResponse record {|
ProductVersion[] versions;
json...; // TODO: Add pagination
|};

# Date.
@constraint:String {
pattern: {
value: re `^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$`,
message: "Invalid date format."
}
}
public type Date string;

# Request payload for searching time cards.
public type TimeCardSearchPayload record {|
# Filter criteria
record {
# List of project IDs to filter
string[] projectIds?;
# Start date for filtering time cards (ISO 8601 format)
Date startDate?;
# End date for filtering time cards (ISO 8601 format)
Date endDate?;
} filters?;
# Pagination details
Pagination pagination?;
|};

# Time card data.
public type TimeCard record {|
# ID
string id;
# Total time logged
decimal totalTime;
# Created date and time
string createdOn;
# Indicates if the time card has billable hours
boolean hasBillable;
# State information (e.g., "Approved", "Submitted")
string state;
# User who approved the time card
ReferenceTableItem? approvedBy;
# Associated project
ReferenceTableItem? project;
# Associated case
TimeCardCase? case;
json...;
|};

# Time card information associated with a case.
public type TimeCardCase record {|
*ReferenceTableItem;
# Case number
string number;
|};

# Time cards response.
public type TimeCardsResponse record {|
# List of time cards
TimeCard[] timeCards;
# Total records count
int totalRecords;
*Pagination;
|};
2 changes: 1 addition & 1 deletion apps/customer-portal/backend/modules/entity/utils.bal
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public isolated function validateCallRequestUpdatePayload(CallRequestUpdatePaylo
#
# + utcTimes - Array of UTC time strings to validate
# + return - Error message if any UTC time is invalid or in the past, nil otherwise
public isolated function validateUtcTimes(Date[]? utcTimes) returns string|error? {
public isolated function validateUtcTimes(DateTime[]? utcTimes) returns string|error? {
if utcTimes != () {
foreach string utcTime in utcTimes {
time:Utc input = check time:utcFromString(utcTime);
Expand Down
62 changes: 50 additions & 12 deletions apps/customer-portal/backend/modules/types/types.bal
Original file line number Diff line number Diff line change
Expand Up @@ -674,22 +674,13 @@ public type CallRequestsResponse record {|
// TODO: Remove after adding pagination
|};

# Date Constraint.
@constraint:String {
pattern: {
value: re `^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):[0-5]\d(:[0-5]\d(\.\d{1,9})?)?(Z|[+-]([01]\d|2[0-3]):?[0-5]\d)$`,
message: "Invalid date provided. Please provide a valid date value."
}
}
public type Date string;

# Request payload for creating a call request.
public type CallRequestCreatePayload record {|
# Reason for the call request
string reason;
# Preferred UTC times for the call
@constraint:Array {minLength: 1}
Date[] utcTimes;
entity:DateTime[] utcTimes;
# Duration in minutes
@constraint:Int {minValue: 1}
int durationInMinutes;
Expand All @@ -700,9 +691,9 @@ public type CallRequestUpdatePayload record {|
# State key
int stateKey;
# Reason for the update
string? reason;
string reason?;
# New preferred UTC times for the call (mandatory when stateKey is 2)
Date[] utcTimes?;
entity:DateTime[] utcTimes?;
|};

# Product version data.
Expand Down Expand Up @@ -730,3 +721,50 @@ public type ProductVersionsResponse record {|
ProductVersion[] versions;
json...; // TODO: Add pagination
|};

# Time card data.
public type TimeCard record {|
# ID
string id;
# Total time logged
decimal totalTime;
# Created date and time
string createdOn;
# Indicates if the time card has billable hours
boolean hasBillable;
# State information (e.g., "Approved", "Submitted")
string state;
# User who approved the time card
ReferenceItem? approvedBy;
# Associated project
ReferenceItem? project;
# Associated case
record {|
*ReferenceItem;
# Case number
string number;
|}? case;
json...;
|};

# Time cards response.
public type TimeCardsResponse record {|
# List of time cards
TimeCard[] timeCards;
# Total records count
int totalRecords;
*entity:Pagination;
|};

# Request payload for searching time cards.
public type TimeCardSearchPayload record {|
# Filter criteria
record {
# Start date for filtering time cards (ISO 8601 format)
entity:Date startDate?;
# End date for filtering time cards (ISO 8601 format)
entity:Date endDate?;
} filters?;
# Pagination details
entity:Pagination pagination?;
|};
67 changes: 67 additions & 0 deletions apps/customer-portal/backend/service.bal
Original file line number Diff line number Diff line change
Expand Up @@ -2116,4 +2116,71 @@ service http:InterceptableService / on new http:Listener(9090, listenerConf) {
body: mapProductVersionsResponse(response)
};
}

# Search time cards based on provided filters.
#
# + id - ID of the project
# + payload - Time card search payload containing filters and pagination info
# + return - List of time cards matching the criteria or an error
resource function post projects/[string id]/time\-cards/search(http:RequestContext ctx,
types:TimeCardSearchPayload payload)
returns http:Ok|http:BadRequest|http:Unauthorized|http:Forbidden|http:InternalServerError {

authorization:UserInfoPayload|error userInfo = ctx.getWithType(authorization:HEADER_USER_INFO);
if userInfo is error {
return <http:InternalServerError>{
body: {
message: ERR_MSG_USER_INFO_HEADER_NOT_FOUND
}
};
}

entity:TimeCardsResponse|error response = entity:searchTimeCards(userInfo.idToken,
{
filters: {
projectIds: [id],
startDate: payload.filters?.startDate,
endDate: payload.filters?.endDate
},
pagination: payload.pagination
});
if response is error {
if getStatusCode(response) == http:STATUS_BAD_REQUEST {
return <http:BadRequest>{
body: {
message: "Invalid request parameters for searching time cards."
}
};
}

if getStatusCode(response) == http:STATUS_UNAUTHORIZED {
log:printWarn(string `User: ${userInfo.userId} is not authorized to access time card information!`);
return <http:Unauthorized>{
body: {
message: ERR_MSG_UNAUTHORIZED_ACCESS
}
};
}

if getStatusCode(response) == http:STATUS_FORBIDDEN {
log:printWarn(string `Access to time card information is forbidden for user: ${userInfo.userId}`);
return <http:Forbidden>{
body: {
message: "Access to time card information is forbidden for the user!"
}
};
}

string customError = "Failed to search time cards.";
log:printError(customError, response);
return <http:InternalServerError>{
body: {
message: customError
}
};
}
return <http:Ok>{
body: mapTimeCardSearchResponse(response)
};
}
}
23 changes: 23 additions & 0 deletions apps/customer-portal/backend/utils.bal
Original file line number Diff line number Diff line change
Expand Up @@ -416,3 +416,26 @@ public isolated function mapProductVersionsResponse(entity:ProductVersionsRespon
};
return {versions};
}

# Map time cards search response to the desired structure.
#
# + response - Time cards search response from the entity service
# + return - Mapped time cards search response
public isolated function mapTimeCardSearchResponse(entity:TimeCardsResponse response) returns types:TimeCardsResponse {
types:TimeCard[] timeCards = from entity:TimeCard timeCard in response.timeCards
let entity:ReferenceTableItem? approvedBy = timeCard.approvedBy
let entity:ReferenceTableItem? project = timeCard.project
let entity:TimeCardCase? case = timeCard.case
select {
id: timeCard.id,
totalTime: timeCard.totalTime,
createdOn: timeCard.createdOn,
hasBillable: timeCard.hasBillable,
state: timeCard.state,
approvedBy: approvedBy != () ? {id: approvedBy.id, label: approvedBy.name} : (),
project: project != () ? {id: project.id, label: project.name} : (),
case: case != () ? {id: case.id, label: case.name, number: case.number} : ()
};

return {timeCards, totalRecords: response.totalRecords, 'limit: response.'limit, offset: response.offset};
}