mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-29 14:34:12 +03:00
EZQMS-1069: Fix request model (#6131)
Signed-off-by: Alexey Zinoviev <alexey.zinoviev@xored.com>
This commit is contained in:
parent
77f0a2c3e1
commit
ed903a9396
@ -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],
|
||||
|
@ -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
|
||||
|
99
models/request/src/migration.ts
Normal file
99
models/request/src/migration.ts
Normal 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> {}
|
||||
}
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user