mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-25 09:50:19 +00:00
EZQMS-1109: Add signature details for reviews/approvals (#6111)
* ezqms-1109: add signature details for reviews/approvals Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
parent
93b798c99b
commit
8163a30b77
@ -14,7 +14,7 @@
|
|||||||
-->
|
-->
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Ref, SortingOrder } from '@hcengineering/core'
|
import { Ref, SortingOrder } from '@hcengineering/core'
|
||||||
import { Label, Scroller, getUserTimezone } from '@hcengineering/ui'
|
import { Label, Scroller } from '@hcengineering/ui'
|
||||||
import { createQuery } from '@hcengineering/presentation'
|
import { createQuery } from '@hcengineering/presentation'
|
||||||
import documents, { DocumentApprovalRequest, DocumentReviewRequest } from '@hcengineering/controlled-documents'
|
import documents, { DocumentApprovalRequest, DocumentReviewRequest } from '@hcengineering/controlled-documents'
|
||||||
import { employeeByIdStore, personAccountByIdStore } from '@hcengineering/contact-resources'
|
import { employeeByIdStore, personAccountByIdStore } from '@hcengineering/contact-resources'
|
||||||
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
import documentsRes from '../../plugin'
|
import documentsRes from '../../plugin'
|
||||||
import { $controlledDocument as controlledDocument } from '../../stores/editors/document/editor'
|
import { $controlledDocument as controlledDocument } from '../../stores/editors/document/editor'
|
||||||
|
import { formatSignatureDate } from '../../utils'
|
||||||
|
|
||||||
interface Signer {
|
interface Signer {
|
||||||
id?: Ref<Person>
|
id?: Ref<Person>
|
||||||
@ -38,7 +39,6 @@
|
|||||||
|
|
||||||
const reviewQuery = createQuery()
|
const reviewQuery = createQuery()
|
||||||
const approvalQuery = createQuery()
|
const approvalQuery = createQuery()
|
||||||
const timeZone: string = getUserTimezone()
|
|
||||||
|
|
||||||
$: if ($controlledDocument !== undefined) {
|
$: if ($controlledDocument !== undefined) {
|
||||||
reviewQuery.query(
|
reviewQuery.query(
|
||||||
@ -92,7 +92,7 @@
|
|||||||
id: $controlledDocument.author,
|
id: $controlledDocument.author,
|
||||||
role: 'author',
|
role: 'author',
|
||||||
name: getNameByEmployeeId($controlledDocument.author),
|
name: getNameByEmployeeId($controlledDocument.author),
|
||||||
date: $controlledDocument.createdOn !== undefined ? formatDate($controlledDocument.createdOn) : ''
|
date: $controlledDocument.createdOn !== undefined ? formatSignatureDate($controlledDocument.createdOn) : ''
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -105,7 +105,7 @@
|
|||||||
id: rAcc?.person,
|
id: rAcc?.person,
|
||||||
role: 'reviewer',
|
role: 'reviewer',
|
||||||
name: getNameByEmployeeId(rAcc?.person),
|
name: getNameByEmployeeId(rAcc?.person),
|
||||||
date: formatDate(date ?? reviewRequest.modifiedOn)
|
date: formatSignatureDate(date ?? reviewRequest.modifiedOn)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -119,25 +119,12 @@
|
|||||||
id: aAcc?.person,
|
id: aAcc?.person,
|
||||||
role: 'approver',
|
role: 'approver',
|
||||||
name: getNameByEmployeeId(aAcc?.person),
|
name: getNameByEmployeeId(aAcc?.person),
|
||||||
date: formatDate(date ?? approvalRequest.modifiedOn)
|
date: formatSignatureDate(date ?? approvalRequest.modifiedOn)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDate (date: number): string {
|
|
||||||
return new Date(date).toLocaleDateString('default', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
timeZone,
|
|
||||||
timeZoneName: 'short',
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
second: 'numeric'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSignerLabel (role: 'author' | 'reviewer' | 'approver'): IntlString {
|
function getSignerLabel (role: 'author' | 'reviewer' | 'approver'): IntlString {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case 'author':
|
case 'author':
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
import { slide } from 'svelte/transition'
|
import { slide } from 'svelte/transition'
|
||||||
import documents, { DocumentRequest } from '@hcengineering/controlled-documents'
|
import documents, { DocumentRequest } from '@hcengineering/controlled-documents'
|
||||||
import chunter from '@hcengineering/chunter'
|
import chunter from '@hcengineering/chunter'
|
||||||
import { PersonAccount } from '@hcengineering/contact'
|
import { type Person, type PersonAccount } from '@hcengineering/contact'
|
||||||
import { PersonAccountRefPresenter } from '@hcengineering/contact-resources'
|
import { PersonAccountRefPresenter, personAccountByIdStore } from '@hcengineering/contact-resources'
|
||||||
import { Ref } from '@hcengineering/core'
|
import { Ref } from '@hcengineering/core'
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { Chevron, Label } from '@hcengineering/ui'
|
import { Chevron, Label, tooltip } from '@hcengineering/ui'
|
||||||
|
|
||||||
import { $documentSnapshots as documentSnapshots } from '../../../stores/editors/document'
|
import { $documentSnapshots as documentSnapshots } from '../../../stores/editors/document'
|
||||||
import documentsRes from '../../../plugin'
|
import documentsRes from '../../../plugin'
|
||||||
@ -14,13 +14,16 @@
|
|||||||
import RejectedIcon from '../../icons/Rejected.svelte'
|
import RejectedIcon from '../../icons/Rejected.svelte'
|
||||||
import CancelledIcon from '../../icons/Cancelled.svelte'
|
import CancelledIcon from '../../icons/Cancelled.svelte'
|
||||||
import WaitingIcon from '../../icons/Waiting.svelte'
|
import WaitingIcon from '../../icons/Waiting.svelte'
|
||||||
|
import SignatureInfo from './SignatureInfo.svelte'
|
||||||
|
|
||||||
export let request: DocumentRequest
|
export let request: DocumentRequest
|
||||||
export let initiallyExpanded: boolean = false
|
export let initiallyExpanded: boolean = false
|
||||||
|
|
||||||
interface PersonalApproval {
|
interface PersonalApproval {
|
||||||
account: Ref<PersonAccount>
|
account: Ref<PersonAccount>
|
||||||
|
person?: Ref<Person>
|
||||||
approved: 'approved' | 'rejected' | 'cancelled' | 'waiting'
|
approved: 'approved' | 'rejected' | 'cancelled' | 'waiting'
|
||||||
|
timestamp?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
@ -31,38 +34,45 @@
|
|||||||
let rejectingMessage: string | undefined
|
let rejectingMessage: string | undefined
|
||||||
let approvals: PersonalApproval[] = []
|
let approvals: PersonalApproval[] = []
|
||||||
|
|
||||||
$: if (request != null) {
|
$: void getRequestData(request)
|
||||||
void getRequestData()
|
|
||||||
}
|
|
||||||
|
|
||||||
$: type = hierarchy.isDerived(request._class, documents.class.DocumentApprovalRequest)
|
$: type = hierarchy.isDerived(request._class, documents.class.DocumentApprovalRequest)
|
||||||
? documents.string.Approval
|
? documents.string.Approval
|
||||||
: documents.string.Review
|
: documents.string.Review
|
||||||
|
|
||||||
async function getRequestData (): Promise<void> {
|
async function getRequestData (req: DocumentRequest): Promise<void> {
|
||||||
if (request !== undefined) {
|
if (req == null) {
|
||||||
approvals = await getApprovals(request)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
approvals = await getApprovals(req, $personAccountByIdStore)
|
||||||
const rejectingComment = await client.findOne(chunter.class.ChatMessage, {
|
const rejectingComment = await client.findOne(chunter.class.ChatMessage, {
|
||||||
attachedTo: request?._id,
|
attachedTo: req?._id,
|
||||||
attachedToClass: request?._class
|
attachedToClass: req?._class
|
||||||
})
|
})
|
||||||
rejectingMessage = rejectingComment?.message
|
rejectingMessage = rejectingComment?.message
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async function getApprovals (req: DocumentRequest): Promise<PersonalApproval[]> {
|
async function getApprovals (
|
||||||
|
req: DocumentRequest,
|
||||||
|
accountById: typeof $personAccountByIdStore
|
||||||
|
): Promise<PersonalApproval[]> {
|
||||||
const rejectedBy: PersonalApproval[] =
|
const rejectedBy: PersonalApproval[] =
|
||||||
req.rejected !== undefined
|
req.rejected !== undefined
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
account: req.rejected,
|
account: req.rejected,
|
||||||
approved: 'rejected'
|
person: accountById.get(req.rejected)?.person,
|
||||||
|
approved: 'rejected',
|
||||||
|
timestamp: req.modifiedOn
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []
|
: []
|
||||||
const approvedBy: PersonalApproval[] = req.approved.map((id) => ({
|
const approvedBy: PersonalApproval[] = req.approved.map((id, idx) => ({
|
||||||
account: id,
|
account: id,
|
||||||
approved: 'approved'
|
person: accountById.get(id)?.person,
|
||||||
|
approved: 'approved',
|
||||||
|
timestamp: req.approvedDates?.[idx] ?? req.modifiedOn
|
||||||
}))
|
}))
|
||||||
const ignoredBy = req.requested
|
const ignoredBy = req.requested
|
||||||
.filter((p) => p !== req?.rejected)
|
.filter((p) => p !== req?.rejected)
|
||||||
@ -70,6 +80,7 @@
|
|||||||
.map(
|
.map(
|
||||||
(id): PersonalApproval => ({
|
(id): PersonalApproval => ({
|
||||||
account: id,
|
account: id,
|
||||||
|
person: accountById.get(id)?.person,
|
||||||
approved: req?.rejected !== undefined ? 'cancelled' : 'waiting'
|
approved: req?.rejected !== undefined ? 'cancelled' : 'waiting'
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -115,6 +126,19 @@
|
|||||||
{#each approvals as approver}
|
{#each approvals as approver}
|
||||||
<div class="approver">
|
<div class="approver">
|
||||||
<PersonAccountRefPresenter value={approver.account} avatarSize="x-small" />
|
<PersonAccountRefPresenter value={approver.account} avatarSize="x-small" />
|
||||||
|
{#key approver.timestamp}
|
||||||
|
<!-- For some reason tooltip is not interactive w/o remount -->
|
||||||
|
<span
|
||||||
|
use:tooltip={approver.timestamp !== undefined
|
||||||
|
? {
|
||||||
|
component: SignatureInfo,
|
||||||
|
props: {
|
||||||
|
id: approver.person,
|
||||||
|
timestamp: approver.timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: undefined}
|
||||||
|
>
|
||||||
{#if approver.approved === 'approved'}
|
{#if approver.approved === 'approved'}
|
||||||
<ApprovedIcon size="medium" fill={'var(--theme-docs-accepted-color)'} />
|
<ApprovedIcon size="medium" fill={'var(--theme-docs-accepted-color)'} />
|
||||||
{:else if approver.approved === 'rejected'}
|
{:else if approver.approved === 'rejected'}
|
||||||
@ -124,6 +148,8 @@
|
|||||||
{:else if approver.approved === 'waiting'}
|
{:else if approver.approved === 'waiting'}
|
||||||
<WaitingIcon size="medium" />
|
<WaitingIcon size="medium" />
|
||||||
{/if}
|
{/if}
|
||||||
|
</span>
|
||||||
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
{#if rejectingMessage !== undefined && approver.approved === 'rejected'}
|
{#if rejectingMessage !== undefined && approver.approved === 'rejected'}
|
||||||
<div class="reject-message">{@html rejectingMessage}</div>
|
<div class="reject-message">{@html rejectingMessage}</div>
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
<!--
|
||||||
|
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||||
|
// Copyright © 2022, 2023, 2024 Hardcore Engineering Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License. You may
|
||||||
|
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
//
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { formatSignatureDate } from '../../../utils'
|
||||||
|
|
||||||
|
export let id: string
|
||||||
|
export let timestamp: number
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>ID: {id}</div>
|
||||||
|
<div>{formatSignatureDate(timestamp)}</div>
|
@ -35,7 +35,7 @@ import contact, { type Employee, type PersonAccount } from '@hcengineering/conta
|
|||||||
import request, { RequestStatus } from '@hcengineering/request'
|
import request, { RequestStatus } from '@hcengineering/request'
|
||||||
import textEditor from '@hcengineering/text-editor'
|
import textEditor from '@hcengineering/text-editor'
|
||||||
import { isEmptyMarkup } from '@hcengineering/text'
|
import { isEmptyMarkup } from '@hcengineering/text'
|
||||||
import { getEventPositionElement, showPopup, type Location } from '@hcengineering/ui'
|
import { getEventPositionElement, showPopup, getUserTimezone, type Location } from '@hcengineering/ui'
|
||||||
import { type KeyFilter } from '@hcengineering/view'
|
import { type KeyFilter } from '@hcengineering/view'
|
||||||
import chunter from '@hcengineering/chunter'
|
import chunter from '@hcengineering/chunter'
|
||||||
import documents, {
|
import documents, {
|
||||||
@ -835,3 +835,18 @@ export async function createTemplate (space: OrgSpace): Promise<void> {
|
|||||||
wizardOpened({ $$currentStep: 'info', location: { space: space._id, project: project ?? documents.ids.NoProject } })
|
wizardOpened({ $$currentStep: 'info', location: { space: space._id, project: project ?? documents.ids.NoProject } })
|
||||||
showPopup(documents.component.QmsTemplateWizard, {})
|
showPopup(documents.component.QmsTemplateWizard, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatSignatureDate (date: number): string {
|
||||||
|
const timeZone: string = getUserTimezone()
|
||||||
|
|
||||||
|
return new Date(date).toLocaleDateString('default', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
timeZone,
|
||||||
|
timeZoneName: 'short',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
second: 'numeric'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@ export class DocumentApprovalsPage extends DocumentCommonPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async checkSuccessApproval (approvalName: string): Promise<void> {
|
async checkSuccessApproval (approvalName: string): Promise<void> {
|
||||||
await expect(this.page.locator('svg[fill*="accepted"]').locator('xpath=..').locator('span.ap-label')).toHaveText(
|
await expect(this.page.locator('svg[fill*="accepted"]').locator('xpath=../..').locator('span.ap-label')).toHaveText(
|
||||||
approvalName
|
approvalName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user