status / assignee updates
This commit is contained in:
parent
e4eebe5396
commit
2c7194f6ef
@ -47,7 +47,7 @@ timo-redaction-dev.iqser.cloud
|
||||
| useradmin | RED_ADMIN, RED_USER | has super power ! |
|
||||
| manageradmin | RED_ADMIN RED_MANAGER RED_USER | has super super power ! |
|
||||
|
||||
Password for all users is `OsloImWinter`
|
||||
Password for all users is `OsloImWinter!23`
|
||||
|
||||
```
|
||||
|
||||
|
||||
@ -57,7 +57,7 @@ export class AssignReviewerApproverDialogComponent {
|
||||
}
|
||||
|
||||
for (const file of this.data.files) {
|
||||
if (file.currentReviewer !== this.selectedUser) {
|
||||
if (file.assignee !== this.selectedUser) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -77,8 +77,8 @@ export class AssignReviewerApproverDialogComponent {
|
||||
private get _uniqueReviewers(): Set<string> {
|
||||
const uniqueReviewers = new Set<string>();
|
||||
for (const file of this.data.files) {
|
||||
if (file.currentReviewer) {
|
||||
uniqueReviewers.add(file.currentReviewer);
|
||||
if (file.assignee) {
|
||||
uniqueReviewers.add(file.assignee);
|
||||
}
|
||||
}
|
||||
return uniqueReviewers;
|
||||
|
||||
@ -38,7 +38,7 @@ export class DossierOverviewBulkActionsComponent {
|
||||
|
||||
get allSelectedFilesCanBeAssignedIntoSameState() {
|
||||
const allFilesAreUnderReviewOrUnassigned = this.selectedFiles.reduce(
|
||||
(acc, file) => acc && (file.isUnderReview || file.isUnassigned),
|
||||
(acc, file) => acc && (file.isUnderReview || file.isNew),
|
||||
true,
|
||||
);
|
||||
const allFilesAreUnderApproval = this.selectedFiles.reduce((acc, file) => acc && file.isUnderApproval, true);
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
</div>
|
||||
|
||||
<div class="user-column cell">
|
||||
<redaction-initials-avatar [user]="file.currentReviewer" [withName]="true"></redaction-initials-avatar>
|
||||
<redaction-initials-avatar [user]="file.assignee" [withName]="true"></redaction-initials-avatar>
|
||||
</div>
|
||||
|
||||
<div class="cell">
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
</div>
|
||||
|
||||
<div class="user">
|
||||
<redaction-initials-avatar [user]="file.currentReviewer"></redaction-initials-avatar>
|
||||
<redaction-initials-avatar [user]="file.assignee"></redaction-initials-avatar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -61,8 +61,8 @@ export class ConfigService {
|
||||
itemVersionFn: (entity: File) => `${entity.lastUpdated}-${entity.numberOfAnalyses}`,
|
||||
columns: [
|
||||
{
|
||||
label: workflowFileStatusTranslations[WorkflowFileStatuses.UNASSIGNED],
|
||||
key: WorkflowFileStatuses.UNASSIGNED,
|
||||
label: workflowFileStatusTranslations[WorkflowFileStatuses.NEW],
|
||||
key: WorkflowFileStatuses.NEW,
|
||||
enterFn: this._unassignFn,
|
||||
enterPredicate: (file: File) => this._permissionsService.canUnassignUser(file),
|
||||
color: '#D3D5DA',
|
||||
@ -162,7 +162,7 @@ export class ConfigService {
|
||||
const filterGroups: IFilterGroup[] = [];
|
||||
|
||||
entities.forEach(file => {
|
||||
allDistinctPeople.add(file.currentReviewer);
|
||||
allDistinctPeople.add(file.assignee);
|
||||
allDistinctWorkflowFileStatuses.add(file.workflowStatus);
|
||||
allDistinctAddedDates.add(moment(file.added).format('DD/MM/YYYY'));
|
||||
|
||||
@ -318,11 +318,11 @@ export class ConfigService {
|
||||
_recentlyModifiedChecker = (file: File) =>
|
||||
moment(file.lastUpdated).add(this._appConfigService.values.RECENT_PERIOD_IN_HOURS, 'hours').isAfter(moment());
|
||||
|
||||
_assignedToMeChecker = (file: File) => file.currentReviewer === this._userService.currentUser.id;
|
||||
_assignedToMeChecker = (file: File) => file.assignee === this._userService.currentUser.id;
|
||||
|
||||
_unassignedChecker = (file: File) => file.isUnassigned;
|
||||
_unassignedChecker = (file: File) => file.isNew;
|
||||
|
||||
_assignedToOthersChecker = (file: File) => !file.isUnassigned && file.currentReviewer !== this._userService.currentUser.id;
|
||||
_assignedToOthersChecker = (file: File) => !file.isNew && file.assignee !== this._userService.currentUser.id;
|
||||
|
||||
private _quickFilters(entities: File[]): NestedFilter[] {
|
||||
const recentPeriod = this._appConfigService.values.RECENT_PERIOD_IN_HOURS;
|
||||
|
||||
@ -46,8 +46,8 @@
|
||||
|
||||
<redaction-initials-avatar
|
||||
*ngIf="!editingReviewer"
|
||||
[user]="file.currentReviewer"
|
||||
[withName]="!!file.currentReviewer"
|
||||
[user]="file.assignee"
|
||||
[withName]="!!file.assignee"
|
||||
tooltipPosition="below"
|
||||
></redaction-initials-avatar>
|
||||
<div
|
||||
@ -62,15 +62,13 @@
|
||||
(save)="assignReviewer(file, $event)"
|
||||
*ngIf="editingReviewer"
|
||||
[options]="usersOptions(file, dossier)"
|
||||
[value]="file.currentReviewer"
|
||||
[value]="file.assignee"
|
||||
></redaction-assign-user-dropdown>
|
||||
|
||||
<div *ngIf="canAssign(file)" class="assign-actions-wrapper">
|
||||
<iqser-circle-button
|
||||
(action)="editingReviewer = true"
|
||||
*ngIf="
|
||||
(permissionsService.canAssignUser(file) || permissionsService.canUnassignUser(file)) && !!file.currentReviewer
|
||||
"
|
||||
*ngIf="(permissionsService.canAssignUser(file) || permissionsService.canUnassignUser(file)) && !!file.assignee"
|
||||
[tooltip]="assignTooltip(file)"
|
||||
icon="iqser:edit"
|
||||
tooltipPosition="below"
|
||||
|
||||
@ -178,7 +178,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
assignOrChangeReviewerTooltip(file: File): string {
|
||||
return file.currentReviewer
|
||||
return file.assignee
|
||||
? this._translateService.instant('file-preview.change-reviewer')
|
||||
: this._translateService.instant('file-preview.assign-reviewer');
|
||||
}
|
||||
@ -206,7 +206,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
canAssignReviewer(file: File, dossier: Dossier): boolean {
|
||||
return !file.currentReviewer && this.permissionsService.canAssignUser(file) && dossier.hasReviewers;
|
||||
return !file.assignee && this.permissionsService.canAssignUser(file) && dossier.hasReviewers;
|
||||
}
|
||||
|
||||
async updateViewMode(): Promise<void> {
|
||||
@ -487,12 +487,12 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
}
|
||||
|
||||
async assignReviewer(file: File, user: User | string) {
|
||||
const reviewerId = typeof user === 'string' ? user : user?.id;
|
||||
const reviewerName = this._userService.getNameForId(reviewerId);
|
||||
const assigneeId = typeof user === 'string' ? user : user?.id;
|
||||
const reviewerName = this._userService.getNameForId(assigneeId);
|
||||
|
||||
const { dossierId, fileId, filename } = file;
|
||||
this._loadingService.start();
|
||||
await this._filesService.setReviewerFor([fileId], dossierId, reviewerId).toPromise();
|
||||
await this._filesService.setReviewerFor([fileId], dossierId, assigneeId).toPromise();
|
||||
this._loadingService.stop();
|
||||
|
||||
this._toaster.info(_('assignment.reviewer'), { params: { reviewerName, filename } });
|
||||
|
||||
@ -22,7 +22,7 @@ export class FileAssignService {
|
||||
|
||||
async assignToMe(files: File[]) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const atLeastOneFileHasReviewer = files.reduce((acc, fs) => acc || !!fs.currentReviewer, false);
|
||||
const atLeastOneFileHasReviewer = files.reduce((acc, fs) => acc || !!fs.assignee, false);
|
||||
if (atLeastOneFileHasReviewer) {
|
||||
const data = new ConfirmationDialogInput({
|
||||
title: _('confirmation-dialog.assign-file-to-me.title'),
|
||||
@ -58,9 +58,9 @@ export class FileAssignService {
|
||||
const currentDossier = this._dossiersService.find(file.dossierId);
|
||||
const eligibleUsersIds = this._getUserIds(mode, currentDossier);
|
||||
|
||||
if (file.isUnassigned) {
|
||||
if (file.isNew) {
|
||||
await this._makeAssignFileRequest(currentUserId, mode, [file]);
|
||||
} else if (file.currentReviewer === currentUserId) {
|
||||
} else if (file.assignee === currentUserId) {
|
||||
if (eligibleUsersIds.includes(currentUserId)) {
|
||||
await this._makeAssignFileRequest(currentUserId, mode, [file]);
|
||||
} else if (eligibleUsersIds.length === 1) {
|
||||
|
||||
@ -3,6 +3,7 @@ import { ProcessingFileStatus, WorkflowFileStatus } from '@red/domain';
|
||||
|
||||
export const workflowFileStatusTranslations: { [key in WorkflowFileStatus]: string } = {
|
||||
APPROVED: _('file-status.approved'),
|
||||
NEW: _('file-status.new'),
|
||||
UNASSIGNED: _('file-status.unassigned'),
|
||||
UNDER_APPROVAL: _('file-status.under-approval'),
|
||||
UNDER_REVIEW: _('file-status.under-review'),
|
||||
|
||||
@ -22,32 +22,32 @@ export class FilesService extends EntitiesService<File, IFile> {
|
||||
|
||||
/** Reload dossier files + stats. */
|
||||
loadAll(dossierId: string) {
|
||||
const files$ = this.getFor(dossierId).pipe(mapEach(file => new File(file, this._userService.getNameForId(file.currentReviewer))));
|
||||
const files$ = this.getFor(dossierId).pipe(mapEach(file => new File(file, this._userService.getNameForId(file.assignee))));
|
||||
const loadStats$ = files$.pipe(switchMap(files => this._dossierStatsService.getFor([dossierId]).pipe(mapTo(files))));
|
||||
return loadStats$.pipe(tap(files => this._filesMapService.set(dossierId, files)));
|
||||
}
|
||||
|
||||
reload(dossierId: string, fileId: string): Observable<File> {
|
||||
return super._getOne([dossierId, fileId]).pipe(
|
||||
map(file => new File(file, this._userService.getNameForId(file.currentReviewer))),
|
||||
map(file => new File(file, this._userService.getNameForId(file.assignee))),
|
||||
switchMap(file => this._dossierStatsService.getFor([dossierId]).pipe(mapTo(file))),
|
||||
tap(file => this._filesMapService.replace(file)),
|
||||
);
|
||||
}
|
||||
|
||||
@Validate()
|
||||
setUnderApprovalFor(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, approverId: string) {
|
||||
setUnderApprovalFor(@RequiredParam() fileIds: List, @RequiredParam() dossierId: string, asigneeId: string) {
|
||||
const url = `${this._defaultModelPath}/under-approval/${dossierId}/bulk`;
|
||||
return this._post<unknown>(fileIds, url, [{ key: 'approverId', value: approverId }]).pipe(switchMapTo(this.loadAll(dossierId)));
|
||||
return this._post<unknown>(fileIds, url, [{ key: 'assigneeId', value: asigneeId }]).pipe(switchMapTo(this.loadAll(dossierId)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a reviewer for a list of files.
|
||||
*/
|
||||
@Validate()
|
||||
setReviewerFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string, reviewerId: string) {
|
||||
const url = `${this._defaultModelPath}/set-reviewer/${dossierId}/bulk`;
|
||||
return this._post<unknown>(filesIds, url, [{ key: 'reviewerId', value: reviewerId }]).pipe(switchMapTo(this.loadAll(dossierId)));
|
||||
setReviewerFor(@RequiredParam() filesIds: List, @RequiredParam() dossierId: string, assigneeId: string) {
|
||||
const url = `${this._defaultModelPath}/under-review/${dossierId}/bulk`;
|
||||
return this._post<unknown>(filesIds, url, [{ key: 'assigneeId', value: assigneeId }]).pipe(switchMapTo(this.loadAll(dossierId)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -19,20 +19,20 @@ export class PermissionsService {
|
||||
}
|
||||
|
||||
canToggleAnalysis(file: File): boolean {
|
||||
return this.isReviewerOrApprover(file) && (file.isUnassigned || file.isUnderReview || file.isUnderApproval);
|
||||
return this.isReviewerOrApprover(file) && (file.isNew || file.isUnderReview || file.isUnderApproval);
|
||||
}
|
||||
|
||||
canReanalyseFile(file: File): boolean {
|
||||
return this.isReviewerOrApprover(file) || file.isUnassigned || (file.isError && file.isUnassigned);
|
||||
return this.isReviewerOrApprover(file) || file.isNew || (file.isError && file.isNew);
|
||||
}
|
||||
|
||||
isFileReviewer(file: File): boolean {
|
||||
return file.currentReviewer === this._userService.currentUser.id;
|
||||
return file.assignee === this._userService.currentUser.id;
|
||||
}
|
||||
|
||||
canDeleteFile(file: File): boolean {
|
||||
const dossier = this._getDossier(file);
|
||||
return (this.isOwner(dossier) && !file.isApproved) || file.isUnassigned;
|
||||
return (this.isOwner(dossier) && !file.isApproved) || file.isNew;
|
||||
}
|
||||
|
||||
canAssignToSelf(file: File): boolean {
|
||||
@ -43,8 +43,8 @@ export class PermissionsService {
|
||||
|
||||
if (precondition) {
|
||||
if (
|
||||
(file.isUnassigned || (file.isUnderReview && !this.isFileReviewer(file))) &&
|
||||
(this.isApprover(dossier) || isTheOnlyReviewer || (this.isDossierReviewer(dossier) && file.isUnassigned))
|
||||
(file.isNew || (file.isUnderReview && !this.isFileReviewer(file))) &&
|
||||
(this.isApprover(dossier) || isTheOnlyReviewer || (this.isDossierReviewer(dossier) && file.isNew))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@ -57,7 +57,7 @@ export class PermissionsService {
|
||||
const precondition = !file.isProcessing && !file.isError && !file.isApproved && this.isApprover(dossier);
|
||||
|
||||
if (precondition) {
|
||||
if ((file.isUnassigned || file.isUnderReview) && dossier.hasReviewers) {
|
||||
if ((file.isNew || file.isUnderReview) && dossier.hasReviewers) {
|
||||
return true;
|
||||
}
|
||||
if (file.isUnderApproval && dossier.approverIds.length > 1) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"ADMIN_CONTACT_NAME": null,
|
||||
"ADMIN_CONTACT_URL": null,
|
||||
"API_URL": "https://dev-06.iqser.cloud/redaction-gateway-v1",
|
||||
"API_URL": "https://dev-04.iqser.cloud/redaction-gateway-v1",
|
||||
"APP_NAME": "RedactManager",
|
||||
"AUTO_READ_TIME": 1.5,
|
||||
"BACKEND_APP_VERSION": "4.4.40",
|
||||
@ -17,7 +17,7 @@
|
||||
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
||||
"OAUTH_CLIENT_ID": "redaction",
|
||||
"OAUTH_IDP_HINT": null,
|
||||
"OAUTH_URL": "https://dev-06.iqser.cloud/auth/realms/redaction",
|
||||
"OAUTH_URL": "https://dev-04.iqser.cloud/auth/realms/redaction",
|
||||
"RECENT_PERIOD_IN_HOURS": 24,
|
||||
"SELECTION_MODE": "structural"
|
||||
}
|
||||
|
||||
@ -733,7 +733,8 @@
|
||||
"assigned-to-me": "Assigned to me",
|
||||
"assigned-to-others": "Assigned to others",
|
||||
"recent": "Recent ({hours} h)",
|
||||
"unassigned": "Unassigned"
|
||||
"unassigned": "Unassigned",
|
||||
"new": "New"
|
||||
},
|
||||
"reanalyse-dossier": {
|
||||
"error": "Failed to schedule files for reanalysis. Please try again.",
|
||||
@ -1097,6 +1098,7 @@
|
||||
"processing": "Processing...",
|
||||
"reprocess": "Processing",
|
||||
"unassigned": "Unassigned",
|
||||
"new": "New",
|
||||
"under-approval": "Under Approval",
|
||||
"under-review": "Under Review",
|
||||
"unprocessed": "Unprocessed"
|
||||
@ -1187,6 +1189,7 @@
|
||||
},
|
||||
"initials-avatar": {
|
||||
"unassigned": "Unassigned",
|
||||
"new": "New",
|
||||
"you": "You"
|
||||
},
|
||||
"justifications": "Justifications",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
@use 'variables';
|
||||
@use 'common-mixins';
|
||||
|
||||
.UNASSIGNED {
|
||||
.NEW {
|
||||
stroke: variables.$grey-5;
|
||||
background-color: variables.$grey-5;
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 4621e95481fc48b682c46d75d94b5af66426368e
|
||||
Subproject commit 5d6e3053fe6a92eb7bc9fe3e44934bd3c6f5943d
|
||||
@ -10,7 +10,7 @@ export class File extends Entity<IFile> implements IFile {
|
||||
readonly analysisDuration?: number;
|
||||
readonly analysisRequired: boolean;
|
||||
readonly approvalDate?: string;
|
||||
readonly currentReviewer?: string;
|
||||
readonly assignee?: string;
|
||||
readonly dictionaryVersion?: number;
|
||||
readonly dossierDictionaryVersion?: number;
|
||||
readonly dossierId: string;
|
||||
@ -26,6 +26,7 @@ export class File extends Entity<IFile> implements IFile {
|
||||
readonly lastOCRTime?: string;
|
||||
readonly lastProcessed?: string;
|
||||
readonly lastReviewer?: string;
|
||||
readonly lastApprover?: string;
|
||||
readonly lastUpdated?: string;
|
||||
readonly lastUploaded?: string;
|
||||
readonly legalBasisVersion?: number;
|
||||
@ -43,7 +44,8 @@ export class File extends Entity<IFile> implements IFile {
|
||||
readonly cacheIdentifier?: string;
|
||||
readonly hintsOnly: boolean;
|
||||
readonly hasNone: boolean;
|
||||
readonly isUnassigned: boolean;
|
||||
readonly isNew: boolean;
|
||||
// readonly isUnassigned: boolean;
|
||||
readonly isError: boolean;
|
||||
readonly isProcessing: boolean;
|
||||
readonly isApproved: boolean;
|
||||
@ -61,7 +63,7 @@ export class File extends Entity<IFile> implements IFile {
|
||||
this.analysisDuration = file.analysisDuration;
|
||||
this.analysisRequired = !!file.analysisRequired && !file.excluded;
|
||||
this.approvalDate = file.approvalDate;
|
||||
this.currentReviewer = file.currentReviewer;
|
||||
this.assignee = file.assignee;
|
||||
this.dictionaryVersion = file.dictionaryVersion;
|
||||
this.dossierDictionaryVersion = file.dossierDictionaryVersion;
|
||||
this.dossierId = file.dossierId;
|
||||
@ -77,6 +79,7 @@ export class File extends Entity<IFile> implements IFile {
|
||||
this.lastOCRTime = file.lastOCRTime;
|
||||
this.lastProcessed = file.lastProcessed;
|
||||
this.lastReviewer = file.lastReviewer;
|
||||
this.lastApprover = file.lastApprover;
|
||||
this.lastUpdated = file.lastUpdated;
|
||||
this.lastUploaded = file.lastUploaded;
|
||||
this.legalBasisVersion = file.legalBasisVersion;
|
||||
@ -99,12 +102,13 @@ export class File extends Entity<IFile> implements IFile {
|
||||
this.isPending = this.processingStatus === ProcessingFileStatuses.UNPROCESSED;
|
||||
this.isProcessing = isProcessingStatuses.includes(this.processingStatus);
|
||||
this.isApproved = this.workflowStatus === WorkflowFileStatuses.APPROVED;
|
||||
this.isUnassigned = this.workflowStatus === WorkflowFileStatuses.UNASSIGNED;
|
||||
this.isNew = this.workflowStatus === WorkflowFileStatuses.NEW;
|
||||
// this.isUnassigned = !this.assignee;
|
||||
this.isUnderReview = this.workflowStatus === WorkflowFileStatuses.UNDER_REVIEW;
|
||||
this.isUnderApproval = this.workflowStatus === WorkflowFileStatuses.UNDER_APPROVAL;
|
||||
this.canBeApproved = !this.analysisRequired && !this.hasSuggestions;
|
||||
this.canBeOpened = !this.isError && !this.isPending && this.numberOfAnalyses > 0;
|
||||
this.canBeOCRed = !this.excluded && !this.lastOCRTime && (this.isUnassigned || this.isUnderReview || this.isUnderApproval);
|
||||
this.canBeOCRed = !this.excluded && !this.lastOCRTime && (this.isNew || this.isUnderReview || this.isUnderApproval);
|
||||
|
||||
if (!this.fileAttributes || !this.fileAttributes.attributeIdToValue) {
|
||||
this.fileAttributes = { attributeIdToValue: {} };
|
||||
|
||||
@ -28,7 +28,7 @@ export interface IFile {
|
||||
/**
|
||||
* The current reviewer's (if any) user id.
|
||||
*/
|
||||
readonly currentReviewer?: string;
|
||||
readonly assignee?: string;
|
||||
/**
|
||||
* Shows which dictionary versions was used during the analysis.
|
||||
*/
|
||||
@ -102,6 +102,10 @@ export interface IFile {
|
||||
* The last reviewer's (if any) user id.
|
||||
*/
|
||||
readonly lastReviewer?: string;
|
||||
/**
|
||||
* The last approver's (if any) user id.
|
||||
*/
|
||||
readonly lastApprover?: string;
|
||||
/**
|
||||
* Date and time when the file was last updated.
|
||||
*/
|
||||
|
||||
@ -2,6 +2,7 @@ import { List } from '@iqser/common-ui';
|
||||
|
||||
export const WorkflowFileStatuses = {
|
||||
APPROVED: 'APPROVED',
|
||||
NEW: 'NEW',
|
||||
UNASSIGNED: 'UNASSIGNED',
|
||||
UNDER_APPROVAL: 'UNDER_APPROVAL',
|
||||
UNDER_REVIEW: 'UNDER_REVIEW',
|
||||
|
||||
@ -6,6 +6,7 @@ export const ReportStatuses = {
|
||||
OCR_PROCESSING: 'OCR_PROCESSING',
|
||||
PROCESSING: 'PROCESSING',
|
||||
REPROCESS: 'REPROCESS',
|
||||
NEW: 'NEW',
|
||||
UNASSIGNED: 'UNASSIGNED',
|
||||
UNDER_APPROVAL: 'UNDER_APPROVAL',
|
||||
UNDER_REVIEW: 'UNDER_REVIEW',
|
||||
|
||||
@ -6,6 +6,7 @@ type Sorter = Record<WorkflowFileStatus, number> & {
|
||||
};
|
||||
|
||||
export const StatusSorter: Sorter = {
|
||||
NEW: 0,
|
||||
UNASSIGNED: 1,
|
||||
UNDER_REVIEW: 2,
|
||||
UNDER_APPROVAL: 3,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user