mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-20 19:32:22 +02:00
* Add date difference calculation functionality Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com> * Fix Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com> * Fix formatting Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com> --------- Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
539 lines
15 KiB
TypeScript
539 lines
15 KiB
TypeScript
//
|
|
// 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.
|
|
//
|
|
|
|
import contact, { Employee, Person } from '@hcengineering/contact'
|
|
import core, { Doc, matchQuery, Ref, Timestamp } from '@hcengineering/core'
|
|
import { Execution, parseContext } from '@hcengineering/process'
|
|
import { ProcessControl } from '@hcengineering/server-process'
|
|
import { getContextValue } from './utils'
|
|
import cardPlugin from '@hcengineering/card'
|
|
|
|
// #region ArrayReduce
|
|
|
|
export function FirstValue (value: Doc[]): Doc | undefined {
|
|
if (!Array.isArray(value)) return value
|
|
return value[0]
|
|
}
|
|
|
|
export function LastValue (value: Doc[]): Doc | undefined {
|
|
if (!Array.isArray(value)) return value
|
|
if (value.length === 0) return
|
|
return value[value.length - 1]
|
|
}
|
|
|
|
export function Random (value: Doc[]): Doc {
|
|
if (!Array.isArray(value)) return value
|
|
return value[Math.floor(Math.random() * value.length)]
|
|
}
|
|
|
|
export function All (value: Doc[]): Doc[] {
|
|
return value ?? []
|
|
}
|
|
|
|
export async function FirstMatchValue (
|
|
value: any[],
|
|
props: Record<string, any>,
|
|
control: ProcessControl
|
|
): Promise<any | undefined> {
|
|
if (value == null) {
|
|
return
|
|
}
|
|
if (!Array.isArray(value)) return value
|
|
const { _class, ...otherProps } = props
|
|
if (_class == null) return
|
|
if (value.length === 0) return
|
|
if (typeof value[0] === 'string') {
|
|
const docs = await control.client.findAll(_class, { _id: { $in: value } })
|
|
return matchQuery(docs, otherProps, core.class.Doc, control.client.getHierarchy(), true)[0]?._id
|
|
} else if (typeof value[0] === 'object') {
|
|
return matchQuery(value, otherProps, core.class.Doc, control.client.getHierarchy(), true)[0]
|
|
}
|
|
}
|
|
|
|
// #endregion
|
|
|
|
// #region Array
|
|
|
|
export async function Insert (
|
|
value: any[],
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<any[]> {
|
|
if (value == null) {
|
|
value = []
|
|
}
|
|
if (!Array.isArray(value)) return value
|
|
if (props.value == null) return value
|
|
const context = parseContext(props.value)
|
|
if (context !== undefined) {
|
|
const val = await getContextValue(props.value, control, execution)
|
|
Array.isArray(val) ? value.push(...val) : value.push(val)
|
|
} else {
|
|
value.push(props.value)
|
|
}
|
|
return value
|
|
}
|
|
|
|
export async function Remove (
|
|
value: any[],
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<any[]> {
|
|
if (value == null) {
|
|
value = []
|
|
}
|
|
if (!Array.isArray(value)) return value
|
|
if (props.value == null) return value
|
|
const context = parseContext(props.value)
|
|
if (context !== undefined) {
|
|
const val = await getContextValue(props.value, control, execution)
|
|
return value.filter((item) => item !== val)
|
|
} else {
|
|
return value.filter((item) => item !== props.value)
|
|
}
|
|
}
|
|
|
|
export function RemoveFirst (value: any[]): any[] {
|
|
if (value == null) {
|
|
value = []
|
|
}
|
|
if (!Array.isArray(value)) return value
|
|
return value.slice(1)
|
|
}
|
|
|
|
export function RemoveLast (value: any[]): any[] {
|
|
if (value == null) {
|
|
value = []
|
|
}
|
|
if (!Array.isArray(value)) return value
|
|
return value.slice(0, -1)
|
|
}
|
|
|
|
export async function Filter (value: any[], props: Record<string, any>, control: ProcessControl): Promise<any[]> {
|
|
if (value == null) {
|
|
return []
|
|
}
|
|
if (!Array.isArray(value)) return value
|
|
const { _class, ...otherProps } = props
|
|
if (_class == null) return value
|
|
if (value.length === 0) return value
|
|
if (typeof value[0] === 'string') {
|
|
const docs = await control.client.findAll(_class, { _id: { $in: value } })
|
|
return matchQuery(docs, otherProps, core.class.Doc, control.client.getHierarchy(), true).map((p) => p._id)
|
|
} else if (typeof value[0] === 'object') {
|
|
return matchQuery(value, otherProps, core.class.Doc, control.client.getHierarchy(), true)
|
|
}
|
|
return value
|
|
}
|
|
|
|
// #endregion
|
|
|
|
// #region String
|
|
|
|
export function UpperCase (value: string): string {
|
|
if (typeof value !== 'string') return value
|
|
return value.toUpperCase()
|
|
}
|
|
|
|
export function LowerCase (value: string): string {
|
|
if (typeof value !== 'string') return value
|
|
return value.toLowerCase()
|
|
}
|
|
|
|
export function Trim (value: string): string {
|
|
if (typeof value !== 'string') return value
|
|
return value.trim()
|
|
}
|
|
|
|
export async function Prepend (
|
|
value: string,
|
|
props: Record<string, string>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<string> {
|
|
const context = parseContext(props.value)
|
|
if (context !== undefined) {
|
|
const val = await getContextValue(props.value, control, execution)
|
|
if (typeof val !== 'string') return value
|
|
return val + value
|
|
} else if (typeof value === 'string') {
|
|
return props.value + value
|
|
}
|
|
return value
|
|
}
|
|
|
|
export async function Append (
|
|
value: string,
|
|
props: Record<string, string>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<string> {
|
|
const context = parseContext(props.value)
|
|
if (context !== undefined) {
|
|
const val = await getContextValue(props.value, control, execution)
|
|
if (typeof val !== 'string') return value
|
|
return value + val
|
|
} else if (typeof value === 'string') {
|
|
return value + props.value
|
|
}
|
|
return value
|
|
}
|
|
|
|
export function Replace (value: string, props: Record<string, string>): string {
|
|
if (typeof value !== 'string') return value
|
|
return value.replace(props.search, props.replacement)
|
|
}
|
|
|
|
export function ReplaceAll (value: string, props: Record<string, string>): string {
|
|
if (typeof value !== 'string') return value
|
|
return value.replaceAll(props.search, props.replacement)
|
|
}
|
|
|
|
export function Split (value: string, props: Record<string, string>): string {
|
|
if (typeof value !== 'string') return value
|
|
return value.split(props.separator)[0]
|
|
}
|
|
|
|
export function Cut (value: string, props: Record<string, number>): string {
|
|
if (typeof value !== 'string') return value
|
|
const start = props.start ?? 1
|
|
const end = props.end ?? value.length + 1
|
|
return value.slice(start - 1, end)
|
|
}
|
|
|
|
// #endregion
|
|
|
|
// #region Dates
|
|
|
|
export function FirstWorkingDayAfter (val: Timestamp): Timestamp {
|
|
if (typeof val !== 'number') return val
|
|
const value = new Date(val)
|
|
const day = value.getUTCDay()
|
|
if (day === 6 || day === 0) {
|
|
const date = value.getDate() + (day === 6 ? 2 : 1)
|
|
const res = value.setDate(date)
|
|
return res
|
|
}
|
|
return val
|
|
}
|
|
|
|
export function Offset (val: Timestamp, props: Record<string, any>): Timestamp {
|
|
if (typeof val !== 'number') return val
|
|
const value = new Date(val)
|
|
const offset = props.offset * (props.direction === 'after' ? 1 : -1)
|
|
switch (props.offsetType) {
|
|
case 'days':
|
|
return value.setDate(value.getDate() + offset)
|
|
case 'weeks':
|
|
return value.setDate(value.getDate() + 7 * offset)
|
|
case 'months':
|
|
return value.setMonth(value.getMonth() + offset)
|
|
}
|
|
return val
|
|
}
|
|
|
|
export async function DateDifference (
|
|
value: Timestamp,
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<number> {
|
|
if (typeof value !== 'number') return 0
|
|
const otherDate = await getContextValue(props.otherDate, control, execution)
|
|
if (typeof otherDate !== 'number') return 0
|
|
|
|
const diff = value - otherDate
|
|
const v = new Date(value)
|
|
const o = new Date(otherDate)
|
|
switch (props.unit) {
|
|
case 'hours':
|
|
return Math.floor(diff / (1000 * 60 * 60))
|
|
case 'weeks':
|
|
return Math.floor(diff / (1000 * 60 * 60 * 24 * 7))
|
|
case 'months':
|
|
return (v.getUTCFullYear() - o.getUTCFullYear()) * 12 + (v.getUTCMonth() - o.getUTCMonth())
|
|
case 'years':
|
|
return v.getUTCFullYear() - o.getUTCFullYear()
|
|
case 'days':
|
|
default:
|
|
return Math.floor(diff / (1000 * 60 * 60 * 24))
|
|
}
|
|
}
|
|
|
|
// #endregion
|
|
|
|
// #region Numbers
|
|
|
|
export async function Add (
|
|
value: number,
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<number> {
|
|
const context = parseContext(props.value)
|
|
if (context !== undefined) {
|
|
const val = await getContextValue(props.value, control, execution)
|
|
if (typeof val !== 'number') return value
|
|
return value + val
|
|
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
|
return value + props.value
|
|
}
|
|
return value
|
|
}
|
|
|
|
export async function Subtract (
|
|
value: number,
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<number> {
|
|
const context = parseContext(props.value)
|
|
if (context !== undefined) {
|
|
const val = await getContextValue(props.value, control, execution)
|
|
if (typeof val !== 'number') return value
|
|
return value - val
|
|
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
|
return value - props.value
|
|
}
|
|
return value
|
|
}
|
|
|
|
export async function Multiply (
|
|
value: number,
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<number> {
|
|
const context = parseContext(props.value)
|
|
if (context !== undefined) {
|
|
const val = await getContextValue(props.value, control, execution)
|
|
if (typeof val !== 'number') return value
|
|
return value * val
|
|
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
|
return value * props.value
|
|
}
|
|
return value
|
|
}
|
|
|
|
export async function Divide (
|
|
value: number,
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<number> {
|
|
const context = parseContext(props.value)
|
|
if (context !== undefined) {
|
|
const val = await getContextValue(props.value, control, execution)
|
|
if (val === 0 || typeof val !== 'number') {
|
|
return value // Avoid division by zero
|
|
}
|
|
return value / val
|
|
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
|
if (props.value === 0) {
|
|
return value // Avoid division by zero
|
|
}
|
|
return value / props.value
|
|
}
|
|
return value
|
|
}
|
|
|
|
export async function Modulo (
|
|
value: number,
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<number> {
|
|
const context = parseContext(props.value)
|
|
if (context !== undefined) {
|
|
const val = await getContextValue(props.value, control, execution)
|
|
if (val === 0 || typeof val !== 'number') {
|
|
return value // Avoid division by zero
|
|
}
|
|
return value % val
|
|
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
|
if (props.value === 0) {
|
|
return value // Avoid division by zero
|
|
}
|
|
return value % props.value
|
|
}
|
|
return value
|
|
}
|
|
|
|
export async function Power (
|
|
value: number,
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<number> {
|
|
const context = parseContext(props.value)
|
|
if (context !== undefined) {
|
|
const val = await getContextValue(props.value, control, execution)
|
|
if (typeof val !== 'number') return value
|
|
return Math.pow(value, val)
|
|
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
|
return Math.pow(value, props.value)
|
|
}
|
|
return value
|
|
}
|
|
|
|
export function Sqrt (value: number): number {
|
|
if (typeof value === 'number') {
|
|
return Math.sqrt(value)
|
|
}
|
|
return value
|
|
}
|
|
|
|
export function Round (value: number): number {
|
|
if (typeof value === 'number') {
|
|
return Math.round(value)
|
|
}
|
|
return value
|
|
}
|
|
|
|
export function Absolute (value: number): number {
|
|
if (typeof value === 'number') {
|
|
return Math.abs(value)
|
|
}
|
|
return value
|
|
}
|
|
|
|
export function Ceil (value: number): number {
|
|
if (typeof value === 'number') {
|
|
return Math.ceil(value)
|
|
}
|
|
return value
|
|
}
|
|
|
|
export function Floor (value: number): number {
|
|
if (typeof value === 'number') {
|
|
return Math.floor(value)
|
|
}
|
|
return value
|
|
}
|
|
|
|
// #endregion
|
|
|
|
// #region Func
|
|
|
|
export async function RoleContext (
|
|
value: null,
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<Ref<Employee>[]> {
|
|
const targetRole = props.target
|
|
if (targetRole === undefined) return []
|
|
const targetCard =
|
|
control.cache.get(execution.card) ?? (await control.client.findOne(cardPlugin.class.Card, { _id: execution.card }))
|
|
if (targetCard === undefined) return []
|
|
const targetSpace =
|
|
control.cache.get(targetCard.space) ?? (await control.client.findOne(core.class.Space, { _id: targetCard.space }))
|
|
if (targetSpace === undefined) return []
|
|
const mixin = control.client.getHierarchy().as(targetSpace, core.mixin.SpacesTypeData)
|
|
const accs = (mixin as any)[targetRole]
|
|
if (accs === undefined || accs.length === 0) return []
|
|
const users = await control.client.findAll(contact.mixin.Employee, { personUuid: { $in: accs } })
|
|
return users.map((it) => it._id)
|
|
}
|
|
|
|
export async function CurrentUser (
|
|
value: null,
|
|
props: Record<string, any>,
|
|
control: ProcessControl
|
|
): Promise<Ref<Person> | undefined> {
|
|
const socialId = await control.client.findOne(contact.class.SocialIdentity, { _id: control.client.user as any })
|
|
return socialId?.attachedTo
|
|
}
|
|
|
|
export async function CurrentDate (): Promise<Timestamp> {
|
|
return Date.now()
|
|
}
|
|
|
|
export function EmptyArray (): any[] {
|
|
return []
|
|
}
|
|
|
|
export async function ExecutionInitiator (
|
|
value: null,
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<Ref<Person> | undefined> {
|
|
const socialId = await control.client.findOne(contact.class.SocialIdentity, {
|
|
_id: (execution.createdBy ?? execution.modifiedBy) as any
|
|
})
|
|
return socialId?.attachedTo
|
|
}
|
|
|
|
export async function ExecutionStarted (
|
|
value: null,
|
|
props: Record<string, any>,
|
|
control: ProcessControl,
|
|
execution: Execution
|
|
): Promise<Timestamp> {
|
|
return execution.createdOn ?? execution.modifiedOn
|
|
}
|
|
|
|
// #endregion
|
|
|
|
// #region Convert
|
|
|
|
export function StringFromNumber (value: number): string {
|
|
if (value == null) return ''
|
|
return String(value)
|
|
}
|
|
|
|
export function StringFromDate (value: Timestamp): string {
|
|
if (value == null) return ''
|
|
return new Date(value).toISOString()
|
|
}
|
|
|
|
export function StringFromBoolean (value: boolean): string {
|
|
if (value == null) return ''
|
|
return String(value)
|
|
}
|
|
|
|
export function NumberFromDate (value: Timestamp): number {
|
|
return value
|
|
}
|
|
|
|
export function DateFromNumber (value: number): Date {
|
|
return new Date(value)
|
|
}
|
|
|
|
export function NumberFromString (value: string): number {
|
|
return Number(value)
|
|
}
|
|
|
|
export function DateFromString (value: string): Date {
|
|
return new Date(value)
|
|
}
|
|
|
|
export function YearFromDate (value: Date): number {
|
|
return new Date(value).getFullYear()
|
|
}
|
|
|
|
export function MonthFromDate (value: Date): number {
|
|
return new Date(value).getMonth() + 1
|
|
}
|
|
|
|
export function DayFromDate (value: Date): number {
|
|
return new Date(value).getDate()
|
|
}
|
|
|
|
// #endregion
|