mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-13 05:07:43 +02:00
Approve requests (#10486)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Generated
+6
@@ -24660,6 +24660,9 @@ importers:
|
||||
|
||||
../../plugins/process-resources:
|
||||
dependencies:
|
||||
'@hcengineering/account-client':
|
||||
specifier: workspace:^0.7.21
|
||||
version: link:../../foundations/core/packages/account-client
|
||||
'@hcengineering/analytics':
|
||||
specifier: workspace:^0.7.17
|
||||
version: link:../../foundations/core/packages/analytics
|
||||
@@ -24681,6 +24684,9 @@ importers:
|
||||
'@hcengineering/core':
|
||||
specifier: workspace:^0.7.24
|
||||
version: link:../../foundations/core/packages/core
|
||||
'@hcengineering/login':
|
||||
specifier: workspace:^0.7.0
|
||||
version: link:../login
|
||||
'@hcengineering/platform':
|
||||
specifier: workspace:^0.7.19
|
||||
version: link:../../foundations/core/packages/platform
|
||||
|
||||
@@ -31,6 +31,23 @@ export function defineMethods (builder: Builder): void {
|
||||
process.method.RunSubProcess
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.Method,
|
||||
core.space.Model,
|
||||
{
|
||||
label: process.string.RequestApproval,
|
||||
objectClass: process.class.ApproveRequest,
|
||||
editor: process.component.ApproveRequestEditor,
|
||||
presenter: process.component.ApproveRequestPresenter,
|
||||
createdContext: {
|
||||
_class: process.class.ApproveRequest,
|
||||
nameField: 'title'
|
||||
},
|
||||
requiredParams: ['user']
|
||||
},
|
||||
process.method.RequestApproval
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.Method,
|
||||
core.space.Model,
|
||||
@@ -103,4 +120,30 @@ export function defineMethods (builder: Builder): void {
|
||||
},
|
||||
process.method.AddTag
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.Method,
|
||||
core.space.Model,
|
||||
{
|
||||
label: process.string.LockCard,
|
||||
objectClass: card.class.Card,
|
||||
requiredParams: [],
|
||||
createdContext: null
|
||||
},
|
||||
process.method.LockCard
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.Method,
|
||||
core.space.Model,
|
||||
{
|
||||
label: process.string.LockSection,
|
||||
objectClass: card.class.Card,
|
||||
editor: process.component.LockSectionEditor,
|
||||
presenter: process.component.LockSectionPresenter,
|
||||
requiredParams: ['_id'],
|
||||
createdContext: null
|
||||
},
|
||||
process.method.LockSection
|
||||
)
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import workbench from '@hcengineering/model-workbench'
|
||||
import notification from '@hcengineering/notification'
|
||||
import { type Asset, type IntlString, type Resource } from '@hcengineering/platform'
|
||||
import {
|
||||
type ApproveRequest,
|
||||
type CheckFunc,
|
||||
type ContextId,
|
||||
type CreatedContext,
|
||||
@@ -204,6 +205,19 @@ export class TProcessToDo extends TToDo implements ProcessToDo {
|
||||
withRollback!: boolean
|
||||
}
|
||||
|
||||
@Model(process.class.ApproveRequest, process.class.ProcessToDo)
|
||||
export class TApproveRequest extends TProcessToDo implements ApproveRequest {
|
||||
@Prop(TypeBoolean(), process.string.IsApproved)
|
||||
approved?: boolean
|
||||
|
||||
@Prop(TypeString(), process.string.RejectionReason)
|
||||
reason?: string
|
||||
|
||||
group!: string
|
||||
|
||||
card!: Ref<Card>
|
||||
}
|
||||
|
||||
@Model(process.class.Method, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TMethod extends TDoc implements Method<Doc> {
|
||||
label!: IntlString
|
||||
@@ -290,6 +304,7 @@ export function createModel (builder: Builder): void {
|
||||
TProcess,
|
||||
TExecution,
|
||||
TProcessToDo,
|
||||
TApproveRequest,
|
||||
TMethod,
|
||||
TState,
|
||||
TProcessFunction,
|
||||
@@ -323,6 +338,28 @@ export function createModel (builder: Builder): void {
|
||||
process.ids.ProcessToDoCreated
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
notification.class.NotificationType,
|
||||
core.space.Model,
|
||||
{
|
||||
hidden: false,
|
||||
generated: false,
|
||||
allowedForAuthor: true,
|
||||
label: process.string.ApproveRequest,
|
||||
group: time.ids.TimeNotificationGroup,
|
||||
txClasses: [core.class.TxCreateDoc],
|
||||
objectClass: process.class.ApproveRequest,
|
||||
onlyOwn: true,
|
||||
defaultEnabled: true,
|
||||
templates: {
|
||||
textTemplate: '{body}',
|
||||
htmlTemplate: '<p>{body}</p>',
|
||||
subjectTemplate: '{title}'
|
||||
}
|
||||
},
|
||||
process.ids.ApproveRequestCreated
|
||||
)
|
||||
|
||||
createAction(builder, {
|
||||
action: view.actionImpl.Delete,
|
||||
label: view.string.Delete,
|
||||
@@ -413,6 +450,42 @@ export function createModel (builder: Builder): void {
|
||||
presenter: process.component.StatePresenter
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
variant: 'cardRequests',
|
||||
attachTo: process.class.ApproveRequest,
|
||||
descriptor: view.viewlet.List,
|
||||
props: {
|
||||
baseMenuClass: process.class.ApproveRequest
|
||||
},
|
||||
viewOptions: {
|
||||
groupBy: ['user', 'approved'],
|
||||
orderBy: [
|
||||
['approved', SortingOrder.Descending],
|
||||
['modifiedOn', SortingOrder.Descending],
|
||||
['createdOn', SortingOrder.Descending]
|
||||
],
|
||||
other: []
|
||||
},
|
||||
configOptions: {
|
||||
strict: true
|
||||
},
|
||||
config: [
|
||||
{
|
||||
key: '',
|
||||
label: process.string.ApproveRequest,
|
||||
presenter: process.component.ApproveRequestPresenter
|
||||
},
|
||||
'user',
|
||||
'approved',
|
||||
'reason'
|
||||
]
|
||||
},
|
||||
process.viewlet.CardRequests
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
@@ -600,6 +673,12 @@ export function createModel (builder: Builder): void {
|
||||
props: {}
|
||||
})
|
||||
|
||||
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
|
||||
extension: card.extensions.EditCardExtension,
|
||||
component: process.component.RequestsExtension,
|
||||
props: {}
|
||||
})
|
||||
|
||||
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
|
||||
extension: card.extensions.EditCardHeaderExtension,
|
||||
component: process.component.ProcessesHeaderExtension,
|
||||
@@ -623,6 +702,19 @@ export function createModel (builder: Builder): void {
|
||||
process.section.CardProcesses
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
card.class.CardSection,
|
||||
core.space.Model,
|
||||
{
|
||||
label: process.string.ApproveRequest,
|
||||
component: process.component.RequestsCardSection,
|
||||
checkVisibility: process.function.CheckRequestsSectionVisibility,
|
||||
order: 360,
|
||||
navigation: []
|
||||
},
|
||||
process.section.CardApproveRequest
|
||||
)
|
||||
|
||||
builder.createDoc(card.class.MasterTagEditorSection, core.space.Model, {
|
||||
id: 'processes',
|
||||
label: process.string.Processes,
|
||||
|
||||
@@ -24,14 +24,16 @@ export default mergeIds(processId, process, {
|
||||
Process: '' as Ref<Doc>
|
||||
},
|
||||
section: {
|
||||
CardProcesses: '' as Ref<CardSection>
|
||||
CardProcesses: '' as Ref<CardSection>,
|
||||
CardApproveRequest: '' as Ref<CardSection>
|
||||
},
|
||||
pipeline: {
|
||||
ProcessMiddleware: '' as Ref<PresentationMiddlewareFactory>
|
||||
},
|
||||
ids: {
|
||||
ProcessSettings: '' as Ref<Doc>,
|
||||
ProcessToDoCreated: '' as Ref<Doc>
|
||||
ProcessToDoCreated: '' as Ref<Doc>,
|
||||
ApproveRequestCreated: '' as Ref<Doc>
|
||||
},
|
||||
actionImpl: {
|
||||
ContinueExecution: '' as ViewAction
|
||||
|
||||
@@ -16,6 +16,38 @@ import { type Builder } from '@hcengineering/model'
|
||||
import process from './plugin'
|
||||
|
||||
export function defineTriggers (builder: Builder): void {
|
||||
builder.createDoc(
|
||||
process.class.Trigger,
|
||||
core.space.Model,
|
||||
{
|
||||
label: process.string.OnApproveRequestApproved,
|
||||
icon: process.icon.ToDo,
|
||||
editor: process.component.ApproveRequestTriggerEditor,
|
||||
presenter: process.component.ApproveRequestTriggerPresenter,
|
||||
requiredParams: ['_id'],
|
||||
checkFunction: process.triggerCheck.ApproveRequestApproved,
|
||||
init: false,
|
||||
auto: true
|
||||
},
|
||||
process.trigger.OnApproveRequestApproved
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.Trigger,
|
||||
core.space.Model,
|
||||
{
|
||||
label: process.string.OnApproveRequestRejected,
|
||||
icon: process.icon.ToDoRemove,
|
||||
editor: process.component.ApproveRequestTriggerEditor,
|
||||
presenter: process.component.ApproveRequestTriggerPresenter,
|
||||
requiredParams: ['_id'],
|
||||
checkFunction: process.triggerCheck.ApproveRequestRejected,
|
||||
init: false,
|
||||
auto: true
|
||||
},
|
||||
process.trigger.OnApproveRequestRejected
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.Trigger,
|
||||
core.space.Model,
|
||||
|
||||
@@ -70,6 +70,16 @@ export function createModel (builder: Builder): void {
|
||||
serverCheckFunc: serverProcess.func.FieldChangedCheck
|
||||
})
|
||||
|
||||
builder.mixin(process.trigger.OnApproveRequestApproved, process.class.Trigger, serverProcess.mixin.TriggerImpl, {
|
||||
preventRollback: true,
|
||||
serverCheckFunc: serverProcess.func.ApproveRequestApproved
|
||||
})
|
||||
|
||||
builder.mixin(process.trigger.OnApproveRequestRejected, process.class.Trigger, serverProcess.mixin.TriggerImpl, {
|
||||
preventRollback: true,
|
||||
serverCheckFunc: serverProcess.func.ApproveRequestRejected
|
||||
})
|
||||
|
||||
builder.mixin(process.trigger.OnExecutionStart, process.class.Trigger, serverProcess.mixin.TriggerImpl, {
|
||||
preventRollback: true
|
||||
})
|
||||
@@ -117,6 +127,18 @@ export function createModel (builder: Builder): void {
|
||||
func: serverProcess.func.AddTag
|
||||
})
|
||||
|
||||
builder.mixin(process.method.RequestApproval, process.class.Method, serverProcess.mixin.MethodImpl, {
|
||||
func: serverProcess.func.RequestApproval
|
||||
})
|
||||
|
||||
builder.mixin(process.method.LockCard, process.class.Method, serverProcess.mixin.MethodImpl, {
|
||||
func: serverProcess.func.LockCard
|
||||
})
|
||||
|
||||
builder.mixin(process.method.LockSection, process.class.Method, serverProcess.mixin.MethodImpl, {
|
||||
func: serverProcess.func.LockSection
|
||||
})
|
||||
|
||||
builder.mixin(process.function.FirstValue, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
|
||||
func: serverProcess.transform.FirstValue
|
||||
})
|
||||
|
||||
@@ -55,14 +55,26 @@
|
||||
<div class="divider" />
|
||||
{/if}
|
||||
<div class="masterTag">
|
||||
<MasterTagAttributes bind:this={masterTagAttributes} {readonly} {value} {ignoreKeys} fourRows={columns === 2} />
|
||||
<MasterTagAttributes
|
||||
bind:this={masterTagAttributes}
|
||||
readonly={readonly || value.readonlySections?.includes(value._class)}
|
||||
{value}
|
||||
{ignoreKeys}
|
||||
fourRows={columns === 2}
|
||||
/>
|
||||
</div>
|
||||
{#if mixins.length > 0}
|
||||
<div class="divider" />
|
||||
<Grid column={columns} columnGap={0} rowGap={0} alignItems={'start'}>
|
||||
{#each mixins as tag, i (tag._id)}
|
||||
<div class="tag" class:withoutBorder={Math.ceil((i + 1) / columns) === Math.ceil(mixins.length / columns)}>
|
||||
<TagAttributes bind:this={tagAttributes[i]} {readonly} {tag} {value} {ignoreKeys} />
|
||||
<TagAttributes
|
||||
bind:this={tagAttributes[i]}
|
||||
readonly={readonly || value.readonlySections?.includes(tag._id)}
|
||||
{tag}
|
||||
{value}
|
||||
{ignoreKeys}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</Grid>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<div class="content" class:hidden>
|
||||
<Content
|
||||
{doc}
|
||||
readonly={readonly || updatePermissionForbidden}
|
||||
readonly={readonly || updatePermissionForbidden || doc.readonlySections?.includes(doc._class)}
|
||||
content={contentDiv}
|
||||
showToc={false}
|
||||
on:loaded
|
||||
|
||||
@@ -57,9 +57,10 @@ export interface Card extends Doc, IconProps, VersionableDoc {
|
||||
parentInfo: ParentInfo[]
|
||||
parent?: Ref<Card> | null
|
||||
rank: Rank
|
||||
readonly?: boolean
|
||||
|
||||
peerId?: string
|
||||
|
||||
readonlySections?: Ref<MasterTag>[]
|
||||
}
|
||||
|
||||
export interface CardSpace extends TypedSpace {
|
||||
|
||||
@@ -34,9 +34,6 @@ export default mergeIds(loginId, login, {
|
||||
HaveAccount: '' as IntlString,
|
||||
LoadingAccount: '' as IntlString,
|
||||
Join: '' as IntlString,
|
||||
Email: '' as IntlString,
|
||||
Password: '' as IntlString,
|
||||
PasswordRepeat: '' as IntlString,
|
||||
Workspace: '' as IntlString,
|
||||
SignUp: '' as IntlString,
|
||||
DoNotHaveAnAccount: '' as IntlString,
|
||||
|
||||
@@ -89,7 +89,10 @@ export default plugin(loginId, {
|
||||
WorkspaceArchived: '' as IntlString,
|
||||
WorkspaceArchivedDesc: '' as IntlString,
|
||||
RestoreArchivedWorkspace: '' as IntlString,
|
||||
PasswordExpiredDesc: '' as IntlString
|
||||
PasswordExpiredDesc: '' as IntlString,
|
||||
Email: '' as IntlString,
|
||||
Password: '' as IntlString,
|
||||
PasswordRepeat: '' as IntlString
|
||||
},
|
||||
function: {
|
||||
SendInvite: '' as Resource<(email: string, role: AccountRole) => Promise<void>>,
|
||||
|
||||
@@ -121,7 +121,23 @@
|
||||
"RunProcessPermission": "Spustit proces",
|
||||
"CancelProcessPermission": "Zrušit proces",
|
||||
"ForbidRunProcessPermission": "Zakázat spuštění procesu",
|
||||
"ForbidCancelProcessPermission": "Zakázat zrušení procesu"
|
||||
"ForbidCancelProcessPermission": "Forbid cancelling process",
|
||||
"RequestApproval": "Požádat o schválení",
|
||||
"IsApproved": "Schváleno",
|
||||
"Approve": "Schválit",
|
||||
"Reject": "Odmítnout",
|
||||
"Reason": "Důvod",
|
||||
"ApproveRequest": "Schválit požadavek",
|
||||
"OnApproveRequestApproved": "Při schválení požadavku",
|
||||
"OnApproveRequestRejected": "Při zamítnutí požadavku",
|
||||
"ConfirmApproval": "Potvrdit schválení",
|
||||
"ConfirmRejection": "Potvrdit zamítnutí",
|
||||
"RejectionReason": "Důvod zamítnutí",
|
||||
"ProvideRejectionReason": "Poskytnout důvod zamítnutí",
|
||||
"FieldIsEmpty": "Pole je prázdné",
|
||||
"Reviewers": "Recenzenti",
|
||||
"LockCard": "Zamknout kartu",
|
||||
"LockSection": "Zamknout sekci"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metoda nenalezena: {methodId}",
|
||||
|
||||
@@ -121,7 +121,23 @@
|
||||
"RunProcessPermission": "Prozess ausführen",
|
||||
"CancelProcessPermission": "Prozess abbrechen",
|
||||
"ForbidRunProcessPermission": "Ausführen des Prozesses verbieten",
|
||||
"ForbidCancelProcessPermission": "Abbrechen des Prozesses verbieten"
|
||||
"ForbidCancelProcessPermission": "Abbrechen des Prozesses verbieten",
|
||||
"RequestApproval": "Genehmigung anfordern",
|
||||
"IsApproved": "Ist genehmigt",
|
||||
"Approve": "Genehmigen",
|
||||
"Reject": "Ablehnen",
|
||||
"Reason": "Grund",
|
||||
"ApproveRequest": "Genehmigungsanfrage",
|
||||
"OnApproveRequestApproved": "Bei Genehmigung der Anfrage",
|
||||
"OnApproveRequestRejected": "Bei Ablehnung der Anfrage",
|
||||
"ConfirmApproval": "Genehmigung bestätigen",
|
||||
"ConfirmRejection": "Ablehnung bestätigen",
|
||||
"RejectionReason": "Ablehnungsgrund",
|
||||
"ProvideRejectionReason": "Ablehnungsgrund angeben",
|
||||
"FieldIsEmpty": "Feld ist leer",
|
||||
"Reviewers": "Prüfer",
|
||||
"LockCard": "Karte sperren",
|
||||
"LockSection": "Abschnitt sperren"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Methode nicht gefunden: {methodId}",
|
||||
|
||||
@@ -126,7 +126,23 @@
|
||||
"RunProcessPermission": "Run process",
|
||||
"CancelProcessPermission": "Cancel process",
|
||||
"ForbidRunProcessPermission": "Forbid running process",
|
||||
"ForbidCancelProcessPermission": "Forbid cancelling process"
|
||||
"ForbidCancelProcessPermission": "Forbid cancelling process",
|
||||
"RequestApproval": "Request approval",
|
||||
"IsApproved": "Is approved",
|
||||
"Approve": "Approve",
|
||||
"Reject": "Reject",
|
||||
"Reason": "Reason",
|
||||
"ApproveRequest": "Approve request",
|
||||
"OnApproveRequestApproved": "On approved",
|
||||
"OnApproveRequestRejected": "On rejected",
|
||||
"ConfirmApproval": "Confirm approval",
|
||||
"ConfirmRejection": "Confirm rejection",
|
||||
"RejectionReason": "Rejection reason",
|
||||
"ProvideRejectionReason": "Provide rejection reason",
|
||||
"FieldIsEmpty": "Field is empty",
|
||||
"Reviewers": "Reviewers",
|
||||
"LockCard": "Lock card",
|
||||
"LockSection": "Lock section"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Method not found: {methodId}",
|
||||
|
||||
@@ -126,7 +126,23 @@
|
||||
"RunProcessPermission": "Ejecutar proceso",
|
||||
"CancelProcessPermission": "Cancelar proceso",
|
||||
"ForbidRunProcessPermission": "Prohibir ejecución de proceso",
|
||||
"ForbidCancelProcessPermission": "Prohibir cancelación de proceso"
|
||||
"ForbidCancelProcessPermission": "Forbid cancelling process",
|
||||
"RequestApproval": "Solicitar aprobación",
|
||||
"IsApproved": "Aprobado",
|
||||
"Approve": "Aprobar",
|
||||
"Reject": "Rechazar",
|
||||
"Reason": "Razón",
|
||||
"ApproveRequest": "Aprobar solicitud",
|
||||
"OnApproveRequestApproved": "Al aprobar la solicitud",
|
||||
"OnApproveRequestRejected": "Al rechazar la solicitud",
|
||||
"ConfirmApproval": "Confirmar aprobación",
|
||||
"ConfirmRejection": "Confirmar rechazo",
|
||||
"RejectionReason": "Razón de rechazo",
|
||||
"ProvideRejectionReason": "Proporcionar razón de rechazo",
|
||||
"FieldIsEmpty": "Campo vacío",
|
||||
"Reviewers": "Revisores",
|
||||
"LockCard": "Bloquear tarjeta",
|
||||
"LockSection": "Bloquear sección"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Método no encontrado: {methodId}",
|
||||
|
||||
@@ -126,7 +126,23 @@
|
||||
"RunProcessPermission": "Exécuter le processus",
|
||||
"CancelProcessPermission": "Annuler le processus",
|
||||
"ForbidRunProcessPermission": "Interdire l'exécution du processus",
|
||||
"ForbidCancelProcessPermission": "Interdire l'annulation du processus"
|
||||
"ForbidCancelProcessPermission": "Forbid cancelling process",
|
||||
"RequestApproval": "Demander l'approbation",
|
||||
"IsApproved": "Approuvé",
|
||||
"Approve": "Approuver",
|
||||
"Reject": "Rejeter",
|
||||
"Reason": "Raison",
|
||||
"ApproveRequest": "Demander l'approbation",
|
||||
"OnApproveRequestApproved": "Lors de l'approbation",
|
||||
"OnApproveRequestRejected": "Lors du rejet",
|
||||
"ConfirmApproval": "Confirmer l'approbation",
|
||||
"ConfirmRejection": "Confirmer le rejet",
|
||||
"RejectionReason": "Raison du rejet",
|
||||
"ProvideRejectionReason": "Fournir la raison du rejet",
|
||||
"FieldIsEmpty": "Le champ est vide",
|
||||
"Reviewers": "Relecteurs",
|
||||
"LockCard": "Verrouiller la carte",
|
||||
"LockSection": "Verrouiller la section"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Méthode introuvable : {methodId}",
|
||||
|
||||
@@ -121,12 +121,28 @@
|
||||
"ExecutionStarted": "Esecuzione avviata",
|
||||
"Filter": "Filtro",
|
||||
"FirstMatchValue": "Primo valore corrispondente",
|
||||
"ConfigLabel": "Configurazione del processo",
|
||||
"ConfigDescription": "Definire i processi per automatizzare i flussi di lavoro e i processi aziendali.",
|
||||
"RunProcessPermission": "Esegui processo",
|
||||
"CancelProcessPermission": "Annulla processo",
|
||||
"ForbidRunProcessPermission": "Vieta l'esecuzione del processo",
|
||||
"ForbidCancelProcessPermission": "Vieta l'annullamento del processo"
|
||||
"ConfigLabel": "Configurazione del processo",
|
||||
"ConfigDescription": "Definire i processi per automatizzare i flussi di lavoro e i processi aziendali.",
|
||||
"RunProcessPermission": "Esegui processo",
|
||||
"CancelProcessPermission": "Annulla processo",
|
||||
"ForbidRunProcessPermission": "Vieta l'esecuzione del processo",
|
||||
"ForbidCancelProcessPermission": "Vieta l'annullamento del processo",
|
||||
"RequestApproval": "Richiedi approvazione",
|
||||
"IsApproved": "Approvato",
|
||||
"Approve": "Approva",
|
||||
"Reject": "Rifiuta",
|
||||
"Reason": "Motivo",
|
||||
"ApproveRequest": "Richiedi approvazione",
|
||||
"OnApproveRequestApproved": "Quando approvato",
|
||||
"OnApproveRequestRejected": "Quando rifiutato",
|
||||
"ConfirmApproval": "Conferma approvazione",
|
||||
"ConfirmRejection": "Conferma rifiuto",
|
||||
"RejectionReason": "Motivo del rifiuto",
|
||||
"ProvideRejectionReason": "Fornisci motivo del rifiuto",
|
||||
"FieldIsEmpty": "Campo vuoto",
|
||||
"Reviewers": "Revisori",
|
||||
"LockCard": "Blocca scheda",
|
||||
"LockSection": "Blocca sezione"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metodo non trovato: {methodId}",
|
||||
|
||||
@@ -125,7 +125,23 @@
|
||||
"RunProcessPermission": "プロセスを実行",
|
||||
"CancelProcessPermission": "プロセスをキャンセル",
|
||||
"ForbidRunProcessPermission": "プロセスの実行を禁止",
|
||||
"ForbidCancelProcessPermission": "プロセスのキャンセルを禁止"
|
||||
"ForbidCancelProcessPermission": "Forbid cancelling process",
|
||||
"RequestApproval": "承認をリクエスト",
|
||||
"IsApproved": "承認済み",
|
||||
"Approve": "承認",
|
||||
"Reject": "拒否",
|
||||
"Reason": "理由",
|
||||
"ApproveRequest": "承認をリクエスト",
|
||||
"OnApproveRequestApproved": "承認リクエストが承認されたとき",
|
||||
"OnApproveRequestRejected": "承認リクエストが拒否されたとき",
|
||||
"ConfirmApproval": "承認を確認",
|
||||
"ConfirmRejection": "拒否を確認",
|
||||
"RejectionReason": "拒否理由",
|
||||
"ProvideRejectionReason": "拒否理由を提供する",
|
||||
"FieldIsEmpty": "フィールドが空です",
|
||||
"Reviewers": "レビュー担当者",
|
||||
"LockCard": "カードをロック",
|
||||
"LockSection": "セクションをロック"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "メソッドが見つかりません: {methodId}",
|
||||
|
||||
@@ -126,7 +126,23 @@
|
||||
"RunProcessPermission": "Executar processo",
|
||||
"CancelProcessPermission": "Cancelar processo",
|
||||
"ForbidRunProcessPermission": "Proibir execução do processo",
|
||||
"ForbidCancelProcessPermission": "Proibir cancelamento do processo"
|
||||
"ForbidCancelProcessPermission": "Proibir cancelamento do processo",
|
||||
"RequestApproval": "Solicitar aprovação",
|
||||
"IsApproved": "Está aprovado",
|
||||
"Approve": "Aprovar",
|
||||
"Reject": "Rejeitar",
|
||||
"Reason": "Razão",
|
||||
"ApproveRequest": "Aprovar solicitação",
|
||||
"OnApproveRequestApproved": "Ao aprovar solicitação aprovada",
|
||||
"OnApproveRequestRejected": "Ao aprovar solicitação rejeitada",
|
||||
"ConfirmApproval": "Confirmar aprovação",
|
||||
"ConfirmRejection": "Confirmar rejeição",
|
||||
"RejectionReason": "Razão de rejeição",
|
||||
"ProvideRejectionReason": "Fornecer razão de rejeição",
|
||||
"FieldIsEmpty": "Campo está vazio",
|
||||
"Reviewers": "Revisores",
|
||||
"LockCard": "Bloquear cartão",
|
||||
"LockSection": "Bloquear seção"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Método não encontrado: {methodId}",
|
||||
|
||||
@@ -126,7 +126,23 @@
|
||||
"RunProcessPermission": "Запустить процесс",
|
||||
"CancelProcessPermission": "Отменить процесс",
|
||||
"ForbidRunProcessPermission": "Запретить запуск процесса",
|
||||
"ForbidCancelProcessPermission": "Запретить отмену процесса"
|
||||
"ForbidCancelProcessPermission": "Запретить отмену процесса",
|
||||
"RequestApproval": "Запрос на утверждение",
|
||||
"IsApproved": "Утверждено",
|
||||
"Approve": "Утвердить",
|
||||
"Reject": "Отклонить",
|
||||
"Reason": "Причина",
|
||||
"ApproveRequest": "Запрос на утверждение",
|
||||
"OnApproveRequestApproved": "При утверждении",
|
||||
"OnApproveRequestRejected": "При отклонении",
|
||||
"ConfirmApproval": "Подтвердить утверждение",
|
||||
"ConfirmRejection": "Подтвердить отклонение",
|
||||
"RejectionReason": "Причина отклонения",
|
||||
"ProvideRejectionReason": "Укажите причину отклонения",
|
||||
"FieldIsEmpty": "Поле пустое",
|
||||
"Reviewers": "Рецензенты",
|
||||
"LockCard": "Заблокировать карточку",
|
||||
"LockSection": "Заблокировать секцию"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Метод не найден: {methodId}",
|
||||
|
||||
@@ -123,7 +123,23 @@
|
||||
"RunProcessPermission": "Süreci çalıştır",
|
||||
"CancelProcessPermission": "Süreci iptal et",
|
||||
"ForbidRunProcessPermission": "Süreci çalıştırmayı yasakla",
|
||||
"ForbidCancelProcessPermission": "Süreci iptal etmeyi yasakla"
|
||||
"ForbidCancelProcessPermission": "Forbid cancelling process",
|
||||
"RequestApproval": "Onay iste",
|
||||
"IsApproved": "Onaylandı",
|
||||
"Approve": "Onayla",
|
||||
"Reject": "Reddet",
|
||||
"Reason": "Sebep",
|
||||
"ApproveRequest": "Onay iste",
|
||||
"OnApproveRequestApproved": "Onay isteği onaylandığında",
|
||||
"OnApproveRequestRejected": "Onay isteği reddedildiğinde",
|
||||
"ConfirmApproval": "Onayı onayla",
|
||||
"ConfirmRejection": "Reddi onayla",
|
||||
"RejectionReason": "Red sebebi",
|
||||
"ProvideRejectionReason": "Red sebebini sağla",
|
||||
"FieldIsEmpty": "Alan boş",
|
||||
"Reviewers": "Gözden geçirenler",
|
||||
"LockCard": "Kartı kilitle",
|
||||
"LockSection": "Bölümü kilitle"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metod bulunamadı: {methodId}",
|
||||
|
||||
@@ -126,7 +126,23 @@
|
||||
"RunProcessPermission": "运行过程",
|
||||
"CancelProcessPermission": "取消过程",
|
||||
"ForbidRunProcessPermission": "禁止运行过程",
|
||||
"ForbidCancelProcessPermission": "禁止取消过程"
|
||||
"ForbidCancelProcessPermission": "禁止取消过程",
|
||||
"RequestApproval": "请求批准",
|
||||
"IsApproved": "批准",
|
||||
"Approve": "批准",
|
||||
"Reject": "拒绝",
|
||||
"Reason": "原因",
|
||||
"ApproveRequest": "批准请求",
|
||||
"OnApproveRequestApproved": "批准请求批准时",
|
||||
"OnApproveRequestRejected": "批准请求拒绝时",
|
||||
"ConfirmApproval": "确认批准",
|
||||
"ConfirmRejection": "确认拒绝",
|
||||
"RejectionReason": "拒绝原因",
|
||||
"ProvideRejectionReason": "提供拒绝原因",
|
||||
"FieldIsEmpty": "字段为空",
|
||||
"Reviewers": "审查员",
|
||||
"LockCard": "锁定卡片",
|
||||
"LockSection": "锁定部分"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "找不到方法:{methodId}",
|
||||
|
||||
@@ -58,6 +58,8 @@
|
||||
"@hcengineering/rank": "workspace:^0.7.17",
|
||||
"@hcengineering/platform": "workspace:^0.7.19",
|
||||
"@hcengineering/process": "workspace:^0.7.0",
|
||||
"@hcengineering/login": "workspace:^0.7.0",
|
||||
"@hcengineering/account-client": "workspace:^0.7.21",
|
||||
"svelte": "^4.2.20",
|
||||
"fast-equals": "^5.2.2"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<!--
|
||||
// Copyright © 2026 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 { Card } from '@hcengineering/card'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { ApproveRequest } from '@hcengineering/process'
|
||||
import { Button, eventToHTMLElement, showPopup } from '@hcengineering/ui'
|
||||
import SignatureDialog from './SignatureDialog.svelte'
|
||||
import process from '../plugin'
|
||||
|
||||
export let todo: ApproveRequest
|
||||
export let card: Ref<Card>
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function changeApprovalRequestState (ev: MouseEvent, isRejection: boolean): Promise<void> {
|
||||
showPopup(SignatureDialog, { isRejection }, eventToHTMLElement(ev), async (res) => {
|
||||
if (!res) return
|
||||
|
||||
const { rejectionNote } = res
|
||||
|
||||
if (isRejection && rejectionNote == null) {
|
||||
return
|
||||
}
|
||||
|
||||
await client.update(todo, {
|
||||
doneOn: new Date().getTime(),
|
||||
approved: !isRejection
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button label={process.string.Approve} kind="positive" on:click={(ev) => changeApprovalRequestState(ev, false)} />
|
||||
<Button label={process.string.Reject} kind="negative" on:click={(ev) => changeApprovalRequestState(ev, true)} />
|
||||
@@ -0,0 +1,22 @@
|
||||
<!--
|
||||
// Copyright © 2026 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 { ApproveRequest } from '@hcengineering/process'
|
||||
|
||||
export let value: ApproveRequest
|
||||
</script>
|
||||
|
||||
{value.title}
|
||||
@@ -13,13 +13,13 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import plugin from '../plugin'
|
||||
import { Execution, ProcessToDo } from '@hcengineering/process'
|
||||
import { getCurrentEmployee } from '@hcengineering/contact'
|
||||
import { Button, Component } from '@hcengineering/ui'
|
||||
import time from '@hcengineering/time'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ApproveRequest, Execution, ProcessToDo } from '@hcengineering/process'
|
||||
import { Button } from '@hcengineering/ui'
|
||||
import plugin from '../plugin'
|
||||
import ApproveRequestButtons from './ApproveRequestButtons.svelte'
|
||||
|
||||
export let value: Execution
|
||||
|
||||
@@ -47,8 +47,15 @@
|
||||
doneOn: new Date().getTime()
|
||||
})
|
||||
}
|
||||
|
||||
function isRequest (todo: ProcessToDo): todo is ApproveRequest {
|
||||
return todo._class === plugin.class.ApproveRequest
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each todos as todo (todo._id)}
|
||||
{#if isRequest(todo)}
|
||||
<ApproveRequestButtons {todo} card={value.card} />
|
||||
{/if}
|
||||
<Button label={getEmbeddedLabel(todo.title)} on:click={() => checkTodo(todo)} />
|
||||
{/each}
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
import { getCurrentEmployee } from '@hcengineering/contact'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { EventButton, Execution, ExecutionStatus, ProcessToDo } from '@hcengineering/process'
|
||||
import { ApproveRequest, EventButton, Execution, ExecutionStatus, ProcessToDo } from '@hcengineering/process'
|
||||
import { Button } from '@hcengineering/ui'
|
||||
import process from '../plugin'
|
||||
import ApproveRequestButtons from './ApproveRequestButtons.svelte'
|
||||
|
||||
export let card: Card
|
||||
|
||||
@@ -98,10 +99,18 @@
|
||||
}
|
||||
|
||||
$: rollbacks = docs.filter((d) => d.rollback.length > 0)
|
||||
|
||||
function isRequest (todo: ProcessToDo): todo is ApproveRequest {
|
||||
return todo._class === process.class.ApproveRequest
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each todos as todo (todo._id)}
|
||||
<Button kind={'primary'} label={getEmbeddedLabel(todo.title)} on:click={() => checkTodo(todo)} />
|
||||
{#if isRequest(todo)}
|
||||
<ApproveRequestButtons {todo} card={card._id} />
|
||||
{:else}
|
||||
<Button kind={'primary'} label={getEmbeddedLabel(todo.title)} on:click={() => checkTodo(todo)} />
|
||||
{/if}
|
||||
{/each}
|
||||
{#each actions as action (action._id)}
|
||||
<Button kind={'primary'} label={getEmbeddedLabel(action.title)} on:click={() => performAction(action)} />
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<!--
|
||||
// Copyright © 2026 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 { Card } from '@hcengineering/card'
|
||||
|
||||
import RequestsExtension from './RequestsExtension.svelte'
|
||||
|
||||
export let doc: Card
|
||||
export let hidden: boolean = false
|
||||
</script>
|
||||
|
||||
{#if !hidden}
|
||||
<div class="requests__section">
|
||||
<RequestsExtension card={doc} on:loaded />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.requests__section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,156 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { Card } from '@hcengineering/card'
|
||||
import core, { Doc, FindOptions, SortingOrder } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import { ApproveRequest } from '@hcengineering/process'
|
||||
import { Label, registerFocus, resizeObserver, Section } from '@hcengineering/ui'
|
||||
import view, { Viewlet, ViewletPreference, ViewOptions } from '@hcengineering/view'
|
||||
import {
|
||||
List,
|
||||
ListSelectionProvider,
|
||||
noCategory,
|
||||
SelectDirection,
|
||||
ViewletsSettingButton
|
||||
} from '@hcengineering/view-resources'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import process from '../plugin'
|
||||
|
||||
export let card: Card
|
||||
|
||||
const viewletId = process.viewlet.CardRequests
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: query = {
|
||||
card: card._id
|
||||
}
|
||||
|
||||
const options: FindOptions<ApproveRequest> = {
|
||||
sort: {
|
||||
modifiedOn: SortingOrder.Descending
|
||||
}
|
||||
}
|
||||
|
||||
let list: List
|
||||
|
||||
const listProvider = new ListSelectionProvider(
|
||||
(offset: 1 | -1 | 0, of?: Doc, dir?: SelectDirection, noScroll?: boolean) => {
|
||||
if (dir === 'vertical') {
|
||||
// Select next
|
||||
list?.select(offset, of, noScroll)
|
||||
}
|
||||
}
|
||||
)
|
||||
let docs: ApproveRequest[] = []
|
||||
function select () {
|
||||
listProvider.update(docs)
|
||||
listProvider.updateFocus(docs[0])
|
||||
list?.select(0, undefined)
|
||||
}
|
||||
const selection = listProvider.selection
|
||||
|
||||
// Focusable control with index
|
||||
let focused = false
|
||||
export let focusIndex = -1
|
||||
registerFocus(focusIndex, {
|
||||
focus: () => {
|
||||
;(window.document.activeElement as HTMLElement).blur()
|
||||
focused = true
|
||||
select()
|
||||
return true
|
||||
},
|
||||
isFocus: () => focused
|
||||
})
|
||||
|
||||
const preferenceQuery = createQuery()
|
||||
let preference: ViewletPreference | undefined = undefined
|
||||
preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
space: core.space.Workspace,
|
||||
attachedTo: process.viewlet.CardRequests
|
||||
},
|
||||
(res) => {
|
||||
preference = res[0]
|
||||
}
|
||||
)
|
||||
|
||||
let listWidth: number
|
||||
|
||||
let viewlet: Viewlet | undefined
|
||||
let viewOptions: ViewOptions | undefined
|
||||
|
||||
let docsProvided = false
|
||||
</script>
|
||||
|
||||
<Section icon={process.icon.Process} label={process.string.ApproveRequest} spaceBeforeContent>
|
||||
<svelte:fragment slot="header">
|
||||
<div class="buttons-group xsmall-gap">
|
||||
<ViewletsSettingButton bind:viewOptions viewletQuery={{ _id: viewletId }} kind={'tertiary'} bind:viewlet />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="content">
|
||||
<div
|
||||
class="antiSection-empty {docsProvided && docs.length === 0 ? 'solid' : 'none-appearance flex-gap-2'}"
|
||||
use:resizeObserver={(evt) => {
|
||||
listWidth = evt.clientWidth
|
||||
}}
|
||||
>
|
||||
{#if viewOptions && viewlet}
|
||||
<List
|
||||
bind:this={list}
|
||||
_class={process.class.ApproveRequest}
|
||||
{viewOptions}
|
||||
baseMenuClass={process.class.ApproveRequest}
|
||||
viewOptionsConfig={viewlet.viewOptions?.other}
|
||||
config={preference?.config ?? viewlet.config}
|
||||
configurations={undefined}
|
||||
{query}
|
||||
{options}
|
||||
compactMode={listWidth <= 600}
|
||||
flatHeaders={true}
|
||||
disableHeader={viewOptions.groupBy?.length === 0 || viewOptions.groupBy[0] === noCategory}
|
||||
{listProvider}
|
||||
selectedObjectIds={$selection ?? []}
|
||||
on:row-focus={(event) => {
|
||||
listProvider.updateFocus(event.detail ?? undefined)
|
||||
}}
|
||||
on:check={(event) => {
|
||||
listProvider.updateSelection(event.detail.docs, event.detail.value)
|
||||
}}
|
||||
on:content={(evt) => {
|
||||
docsProvided = true
|
||||
docs = evt.detail
|
||||
listProvider.update(evt.detail)
|
||||
dispatch('loaded')
|
||||
}}
|
||||
/>
|
||||
{#if docsProvided && docs.length === 0}
|
||||
<div class="flex-center content-color empty-content">
|
||||
<Label label={process.string.NoProcesses} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Section>
|
||||
|
||||
<style lang="scss">
|
||||
.antiSection-empty:has(.empty-content) :global(.list-container) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,177 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2023 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 { getClient as getAccountClient } from '@hcengineering/account-client'
|
||||
import contact, { SocialIdentityRef } from '@hcengineering/contact'
|
||||
import { getCurrentAccount, SocialIdType } from '@hcengineering/core'
|
||||
import login from '@hcengineering/login'
|
||||
import {
|
||||
ERROR,
|
||||
getMetadata,
|
||||
IntlString,
|
||||
OK,
|
||||
PlatformError,
|
||||
Severity,
|
||||
Status,
|
||||
translate
|
||||
} from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { EditBox, ModernDialog, StylishEdit, Status as StatusControl } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import plugin from '../plugin'
|
||||
|
||||
export let confirmationTitle: IntlString = plugin.string.ConfirmApproval
|
||||
export let rejectionTitle: IntlString = plugin.string.ConfirmRejection
|
||||
export let isRejection: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const account = getCurrentAccount()
|
||||
const client = getClient()
|
||||
|
||||
let rejectionNote = ''
|
||||
const object: LoginInfo = {
|
||||
email: '',
|
||||
password: ''
|
||||
}
|
||||
|
||||
void client
|
||||
.findOne(contact.class.SocialIdentity, {
|
||||
_id: { $in: account.socialIds as SocialIdentityRef[] },
|
||||
type: SocialIdType.EMAIL
|
||||
})
|
||||
.then((si) => {
|
||||
if (si != null) {
|
||||
object.email = si.value
|
||||
}
|
||||
})
|
||||
|
||||
const accountsUrl = getMetadata(login.metadata.AccountsUrl) ?? ''
|
||||
$: disableEmailField = object.email !== ''
|
||||
$: canSubmit = object.email !== '' && object.password !== '' && (!isRejection || rejectionNote.trim().length > 0)
|
||||
|
||||
let status = OK
|
||||
|
||||
async function submit (): Promise<void> {
|
||||
if (object.email === '' || object.password === '') {
|
||||
return
|
||||
}
|
||||
|
||||
status = await validateAccount(object.email, object.password)
|
||||
|
||||
if (status === OK) {
|
||||
dispatch('close', { rejectionNote: isRejection ? rejectionNote : undefined })
|
||||
}
|
||||
}
|
||||
|
||||
async function validateAccount (email: string, password: string): Promise<Status> {
|
||||
const accountClient = getAccountClient(accountsUrl)
|
||||
|
||||
try {
|
||||
await accountClient.login(email, password)
|
||||
|
||||
return OK
|
||||
} catch (err: any) {
|
||||
if (err instanceof PlatformError) {
|
||||
return err.status
|
||||
} else {
|
||||
return ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface LoginInfo {
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
|
||||
const loginIntlFieldNames: Readonly<{ [K in keyof LoginInfo]: IntlString }> = {
|
||||
email: login.string.Email,
|
||||
password: login.string.Password
|
||||
}
|
||||
|
||||
async function validate (): Promise<void> {
|
||||
for (const field of Object.keys(object)) {
|
||||
const k = field as keyof LoginInfo
|
||||
if (object[k] === '') {
|
||||
status = new Status(Severity.INFO, plugin.string.FieldIsEmpty, {
|
||||
field: await translate(loginIntlFieldNames[k], {})
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (isRejection && rejectionNote.trim().length === 0) {
|
||||
status = new Status(Severity.INFO, plugin.string.FieldIsEmpty, {
|
||||
field: await translate(plugin.string.RejectionReason, {})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
status = OK
|
||||
}
|
||||
</script>
|
||||
|
||||
<ModernDialog
|
||||
label={isRejection ? rejectionTitle : confirmationTitle}
|
||||
{canSubmit}
|
||||
on:submit={submit}
|
||||
on:close
|
||||
width="32rem"
|
||||
shadow={true}
|
||||
className={'signature-dialog'}
|
||||
>
|
||||
<div class="flex-col flex-gap-2">
|
||||
<StylishEdit
|
||||
label={login.string.Email}
|
||||
name={login.string.Email}
|
||||
password={false}
|
||||
bind:value={object.email}
|
||||
on:input={validate}
|
||||
on:blur={() => object.email.trim()}
|
||||
disabled={disableEmailField}
|
||||
/>
|
||||
<StylishEdit
|
||||
label={login.string.Password}
|
||||
name={login.string.Password}
|
||||
password={true}
|
||||
bind:value={object.password}
|
||||
on:input={validate}
|
||||
on:blur={() => object.email.trim()}
|
||||
/>
|
||||
{#if isRejection}
|
||||
<div class="mt-2">
|
||||
<EditBox
|
||||
id="rejection-reason"
|
||||
label={plugin.string.RejectionReason}
|
||||
value={rejectionNote}
|
||||
placeholder={plugin.string.ProvideRejectionReason}
|
||||
kind="default"
|
||||
required={true}
|
||||
on:value={({ detail }) => {
|
||||
rejectionNote = detail
|
||||
void validate()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div slot="footerExtra">
|
||||
<StatusControl {status} overflow={false} />
|
||||
</div>
|
||||
</ModernDialog>
|
||||
@@ -38,6 +38,7 @@
|
||||
import plugin from '../../plugin'
|
||||
import ExecutionContextPresenter from '../attributeEditors/ExecutionContextPresenter.svelte'
|
||||
import ProcessContextPresenter from './ProcessContextPresenter.svelte'
|
||||
import { Class, Ref } from '@hcengineering/core'
|
||||
|
||||
export let readonly: boolean
|
||||
export let process: Process
|
||||
@@ -49,6 +50,8 @@
|
||||
export let justify: 'left' | 'center' = 'left'
|
||||
export let width: string | undefined = undefined
|
||||
|
||||
export let _class: Ref<Class<ProcessToDo>> = plugin.class.ProcessToDo
|
||||
|
||||
$: context = getContext(value)
|
||||
|
||||
const client = getClient()
|
||||
@@ -71,7 +74,7 @@
|
||||
const res: SelectPopupValueType[] = []
|
||||
for (const key in process.context) {
|
||||
const ctx = process.context[key as ContextId]
|
||||
if (ctx._class === plugin.class.ProcessToDo) {
|
||||
if (ctx._class === _class) {
|
||||
if (skipRollback) {
|
||||
const transition = client.getModel().findObject(ctx.producer)
|
||||
if (transition === undefined) {
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<!--
|
||||
// Copyright © 2026 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 contact from '@hcengineering/contact'
|
||||
import core, { AnyAttribute } from '@hcengineering/core'
|
||||
import { getAttributeEditor, getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
||||
import { ApproveRequest, Process, Step } from '@hcengineering/process'
|
||||
import { AnySvelteComponent } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
import { getContext, getMockAttribute } from '../../utils'
|
||||
import ProcessAttribute from '../ProcessAttribute.svelte'
|
||||
import ParamsEditor from './ParamsEditor.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let step: Step<ApproveRequest>
|
||||
|
||||
let params = step.params
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
function changeParams (e: CustomEvent<any>): void {
|
||||
if (e.detail !== undefined) {
|
||||
params = e.detail
|
||||
;(step.params as any) = params
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
|
||||
const keys = ['title', 'dueDate']
|
||||
|
||||
$: value = params.user
|
||||
|
||||
const type = {
|
||||
label: core.string.Array,
|
||||
_class: core.class.ArrOf,
|
||||
of: {
|
||||
label: core.string.Ref,
|
||||
_class: core.class.RefTo,
|
||||
to: contact.mixin.Employee
|
||||
}
|
||||
}
|
||||
|
||||
const attribute = getMockAttribute(plugin.class.ApproveRequest, plugin.string.Reviewers, type)
|
||||
|
||||
const presenterClass = getAttributePresenterClass(hierarchy, attribute.type)
|
||||
$: context = getContext(client, process, presenterClass.attrClass, presenterClass.category)
|
||||
|
||||
function onChange (e: CustomEvent<any>): void {
|
||||
params.user = e.detail
|
||||
;(step.params as any) = params
|
||||
dispatch('change', step)
|
||||
}
|
||||
|
||||
let editor: AnySvelteComponent | undefined
|
||||
|
||||
function getBaseEditor (attribute: AnyAttribute): void {
|
||||
void getAttributeEditor(client, plugin.class.ApproveRequest, {
|
||||
attr: attribute,
|
||||
key: 'user'
|
||||
}).then((p) => {
|
||||
editor = p
|
||||
})
|
||||
}
|
||||
|
||||
getBaseEditor(attribute)
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
<ProcessAttribute
|
||||
{process}
|
||||
{context}
|
||||
{editor}
|
||||
{attribute}
|
||||
{presenterClass}
|
||||
{value}
|
||||
masterTag={process.masterTag}
|
||||
allowArray={true}
|
||||
on:remove
|
||||
on:change={onChange}
|
||||
/>
|
||||
</div>
|
||||
<ParamsEditor _class={plugin.class.ApproveRequest} {process} {keys} {params} on:change={changeParams} />
|
||||
|
||||
<style lang="scss">
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
grid-auto-rows: minmax(2rem, max-content);
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
row-gap: 0.5rem;
|
||||
column-gap: 1rem;
|
||||
margin: 0.25rem 2rem 0;
|
||||
width: calc(100% - 4rem);
|
||||
height: min-content;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { Process } from '@hcengineering/process'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
import ToDoContextSelector from '../contextEditors/ToDoContextSelector.svelte'
|
||||
|
||||
export let readonly: boolean
|
||||
export let process: Process
|
||||
export let params: Record<string, any>
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function change (e: CustomEvent<string>): void {
|
||||
if (readonly || e.detail == null) return
|
||||
params._id = e.detail
|
||||
dispatch('change', { params })
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="editor-grid">
|
||||
<Label label={plugin.string.ApproveRequest} />
|
||||
<ToDoContextSelector
|
||||
{readonly}
|
||||
_class={plugin.class.ApproveRequest}
|
||||
{process}
|
||||
value={params._id}
|
||||
on:change={change}
|
||||
/>
|
||||
</div>
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { parseContext, Process, SelectedContext, SelectedExecutionContext } from '@hcengineering/process'
|
||||
import ui, { Label } from '@hcengineering/ui'
|
||||
import ExecutionContextPresenter from '../attributeEditors/ExecutionContextPresenter.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let params: Record<string, any>
|
||||
|
||||
$: context = getContext(params._id)
|
||||
|
||||
function getContext (value: string | undefined): SelectedExecutionContext | undefined {
|
||||
if (value === undefined) return
|
||||
const context = parseContext(value)
|
||||
if (context !== undefined && isExecutionContext(context)) {
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
function isExecutionContext (context: SelectedContext): context is SelectedExecutionContext {
|
||||
return context.type === 'context'
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if context === undefined}
|
||||
<Label label={ui.string.NotSelected} />
|
||||
{:else}
|
||||
<ExecutionContextPresenter {process} contextValue={context} />
|
||||
{/if}
|
||||
@@ -0,0 +1,53 @@
|
||||
<!--
|
||||
// Copyright © 2026 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 cardPlugin, { Tag } from '@hcengineering/card'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { Process, Step } from '@hcengineering/process'
|
||||
import { Label, tooltip } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import TagSelector from './TagSelector.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let step: Step<Tag>
|
||||
|
||||
const params = step.params
|
||||
let _id = params._id as Ref<Tag>
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function changeTag (e: CustomEvent<{ tag: Ref<Tag> }>): void {
|
||||
if (e.detail !== undefined) {
|
||||
_id = e.detail.tag
|
||||
params._id = _id
|
||||
step.params = params
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-col flex-gap-2">
|
||||
<div class="editor-grid">
|
||||
<span
|
||||
class="labelOnPanel"
|
||||
use:tooltip={{
|
||||
props: { label: cardPlugin.string.Tag }
|
||||
}}
|
||||
>
|
||||
<Label label={cardPlugin.string.Tag} />
|
||||
</span>
|
||||
<TagSelector {process} tag={_id} includeBase on:change={changeTag} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,33 @@
|
||||
<!--
|
||||
// Copyright © 2026 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 { getClient } from '@hcengineering/presentation'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import plugin from '../../plugin'
|
||||
|
||||
export let process: Process
|
||||
export let params: Record<string, any>
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: _class = client.getHierarchy().findClass(params._id)
|
||||
</script>
|
||||
|
||||
<Label label={plugin.string.LockSection} />:
|
||||
{#if _class !== undefined}
|
||||
<Label label={_class.label} />
|
||||
{/if}
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import cardPlugin, { Tag } from '@hcengineering/card'
|
||||
import cardPlugin, { Tag as MasterTag } from '@hcengineering/card'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Process } from '@hcengineering/process'
|
||||
@@ -21,7 +21,8 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let process: Process
|
||||
export let tag: Ref<Tag> | undefined = undefined
|
||||
export let tag: Ref<MasterTag> | undefined = undefined
|
||||
export let includeBase: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@@ -29,14 +30,14 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function open (e: MouseEvent): void {
|
||||
const res: Tag[] = []
|
||||
const res = new Set<MasterTag>(includeBase ? [hierarchy.getClass(process.masterTag)] : [])
|
||||
const ancestors = hierarchy.getAncestors(process.masterTag)
|
||||
const tags = client.getModel().findAllSync(cardPlugin.class.Tag, {})
|
||||
for (const p of tags) {
|
||||
try {
|
||||
const base = hierarchy.getBaseClass(p._id)
|
||||
if (process.masterTag === base || ancestors.includes(base)) {
|
||||
res.push(p)
|
||||
res.add(p)
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('error', err, p._id)
|
||||
@@ -44,7 +45,6 @@
|
||||
}
|
||||
const items: SelectPopupValueType[] = []
|
||||
res.forEach((cl) => {
|
||||
if (cl._class !== cardPlugin.class.Tag) return
|
||||
items.push({
|
||||
id: cl._id,
|
||||
label: cl.label,
|
||||
|
||||
@@ -24,11 +24,14 @@ import ExecutionMyToDos from './components/ExecutionMyToDos.svelte'
|
||||
import ExecutonPresenter from './components/ExecutonPresenter.svelte'
|
||||
import ExecutonProgressPresenter from './components/ExecutonProgressPresenter.svelte'
|
||||
import Main from './components/Main.svelte'
|
||||
import ApproveRequestPresenter from './components/ApproveRequestPresenter.svelte'
|
||||
import SubProcessPresenter from './components/presenters/SubProcessPresenter.svelte'
|
||||
import ToDoPresenter from './components/presenters/ToDoPresenter.svelte'
|
||||
import UpdateCardPresenter from './components/presenters/UpdateCardPresenter.svelte'
|
||||
import ProcessesCardSection from './components/ProcessesCardSection.svelte'
|
||||
import ProcessesExtension from './components/ProcessesExtension.svelte'
|
||||
import RequestsCardSection from './components/RequestsCardSection.svelte'
|
||||
import RequestsExtension from './components/RequestsExtension.svelte'
|
||||
import ProcessesSettingSection from './components/ProcessesSection.svelte'
|
||||
import ProcessPresenter from './components/ProcessPresenter.svelte'
|
||||
import RunProcessCardPopup from './components/RunProcessCardPopup.svelte'
|
||||
@@ -71,6 +74,11 @@ import TimeEditor from './components/settings/TimeEditor.svelte'
|
||||
import TimePresenter from './components/settings/TimePresenter.svelte'
|
||||
import ToDoSettingPresenter from './components/settings/ToDoPresenter.svelte'
|
||||
import TransitionRefPresenter from './components/settings/TransitionRefPresenter.svelte'
|
||||
import ApproveRequestEditor from './components/settings/ApproveRequestEditor.svelte'
|
||||
import ApproveRequestTriggerEditor from './components/settings/ApproveRequestTriggerEditor.svelte'
|
||||
import ApproveRequestTriggerPresenter from './components/settings/ApproveRequestTriggerPresenter.svelte'
|
||||
import LockSectionEditor from './components/settings/LockSectionEditor.svelte'
|
||||
import LockSectionPresenter from './components/settings/LockSectionPresenter.svelte'
|
||||
import AppendEditor from './components/transformEditors/AppendEditor.svelte'
|
||||
import CutEditor from './components/transformEditors/CutEditor.svelte'
|
||||
import ReplaceEditor from './components/transformEditors/ReplaceEditor.svelte'
|
||||
@@ -80,7 +88,10 @@ import RolePresenter from './components/transformPresenters/RolePresenter.svelte
|
||||
import { exportProcess } from './exporter'
|
||||
import { ProcessMiddleware } from './middleware'
|
||||
import {
|
||||
approveRequestApproved,
|
||||
approveRequestRejected,
|
||||
checkProcessSectionVisibility,
|
||||
checkRequestsSectionVisibility,
|
||||
continueExecution,
|
||||
eventCheck,
|
||||
fieldChangesCheck,
|
||||
@@ -109,6 +120,8 @@ export default async (): Promise<Resources> => ({
|
||||
ToDoPresenter,
|
||||
UpdateCardPresenter,
|
||||
ProcessesExtension,
|
||||
RequestsExtension,
|
||||
RequestsCardSection,
|
||||
ExecutonPresenter,
|
||||
ExecutonProgressPresenter,
|
||||
ProcessPresenter,
|
||||
@@ -148,7 +161,13 @@ export default async (): Promise<Resources> => ({
|
||||
FunctionSubmenu,
|
||||
SubProcessMatchEditor,
|
||||
SubProcessMatchPresenter,
|
||||
ProcessesHeaderExtension
|
||||
ProcessesHeaderExtension,
|
||||
ApproveRequestPresenter,
|
||||
ApproveRequestEditor,
|
||||
ApproveRequestTriggerEditor,
|
||||
ApproveRequestTriggerPresenter,
|
||||
LockSectionPresenter,
|
||||
LockSectionEditor
|
||||
},
|
||||
criteriaEditor: {
|
||||
BaseCriteria,
|
||||
@@ -177,12 +196,15 @@ export default async (): Promise<Resources> => ({
|
||||
SubProcessMatchCheck: subProcessMatchCheck,
|
||||
ToDo: todoTranstionCheck,
|
||||
Time: timeTransitionCheck,
|
||||
OnEventCheck: eventCheck
|
||||
OnEventCheck: eventCheck,
|
||||
ApproveRequestApproved: approveRequestApproved,
|
||||
ApproveRequestRejected: approveRequestRejected
|
||||
},
|
||||
function: {
|
||||
ExportProcess: exportProcess,
|
||||
ShowDoneQuery: showDoneQuery,
|
||||
CheckProcessSectionVisibility: checkProcessSectionVisibility,
|
||||
CheckRequestsSectionVisibility: checkRequestsSectionVisibility,
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
CreateMiddleware: ProcessMiddleware.create
|
||||
}
|
||||
|
||||
@@ -11,23 +11,24 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import cardPlugin, { type Card } from '@hcengineering/card'
|
||||
import core, {
|
||||
getCurrentAccount,
|
||||
type TxCreateDoc,
|
||||
type TxMixin,
|
||||
SortingOrder,
|
||||
TxOperations,
|
||||
TxProcessor,
|
||||
type Client,
|
||||
type Tx,
|
||||
type TxApplyIf,
|
||||
type TxCreateDoc,
|
||||
type TxMixin,
|
||||
type TxResult,
|
||||
type TxUpdateDoc,
|
||||
TxProcessor,
|
||||
SortingOrder
|
||||
type TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import { BasePresentationMiddleware, type PresentationMiddleware } from '@hcengineering/presentation'
|
||||
import process, { ExecutionStatus, type ProcessToDo, isUpdateTx } from '@hcengineering/process'
|
||||
import { createExecution, getNextStateUserInput, requestResult, pickTransition } from './utils'
|
||||
import cardPlugin, { type Card } from '@hcengineering/card'
|
||||
import { type ApproveRequest, ExecutionStatus, isUpdateTx, type ProcessToDo } from '@hcengineering/process'
|
||||
import process from './plugin'
|
||||
import { createExecution, getNextStateUserInput, pickTransition, requestResult } from './utils'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -65,6 +66,7 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
await this.handleCardUpdate(etx)
|
||||
await this.handleTagAdd(etx)
|
||||
await this.handleToDoDone(etx)
|
||||
await this.handleApproveRequest(etx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +150,43 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
}
|
||||
}
|
||||
|
||||
private async handleApproveRequest (etx: Tx): Promise<void> {
|
||||
if (etx._class === core.class.TxUpdateDoc) {
|
||||
const cud = etx as TxUpdateDoc<ApproveRequest>
|
||||
if (cud.objectClass !== process.class.ApproveRequest) return
|
||||
if (cud.operations.doneOn == null || cud.operations.approved == null) return
|
||||
const approveRequest = await this.client.findOne(process.class.ApproveRequest, {
|
||||
_id: cud.objectId
|
||||
})
|
||||
if (approveRequest === undefined) return
|
||||
const execution = await this.client.findOne(process.class.Execution, {
|
||||
_id: approveRequest.execution
|
||||
})
|
||||
if (execution === undefined) return
|
||||
const txop = new TxOperations(this.client, getCurrentAccount().primarySocialId)
|
||||
const transitions = this.client.getModel().findAllSync(
|
||||
process.class.Transition,
|
||||
{
|
||||
process: execution.process,
|
||||
from: execution.currentState,
|
||||
trigger: cud.operations.approved
|
||||
? process.trigger.OnApproveRequestApproved
|
||||
: process.trigger.OnApproveRequestRejected
|
||||
},
|
||||
{ sort: { rank: SortingOrder.Ascending } }
|
||||
)
|
||||
const updatedApproveRequest = TxProcessor.updateDoc2Doc(approveRequest, cud)
|
||||
const transition = await pickTransition(this.client, execution, transitions, {
|
||||
todo: updatedApproveRequest
|
||||
})
|
||||
if (transition === undefined) return
|
||||
const context = await getNextStateUserInput(execution, transition, execution.context)
|
||||
await txop.update(execution, {
|
||||
context
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private async handleToDoDone (etx: Tx): Promise<void> {
|
||||
if (etx._class === core.class.TxUpdateDoc) {
|
||||
const cud = etx as TxUpdateDoc<ProcessToDo>
|
||||
|
||||
@@ -22,19 +22,25 @@ export default mergeIds(processId, process, {
|
||||
viewlet: {
|
||||
ExecutionsList: '' as Ref<Viewlet>,
|
||||
ExecutionLogList: '' as Ref<Viewlet>,
|
||||
CardExecutions: '' as Ref<Viewlet>
|
||||
CardExecutions: '' as Ref<Viewlet>,
|
||||
CardRequests: '' as Ref<Viewlet>
|
||||
},
|
||||
component: {
|
||||
Main: '' as AnyComponent,
|
||||
ProcessEditor: '' as AnyComponent,
|
||||
ProcessesSettingSection: '' as AnyComponent,
|
||||
SubProcessEditor: '' as AnyComponent,
|
||||
ApproveRequestEditor: '' as AnyComponent,
|
||||
ApproveRequestPresenter: '' as AnyComponent,
|
||||
ApproveRequestTriggerEditor: '' as AnyComponent,
|
||||
ApproveRequestTriggerPresenter: '' as AnyComponent,
|
||||
UpdateCardEditor: '' as AnyComponent,
|
||||
ToDoEditor: '' as AnyComponent,
|
||||
SubProcessPresenter: '' as AnyComponent,
|
||||
ToDoPresenter: '' as AnyComponent,
|
||||
RunProcessPopup: '' as AnyComponent,
|
||||
UpdateCardPresenter: '' as AnyComponent,
|
||||
RequestsExtension: '' as AnyComponent,
|
||||
ProcessesExtension: '' as AnyComponent,
|
||||
ProcessesHeaderExtension: '' as AnyComponent,
|
||||
ProcessPresenter: '' as AnyComponent,
|
||||
@@ -56,6 +62,7 @@ export default mergeIds(processId, process, {
|
||||
ToDoCloseEditor: '' as AnyComponent,
|
||||
ToDoRemoveEditor: '' as AnyComponent,
|
||||
ProcessesCardSection: '' as AnyComponent,
|
||||
RequestsCardSection: '' as AnyComponent,
|
||||
TransitionEditor: '' as AnyComponent,
|
||||
StateEditor: '' as AnyComponent,
|
||||
TransitionRefPresenter: '' as AnyComponent,
|
||||
@@ -77,7 +84,9 @@ export default mergeIds(processId, process, {
|
||||
AddTagPresenter: '' as AnyComponent,
|
||||
SubProcessMatchEditor: '' as AnyComponent,
|
||||
SubProcessMatchPresenter: '' as AnyComponent,
|
||||
FunctionSubmenu: '' as AnyComponent
|
||||
FunctionSubmenu: '' as AnyComponent,
|
||||
LockSectionEditor: '' as AnyComponent,
|
||||
LockSectionPresenter: '' as AnyComponent
|
||||
},
|
||||
criteriaEditor: {
|
||||
BaseCriteria: '' as AnyComponent,
|
||||
@@ -217,7 +226,23 @@ export default mergeIds(processId, process, {
|
||||
RunProcessPermission: '' as IntlString,
|
||||
CancelProcessPermission: '' as IntlString,
|
||||
ForbidRunProcessPermission: '' as IntlString,
|
||||
ForbidCancelProcessPermission: '' as IntlString
|
||||
ForbidCancelProcessPermission: '' as IntlString,
|
||||
RequestApproval: '' as IntlString,
|
||||
IsApproved: '' as IntlString,
|
||||
Approve: '' as IntlString,
|
||||
Reject: '' as IntlString,
|
||||
Reason: '' as IntlString,
|
||||
ApproveRequest: '' as IntlString,
|
||||
OnApproveRequestApproved: '' as IntlString,
|
||||
OnApproveRequestRejected: '' as IntlString,
|
||||
ConfirmApproval: '' as IntlString,
|
||||
ConfirmRejection: '' as IntlString,
|
||||
RejectionReason: '' as IntlString,
|
||||
ProvideRejectionReason: '' as IntlString,
|
||||
FieldIsEmpty: '' as IntlString,
|
||||
Reviewers: '' as IntlString,
|
||||
LockCard: '' as IntlString,
|
||||
LockSection: '' as IntlString
|
||||
},
|
||||
permission: {
|
||||
RunProcess: '' as Ref<Permission>,
|
||||
|
||||
@@ -643,6 +643,26 @@ export function eventCheck (
|
||||
return context.eventType === params.eventType
|
||||
}
|
||||
|
||||
export async function approveRequestApproved (
|
||||
client: Client,
|
||||
execution: Execution,
|
||||
params: Record<string, any>,
|
||||
context: Record<string, any>
|
||||
): Promise<boolean> {
|
||||
if (params._id === undefined) return false
|
||||
return context.todo?.group === params._id && context.todo?.approved === true
|
||||
}
|
||||
|
||||
export async function approveRequestRejected (
|
||||
client: Client,
|
||||
execution: Execution,
|
||||
params: Record<string, any>,
|
||||
context: Record<string, any>
|
||||
): Promise<boolean> {
|
||||
if (params._id === undefined) return false
|
||||
return context.todo?.group === params._id && context.todo?.approved === false
|
||||
}
|
||||
|
||||
export function matchCardCheck (
|
||||
client: Client,
|
||||
execution: Execution,
|
||||
@@ -756,3 +776,9 @@ export async function checkProcessSectionVisibility (doc: Card): Promise<boolean
|
||||
const processes = client.getModel().findAllSync(process.class.Process, { masterTag: { $in: anc } })
|
||||
return processes.length > 0
|
||||
}
|
||||
|
||||
export async function checkRequestsSectionVisibility (doc: Card): Promise<boolean> {
|
||||
const client = getClient()
|
||||
const requests = await client.findOne(process.class.ApproveRequest, { card: doc._id })
|
||||
return requests !== undefined
|
||||
}
|
||||
|
||||
@@ -125,6 +125,16 @@ export interface ProcessToDo extends ToDo {
|
||||
results?: UserResult[]
|
||||
}
|
||||
|
||||
export interface ApproveRequest extends ProcessToDo {
|
||||
card: Ref<Card>
|
||||
|
||||
approved?: boolean
|
||||
|
||||
reason?: string
|
||||
|
||||
group: string
|
||||
}
|
||||
|
||||
export type MethodParams<T extends Doc> = {
|
||||
[P in keyof T]?: ObjQueryType<T[P]> | string
|
||||
} & DocumentUpdate<T> &
|
||||
@@ -214,6 +224,7 @@ export default plugin(processId, {
|
||||
Process: '' as Ref<Class<Process>>,
|
||||
Execution: '' as Ref<Class<Execution>>,
|
||||
ProcessToDo: '' as Ref<Class<ProcessToDo>>,
|
||||
ApproveRequest: '' as Ref<Class<ApproveRequest>>,
|
||||
Method: '' as Ref<Class<Method<Doc>>>,
|
||||
State: '' as Ref<Class<State>>,
|
||||
ProcessFunction: '' as Ref<Class<ProcessFunction>>,
|
||||
@@ -233,7 +244,10 @@ export default plugin(processId, {
|
||||
UpdateCard: '' as Ref<Method<Card>>,
|
||||
CreateCard: '' as Ref<Method<Card>>,
|
||||
AddRelation: '' as Ref<Method<Association>>,
|
||||
AddTag: '' as Ref<Method<Tag>>
|
||||
AddTag: '' as Ref<Method<Tag>>,
|
||||
RequestApproval: '' as Ref<Method<ApproveRequest>>,
|
||||
LockCard: '' as Ref<Method<Card>>,
|
||||
LockSection: '' as Ref<Method<Card>>
|
||||
},
|
||||
trigger: {
|
||||
OnCardUpdate: '' as Ref<Trigger>, // in fact WhenCardMatches, should migrate in future
|
||||
@@ -245,7 +259,9 @@ export default plugin(processId, {
|
||||
OnExecutionStart: '' as Ref<Trigger>,
|
||||
OnExecutionContinue: '' as Ref<Trigger>,
|
||||
OnTime: '' as Ref<Trigger>,
|
||||
OnEvent: '' as Ref<Trigger>
|
||||
OnEvent: '' as Ref<Trigger>,
|
||||
OnApproveRequestApproved: '' as Ref<Trigger>,
|
||||
OnApproveRequestRejected: '' as Ref<Trigger>
|
||||
},
|
||||
triggerCheck: {
|
||||
ToDo: '' as Resource<CheckFunc>,
|
||||
@@ -254,7 +270,9 @@ export default plugin(processId, {
|
||||
SubProcessesDoneCheck: '' as Resource<CheckFunc>,
|
||||
SubProcessMatchCheck: '' as Resource<CheckFunc>,
|
||||
Time: '' as Resource<CheckFunc>,
|
||||
OnEventCheck: '' as Resource<CheckFunc>
|
||||
OnEventCheck: '' as Resource<CheckFunc>,
|
||||
ApproveRequestApproved: '' as Resource<CheckFunc>,
|
||||
ApproveRequestRejected: '' as Resource<CheckFunc>
|
||||
},
|
||||
string: {
|
||||
Method: '' as IntlString,
|
||||
@@ -341,6 +359,7 @@ export default plugin(processId, {
|
||||
EmptyArray: '' as Ref<ProcessFunction>,
|
||||
CurrentDate: '' as Ref<ProcessFunction>,
|
||||
ExportProcess: '' as Resource<ExportFunc>,
|
||||
CheckProcessSectionVisibility: '' as Resource<(doc: Card) => Promise<boolean>>
|
||||
CheckProcessSectionVisibility: '' as Resource<(doc: Card) => Promise<boolean>>,
|
||||
CheckRequestsSectionVisibility: '' as Resource<(doc: Card) => Promise<boolean>>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -29,11 +29,13 @@ import core, {
|
||||
Relation,
|
||||
splitMixinUpdate,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
TxProcessor,
|
||||
Type,
|
||||
TypeNumber
|
||||
} from '@hcengineering/core'
|
||||
import process, {
|
||||
ApproveRequest,
|
||||
Execution,
|
||||
ExecutionContext,
|
||||
ExecutionStatus,
|
||||
@@ -392,6 +394,124 @@ export async function RunSubProcess (
|
||||
return { txes: res, rollback, context: resultContext }
|
||||
}
|
||||
|
||||
export async function RequestApproval (
|
||||
params: MethodParams<ApproveRequest>,
|
||||
execution: Execution,
|
||||
control: ProcessControl,
|
||||
results: UserResult[] | undefined
|
||||
): Promise<ExecuteResult> {
|
||||
if (params.title === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: 'title' })
|
||||
if (params.user === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: 'user' })
|
||||
const group = generateId()
|
||||
const res: TxCreateDoc<ApproveRequest>[] = []
|
||||
const rollback: Tx[] = []
|
||||
for (const user of Array.isArray(params.user) ? params.user : [params.user]) {
|
||||
const id = generateId<ApproveRequest>()
|
||||
const tx = control.client.txFactory.createTxCreateDoc(
|
||||
process.class.ApproveRequest,
|
||||
time.space.ToDos,
|
||||
{
|
||||
attachedTo: execution.card,
|
||||
attachedToClass: cardPlugin.class.Card,
|
||||
collection: 'todos',
|
||||
workslots: 0,
|
||||
execution: execution._id,
|
||||
title: params.title,
|
||||
user,
|
||||
description: params.description ?? '',
|
||||
dueDate: params.dueDate,
|
||||
priority: params.priority ?? ToDoPriority.NoPriority,
|
||||
visibility: 'public',
|
||||
card: execution.card,
|
||||
doneOn: null,
|
||||
rank: '',
|
||||
withRollback: params.withRollback ?? false,
|
||||
results,
|
||||
group
|
||||
},
|
||||
id
|
||||
)
|
||||
res.push(tx)
|
||||
rollback.push(control.client.txFactory.createTxRemoveDoc(process.class.ApproveRequest, time.space.ToDos, id))
|
||||
}
|
||||
return {
|
||||
txes: res,
|
||||
rollback,
|
||||
context: [
|
||||
{
|
||||
_id: group,
|
||||
value: res.map((tx) => TxProcessor.createDoc2Doc(tx, true))
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export async function ApproveRequestApproved (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
params: Record<string, any>,
|
||||
context: Record<string, any>
|
||||
): Promise<boolean> {
|
||||
if (params._id === undefined) return false
|
||||
const todo = await control.client.findAll(process.class.ApproveRequest, { group: params._id })
|
||||
return todo.every((t) => t.approved === true)
|
||||
}
|
||||
|
||||
export async function ApproveRequestRejected (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
params: Record<string, any>,
|
||||
context: Record<string, any>
|
||||
): Promise<boolean> {
|
||||
if (params._id === undefined) return false
|
||||
const todo = await control.client.findAll(process.class.ApproveRequest, { group: params._id })
|
||||
return todo.some((t) => t.approved === false)
|
||||
}
|
||||
|
||||
export async function LockCard (
|
||||
params: MethodParams<Card>,
|
||||
execution: Execution,
|
||||
control: ProcessControl
|
||||
): Promise<ExecuteResult> {
|
||||
const res: Tx[] = []
|
||||
const rollback: Tx[] = []
|
||||
const tx = control.client.txFactory.createTxUpdateDoc(cardPlugin.class.Card, execution.space, execution.card, {
|
||||
readonly: true
|
||||
})
|
||||
res.push(tx)
|
||||
rollback.push(
|
||||
control.client.txFactory.createTxUpdateDoc(cardPlugin.class.Card, execution.space, execution.card, {
|
||||
readonly: false
|
||||
})
|
||||
)
|
||||
return { txes: res, rollback, context: [] }
|
||||
}
|
||||
|
||||
export async function LockSection (
|
||||
params: MethodParams<Card>,
|
||||
execution: Execution,
|
||||
control: ProcessControl
|
||||
): Promise<ExecuteResult> {
|
||||
if (params._id === undefined) throw processError(process.error.RequiredParamsNotProvided, { params: '_id' })
|
||||
const res: Tx[] = []
|
||||
const rollback: Tx[] = []
|
||||
const card = await control.client.findOne(cardPlugin.class.Card, { _id: execution.card })
|
||||
if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card })
|
||||
const readonlySections = card.readonlySections ?? []
|
||||
const target = params._id as Ref<MasterTag>
|
||||
readonlySections.push(target)
|
||||
const tx = control.client.txFactory.createTxUpdateDoc(cardPlugin.class.Card, execution.space, execution.card, {
|
||||
readonlySections
|
||||
})
|
||||
res.push(tx)
|
||||
rollback.push(
|
||||
control.client.txFactory.createTxUpdateDoc(cardPlugin.class.Card, execution.space, execution.card, {
|
||||
$pull: { readonlySections: target }
|
||||
})
|
||||
)
|
||||
return { txes: res, rollback, context: [] }
|
||||
}
|
||||
|
||||
export async function CreateToDo (
|
||||
params: MethodParams<ProcessToDo>,
|
||||
execution: Execution,
|
||||
|
||||
@@ -39,7 +39,8 @@ import process, {
|
||||
Step,
|
||||
Transition,
|
||||
isUpdateTx,
|
||||
ProcessCustomEvent
|
||||
ProcessCustomEvent,
|
||||
ApproveRequest
|
||||
} from '@hcengineering/process'
|
||||
import { QueueTopic, TriggerControl } from '@hcengineering/server-core'
|
||||
import { ProcessMessage } from '@hcengineering/server-process'
|
||||
@@ -97,7 +98,12 @@ import {
|
||||
CheckSubProcessMatch,
|
||||
CheckTime,
|
||||
FieldChangedCheck,
|
||||
EventCheck
|
||||
EventCheck,
|
||||
RequestApproval,
|
||||
ApproveRequestApproved,
|
||||
ApproveRequestRejected,
|
||||
LockCard,
|
||||
LockSection
|
||||
} from './functions'
|
||||
import { ToDoCancellRollback, ToDoCloseRollback } from './rollback'
|
||||
|
||||
@@ -139,6 +145,42 @@ export async function OnProcessToDoClose (txes: Tx[], control: TriggerControl):
|
||||
},
|
||||
control
|
||||
)
|
||||
if (todo._class === process.class.ApproveRequest) {
|
||||
const request = todo as ApproveRequest
|
||||
if (request.approved === true) {
|
||||
await putEventToQueue(
|
||||
{
|
||||
event: process.trigger.OnApproveRequestApproved,
|
||||
execution: todo.execution,
|
||||
createdOn: tx.modifiedOn,
|
||||
context: {
|
||||
todo
|
||||
}
|
||||
},
|
||||
control
|
||||
)
|
||||
} else if (request.approved === false) {
|
||||
// remove all other approve requests for this execution
|
||||
const toRemove = await control.findAll(control.ctx, process.class.ApproveRequest, {
|
||||
group: request.group,
|
||||
doneOn: null
|
||||
})
|
||||
for (const req of toRemove) {
|
||||
res.push(control.txFactory.createTxRemoveDoc(req._class, req.space, req._id))
|
||||
}
|
||||
await putEventToQueue(
|
||||
{
|
||||
event: process.trigger.OnApproveRequestRejected,
|
||||
execution: todo.execution,
|
||||
createdOn: tx.modifiedOn,
|
||||
context: {
|
||||
todo
|
||||
}
|
||||
},
|
||||
control
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -476,7 +518,12 @@ export default async () => ({
|
||||
CheckSubProcessesDone,
|
||||
CheckSubProcessMatch,
|
||||
CheckTime,
|
||||
EventCheck
|
||||
EventCheck,
|
||||
RequestApproval,
|
||||
ApproveRequestApproved,
|
||||
ApproveRequestRejected,
|
||||
LockCard,
|
||||
LockSection
|
||||
},
|
||||
transform: {
|
||||
CurrentDate,
|
||||
|
||||
@@ -54,6 +54,9 @@ export default plugin(serverProcessId, {
|
||||
AddRelation: '' as Resource<ExecuteFunc>,
|
||||
WaitSubProcess: '' as Resource<ExecuteFunc>,
|
||||
AddTag: '' as Resource<ExecuteFunc>,
|
||||
RequestApproval: '' as Resource<ExecuteFunc>,
|
||||
LockCard: '' as Resource<ExecuteFunc>,
|
||||
LockSection: '' as Resource<ExecuteFunc>,
|
||||
CheckToDoDone: '' as Resource<CheckFunc>,
|
||||
CheckToDoCancelled: '' as Resource<CheckFunc>,
|
||||
MatchCardCheck: '' as Resource<CheckFunc>,
|
||||
@@ -61,7 +64,9 @@ export default plugin(serverProcessId, {
|
||||
CheckSubProcessesDone: '' as Resource<CheckFunc>,
|
||||
CheckSubProcessMatch: '' as Resource<CheckFunc>,
|
||||
CheckTime: '' as Resource<CheckFunc>,
|
||||
EventCheck: '' as Resource<CheckFunc>
|
||||
EventCheck: '' as Resource<CheckFunc>,
|
||||
ApproveRequestApproved: '' as Resource<CheckFunc>,
|
||||
ApproveRequestRejected: '' as Resource<CheckFunc>
|
||||
},
|
||||
transform: {
|
||||
FirstValue: '' as Resource<TransformFunc>,
|
||||
|
||||
Reference in New Issue
Block a user