EZQMS-1069: Fix request model ()

Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
Alexey Zinoviev 2024-07-27 19:05:20 +04:00 committed by GitHub
parent 77f0a2c3e1
commit ed903a9396
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 155 additions and 72 deletions
models
plugins
controlled-documents-resources/src
components/document
stores/editors/document
text.tsutils.ts
request-resources/src/components
request/src

View File

@ -49,6 +49,7 @@ import { questionsOperation } from '@hcengineering/model-questions'
import { trainingOperation } from '@hcengineering/model-training'
import { documentsOperation } from '@hcengineering/model-controlled-documents'
import { productsOperation } from '@hcengineering/model-products'
import { requestOperation } from '@hcengineering/model-request'
export const migrateOperations: [string, MigrateOperation][] = [
['core', coreOperation],
@ -72,6 +73,7 @@ export const migrateOperations: [string, MigrateOperation][] = [
['documents', documentsOperation],
['questions', questionsOperation],
['training', trainingOperation],
['request', requestOperation],
['products', productsOperation],
['board', boardOperation],
['hr', hrOperation],

View File

@ -14,7 +14,7 @@
//
import activity from '@hcengineering/activity'
import type { PersonAccount } from '@hcengineering/contact'
import type { Person } from '@hcengineering/contact'
import contact from '@hcengineering/contact'
import { type Timestamp, type Domain, type Ref, type Tx } from '@hcengineering/core'
import {
@ -43,6 +43,7 @@ import {
import { type AnyComponent } from '@hcengineering/ui/src/types'
import request from './plugin'
export { requestOperation } from './migration'
export { requestId } from '@hcengineering/request'
export { default } from './plugin'
@ -51,13 +52,13 @@ export const DOMAIN_REQUEST = 'request' as Domain
@Model(request.class.Request, core.class.AttachedDoc, DOMAIN_REQUEST)
@UX(request.string.Request, request.icon.Requests)
export class TRequest extends TAttachedDoc implements Request {
@Prop(ArrOf(TypeRef(contact.class.PersonAccount)), request.string.Requested)
@Prop(ArrOf(TypeRef(contact.class.Person)), request.string.Requested)
// @Index(IndexKind.Indexed)
requested!: Ref<PersonAccount>[]
requested!: Ref<Person>[]
@Prop(ArrOf(TypeRef(contact.class.PersonAccount)), request.string.Approved)
@Prop(ArrOf(TypeRef(contact.class.Person)), request.string.Approved)
@ReadOnly()
approved!: Ref<PersonAccount>[]
approved!: Ref<Person>[]
approvedDates?: Timestamp[]
@ -70,9 +71,9 @@ export class TRequest extends TAttachedDoc implements Request {
tx!: Tx
rejectedTx?: Tx
@Prop(TypeRef(contact.class.PersonAccount), request.string.Rejected)
@Prop(TypeRef(contact.class.Person), request.string.Rejected)
@ReadOnly()
rejected?: Ref<PersonAccount>
rejected?: Ref<Person>
@Prop(Collection(chunter.class.ChatMessage), chunter.string.Comments)
comments?: number

View File

@ -0,0 +1,99 @@
//
// Copyright © 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.
//
import core, { DOMAIN_TX, type Ref, type TxCreateDoc } from '@hcengineering/core'
import request, { requestId, type Request } from '@hcengineering/request'
import {
type MigrateUpdate,
type MigrationDocumentQuery,
tryMigrate,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient,
type ModelLogger
} from '@hcengineering/model'
import contact, { type Person, type PersonAccount } from '@hcengineering/contact'
import { DOMAIN_REQUEST } from '.'
async function migrateRequestPersonAccounts (client: MigrationClient): Promise<void> {
const descendants = client.hierarchy.getDescendants(request.class.Request)
const requests = await client.find<Request>(DOMAIN_REQUEST, {
_class: { $in: descendants }
})
const personAccountsCreateTxes = await client.find(DOMAIN_TX, {
_class: core.class.TxCreateDoc,
objectClass: contact.class.PersonAccount
})
const personAccountToPersonMap = personAccountsCreateTxes.reduce<Record<Ref<PersonAccount>, Ref<Person>>>(
(map, tx) => {
const ctx = tx as TxCreateDoc<PersonAccount>
map[ctx.objectId] = ctx.attributes.person
return map
},
{}
)
const operations: { filter: MigrationDocumentQuery<Request>, update: MigrateUpdate<Request> }[] = []
for (const request of requests) {
const newRequestedPersons = request.requested
.map((paId) => personAccountToPersonMap[paId as unknown as Ref<PersonAccount>])
.filter((p) => p != null)
const newApprovedPersons = request.approved
.map((paId) => personAccountToPersonMap[paId as unknown as Ref<PersonAccount>])
.filter((p) => p != null)
const newRejectedPerson =
request.rejected != null ? personAccountToPersonMap[request.rejected as unknown as Ref<PersonAccount>] : undefined
if (newRequestedPersons.length > 0) {
operations.push({
filter: {
_id: request._id
},
update: {
requested: newRequestedPersons,
approved: newApprovedPersons
}
})
}
if (newRejectedPerson !== undefined) {
operations.push({
filter: {
_id: request._id
},
update: {
rejected: newRejectedPerson
}
})
}
}
if (operations.length > 0) {
await client.bulk(DOMAIN_REQUEST, operations)
}
}
export const requestOperation: MigrateOperation = {
async migrate (client: MigrationClient, logger: ModelLogger): Promise<void> {
await tryMigrate(client, requestId, [
{
state: 'migrateRequestPersonAccounts',
func: migrateRequestPersonAccounts
}
])
},
async upgrade (state: Map<string, Set<string>>, client: () => Promise<MigrationUpgradeClient>): Promise<void> {}
}

View File

@ -1,5 +1,5 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
// Copyright © 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
@ -17,7 +17,7 @@
import { Label, Scroller } from '@hcengineering/ui'
import { createQuery } from '@hcengineering/presentation'
import documents, { DocumentApprovalRequest, DocumentReviewRequest } from '@hcengineering/controlled-documents'
import { employeeByIdStore, personAccountByIdStore } from '@hcengineering/contact-resources'
import { employeeByIdStore } from '@hcengineering/contact-resources'
import { Employee, Person, formatName } from '@hcengineering/contact'
import { IntlString } from '@hcengineering/platform'
@ -98,13 +98,12 @@
if (reviewRequest !== undefined) {
reviewRequest.approved.forEach((reviewer, idx) => {
const rAcc = $personAccountByIdStore.get(reviewer)
const date = reviewRequest.approvedDates?.[idx]
signers.push({
id: rAcc?.person,
id: reviewer,
role: 'reviewer',
name: getNameByEmployeeId(rAcc?.person),
name: getNameByEmployeeId(reviewer),
date: formatSignatureDate(date ?? reviewRequest.modifiedOn)
})
})
@ -112,13 +111,12 @@
if (approvalRequest !== undefined) {
approvalRequest.approved.forEach((approver, idx) => {
const aAcc = $personAccountByIdStore.get(approver)
const date = approvalRequest.approvedDates?.[idx]
signers.push({
id: aAcc?.person,
id: approver,
role: 'approver',
name: getNameByEmployeeId(aAcc?.person),
name: getNameByEmployeeId(approver),
date: formatSignatureDate(date ?? approvalRequest.modifiedOn)
})
})

View File

@ -2,8 +2,8 @@
import { slide } from 'svelte/transition'
import documents, { DocumentRequest } from '@hcengineering/controlled-documents'
import chunter from '@hcengineering/chunter'
import { type Person, type PersonAccount } from '@hcengineering/contact'
import { PersonAccountRefPresenter, personAccountByIdStore } from '@hcengineering/contact-resources'
import { type Person } from '@hcengineering/contact'
import { PersonRefPresenter, personAccountByIdStore } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Chevron, Label, tooltip } from '@hcengineering/ui'
@ -20,7 +20,6 @@
export let initiallyExpanded: boolean = false
interface PersonalApproval {
account: Ref<PersonAccount>
person?: Ref<Person>
approved: 'approved' | 'rejected' | 'cancelled' | 'waiting'
timestamp?: number
@ -61,16 +60,14 @@
req.rejected !== undefined
? [
{
account: req.rejected,
person: accountById.get(req.rejected)?.person,
person: req.rejected,
approved: 'rejected',
timestamp: req.modifiedOn
}
]
: []
const approvedBy: PersonalApproval[] = req.approved.map((id, idx) => ({
account: id,
person: accountById.get(id)?.person,
person: id,
approved: 'approved',
timestamp: req.approvedDates?.[idx] ?? req.modifiedOn
}))
@ -79,8 +76,7 @@
.filter((p) => !(req?.approved as string[]).includes(p))
.map(
(id): PersonalApproval => ({
account: id,
person: accountById.get(id)?.person,
person: id,
approved: req?.rejected !== undefined ? 'cancelled' : 'waiting'
})
)
@ -125,7 +121,7 @@
<div class="section" transition:slide|local>
{#each approvals as approver}
<div class="approver">
<PersonAccountRefPresenter value={approver.account} avatarSize="x-small" />
<PersonRefPresenter value={approver.person} avatarSize="x-small" />
{#key approver.timestamp}
<!-- For some reason tooltip is not interactive w/o remount -->
<span

View File

@ -202,8 +202,8 @@ export const $documentStateForCurrentUser = combine($controlledDocument, $review
return ControlledDocumentState.InReview
}
const currentAccount = getCurrentAccount()._id as Ref<PersonAccount>
if (reviewRequest.approved?.includes(currentAccount)) {
const currentPerson = (getCurrentAccount() as PersonAccount).person
if (reviewRequest.approved?.includes(currentPerson)) {
return ControlledDocumentState.Reviewed
}
}
@ -228,7 +228,7 @@ export const $documentState = $controlledDocument.map((doc) => {
})
export const $documentReviewIsActive = combine($reviewRequest, $documentStateForCurrentUser, (reviewReq, state) => {
const me = getCurrentAccount()._id as Ref<PersonAccount>
const me = (getCurrentAccount() as PersonAccount).person
if (reviewReq == null) {
return false
@ -244,7 +244,7 @@ export const $documentApprovalIsActive = combine(
$approvalRequest,
$documentStateForCurrentUser,
(doc, approvalReq, state) => {
const me = getCurrentAccount()._id as Ref<PersonAccount>
const me = (getCurrentAccount() as PersonAccount).person
if (approvalReq == null) {
return false

View File

@ -56,8 +56,8 @@ async function getDocumentStateForCurrentUser (
return ControlledDocumentState.InReview
}
const currentAccount = getCurrentAccount()._id as Ref<PersonAccount>
if (reviewRequest.approved?.includes(currentAccount)) {
const me = (getCurrentAccount() as PersonAccount).person
if (reviewRequest.approved?.includes(me)) {
return ControlledDocumentState.Reviewed
}
}

View File

@ -31,7 +31,7 @@ import core, {
} from '@hcengineering/core'
import { type IntlString, getMetadata, getResource, translate } from '@hcengineering/platform'
import presentation, { copyDocumentContent, getClient } from '@hcengineering/presentation'
import contact, { type Employee, type PersonAccount } from '@hcengineering/contact'
import { type Person, type Employee, type PersonAccount } from '@hcengineering/contact'
import request, { RequestStatus } from '@hcengineering/request'
import textEditor from '@hcengineering/text-editor'
import { isEmptyMarkup } from '@hcengineering/text'
@ -313,16 +313,6 @@ export async function sendReviewRequest (
controlledDoc: ControlledDocument,
reviewers: Array<Ref<Employee>>
): Promise<void> {
const reviewersAccounts = await client.findAll(contact.class.PersonAccount, { person: { $in: reviewers } })
if (reviewersAccounts.length === 0) {
return
}
if (reviewersAccounts.length < reviewers.length) {
console.warn('Number of user accounts is less than requested for document review request')
}
const approveTx = client.txFactory.createTxUpdateDoc(controlledDoc._class, controlledDoc.space, controlledDoc._id, {
controlledState: ControlledDocumentState.Reviewed
})
@ -338,7 +328,7 @@ export async function sendReviewRequest (
controlledDoc._class,
documents.class.DocumentReviewRequest,
controlledDoc.space,
reviewersAccounts.map((u) => u._id),
reviewers,
approveTx,
undefined,
true
@ -350,16 +340,6 @@ export async function sendApprovalRequest (
controlledDoc: ControlledDocument,
approvers: Array<Ref<Employee>>
): Promise<void> {
const approversAccounts = await client.findAll(contact.class.PersonAccount, { person: { $in: approvers } })
if (approversAccounts.length === 0) {
return
}
if (approversAccounts.length < approvers.length) {
console.warn('Number of user accounts is less than requested for document approval request')
}
const approveTx = client.txFactory.createTxUpdateDoc(controlledDoc._class, controlledDoc.space, controlledDoc._id, {
controlledState: ControlledDocumentState.Approved
})
@ -379,7 +359,7 @@ export async function sendApprovalRequest (
controlledDoc._class,
documents.class.DocumentApprovalRequest,
controlledDoc.space,
approversAccounts.map((u) => u._id),
approvers,
approveTx,
rejectTx,
true
@ -392,7 +372,7 @@ async function createRequest<T extends Doc> (
attachedToClass: Ref<Class<T>>,
reqClass: Ref<Class<Request>>,
space: Ref<DocumentSpace>,
users: Array<Ref<PersonAccount>>,
users: Array<Ref<Person>>,
approveTx: Tx,
rejectedTx?: Tx,
areAllApprovesRequired = true
@ -429,7 +409,7 @@ export async function completeRequest (
): Promise<void> {
const req = await getActiveRequest(client, reqClass, controlledDoc)
const me = getCurrentAccount()._id as Ref<PersonAccount>
const me = (getCurrentAccount() as PersonAccount).person
if (req == null || !req.requested.includes(me) || req.approved.includes(me)) {
return
@ -465,7 +445,7 @@ export async function rejectRequest (
return
}
const me = getCurrentAccount()._id as Ref<PersonAccount>
const me = (getCurrentAccount() as PersonAccount).person
await saveComment(rejectionNote, req)

View File

@ -32,14 +32,16 @@
const client = getClient()
const me = getCurrentAccount()._id as Ref<PersonAccount>
const myPerson = (getCurrentAccount() as PersonAccount).person
const approvable = value.requested.filter((a) => a === me).length > value.approved.filter((a) => a === me).length
const approvable =
value.requested.filter((a) => a === myPerson).length > value.approved.filter((a) => a === myPerson).length
async function approve () {
await saveComment()
await client.update(value, {
$push: {
approved: me
approved: myPerson
}
})
}
@ -49,7 +51,7 @@
async function reject () {
await saveComment()
await client.update(value, {
rejected: me,
rejected: myPerson,
status: RequestStatus.Rejected
})
}

View File

@ -13,29 +13,34 @@
// limitations under the License.
-->
<script lang="ts">
import contact, { PersonAccount } from '@hcengineering/contact'
import { PersonAccountRefPresenter } from '@hcengineering/contact-resources'
import { Account, Ref } from '@hcengineering/core'
import contact, { Person, PersonAccount } from '@hcengineering/contact'
import { personAccountByIdStore, PersonRefPresenter } from '@hcengineering/contact-resources'
import { Ref } from '@hcengineering/core'
import { createQuery, MessageViewer } from '@hcengineering/presentation'
import { Request, RequestDecisionComment } from '@hcengineering/request'
import { BooleanIcon, Label, ShowMore } from '@hcengineering/ui'
import request from '../plugin'
export let value: Request
let comments = new Map<Ref<Account>, RequestDecisionComment>()
let comments = new Map<Ref<Person> | undefined, RequestDecisionComment>()
const query = createQuery()
$: query.query(request.mixin.RequestDecisionComment, { attachedTo: value._id }, (res) => {
comments = new Map(res.map((r) => [r.modifiedBy, r]))
comments = new Map(
res.map((r) => {
const personAccount = $personAccountByIdStore.get(r.modifiedBy as Ref<PersonAccount>)
return [personAccount?.person, r]
})
)
})
interface RequestDecision {
employee: Ref<PersonAccount>
employee: Ref<Person>
decision?: boolean
comment?: RequestDecisionComment
}
function convert (value: Request, comments: Map<Ref<Account>, RequestDecisionComment>): RequestDecision[] {
function convert (value: Request, comments: Map<Ref<Person> | undefined, RequestDecisionComment>): RequestDecision[] {
const res: RequestDecision[] = []
for (const emp of value.requested) {
const decision = value.rejected === emp ? false : value.approved.includes(emp) ? true : undefined
@ -63,7 +68,7 @@
<tbody>
{#each convert(value, comments) as requested}
<tr class="antiTable-body__row">
<td><PersonAccountRefPresenter value={requested.employee} /></td>
<td><PersonRefPresenter value={requested.employee} /></td>
<td><BooleanIcon value={requested.decision} /></td>
<td
>{#if requested.comment}

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import { PersonAccount } from '@hcengineering/contact'
import { type Person } from '@hcengineering/contact'
import type { AttachedDoc, Class, Doc, Mixin, Ref, Timestamp, Tx } from '@hcengineering/core'
import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
@ -24,11 +24,11 @@ import { ChatMessage } from '@hcengineering/chunter'
* @public
*/
export interface Request extends AttachedDoc {
requested: Ref<PersonAccount>[]
approved: Ref<PersonAccount>[]
requested: Ref<Person>[]
approved: Ref<Person>[]
approvedDates?: Timestamp[]
requiredApprovesCount: number
rejected?: Ref<PersonAccount>
rejected?: Ref<Person>
status: RequestStatus
tx: Tx
rejectedTx?: Tx