mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
Fix HR statistics (#2242)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
0e0b8ab766
commit
261284df29
12
changelog.md
12
changelog.md
@ -1,11 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.31 (upcoming)
|
||||
## 0.6.32 (upcoming)
|
||||
|
||||
## 0.6.31
|
||||
|
||||
Core:
|
||||
|
||||
- Fix password change settings
|
||||
- Fix settings collapse
|
||||
- Allow to add multiple enum values
|
||||
- Fix password change issues
|
||||
- Fix minxin query
|
||||
|
||||
HR:
|
||||
|
||||
- Talant with Active/Inactive Application filter
|
||||
- Improve PTO table statistics
|
||||
|
||||
## 0.6.30
|
||||
|
||||
|
@ -14,8 +14,8 @@
|
||||
//
|
||||
|
||||
import { Employee } from '@anticrm/contact'
|
||||
import { Arr, Class, Domain, DOMAIN_MODEL, IndexKind, Markup, Ref, Timestamp } from '@anticrm/core'
|
||||
import { Department, DepartmentMember, hrId, Request, RequestType, Staff } from '@anticrm/hr'
|
||||
import { Arr, Class, Domain, DOMAIN_MODEL, IndexKind, Markup, Ref, Type } from '@anticrm/core'
|
||||
import { Department, DepartmentMember, hrId, Request, RequestType, Staff, TzDate } from '@anticrm/hr'
|
||||
import {
|
||||
ArrOf,
|
||||
Builder,
|
||||
@ -25,7 +25,6 @@ import {
|
||||
Mixin,
|
||||
Model,
|
||||
Prop,
|
||||
TypeDate,
|
||||
TypeIntlString,
|
||||
TypeMarkup,
|
||||
TypeRef,
|
||||
@ -36,8 +35,8 @@ import attachment from '@anticrm/model-attachment'
|
||||
import calendar from '@anticrm/model-calendar'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import contact, { TEmployee, TEmployeeAccount } from '@anticrm/model-contact'
|
||||
import core, { TAttachedDoc, TDoc, TSpace } from '@anticrm/model-core'
|
||||
import view, { createAction } from '@anticrm/model-view'
|
||||
import core, { TAttachedDoc, TDoc, TSpace, TType } from '@anticrm/model-core'
|
||||
import view, { classPresenter, createAction } from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import { Asset, IntlString } from '@anticrm/platform'
|
||||
import hr from './plugin'
|
||||
@ -95,6 +94,22 @@ export class TRequestType extends TDoc implements RequestType {
|
||||
color!: number
|
||||
}
|
||||
|
||||
@Model(hr.class.TzDate, core.class.Type)
|
||||
@UX(core.string.Timestamp)
|
||||
export class TTzDate extends TType {
|
||||
year!: number
|
||||
month!: number
|
||||
day!: number
|
||||
offset!: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function TypeTzDate (): Type<TzDate> {
|
||||
return { _class: hr.class.TzDate, label: core.string.Timestamp }
|
||||
}
|
||||
|
||||
@Model(hr.class.Request, core.class.AttachedDoc, DOMAIN_HR)
|
||||
@UX(hr.string.Request, hr.icon.PTO)
|
||||
export class TRequest extends TAttachedDoc implements Request {
|
||||
@ -123,18 +138,15 @@ export class TRequest extends TAttachedDoc implements Request {
|
||||
@Index(IndexKind.FullText)
|
||||
description!: Markup
|
||||
|
||||
@Prop(TypeDate(false), calendar.string.Date)
|
||||
date!: Timestamp
|
||||
@Prop(TypeTzDate(), calendar.string.Date)
|
||||
tzDate!: TzDate
|
||||
|
||||
@Prop(TypeDate(false), calendar.string.DueTo)
|
||||
dueDate!: Timestamp
|
||||
|
||||
// @Prop(TypeNumber(), calendar.string.Date)
|
||||
timezoneOffset!: number
|
||||
@Prop(TypeTzDate(), calendar.string.DueTo)
|
||||
tzDueDate!: TzDate
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TDepartment, TDepartmentMember, TRequest, TRequestType, TStaff)
|
||||
builder.createModel(TDepartment, TDepartmentMember, TRequest, TRequestType, TStaff, TTzDate)
|
||||
|
||||
builder.createDoc(
|
||||
workbench.class.Application,
|
||||
@ -183,6 +195,8 @@ export function createModel (builder: Builder): void {
|
||||
editor: hr.component.DepartmentStaff
|
||||
})
|
||||
|
||||
classPresenter(builder, hr.class.TzDate, hr.component.TzDatePresenter, hr.component.TzDateEditor)
|
||||
|
||||
builder.createDoc(
|
||||
hr.class.RequestType,
|
||||
core.space.Model,
|
||||
@ -337,6 +351,18 @@ export function createModel (builder: Builder): void {
|
||||
hr.viewlet.TableMember
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Viewlet,
|
||||
core.space.Model,
|
||||
{
|
||||
attachTo: hr.mixin.Staff,
|
||||
descriptor: view.viewlet.Table,
|
||||
config: [''],
|
||||
hiddenKeys: []
|
||||
},
|
||||
hr.viewlet.StaffStats
|
||||
)
|
||||
|
||||
createAction(builder, {
|
||||
action: view.actionImpl.ValueSelector,
|
||||
actionPopup: view.component.ValueSelector,
|
||||
@ -359,6 +385,10 @@ export function createModel (builder: Builder): void {
|
||||
group: 'associate'
|
||||
}
|
||||
})
|
||||
|
||||
builder.mixin(hr.class.Request, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: hr.component.RequestPresenter
|
||||
})
|
||||
}
|
||||
|
||||
export { hrOperation } from './migration'
|
||||
|
@ -13,8 +13,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { DOMAIN_TX, SortingOrder, TxCreateDoc, TxOperations, TxUpdateDoc } from '@anticrm/core'
|
||||
import { Request } from '@anticrm/hr'
|
||||
import { Employee } from '@anticrm/contact'
|
||||
import { DOMAIN_TX, TxCollectionCUD, TxCreateDoc, TxOperations, TxUpdateDoc } from '@anticrm/core'
|
||||
import { Request, TzDate } from '@anticrm/hr'
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
|
||||
import core from '@anticrm/model-core'
|
||||
import hr, { DOMAIN_HR } from './index'
|
||||
@ -40,102 +41,72 @@ async function createSpace (tx: TxOperations): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function toUTC (date: Date | number): number {
|
||||
function toTzDate (date: number): TzDate {
|
||||
const res = new Date(date)
|
||||
if (res.getUTCFullYear() !== res.getFullYear()) {
|
||||
res.setUTCFullYear(res.getFullYear())
|
||||
return {
|
||||
year: res.getFullYear(),
|
||||
month: res.getMonth(),
|
||||
day: res.getDate(),
|
||||
offset: res.getTimezoneOffset()
|
||||
}
|
||||
if (res.getUTCMonth() !== res.getMonth()) {
|
||||
res.setUTCMonth(res.getMonth())
|
||||
}
|
||||
if (res.getUTCDate() !== res.getDate()) {
|
||||
res.setUTCDate(res.getDate())
|
||||
}
|
||||
return res.setUTCHours(12, 0, 0, 0)
|
||||
}
|
||||
|
||||
function isDefault (date: number, due: number): boolean {
|
||||
const start = new Date(date)
|
||||
const end = new Date(due)
|
||||
if (start.getDate() === end.getDate() && end.getHours() - start.getHours() === 12) {
|
||||
return true
|
||||
}
|
||||
if (start.getDate() + 1 === end.getDate() && end.getHours() === start.getHours()) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
async function migrateRequestTime (client: MigrationClient, request: Request): Promise<void> {
|
||||
const date = toUTC(request.date)
|
||||
const dueDate = isDefault(request.date, request.dueDate) ? date : toUTC(request.dueDate)
|
||||
const date = toTzDate((request as any).date as unknown as number)
|
||||
const dueDate = toTzDate((request as any).dueDate as unknown as number)
|
||||
await client.update(
|
||||
DOMAIN_HR,
|
||||
{ _id: request._id },
|
||||
{
|
||||
date,
|
||||
dueDate
|
||||
tzDate: date,
|
||||
tzDueDate: dueDate
|
||||
}
|
||||
)
|
||||
|
||||
const updateDateTx = (
|
||||
await client.find<TxUpdateDoc<Request>>(
|
||||
DOMAIN_TX,
|
||||
{ _class: core.class.TxUpdateDoc, objectId: request._id, 'operations.date': { $exists: true } },
|
||||
{ sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
)[0]
|
||||
if (updateDateTx !== undefined) {
|
||||
const operations = updateDateTx.operations
|
||||
operations.dueDate = date
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ _id: updateDateTx._id },
|
||||
{
|
||||
operations
|
||||
}
|
||||
)
|
||||
}
|
||||
const updateDueTx = (
|
||||
await client.find<TxUpdateDoc<Request>>(
|
||||
DOMAIN_TX,
|
||||
{ _class: core.class.TxUpdateDoc, objectId: request._id, 'operations.dueDate': { $exists: true } },
|
||||
{ sort: { modifiedOn: SortingOrder.Descending } }
|
||||
)
|
||||
)[0]
|
||||
if (updateDueTx !== undefined) {
|
||||
const operations = updateDueTx.operations
|
||||
operations.dueDate = dueDate
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ _id: updateDateTx._id },
|
||||
{
|
||||
operations
|
||||
}
|
||||
)
|
||||
}
|
||||
const txes = await client.find<TxCollectionCUD<Employee, Request>>(DOMAIN_TX, {
|
||||
'tx._class': { $in: [core.class.TxCreateDoc, core.class.TxUpdateDoc] },
|
||||
'tx.objectId': request._id
|
||||
})
|
||||
|
||||
if (updateDueTx === undefined || updateDateTx === undefined) {
|
||||
const createTx = (
|
||||
await client.find<TxCreateDoc<Request>>(
|
||||
for (const utx of txes) {
|
||||
if (utx.tx._class === core.class.TxCreateDoc) {
|
||||
const ctx = utx.tx as TxCreateDoc<Request>
|
||||
const { date, dueDate, ...attributes } = ctx.attributes as any
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ _class: core.class.TxCreateDoc, objectId: request._id },
|
||||
{ sort: { modifiedOn: SortingOrder.Descending } }
|
||||
{ _id: utx._id },
|
||||
{
|
||||
tx: {
|
||||
...ctx,
|
||||
attributes: {
|
||||
...attributes,
|
||||
tzDate: toTzDate(date as unknown as number),
|
||||
tzDueDate: toTzDate((dueDate ?? date) as unknown as number)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)[0]
|
||||
if (createTx !== undefined) {
|
||||
const attributes = createTx.attributes
|
||||
if (updateDateTx === undefined) {
|
||||
attributes.date = date
|
||||
}
|
||||
if (utx.tx._class === core.class.TxUpdateDoc) {
|
||||
const ctx = utx.tx as TxUpdateDoc<Request>
|
||||
const { date, dueDate, ...operations } = ctx.operations as any
|
||||
const ops: any = {
|
||||
...operations
|
||||
}
|
||||
if (updateDueTx === undefined) {
|
||||
attributes.dueDate = dueDate
|
||||
if (date !== undefined) {
|
||||
ops.tzDate = toTzDate(date as unknown as number)
|
||||
}
|
||||
if (dueDate !== undefined) {
|
||||
ops.tzDueDate = toTzDate(dueDate as unknown as number)
|
||||
}
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ _id: createTx._id },
|
||||
{ _id: utx._id },
|
||||
{
|
||||
attributes
|
||||
tx: {
|
||||
...ctx,
|
||||
operations: ops
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -143,7 +114,34 @@ async function migrateRequestTime (client: MigrationClient, request: Request): P
|
||||
}
|
||||
|
||||
async function migrateTime (client: MigrationClient): Promise<void> {
|
||||
const requests = await client.find<Request>(DOMAIN_HR, { _class: hr.class.Request })
|
||||
const createTxes = await client.find<TxCreateDoc<Request>>(DOMAIN_TX, {
|
||||
_class: core.class.TxCreateDoc,
|
||||
objectClass: hr.class.Request
|
||||
})
|
||||
for (const tx of createTxes) {
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ _id: tx._id },
|
||||
{
|
||||
_class: core.class.TxCollectionCUD,
|
||||
tx: tx,
|
||||
collection: tx.attributes.collection,
|
||||
objectId: tx.attributes.attachedTo,
|
||||
objectClass: tx.attributes.attachedToClass
|
||||
}
|
||||
)
|
||||
await client.update(
|
||||
DOMAIN_TX,
|
||||
{ _id: tx._id },
|
||||
{
|
||||
$unset: {
|
||||
attributes: ''
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const requests = await client.find<Request>(DOMAIN_HR, { _class: hr.class.Request, tzDate: { $exists: false } })
|
||||
for (const request of requests) {
|
||||
await migrateRequestTime(client, request)
|
||||
}
|
||||
|
@ -39,7 +39,10 @@ export default mergeIds(hrId, hr, {
|
||||
DepartmentStaff: '' as AnyComponent,
|
||||
DepartmentEditor: '' as AnyComponent,
|
||||
Schedule: '' as AnyComponent,
|
||||
EditRequest: '' as AnyComponent
|
||||
EditRequest: '' as AnyComponent,
|
||||
TzDatePresenter: '' as AnyComponent,
|
||||
TzDateEditor: '' as AnyComponent,
|
||||
RequestPresenter: '' as AnyComponent
|
||||
},
|
||||
category: {
|
||||
HR: '' as Ref<ActionCategory>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
ArrayAsElementPosition,
|
||||
Client,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
@ -6,16 +7,25 @@ import {
|
||||
FindOptions,
|
||||
IncOptions,
|
||||
ObjQueryType,
|
||||
OmitNever,
|
||||
PushOptions,
|
||||
Ref
|
||||
} from '@anticrm/core'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface UnsetOptions<T extends object> {
|
||||
$unset?: Partial<OmitNever<ArrayAsElementPosition<Required<T>>>>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type MigrateUpdate<T extends Doc> = Partial<T> &
|
||||
Omit<PushOptions<T>, '$move'> &
|
||||
IncOptions<T> & {
|
||||
IncOptions<T> &
|
||||
UnsetOptions<T> & {
|
||||
// For any other mongo stuff
|
||||
[key: string]: any
|
||||
}
|
||||
|
@ -23,13 +23,16 @@
|
||||
export let is: AnyComponent
|
||||
export let props = {}
|
||||
export let shrink: boolean = false
|
||||
export let showLoading = true
|
||||
|
||||
$: component = is != null ? getResource(is) : Promise.reject(new Error('is not defined'))
|
||||
</script>
|
||||
|
||||
{#if is}
|
||||
{#await component}
|
||||
<Loading {shrink} />
|
||||
{#if showLoading}
|
||||
<Loading {shrink} />
|
||||
{/if}
|
||||
{:then Ctor}
|
||||
<ErrorBoundary>
|
||||
<Ctor {...props} on:change on:close on:open on:click on:delete>
|
||||
|
@ -22,7 +22,7 @@
|
||||
import ui, { Button, DateRangePresenter, DropdownLabelsIntl, IconAttachment } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import hr from '../plugin'
|
||||
import { toUTC } from '../utils'
|
||||
import { toTzDate } from '../utils'
|
||||
|
||||
export let staff: Staff
|
||||
export let date: Date
|
||||
@ -59,15 +59,11 @@
|
||||
if (value != null) date = value
|
||||
if (date === undefined) return
|
||||
if (type === undefined) return
|
||||
await client.createDoc(hr.class.Request, staff.department, {
|
||||
attachedTo: staff._id,
|
||||
attachedToClass: staff._class,
|
||||
await client.addCollection(hr.class.Request, staff.department, staff._id, staff._class, 'requests', {
|
||||
type: type._id,
|
||||
date: toUTC(date),
|
||||
dueDate: toUTC(dueDate),
|
||||
description,
|
||||
collection: 'requests',
|
||||
timezoneOffset: new Date(date).getTimezoneOffset()
|
||||
tzDate: toTzDate(new Date(date)),
|
||||
tzDueDate: toTzDate(new Date(dueDate)),
|
||||
description
|
||||
})
|
||||
await descriptionBox.createAttachments()
|
||||
}
|
||||
|
44
plugins/hr-resources/src/components/RequestPresenter.svelte
Normal file
44
plugins/hr-resources/src/components/RequestPresenter.svelte
Normal file
@ -0,0 +1,44 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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 { Request } from '@anticrm/hr'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { DateRangePresenter, Label } from '@anticrm/ui'
|
||||
import { fromTzDate, tzDateEqual } from '../utils'
|
||||
|
||||
export let value: Request | null | undefined
|
||||
export let noShift: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: type = value?.type !== undefined ? client.getModel().getObject(value?.type) : undefined
|
||||
</script>
|
||||
|
||||
{#if type && value != null}
|
||||
<div class="flex-row-center gap-2">
|
||||
<div class="fs-title">
|
||||
<Label label={type.label} />
|
||||
</div>
|
||||
{#if value.tzDate && tzDateEqual(value.tzDate, value.tzDueDate)}
|
||||
<DateRangePresenter value={fromTzDate(value.tzDate)} {noShift} />
|
||||
{:else if value.tzDate}
|
||||
<DateRangePresenter value={fromTzDate(value.tzDate)} {noShift} />
|
||||
{#if value.tzDueDate}
|
||||
<DateRangePresenter value={fromTzDate(value.tzDueDate)} {noShift} />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
@ -14,21 +14,17 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref, SortingOrder } from '@anticrm/core'
|
||||
import hr, { Staff } from '@anticrm/hr'
|
||||
import hr, { Request } from '@anticrm/hr'
|
||||
import { Table } from '@anticrm/view-resources'
|
||||
|
||||
export let date: Date
|
||||
export let endDate: number
|
||||
export let employee: Ref<Staff>
|
||||
export let requests: Ref<Request>[]
|
||||
</script>
|
||||
|
||||
<Table
|
||||
_class={hr.class.Request}
|
||||
query={{
|
||||
attachedTo: employee,
|
||||
dueDate: { $gt: date.getTime() },
|
||||
date: { $lt: endDate }
|
||||
_id: { $in: requests }
|
||||
}}
|
||||
config={['$lookup.type.label', 'date', 'dueDate']}
|
||||
config={['$lookup.type.label', 'tzDate', 'tzDueDate']}
|
||||
options={{ sort: { date: SortingOrder.Ascending } }}
|
||||
/>
|
||||
|
@ -17,12 +17,10 @@
|
||||
import calendar from '@anticrm/calendar-resources/src/plugin'
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { Department } from '@anticrm/hr'
|
||||
import { getEmbeddedLabel } from '@anticrm/platform'
|
||||
import { createQuery, SpaceSelector } from '@anticrm/presentation'
|
||||
import { Button, Icon, IconBack, IconForward, Label } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import hr from '../plugin'
|
||||
import { tableToCSV } from '../utils'
|
||||
import ScheduleMonthView from './ScheduleView.svelte'
|
||||
|
||||
let department = hr.ids.Head
|
||||
@ -140,23 +138,6 @@
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#if display === 'stats'}
|
||||
<Button
|
||||
label={getEmbeddedLabel('Export')}
|
||||
on:click={() => {
|
||||
// Download it
|
||||
const filename = 'exportStaff' + new Date().toLocaleDateString() + '.csv'
|
||||
const link = document.createElement('a')
|
||||
link.style.display = 'none'
|
||||
link.setAttribute('target', '_blank')
|
||||
link.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(tableToCSV('exportableData')))
|
||||
link.setAttribute('download', filename)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<SpaceSelector _class={hr.class.Department} label={hr.string.Department} bind:space={department} />
|
||||
|
@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { CalendarMode } from '@anticrm/calendar-resources'
|
||||
import { Employee } from '@anticrm/contact'
|
||||
import { Ref, Timestamp } from '@anticrm/core'
|
||||
import { Ref } from '@anticrm/core'
|
||||
import type { Department, Request, RequestType, Staff } from '@anticrm/hr'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Label } from '@anticrm/ui'
|
||||
@ -33,11 +33,12 @@
|
||||
|
||||
$: startDate = new Date(
|
||||
new Date(mode === CalendarMode.Year ? new Date(currentDate).setMonth(1) : currentDate).setDate(1)
|
||||
).setHours(0, 0, 0, 0)
|
||||
$: endDate =
|
||||
)
|
||||
$: endDate = new Date(
|
||||
mode === CalendarMode.Year
|
||||
? new Date(startDate).setFullYear(new Date(startDate).getFullYear() + 1)
|
||||
: new Date(startDate).setMonth(new Date(startDate).getMonth() + 1)
|
||||
)
|
||||
$: departments = [department, ...getDescendants(department, descendants)]
|
||||
|
||||
const lq = createQuery()
|
||||
@ -76,12 +77,14 @@
|
||||
return res
|
||||
}
|
||||
|
||||
function update (departments: Ref<Department>[], startDate: Timestamp, endDate: Timestamp) {
|
||||
function update (departments: Ref<Department>[], startDate: Date, endDate: Date) {
|
||||
lq.query(
|
||||
hr.class.Request,
|
||||
{
|
||||
dueDate: { $gte: startDate },
|
||||
date: { $lt: endDate },
|
||||
'tzDueDate.year': { $gte: startDate.getFullYear() },
|
||||
'tzDueDate.month': { $gte: startDate.getMonth() },
|
||||
'tzDate.year': { $lte: endDate.getFullYear() },
|
||||
'tzDate.month': { $lte: endDate.getFullYear() },
|
||||
space: { $in: departments }
|
||||
},
|
||||
(res) => {
|
||||
@ -116,14 +119,13 @@
|
||||
<MonthView
|
||||
{departmentStaff}
|
||||
{employeeRequests}
|
||||
{startDate}
|
||||
{endDate}
|
||||
teamLead={getTeamLead(department)}
|
||||
{types}
|
||||
{startDate}
|
||||
teamLead={getTeamLead(department)}
|
||||
{currentDate}
|
||||
/>
|
||||
{:else if display === 'stats'}
|
||||
<MonthTableView {departmentStaff} {employeeRequests} {startDate} {endDate} {types} {currentDate} />
|
||||
<MonthTableView {departmentStaff} {employeeRequests} {types} {currentDate} />
|
||||
{/if}
|
||||
{/if}
|
||||
{:else}
|
||||
|
51
plugins/hr-resources/src/components/TzDateEditor.svelte
Normal file
51
plugins/hr-resources/src/components/TzDateEditor.svelte
Normal file
@ -0,0 +1,51 @@
|
||||
<!--
|
||||
// Copyright © 2022 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 { TzDate } from '@anticrm/hr'
|
||||
|
||||
// import { IntlString } from '@anticrm/platform'
|
||||
import { DateRangePresenter } from '@anticrm/ui'
|
||||
import { fromTzDate, toTzDate } from '../utils'
|
||||
|
||||
export let value: TzDate | null | undefined
|
||||
export let onChange: (value: TzDate | null | undefined) => void
|
||||
export let kind: 'no-border' | 'link' = 'no-border'
|
||||
export let noShift: boolean = false
|
||||
|
||||
$: _value = value != null ? fromTzDate(value) : null
|
||||
</script>
|
||||
|
||||
<DateRangePresenter
|
||||
value={_value}
|
||||
withTime={false}
|
||||
editable
|
||||
{kind}
|
||||
{noShift}
|
||||
on:change={(res) => {
|
||||
if (res.detail != null) {
|
||||
const dte = new Date(res.detail)
|
||||
const tzdte = {
|
||||
year: dte.getFullYear(),
|
||||
month: dte.getMonth(),
|
||||
day: dte.getDate(),
|
||||
offset: dte.getTimezoneOffset()
|
||||
}
|
||||
const tzd = toTzDate(new Date(_value ?? Date.now()))
|
||||
if (tzd.year !== tzdte.year || tzd.month !== tzdte.month || tzd.day !== tzdte.day) {
|
||||
onChange(tzdte)
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
26
plugins/hr-resources/src/components/TzDatePresenter.svelte
Normal file
26
plugins/hr-resources/src/components/TzDatePresenter.svelte
Normal file
@ -0,0 +1,26 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 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 { TzDate } from '@anticrm/hr'
|
||||
import { DateRangePresenter } from '@anticrm/ui'
|
||||
|
||||
export let value: TzDate | null | undefined
|
||||
export let noShift: boolean = false
|
||||
|
||||
$: _value = value != null ? new Date().setFullYear(value?.year ?? 0, value?.month, value?.day) : null
|
||||
</script>
|
||||
|
||||
<DateRangePresenter value={_value} {noShift} />
|
@ -13,20 +13,19 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { FindOptions, Ref } from '@anticrm/core'
|
||||
import { Doc, Ref } from '@anticrm/core'
|
||||
import type { Request, RequestType, Staff } from '@anticrm/hr'
|
||||
import { getEmbeddedLabel } from '@anticrm/platform'
|
||||
import { Label, Scroller } from '@anticrm/ui'
|
||||
import { Table } from '@anticrm/view-resources'
|
||||
import { createQuery, getClient } from '@anticrm/presentation'
|
||||
import { Button, Label, Loading, Scroller } from '@anticrm/ui'
|
||||
import view, { BuildModelKey, Viewlet, ViewletPreference } from '@anticrm/view'
|
||||
import { Table, ViewletSettingButton } from '@anticrm/view-resources'
|
||||
import hr from '../../plugin'
|
||||
import { getMonth, getTotal, weekDays } from '../../utils'
|
||||
import { fromTzDate, getMonth, getTotal, tableToCSV, weekDays } from '../../utils'
|
||||
import NumberPresenter from './StatPresenter.svelte'
|
||||
|
||||
export let currentDate: Date = new Date()
|
||||
|
||||
export let startDate: number
|
||||
export let endDate: number
|
||||
|
||||
export let departmentStaff: Staff[]
|
||||
export let types: Map<Ref<RequestType>, RequestType>
|
||||
|
||||
@ -35,91 +34,203 @@
|
||||
$: month = getMonth(currentDate, currentDate.getMonth())
|
||||
$: wDays = weekDays(month.getUTCFullYear(), month.getUTCMonth())
|
||||
|
||||
const options: FindOptions<Staff> = {
|
||||
lookup: {
|
||||
department: hr.class.Department
|
||||
}
|
||||
}
|
||||
|
||||
function getDateRange (req: Request): string {
|
||||
const st = new Date(req.date).getDate()
|
||||
let days = Math.abs((req.dueDate - req.date) / 1000 / 60 / 60 / 24)
|
||||
if (days === 0) {
|
||||
days = 1
|
||||
}
|
||||
const stDate = new Date(req.date)
|
||||
function getDateRange (request: Request): string {
|
||||
const st = new Date(fromTzDate(request.tzDate)).getDate()
|
||||
const days =
|
||||
Math.floor(Math.abs((1 + fromTzDate(request.tzDueDate) - fromTzDate(request.tzDate)) / 1000 / 60 / 60 / 24)) + 1
|
||||
const stDate = new Date(fromTzDate(request.tzDate))
|
||||
let ds = Array.from(Array(days).keys()).map((it) => st + it)
|
||||
const type = types.get(req.type)
|
||||
const type = types.get(request.type)
|
||||
if ((type?.value ?? -1) < 0) {
|
||||
ds = ds.filter((it) => ![0, 6].includes(new Date(stDate.setDate(it)).getDay()))
|
||||
}
|
||||
return ds.join(' ')
|
||||
}
|
||||
|
||||
$: typevals = Array.from(
|
||||
Array.from(types.values()).map((it) => ({
|
||||
key: '',
|
||||
label: it.label,
|
||||
presenter: NumberPresenter,
|
||||
props: {
|
||||
month: month ?? getMonth(currentDate, currentDate.getMonth()),
|
||||
employeeRequests,
|
||||
display: (req: Request[]) =>
|
||||
req
|
||||
.filter((r) => r.type === it._id)
|
||||
.map((it) => getDateRange(it))
|
||||
.join(' ')
|
||||
function getEndDate (date: Date): number {
|
||||
return new Date(date).setMonth(date.getMonth() + 1)
|
||||
}
|
||||
function getRequests (employee: Ref<Staff>, date: Date): Request[] {
|
||||
const requests = employeeRequests.get(employee)
|
||||
if (requests === undefined) return []
|
||||
const res: Request[] = []
|
||||
const time = date.getTime()
|
||||
const endTime = getEndDate(date)
|
||||
for (const request of requests) {
|
||||
if (fromTzDate(request.tzDate) <= endTime && fromTzDate(request.tzDueDate) > time) {
|
||||
res.push(request)
|
||||
}
|
||||
}))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
$: typevals = new Map<string, BuildModelKey>(
|
||||
Array.from(types.values()).map((it) => [
|
||||
it.label as string,
|
||||
{
|
||||
key: '',
|
||||
label: it.label,
|
||||
presenter: NumberPresenter,
|
||||
props: {
|
||||
month: month ?? getMonth(currentDate, currentDate.getMonth()),
|
||||
display: (req: Request[]) =>
|
||||
req
|
||||
.filter((r) => r.type === it._id)
|
||||
.map((it) => getDateRange(it))
|
||||
.join(' '),
|
||||
getRequests
|
||||
}
|
||||
}
|
||||
])
|
||||
)
|
||||
|
||||
$: config = [
|
||||
'',
|
||||
'$lookup.department',
|
||||
{
|
||||
key: '',
|
||||
label: getEmbeddedLabel('Working days'),
|
||||
presenter: NumberPresenter,
|
||||
props: {
|
||||
month: month ?? getMonth(currentDate, currentDate.getMonth()),
|
||||
employeeRequests,
|
||||
display: (req: Request[]) => wDays + getTotal(req, types)
|
||||
$: overrideConfig = new Map<string, BuildModelKey>([
|
||||
[
|
||||
'@wdCount',
|
||||
{
|
||||
key: '',
|
||||
label: getEmbeddedLabel('Working days'),
|
||||
presenter: NumberPresenter,
|
||||
props: {
|
||||
month: month ?? getMonth(currentDate, currentDate.getMonth()),
|
||||
display: (req: Request[]) => wDays + getTotal(req, types),
|
||||
getRequests
|
||||
},
|
||||
sortingKey: '@wdCount',
|
||||
sortingFunction: (a: Doc, b: Doc) =>
|
||||
getTotal(getRequests(b._id as Ref<Staff>, month), types) -
|
||||
getTotal(getRequests(a._id as Ref<Staff>, month), types)
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: getEmbeddedLabel('PTOs'),
|
||||
presenter: NumberPresenter,
|
||||
props: {
|
||||
month: month ?? getMonth(currentDate, currentDate.getMonth()),
|
||||
employeeRequests,
|
||||
display: (req: Request[]) => getTotal(req, types, (a) => (a < 0 ? Math.abs(a) : 0))
|
||||
],
|
||||
[
|
||||
'@ptoCount',
|
||||
{
|
||||
key: '',
|
||||
label: getEmbeddedLabel('PTOs'),
|
||||
presenter: NumberPresenter,
|
||||
props: {
|
||||
month: month ?? getMonth(currentDate, currentDate.getMonth()),
|
||||
display: (req: Request[]) => getTotal(req, types, (a) => (a < 0 ? Math.abs(a) : 0)),
|
||||
getRequests
|
||||
},
|
||||
sortingKey: '@ptoCount',
|
||||
sortingFunction: (a: Doc, b: Doc) =>
|
||||
getTotal(getRequests(b._id as Ref<Staff>, month), types, (a) => (a < 0 ? Math.abs(a) : 0)) -
|
||||
getTotal(getRequests(a._id as Ref<Staff>, month), types, (a) => (a < 0 ? Math.abs(a) : 0))
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '',
|
||||
label: getEmbeddedLabel('EXTRa'),
|
||||
presenter: NumberPresenter,
|
||||
props: {
|
||||
month: month ?? getMonth(currentDate, currentDate.getMonth()),
|
||||
employeeRequests,
|
||||
display: (req: Request[]) => getTotal(req, types, (a) => (a > 0 ? Math.abs(a) : 0))
|
||||
],
|
||||
[
|
||||
'@extraCount',
|
||||
{
|
||||
key: '',
|
||||
label: getEmbeddedLabel('EXTRa'),
|
||||
presenter: NumberPresenter,
|
||||
props: {
|
||||
month: month ?? getMonth(currentDate, currentDate.getMonth()),
|
||||
display: (req: Request[]) => getTotal(req, types, (a) => (a > 0 ? Math.abs(a) : 0)),
|
||||
getRequests
|
||||
},
|
||||
sortingKey: '@extraCount',
|
||||
sortingFunction: (a: Doc, b: Doc) =>
|
||||
getTotal(getRequests(b._id as Ref<Staff>, month), types, (a) => (a > 0 ? Math.abs(a) : 0)) -
|
||||
getTotal(getRequests(a._id as Ref<Staff>, month), types, (a) => (a > 0 ? Math.abs(a) : 0))
|
||||
}
|
||||
},
|
||||
...(typevals ?? [])
|
||||
]
|
||||
],
|
||||
...typevals
|
||||
])
|
||||
|
||||
const preferenceQuery = createQuery()
|
||||
let preference: ViewletPreference | undefined
|
||||
let descr: Viewlet | undefined
|
||||
|
||||
$: updateDescriptor(hr.viewlet.StaffStats)
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let loading = false
|
||||
|
||||
function updateDescriptor (id: Ref<Viewlet>) {
|
||||
loading = true
|
||||
client
|
||||
.findOne<Viewlet>(view.class.Viewlet, {
|
||||
_id: id
|
||||
})
|
||||
.then((res) => {
|
||||
descr = res
|
||||
if (res !== undefined) {
|
||||
preferenceQuery.query(
|
||||
view.class.ViewletPreference,
|
||||
{
|
||||
attachedTo: res._id
|
||||
},
|
||||
(res) => {
|
||||
preference = res[0]
|
||||
loading = false
|
||||
},
|
||||
{ limit: 1 }
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createConfig (descr: Viewlet, preference: ViewletPreference | undefined): (string | BuildModelKey)[] {
|
||||
const base = preference?.config ?? descr.config
|
||||
const result: (string | BuildModelKey)[] = []
|
||||
|
||||
for (const c of overrideConfig.values()) {
|
||||
base.push(c)
|
||||
}
|
||||
for (const key of base) {
|
||||
if (typeof key === 'string') {
|
||||
result.push(overrideConfig.get(key) ?? key)
|
||||
} else {
|
||||
result.push(overrideConfig.get(key.key) ?? key)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if departmentStaff.length}
|
||||
<Scroller tableFade>
|
||||
<div class="p-2">
|
||||
<Table
|
||||
tableId={'exportableData'}
|
||||
_class={hr.mixin.Staff}
|
||||
query={{ _id: { $in: departmentStaff.map((it) => it._id) } }}
|
||||
{config}
|
||||
{options}
|
||||
/>
|
||||
{#if descr}
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<div class="flex-row-center flex-reverse">
|
||||
<div class="ml-1">
|
||||
<ViewletSettingButton viewlet={descr} />
|
||||
</div>
|
||||
<Button
|
||||
label={getEmbeddedLabel('Export')}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
// Download it
|
||||
const filename = 'exportStaff' + new Date().toLocaleDateString() + '.csv'
|
||||
const link = document.createElement('a')
|
||||
link.style.display = 'none'
|
||||
link.setAttribute('target', '_blank')
|
||||
link.setAttribute(
|
||||
'href',
|
||||
'data:text/csv;charset=utf-8,' + encodeURIComponent(tableToCSV('exportableData'))
|
||||
)
|
||||
link.setAttribute('download', filename)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Table
|
||||
tableId={'exportableData'}
|
||||
_class={hr.mixin.Staff}
|
||||
query={{ _id: { $in: departmentStaff.map((it) => it._id) } }}
|
||||
config={createConfig(descr, preference)}
|
||||
options={descr.options}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</Scroller>
|
||||
{:else}
|
||||
|
@ -32,20 +32,20 @@
|
||||
tooltip
|
||||
} from '@anticrm/ui'
|
||||
import hr from '../../plugin'
|
||||
import { fromTzDate, getTotal } from '../../utils'
|
||||
import CreateRequest from '../CreateRequest.svelte'
|
||||
import RequestsPopup from '../RequestsPopup.svelte'
|
||||
import ScheduleRequests from '../ScheduleRequests.svelte'
|
||||
|
||||
export let currentDate: Date = new Date()
|
||||
|
||||
export let startDate: number
|
||||
export let endDate: number
|
||||
export let startDate: Date
|
||||
|
||||
export let departmentStaff: Staff[]
|
||||
export let types: Map<Ref<RequestType>, RequestType>
|
||||
|
||||
export let employeeRequests: Map<Ref<Staff>, Request[]>
|
||||
export let teamLead: Ref<Employee> | undefined
|
||||
export let types: Map<Ref<RequestType>, RequestType>
|
||||
|
||||
const todayDate = new Date()
|
||||
|
||||
@ -56,7 +56,7 @@
|
||||
const time = date.getTime()
|
||||
const endTime = getEndDate(date)
|
||||
for (const request of requests) {
|
||||
if (request.date <= endTime && request.dueDate > time) {
|
||||
if (fromTzDate(request.tzDate) <= endTime && fromTzDate(request.tzDueDate) > time) {
|
||||
res.push(request)
|
||||
}
|
||||
}
|
||||
@ -85,15 +85,14 @@
|
||||
}
|
||||
|
||||
function getEndDate (date: Date): number {
|
||||
return new Date(date).setDate(date.getDate() + 1) - 1
|
||||
return new Date(date).setDate(date.getDate() + 1)
|
||||
}
|
||||
|
||||
function getTooltip (requests: Request[], employee: Staff, date: Date): LabelAndProps | undefined {
|
||||
function getTooltip (requests: Request[]): LabelAndProps | undefined {
|
||||
if (requests.length === 0) return
|
||||
const endDate = getEndDate(date)
|
||||
return {
|
||||
component: RequestsPopup,
|
||||
props: { date, endDate, employee: employee._id }
|
||||
props: { requests: requests.map((it) => it._id) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,8 +109,9 @@
|
||||
<th>
|
||||
<Label label={contact.string.Employee} />
|
||||
</th>
|
||||
<th>#</th>
|
||||
{#each values as value, i}
|
||||
{@const day = getDay(new Date(startDate), value)}
|
||||
{@const day = getDay(startDate, value)}
|
||||
<th
|
||||
class:today={areDatesEqual(todayDate, day)}
|
||||
class:weekend={isWeekend(day)}
|
||||
@ -130,15 +130,19 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each departmentStaff as employee, row}
|
||||
{@const requests = employeeRequests.get(employee._id) ?? []}
|
||||
<tr>
|
||||
<td>
|
||||
<EmployeePresenter value={employee} />
|
||||
</td>
|
||||
<td class="flex-center p-1" class:firstLine={row === 0} class:lastLine={row === departmentStaff.length - 1}>
|
||||
{getTotal(requests, types)}
|
||||
</td>
|
||||
{#each values as value, i}
|
||||
{@const date = getDay(new Date(startDate), value)}
|
||||
{@const date = getDay(startDate, value)}
|
||||
{@const requests = getRequests(employee._id, date)}
|
||||
{@const editable = isEditable(employee)}
|
||||
{@const tooltipValue = getTooltip(requests, employee, date)}
|
||||
{@const tooltipValue = getTooltip(requests)}
|
||||
{#key [tooltipValue, editable]}
|
||||
<td
|
||||
class:today={areDatesEqual(todayDate, date)}
|
||||
|
@ -18,27 +18,9 @@
|
||||
import { Request, Staff } from '@anticrm/hr'
|
||||
|
||||
export let value: Staff
|
||||
export let employeeRequests: Map<Ref<Staff>, Request[]>
|
||||
export let display: (requests: Request[]) => number | string
|
||||
export let month: Date
|
||||
|
||||
function getEndDate (date: Date): number {
|
||||
return new Date(date).setMonth(date.getMonth() + 1)
|
||||
}
|
||||
|
||||
function getRequests (employee: Ref<Staff>, date: Date): Request[] {
|
||||
const requests = employeeRequests.get(employee)
|
||||
if (requests === undefined) return []
|
||||
const res: Request[] = []
|
||||
const time = date.getTime()
|
||||
const endTime = getEndDate(date)
|
||||
for (const request of requests) {
|
||||
if (request.date <= endTime && request.dueDate > time) {
|
||||
res.push(request)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
export let getRequests: (employee: Ref<Staff>, date: Date) => Request[]
|
||||
|
||||
$: reqs = getRequests(value._id, month)
|
||||
$: _value = display(reqs)
|
||||
|
@ -19,7 +19,7 @@
|
||||
import type { Request, RequestType, Staff } from '@anticrm/hr'
|
||||
import { Label, LabelAndProps, Scroller, tooltip } from '@anticrm/ui'
|
||||
import hr from '../../plugin'
|
||||
import { getMonth, getTotal, weekDays } from '../../utils'
|
||||
import { fromTzDate, getMonth, getTotal, weekDays } from '../../utils'
|
||||
import RequestsPopup from '../RequestsPopup.svelte'
|
||||
|
||||
export let currentDate: Date = new Date()
|
||||
@ -38,7 +38,7 @@
|
||||
const time = date.getTime()
|
||||
const endTime = getEndDate(date)
|
||||
for (const request of requests) {
|
||||
if (request.date <= endTime && request.dueDate > time) {
|
||||
if (fromTzDate(request.tzDate) <= endTime && fromTzDate(request.tzDueDate) > time) {
|
||||
res.push(request)
|
||||
}
|
||||
}
|
||||
@ -49,12 +49,11 @@
|
||||
return new Date(date).setMonth(date.getMonth() + 1)
|
||||
}
|
||||
|
||||
function getTooltip (requests: Request[], employee: Staff, date: Date): LabelAndProps | undefined {
|
||||
function getTooltip (requests: Request[]): LabelAndProps | undefined {
|
||||
if (requests.length === 0) return
|
||||
const endDate = getEndDate(date)
|
||||
return {
|
||||
component: RequestsPopup,
|
||||
props: { date, endDate, employee: employee._id }
|
||||
props: { requests: requests.map((it) => it._id) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +118,7 @@
|
||||
{#each values as value, i}
|
||||
{@const month = getMonth(currentDate, value)}
|
||||
{@const requests = getRequests(employeeRequests, employee._id, month)}
|
||||
{@const tooltipValue = getTooltip(requests, employee, month)}
|
||||
{@const tooltipValue = getTooltip(requests)}
|
||||
{#key tooltipValue}
|
||||
<td
|
||||
class:today={month.getFullYear() === todayDate.getFullYear() &&
|
||||
|
@ -20,6 +20,9 @@ import EditDepartment from './components/EditDepartment.svelte'
|
||||
import EditRequest from './components/EditRequest.svelte'
|
||||
import Schedule from './components/Schedule.svelte'
|
||||
import Structure from './components/Structure.svelte'
|
||||
import TzDatePresenter from './components/TzDatePresenter.svelte'
|
||||
import TzDateEditor from './components/TzDateEditor.svelte'
|
||||
import RequestPresenter from './components/RequestPresenter.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
@ -28,6 +31,9 @@ export default async (): Promise<Resources> => ({
|
||||
DepartmentStaff,
|
||||
DepartmentEditor,
|
||||
Schedule,
|
||||
EditRequest
|
||||
EditRequest,
|
||||
TzDatePresenter,
|
||||
TzDateEditor,
|
||||
RequestPresenter
|
||||
}
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Employee, formatName } from '@anticrm/contact'
|
||||
import { Ref, TxOperations } from '@anticrm/core'
|
||||
import { Department, Request, RequestType } from '@anticrm/hr'
|
||||
import { Department, Request, RequestType, TzDate } from '@anticrm/hr'
|
||||
import { MessageBox } from '@anticrm/presentation'
|
||||
import { showPopup } from '@anticrm/ui'
|
||||
import hr from './plugin'
|
||||
@ -56,18 +56,27 @@ export async function addMember (client: TxOperations, employee?: Employee, valu
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function toUTC (date: Date | number, hours = 12, mins = 0, sec = 0): number {
|
||||
const res = new Date(date)
|
||||
if (res.getUTCFullYear() !== res.getFullYear()) {
|
||||
res.setUTCFullYear(res.getFullYear())
|
||||
export function toTzDate (date: Date): TzDate {
|
||||
return {
|
||||
year: date.getFullYear(),
|
||||
month: date.getMonth(),
|
||||
day: date.getDate(),
|
||||
offset: date.getTimezoneOffset()
|
||||
}
|
||||
if (res.getUTCMonth() !== res.getMonth()) {
|
||||
res.setUTCMonth(res.getMonth())
|
||||
}
|
||||
if (res.getUTCDate() !== res.getDate()) {
|
||||
res.setUTCDate(res.getDate())
|
||||
}
|
||||
return res.setUTCHours(hours, mins, sec, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function fromTzDate (tzDate: TzDate): number {
|
||||
return new Date().setFullYear(tzDate?.year ?? 0, tzDate.month, tzDate.day)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function tzDateEqual (tzDate: TzDate, tzDate2: TzDate): boolean {
|
||||
return tzDate.year === tzDate2.year && tzDate.month === tzDate2.month && tzDate.day === tzDate2.day
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,11 +106,9 @@ export function getTotal (
|
||||
let total = 0
|
||||
for (const request of requests) {
|
||||
const type = types.get(request.type)
|
||||
let days = Math.abs((request.dueDate - request.date) / 1000 / 60 / 60 / 24)
|
||||
if (days === 0) {
|
||||
days = 1
|
||||
}
|
||||
const stDate = new Date(request.date)
|
||||
const days =
|
||||
Math.floor(Math.abs((1 + fromTzDate(request.tzDueDate) - fromTzDate(request.tzDate)) / 1000 / 60 / 60 / 24)) + 1
|
||||
const stDate = new Date(fromTzDate(request.tzDate))
|
||||
const stDateDate = stDate.getDate()
|
||||
let ds = Array.from(Array(days).keys()).map((it) => stDateDate + it)
|
||||
if ((type?.value ?? -1) < 0) {
|
||||
|
@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import type { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import type { Arr, AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import type { Arr, AttachedDoc, Class, Doc, Markup, Mixin, Ref, Space, Type } from '@anticrm/core'
|
||||
import type { Asset, IntlString, Plugin } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import { Viewlet } from '@anticrm/view'
|
||||
@ -54,6 +54,16 @@ export interface RequestType extends Doc {
|
||||
color: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface TzDate {
|
||||
year: number
|
||||
month: number
|
||||
day: number
|
||||
offset: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
@ -71,11 +81,8 @@ export interface Request extends AttachedDoc {
|
||||
attachments?: number
|
||||
|
||||
// Date always in UTC
|
||||
date: Timestamp
|
||||
dueDate: Timestamp
|
||||
|
||||
// Timezone offset in minutes.
|
||||
timezoneOffset: number
|
||||
tzDate: TzDate
|
||||
tzDueDate: TzDate
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,7 +101,8 @@ const hr = plugin(hrId, {
|
||||
Department: '' as Ref<Class<Department>>,
|
||||
DepartmentMember: '' as Ref<Class<DepartmentMember>>,
|
||||
Request: '' as Ref<Class<Request>>,
|
||||
RequestType: '' as Ref<Class<RequestType>>
|
||||
RequestType: '' as Ref<Class<RequestType>>,
|
||||
TzDate: '' as Ref<Class<Type<TzDate>>>
|
||||
},
|
||||
mixin: {
|
||||
Staff: '' as Ref<Mixin<Staff>>
|
||||
@ -121,7 +129,8 @@ const hr = plugin(hrId, {
|
||||
Overtime2: '' as Ref<RequestType>
|
||||
},
|
||||
viewlet: {
|
||||
TableMember: '' as Ref<Viewlet>
|
||||
TableMember: '' as Ref<Viewlet>,
|
||||
StaffStats: '' as Ref<Viewlet>
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref, SortingOrder } from '@anticrm/core'
|
||||
import { IntlString, translate } from '@anticrm/platform'
|
||||
import { getEmbeddedLabel, IntlString, translate } from '@anticrm/platform'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Project } from '@anticrm/tracker'
|
||||
import type { ButtonKind, ButtonSize } from '@anticrm/ui'
|
||||
@ -89,32 +89,14 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if onlyIcon}
|
||||
<Button
|
||||
{kind}
|
||||
{size}
|
||||
{shape}
|
||||
{width}
|
||||
{justify}
|
||||
icon={projectIcon}
|
||||
disabled={!isEditable}
|
||||
on:click={handleProjectEditorOpened}
|
||||
/>
|
||||
{:else}
|
||||
<Button
|
||||
{kind}
|
||||
{size}
|
||||
{shape}
|
||||
{width}
|
||||
{justify}
|
||||
icon={projectIcon}
|
||||
disabled={!isEditable}
|
||||
on:click={handleProjectEditorOpened}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
{#if projectText}
|
||||
<span class="overflow-label disabled">{projectText}</span>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
{kind}
|
||||
{size}
|
||||
{shape}
|
||||
{width}
|
||||
{justify}
|
||||
label={onlyIcon || projectText === undefined ? undefined : getEmbeddedLabel(projectText)}
|
||||
icon={projectIcon}
|
||||
disabled={!isEditable}
|
||||
on:click={handleProjectEditorOpened}
|
||||
/>
|
||||
|
@ -228,6 +228,7 @@
|
||||
</div>
|
||||
<Component
|
||||
is={notification.component.NotificationPresenter}
|
||||
showLoading={false}
|
||||
props={{ value: docObject, kind: 'table' }}
|
||||
/>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user