From 1289c369b537c27f6b520a2c0e3883acd1680e69 Mon Sep 17 00:00:00 2001 From: Chetan Khandla Date: Fri, 8 Mar 2024 20:11:34 +0530 Subject: [PATCH 1/2] [Feat] Mikro Orm Query Support (#7507) * feat: create mikro-orm and typeorm combined decorator * fix: type issue * feat: update relations in entities to support mikro-orm * feat: create mikro-orm and typeorm combined decorator * fix: type issue * feat: update relations in entities to support mikro-orm * fix: relations for mikro-orm * fix: one-to-one-relation for mikro-orm * fix: one-to-one and many-to-one relation in mikro-orm * fix: relation ref column and join column * feat: update column decorator * fix: many-to-one relations * feat: update joinColumn * fix:Map Mikro ORM Entity To Json * feat: create mikro-orm and typeorm combined decorator * fix: type issue * feat: update relations in entities to support mikro-orm * feat: create mikro-orm and typeorm combined decorator * fix: type issue * feat: update relations in entities to support mikro-orm * fix: relations for mikro-orm * fix: one-to-one-relation for mikro-orm * fix: one-to-one and many-to-one relation in mikro-orm * fix: relation ref column and join column * feat: update column decorator * fix: many-to-one relations * feat: update joinColumn * fix:Map Mikro ORM Entity To Json * fix: circular user and employee entity (create employee) * fix(deepscan): removed unnecessary import * fix: gauzy develop start error * feat: parse where condition in mikro-orm * chore(deps): updated yarn.lock * refactor: many to one decorator * fix: added new decortors for relations and columns * fix(deepscan): removed unused import * fix: updated column decorator * fix: refactor column decorator types * fix: one to one entity relations * fix: updated employee create handler * fix: refactor one to one decortor * feat: added pipeline core subscriber * fix: pipeline entity and shared utils * fix: updated pipeline filters * refactor: updated crud service * fix: warehouse many to many relations * fix: refactor relations decorators * Revert "fix: refactor relations decorators" This reverts commit 1f2d94d7aea21954f0d26346cf73a0ca3f0dce2a. * feat: added shared types * refactor: one to one and many to one decorators * refactor: one to many and many to many decorators * fix: missing await / async for find one method * fix: updated transform interceptor * fix: updated relations decorators * fix: role service used custom role repository * fix: missing where in count by crud method * wip: column/property indexing * feat: added mikro orm DB logging * fix: serialize and wrap mikro orm entity * fix: replace user module for permission guard * fix: role permission custom repository for typeorm * Update utils.ts * feat: create type-orm to mikro-orm query builder mapping * remove console log * feat: fix query builder class mapping for mikro-orm * chore(deps): bump @mikro-orm/core from 6.0.5 to 6.1.7 * fix: strict partial loading for specific fields * fix: added missing subscribers for mikro-orm * wip: event subscriber for mikro and type orm * fix: rename event subscriber name * fix: created generic entity event subscriber for MikroOrm and TypeOrm * fix: rename base entity event subscriber * fix: revert back defaults for local env * chore: logging was not working well * fix: tracer importing * fix: before insert and create combine subscriber method * fix: common mikro and typeorm before update event hook --------- Co-authored-by: RAHUL RATHORE <41804588+rahul-rocket@users.noreply.github.com> Co-authored-by: Ruslan K Co-authored-by: Rahul R --- .../src/app/@core/sentry-error.handler.ts | 2 +- .../at-least-one-field.validator.ts | 15 +- .../pages/pipelines/pipelines.component.html | 19 +- .../pages/pipelines/pipelines.component.ts | 8 +- packages/auth/src/social-auth.service.ts | 59 ++-- .../common-angular/src/utils/shared-utils.ts | 20 ++ packages/common/src/utils/shared-utils.ts | 22 +- packages/config/src/database-helpers.ts | 24 +- packages/config/src/database.ts | 23 +- .../accounting-template.entity.ts | 14 +- .../appointment-employees.entity.ts | 20 +- .../approval-policy/approval-policy.entity.ts | 10 +- .../availability-slots.entity.ts | 23 +- .../core/src/bootstrap/bootstrap.module.ts | 10 +- packages/core/src/bootstrap/index.ts | 8 +- packages/core/src/bootstrap/tracer.ts | 6 +- .../candidate-criterion-rating.entity.ts | 19 +- .../candidate-documents.entity.ts | 13 +- .../candidate-education.entity.ts | 19 +- .../candidate-experience.entity.ts | 15 +- .../candidate-feedbacks.entity.ts | 27 +- .../candidate-interview.entity.ts | 29 +- .../candidate-interviewers.entity.ts | 13 +- .../candidate-personal-qualities.entity.ts | 15 +- .../candidate-skill/candidate-skill.entity.ts | 16 +- .../candidate-source.entity.ts | 8 +- .../candidate-technologies.entity.ts | 15 +- .../core/src/candidate/candidate.entity.ts | 85 ++--- packages/core/src/contact/contact.entity.ts | 46 +-- packages/core/src/core/crud/crud.service.ts | 245 ++++++--------- .../core/crud/tenant-aware-crud.service.ts | 36 ++- .../decorators/entity/column-options.types.ts | 17 + .../decorators/entity/column.decorator.ts | 38 +++ .../core/decorators/entity/column.helper.ts | 28 ++ .../core/decorators/entity/index.decorator.ts | 20 ++ .../core/src/core/decorators/entity/index.ts | 2 + .../core/decorators/entity/relations/index.ts | 4 + .../relations/many-to-many.decorator.ts | 128 ++++++++ .../entity/relations/many-to-one.decorator.ts | 132 ++++++++ .../decorators/entity/relations/mikro-orm.ts | 16 +- .../entity/relations/one-to-many.decorator.ts | 119 +++++++ .../entity/relations/one-to-one.decorator.ts | 140 +++++++++ .../entity/relations/shared-types.ts | 20 ++ .../decorators/entity/relations/type-orm.ts | 16 +- .../core/src/core/entities/base.entity.ts | 10 +- packages/core/src/core/entities/internal.ts | 73 ++--- .../base-entity-event.subscriber.ts | 75 +++++ .../entity-event-subscriber.types.ts | 54 ++++ .../subscribers/entity-event.subsciber.ts | 156 ++++++++++ .../{subscribers.ts => subscribers/index.ts} | 6 +- .../src/core/entities/tenant-base.entity.ts | 28 +- .../tenant-organization-base.entity.ts | 35 +-- packages/core/src/core/index.ts | 1 + .../interceptors/transform.interceptor.ts | 50 +-- packages/core/src/core/orm-type.ts | 4 + .../core/orm/query-builder/iquery-builder.ts | 36 +++ .../query-builder/mikro-orm-query-builder.ts | 290 ++++++++++++++++++ .../query-builder/query-builder.factory.ts | 30 ++ .../query-builder/typeorm-query-builder.ts | 161 ++++++++++ packages/core/src/core/orm/utils.ts | 20 ++ packages/core/src/core/util/object-utils.ts | 10 + packages/core/src/core/utils.ts | 112 ++++++- packages/core/src/country/country.entity.ts | 8 +- packages/core/src/currency/currency.entity.ts | 8 +- .../src/custom-smtp/custom-smtp.entity.ts | 17 +- packages/core/src/deal/deal.entity.ts | 28 +- .../src/email-history/email-history.entity.ts | 21 +- .../src/email-reset/email-reset.entity.ts | 20 +- .../email-template/email-template.entity.ts | 12 +- .../employee-appointment.entity.ts | 36 +-- .../employee-award/employee-award.entity.ts | 13 +- ...yee-upwork-jobs-search-criterion.entity.ts | 24 +- ...eset-upwork-job-search-criterion.entity.ts | 20 +- .../employee-job-preset/job-preset.entity.ts | 20 +- .../job-search-category.entity.ts | 14 +- .../job-search-occupation.entity.ts | 14 +- .../employee-level/employee-level.entity.ts | 13 +- .../employee-phone/employee-phone.entity.ts | 13 +- .../employee-proposal-template.entity.ts | 15 +- .../employee-recurring-expense.entity.ts | 33 +- .../employee-setting.entity.ts | 19 +- .../handlers/employee.create.handler.ts | 28 +- packages/core/src/employee/employee.entity.ts | 183 ++++++----- .../core/src/employee/employee.service.ts | 2 +- .../equipment-sharing-policy.entity.ts | 11 +- .../equipment-sharing.entity.ts | 40 +-- .../core/src/equipment/equipment.entity.ts | 33 +- .../estimate-email/estimate-email.entity.ts | 11 +- .../core/src/event-types/event-type.entity.ts | 24 +- .../expense-category.entity.ts | 15 +- packages/core/src/expense/expense.entity.ts | 63 ++-- .../import-history/import-history.entity.ts | 13 +- .../import-record/import-record.entity.ts | 11 +- .../feature/feature-organization.entity.ts | 15 +- packages/core/src/feature/feature.entity.ts | 30 +- .../core/src/feature/feature.subscriber.ts | 19 +- .../goal-general-setting.entity.ts | 17 +- .../goal-kpi-template.entity.ts | 21 +- packages/core/src/goal-kpi/goal-kpi.entity.ts | 23 +- .../src/goal-template/goal-template.entity.ts | 12 +- .../goal-time-frame/goal-time-frame.entity.ts | 11 +- packages/core/src/goal/goal.entity.ts | 33 +- .../src/image-asset/image-asset.entity.ts | 30 +- packages/core/src/income/income.entity.ts | 32 +- .../integration-entity-setting-tied.entity.ts | 13 +- .../integration-entity-setting.entity.ts | 17 +- .../integration-map/integration-map.entity.ts | 15 +- .../integration-setting.entity.ts | 13 +- .../integration-tenant.entity.ts | 19 +- .../repository/github-repository.entity.ts | 29 +- .../issue/github-repository-issue.entity.ts | 13 +- .../integration/integration-type.entity.ts | 17 +- .../src/integration/integration.entity.ts | 39 ++- packages/core/src/invite/invite.entity.ts | 52 ++-- packages/core/src/invite/invite.service.ts | 80 ++--- .../invoice-estimate-history.entity.ts | 17 +- .../src/invoice-item/invoice-item.entity.ts | 41 +-- packages/core/src/invoice/invoice.entity.ts | 75 +++-- packages/core/src/invoice/invoice.service.ts | 2 +- .../keyresult-template.entity.ts | 25 +- .../keyresult-update.entity.ts | 17 +- .../core/src/keyresult/keyresult.entity.ts | 58 ++-- packages/core/src/language/language.entity.ts | 17 +- packages/core/src/merchant/merchant.entity.ts | 44 +-- .../organization-award.entity.ts | 8 +- .../organization-contact.entity.ts | 67 ++-- .../organization-contact.service.ts | 2 +- .../organization-department.entity.ts | 25 +- .../organization-document.entity.ts | 13 +- .../organization-employment-type.entity.ts | 25 +- .../organization-language.entity.ts | 17 +- .../organization-position.entity.ts | 13 +- .../organization-project.entity.ts | 99 +++--- .../organization-project.service.ts | 4 +- .../organization-recurring-expense.entity.ts | 30 +- .../organization-sprint.entity.ts | 34 +- .../organization-task-setting.entity.ts | 53 ++-- .../organization-team-employee.entity.ts | 25 +- .../organization-team-join-request.entity.ts | 29 +- .../organization-team-join-request.service.ts | 2 +- .../organization-team.entity.ts | 65 ++-- .../organization-team.service.ts | 4 +- .../organization-vendor.entity.ts | 21 +- .../src/organization/organization.entity.ts | 181 +++++------ .../password-reset/password-reset.entity.ts | 8 +- packages/core/src/payment/payment.entity.ts | 48 +-- packages/core/src/payment/payment.service.ts | 8 +- .../core/src/payment/payment.subscriber.ts | 13 +- .../pipeline-stage/pipeline-stage.entity.ts | 37 ++- .../core/src/pipeline/pipeline.controller.ts | 94 ++++-- packages/core/src/pipeline/pipeline.entity.ts | 55 ++-- .../core/src/pipeline/pipeline.service.ts | 42 ++- .../core/src/pipeline/pipeline.subscriber.ts | 61 ++++ .../product-category-translation.entity.ts | 15 +- .../product-category.entity.ts | 15 +- ...product-option-group-translation.entity.ts | 13 +- .../product-option-group.entity.ts | 15 +- .../product-option-translation.entity.ts | 15 +- .../product-option/product-option.entity.ts | 15 +- .../product-setting/product-setting.entity.ts | 26 +- .../product-type-translation.entity.ts | 15 +- .../src/product-type/product-type.entity.ts | 10 +- .../product-variant-price.entity.ts | 18 +- .../product-variant/product-variant.entity.ts | 44 ++- .../src/product/product-translation.entity.ts | 15 +- packages/core/src/product/product.entity.ts | 47 +-- packages/core/src/proposal/proposal.entity.ts | 30 +- .../src/reports/report-category.entity.ts | 11 +- .../src/reports/report-organization.entity.ts | 11 +- packages/core/src/reports/report.entity.ts | 27 +- packages/core/src/reports/report.service.ts | 4 +- .../request-approval-employee.entity.ts | 15 +- .../request-approval-team.entity.ts | 15 +- .../request-approval.entity.ts | 35 +-- .../src/role-permission/repository/index.ts | 2 + .../type-orm-role-permission.repository.ts | 10 +- .../role-permission/role-permission.entity.ts | 15 +- .../role-permission/role-permission.module.ts | 5 +- .../role-permission.service.ts | 8 +- packages/core/src/role/repository/index.ts | 2 + .../repository/type-orm-role.repository.ts | 10 +- packages/core/src/role/role.controller.ts | 2 +- packages/core/src/role/role.entity.ts | 11 +- packages/core/src/role/role.module.ts | 10 +- packages/core/src/role/role.service.ts | 17 +- .../pipes/column-numeric-transformer.pipe.ts | 31 +- packages/core/src/skills/skill.entity.ts | 23 +- packages/core/src/tags/tag.entity.ts | 73 ++--- packages/core/src/tags/tag.service.ts | 4 +- .../estimation/task-estimation.entity.ts | 15 +- .../src/tasks/issue-type/issue-type.entity.ts | 29 +- .../tasks/issue-type/issue-type.service.ts | 4 +- .../linked-issue/task-linked-issue.entity.ts | 15 +- .../src/tasks/priorities/priority.entity.ts | 25 +- .../related-issue-type.entity.ts | 25 +- .../repository/mikro-orm-task.repository.ts | 5 +- packages/core/src/tasks/sizes/size.entity.ts | 25 +- .../core/src/tasks/statuses/status.entity.ts | 29 +- .../task-status-priority-size.service.ts | 7 +- packages/core/src/tasks/task.entity.ts | 89 +++--- packages/core/src/tasks/task.service.ts | 14 +- .../core/src/tasks/versions/version.entity.ts | 25 +- .../tenant-setting/tenant-setting.entity.ts | 7 +- packages/core/src/tenant/tenant.entity.ts | 26 +- packages/core/src/tenant/tenant.subscriber.ts | 72 +++-- .../time-off-policy/time-off-policy.entity.ts | 15 +- .../time-off-request.entity.ts | 30 +- .../time-off-request.service.ts | 2 +- .../time-tracking/activity/activity.entity.ts | 48 +-- .../screenshot/screenshot.entity.ts | 27 +- .../screenshot/screenshot.service.ts | 2 +- .../time-tracking/time-log/time-log.entity.ts | 53 ++-- .../time-log/time-log.service.ts | 4 +- .../time-slot/time-slot-minute.entity.ts | 15 +- .../time-slot/time-slot.entity.ts | 33 +- .../timesheet/timesheet.entity.ts | 39 ++- .../user-organization.entity.ts | 13 +- packages/core/src/user/user.entity.ts | 108 +++---- packages/core/src/user/user.subscriber.ts | 37 ++- .../type-orm-warehouse.repository.ts | 10 +- .../warehouse-product-variant.entity.ts | 15 +- .../src/warehouse/warehouse-product.entity.ts | 18 +- .../src/warehouse/warehouse.controller.ts | 15 +- .../core/src/warehouse/warehouse.entity.ts | 51 +-- .../core/src/warehouse/warehouse.module.ts | 5 +- .../core/src/warehouse/warehouse.service.ts | 18 +- .../plugins/changelog/src/changelog.entity.ts | 17 +- .../src/lib/wakatime.entity.ts | 33 +- .../help-center-article.entity.ts | 21 +- .../help-center-author.entity.ts | 11 +- .../src/help-center/help-center.entity.ts | 29 +- .../src/entities/product-review.entity.ts | 8 +- 232 files changed, 4480 insertions(+), 2669 deletions(-) create mode 100644 packages/core/src/core/decorators/entity/column-options.types.ts create mode 100644 packages/core/src/core/decorators/entity/column.decorator.ts create mode 100644 packages/core/src/core/decorators/entity/column.helper.ts create mode 100644 packages/core/src/core/decorators/entity/index.decorator.ts create mode 100644 packages/core/src/core/decorators/entity/relations/index.ts create mode 100644 packages/core/src/core/decorators/entity/relations/many-to-many.decorator.ts create mode 100644 packages/core/src/core/decorators/entity/relations/many-to-one.decorator.ts create mode 100644 packages/core/src/core/decorators/entity/relations/one-to-many.decorator.ts create mode 100644 packages/core/src/core/decorators/entity/relations/one-to-one.decorator.ts create mode 100644 packages/core/src/core/decorators/entity/relations/shared-types.ts create mode 100644 packages/core/src/core/entities/subscribers/base-entity-event.subscriber.ts create mode 100644 packages/core/src/core/entities/subscribers/entity-event-subscriber.types.ts create mode 100644 packages/core/src/core/entities/subscribers/entity-event.subsciber.ts rename packages/core/src/core/entities/{subscribers.ts => subscribers/index.ts} (95%) create mode 100644 packages/core/src/core/orm-type.ts create mode 100644 packages/core/src/core/orm/query-builder/iquery-builder.ts create mode 100644 packages/core/src/core/orm/query-builder/mikro-orm-query-builder.ts create mode 100644 packages/core/src/core/orm/query-builder/query-builder.factory.ts create mode 100644 packages/core/src/core/orm/query-builder/typeorm-query-builder.ts create mode 100644 packages/core/src/core/orm/utils.ts create mode 100644 packages/core/src/core/util/object-utils.ts create mode 100644 packages/core/src/pipeline/pipeline.subscriber.ts create mode 100644 packages/core/src/role-permission/repository/index.ts create mode 100644 packages/core/src/role/repository/index.ts diff --git a/apps/gauzy/src/app/@core/sentry-error.handler.ts b/apps/gauzy/src/app/@core/sentry-error.handler.ts index e997dba6bea..4a1aa831b82 100644 --- a/apps/gauzy/src/app/@core/sentry-error.handler.ts +++ b/apps/gauzy/src/app/@core/sentry-error.handler.ts @@ -5,7 +5,7 @@ import { GAUZY_ENV } from './constants'; @Injectable() export class SentryErrorHandler implements ErrorHandler { - constructor(@Inject(GAUZY_ENV) private readonly environment: Environment) {} + constructor(@Inject(GAUZY_ENV) private readonly environment: Environment) { } handleError(error) { if (this.environment.SENTRY_DSN) { diff --git a/apps/gauzy/src/app/@core/validators/at-least-one-field.validator.ts b/apps/gauzy/src/app/@core/validators/at-least-one-field.validator.ts index 4508d081cea..895fa645eda 100644 --- a/apps/gauzy/src/app/@core/validators/at-least-one-field.validator.ts +++ b/apps/gauzy/src/app/@core/validators/at-least-one-field.validator.ts @@ -1,14 +1,27 @@ import { UntypedFormGroup } from '@angular/forms'; +import { isNotNullOrUndefined } from '@gauzy/common-angular'; +/** + * Validates that at least one field in the group has a valid, non-null, and non-undefined value. + * + * @param group - The form group to validate. + * @returns A validation error object if no valid value is found in the group, otherwise null. + */ export function AtLeastOneFieldValidator(group: UntypedFormGroup): { [key: string]: any } { let isAtLeastOne = false; + if (group && group.controls) { for (const control in group.controls) { - if (group.controls.hasOwnProperty(control) && group.controls[control].valid && group.controls[control].value) { + if ( + group.controls.hasOwnProperty(control) && + group.controls[control].valid && + isNotNullOrUndefined(group.controls[control].value) + ) { isAtLeastOne = true; break; } } } + return isAtLeastOne ? null : { requiredAtLeastOne: true }; } diff --git a/apps/gauzy/src/app/pages/pipelines/pipelines.component.html b/apps/gauzy/src/app/pages/pipelines/pipelines.component.html index 06daf2e4089..a9e3b26cdeb 100644 --- a/apps/gauzy/src/app/pages/pipelines/pipelines.component.html +++ b/apps/gauzy/src/app/pages/pipelines/pipelines.component.html @@ -102,24 +102,15 @@

- - {{ - 'PIPELINES_PAGE.ACTIVE' - | translate - }} + + {{ 'PIPELINES_PAGE.ACTIVE' | translate }} - - {{ - 'PIPELINES_PAGE.INACTIVE' - | translate - }} + + {{ 'PIPELINES_PAGE.INACTIVE' | translate }}
diff --git a/apps/gauzy/src/app/pages/pipelines/pipelines.component.ts b/apps/gauzy/src/app/pages/pipelines/pipelines.component.ts index 2ef41e4ea56..d0baabc46d2 100644 --- a/apps/gauzy/src/app/pages/pipelines/pipelines.component.ts +++ b/apps/gauzy/src/app/pages/pipelines/pipelines.component.ts @@ -13,7 +13,7 @@ import { TranslateService } from '@ngx-translate/core'; import { NbDialogService, NbTabComponent } from '@nebular/theme'; import { Subject, firstValueFrom, BehaviorSubject } from 'rxjs'; import { debounceTime, filter, tap } from 'rxjs/operators'; -import { distinctUntilChange, isNotEmpty } from '@gauzy/common-angular'; +import { distinctUntilChange, isNotEmpty, isNotNullOrUndefined } from '@gauzy/common-angular'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { PipelineFormComponent } from './pipeline-form/pipeline-form.component'; import { DeleteConfirmationComponent } from '../../@shared/user/forms'; @@ -616,13 +616,13 @@ export class PipelinesComponent extends PaginationFilterBaseComponent implements const { status, name, stages } = this.searchForm.getRawValue(); // Set filters based on the extracted values - if (status) { + if (isNotNullOrUndefined(status)) { this.setFilter({ field: 'isActive', search: status }, false); } - if (name) { + if (isNotNullOrUndefined(name)) { this.setFilter({ field: 'name', search: name }, false); } - if (stages) { + if (isNotNullOrUndefined(stages)) { this.setFilter({ field: 'stages', search: stages }, false); } diff --git a/packages/auth/src/social-auth.service.ts b/packages/auth/src/social-auth.service.ts index d9a3f754a55..eea54c61e0b 100644 --- a/packages/auth/src/social-auth.service.ts +++ b/packages/auth/src/social-auth.service.ts @@ -2,7 +2,16 @@ import { Injectable } from '@nestjs/common'; import { ConfigService, IEnvironment } from '@gauzy/config'; import * as bcrypt from 'bcrypt'; +/** + * Base class for social authentication. + */ export abstract class BaseSocialAuth { + /** + * Validate OAuth login email. + * + * @param args - Arguments for validating OAuth login email. + * @returns The result of the validation. + */ public abstract validateOAuthLoginEmail(args: []): any; } @@ -15,34 +24,42 @@ export class SocialAuthService extends BaseSocialAuth { constructor() { super(); this.configService = new ConfigService(); - this.saltRounds = this.configService.get( - 'USER_PASSWORD_BCRYPT_SALT_ROUNDS' - ) as number; - this.clientBaseUrl = this.configService.get( - 'clientBaseUrl' - ) as keyof IEnvironment; + this.saltRounds = this.configService.get('USER_PASSWORD_BCRYPT_SALT_ROUNDS') as number; + this.clientBaseUrl = this.configService.get('clientBaseUrl') as keyof IEnvironment; } public validateOAuthLoginEmail(args: []): any { } + /** + * Generate a hash for the provided password. + * + * @param password - The password to hash. + * @returns A promise that resolves to the hashed password. + */ public async getPasswordHash(password: string): Promise { - return bcrypt.hash(password, this.saltRounds); + try { + return await bcrypt.hash(password, this.saltRounds); + } catch (error) { + // Handle the error appropriately, e.g., log it or throw a custom error + console.error('Error in getPasswordHash:', error); + throw new Error('Failed to hash the password'); + } } - // Redirect frontend - public async routeRedirect( - success: boolean, - auth: { - jwt: string; - userId: string; - }, - res: any - ) { + /** + * Redirect the user based on the success status. + * + * @param success - Indicates whether the operation was successful. + * @param auth - Object containing JWT and userId. + * @param res - Express response object. + * @returns The redirect response. + */ + async routeRedirect(success: boolean, auth: { jwt: string; userId: string }, res: any) { const { userId, jwt } = auth; - if (success) { - return res.redirect(`${this.clientBaseUrl}/#/sign-in/success?jwt=${jwt}&userId=${userId}`); - } else { - return res.redirect(`${this.clientBaseUrl}/#/auth/register`); - } + + const redirectPath = success ? `#/sign-in/success?jwt=${jwt}&userId=${userId}` : `#/auth/register`; + const redirectUrl = `${this.clientBaseUrl}/${redirectPath}`; + + return res.redirect(redirectUrl); } } diff --git a/packages/common-angular/src/utils/shared-utils.ts b/packages/common-angular/src/utils/shared-utils.ts index 328a68cf833..9547fcc0524 100644 --- a/packages/common-angular/src/utils/shared-utils.ts +++ b/packages/common-angular/src/utils/shared-utils.ts @@ -4,6 +4,26 @@ import * as timezone from 'moment-timezone'; import { distinctUntilChanged } from 'rxjs/operators'; import slugify from 'slugify'; +/** + * Check string is null or undefined + * From https://github.com/typeorm/typeorm/issues/873#issuecomment-502294597 + * + * @param obj + * @returns + */ +export function isNullOrUndefined(value: T | null | undefined): value is null | undefined { + return value === undefined || value === null; +} + +/** + * Checks if a value is not null or undefined. + * @param value The value to be checked. + * @returns true if the value is not null or undefined, false otherwise. + */ +export function isNotNullOrUndefined(value: T | undefined | null): value is T { + return value !== undefined && value !== null; +} + // It will use for pass nested object or array in query params in get method. export function toParams(query) { let params: HttpParams = new HttpParams(); diff --git a/packages/common/src/utils/shared-utils.ts b/packages/common/src/utils/shared-utils.ts index fd2355e0f0a..e49156468a6 100644 --- a/packages/common/src/utils/shared-utils.ts +++ b/packages/common/src/utils/shared-utils.ts @@ -1,14 +1,5 @@ import slugify from 'slugify'; -/** - * Checks if a value is not null or undefined. - * @param value The value to be checked. - * @returns true if the value is not null or undefined, false otherwise. - */ -export function isNotNullOrUndefined(value: T | undefined | null): boolean { - return value !== undefined && value !== null; -} - /** * Check is function . * @param item @@ -134,8 +125,17 @@ export function removeDuplicates(data: string[]) { * @param obj * @returns */ -export function isNullOrUndefined(string: T | null | undefined): string is null | undefined { - return typeof string === 'undefined' || string === null; +export function isNullOrUndefined(value: T | null | undefined): value is null | undefined { + return value === undefined || value === null; +} + +/** + * Checks if a value is not null or undefined. + * @param value The value to be checked. + * @returns true if the value is not null or undefined, false otherwise. + */ +export function isNotNullOrUndefined(value: T | undefined | null): value is T { + return value !== undefined && value !== null; } /** diff --git a/packages/config/src/database-helpers.ts b/packages/config/src/database-helpers.ts index 216efbd3b53..5858bc9e24a 100644 --- a/packages/config/src/database-helpers.ts +++ b/packages/config/src/database-helpers.ts @@ -1,4 +1,6 @@ -import { TlsOptions } from "tls"; +import { TlsOptions } from 'tls'; + +export type MikroLoggerNamespace = 'query' | 'query-params' | 'schema' | 'discovery' | 'info'; export enum DatabaseTypeEnum { mongodb = 'mongodb', @@ -57,7 +59,6 @@ export const getTlsOptions = (dbSslMode: boolean): TlsOptions | undefined => { } }; - /** * Get logging options based on the provided dbLogging value. * @param {string} dbLogging - The value of process.env.DB_LOGGING @@ -80,3 +81,22 @@ export const getLoggingOptions = (dbLogging: string): false | 'all' | ['query', } return loggingOptions; }; + +/** + * Gets MikroORM logging options based on the specified logging type. + * + * @param dbLogging - The logging type. + * @returns False if logging is disabled, or an array of LoggerNamespace for the specified logging type. + */ +export const getLoggingMikroOptions = (dbLogging: string): false | MikroLoggerNamespace[] => { + const loggingOptionsMap: Record = { + query: ['query'], + 'query-params': ['query-params'], + schema: ['schema'], + discovery: ['discovery'], + info: ['info'], + all: ['query', 'query-params', 'schema', 'discovery', 'info'] + }; + + return loggingOptionsMap[dbLogging] || false; +}; diff --git a/packages/config/src/database.ts b/packages/config/src/database.ts index 9f27ba38c77..04634bad6b2 100644 --- a/packages/config/src/database.ts +++ b/packages/config/src/database.ts @@ -11,7 +11,7 @@ import { DataSourceOptions } from 'typeorm'; import { KnexModuleOptions } from 'nest-knexjs'; import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions'; import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'; -import { DatabaseTypeEnum, getLoggingOptions, getTlsOptions } from './database-helpers'; +import { DatabaseTypeEnum, getLoggingMikroOptions, getLoggingOptions, getTlsOptions } from './database-helpers'; /** * Type representing the ORM types. @@ -37,11 +37,11 @@ function getORMType(defaultValue: MultiORM = MultiORMEnum.TypeORM): MultiORM { } console.log(chalk.magenta(`NodeJs Version %s`), process.version); -console.log('Is DEMO: ', process.env.DEMO); -console.log('NODE_ENV: ', process.env.NODE_ENV); +console.log('Is DEMO: %s', process.env.DEMO); +console.log('NODE_ENV: %s', process.env.NODE_ENV); const dbORM: MultiORM = getORMType(); -console.log('DB ORM: ' + dbORM); +console.log('DB ORM: %s', dbORM); const dbType = process.env.DB_TYPE || DatabaseTypeEnum.betterSqlite3; @@ -100,7 +100,8 @@ switch (dbType) { min: 0, max: dbPoolSize }, - namingStrategy: EntityCaseNamingStrategy + namingStrategy: EntityCaseNamingStrategy, + debug: getLoggingMikroOptions(process.env.DB_LOGGING), // by default set to false only }; mikroOrmConnectionConfig = mikroOrmMySqlOptions; @@ -193,7 +194,8 @@ switch (dbType) { // connection timeout - number of milliseconds to wait before timing out when connecting a new client acquireTimeoutMillis: dbConnectionTimeout }, - namingStrategy: EntityCaseNamingStrategy + namingStrategy: EntityCaseNamingStrategy, + debug: getLoggingMikroOptions(process.env.DB_LOGGING), // by default set to false only }; mikroOrmConnectionConfig = mikroOrmPostgresOptions; @@ -275,7 +277,8 @@ switch (dbType) { const mikroOrmSqliteConfig: MikroOrmSqliteOptions = { driver: SqliteDriver, dbName: dbPath, - namingStrategy: EntityCaseNamingStrategy + namingStrategy: EntityCaseNamingStrategy, + debug: getLoggingMikroOptions(process.env.DB_LOGGING), // by default set to false only }; mikroOrmConnectionConfig = mikroOrmSqliteConfig; @@ -304,15 +307,15 @@ switch (dbType) { break; case DatabaseTypeEnum.betterSqlite3: - const betterSqlitePath = - process.env.DB_PATH || path.join(process.cwd(), ...['apps', 'api', 'data'], 'gauzy.sqlite3'); + const betterSqlitePath = process.env.DB_PATH || path.join(process.cwd(), ...['apps', 'api', 'data'], 'gauzy.sqlite3'); console.log('Better Sqlite DB Path: ' + betterSqlitePath); // MikroORM DB Config (Better-SQLite3) const mikroOrmBetterSqliteConfig: MikroOrmBetterSqliteOptions = { driver: BetterSqliteDriver, dbName: betterSqlitePath, - namingStrategy: EntityCaseNamingStrategy + namingStrategy: EntityCaseNamingStrategy, + debug: getLoggingMikroOptions(process.env.DB_LOGGING), // by default set to false only }; mikroOrmConnectionConfig = mikroOrmBetterSqliteConfig; diff --git a/packages/core/src/accounting-template/accounting-template.entity.ts b/packages/core/src/accounting-template/accounting-template.entity.ts index 4c8b322be85..dad9c238b3a 100644 --- a/packages/core/src/accounting-template/accounting-template.entity.ts +++ b/packages/core/src/accounting-template/accounting-template.entity.ts @@ -1,9 +1,9 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Column, Index } from 'typeorm'; +import { Index } from 'typeorm'; import { AccountingTemplateTypeEnum, IAccountingTemplate } from '@gauzy/contracts'; import { isMySQL } from '@gauzy/config'; import { TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmAccountingTemplateRepository } from './repository/mikro-orm-accounting-template.repository'; @MultiORMEntity('accounting_template', { mikroOrmRepository: () => MikroOrmAccountingTemplateRepository }) @@ -12,23 +12,23 @@ export class AccountingTemplate extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() name?: string; @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() languageCode?: string; @ApiProperty({ type: () => String }) - @Column({ type: 'text', nullable: true }) + @MultiORMColumn({ type: 'text', nullable: true }) mjml?: string; @ApiProperty({ type: () => String }) - @Column({ ...(isMySQL() ? { type: "longtext" } : {}) }) + @MultiORMColumn({ ...(isMySQL() ? { type: "longtext" } : {}) }) hbs?: string; @ApiProperty({ type: () => String, enum: AccountingTemplateTypeEnum }) - @Column() + @MultiORMColumn() templateType?: string; } diff --git a/packages/core/src/appointment-employees/appointment-employees.entity.ts b/packages/core/src/appointment-employees/appointment-employees.entity.ts index 10f140f9a3a..99e6d129d3b 100644 --- a/packages/core/src/appointment-employees/appointment-employees.entity.ts +++ b/packages/core/src/appointment-employees/appointment-employees.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Column, ManyToOne, RelationId, Index } from 'typeorm'; +import { RelationId, Index } from 'typeorm'; import { IAppointmentEmployee, IEmployee, IEmployeeAppointment } from '@gauzy/contracts'; import { IsString, IsNotEmpty } from 'class-validator'; import { @@ -7,8 +7,9 @@ import { EmployeeAppointment, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmAppointmentEmployeeRepository } from './repository/mikro-orm-appointment-employee.repository'; +import { MultiORMManyToOne } from './../core/decorators/entity/relations'; @MultiORMEntity('appointment_employee', { mikroOrmRepository: () => MikroOrmAppointmentEmployeeRepository }) export class AppointmentEmployee extends TenantOrganizationBaseEntity implements IAppointmentEmployee { @@ -16,20 +17,15 @@ export class AppointmentEmployee extends TenantOrganizationBaseEntity implements @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column() + @MultiORMColumn() public appointmentId!: string; - /* - |-------------------------------------------------------------------------- - | @ManyToOne - |-------------------------------------------------------------------------- - */ /** * Employee */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { + @MultiORMManyToOne(() => Employee, { onDelete: 'CASCADE' }) public employee?: IEmployee @@ -39,14 +35,14 @@ export class AppointmentEmployee extends TenantOrganizationBaseEntity implements @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn({ relationId: true }) public employeeId?: string; /** * EmployeeAppointment */ @ApiProperty({ type: () => EmployeeAppointment }) - @ManyToOne(() => EmployeeAppointment, (employeeAppointment) => employeeAppointment, { + @MultiORMManyToOne(() => EmployeeAppointment, (employeeAppointment) => employeeAppointment?.invitees, { onDelete: 'SET NULL' }) public employeeAppointment?: IEmployeeAppointment; @@ -55,6 +51,6 @@ export class AppointmentEmployee extends TenantOrganizationBaseEntity implements @RelationId((it: AppointmentEmployee) => it.employeeAppointment) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) public employeeAppointmentId?: string; } diff --git a/packages/core/src/approval-policy/approval-policy.entity.ts b/packages/core/src/approval-policy/approval-policy.entity.ts index d093c0b643a..fb3b3ed4e3f 100644 --- a/packages/core/src/approval-policy/approval-policy.entity.ts +++ b/packages/core/src/approval-policy/approval-policy.entity.ts @@ -3,11 +3,11 @@ * E.g. for example, "Business Trip", "Borrow Items", ... * Approval Policy table has the many to one relationship to the Organization table and Tenant by organizationId and tenantId */ -import { Index, Column } from 'typeorm'; +import { Index } from 'typeorm'; import { IApprovalPolicy } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmApprovalPolicyRepository } from './repository/mikro-orm-approval-policy.repository'; @MultiORMEntity('approval_policy', { mikroOrmRepository: () => MikroOrmApprovalPolicyRepository }) @@ -16,14 +16,14 @@ export class ApprovalPolicy extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) approvalType: string; } diff --git a/packages/core/src/availability-slots/availability-slots.entity.ts b/packages/core/src/availability-slots/availability-slots.entity.ts index 9be5acbd729..54ace93b455 100644 --- a/packages/core/src/availability-slots/availability-slots.entity.ts +++ b/packages/core/src/availability-slots/availability-slots.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, RelationId, JoinColumn, Index } from 'typeorm'; +import { RelationId, JoinColumn, Index } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, @@ -16,44 +16,39 @@ import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmAvailabilitySlotRepository } from './repository/mikro-orm-availability-slot.repository'; +import { MultiORMManyToOne } from './../core/decorators/entity/relations'; @MultiORMEntity('availability_slot', { mikroOrmRepository: () => MikroOrmAvailabilitySlotRepository }) export class AvailabilitySlot extends TenantOrganizationBaseEntity implements IAvailabilitySlot { @ApiProperty({ type: () => Date }) @IsDate() - @Column() + @MultiORMColumn() startTime: Date; @ApiProperty({ type: () => Date }) @IsDate() - @Column() + @MultiORMColumn() endTime: Date; @ApiProperty({ type: () => Boolean }) @IsBoolean() - @Column() + @MultiORMColumn() allDay: boolean; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column({ type: 'text', nullable: true }) + @MultiORMColumn({ type: 'text', nullable: true }) type: AvailabilitySlotType; - /* - |-------------------------------------------------------------------------- - | @ManyToOne - |-------------------------------------------------------------------------- - */ - /** * Employee */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { + @MultiORMManyToOne(() => Employee, { nullable: true, onDelete: 'CASCADE' }) @@ -65,6 +60,6 @@ export class AvailabilitySlot extends TenantOrganizationBaseEntity implements IA @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly employeeId?: string; } diff --git a/packages/core/src/bootstrap/bootstrap.module.ts b/packages/core/src/bootstrap/bootstrap.module.ts index 837d73a4e9f..de3d1d31775 100644 --- a/packages/core/src/bootstrap/bootstrap.module.ts +++ b/packages/core/src/bootstrap/bootstrap.module.ts @@ -1,5 +1,3 @@ -import tracer from './tracer'; - import { MiddlewareConsumer, Module, NestModule, OnApplicationShutdown } from '@nestjs/common'; import { ConfigModule } from '@gauzy/config'; import { PluginModule } from '@gauzy/plugin'; @@ -28,9 +26,13 @@ export class BootstrapModule implements NestModule, OnApplicationShutdown { if (signal) { Logger.log(`Received shutdown signal: ${signal}`); - if (process.env.OTEL_ENABLED === 'true' && tracer) { + if (process.env.OTEL_ENABLED === 'true') { try { - await tracer.shutdown(); + // Dynamically import the tracer module. We need it because otherwise tracer can initialize at different times etc + const { default: tracer } = await import('./tracer'); + if (tracer) { + await tracer.shutdown(); + } } catch (error) { console.error('Error terminating tracing', error); } diff --git a/packages/core/src/bootstrap/index.ts b/packages/core/src/bootstrap/index.ts index 58c67e9221d..ddd117befab 100644 --- a/packages/core/src/bootstrap/index.ts +++ b/packages/core/src/bootstrap/index.ts @@ -31,6 +31,7 @@ startTracing(); // Start tracing if OTEL is enabled. import { ConflictException, INestApplication, Type } from '@nestjs/common'; import { NestFactory, Reflector } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; +import { EventSubscriber } from '@mikro-orm/core'; import { useContainer } from 'class-validator'; import * as expressSession from 'express-session'; import RedisStore from 'connect-redis'; @@ -299,7 +300,7 @@ export async function registerPluginConfig(pluginConfig: Partial>, subscribers: subscribers as Array> }, dbMikroOrmConnectionOptions: { - entities: entities as Array> + entities: entities as Array>, + subscribers: subscribers as Array } }); diff --git a/packages/core/src/bootstrap/tracer.ts b/packages/core/src/bootstrap/tracer.ts index 812a1672035..4c774420633 100644 --- a/packages/core/src/bootstrap/tracer.ts +++ b/packages/core/src/bootstrap/tracer.ts @@ -312,10 +312,10 @@ export default { } } }, - shutdown: () => { + shutdown: async () => { if (process.env.OTEL_ENABLED === 'true') { - provider?.shutdown(); - honeycombSDK?.shutdown(); + await provider?.shutdown(); + await honeycombSDK?.shutdown(); } } }; diff --git a/packages/core/src/candidate-criterions-rating/candidate-criterion-rating.entity.ts b/packages/core/src/candidate-criterions-rating/candidate-criterion-rating.entity.ts index bad9996b52e..9237b3bd1df 100644 --- a/packages/core/src/candidate-criterions-rating/candidate-criterion-rating.entity.ts +++ b/packages/core/src/candidate-criterions-rating/candidate-criterion-rating.entity.ts @@ -1,4 +1,4 @@ -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { ICandidateCriterionsRating, @@ -12,15 +12,16 @@ import { CandidateTechnologies, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidateCriterionsRatingRepository } from './repository/mikro-orm-candidate-criterions-rating.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('candidate_criterion_rating', { mikroOrmRepository: () => MikroOrmCandidateCriterionsRatingRepository }) export class CandidateCriterionsRating extends TenantOrganizationBaseEntity implements ICandidateCriterionsRating { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() rating: number; /* @@ -33,7 +34,7 @@ export class CandidateCriterionsRating extends TenantOrganizationBaseEntity * Candidate Technologies */ @ApiProperty({ type: () => CandidateTechnologies }) - @ManyToOne(() => CandidateTechnologies, (quality) => quality.criterionsRatings, { + @MultiORMManyToOne(() => CandidateTechnologies, (quality) => quality.criterionsRatings, { onDelete: 'CASCADE' }) technology?: ICandidateTechnologies; @@ -41,14 +42,14 @@ export class CandidateCriterionsRating extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: CandidateCriterionsRating) => it.technology) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) technologyId?: string; /** * Candidate Personal Qualities */ @ApiProperty({ type: () => CandidatePersonalQualities }) - @ManyToOne(() => CandidatePersonalQualities, (quality) => quality.criterionsRatings, { + @MultiORMManyToOne(() => CandidatePersonalQualities, (quality) => quality.criterionsRatings, { onDelete: 'CASCADE' }) personalQuality?: ICandidatePersonalQualities; @@ -56,14 +57,14 @@ export class CandidateCriterionsRating extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: CandidateCriterionsRating) => it.personalQuality) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) personalQualityId?: string; /** * Candidate Feedback */ @ApiProperty({ type: () => CandidateFeedback }) - @ManyToOne(() => CandidateFeedback, (feedback) => feedback.criterionsRating, { + @MultiORMManyToOne(() => CandidateFeedback, (feedback) => feedback.criterionsRating, { onDelete: 'CASCADE' }) feedback?: ICandidateFeedback; @@ -71,6 +72,6 @@ export class CandidateCriterionsRating extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: CandidateCriterionsRating) => it.feedback) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) feedbackId?: string; } diff --git a/packages/core/src/candidate-documents/candidate-documents.entity.ts b/packages/core/src/candidate-documents/candidate-documents.entity.ts index 19800069af0..cefde17c331 100644 --- a/packages/core/src/candidate-documents/candidate-documents.entity.ts +++ b/packages/core/src/candidate-documents/candidate-documents.entity.ts @@ -1,22 +1,23 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { ICandidateDocument, ICandidate } from '@gauzy/contracts'; import { IsString } from 'class-validator'; import { Candidate, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidateDocumentRepository } from './repository/mikro-orm-candidate-document.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('candidate_document', { mikroOrmRepository: () => MikroOrmCandidateDocumentRepository }) export class CandidateDocument extends TenantOrganizationBaseEntity implements ICandidateDocument { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) documentUrl: string; /* @@ -25,7 +26,7 @@ export class CandidateDocument extends TenantOrganizationBaseEntity implements I |-------------------------------------------------------------------------- */ @ApiPropertyOptional({ type: () => Candidate }) - @ManyToOne(() => Candidate, (candidate) => candidate.documents, { + @MultiORMManyToOne(() => Candidate, (candidate) => candidate.documents, { onDelete: 'CASCADE' }) candidate?: ICandidate; @@ -34,6 +35,6 @@ export class CandidateDocument extends TenantOrganizationBaseEntity implements I @RelationId((it: CandidateDocument) => it.candidate) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) candidateId?: string; } diff --git a/packages/core/src/candidate-education/candidate-education.entity.ts b/packages/core/src/candidate-education/candidate-education.entity.ts index f8fc1dd42ae..f4223dc71c2 100644 --- a/packages/core/src/candidate-education/candidate-education.entity.ts +++ b/packages/core/src/candidate-education/candidate-education.entity.ts @@ -1,35 +1,36 @@ -import { Column, ManyToOne, RelationId } from 'typeorm'; +import { RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { ICandidateEducation, ICandidate } from '@gauzy/contracts'; import { Candidate, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidateEducationRepository } from './repository/mikro-orm-candidate-education.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('candidate_education', { mikroOrmRepository: () => MikroOrmCandidateEducationRepository }) export class CandidateEducation extends TenantOrganizationBaseEntity implements ICandidateEducation { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() schoolName: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() degree: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() field: string; @ApiProperty({ type: () => Date }) - @Column() + @MultiORMColumn() completionDate: Date; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) notes?: string; /* @@ -42,13 +43,13 @@ export class CandidateEducation extends TenantOrganizationBaseEntity * Candidate */ @ApiProperty({ type: () => Candidate }) - @ManyToOne(() => Candidate, (candidate) => candidate.educations, { + @MultiORMManyToOne(() => Candidate, (candidate) => candidate.educations, { onDelete: 'CASCADE' }) candidate?: ICandidate; @ApiProperty({ type: () => String }) @RelationId((it: CandidateEducation) => it.candidate) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) candidateId?: string; } diff --git a/packages/core/src/candidate-experience/candidate-experience.entity.ts b/packages/core/src/candidate-experience/candidate-experience.entity.ts index ecb0ce6d981..1d6aadfd2b9 100644 --- a/packages/core/src/candidate-experience/candidate-experience.entity.ts +++ b/packages/core/src/candidate-experience/candidate-experience.entity.ts @@ -1,27 +1,28 @@ -import { Column, ManyToOne, RelationId } from 'typeorm'; +import { RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { ICandidateExperience, ICandidate } from '@gauzy/contracts'; import { Candidate, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidateExperienceRepository } from './repository/mikro-orm-candidate-experience.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('candidate_experience', { mikroOrmRepository: () => MikroOrmCandidateExperienceRepository }) export class CandidateExperience extends TenantOrganizationBaseEntity implements ICandidateExperience { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() occupation: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() duration: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; /* @@ -30,13 +31,13 @@ export class CandidateExperience extends TenantOrganizationBaseEntity |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Candidate }) - @ManyToOne(() => Candidate, (candidate) => candidate.experience, { + @MultiORMManyToOne(() => Candidate, (candidate) => candidate.experience, { onDelete: 'CASCADE' }) candidate?: ICandidate; @ApiProperty({ type: () => String }) @RelationId((it: CandidateExperience) => it.candidate) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) candidateId?: string; } diff --git a/packages/core/src/candidate-feedbacks/candidate-feedbacks.entity.ts b/packages/core/src/candidate-feedbacks/candidate-feedbacks.entity.ts index 11a7acff5c2..12c11fd02e8 100644 --- a/packages/core/src/candidate-feedbacks/candidate-feedbacks.entity.ts +++ b/packages/core/src/candidate-feedbacks/candidate-feedbacks.entity.ts @@ -1,9 +1,5 @@ import { - Column, - OneToOne, JoinColumn, - OneToMany, - ManyToOne, RelationId, Index } from 'typeorm'; @@ -24,19 +20,20 @@ import { CandidateInterviewers, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidateFeedbackRepository } from './repository/mikro-orm-candidate-feedback.repository'; +import { MultiORMManyToOne, MultiORMOneToMany, MultiORMOneToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('candidate_feedback', { mikroOrmRepository: () => MikroOrmCandidateFeedbackRepository }) export class CandidateFeedback extends TenantOrganizationBaseEntity implements ICandidateFeedback { @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiPropertyOptional({ type: () => Number }) - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -44,7 +41,7 @@ export class CandidateFeedback extends TenantOrganizationBaseEntity rating: number; @ApiProperty({ type: () => String, enum: CandidateStatusEnum }) - @Column({ + @MultiORMColumn({ type: 'simple-enum', nullable: true, enum: CandidateStatusEnum @@ -61,7 +58,7 @@ export class CandidateFeedback extends TenantOrganizationBaseEntity * Candidate */ @ApiProperty({ type: () => Candidate }) - @ManyToOne(() => Candidate, (candidate) => candidate.feedbacks, { + @MultiORMManyToOne(() => Candidate, (candidate) => candidate.feedbacks, { onDelete: 'CASCADE' }) candidate?: ICandidate; @@ -69,14 +66,14 @@ export class CandidateFeedback extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: CandidateFeedback) => it.candidate) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) candidateId?: ICandidate['id']; /** * Candidate Interview */ @ApiProperty({ type: () => CandidateInterview }) - @ManyToOne(() => CandidateInterview, (candidateInterview) => candidateInterview.feedbacks, { + @MultiORMManyToOne(() => CandidateInterview, (candidateInterview) => candidateInterview.feedbacks, { onDelete: 'CASCADE' }) interview?: ICandidateInterview; @@ -84,7 +81,7 @@ export class CandidateFeedback extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: CandidateFeedback) => it.interview) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) interviewId?: ICandidateInterview['id']; /* |-------------------------------------------------------------------------- @@ -96,7 +93,7 @@ export class CandidateFeedback extends TenantOrganizationBaseEntity * Candidate Criterions Rating */ @ApiProperty({ type: () => CandidateCriterionsRating }) - @OneToMany(() => CandidateCriterionsRating, (criterionsRating) => criterionsRating.feedback, { + @MultiORMOneToMany(() => CandidateCriterionsRating, (criterionsRating) => criterionsRating.feedback, { cascade: true }) criterionsRating?: ICandidateCriterionsRating[]; @@ -111,12 +108,12 @@ export class CandidateFeedback extends TenantOrganizationBaseEntity * Candidate Interviewers */ @ApiProperty({ type: () => CandidateInterviewers }) - @OneToOne(() => CandidateInterviewers) + @MultiORMOneToOne(() => CandidateInterviewers, { owner: true }) @JoinColumn() interviewer?: ICandidateInterviewers; @ApiProperty({ type: () => String }) @RelationId((it: CandidateFeedback) => it.interviewer) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) interviewerId?: ICandidateInterviewers['id']; } diff --git a/packages/core/src/candidate-interview/candidate-interview.entity.ts b/packages/core/src/candidate-interview/candidate-interview.entity.ts index a7512ba6480..62b6176a79a 100644 --- a/packages/core/src/candidate-interview/candidate-interview.entity.ts +++ b/packages/core/src/candidate-interview/candidate-interview.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, OneToMany, JoinColumn, RelationId, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { ICandidateInterview, @@ -17,36 +17,37 @@ import { TenantOrganizationBaseEntity } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidateInterviewRepository } from './repository/mikro-orm-candidate-interview.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('candidate_interview', { mikroOrmRepository: () => MikroOrmCandidateInterviewRepository }) export class CandidateInterview extends TenantOrganizationBaseEntity implements ICandidateInterview { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() title: string; @ApiProperty({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) startTime: Date; @ApiProperty({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endTime: Date; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) location?: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) note?: string; @ApiPropertyOptional({ type: () => Number }) - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -59,28 +60,28 @@ export class CandidateInterview extends TenantOrganizationBaseEntity |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => CandidateFeedback }) - @OneToMany(() => CandidateFeedback, (feedback) => feedback.interview, { + @MultiORMOneToMany(() => CandidateFeedback, (feedback) => feedback.interview, { onDelete: 'SET NULL' }) @JoinColumn() feedbacks?: ICandidateFeedback[]; @ApiProperty({ type: () => CandidateTechnologies }) - @OneToMany(() => CandidateTechnologies, (technologies) => technologies.interview, { + @MultiORMOneToMany(() => CandidateTechnologies, (technologies) => technologies.interview, { onDelete: 'SET NULL' }) @JoinColumn() technologies?: ICandidateTechnologies[]; @ApiProperty({ type: () => CandidatePersonalQualities }) - @OneToMany(() => CandidatePersonalQualities, (personalQualities) => personalQualities.interview, { + @MultiORMOneToMany(() => CandidatePersonalQualities, (personalQualities) => personalQualities.interview, { onDelete: 'SET NULL' }) @JoinColumn() personalQualities?: ICandidatePersonalQualities[]; @ApiProperty({ type: () => CandidateInterviewers }) - @OneToMany(() => CandidateInterviewers, (interviewers) => interviewers.interview, { + @MultiORMOneToMany(() => CandidateInterviewers, (interviewers) => interviewers.interview, { onDelete: 'SET NULL' }) @JoinColumn() @@ -96,7 +97,7 @@ export class CandidateInterview extends TenantOrganizationBaseEntity * Candidate */ @ApiProperty({ type: () => Candidate }) - @ManyToOne(() => Candidate, (candidate) => candidate.interview, { + @MultiORMManyToOne(() => Candidate, (candidate) => candidate.interview, { onDelete: 'CASCADE' }) candidate?: ICandidate; @@ -104,6 +105,6 @@ export class CandidateInterview extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: CandidateInterview) => it.candidate) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) candidateId?: string; } diff --git a/packages/core/src/candidate-interviewers/candidate-interviewers.entity.ts b/packages/core/src/candidate-interviewers/candidate-interviewers.entity.ts index d5becadd995..aceb38e30b1 100644 --- a/packages/core/src/candidate-interviewers/candidate-interviewers.entity.ts +++ b/packages/core/src/candidate-interviewers/candidate-interviewers.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, RelationId, Index } from 'typeorm'; +import { RelationId, Index } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { ICandidateInterviewers, ICandidateInterview, IEmployee } from '@gauzy/contracts'; import { @@ -7,8 +7,9 @@ import { TenantOrganizationBaseEntity } from '../core/entities/internal'; import { IsString } from 'class-validator'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidateInterviewersRepository } from './repository/mikro-orm-candidate-interviewers.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('candidate_interviewer', { mikroOrmRepository: () => MikroOrmCandidateInterviewersRepository }) export class CandidateInterviewers extends TenantOrganizationBaseEntity implements ICandidateInterviewers { @@ -18,7 +19,7 @@ export class CandidateInterviewers extends TenantOrganizationBaseEntity implemen |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => CandidateInterview }) - @ManyToOne(() => CandidateInterview, (interview) => interview.interviewers, { + @MultiORMManyToOne(() => CandidateInterview, (interview) => interview.interviewers, { onDelete: 'CASCADE' }) interview: ICandidateInterview; @@ -27,11 +28,11 @@ export class CandidateInterviewers extends TenantOrganizationBaseEntity implemen @RelationId((it: CandidateInterviewers) => it.interview) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) interviewId: string; @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { + @MultiORMManyToOne(() => Employee, { onDelete: 'CASCADE' }) employee: IEmployee; @@ -40,6 +41,6 @@ export class CandidateInterviewers extends TenantOrganizationBaseEntity implemen @RelationId((it: CandidateInterviewers) => it.employee) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) employeeId: string; } diff --git a/packages/core/src/candidate-personal-qualities/candidate-personal-qualities.entity.ts b/packages/core/src/candidate-personal-qualities/candidate-personal-qualities.entity.ts index c64f5002d2d..7a1c14523cb 100644 --- a/packages/core/src/candidate-personal-qualities/candidate-personal-qualities.entity.ts +++ b/packages/core/src/candidate-personal-qualities/candidate-personal-qualities.entity.ts @@ -1,4 +1,4 @@ -import { Column, Index, JoinColumn, ManyToOne, OneToMany, RelationId } from 'typeorm'; +import { Index, JoinColumn, RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { ICandidatePersonalQualities, @@ -12,18 +12,19 @@ import { } from '../core/entities/internal'; import { IsOptional, IsString } from 'class-validator'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidatePersonalQualitiesRepository } from './repository/mikro-orm-candidate-personal-qualities.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('candidate_personal_quality', { mikroOrmRepository: () => MikroOrmCandidatePersonalQualitiesRepository }) export class CandidatePersonalQualities extends TenantOrganizationBaseEntity implements ICandidatePersonalQualities { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => Number }) - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -37,7 +38,7 @@ export class CandidatePersonalQualities extends TenantOrganizationBaseEntity imp */ @ApiProperty({ type: () => CandidateInterview }) - @ManyToOne(() => CandidateInterview, (interview) => interview.personalQualities, { + @MultiORMManyToOne(() => CandidateInterview, (interview) => interview.personalQualities, { onDelete: 'CASCADE' }) interview?: ICandidateInterview; @@ -47,7 +48,7 @@ export class CandidatePersonalQualities extends TenantOrganizationBaseEntity imp @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) interviewId?: string; /* @@ -56,7 +57,7 @@ export class CandidatePersonalQualities extends TenantOrganizationBaseEntity imp |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => CandidateCriterionsRating }) - @OneToMany(() => CandidateCriterionsRating, (criterionsRating) => criterionsRating.personalQuality, { + @MultiORMOneToMany(() => CandidateCriterionsRating, (criterionsRating) => criterionsRating.personalQuality, { cascade: true }) @JoinColumn() diff --git a/packages/core/src/candidate-skill/candidate-skill.entity.ts b/packages/core/src/candidate-skill/candidate-skill.entity.ts index 391b21d322c..3507b62ed50 100644 --- a/packages/core/src/candidate-skill/candidate-skill.entity.ts +++ b/packages/core/src/candidate-skill/candidate-skill.entity.ts @@ -1,18 +1,19 @@ -import { Column, ManyToOne, RelationId } from 'typeorm'; +import { RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { ICandidateSkill, ICandidate } from '@gauzy/contracts'; import { Candidate, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidateSkillRepository } from './repository/mikro-orm-candidate-skill.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('candidate_skill', { mikroOrmRepository: () => MikroOrmCandidateSkillRepository }) export class CandidateSkill extends TenantOrganizationBaseEntity implements ICandidateSkill { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; /* @@ -21,17 +22,14 @@ export class CandidateSkill extends TenantOrganizationBaseEntity implements ICan |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Candidate }) - @ManyToOne(() => Candidate, (candidate) => candidate.skills, { - /** Indicates if the relation column value can be nullable or not. */ + @MultiORMManyToOne(() => Candidate, (candidate) => candidate.skills, { + onDelete: 'CASCADE', nullable: true, - - /** Defines the database cascade action on delete. */ - onDelete: 'CASCADE' }) candidate?: ICandidate; @ApiProperty({ type: () => String }) @RelationId((it: CandidateSkill) => it.candidate) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) candidateId?: ICandidate['id']; } diff --git a/packages/core/src/candidate-source/candidate-source.entity.ts b/packages/core/src/candidate-source/candidate-source.entity.ts index 2ce7217715d..edc9ab3e6c8 100644 --- a/packages/core/src/candidate-source/candidate-source.entity.ts +++ b/packages/core/src/candidate-source/candidate-source.entity.ts @@ -1,16 +1,16 @@ -import { Column, OneToOne } from 'typeorm'; import { ICandidate, ICandidateSource } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { Candidate, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidateSourceRepository } from './repository/mikro-orm-candidate-source.repository'; +import { MultiORMOneToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('candidate_source', { mikroOrmRepository: () => MikroOrmCandidateSourceRepository }) export class CandidateSource extends TenantOrganizationBaseEntity implements ICandidateSource { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; /* @@ -19,6 +19,6 @@ export class CandidateSource extends TenantOrganizationBaseEntity |-------------------------------------------------------------------------- */ - @OneToOne(() => Candidate, (candidate) => candidate.source) + @MultiORMOneToOne(() => Candidate, (candidate) => candidate.source) candidate?: ICandidate; } diff --git a/packages/core/src/candidate-technologies/candidate-technologies.entity.ts b/packages/core/src/candidate-technologies/candidate-technologies.entity.ts index 046b87c7673..3b774192ee0 100644 --- a/packages/core/src/candidate-technologies/candidate-technologies.entity.ts +++ b/packages/core/src/candidate-technologies/candidate-technologies.entity.ts @@ -1,4 +1,4 @@ -import { Column, Index, JoinColumn, ManyToOne, OneToMany, RelationId } from 'typeorm'; +import { Index, JoinColumn, RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { ICandidateTechnologies, ICandidateInterview, ICandidateCriterionsRating } from '@gauzy/contracts'; import { @@ -8,18 +8,19 @@ import { } from '../core/entities/internal'; import { IsString } from 'class-validator'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidateTechnologiesRepository } from './repository/mikro-orm-candidate-technologies.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('candidate_technology', { mikroOrmRepository: () => MikroOrmCandidateTechnologiesRepository }) export class CandidateTechnologies extends TenantOrganizationBaseEntity implements ICandidateTechnologies { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => Number }) - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -33,7 +34,7 @@ export class CandidateTechnologies extends TenantOrganizationBaseEntity implemen */ @ApiProperty({ type: () => CandidateInterview }) - @ManyToOne(() => CandidateInterview, (interview) => interview.technologies, { + @MultiORMManyToOne(() => CandidateInterview, (interview) => interview.technologies, { onDelete: 'CASCADE' }) interview?: ICandidateInterview; @@ -42,7 +43,7 @@ export class CandidateTechnologies extends TenantOrganizationBaseEntity implemen @RelationId((it: CandidateTechnologies) => it.interview) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) interviewId?: string; /* @@ -52,7 +53,7 @@ export class CandidateTechnologies extends TenantOrganizationBaseEntity implemen */ @ApiProperty({ type: () => CandidateCriterionsRating }) - @OneToMany(() => CandidateCriterionsRating, (criterionsRating) => criterionsRating.technology, { + @MultiORMOneToMany(() => CandidateCriterionsRating, (criterionsRating) => criterionsRating.technology, { cascade: true }) @JoinColumn() diff --git a/packages/core/src/candidate/candidate.entity.ts b/packages/core/src/candidate/candidate.entity.ts index 6075cdfa82a..2f6e1366f3a 100644 --- a/packages/core/src/candidate/candidate.entity.ts +++ b/packages/core/src/candidate/candidate.entity.ts @@ -19,13 +19,8 @@ import { } from '@gauzy/contracts'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { - Column, JoinColumn, - ManyToMany, - ManyToOne, - OneToOne, RelationId, - OneToMany, Index, JoinTable } from 'typeorm'; @@ -47,13 +42,14 @@ import { User } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCandidateRepository } from './repository/mikro-orm-candidate.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany, MultiORMOneToOne } from './../core/decorators/entity/relations'; @MultiORMEntity('candidate', { mikroOrmRepository: () => MikroOrmCandidateRepository }) export class Candidate extends TenantOrganizationBaseEntity implements ICandidate { @ApiPropertyOptional({ type: () => Number }) - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -61,51 +57,51 @@ export class Candidate extends TenantOrganizationBaseEntity implements ICandidat rating?: number; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) valueDate?: Date; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) appliedDate?: Date; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) hiredDate?: Date; @ApiProperty({ type: () => String, enum: CandidateStatusEnum }) - @Column({ nullable: true, default: CandidateStatusEnum.APPLIED }) + @MultiORMColumn({ nullable: true, default: CandidateStatusEnum.APPLIED }) status?: CandidateStatusEnum; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) rejectDate?: Date; @ApiPropertyOptional({ type: () => String, maxLength: 500 }) - @Column({ length: 500, nullable: true }) + @MultiORMColumn({ length: 500, nullable: true }) candidateLevel?: string; @ApiPropertyOptional({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) reWeeklyLimit?: number; // Recurring Weekly Limit (hours) @ApiPropertyOptional({ type: () => String, maxLength: 255 }) - @Column({ length: 255, nullable: true }) + @MultiORMColumn({ length: 255, nullable: true }) billRateCurrency?: string; @ApiPropertyOptional({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) billRateValue?: number; @ApiPropertyOptional({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) minimumBillingRate?: number; @ApiProperty({ type: () => String, enum: PayPeriodEnum }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) payPeriod?: PayPeriodEnum; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) cvUrl?: string; @@ -122,9 +118,10 @@ export class Candidate extends TenantOrganizationBaseEntity implements ICandidat * Contact */ @ApiProperty({ type: () => Contact }) - @OneToOne(() => Contact, (contact) => contact.candidate, { + @MultiORMOneToOne(() => Contact, (contact) => contact.candidate, { cascade: true, - onDelete: 'SET NULL' + onDelete: 'SET NULL', + owner: true }) @JoinColumn() contact?: IContact; @@ -132,7 +129,7 @@ export class Candidate extends TenantOrganizationBaseEntity implements ICandidat @ApiProperty({ type: () => String, readOnly: true }) @RelationId((it: Candidate) => it.contact) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly contactId?: string; /* @@ -141,14 +138,14 @@ export class Candidate extends TenantOrganizationBaseEntity implements ICandidat |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => OrganizationPosition }) - @ManyToOne(() => OrganizationPosition, { nullable: true }) + @MultiORMManyToOne(() => OrganizationPosition, { nullable: true }) @JoinColumn() organizationPosition?: IOrganizationPosition; @ApiProperty({ type: () => String }) @RelationId((it: Candidate) => it.organizationPosition) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationPositionId?: IOrganizationPosition['id']; /* @@ -158,10 +155,11 @@ export class Candidate extends TenantOrganizationBaseEntity implements ICandidat */ @ApiProperty({ type: () => CandidateSource }) - @OneToOne(() => CandidateSource, { + @MultiORMOneToOne(() => CandidateSource, (candidateSource) => candidateSource.candidate, { nullable: true, cascade: true, - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true }) @JoinColumn() source?: ICandidateSource; @@ -169,16 +167,17 @@ export class Candidate extends TenantOrganizationBaseEntity implements ICandidat @ApiProperty({ type: () => String }) @RelationId((it: Candidate) => it.source) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) sourceId?: ICandidateSource['id']; /** * User */ @ApiProperty({ type: () => User }) - @OneToOne(() => User, (user) => user.candidate, { + @MultiORMOneToOne(() => User, (user) => user.candidate, { cascade: true, - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true }) @JoinColumn() user: IUser; @@ -186,21 +185,21 @@ export class Candidate extends TenantOrganizationBaseEntity implements ICandidat @ApiProperty({ type: () => String }) @RelationId((it: Candidate) => it.user) @Index() - @Column() + @MultiORMColumn({ relationId: true }) userId: IUser['id']; /** * Employee */ @ApiProperty({ type: () => Employee }) - @OneToOne(() => Employee, (employee) => employee.candidate) + @MultiORMOneToOne(() => Employee, (employee) => employee.candidate, { owner: true }) @JoinColumn() employee?: IEmployee; @ApiProperty({ type: () => String }) @RelationId((it: Candidate) => it.employee) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) employeeId?: IEmployee['id']; /* @@ -208,37 +207,37 @@ export class Candidate extends TenantOrganizationBaseEntity implements ICandidat | @OneToMany |-------------------------------------------------------------------------- */ - @OneToMany(() => CandidateEducation, (education) => education.candidate, { + @MultiORMOneToMany(() => CandidateEducation, (education) => education.candidate, { onDelete: 'SET NULL' }) @JoinColumn() educations?: ICandidateEducation[]; - @OneToMany(() => CandidateInterview, (interview) => interview.candidate, { + @MultiORMOneToMany(() => CandidateInterview, (interview) => interview.candidate, { onDelete: 'SET NULL' }) @JoinColumn() interview?: ICandidateInterview[]; - @OneToMany(() => CandidateExperience, (experience) => experience.candidate, { + @MultiORMOneToMany(() => CandidateExperience, (experience) => experience.candidate, { onDelete: 'SET NULL' }) @JoinColumn() experience?: ICandidateExperience[]; - @OneToMany(() => CandidateSkill, (skill) => skill.candidate, { + @MultiORMOneToMany(() => CandidateSkill, (skill) => skill.candidate, { onDelete: 'SET NULL' }) @JoinColumn() skills?: ICandidateSkill[]; - @OneToMany(() => CandidateDocument, (document) => document.candidate, { + @MultiORMOneToMany(() => CandidateDocument, (document) => document.candidate, { onDelete: 'SET NULL' }) @JoinColumn() documents?: ICandidateDocument[]; - @OneToMany(() => CandidateFeedback, (feedback) => feedback.candidate, { + @MultiORMOneToMany(() => CandidateFeedback, (feedback) => feedback.candidate, { onDelete: 'SET NULL' }) @JoinColumn() @@ -251,9 +250,11 @@ export class Candidate extends TenantOrganizationBaseEntity implements ICandidat */ @ApiProperty({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.candidates, { + @MultiORMManyToMany(() => Tag, (tag) => tag.candidates, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + pivotTable: 'tag_candidate', + owner: true }) @JoinTable({ name: 'tag_candidate' @@ -263,7 +264,7 @@ export class Candidate extends TenantOrganizationBaseEntity implements ICandidat /** * Organization Departments */ - @ManyToMany(() => OrganizationDepartment, (department) => department.candidates, { + @MultiORMManyToMany(() => OrganizationDepartment, (department) => department.candidates, { onUpdate: 'CASCADE', onDelete: 'CASCADE' }) @@ -272,6 +273,6 @@ export class Candidate extends TenantOrganizationBaseEntity implements ICandidat /** * Organization Employment Types */ - @ManyToMany(() => OrganizationEmploymentType, (employmentType) => employmentType.candidates) + @MultiORMManyToMany(() => OrganizationEmploymentType, (employmentType) => employmentType.candidates) organizationEmploymentTypes?: IOrganizationEmploymentType[]; } diff --git a/packages/core/src/contact/contact.entity.ts b/packages/core/src/contact/contact.entity.ts index 88b572024cd..faa15a32ef4 100644 --- a/packages/core/src/contact/contact.entity.ts +++ b/packages/core/src/contact/contact.entity.ts @@ -1,4 +1,3 @@ -import { Column, OneToOne } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsString, IsOptional, IsNumber } from 'class-validator'; import { @@ -14,63 +13,64 @@ import { TenantOrganizationBaseEntity } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmContactRepository } from './repository/mikro-orm-contact.repository'; +import { MultiORMOneToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('contact', { mikroOrmRepository: () => MikroOrmContactRepository }) export class Contact extends TenantOrganizationBaseEntity implements IContact { @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) name?: string; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) firstName?: string; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) lastName?: string; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) country?: string; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) city?: string; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) address?: string; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) address2?: string; @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) postcode?: string; @ApiPropertyOptional({ type: () => Number }) @IsNumber() @IsOptional() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -80,7 +80,7 @@ export class Contact extends TenantOrganizationBaseEntity implements IContact { @ApiPropertyOptional({ type: () => Number }) @IsNumber() @IsOptional() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -88,27 +88,27 @@ export class Contact extends TenantOrganizationBaseEntity implements IContact { longitude?: number; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) regionCode?: string; @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) fax?: string; @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) fiscalInformation?: string; @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) website?: string; /* @@ -121,8 +121,8 @@ export class Contact extends TenantOrganizationBaseEntity implements IContact { * Employee */ @ApiProperty({ type: () => Employee }) - @OneToOne(() => Employee, (employee) => employee.contact, { - onDelete: 'SET NULL' + @MultiORMOneToOne(() => Employee, (employee) => employee.contact, { + onDelete: 'SET NULL', }) employee?: IEmployee; @@ -130,8 +130,8 @@ export class Contact extends TenantOrganizationBaseEntity implements IContact { * Employee */ @ApiProperty({ type: () => Candidate }) - @OneToOne(() => Candidate, (candidate) => candidate.contact, { - onDelete: 'SET NULL' + @MultiORMOneToOne(() => Candidate, (candidate) => candidate.contact, { + onDelete: 'SET NULL', }) candidate?: ICandidate; @@ -139,8 +139,8 @@ export class Contact extends TenantOrganizationBaseEntity implements IContact { * Organization Contact */ @ApiProperty({ type: () => OrganizationContact }) - @OneToOne(() => OrganizationContact, (organizationContact) => organizationContact.contact, { - onDelete: 'SET NULL' + @MultiORMOneToOne(() => OrganizationContact, (organizationContact) => organizationContact.contact, { + onDelete: 'SET NULL', }) organizationContact?: IOrganizationContact; } diff --git a/packages/core/src/core/crud/crud.service.ts b/packages/core/src/core/crud/crud.service.ts index f0e7e9abfb5..0081e15d603 100644 --- a/packages/core/src/core/crud/crud.service.ts +++ b/packages/core/src/core/crud/crud.service.ts @@ -15,19 +15,20 @@ import { import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { EntityRepository, - FindOneOptions as MikroFindOneOptions, - FindOptions as MikroFindOptions, FilterQuery as MikroFilterQuery, RequiredEntityData, DeleteOptions, - FindOneOrFailOptions, - FilterQuery + wrap } from '@mikro-orm/core'; -import { of as observableOf, throwError } from 'rxjs'; -import { mergeMap } from 'rxjs/operators'; import { IPagination } from '@gauzy/contracts'; import { BaseEntity } from '../entities/internal'; -import { MultiORM, MultiORMEnum, flatten, getORMType } from './../../core/utils'; +import { + MultiORM, + MultiORMEnum, + concatIdToWhere, + getORMType, + parseTypeORMFindToMikroOrm +} from './../../core/utils'; import { parseTypeORMFindCountOptions } from './utils'; import { ICountByOptions, @@ -36,13 +37,13 @@ import { IFindManyOptions, IFindOneOptions, IFindWhereOptions, - IMikroOptions, IPartialEntity, IUpdateCriteria } from './icrud.service'; import { ITryRequest } from './try-request'; +import { multiORMCreateQueryBuilder } from 'core/orm/query-builder/query-builder.factory'; -// +// Get the type of the Object-Relational Mapping (ORM) used in the application. const ormType: MultiORM = getORMType(); export abstract class CrudService implements ICrudService { @@ -59,15 +60,6 @@ export abstract class CrudService implements ICrudService< return this.repository.metadata.tableName; } - /** - * Get the alias for pagination CRUD operations. - * By default, it uses the table name. - * @returns {string} The alias. - */ - protected get alias(): string { - return this.repository.metadata.tableName; - } - /** * Get the ORM type. * @returns {MultiORM} The ORM type. @@ -76,17 +68,30 @@ export abstract class CrudService implements ICrudService< return ormType; } + createQueryBuilder(entity?: any) { + + console.log('this.mikroRepository.getEntityManager', this.mikroRepository.getEntityName()); + + switch (this.ormType) { + case MultiORMEnum.MikroORM: + return multiORMCreateQueryBuilder(this.repository, this.ormType as MultiORMEnum); + + case MultiORMEnum.TypeORM: + return multiORMCreateQueryBuilder(this.mikroRepository as any, this.ormType as MultiORMEnum); + } + } + /** - * Counts entities that match given options. - * Useful for pagination. + * Count the number of entities based on the provided options. * - * @param options - * @returns + * @param options - Options for counting entities. + * @returns A Promise that resolves to the count of entities. */ public async count(options?: ICountOptions): Promise { switch (this.ormType) { case MultiORMEnum.MikroORM: - return await this.mikroRepository.count(options as MikroFilterQuery); + const { where, mikroOptions } = parseTypeORMFindToMikroOrm(options as FindManyOptions); + return await this.mikroRepository.count(where, mikroOptions); case MultiORMEnum.TypeORM: const typeormOptions = parseTypeORMFindCountOptions(options as FindManyOptions); return await this.repository.count(typeormOptions as FindManyOptions); @@ -105,7 +110,8 @@ export abstract class CrudService implements ICrudService< public async countBy(options?: ICountByOptions): Promise { switch (this.ormType) { case MultiORMEnum.MikroORM: - return await this.mikroRepository.count(options as MikroFilterQuery); + const { where, mikroOptions } = parseTypeORMFindToMikroOrm({ where: options } as FindManyOptions); + return await this.mikroRepository.count(where, mikroOptions); case MultiORMEnum.TypeORM: const typeormOptions = parseTypeORMFindCountOptions({ where: options } as FindManyOptions); return await this.repository.count(typeormOptions as FindManyOptions); @@ -122,16 +128,15 @@ export abstract class CrudService implements ICrudService< * @param options * @returns */ - public async findAll(options?: ICountOptions): Promise> { + public async findAll(options?: IFindManyOptions): Promise> { let total: number; let items: T[]; switch (this.ormType) { case MultiORMEnum.MikroORM: - [items, total] = await this.mikroRepository.findAndCount( - options?.where as MikroFilterQuery, - options as MikroFindOptions - ); + const { where, mikroOptions } = parseTypeORMFindToMikroOrm(options as FindManyOptions); + [items, total] = await this.mikroRepository.findAndCount(where, mikroOptions) as any; + items = items.map((entity: T) => this.serialize(entity)) as T[]; break; case MultiORMEnum.TypeORM: [items, total] = await this.repository.findAndCount(options as FindManyOptions); @@ -139,6 +144,7 @@ export abstract class CrudService implements ICrudService< default: throw new Error(`Not implemented for ${this.ormType}`); } + return { items, total }; } @@ -151,10 +157,9 @@ export abstract class CrudService implements ICrudService< public async find(options?: IFindManyOptions): Promise { switch (this.ormType) { case MultiORMEnum.MikroORM: - return await this.mikroRepository.find( - options.where as MikroFilterQuery, - options as MikroFindOptions - ); + const { where, mikroOptions } = parseTypeORMFindToMikroOrm(options as FindManyOptions); + const items = await this.mikroRepository.find(where, mikroOptions); + return items.map((entity: T) => this.serialize(entity)) as T[]; case MultiORMEnum.TypeORM: return await this.repository.find(options as FindManyOptions); default: @@ -177,14 +182,9 @@ export abstract class CrudService implements ICrudService< switch (this.ormType) { case MultiORMEnum.MikroORM: - [items, total] = await this.mikroRepository.findAndCount( - options?.where as MikroFilterQuery, - { - skip: options && options.skip ? options.take * (options.skip - 1) : 0, - take: options && options.take ? options.take : 10, - ...options - } as MikroFindOptions - ); + const { where, mikroOptions } = parseTypeORMFindToMikroOrm(options as FindManyOptions); + [items, total] = await this.mikroRepository.findAndCount(where, mikroOptions) as any; + items = items.map((entity) => this.serialize(entity)) as T[]; break; case MultiORMEnum.TypeORM: [items, total] = await this.repository.findAndCount({ @@ -195,11 +195,7 @@ export abstract class CrudService implements ICrudService< * * @deprecated */ - ...(options && options.join - ? { - join: options.join - } - : {}), + ...(options && options.join ? { join: options.join } : {}), ...(options && options.select ? { select: options.select } : {}), ...(options && options.relations ? { relations: options.relations } : {}), ...(options && options.where ? { where: options.where } : {}), @@ -209,6 +205,7 @@ export abstract class CrudService implements ICrudService< default: throw new Error(`Not implemented for ${this.ormType}`); } + return { items, total }; } catch (error) { console.log(error); @@ -235,34 +232,17 @@ export abstract class CrudService implements ICrudService< let record: T; switch (this.ormType) { case MultiORMEnum.MikroORM: - // return await this.mikroRepository.find(options.where as MikroFilterQuery, options as MikroFindOptions); - options = options as IMikroOptions; - - let where: MikroFilterQuery; - if (options?.where instanceof Array) { - where = options.where.concat({ id } as any); - } else { - where = { - id, - ...(options?.where ? options.where : ({} as any)) - }; - } - - record = await this.mikroRepository.findOneOrFail(where, options as FindOneOrFailOptions); + const { where, mikroOptions } = parseTypeORMFindToMikroOrm(options as FindManyOptions); + record = await this.mikroRepository.findOneOrFail(concatIdToWhere(id, where), mikroOptions) as any; break; - case MultiORMEnum.TypeORM: options = options as FindOneOptions; record = await this.repository.findOneOrFail({ - ...(options && options.select - ? { - select: options.select - } - : {}), where: { id, ...(options && options.where ? options.where : {}) }, + ...(options && options.select ? { select: options.select } : {}), ...(options && options.relations ? { relations: options.relations } : []), ...(options && options.order ? { order: options.order } : {}) } as FindOneOptions); @@ -270,10 +250,9 @@ export abstract class CrudService implements ICrudService< default: throw new Error(`Not implemented for ${this.ormType}`); } - return { success: true, - record + record: this.serialize(record) }; } catch (error) { return { @@ -295,10 +274,8 @@ export abstract class CrudService implements ICrudService< let record: T; switch (this.ormType) { case MultiORMEnum.MikroORM: - record = await this.mikroRepository.findOneOrFail( - options.where as MikroFilterQuery, - options as FindOneOrFailOptions - ); + const { where, mikroOptions } = parseTypeORMFindToMikroOrm(options as FindManyOptions); + record = await this.mikroRepository.findOneOrFail(where, mikroOptions) as any; break; case MultiORMEnum.TypeORM: record = await this.repository.findOneOrFail(options as FindOneOptions); @@ -308,7 +285,7 @@ export abstract class CrudService implements ICrudService< } return { success: true, - record + record: this.serialize(record) }; } catch (error) { return { @@ -330,7 +307,8 @@ export abstract class CrudService implements ICrudService< let record: T; switch (this.ormType) { case MultiORMEnum.MikroORM: - record = await this.mikroRepository.findOneOrFail(options as MikroFilterQuery); + const { where, mikroOptions } = parseTypeORMFindToMikroOrm(options as FindManyOptions); + record = await this.mikroRepository.findOneOrFail(where, mikroOptions) as any; break; case MultiORMEnum.TypeORM: record = await this.repository.findOneByOrFail(options as FindOptionsWhere); @@ -340,7 +318,7 @@ export abstract class CrudService implements ICrudService< } return { success: true, - record + record: this.serialize(record) }; } catch (error) { return { @@ -365,31 +343,20 @@ export abstract class CrudService implements ICrudService< */ public async findOneByIdString(id: T['id'], options?: IFindOneOptions): Promise { let record: T; + switch (this.ormType) { case MultiORMEnum.MikroORM: - // return await this.mikroRepository.find(options.where as MikroFilterQuery, options as MikroFindOptions); - options = options as IMikroOptions; - - let where: MikroFilterQuery; - if (options?.where instanceof Array) { - where = options.where.concat({ id } as any); - } else { - where = { - id, - ...(options?.where ? options.where : ({} as any)) - }; - } - - record = await this.mikroRepository.findOne(where, options as MikroFindOneOptions); + const { where, mikroOptions } = parseTypeORMFindToMikroOrm(options as FindManyOptions); + record = await this.mikroRepository.findOne(concatIdToWhere(id, where), mikroOptions) as any; break; case MultiORMEnum.TypeORM: options = options as FindOneOptions; record = await this.repository.findOne({ - ...(options && options.select ? { select: options.select } : {}), where: { id, ...(options && options.where ? options.where : {}) }, + ...(options && options.select ? { select: options.select } : {}), ...(options && options.relations ? { relations: options.relations } : []), ...(options && options.order ? { order: options.order } : {}) } as FindOneOptions); @@ -402,7 +369,7 @@ export abstract class CrudService implements ICrudService< throw new NotFoundException(`The requested record was not found`); } - return record; + return this.serialize(record); } /** @@ -416,14 +383,8 @@ export abstract class CrudService implements ICrudService< let record: T; switch (this.ormType) { case MultiORMEnum.MikroORM: - const mikroOptions = options as any; - const mikroFilterQuery: MikroFilterQuery = options.where as FilterQuery; - const mikroFindOneOptions: MikroFindOneOptions = { - ...(mikroOptions.relations ? { populate: flatten(mikroOptions.relations) } : {}), - ...(mikroOptions.order ? { orderBy: mikroOptions.order } : {}) - }; - console.log({ mikroFilterQuery }, { mikroFindOneOptions }); - record = await this.mikroRepository.findOne(mikroFilterQuery, mikroFindOneOptions); + const { where, mikroOptions } = parseTypeORMFindToMikroOrm(options as FindManyOptions); + record = await this.mikroRepository.findOne(where, mikroOptions) as any; break; case MultiORMEnum.TypeORM: record = await this.repository.findOne(options as FindOneOptions); @@ -436,7 +397,7 @@ export abstract class CrudService implements ICrudService< throw new NotFoundException(`The requested record was not found`); } - return record; + return this.serialize(record); } /** @@ -450,7 +411,8 @@ export abstract class CrudService implements ICrudService< let record: T; switch (this.ormType) { case MultiORMEnum.MikroORM: - record = await this.mikroRepository.findOne(options as MikroFilterQuery); + const { where, mikroOptions } = parseTypeORMFindToMikroOrm({ where: options } as FindManyOptions); + record = await this.mikroRepository.findOne(where, mikroOptions) as any; break; case MultiORMEnum.TypeORM: record = await this.repository.findOneBy(options as FindOptionsWhere); @@ -462,31 +424,32 @@ export abstract class CrudService implements ICrudService< if (!record) { throw new NotFoundException(`The requested record was not found`); } - - return record; + return this.serialize(record); } + + + /** + * Create a new entity. + * + * @param entity - The entity data to create. + * @returns A promise resolving to the created entity. + */ public async create(entity: IPartialEntity): Promise { - switch (this.ormType) { - case MultiORMEnum.MikroORM: - try { + try { + switch (this.ormType) { + case MultiORMEnum.MikroORM: const row = this.mikroRepository.create(entity as RequiredEntityData); return await this.mikroRepository.upsert(row); - } catch (err /*: WriteError*/) { - throw new BadRequestException(err); - } - - case MultiORMEnum.TypeORM: - const obj = this.repository.create(entity as DeepPartial); - try { - // https://github.com/Microsoft/TypeScript/issues/21592 - return await this.repository.save(obj as any); - } catch (err /*: WriteError*/) { - throw new BadRequestException(err); - } - - default: - throw new Error(`Not implemented for ${this.ormType}`); + case MultiORMEnum.TypeORM: + const obj = this.repository.create(entity as DeepPartial); + return await this.repository.save(obj); + default: + throw new Error(`Not implemented for ${this.ormType}`); + } + } catch (error) { + console.error('Error in crud service create method:', error); + throw new BadRequestException(error); } } @@ -508,6 +471,7 @@ export abstract class CrudService implements ICrudService< throw new Error(`Not implemented for ${this.ormType}`); } } catch (error) { + console.error('Error in crud service save method:', error); throw new BadRequestException(error); } } @@ -534,22 +498,17 @@ export abstract class CrudService implements ICrudService< } const row = partialEntity as RequiredEntityData; const updatedRow = await this.mikroRepository.nativeUpdate(where, row as T); - - return { - affected: updatedRow - } as UpdateResult; - + return { affected: updatedRow } as UpdateResult; case MultiORMEnum.TypeORM: return await this.repository.update( id as string | number | FindOptionsWhere, partialEntity as QueryDeepPartialEntity ); - default: throw new Error(`Not implemented for ${this.ormType}`); } - } catch (err /*: WriteError*/) { - throw new BadRequestException(err); + } catch (error) { + throw new BadRequestException(error); } } @@ -581,27 +540,19 @@ export abstract class CrudService implements ICrudService< } catch (error) { throw new NotFoundException(`The record was not found`, error); } - - // try { - // return await this.repository.delete(criteria); - // } catch (error) { - // console.log(error) - // throw new NotFoundException(`The record was not found`, error); - // } } /** - * e.g., findOneById(id).pipe(map(entity => entity.id), entityNotFound()) + * Serializes the provided entity based on the ORM type. + * @param entity The entity to be serialized. + * @returns The serialized entity. */ - private entityNotFound() { - return (stream$) => - stream$.pipe( - mergeMap((signal) => { - if (!signal) { - return throwError(() => new NotFoundException(`The requested record was not found`)); - } - return observableOf(signal); - }) - ); + private serialize(entity: T): T { + if (this.ormType === MultiORMEnum.MikroORM) { + // If using MikroORM, use wrap(entity).toJSON() for serialization + return wrap(entity).toJSON() as T; + } + // If using other ORM types, return the entity as is + return entity; } } diff --git a/packages/core/src/core/crud/tenant-aware-crud.service.ts b/packages/core/src/core/crud/tenant-aware-crud.service.ts index 65cb7e8148d..573062a4713 100644 --- a/packages/core/src/core/crud/tenant-aware-crud.service.ts +++ b/packages/core/src/core/crud/tenant-aware-crud.service.ts @@ -32,19 +32,21 @@ export abstract class TenantAwareCrudService extends } /** + * Define find conditions when retrieving data with employee by user. * - * @returns + * @returns The find conditions based on the current user's relationship with employees. */ private findConditionsWithEmployeeByUser(): FindOptionsWhere { const employeeId = RequestContext.currentEmployeeId(); return ( /** - * If employee has login & retrieve self data + * If the employee has logged in, retrieve their own data unless + * they have the permission to change the selected employee. */ ( isNotEmpty(employeeId) ? !RequestContext.hasPermission(PermissionsEnum.CHANGE_SELECTED_EMPLOYEE) && - this.repository.metadata.hasColumnWithPropertyPath('employeeId') + this.repository.metadata?.hasColumnWithPropertyPath('employeeId') ? { employee: { id: employeeId @@ -58,13 +60,14 @@ export abstract class TenantAwareCrudService extends } /** + * Define find conditions when retrieving data with tenant by user. * - * @param user - * @returns + * @param user - The user for whom the conditions are defined. + * @returns The find conditions based on the user's relationship with the tenant and employees. */ private findConditionsWithTenantByUser(user: IUser): FindOptionsWhere { return { - ...(this.repository.metadata.hasColumnWithPropertyPath('tenantId') + ...(this.repository.metadata?.hasColumnWithPropertyPath('tenantId') ? { tenant: { id: user.tenantId @@ -77,10 +80,11 @@ export abstract class TenantAwareCrudService extends } /** + * Define find conditions when retrieving data with tenant. * - * @param user - * @param where - * @returns + * @param user - The user for whom the conditions are defined. + * @param where - Additional find options. + * @returns The find conditions based on the user's relationship with the tenant and additional options. */ private findConditionsWithTenant( user: User, @@ -109,9 +113,10 @@ export abstract class TenantAwareCrudService extends } /** + * Define find one options when retrieving data with tenant. * - * @param filter - * @returns + * @param filter - Additional find options. + * @returns The find one options based on the current user's relationship with the tenant and additional options. */ private findOneWithTenant(filter?: FindOneOptions): FindOneOptions { const user = RequestContext.currentUser(); @@ -139,9 +144,10 @@ export abstract class TenantAwareCrudService extends } /** + * Define find many options when retrieving data with tenant. * - * @param filter - * @returns + * @param filter - Additional find options. + * @returns The find many options based on the current user's relationship with the tenant and additional options. */ private findManyWithTenant(filter?: FindManyOptions): FindManyOptions { const user = RequestContext.currentUser(); @@ -328,7 +334,7 @@ export abstract class TenantAwareCrudService extends return await super.create({ ...entity, - ...(this.repository.metadata.hasColumnWithPropertyPath('tenantId') + ...(this.repository.metadata?.hasColumnWithPropertyPath('tenantId') ? { tenant: { id: tenantId @@ -341,7 +347,7 @@ export abstract class TenantAwareCrudService extends */ ...(isNotEmpty(employeeId) ? !RequestContext.hasPermission(PermissionsEnum.CHANGE_SELECTED_EMPLOYEE) && - this.repository.metadata.hasColumnWithPropertyPath('employeeId') + this.repository.metadata?.hasColumnWithPropertyPath('employeeId') ? { employee: { id: employeeId diff --git a/packages/core/src/core/decorators/entity/column-options.types.ts b/packages/core/src/core/decorators/entity/column-options.types.ts new file mode 100644 index 00000000000..26d5d469176 --- /dev/null +++ b/packages/core/src/core/decorators/entity/column-options.types.ts @@ -0,0 +1,17 @@ +import { ColumnType as MikroORMColumnType, PropertyOptions as MikroORMPropertyOptions } from '@mikro-orm/core'; +import { ColumnType as TypeORMColumnType, ColumnOptions as TypeORMColumnOptions } from 'typeorm'; + +// +type CommonColumnOptions = Omit, 'type' | 'default'> & Omit & { + type?: ColumnDataType; + relationId?: boolean; // Need to prevent Mikro-orm property decorator when relationId column +}; + +// Represents MikroORM-specific column options, using MikroORM's PropertyOptions. +export type MikroORMColumnOptions = MikroORMPropertyOptions; + +// Represents the type of data that can be used for a column in either TypeORM or MikroORM. +export type ColumnDataType = TypeORMColumnType | MikroORMColumnType; + +// Represents common column options that can be used in both TypeORM and MikroORM. +export type ColumnOptions = CommonColumnOptions; diff --git a/packages/core/src/core/decorators/entity/column.decorator.ts b/packages/core/src/core/decorators/entity/column.decorator.ts new file mode 100644 index 00000000000..e7aa1068a36 --- /dev/null +++ b/packages/core/src/core/decorators/entity/column.decorator.ts @@ -0,0 +1,38 @@ +import { Property as MikroORMColumn } from '@mikro-orm/core'; +import { Column as TypeORMColumn } from 'typeorm'; +import { ObjectUtils } from '../../../core/util/object-utils'; +import { ColumnDataType, ColumnOptions } from './column-options.types'; +import { parseMikroOrmColumnOptions, resolveDbType } from './column.helper'; + +/** + * Decorator for creating column definitions for both MikroORM and TypeORM. + * + * @template T - The type of the column. + * @param typeOrOptions - The column type or additional options if provided. + * @param options - The options for the column. + * @returns PropertyDecorator. + */ +export function MultiORMColumn( + typeOrOptions?: ColumnDataType | ColumnOptions, + options?: ColumnOptions +): PropertyDecorator { + // normalize parameters + let type: ColumnDataType | undefined; + + if (typeof typeOrOptions === 'string' || typeof typeOrOptions === 'function') { + // If typeOrOptions is a string or function, set 'type' to the resolved type and 'options' to an empty object. + type = resolveDbType(typeOrOptions); + } else if (ObjectUtils.isObject(typeOrOptions)) { + // If typeOrOptions is an object, assume it is 'options' and set 'type' accordingly. + options = >typeOrOptions; + type = resolveDbType(options.type); + } + + // Ensure 'options' is initialized to an empty object if it is null or undefined. + if (!options) options = {} as ColumnOptions; + + return (target: any, propertyKey: string) => { + TypeORMColumn({ type, ...options })(target, propertyKey); + MikroORMColumn(parseMikroOrmColumnOptions({ type, options }))(target, propertyKey); + }; +} diff --git a/packages/core/src/core/decorators/entity/column.helper.ts b/packages/core/src/core/decorators/entity/column.helper.ts new file mode 100644 index 00000000000..79bbca93cc7 --- /dev/null +++ b/packages/core/src/core/decorators/entity/column.helper.ts @@ -0,0 +1,28 @@ +import { ColumnDataType, MikroORMColumnOptions } from "./column-options.types"; + +/** + * Resolve the database column type. + * @param columnType - The input column type. + * @returns The resolved column type. + */ +export function resolveDbType(columnType: ColumnDataType): ColumnDataType { + return columnType; +} + +/** + * Parse MikroORM column options. + * @param param0 - The options for parsing column arguments. + * @returns MikroORM column options. + */ +export function parseMikroOrmColumnOptions({ type, options }): MikroORMColumnOptions { + if (typeof options?.default === 'function') { + options.default = options.default(); + } + if (options?.relationId) { + options.persist = false; + } + return { + type: type, + ...options + } +} diff --git a/packages/core/src/core/decorators/entity/index.decorator.ts b/packages/core/src/core/decorators/entity/index.decorator.ts new file mode 100644 index 00000000000..3c78701c58d --- /dev/null +++ b/packages/core/src/core/decorators/entity/index.decorator.ts @@ -0,0 +1,20 @@ +import { Index as TypeOrmIndex, IndexOptions as TypeOrmIndexOptions } from 'typeorm'; +import { Index as MikroOrmIndex, IndexOptions as MikroOrmIndexOptions } from '@mikro-orm/core'; + +// Type definition for the TypeORM target. +type TypeORMIndexOptions = string | string[] | ((object: any) => any[] | { [key: string]: number }) | TypeOrmIndexOptions; +type IndexOptions = TypeORMIndexOptions | MikroOrmIndexOptions; + +/** + * + * @param nameOrFieldsOrOptions + * @returns + */ +export function ColumnIndex( + nameOrFieldsOrOptions?: IndexOptions +) { + return (target: any, propertyKey: string) => { + TypeOrmIndex(nameOrFieldsOrOptions as TypeOrmIndexOptions)(target, propertyKey); + MikroOrmIndex(nameOrFieldsOrOptions as MikroOrmIndexOptions) + }; +} diff --git a/packages/core/src/core/decorators/entity/index.ts b/packages/core/src/core/decorators/entity/index.ts index 796913fe369..5f3430d8f1f 100644 --- a/packages/core/src/core/decorators/entity/index.ts +++ b/packages/core/src/core/decorators/entity/index.ts @@ -1 +1,3 @@ export * from './entity.decorator'; +export * from './column.decorator'; +export * from './relations'; diff --git a/packages/core/src/core/decorators/entity/relations/index.ts b/packages/core/src/core/decorators/entity/relations/index.ts new file mode 100644 index 00000000000..e14502bd511 --- /dev/null +++ b/packages/core/src/core/decorators/entity/relations/index.ts @@ -0,0 +1,4 @@ +export * from "./one-to-many.decorator"; +export * from "./one-to-one.decorator"; +export * from "./many-to-many.decorator"; +export * from "./many-to-one.decorator"; diff --git a/packages/core/src/core/decorators/entity/relations/many-to-many.decorator.ts b/packages/core/src/core/decorators/entity/relations/many-to-many.decorator.ts new file mode 100644 index 00000000000..209ef7e0426 --- /dev/null +++ b/packages/core/src/core/decorators/entity/relations/many-to-many.decorator.ts @@ -0,0 +1,128 @@ +import { Cascade, EntityName, ManyToManyOptions } from "@mikro-orm/core"; +import { RelationOptions as TypeOrmRelationOptions } from 'typeorm'; +import { omit } from "underscore"; +import { deepClone } from "@gauzy/common"; +import { MultiORMEnum } from "../../../../core/utils"; +import { ObjectUtils } from '../../../../core/util/object-utils'; +import { TypeOrmManyToMany } from "./type-orm"; +import { MikroOrmManyToMany } from "./mikro-orm"; +import { MikroORMInverseSide, TypeORMInverseSide, TypeORMRelationOptions, TypeORMTarget } from "./shared-types"; + +/** + * Interface for options used in mapping Many-to-Many relationships in MikroORM. + * + */ +export interface MapManyToManyArgsForMikroORMOptions { + // Type or target function for the related entity. + typeFunctionOrTarget: TargetEntity; + // Inverse side of the relationship. + inverseSide?: InverseSide; + // Additional options for the Many-to-Many relationship. + options?: RelationOptions; +} + +type MikroORMTarget = ManyToManyOptions | string | ((e?: any) => EntityName); +type MikroORMRelationOptions = Partial, | 'cascade' | 'onCreate' | 'onUpdate'>>; + +type TargetEntity = TypeORMTarget | MikroORMTarget; +type InverseSide = TypeORMInverseSide & MikroORMInverseSide; +type RelationOptions = MikroORMRelationOptions & TypeORMRelationOptions & { + cascade?: Cascade[] | (boolean | ("update" | "insert" | "remove" | "soft-remove" | "recover")[]); +}; + +/** + * Decorator for defining Many-to-Many relationships in both TypeORM and MikroORM. + * + * @param typeFunctionOrTarget - Type or target function for the related entity. + * @param inverseSide - Inverse side of the relationship or additional options. + * @param options - Additional options for the Many-to-Many relationship. + * @returns PropertyDecorator + */ +export function MultiORMManyToMany( + typeFunctionOrTarget: TargetEntity, + inverseSide?: InverseSide | RelationOptions, + options?: RelationOptions +): PropertyDecorator { + // Normalize parameters. + let inverseSideProperty: InverseSide; + + if (ObjectUtils.isObject(inverseSide)) { + options = >inverseSide; + } else { + inverseSideProperty = inverseSide as any; + } + + return (target: any, propertyKey: string) => { + // If options are not provided, initialize an empty object + if (!options) options = {} as RelationOptions; + + // Use TypeORM decorator for Many-to-Many + TypeOrmManyToMany(typeFunctionOrTarget as TypeORMTarget, inverseSideProperty as TypeORMInverseSide, options as TypeORMRelationOptions)(target, propertyKey); + + // Use MikroORM decorator for Many-to-Many + MikroOrmManyToMany(mapManyToManyArgsForMikroORM({ typeFunctionOrTarget, inverseSide: inverseSideProperty as InverseSide, options }))(target, propertyKey); + }; +} + +/** + * Maps Many-to-Many relationship options for MikroORM. + * + * @param typeFunctionOrTarget - Type or target function for the related entity. + * @param inverseSide - Inverse side of the relationship. + * @param options - Additional options for the Many-to-Many relationship. + * @returns MikroORM-specific Many-to-Many relationship options. + */ +function mapManyToManyArgsForMikroORM({ typeFunctionOrTarget, inverseSide, options }: MapManyToManyArgsForMikroORMOptions) { + // Cast options to RelationOptions + const typeOrmOptions = deepClone(options) as TypeOrmRelationOptions; + + // Initialize an array to store MikroORM cascade options + let mikroORMCascade: Cascade[] = []; + + // Check if TypeORM cascade options are provided + if (typeOrmOptions?.cascade) { + // Handle boolean cascade option + if (typeof typeOrmOptions.cascade === 'boolean') { + mikroORMCascade = typeOrmOptions.cascade ? [Cascade.ALL] : []; + } + + // Handle array cascade options + if (typeOrmOptions?.cascade instanceof Array) { + mikroORMCascade = typeOrmOptions.cascade.map((c) => { + switch (c) { + case 'insert': + return Cascade.PERSIST; + case 'update': + return Cascade.MERGE; + case 'remove': + return Cascade.REMOVE; + case 'soft-remove': + case 'recover': + return null; + default: + return null; + } + }).filter((c) => c) as Cascade[]; + } + } + + // Create MikroORM relation options + const mikroOrmOptions: Partial> = { + ...omit(options, 'onDelete', 'onUpdate') as any, + entity: typeFunctionOrTarget as (string | ((e?: any) => EntityName)), + cascade: mikroORMCascade, + ...(typeOrmOptions?.nullable ? { nullable: typeOrmOptions?.nullable } : {}), + ...(typeOrmOptions?.lazy ? { lazy: typeOrmOptions?.lazy } : {}), + }; + + // Map inverseSideOrOptions based on the DB_ORM environment variable + if (process.env.DB_ORM == MultiORMEnum.MikroORM) { + if (mikroOrmOptions.owner === true) { + mikroOrmOptions.inversedBy = inverseSide; + } else { + mikroOrmOptions.mappedBy = inverseSide; + } + } + + return mikroOrmOptions as MikroORMRelationOptions +} diff --git a/packages/core/src/core/decorators/entity/relations/many-to-one.decorator.ts b/packages/core/src/core/decorators/entity/relations/many-to-one.decorator.ts new file mode 100644 index 00000000000..2353a60f3a6 --- /dev/null +++ b/packages/core/src/core/decorators/entity/relations/many-to-one.decorator.ts @@ -0,0 +1,132 @@ +import { Cascade, EntityName, ManyToOneOptions } from "@mikro-orm/core"; +import { RelationOptions as TypeOrmRelationOptions } from 'typeorm'; +import { omit } from "underscore"; +import { deepClone } from "@gauzy/common"; +import { ObjectUtils } from "../../../../core/util/object-utils"; +import { TypeOrmManyToOne } from "./type-orm"; +import { MikroOrmManyToOne } from "./mikro-orm"; +import { MikroORMInverseSide, TypeORMInverseSide, TypeORMRelationOptions, TypeORMTarget } from "./shared-types"; + +/** + * Options for mapping ManyToOne relationship arguments for MikroORM. + * + * @template T - The type of the target entity. + * @template O - The type of additional options. + */ +export interface MapManyToOneArgsForMikroORMOptions { + // The target entity class or function returning the target entity class. + typeFunctionOrTarget: TargetEntity; + // The inverse side of the relationship or additional options if provided. + inverseSideOrOptions?: InverseSide; + // The options for the ManyToOne relationship. + options?: RelationOptions; + // The property key of the target entity. + propertyKey?: string; + // The target string (optional). + target?: string; +} + +type MikroORMTarget = ManyToOneOptions | string | ((e?: any) => EntityName); +type MikroORMRelationOptions = Omit>, 'cascade' | 'onUpdate' | 'onDelete'>; + +type TargetEntity = TypeORMTarget | MikroORMTarget; +type InverseSide = TypeORMInverseSide & MikroORMInverseSide; +type RelationOptions = MikroORMRelationOptions & TypeORMRelationOptions & { + cascade?: Cascade[] | (boolean | ("update" | "insert" | "remove" | "soft-remove" | "recover")[]); +}; + +/** + * Decorator for defining Many-to-One relationships in both TypeORM and MikroORM. + * + * @param typeFunctionOrTarget - Type or target function for the related entity. + * @param inverseSideOrOptions - Inverse side of the relationship or additional options. + * @param options - Additional options for the Many-to-One relationship. + * @returns PropertyDecorator + */ +export function MultiORMManyToOne( + typeFunctionOrTarget: TargetEntity, + inverseSideOrOptions?: InverseSide | RelationOptions, + options?: RelationOptions +): PropertyDecorator { + // Normalize parameters. + let inverseSideProperty: InverseSide; + + if (ObjectUtils.isObject(inverseSideOrOptions)) { + options = >inverseSideOrOptions; + } else { + inverseSideProperty = inverseSideOrOptions as any; + } + + return (target: any, propertyKey: string) => { + // If options are not provided, initialize an empty object + if (!options) options = {} as RelationOptions; + + // Use TypeORM decorator for Many-to-One + TypeOrmManyToOne(typeFunctionOrTarget as TypeORMTarget, inverseSideOrOptions as TypeORMInverseSide, options as TypeORMRelationOptions)(target, propertyKey); + + // Use MikroORM decorator for Many-to-One + MikroOrmManyToOne(mapManyToOneArgsForMikroORM({ typeFunctionOrTarget, inverseSideOrOptions: inverseSideProperty as InverseSide, options, propertyKey, target }))(target, propertyKey); + }; +} + +/** + * Maps TypeORM ManyToOne relation options to MikroORM options for MikroORM integration with TypeORM. + * + * @param param0 - Destructured parameters object. + * @returns MikroORMRelationOptions - The mapped MikroORM relation options. + */ +export function mapManyToOneArgsForMikroORM({ typeFunctionOrTarget, options, propertyKey }: MapManyToOneArgsForMikroORMOptions) { + // Cast options to RelationOptions + const typeOrmOptions = deepClone(options) as TypeOrmRelationOptions; + + // Initialize an array to store MikroORM cascade options + let mikroORMCascade: Cascade[] = []; + + // Check if TypeORM cascade options are provided + if (typeOrmOptions?.cascade) { + // Handle boolean cascade option + if (typeof typeOrmOptions.cascade === 'boolean') { + mikroORMCascade = typeOrmOptions.cascade ? [Cascade.ALL] : []; + } + + // Handle array cascade options + if (typeOrmOptions?.cascade instanceof Array) { + mikroORMCascade = typeOrmOptions.cascade.map((c) => { + switch (c) { + case 'insert': + return Cascade.PERSIST; + case 'update': + return Cascade.MERGE; + case 'remove': + return Cascade.REMOVE; + case 'soft-remove': + case 'recover': + return null; + default: + return null; + } + }).filter((c) => c) as Cascade[]; + } + } + + // Create MikroORM relation options + const mikroOrmOptions: Partial> = { + ...omit(options, 'onDelete', 'onUpdate') as any, + entity: typeFunctionOrTarget as (string | ((e?: any) => EntityName)), + cascade: mikroORMCascade, + deleteRule: typeOrmOptions?.onDelete?.toLocaleLowerCase(), + updateRule: typeOrmOptions?.onUpdate?.toLocaleLowerCase(), + ...(typeOrmOptions?.nullable ? { nullable: typeOrmOptions?.nullable } : {}), + ...(typeOrmOptions?.lazy ? { lazy: typeOrmOptions?.lazy } : {}), + }; + + // Set default joinColumn and referenceColumnName if not provided + if (!mikroOrmOptions.joinColumn && propertyKey) { + // Set default joinColumn if not overwrite in options + mikroOrmOptions.joinColumn = `${propertyKey}Id`; + mikroOrmOptions.referenceColumnName = `id`; + } + + // Return the mapped MikroORM relation options + return mikroOrmOptions as MikroORMRelationOptions +} diff --git a/packages/core/src/core/decorators/entity/relations/mikro-orm.ts b/packages/core/src/core/decorators/entity/relations/mikro-orm.ts index 3e9026392d1..cc771e638d1 100644 --- a/packages/core/src/core/decorators/entity/relations/mikro-orm.ts +++ b/packages/core/src/core/decorators/entity/relations/mikro-orm.ts @@ -1,6 +1,14 @@ import { ManyToOne, OneToMany, OneToOne, ManyToMany } from "@mikro-orm/core"; -export const MikroManyToOne = ManyToOne; -export const MikroOneToMany = OneToMany; -export const MikroOneToOne = OneToOne; -export const MikroManyToMany = ManyToMany; +/** + * Mikro-ORM Decorator Aliases. + * + * This module provides aliases for commonly used Mikro-ORM decorators. + * It simplifies import statements and enhances code readability. + */ +export { + ManyToOne as MikroOrmManyToOne, + OneToMany as MikroOrmOneToMany, + OneToOne as MikroOrmOneToOne, + ManyToMany as MikroOrmManyToMany, +}; diff --git a/packages/core/src/core/decorators/entity/relations/one-to-many.decorator.ts b/packages/core/src/core/decorators/entity/relations/one-to-many.decorator.ts new file mode 100644 index 00000000000..d9150a9efa7 --- /dev/null +++ b/packages/core/src/core/decorators/entity/relations/one-to-many.decorator.ts @@ -0,0 +1,119 @@ +import { Cascade, EntityName, OneToManyOptions } from '@mikro-orm/core'; +import { RelationOptions as TypeOrmRelationOptions } from 'typeorm'; +import { omit } from 'underscore'; +import { deepClone } from '@gauzy/common'; +import { ObjectUtils } from '../../../../core/util/object-utils'; +import { TypeOrmOneToMany } from './type-orm'; +import { MikroOrmOneToMany } from './mikro-orm'; +import { MikroORMInverseSide, TypeORMInverseSide, TypeORMRelationOptions, TypeORMTarget } from './shared-types'; + +/** + * Options for mapping One-to-Many relationship arguments for MikroORM. + */ +export interface MapOneToManyArgsForMikroORMOptions { + // Type or target function for the related entity + typeFunctionOrTarget: TargetEntity; + // Inverse side of the relationship + inverseSide?: InverseSide; + // Additional options for the One-to-Many relationship + options?: RelationOptions; +} + +type MikroORMTarget = OneToManyOptions | string | ((e?: any) => EntityName); +type MikroORMRelationOptions = Omit>, 'cascade'>; + +type TargetEntity = TypeORMTarget | MikroORMTarget; +type InverseSide = TypeORMInverseSide & MikroORMInverseSide; +type RelationOptions = MikroORMRelationOptions & TypeORMRelationOptions & { + cascade?: Cascade[] | (boolean | ("update" | "insert" | "remove" | "soft-remove" | "recover")[]); +}; + +/** + * Decorator for defining One-to-Many relationships that works with both MikroORM and TypeORM. + * + * @param typeFunctionOrTarget - Type or target function for the related entity. + * @param inverseSide - Inverse side of the relationship or additional options (if options is provided first). + * @param options - Additional options for the One-to-Many relationship. + * @returns PropertyDecorator. + */ +export function MultiORMOneToMany( + typeFunctionOrTarget: TargetEntity, + inverseSide?: InverseSide | RelationOptions, + options?: RelationOptions +): PropertyDecorator { + // Normalize parameters. + let inverseSideProperty: InverseSide; + + if (ObjectUtils.isObject(inverseSide)) { + options = >inverseSide; + } else { + inverseSideProperty = inverseSide as any; + } + + // The decorator function applied to the target property + return (target: any, propertyKey: string) => { + // If options are not provided, initialize an empty object + if (!options) options = {} as RelationOptions; + + // Apply TypeORM One-to-Many decorator + TypeOrmOneToMany(typeFunctionOrTarget as TypeORMTarget, inverseSideProperty as TypeORMInverseSide, options as TypeORMRelationOptions)(target, propertyKey); + + // Apply MikroORM One-to-Many decorator + MikroOrmOneToMany(mapOneToManyArgsForMikroORM({ typeFunctionOrTarget, inverseSide: inverseSideProperty as InverseSide, options }))(target, propertyKey); + }; +} + +/** + * Maps TypeORM OneToMany relation options to MikroORM options for MikroORM integration with TypeORM. + * + * @param typeFunctionOrTarget - Type or target function for the related entity. + * @param inverseSide - Inverse side of the relationship. + * @param options - Additional options for the One-to-Many relationship. + * @returns MikroORM-specific One-to-Many relationship options. + */ +function mapOneToManyArgsForMikroORM({ typeFunctionOrTarget, inverseSide, options }: MapOneToManyArgsForMikroORMOptions) { + // Cast options to RelationOptions + const typeOrmOptions = deepClone(options) as TypeOrmRelationOptions; + + // Initialize an array to store MikroORM cascade options + let mikroORMCascade: Cascade[] = []; + + // Check if TypeORM cascade options are provided + if (typeOrmOptions?.cascade) { + // Handle boolean cascade option + if (typeof typeOrmOptions.cascade === 'boolean') { + mikroORMCascade = typeOrmOptions.cascade ? [Cascade.ALL] : []; + } + + // Handle array cascade options + if (typeOrmOptions?.cascade instanceof Array) { + mikroORMCascade = typeOrmOptions.cascade.map((c) => { + switch (c) { + case 'insert': + return Cascade.PERSIST; + case 'update': + return Cascade.MERGE; + case 'remove': + return Cascade.REMOVE; + case 'soft-remove': + case 'recover': + return null; + default: + return null; + } + }).filter((c) => c) as Cascade[]; + } + } + + // Create MikroORM relation options + const mikroOrmOptions: Partial> = { + ...omit(options, 'onDelete', 'onUpdate') as Partial>, + entity: typeFunctionOrTarget as (string | ((e?: any) => EntityName)), + mappedBy: inverseSide, + cascade: mikroORMCascade, + ...(typeOrmOptions?.nullable ? { nullable: typeOrmOptions?.nullable } : {}), + ...(typeOrmOptions?.lazy ? { lazy: typeOrmOptions?.lazy } : {}), + }; + + return mikroOrmOptions as OneToManyOptions; +} diff --git a/packages/core/src/core/decorators/entity/relations/one-to-one.decorator.ts b/packages/core/src/core/decorators/entity/relations/one-to-one.decorator.ts new file mode 100644 index 00000000000..d85fd99d94a --- /dev/null +++ b/packages/core/src/core/decorators/entity/relations/one-to-one.decorator.ts @@ -0,0 +1,140 @@ +import { deepClone } from "@gauzy/common"; +import { Cascade, EntityName, OneToOneOptions } from "@mikro-orm/core"; +import { RelationOptions as TypeOrmRelationOptions } from 'typeorm'; +import { omit } from "underscore"; +import { MultiORMEnum } from "../../../../core/utils"; +import { ObjectUtils } from "../../../../core/util/object-utils"; +import { MikroORMInverseSide, TypeORMInverseSide, TypeORMRelationOptions, TypeORMTarget } from "./shared-types"; +import { TypeOrmOneToOne } from "./type-orm"; +import { MikroOrmOneToOne } from "./mikro-orm"; + +/** + * Options for mapping OneToOne relationship arguments for MikroORM. + * + * @template T - The type of the target entity. + * @template O - The type of additional options. + */ +export interface MapOneToOneArgsForMikroORMOptions { + // The target entity class or function returning the target entity class. + typeFunctionOrTarget: TargetEntity; + // The inverse side of the relationship or additional options if provided. + inverseSideOrOptions?: InverseSide; + // The options for the OneToOne relationship. + options?: RelationOptions; + // The property key of the target entity. + propertyKey?: string; + // The target string (optional). + target?: string; +} + +type MikroORMTarget = OneToOneOptions | string | ((e?: any) => EntityName); +type MikroORMRelationOptions = Omit>, 'cascade'>; + +type TargetEntity = TypeORMTarget | MikroORMTarget; +type InverseSide = TypeORMInverseSide & MikroORMInverseSide; +type RelationOptions = MikroORMRelationOptions & TypeORMRelationOptions & { + cascade?: Cascade[] | (boolean | ("update" | "insert" | "remove" | "soft-remove" | "recover")[]); +}; + +/** + * Decorator for defining One-to-One relationships in both TypeORM and MikroORM. + * + * @param typeFunctionOrTarget - Type or target function for the related entity. + * @param inverseSideOrOptions - Inverse side of the relationship or additional options. + * @param options - Additional options for the One-to-One relationship. + * @returns PropertyDecorator + */ +export function MultiORMOneToOne( + typeFunctionOrTarget: TargetEntity, + inverseSideOrOptions?: InverseSide | RelationOptions, + options?: RelationOptions +): PropertyDecorator { + // Normalize parameters. + let inverseSideProperty: InverseSide; + + if (ObjectUtils.isObject(inverseSideOrOptions)) { + options = >inverseSideOrOptions; + } else { + inverseSideProperty = inverseSideOrOptions as any; + } + + return (target: any, propertyKey: string) => { + // If options are not provided, initialize an empty object + if (!options) options = {} as RelationOptions; + + // Use TypeORM decorator for One-to-One + TypeOrmOneToOne(typeFunctionOrTarget as TypeORMTarget, inverseSideOrOptions as TypeORMInverseSide, options as TypeORMRelationOptions)(target, propertyKey); + + // Use MikroORM decorator for One-to-One + MikroOrmOneToOne(mapOneToOneArgsForMikroORM({ typeFunctionOrTarget, inverseSideOrOptions: inverseSideProperty as InverseSide, options, propertyKey }))(target, propertyKey); + }; +} + +/** + * Maps TypeORM OneToOne relation options to MikroORM options for MikroORM integration with TypeORM. + * + * @param param0 - Destructured parameters object. + * @returns MikroORMRelationOptions - The mapped MikroORM relation options. + */ +export function mapOneToOneArgsForMikroORM({ typeFunctionOrTarget, inverseSideOrOptions, options, propertyKey }: MapOneToOneArgsForMikroORMOptions) { + // Cast options to RelationOptions + const typeOrmOptions = deepClone(options) as TypeOrmRelationOptions; + + // Initialize an array to store MikroORM cascade options + let mikroORMCascade: Cascade[] = []; + + // Check if TypeORM cascade options are provided + if (typeOrmOptions?.cascade) { + // Handle boolean cascade option + if (typeof typeOrmOptions.cascade === 'boolean') { + mikroORMCascade = typeOrmOptions.cascade ? [Cascade.ALL] : []; + } + + // Handle array cascade options + if (typeOrmOptions?.cascade instanceof Array) { + mikroORMCascade = typeOrmOptions.cascade.map((c) => { + switch (c) { + case 'insert': + return Cascade.PERSIST; + case 'update': + return Cascade.MERGE; + case 'remove': + return Cascade.REMOVE; + case 'soft-remove': + case 'recover': + return null; + default: + return null; + } + }).filter((c) => c) as Cascade[]; + } + } + + // Create MikroORM relation options + const mikroOrmOptions: Partial> = { + ...omit(options, 'onDelete', 'onUpdate') as Partial>, + entity: typeFunctionOrTarget as (string | ((e?: any) => EntityName)), + cascade: mikroORMCascade, + deleteRule: typeOrmOptions?.onDelete?.toLocaleLowerCase(), + updateRule: typeOrmOptions?.onUpdate?.toLocaleLowerCase(), + ...(typeOrmOptions?.nullable ? { nullable: typeOrmOptions?.nullable } : {}), + ...(typeOrmOptions?.lazy ? { lazy: typeOrmOptions?.lazy } : {}), + }; + + // Set default joinColumn if not overwritten in options + if (mikroOrmOptions.owner === true && !mikroOrmOptions.joinColumn && propertyKey) { + mikroOrmOptions.joinColumn = `${propertyKey}Id`; + mikroOrmOptions.referenceColumnName = `id`; + } + + // Map inverseSideOrOptions based on the DB_ORM environment variable + if (process.env.DB_ORM == MultiORMEnum.MikroORM) { + if (mikroOrmOptions.owner === true) { + mikroOrmOptions.inversedBy = inverseSideOrOptions; + } else { + mikroOrmOptions.mappedBy = inverseSideOrOptions; + } + } + + return mikroOrmOptions as MikroORMRelationOptions; +} diff --git a/packages/core/src/core/decorators/entity/relations/shared-types.ts b/packages/core/src/core/decorators/entity/relations/shared-types.ts new file mode 100644 index 00000000000..643a2ce77f6 --- /dev/null +++ b/packages/core/src/core/decorators/entity/relations/shared-types.ts @@ -0,0 +1,20 @@ +import { ObjectType, RelationOptions as TypeOrmRelationOptions } from 'typeorm'; + +// Type definition for the TypeORM target. +export type TypeORMTarget = string | ((type?: any) => ObjectType); + +// Type definition for the TypeORM inverse side. +export type TypeORMInverseSide = string | ((object: T) => any); + +// Type definition for the TypeORM relation options, excluding cascade. +export type TypeORMRelationOptions = Omit; + +/** + * Represents the inverse side of a relationship in MikroORM. + * + * This type allows specifying the inverse side as either a string (representing a property name) + * or a function that takes an object of type `T` and returns the value of the inverse side. + * + * @typeparam T - The type of the entity that defines the relationship. + */ +export type MikroORMInverseSide = (string & keyof T) | ((object: T) => any); diff --git a/packages/core/src/core/decorators/entity/relations/type-orm.ts b/packages/core/src/core/decorators/entity/relations/type-orm.ts index 591df38c872..6ca67c37d31 100644 --- a/packages/core/src/core/decorators/entity/relations/type-orm.ts +++ b/packages/core/src/core/decorators/entity/relations/type-orm.ts @@ -1,6 +1,14 @@ import { ManyToOne, OneToMany, OneToOne, ManyToMany } from "typeorm"; -export const TypeManyToOne = ManyToOne; -export const TypeOneToMany = OneToMany; -export const TypeOneToOne = OneToOne; -export const TypeManyToMany = ManyToMany; +/** + * TypeORM Decorator Aliases. + * + * This module provides aliases for commonly used TypeORM decorators. + * It simplifies import statements and enhances code readability. + */ +export { + ManyToOne as TypeOrmManyToOne, + OneToMany as TypeOrmOneToMany, + OneToOne as TypeOrmOneToOne, + ManyToMany as TypeOrmManyToMany, +}; diff --git a/packages/core/src/core/entities/base.entity.ts b/packages/core/src/core/entities/base.entity.ts index e2c0184b8fe..0049378e82c 100644 --- a/packages/core/src/core/entities/base.entity.ts +++ b/packages/core/src/core/entities/base.entity.ts @@ -6,7 +6,6 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, CreateDateColumn, - Column, Index, DeleteDateColumn } from 'typeorm'; @@ -14,6 +13,7 @@ import { ApiPropertyOptional } from '@nestjs/swagger'; import { BaseEntityModel as IBaseEntityModel } from '@gauzy/contracts'; import { IsBoolean, IsDateString, IsOptional } from 'class-validator'; import { PrimaryKey, Property } from '@mikro-orm/core'; +import { MultiORMColumn } from '../decorators/entity'; export abstract class Model { constructor(input?: any) { @@ -26,8 +26,8 @@ export abstract class Model { } export abstract class BaseEntity extends Model implements IBaseEntityModel { - @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' }) @ApiPropertyOptional({ type: () => String }) + @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' }) @PrimaryGeneratedColumn('uuid') id?: string; @@ -68,8 +68,7 @@ export abstract class BaseEntity extends Model implements IBaseEntityModel { @IsOptional() @IsBoolean() @Index() - @Column({ nullable: true, default: true }) - @Property({ nullable: true, default: true }) + @MultiORMColumn({ nullable: true, default: true }) isActive?: boolean; // Indicate if record is archived @@ -77,7 +76,6 @@ export abstract class BaseEntity extends Model implements IBaseEntityModel { @IsOptional() @IsBoolean() @Index() - @Column({ nullable: true, default: false }) - @Property({ nullable: true, default: false }) + @MultiORMColumn({ nullable: true, default: false }) isArchived?: boolean; } diff --git a/packages/core/src/core/entities/internal.ts b/packages/core/src/core/entities/internal.ts index b0f296a1e59..6394c9f650f 100644 --- a/packages/core/src/core/entities/internal.ts +++ b/packages/core/src/core/entities/internal.ts @@ -151,39 +151,40 @@ export * from '../../warehouse/warehouse-product.entity'; export * from '../../warehouse/warehouse.entity'; //core subscribers -export * from './../../candidate/candidate.subscriber'; -export * from './../../custom-smtp/custom-smtp.subscriber'; -export * from './../../email-reset/email-reset.subscriber'; -export * from './../../email-template/email-template.subscriber'; -export * from './../../employee/employee.subscriber'; -export * from './../../export-import/import-history/import-history.subscriber'; -export * from './../../feature/feature.subscriber'; -export * from './../../image-asset/image-asset.subscriber'; -export * from './../../invite/invite.subscriber'; -export * from './../../invoice/invoice.subscriber'; -export * from './../../organization-contact/organization-contact.subscriber'; -export * from './../../organization-document/organization-document.subscriber'; -export * from './../../organization-project/organization-project.subscriber'; -export * from './../../organization-team-employee/organization-team-employee.subscriber'; -export * from './../../organization-team-join-request/organization-team-join-request.subscriber'; -export * from './../../organization-team/organization-team.subscriber'; -export * from './../../organization/organization.subscriber'; -export * from './../../payment/payment.subscriber'; -export * from './../../product-category/product-category.subscriber'; -export * from './../../reports/report.subscriber'; -export * from './../../tags/tag.subscriber'; -export * from './../../tasks/issue-type/issue-type.subscriber'; -export * from './../../tasks/priorities/priority.subscriber'; -export * from './../../tasks/sizes/size.subscriber'; -export * from './../../tasks/related-issue-type/related-issue-type.subscriber'; -export * from './../../tasks/statuses/status.subscriber'; -export * from './../../tasks/versions/version.subscriber'; -export * from './../../tasks/task.subscriber'; -export * from './../../tenant/tenant.subscriber'; -export * from './../../time-off-request/time-off-request.subscriber'; -export * from './../../time-tracking/activity/activity.subscriber'; -export * from './../../time-tracking/screenshot/screenshot.subscriber'; -export * from './../../time-tracking/time-slot/time-slot.subscriber'; -export * from './../../user/user.subscriber'; -export * from './../../integration/integration.subscriber'; -export * from './../../integration-setting/integration-setting.subscriber'; +export * from '../../candidate/candidate.subscriber'; +export * from '../../custom-smtp/custom-smtp.subscriber'; +export * from '../../email-reset/email-reset.subscriber'; +export * from '../../email-template/email-template.subscriber'; +export * from '../../employee/employee.subscriber'; +export * from '../../export-import/import-history/import-history.subscriber'; +export * from '../../feature/feature.subscriber'; +export * from '../../image-asset/image-asset.subscriber'; +export * from '../../integration-setting/integration-setting.subscriber'; +export * from '../../integration/integration.subscriber'; +export * from '../../invite/invite.subscriber'; +export * from '../../invoice/invoice.subscriber'; +export * from '../../organization-contact/organization-contact.subscriber'; +export * from '../../organization-document/organization-document.subscriber'; +export * from '../../organization-project/organization-project.subscriber'; +export * from '../../organization-team-employee/organization-team-employee.subscriber'; +export * from '../../organization-team-join-request/organization-team-join-request.subscriber'; +export * from '../../organization-team/organization-team.subscriber'; +export * from '../../organization/organization.subscriber'; +export * from '../../payment/payment.subscriber'; +export * from '../../pipeline/pipeline.subscriber'; +export * from '../../product-category/product-category.subscriber'; +export * from '../../reports/report.subscriber'; +export * from '../../tags/tag.subscriber'; +export * from '../../tasks/issue-type/issue-type.subscriber'; +export * from '../../tasks/priorities/priority.subscriber'; +export * from '../../tasks/related-issue-type/related-issue-type.subscriber'; +export * from '../../tasks/sizes/size.subscriber'; +export * from '../../tasks/statuses/status.subscriber'; +export * from '../../tasks/task.subscriber'; +export * from '../../tasks/versions/version.subscriber'; +export * from '../../tenant/tenant.subscriber'; +export * from '../../time-off-request/time-off-request.subscriber'; +export * from '../../time-tracking/activity/activity.subscriber'; +export * from '../../time-tracking/screenshot/screenshot.subscriber'; +export * from '../../time-tracking/time-slot/time-slot.subscriber'; +export * from '../../user/user.subscriber'; diff --git a/packages/core/src/core/entities/subscribers/base-entity-event.subscriber.ts b/packages/core/src/core/entities/subscribers/base-entity-event.subscriber.ts new file mode 100644 index 00000000000..55a1774425b --- /dev/null +++ b/packages/core/src/core/entities/subscribers/base-entity-event.subscriber.ts @@ -0,0 +1,75 @@ +import { EntityName } from '@mikro-orm/core'; +import { EntityEventSubscriber } from './entity-event.subsciber'; +import { IEntityEventSubscriber } from './entity-event-subscriber.types'; + +/** + * An abstract class that provides a base implementation for IEntityEventSubscriber. + * This class can be extended to create specific event subscribers for different entities. + */ +export abstract class BaseEntityEventSubscriber extends EntityEventSubscriber implements IEntityEventSubscriber { + + /** + * An optional method that can be implemented by subclasses. + * It should return either a constructor function (a class) or a string + * representing the name of the entity to which this subscriber will listen. + * The default implementation returns undefined. + * + * @returns {Function | string | undefined} The entity class or its name, or undefined. + */ + listenTo(): Function | string | undefined { + return; + } + + /** + * Returns the array of entities this subscriber is subscribed to. + * If listenTo is not defined, it returns an empty array. + * + * @returns {EntityName[]} An array containing the entities to which this subscriber listens. + */ + getSubscribedEntities(): EntityName[] { + if (this.listenTo()) { + return [this.listenTo()]; + } + return []; + } + + /** + * Called before a new entity is persisted. Override in subclasses to define custom pre-creation logic. + * + * @param entity The entity about to be created. + * @returns {Promise} + */ + async beforeEntityCreate(entity: Entity): Promise { + // Default empty implementation + } + + /** + * Invoked before an entity update. Use in subclasses for specific update preparation. + * + * @param entity The entity being updated. + * @returns {Promise} + */ + async beforeEntityUpdate(entity: Entity): Promise { + // Default empty implementation + } + + /** + * Executed after an entity is created. Subclasses can override for post-creation actions. + * + * @param entity The newly created entity. + * @returns {Promise} + */ + async afterEntityCreate(entity: Entity): Promise { + // Default empty implementation + } + + /** + * Called following the loading of an entity. Ideal for post-load processing in subclasses. + * + * @param entity The entity that was loaded. + * @returns {Promise} + */ + async afterEntityLoad(entity: Entity): Promise { + // Default empty implementation + } +} diff --git a/packages/core/src/core/entities/subscribers/entity-event-subscriber.types.ts b/packages/core/src/core/entities/subscribers/entity-event-subscriber.types.ts new file mode 100644 index 00000000000..444ddf0cf89 --- /dev/null +++ b/packages/core/src/core/entities/subscribers/entity-event-subscriber.types.ts @@ -0,0 +1,54 @@ +/** + * + */ +export interface IEntityEventSubscriber { + /** + * Optional method to specify the entity class that this subscriber listens to. + * It should return either a constructor function (a class) or a string + * representing the name of the entity. + * + * @returns {Function | string} The entity class or its name. + */ + listenTo?(): Function | string; + + /** + * Optional method that is called before an entity is created. + * Implement this method to define specific logic to be executed + * just before the creation of an entity + * + * + * @param entity The entity that is about to be created. + * @returns {Promise} + */ + beforeEntityCreate(entity: Entity): Promise; + + /** + * Optional method that is called before an entity is updated. + * Implement this method to define specific logic to be executed + * just before the updation of an entity + * + * @param entity The entity that is about to be updated. + * @returns {Promise} + */ + beforeEntityUpdate(entity: Entity): Promise; + + /** + * Optional method that is called after an entity is created. + * Implement this method to define specific logic to be executed + * after an entity creation event. + * + * @param entity The entity that was created. + * @returns {Promise} + */ + afterEntityCreate(entity: Entity): Promise; + + /** + * Optional method that is called after an entity is loaded. + * Implement this method to define specific logic to be executed + * after an entity loading event. + * + * @param entity The entity that was loaded. + * @returns {Promise} + */ + afterEntityLoad(entity: Entity): Promise; +} diff --git a/packages/core/src/core/entities/subscribers/entity-event.subsciber.ts b/packages/core/src/core/entities/subscribers/entity-event.subsciber.ts new file mode 100644 index 00000000000..20b54aec59c --- /dev/null +++ b/packages/core/src/core/entities/subscribers/entity-event.subsciber.ts @@ -0,0 +1,156 @@ +import { EventArgs, EventSubscriber as MikroEntitySubscriberInterface } from '@mikro-orm/core'; +import { MultiORM, MultiORMEnum, getORMType } from '../../../core/utils'; +import { InsertEvent, LoadEvent, EntitySubscriberInterface as TypeOrmEntitySubscriberInterface, UpdateEvent } from 'typeorm'; + +// Get the type of the Object-Relational Mapping (ORM) used in the application. +const ormType: MultiORM = getORMType(); + +/** + * Implements event handling for entity creation. + * This class should be extended or integrated into your ORM event subscriber. + */ +export abstract class EntityEventSubscriber implements MikroEntitySubscriberInterface, TypeOrmEntitySubscriberInterface { + + /** + * Handles the event before an entity is created in MikroORM. + * + * @param args The event arguments provided by MikroORM. + * @returns {Promise} - Can perform asynchronous operations. + */ + async beforeCreate(args: EventArgs): Promise { + try { + await this.beforeEntityCreate(args.entity); + } catch (error) { + console.error("Error in beforeCreate:", error); + } + } + + /** + * Handles the event before an entity is inserted in TypeORM. + * + * @param event The insert event provided by TypeORM. + * @returns {Promise} - Can perform asynchronous operations. + */ + async beforeInsert(event: InsertEvent): Promise { + try { + await this.beforeEntityCreate(event.entity); + } catch (error) { + console.error("Error in beforeInsert:", error); + } + } + + /** + * Abstract method for pre-creation logic of an entity. Implement in subclasses for custom actions. + * + * @param entity The entity that is about to be updated. + * @returns {Promise} + */ + protected abstract beforeEntityCreate(entity: Entity): Promise; + + /** + * Handles the 'before update' event for both MikroORM and TypeORM entities. It determines the + * type of ORM being used and appropriately casts the event to either EventArgs or UpdateEvent. + * + * @param event The event object which can be either EventArgs from MikroORM or UpdateEvent from TypeORM. + * @returns {Promise} A promise that resolves when the pre-update process is complete. Any errors during processing are caught and logged. + */ + async beforeUpdate(event: EventArgs | UpdateEvent): Promise { + try { + let entity: Entity; + switch (ormType) { + case MultiORMEnum.MikroORM: + entity = (event as EventArgs).entity; + break; + case MultiORMEnum.TypeORM: + entity = (event as UpdateEvent).entity as Entity; + break; + default: + throw new Error(`Unsupported ORM type: ${ormType}`); + } + await this.beforeEntityUpdate(entity); + } catch (error) { + console.error("Error in beforeUpdate:", error); + } + } + + /** + * Abstract method for actions before updating an entity. Override in subclasses for specific logic. + * + * @param entity The entity that is about to be updated. + * @returns {Promise} + */ + protected abstract beforeEntityUpdate(entity: Entity): Promise; + + /** + * Handles the event after an entity has been created in MikroORM. + * + * @param args - The event arguments provided by MikroORM. + * @returns {Promise} - Can perform asynchronous operations. + */ + async afterCreate(args: EventArgs): Promise { + try { + await this.afterEntityCreate(args.entity); + } catch (error) { + console.error("Error in afterCreate:", error); + } + } + + /** + * Handles the event after an entity has been inserted in TypeORM. + * + * @param event - The insert event provided by TypeORM. + * @returns {Promise} - Can perform asynchronous operations. + */ + async afterInsert(event: InsertEvent): Promise { + try { + await this.afterEntityCreate(event.entity); + } catch (error) { + console.error("Error in afterInsert:", error); + } + } + + /** + * Abstract method for post-creation actions on an entity. Override in subclasses to define behavior. + * + * @param entity The entity that is about to be created. + * @returns {Promise} + */ + protected abstract afterEntityCreate(entity: Entity): Promise; + + /** + * Invoked when an entity is loaded in TypeORM. + * + * @param entity The loaded entity. + * @param event The load event details, if available. + * @returns {void | Promise} Can perform asynchronous operations. + */ + async afterLoad(entity: Entity, event?: LoadEvent): Promise { + try { + await this.afterEntityLoad(entity); + } catch (error) { + console.error("Error in afterLoad:", error); + } + } + + /** + * Invoked when an entity is loaded in MikroORM. + * + * @param args The event arguments provided by MikroORM. + * @returns {void | Promise} Can perform asynchronous operations. + */ + async onLoad(args: EventArgs): Promise { + try { + await this.afterEntityLoad(args.entity); + } catch (error) { + console.error("Error in onLoad:", error); + } + } + + /** + * Abstract method for processing after an entity is loaded. Implement in subclasses for custom behavior. + * + * @param entity The entity that has been loaded. + * @returns {Promise} + */ + protected abstract afterEntityLoad(entity: Entity): Promise; +} diff --git a/packages/core/src/core/entities/subscribers.ts b/packages/core/src/core/entities/subscribers/index.ts similarity index 95% rename from packages/core/src/core/entities/subscribers.ts rename to packages/core/src/core/entities/subscribers/index.ts index c5d9f0f46bf..45961f934c6 100644 --- a/packages/core/src/core/entities/subscribers.ts +++ b/packages/core/src/core/entities/subscribers/index.ts @@ -21,6 +21,7 @@ import { OrganizationTeamJoinRequestSubscriber, OrganizationTeamSubscriber, PaymentSubscriber, + PipelineSubscriber, ProductCategorySubscriber, ReportSubscriber, ScreenshotSubscriber, @@ -34,8 +35,8 @@ import { TenantSubscriber, TimeOffRequestSubscriber, TimeSlotSubscriber, - UserSubscriber, -} from './internal'; + UserSubscriber +} from '../internal'; /** * A map of the core TypeORM Subscribers. @@ -63,6 +64,7 @@ export const coreSubscribers = [ OrganizationTeamJoinRequestSubscriber, OrganizationTeamSubscriber, PaymentSubscriber, + PipelineSubscriber, ProductCategorySubscriber, ReportSubscriber, ScreenshotSubscriber, diff --git a/packages/core/src/core/entities/tenant-base.entity.ts b/packages/core/src/core/entities/tenant-base.entity.ts index d5a9b73ae4e..e399be3cf7a 100644 --- a/packages/core/src/core/entities/tenant-base.entity.ts +++ b/packages/core/src/core/entities/tenant-base.entity.ts @@ -1,33 +1,29 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Column, Index, JoinColumn, ManyToOne, RelationId } from 'typeorm'; -import { Property } from '@mikro-orm/core'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Index, RelationId } from 'typeorm'; import { IsString, IsOptional } from 'class-validator'; import { IBasePerTenantEntityModel, ITenant } from '@gauzy/contracts'; import { BaseEntity, Tenant } from '../entities/internal'; -import { MikroManyToOne } from '../../core/decorators/entity/relations/mikro-orm'; +import { MultiORMManyToOne } from '../decorators/entity/relations'; +import { MultiORMColumn } from '../decorators'; export abstract class TenantBaseEntity extends BaseEntity implements IBasePerTenantEntityModel { - @ApiProperty({ type: () => Tenant }) - @ManyToOne(() => Tenant, { + @ApiPropertyOptional({ type: () => Tenant }) + @IsOptional() + @MultiORMManyToOne(() => Tenant, { + /** Indicates if relation column value can be nullable or not. */ nullable: true, + + /** Database cascade action on delete. */ onDelete: 'CASCADE' }) - @MikroManyToOne(() => Tenant, { - nullable: true, - deleteRule: 'cascade', - persist: false, - }) - @JoinColumn() - @IsOptional() tenant?: ITenant; @ApiProperty({ type: () => String }) - @RelationId((t: TenantBaseEntity) => t.tenant) @IsString() @IsOptional() + @RelationId((t: TenantBaseEntity) => t.tenant) @Index() - @Column({ nullable: true }) - @Property({ nullable: true, }) + @MultiORMColumn({ nullable: true, relationId: true }) tenantId?: ITenant['id']; } diff --git a/packages/core/src/core/entities/tenant-organization-base.entity.ts b/packages/core/src/core/entities/tenant-organization-base.entity.ts index 225f92d6029..7a32d60585d 100644 --- a/packages/core/src/core/entities/tenant-organization-base.entity.ts +++ b/packages/core/src/core/entities/tenant-organization-base.entity.ts @@ -1,38 +1,35 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Column, Index, JoinColumn, ManyToOne, RelationId } from 'typeorm'; -import { Property } from '@mikro-orm/core'; +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { Index, RelationId } from 'typeorm'; import { IsOptional, IsString } from 'class-validator'; import { IOrganization, IBasePerTenantAndOrganizationEntityModel } from '@gauzy/contracts'; import { Organization, TenantBaseEntity } from '../entities/internal'; -import { MikroManyToOne } from '../../core/decorators/entity/relations/mikro-orm'; +import { MultiORMManyToOne } from '../decorators/entity/relations'; +import { MultiORMColumn } from '../decorators'; export abstract class TenantOrganizationBaseEntity extends TenantBaseEntity implements IBasePerTenantAndOrganizationEntityModel { - @ApiProperty({ type: () => Organization }) - @ManyToOne(() => Organization, { + @ApiPropertyOptional({ type: () => Organization }) + @IsOptional() + @MultiORMManyToOne(() => Organization, { + /** Indicates if relation column value can be nullable or not. */ nullable: true, + + /** Database cascade action on update. */ onUpdate: 'CASCADE', + + /** Database cascade action on delete. */ onDelete: 'CASCADE' }) - @MikroManyToOne(() => Organization, { - nullable: true, - deleteRule: 'cascade', - updateRule: 'cascade', - persist: false - }) - @JoinColumn() - @IsOptional() organization?: IOrganization; - @ApiProperty({ type: () => String }) - @RelationId((it: TenantOrganizationBaseEntity) => it.organization) - @IsString() + @ApiPropertyOptional({ type: () => String }) @IsOptional() + @IsString() + @RelationId((it: TenantOrganizationBaseEntity) => it.organization) @Index() - @Column({ nullable: true }) - @Property({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationId?: IOrganization['id']; } diff --git a/packages/core/src/core/index.ts b/packages/core/src/core/index.ts index bec00cab986..70989e82437 100644 --- a/packages/core/src/core/index.ts +++ b/packages/core/src/core/index.ts @@ -5,4 +5,5 @@ export * from './context'; export * from './entities'; export * from './entities/internal'; export * from './decorators'; +export * from './orm-type'; export * from './plugin-common.module'; diff --git a/packages/core/src/core/interceptors/transform.interceptor.ts b/packages/core/src/core/interceptors/transform.interceptor.ts index c024c03a6ec..735857d711e 100644 --- a/packages/core/src/core/interceptors/transform.interceptor.ts +++ b/packages/core/src/core/interceptors/transform.interceptor.ts @@ -6,36 +6,38 @@ import { HttpException, BadRequestException } from '@nestjs/common'; -import { Observable, of as observableOf } from 'rxjs'; +import { Observable, of } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { instanceToPlain } from 'class-transformer'; @Injectable() export class TransformInterceptor implements NestInterceptor { - intercept( - ctx: ExecutionContext, - next: CallHandler - ): Observable { - return next - .handle() - .pipe( - map((data) => instanceToPlain(data)), - catchError((error: any) => { - if (error instanceof BadRequestException) { - return observableOf( - new BadRequestException( - error.getResponse() - ) - ); - } - return observableOf( - new HttpException( - error.message, - error.status - ) + /** + * Intercepts the execution context and the call handler. + * Transforms the data using class-transformer's instanceToPlain. + * Catches and handles errors, returning appropriate exceptions. + * @param ctx - The execution context. + * @param next - The call handler. + * @returns An observable that represents the intercepted response. + */ + intercept(ctx: ExecutionContext, next: CallHandler): Observable { + return next.handle().pipe( + // Transform the data using class-transformer's instanceToPlain + map((data) => instanceToPlain(data)), + // Catch and handle errors + catchError((error: any) => { + // If it's a BadRequestException, return a new instance of BadRequestException + if (error instanceof BadRequestException) { + return of( + new BadRequestException(error.getResponse()) ); - }) - ); + } + // For other errors, return a new instance of HttpException + return of( + new HttpException(error.message, error.status) + ); + }) + ); } } diff --git a/packages/core/src/core/orm-type.ts b/packages/core/src/core/orm-type.ts new file mode 100644 index 00000000000..c2d22ec5aca --- /dev/null +++ b/packages/core/src/core/orm-type.ts @@ -0,0 +1,4 @@ +import { Collection } from "@mikro-orm/core"; + + +export const MikroCollection = Collection; diff --git a/packages/core/src/core/orm/query-builder/iquery-builder.ts b/packages/core/src/core/orm/query-builder/iquery-builder.ts new file mode 100644 index 00000000000..41732c8e47a --- /dev/null +++ b/packages/core/src/core/orm/query-builder/iquery-builder.ts @@ -0,0 +1,36 @@ +import { EntityTarget, FindManyOptions, SelectQueryBuilder } from "typeorm"; + +export interface IQueryBuilder { + alias: string; + setFindOptions(findOptions: FindManyOptions): this; + select(selection: string, selectionAliasName?: string): this; + addSelect(selection: string, selectionAliasName?: string): this; + from(entityTarget: EntityTarget | ((qb: SelectQueryBuilder) => SelectQueryBuilder), aliasName?: string): this; + addFrom(entityTarget: EntityTarget | ((qb: SelectQueryBuilder) => SelectQueryBuilder), aliasName?: string): this; + innerJoin(relation: string, alias: string, condition?: string): this; + innerJoinAndSelect(relation: string, alias: string, condition?: string): this; + leftJoin(relation: string, alias: string, condition?: string): this; + leftJoinAndSelect(relation: string, alias: string, condition?: string): this; + where(condition: string | object, parameters?: object): this; + andWhere(condition: string | object, parameters?: object): this; + orWhere(condition: string | object, parameters?: object): this; + having(having: string, parameters?: object): this; + andHaving(having: string, parameters?: object): this; + orHaving(having: string, parameters?: object): this; + groupBy(groupBy: string): this; + addGroupBy(groupBy: string): this; + orderBy(sort: string, order?: "ASC" | "DESC", nulls?: "NULLS FIRST" | "NULLS LAST"): this; + addOrderBy(sort: string, order?: "ASC" | "DESC", nulls?: "NULLS FIRST" | "NULLS LAST"): this; + limit(limit?: number): this; + offset(offset?: number): this; + take(take?: number): this; + skip(skip?: number): this; + + getSql(): string + getCount(): Promise; + getRawMany(): Promise; + getMany(): Promise; + getOne(): Promise; + getRawOne(): Promise; + getManyAndCount(): Promise<[Entity[], number]>; +} diff --git a/packages/core/src/core/orm/query-builder/mikro-orm-query-builder.ts b/packages/core/src/core/orm/query-builder/mikro-orm-query-builder.ts new file mode 100644 index 00000000000..621c6e30795 --- /dev/null +++ b/packages/core/src/core/orm/query-builder/mikro-orm-query-builder.ts @@ -0,0 +1,290 @@ +import { EntityRepository, QueryBuilder, QueryOrder } from '@mikro-orm/knex'; +import { IQueryBuilder } from './iquery-builder'; +import { EntityTarget, FindManyOptions } from 'typeorm'; +import { convertTypeOrmConationAndParamsToMikroOrm } from '../utils'; + +export class MikroOrmQueryBuilder implements IQueryBuilder { + private qb: QueryBuilder; + + private orderCriteria: Record = {}; + + private groupByFields: string[] = []; + + private havingConditions: string[] = []; + private havingParameters: any[] = []; + + + get alias() { + return this.qb.alias; + } + + constructor( + private readonly repo: EntityRepository, + alias?: string + ) { + + + console.log('MikroOrmQueryBuilder', this.repo, this.repo.qb) + this.qb = this.repo.qb(alias); + // this.qb = this.repo.createQueryBuilder(alias) as QueryBuilder; + } + + setFindOptions(findOptions: FindManyOptions): this { + const { select, where, order, skip, take, relations } = findOptions; + + if (select) { + this.qb.select(select as any); + } + + if (where) { + this.qb.where(where); + } + + if (order) { + const orderBy = Object.entries(order).reduce((acc, [field, direction]) => { + acc[field] = direction === 'ASC' ? QueryOrder.ASC : QueryOrder.DESC; + return acc; + }, {}); + + this.qb.orderBy(orderBy); + } + + if (relations) { + this.qb.populate(relations as any); + } + + if (skip) { + this.qb.offset(skip); + } + + if (take) { + this.qb.limit(take); + } + + return this; + } + + select(selection: string, selectionAliasName?: string): this { + if (selectionAliasName) { + this.qb.select(`${selection} AS ${selectionAliasName}`); + } else { + this.qb.select(selection); + } + return this; + } + + addSelect(selection: string, selectionAliasName?: string): this { + if (selectionAliasName) { + this.qb.addSelect(`${selection} AS ${selectionAliasName}`); + } else { + this.qb.addSelect(selection); + } + return this + } + + from(entityTarget: ((qb: QueryBuilder) => QueryBuilder) | EntityTarget, aliasName?: string): this { + if (typeof entityTarget === 'function') { + // Handle subquery function + const subQueryFunction = entityTarget as any; + const subQuery = subQueryFunction(this.repo.createQueryBuilder()).getKnexQuery(); + this.qb = this.repo.createQueryBuilder().from(subQuery, aliasName); + } else { + // Handle direct entity + this.qb = this.repo.createQueryBuilder(aliasName) as QueryBuilder; + } + return this; + } + + addFrom(entityTarget: ((qb: QueryBuilder) => QueryBuilder) | EntityTarget, aliasName?: string): this { + throw new Error(`Note: This is conceptual; MikrORM typically don't support multiple FROMs like typeORM. You might use this for sub-queries or additional joins instead.`); + } + + innerJoin(propertyPath: string, alias: string, condition?: any): this { + this.qb.innerJoin(propertyPath, alias, condition); + return this; + } + + innerJoinAndSelect(propertyPath: string, alias: string, condition?: any): this { + this.qb.innerJoinAndSelect(propertyPath, alias, condition); + return this; + } + + leftJoin(propertyPath: string, alias: string, condition?: any): this { + this.qb.leftJoin(propertyPath, alias, condition); + return this; + } + + leftJoinAndSelect(propertyPath: string, alias: string, condition?: any): this { + this.qb.leftJoinAndSelect(propertyPath, alias, condition); + return this; + } + + + where(condition: string | object | ((qb: QueryBuilder) => QueryBuilder), parameters?: Record): this { + if (typeof condition === 'string') { + const [mikroOrmCondition, mikroOrmParameters] = convertTypeOrmConationAndParamsToMikroOrm(condition, parameters); + this.qb.where(mikroOrmCondition, mikroOrmParameters); + } else if (typeof condition === 'function') { + // Handle function-based subqueries + const subQuery = condition(this.repo.createQueryBuilder()); + this.qb.where(subQuery); + } else if (typeof condition === 'object') { + // Handle object conditions + this.qb.where(condition); // Mikro-ORM handles object conditions directly + } + return this; + } + + andWhere(condition: string | object | ((qb: QueryBuilder) => QueryBuilder), parameters?: Record): this { + if (typeof condition === 'string') { + const [mikroOrmCondition, mikroOrmParameters] = convertTypeOrmConationAndParamsToMikroOrm(condition, parameters); + this.qb.andWhere(mikroOrmCondition, mikroOrmParameters); + } else if (typeof condition === 'function') { + // Handle function-based subqueries + const subQuery = condition(this.repo.createQueryBuilder()); + this.qb.andWhere(subQuery); + } else if (typeof condition === 'object') { + // Handle object conditions + this.qb.andWhere(condition); // Mikro-ORM handles object conditions directly + } + return this; + } + + orWhere(condition: string | object | ((qb: QueryBuilder) => QueryBuilder), parameters?: Record): this { + if (typeof condition === 'string') { + const [mikroOrmCondition, mikroOrmParameters] = convertTypeOrmConationAndParamsToMikroOrm(condition, parameters); + this.qb.orWhere(mikroOrmCondition, mikroOrmParameters); + } else if (typeof condition === 'function') { + // Handle function-based subqueries + const subQuery = condition(this.repo.createQueryBuilder()); + this.qb.orWhere(subQuery); + } else if (typeof condition === 'object') { + // Handle object conditions + this.qb.orWhere(condition); // Mikro-ORM handles object conditions directly + } + return this; + } + + having(condition: string, parameters?: any[]): this { + this.havingConditions = [condition]; // Reset the having conditions + if (parameters) { + this.havingParameters.push(...parameters); + } + this.applyHavingConditions(); + return this; + } + + andHaving(condition: string, parameters?: any[]): this { + this.havingConditions.push(condition); // Add to existing having conditions + if (parameters) { + this.havingParameters.push(...parameters); + } + this.applyHavingConditions(); + return this; + } + + orHaving(condition: string, parameters?: any[]): this { + // Mikro-ORM does not directly support 'orHaving', so we need to handle it manually. + // We achieve this by wrapping existing conditions in parentheses and adding the new condition with 'OR'. + if (this.havingConditions.length > 0) { + const existingConditions = `(${this.havingConditions.join(' AND ')})`; + this.havingConditions = [existingConditions, `(${condition})`]; // Combine with OR + } else { + this.havingConditions = [`(${condition})`]; // Just in case orHaving is called first + } + if (parameters) { + this.havingParameters.push(...parameters); + } + this.applyHavingConditions(); + return this; + } + + private applyHavingConditions(): void { + if (this.havingConditions.length > 0) { + // Join all having conditions with AND and apply them to the query builder + const combinedConditions = this.havingConditions.join(' AND '); + this.qb.having(combinedConditions, ...this.havingParameters); + } + } + + groupBy(field: string): this { + this.groupByFields = [field]; // Reset and set new group by + this.qb.groupBy(field); + return this + } + + addGroupBy(field: string): this { + if (!this.groupByFields.includes(field)) { + this.groupByFields.push(field); + // Apply all group by fields again + this.qb.groupBy(this.groupByFields.join(', ')); + } + return this; + } + + orderBy(field: string, order: 'ASC' | 'DESC' = 'ASC'): this { + this.orderCriteria = { [field]: order }; // Reset and set new order criteria + this.qb.orderBy(this.orderCriteria); + return this; + } + + addOrderBy(field: string, order: 'ASC' | 'DESC' = 'ASC'): this { + this.orderCriteria[field] = order; // Add to existing order criteria + this.qb.orderBy(this.orderCriteria); // Re-apply all order criteria + return this; + } + + + limit(limit?: number): this { + this.qb.limit(limit); + return this; + } + + offset(offset?: number): this { + this.qb.offset(offset); + return this; + } + + take(take?: number): this { + this.qb.limit(take); + return this; + } + + skip(skip?: number): this { + this.qb.offset(skip); + return this; + } + + getSql(): string { + return this.qb.getQuery(); + } + + getCount(): Promise { + return this.qb.getCount(); + } + + getRawMany(): Promise { + return this.qb.execute('all', false); + } + + getMany(): Promise { + return this.qb.execute('all', true); + } + + getOne(): Promise { + return this.qb.execute('get', true); + } + + getRawOne(): Promise { + return this.qb.execute('get', false); + } + + async getManyAndCount(): Promise<[Entity[], number]> { + console.log(this.qb.getResultAndCount); + const count = await this.qb.limit(0).getCount(); + const items = await this.qb.execute('all', true); + return [items, count] + } + + +} diff --git a/packages/core/src/core/orm/query-builder/query-builder.factory.ts b/packages/core/src/core/orm/query-builder/query-builder.factory.ts new file mode 100644 index 00000000000..987650ef71e --- /dev/null +++ b/packages/core/src/core/orm/query-builder/query-builder.factory.ts @@ -0,0 +1,30 @@ + +import { Repository } from 'typeorm'; +import { EntityRepository } from '@mikro-orm/knex'; +import { IQueryBuilder } from './iquery-builder'; +import { TypeOrmQueryBuilder } from './typeorm-query-builder'; +import { MikroOrmQueryBuilder } from './mikro-orm-query-builder'; +import { MultiORMEnum } from 'core/utils'; + +export function createQueryBuilder(repository: Repository | EntityRepository): IQueryBuilder { + if (repository instanceof Repository) { + return new TypeOrmQueryBuilder(repository); + } else if (repository instanceof EntityRepository) { + return new MikroOrmQueryBuilder(repository); + } + throw new Error('Unsupported repository orm-type'); +} + + +export function multiORMCreateQueryBuilder(repository: Repository | EntityRepository, ormType: MultiORMEnum = MultiORMEnum.TypeORM): IQueryBuilder { + switch (ormType) { + case MultiORMEnum.MikroORM: + return new MikroOrmQueryBuilder(repository as EntityRepository); + + case MultiORMEnum.TypeORM: + return new MikroOrmQueryBuilder(repository as EntityRepository); + + default: + throw new Error(`Unsupported orm type "${ormType}"`); + } +} diff --git a/packages/core/src/core/orm/query-builder/typeorm-query-builder.ts b/packages/core/src/core/orm/query-builder/typeorm-query-builder.ts new file mode 100644 index 00000000000..475adc21ba5 --- /dev/null +++ b/packages/core/src/core/orm/query-builder/typeorm-query-builder.ts @@ -0,0 +1,161 @@ + +import { EntityTarget, FindManyOptions, Repository, SelectQueryBuilder } from 'typeorm'; +import { IQueryBuilder } from './iquery-builder'; + +export class TypeOrmQueryBuilder implements IQueryBuilder { + + private qb: SelectQueryBuilder; + + get alias() { + return this.qb.alias; + } + + constructor(private readonly repo: Repository) { + this.qb = this.repo.createQueryBuilder(); + } + + setFindOptions(findOptions: FindManyOptions) { + this.qb.setFindOptions(findOptions); + return this; + } + + select(selection: string, selectionAliasName?: string): this { + this.qb.select(selection, selectionAliasName); + return this; + } + + addSelect(selection: string, selectionAliasName?: string): this { + this.qb.addSelect(selection, selectionAliasName); + return this; + } + + from(entityTarget: EntityTarget | ((qb: SelectQueryBuilder) => SelectQueryBuilder), aliasName?: string): this { + this.qb.from(entityTarget, aliasName); + return this; + } + + addFrom(entityTarget: EntityTarget | ((qb: SelectQueryBuilder) => SelectQueryBuilder), aliasName?: string): this { + this.qb.addFrom(entityTarget, aliasName); + return this; + } + + innerJoin(relation: string, alias: string, condition?: string): this { + this.qb.innerJoin(relation, alias, condition); + return this; + } + + innerJoinAndSelect(relation: string, alias: string, condition?: string): this { + this.qb.innerJoinAndSelect(relation, alias, condition); + return this; + } + + leftJoin(relation: string, alias: string, condition?: string): this { + this.qb.leftJoin(relation, alias, condition); + return this; + } + + leftJoinAndSelect(relation: string, alias: string, condition?: string): this { + this.qb.leftJoinAndSelect(relation, alias, condition); + return this; + } + + where(condition: string | object, parameters?: object): this { + this.qb.where(condition, parameters); + return this; + } + + andWhere(condition: string | object, parameters?: object): this { + this.qb.andWhere(condition, parameters); + return this; + } + + orWhere(condition: string | object, parameters?: object): this { + this.qb.orWhere(condition, parameters); + return this; + } + + having(having: string, parameters?: object): this { + this.qb.having(having, parameters); + return this; + }; + + andHaving(having: string, parameters?: object): this { + this.qb.andHaving(having, parameters); + return this; + }; + + orHaving(having: string, parameters?: object): this { + this.qb.orHaving(having, parameters); + return this; + }; + + groupBy(groupBy: string): this { + this.qb.groupBy(groupBy); + return this; + }; + + addGroupBy(groupBy: string): this { + this.qb.addGroupBy(groupBy); + return this; + }; + + orderBy(sort: string, order?: "ASC" | "DESC", nulls?: "NULLS FIRST" | "NULLS LAST"): this { + this.qb.orderBy(sort, order, nulls); + return this; + }; + + addOrderBy(sort: string, order?: "ASC" | "DESC", nulls?: "NULLS FIRST" | "NULLS LAST"): this { + this.qb.addOrderBy(sort, order, nulls); + return this; + }; + + limit(limit?: number): this { + this.qb.limit(limit); + return this; + }; + + offset(offset?: number): this { + this.qb.offset(offset); + return this; + }; + + take(take?: number): this { + this.qb.take(take); + return this; + }; + + skip(skip?: number): this { + this.qb.skip(skip); + return this; + }; + + getSql(): string { + return this.qb.getSql(); + } + + getCount() { + return this.qb.getCount(); + } + + getMany(): Promise { + return this.qb.getMany(); + } + + getRawMany(): Promise { + return this.qb.getRawMany(); + } + + getOne(): Promise { + return this.qb.getOne(); + } + + getRawOne(): Promise { + return this.qb.getRawOne(); + } + + getManyAndCount(): Promise<[Entity[], number]> { + return this.qb.getManyAndCount(); + } + + +} diff --git a/packages/core/src/core/orm/utils.ts b/packages/core/src/core/orm/utils.ts new file mode 100644 index 00000000000..f9be31708b2 --- /dev/null +++ b/packages/core/src/core/orm/utils.ts @@ -0,0 +1,20 @@ +/** + * Converts TypeORM-style where conditions and parameters to Mikro-ORM-style. + * @param whereCondition The TypeORM-style where condition string. + * @param parameters The TypeORM-style parameters object. + * @returns An array where the first element is the converted where condition string + * and the second element is the array of parameters for Mikro-ORM. + */ +export function convertTypeOrmConationAndParamsToMikroOrm(whereCondition: string, parameters: Record): [string, any[]] { + const mikroOrmParameters: any[] = []; + const mikroOrmCondition = whereCondition.replace(/:(\w+)/g, (match, paramName) => { + // Check if the parameter exists; if not, throw an error or handle as needed + if (!(paramName in parameters)) { + throw new Error(`Parameter "${paramName}" not found.`); + } + mikroOrmParameters.push(parameters[paramName]); + return '?'; // Replace named parameters with positional parameters + }); + + return [mikroOrmCondition, mikroOrmParameters]; +} diff --git a/packages/core/src/core/util/object-utils.ts b/packages/core/src/core/util/object-utils.ts new file mode 100644 index 00000000000..35fe3ebe688 --- /dev/null +++ b/packages/core/src/core/util/object-utils.ts @@ -0,0 +1,10 @@ +export class ObjectUtils { + /** + * Checks if given value is an object. + * We cannot use instanceof because it has problems when running on different contexts. + * And we don't simply use typeof because typeof null === "object". + */ + static isObject(val: any): val is Object { + return val !== null && typeof val === "object" + } +} diff --git a/packages/core/src/core/utils.ts b/packages/core/src/core/utils.ts index b8c5ceb6cd0..1ff53e477be 100644 --- a/packages/core/src/core/utils.ts +++ b/packages/core/src/core/utils.ts @@ -1,18 +1,21 @@ -import { sample } from 'underscore'; import { BadRequestException } from '@nestjs/common'; -import { IDateRange, IUser } from '@gauzy/contracts'; +import { TypeOrmModuleOptions } from '@nestjs/typeorm'; +import { SqliteDriver } from '@mikro-orm/sqlite'; +import { FindOptions as MikroORMFindOptions, FilterQuery as MikroFilterQuery, OrderDefinition } from '@mikro-orm/core'; +import { BetterSqliteDriver } from '@mikro-orm/better-sqlite'; +import { PostgreSqlDriver } from '@mikro-orm/postgresql'; +import { MySqlDriver } from '@mikro-orm/mysql'; +import { FindManyOptions, FindOptionsOrder } from 'typeorm'; +import { sample } from 'underscore'; import * as path from 'path'; import * as fs from 'fs'; import * as os from 'os'; +import { IDateRange, IUser } from '@gauzy/contracts'; import { IDBConnectionOptions } from '@gauzy/common'; import { getConfig, DatabaseTypeEnum } from '@gauzy/config'; import { moment } from './../core/moment-extend'; import { ALPHA_NUMERIC_CODE_LENGTH } from './../constants'; -import { SqliteDriver } from '@mikro-orm/sqlite'; -import { BetterSqliteDriver } from '@mikro-orm/better-sqlite'; -import { PostgreSqlDriver } from '@mikro-orm/postgresql'; -import { MySqlDriver } from '@mikro-orm/mysql'; -import { TypeOrmModuleOptions } from '@nestjs/typeorm'; + namespace Utils { export function generatedLogoColor() { @@ -363,19 +366,22 @@ export enum MultiORMEnum { export type MultiORM = 'typeorm' | 'mikro-orm'; /** - * Gets the ORM type based on the environment variable or a default value. - * @param defaultValue The default ORM type. - * @returns The ORM type ('typeorm' or 'mikro-orm'). + * Get the Object-Relational Mapping (ORM) type from the environment variable `DB_ORM`. + * @param {MultiORM} defaultValue - The default ORM type to use if `DB_ORM` is not set or an invalid value is provided. + * @returns {MultiORM} - The determined ORM type. */ export function getORMType(defaultValue: MultiORM = MultiORMEnum.TypeORM): MultiORM { + // Check if the environment variable `DB_ORM` is not set, and return the default value. if (!process.env.DB_ORM) return defaultValue; + // Determine the ORM type based on the value of `DB_ORM`. switch (process.env.DB_ORM) { case MultiORMEnum.TypeORM: return MultiORMEnum.TypeORM; case MultiORMEnum.MikroORM: return MultiORMEnum.MikroORM; default: + // If an invalid value is provided, return the default value. return defaultValue; } } @@ -457,12 +463,90 @@ export const flatten = (input: any): any => { if (typeof input === 'object' && input !== null) { return Object.keys(input).reduce((acc, key) => { const value = input[key]; - const nestedKeys = flatten(value); - const newKey = Array.isArray(value) ? key : nestedKeys.length > 0 ? `${key}.${nestedKeys.join('.')}` : key; - return acc.concat(newKey); - }, []); + if (value) { + const nestedKeys = flatten(value); + const newKey = Array.isArray(value) ? key : nestedKeys.length > 0 ? `${key}.${nestedKeys.join('.')}` : key; + return acc.concat(newKey); + } + }, []) || []; } // If input is neither an array nor an object, return an empty array return []; }; + +/** + * Concatenate an ID to the given MikroORM where condition. + * + * @param id - The ID to concatenate to the where condition. + * @param where - MikroORM where condition. + * @returns Concatenated MikroORM where condition. + */ +export function concatIdToWhere(id: any, where: MikroFilterQuery): MikroFilterQuery { + if (where instanceof Array) { + where = where.concat({ id } as any); + } else { + where = { + id, + ...(where ? where : ({} as any)), + }; + } + return where; +} + +/** + * Convert TypeORM's FindManyOptions to MikroORM's equivalent options. + * + * @param options - TypeORM's FindManyOptions. + * @returns An object with MikroORM's where and options. + */ +export function parseTypeORMFindToMikroOrm(options: FindManyOptions): { where: MikroFilterQuery; mikroOptions: MikroORMFindOptions } { + const mikroOptions: MikroORMFindOptions = { + disableIdentityMap: true, + }; + let where: MikroFilterQuery = {}; + + // Parses TypeORM `where` option to MikroORM `where` option + if (options && options.where) { + where = options.where as MikroFilterQuery; + } + + // Parses TypeORM `select` option to MikroORM `fields` option + if (options && options.select) { + mikroOptions.fields = flatten(options.select) as string[]; + } + + // Parses TypeORM `relations` option to MikroORM `populate` option + if (options && options.relations) { + mikroOptions.populate = flatten(options.relations) as string[]; + } + + // Parses TypeORM `order` option to MikroORM `orderBy` option + if (options && options.order) { + mikroOptions.orderBy = parseOrderOptions(options.order) as OrderDefinition; + } + + // Parses TypeORM `skip` option to MikroORM `offset` option + if (options && options.skip) { + mikroOptions.offset = options.skip; + } + + // Parses TypeORM `take` option to MikroORM `limit` option + if (options && options.take) { + mikroOptions.limit = options.take; + } + + return { where, mikroOptions }; +} + +/** + * Parses TypeORM 'order' option to MikroORM 'orderBy' option. + * @param order TypeORM 'order' option + * @returns Parsed MikroORM 'orderBy' option + */ +export function parseOrderOptions(order: FindOptionsOrder) { + return Object.entries(order).reduce((acc, [key, value]) => { + acc[key] = `${value}`.toLowerCase(); + return acc; + }, {}); +} diff --git a/packages/core/src/country/country.entity.ts b/packages/core/src/country/country.entity.ts index b367a023393..27fa87c5db5 100644 --- a/packages/core/src/country/country.entity.ts +++ b/packages/core/src/country/country.entity.ts @@ -1,9 +1,9 @@ import { ICountry } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; -import { Column, Index } from 'typeorm'; +import { Index } from 'typeorm'; import { BaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCountryRepository } from './repository/mikro-orm-country.repository'; @MultiORMEntity('country', { mikroOrmRepository: () => MikroOrmCountryRepository }) @@ -12,12 +12,12 @@ export class Country extends BaseEntity implements ICountry { @Index() @IsString() @IsNotEmpty() - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false }) isoCode: string; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false }) country: string; } diff --git a/packages/core/src/currency/currency.entity.ts b/packages/core/src/currency/currency.entity.ts index 9f7b9887452..bcc52400447 100644 --- a/packages/core/src/currency/currency.entity.ts +++ b/packages/core/src/currency/currency.entity.ts @@ -1,9 +1,9 @@ import { ICurrency } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; -import { Column, Index } from 'typeorm'; +import { Index } from 'typeorm'; import { BaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCurrencyRepository } from './repository/mikro-orm-currency.repository'; @MultiORMEntity('currency', { mikroOrmRepository: () => MikroOrmCurrencyRepository }) @@ -12,12 +12,12 @@ export class Currency extends BaseEntity implements ICurrency { @Index() @IsString() @IsNotEmpty() - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false }) isoCode: string; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false }) currency: string; } diff --git a/packages/core/src/custom-smtp/custom-smtp.entity.ts b/packages/core/src/custom-smtp/custom-smtp.entity.ts index d526ae7b174..fd8871d8098 100644 --- a/packages/core/src/custom-smtp/custom-smtp.entity.ts +++ b/packages/core/src/custom-smtp/custom-smtp.entity.ts @@ -1,4 +1,3 @@ -import { Column } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsBoolean, IsEmail, IsNumber, IsOptional, IsString } from 'class-validator'; import { Exclude, Expose } from 'class-transformer'; @@ -6,7 +5,7 @@ import { ICustomSmtp } from '@gauzy/contracts'; import { ISMTPConfig } from '@gauzy/common'; import { TenantOrganizationBaseEntity } from '../core/entities/internal'; import { IsSecret } from './../core/decorators'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmCustomSmtpRepository } from './repository/mikro-orm-custom-smtp.repository'; @MultiORMEntity('custom_smtp', { mikroOrmRepository: () => MikroOrmCustomSmtpRepository }) @@ -15,36 +14,36 @@ export class CustomSmtp extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String, examples: ['noreply@domain.com'] }) @IsEmail() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) fromAddress?: string @ApiProperty({ type: () => String, examples: ['smtp.postmarkapp.com', 'smtp.gmail.com'] }) @IsString() - @Column() + @MultiORMColumn() host: string; @ApiProperty({ type: () => Number, examples: [587, 465] }) @IsNumber() - @Column() + @MultiORMColumn() port: number; @ApiProperty({ type: () => Boolean, examples: [true, false] }) @IsBoolean() - @Column() + @MultiORMColumn() secure: boolean; @Exclude({ toPlainOnly: true }) - @Column() + @MultiORMColumn() username: string; @Exclude({ toPlainOnly: true }) - @Column() + @MultiORMColumn() password: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) @IsOptional() @IsBoolean() - @Column({ default: false }) + @MultiORMColumn({ default: false }) isValidate?: boolean; /** diff --git a/packages/core/src/deal/deal.entity.ts b/packages/core/src/deal/deal.entity.ts index e99cfeb6198..225c724d777 100644 --- a/packages/core/src/deal/deal.entity.ts +++ b/packages/core/src/deal/deal.entity.ts @@ -5,11 +5,8 @@ import { IOrganizationContact } from '@gauzy/contracts'; import { - Column, JoinColumn, - ManyToOne, RelationId, - OneToOne, Index } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; @@ -27,8 +24,9 @@ import { TenantOrganizationBaseEntity, User } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmDealRepository } from './repository/mikro-orm-deal.repository'; +import { MultiORMManyToOne, MultiORMOneToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('deal', { mikroOrmRepository: () => MikroOrmDealRepository }) export class Deal extends TenantOrganizationBaseEntity implements IDeal { @@ -36,7 +34,7 @@ export class Deal extends TenantOrganizationBaseEntity implements IDeal { @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() - @Column() + @MultiORMColumn() public title: string; @ApiProperty({ type: () => Number }) @@ -44,7 +42,7 @@ export class Deal extends TenantOrganizationBaseEntity implements IDeal { @IsInt() @Min(0) @Max(5) - @Column() + @MultiORMColumn() public probability?: number; /* @@ -57,7 +55,9 @@ export class Deal extends TenantOrganizationBaseEntity implements IDeal { * User */ @ApiProperty({ type: () => User }) - @ManyToOne(() => User) + @MultiORMManyToOne(() => User, { + joinColumn: 'createdByUserId', + }) @JoinColumn({ name: 'createdByUserId' }) public createdBy: IUser; @@ -66,14 +66,14 @@ export class Deal extends TenantOrganizationBaseEntity implements IDeal { @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn({ relationId: true }) public createdByUserId: string; /** * PipelineStage */ @ApiProperty({ type: () => PipelineStage }) - @ManyToOne(() => PipelineStage, { onDelete: 'CASCADE' }) + @MultiORMManyToOne(() => PipelineStage, { onDelete: 'CASCADE' }) @JoinColumn() public stage: IPipelineStage; @@ -82,7 +82,7 @@ export class Deal extends TenantOrganizationBaseEntity implements IDeal { @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) public stageId: string; /* @@ -95,7 +95,11 @@ export class Deal extends TenantOrganizationBaseEntity implements IDeal { * OrganizationContact */ @ApiProperty({ type: () => OrganizationContact }) - @OneToOne(() => OrganizationContact, { onDelete: 'CASCADE' }) + @MultiORMOneToOne(() => OrganizationContact, { + onDelete: 'CASCADE', + owner: true, + joinColumn: 'clientId', + }) @JoinColumn() public client: IOrganizationContact; @@ -103,6 +107,6 @@ export class Deal extends TenantOrganizationBaseEntity implements IDeal { @RelationId((it: Deal) => it.client) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) public clientId: string; } diff --git a/packages/core/src/email-history/email-history.entity.ts b/packages/core/src/email-history/email-history.entity.ts index 73be4e2f3dd..7422609234a 100644 --- a/packages/core/src/email-history/email-history.entity.ts +++ b/packages/core/src/email-history/email-history.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { IsEnum, IsOptional, IsUUID } from 'class-validator'; import { isMySQL } from '@gauzy/config'; import { IEmailHistory, IEmailTemplate, IUser, EmailStatusEnum } from '@gauzy/contracts'; @@ -8,8 +8,9 @@ import { TenantOrganizationBaseEntity, User, } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEmailHistoryRepository } from './repository/mikro-orm-email-history.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('email_sent', { mikroOrmRepository: () => MikroOrmEmailHistoryRepository }) export class EmailHistory extends TenantOrganizationBaseEntity implements IEmailHistory { @@ -17,12 +18,12 @@ export class EmailHistory extends TenantOrganizationBaseEntity implements IEmail @ApiPropertyOptional({ type: () => String }) @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) name: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ + @MultiORMColumn({ nullable: true, ...(isMySQL() ? { type: 'text' } : {}) }) @@ -30,7 +31,7 @@ export class EmailHistory extends TenantOrganizationBaseEntity implements IEmail @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() email: string; @@ -44,7 +45,7 @@ export class EmailHistory extends TenantOrganizationBaseEntity implements IEmail * User */ @ApiProperty({ type: () => User }) - @ManyToOne(() => User, { + @MultiORMManyToOne(() => User, { onDelete: 'CASCADE', }) user?: IUser; @@ -54,28 +55,28 @@ export class EmailHistory extends TenantOrganizationBaseEntity implements IEmail @IsUUID() @RelationId((it: EmailHistory) => it.user) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) userId?: IUser['id']; /** * Email Template */ @ApiProperty({ type: () => EmailTemplate }) - @ManyToOne(() => EmailTemplate) + @MultiORMManyToOne(() => EmailTemplate) emailTemplate: IEmailTemplate; @ApiProperty({ type: () => String }) @IsUUID() @RelationId((it: EmailHistory) => it.emailTemplate) @Index() - @Column() + @MultiORMColumn({ relationId: true }) emailTemplateId: IEmailTemplate['id']; @Index() @ApiPropertyOptional({ type: () => String, enum: EmailStatusEnum }) @IsOptional() @IsEnum(EmailStatusEnum) - @Column({ + @MultiORMColumn({ type: 'simple-enum', nullable: true, enum: EmailStatusEnum, diff --git a/packages/core/src/email-reset/email-reset.entity.ts b/packages/core/src/email-reset/email-reset.entity.ts index 166baaa136e..43c22ba61f1 100644 --- a/packages/core/src/email-reset/email-reset.entity.ts +++ b/packages/core/src/email-reset/email-reset.entity.ts @@ -1,7 +1,6 @@ import { Index, - Column, - ManyToOne, + JoinColumn, RelationId } from 'typeorm'; @@ -10,8 +9,9 @@ import { IsEmail, IsOptional, IsUUID } from 'class-validator'; import { Exclude } from 'class-transformer'; import { IEmailReset, IUser } from '@gauzy/contracts'; import { TenantBaseEntity, User } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEmailResetRepository } from './repository/mikro-orm-email-reset.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('email_reset', { mikroOrmRepository: () => MikroOrmEmailResetRepository }) export class EmailReset extends TenantBaseEntity implements IEmailReset { @@ -19,27 +19,27 @@ export class EmailReset extends TenantBaseEntity implements IEmailReset { @ApiProperty({ type: () => String }) @IsEmail() @Index() - @Column() + @MultiORMColumn() email: string; @ApiProperty({ type: () => String }) @IsEmail() @Index() - @Column() + @MultiORMColumn() oldEmail: string; @Exclude({ toPlainOnly: true }) @Index() - @Column() + @MultiORMColumn() code: string; @Exclude({ toPlainOnly: true }) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) token: string; @Exclude({ toPlainOnly: true }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) expiredAt: Date; isExpired: boolean; @@ -53,7 +53,7 @@ export class EmailReset extends TenantBaseEntity implements IEmailReset { /** * User */ - @ManyToOne(() => User, { + @MultiORMManyToOne(() => User, { onDelete: 'CASCADE' }) @JoinColumn() @@ -64,6 +64,6 @@ export class EmailReset extends TenantBaseEntity implements IEmailReset { @IsUUID() @RelationId((it: EmailReset) => it.user) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) userId?: IUser['id']; } diff --git a/packages/core/src/email-template/email-template.entity.ts b/packages/core/src/email-template/email-template.entity.ts index efd504a784d..4833c1d08ff 100644 --- a/packages/core/src/email-template/email-template.entity.ts +++ b/packages/core/src/email-template/email-template.entity.ts @@ -1,9 +1,9 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Column, Index } from 'typeorm'; +import { Index } from 'typeorm'; import { IEmailTemplate } from '@gauzy/contracts'; import { isMySQL } from "@gauzy/config"; import { TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEmailTemplateRepository } from './repository/mikro-orm-email-template.repository'; @MultiORMEntity('email_template', { mikroOrmRepository: () => MikroOrmEmailTemplateRepository }) @@ -12,20 +12,20 @@ export class EmailTemplate extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() languageCode: string; @ApiProperty({ type: () => String }) - @Column({ type: 'text', nullable: true }) + @MultiORMColumn({ type: 'text', nullable: true }) mjml: string; @ApiProperty({ type: () => String }) - @Column({ ...(isMySQL() ? { type: "longtext" } : {}) }) + @MultiORMColumn({ ...(isMySQL() ? { type: "longtext" } : {}) }) hbs: string; title?: string; diff --git a/packages/core/src/employee-appointment/employee-appointment.entity.ts b/packages/core/src/employee-appointment/employee-appointment.entity.ts index f7b73385d57..e440c741bfb 100644 --- a/packages/core/src/employee-appointment/employee-appointment.entity.ts +++ b/packages/core/src/employee-appointment/employee-appointment.entity.ts @@ -8,10 +8,7 @@ import { IsNumber } from 'class-validator'; import { - Column, - OneToMany, JoinColumn, - ManyToOne, RelationId } from 'typeorm'; import { @@ -19,14 +16,15 @@ import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEmployeeAppointmentRepository } from './repository/mikro-orm-employee-appointment.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('employee_appointment', { mikroOrmRepository: () => MikroOrmEmployeeAppointmentRepository }) export class EmployeeAppointment extends TenantOrganizationBaseEntity implements IEmployeeAppointment { @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { nullable: true, onDelete: 'CASCADE' }) + @MultiORMManyToOne(() => Employee, { nullable: true, onDelete: 'CASCADE' }) @JoinColumn() employee?: IEmployee; @@ -35,74 +33,74 @@ export class EmployeeAppointment extends TenantOrganizationBaseEntity implements (employeeAppointment: EmployeeAppointment) => employeeAppointment.employee ) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly employeeId?: string; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column() + @MultiORMColumn() agenda: string; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) location?: string; @ApiProperty({ type: () => Date }) @IsDate() - @Column() + @MultiORMColumn() startDateTime: Date; @ApiProperty({ type: () => Date }) @IsDate() - @Column() + @MultiORMColumn() endDateTime: Date; @ApiProperty({ type: () => Boolean }) @IsBoolean() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) bufferTimeStart?: Boolean; @ApiProperty({ type: () => Boolean }) @IsBoolean() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) bufferTimeEnd?: Boolean; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) bufferTimeInMins?: Number; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) breakTimeInMins?: Number; @ApiProperty({ type: () => Date }) @IsDate() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) breakStartTime?: Date; @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) emails?: string; @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) status?: string; @ApiProperty({ type: () => AppointmentEmployee, isArray: true }) - @OneToMany( + @MultiORMOneToMany( () => AppointmentEmployee, (entity) => entity.employeeAppointment, { diff --git a/packages/core/src/employee-award/employee-award.entity.ts b/packages/core/src/employee-award/employee-award.entity.ts index ef247c3a6b0..f2f42278038 100644 --- a/packages/core/src/employee-award/employee-award.entity.ts +++ b/packages/core/src/employee-award/employee-award.entity.ts @@ -1,12 +1,13 @@ -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IEmployee, IEmployeeAward } from '@gauzy/contracts'; import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEmployeeAwardRepository } from './repository/mikro-orm-employee-award.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('employee_award', { mikroOrmRepository: () => MikroOrmEmployeeAwardRepository }) export class EmployeeAward extends TenantOrganizationBaseEntity @@ -14,11 +15,11 @@ export class EmployeeAward extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() year: string; /* @@ -27,7 +28,7 @@ export class EmployeeAward extends TenantOrganizationBaseEntity |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, (it) => it.awards, { + @MultiORMManyToOne(() => Employee, (it) => it.awards, { onDelete: 'CASCADE' }) employee?: IEmployee; @@ -35,6 +36,6 @@ export class EmployeeAward extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: EmployeeAward) => it.employee) @Index() - @Column() + @MultiORMColumn({ relationId: true }) employeeId?: string; } diff --git a/packages/core/src/employee-job-preset/employee-upwork-jobs-search-criterion.entity.ts b/packages/core/src/employee-job-preset/employee-upwork-jobs-search-criterion.entity.ts index fed354ac413..0deac188f46 100644 --- a/packages/core/src/employee-job-preset/employee-upwork-jobs-search-criterion.entity.ts +++ b/packages/core/src/employee-job-preset/employee-upwork-jobs-search-criterion.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, RelationId } from 'typeorm'; +import { RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString, IsUUID } from 'class-validator'; import { @@ -16,7 +16,7 @@ import { JobSearchOccupation, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from './../core/decorators/entity'; import { MikroOrmEmployeeUpworkJobsSearchCriterionRepository } from './repository/mikro-orm-employee-upwork-jobs-search-criterion.entity.repository'; @MultiORMEntity('employee_upwork_job_search_criterion', { mikroOrmRepository: () => MikroOrmEmployeeUpworkJobsSearchCriterionRepository }) @@ -25,13 +25,13 @@ export class EmployeeUpworkJobsSearchCriterion extends TenantOrganizationBaseEnt @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) keyword?: string; @ApiProperty({ type: () => Boolean }) @IsString() @IsNotEmpty() - @Column({ type: 'text', nullable: true }) + @MultiORMColumn({ type: 'text', nullable: true }) jobType?: JobPostTypeEnum; /* @@ -43,52 +43,52 @@ export class EmployeeUpworkJobsSearchCriterion extends TenantOrganizationBaseEnt /** * */ - @ManyToOne(() => JobPreset, (it) => it.employeeCriterions) + @MultiORMManyToOne(() => JobPreset, (it) => it.employeeCriterions) jobPreset?: IJobPreset; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() @RelationId((it: EmployeeUpworkJobsSearchCriterion) => it.jobPreset) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) jobPresetId?: string; /** * */ - @ManyToOne(() => Employee) + @MultiORMManyToOne(() => Employee) employee?: IEmployee; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsUUID() @RelationId((it: EmployeeUpworkJobsSearchCriterion) => it.employee) - @Column() + @MultiORMColumn({ relationId: true }) employeeId?: string; /** * */ - @ManyToOne(() => JobSearchOccupation, (it) => it.employeeCriterions) + @MultiORMManyToOne(() => JobSearchOccupation, (it) => it.employeeCriterions) occupation?: IJobSearchOccupation; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsUUID() @RelationId((it: EmployeeUpworkJobsSearchCriterion) => it.occupation) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) occupationId?: string; /** * */ - @ManyToOne(() => JobSearchCategory, (it) => it.employeeCriterions) + @MultiORMManyToOne(() => JobSearchCategory, (it) => it.employeeCriterions) category?: IJobSearchCategory; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsUUID() @RelationId((it: EmployeeUpworkJobsSearchCriterion) => it.category) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) categoryId?: string; } diff --git a/packages/core/src/employee-job-preset/job-preset-upwork-job-search-criterion.entity.ts b/packages/core/src/employee-job-preset/job-preset-upwork-job-search-criterion.entity.ts index 09865f92d7c..38708fb61a2 100644 --- a/packages/core/src/employee-job-preset/job-preset-upwork-job-search-criterion.entity.ts +++ b/packages/core/src/employee-job-preset/job-preset-upwork-job-search-criterion.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, RelationId } from 'typeorm'; +import { RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsString, IsUUID } from 'class-validator'; import { @@ -14,7 +14,7 @@ import { JobSearchOccupation, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from './../core/decorators/entity'; import { MikroOrmJobPresetUpworkJobSearchCriterionRepository } from './repository/mikro-orm-job-preset-upwork-job-search-criterion.repository'; @MultiORMEntity('job_preset_upwork_job_search_criterion', { mikroOrmRepository: () => MikroOrmJobPresetUpworkJobSearchCriterionRepository }) @@ -23,13 +23,13 @@ export class JobPresetUpworkJobSearchCriterion extends TenantOrganizationBaseEnt @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) keyword?: string; @ApiProperty({ type: () => Boolean }) @IsNotEmpty() @IsEnum(JobPostTypeEnum) - @Column({ type: 'text', nullable: true }) + @MultiORMColumn({ type: 'text', nullable: true }) jobType?: JobPostTypeEnum; /* @@ -41,39 +41,39 @@ export class JobPresetUpworkJobSearchCriterion extends TenantOrganizationBaseEnt /** * */ - @ManyToOne(() => JobPreset, (it) => it.jobPresetCriterions) + @MultiORMManyToOne(() => JobPreset, (it) => it.jobPresetCriterions) jobPreset?: IJobPreset; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsUUID() @RelationId((it: JobPresetUpworkJobSearchCriterion) => it.jobPreset) - @Column() + @MultiORMColumn({ relationId: true }) jobPresetId?: string; /** * */ - @ManyToOne(() => JobSearchOccupation, (it) => it.jobPresetCriterions) + @MultiORMManyToOne(() => JobSearchOccupation, (it) => it.jobPresetCriterions) occupation?: IJobSearchOccupation; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsUUID() @RelationId((it: JobPresetUpworkJobSearchCriterion) => it.occupation) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) occupationId?: string; /** * */ - @ManyToOne(() => JobSearchCategory, (it) => it.jobPresetCriterions) + @MultiORMManyToOne(() => JobSearchCategory, (it) => it.jobPresetCriterions) category?: IJobSearchCategory; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsUUID() @RelationId((it: JobPresetUpworkJobSearchCriterion) => it.category) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) categoryId?: string; } diff --git a/packages/core/src/employee-job-preset/job-preset.entity.ts b/packages/core/src/employee-job-preset/job-preset.entity.ts index 6dbdc425aef..27ff27a621a 100644 --- a/packages/core/src/employee-job-preset/job-preset.entity.ts +++ b/packages/core/src/employee-job-preset/job-preset.entity.ts @@ -1,9 +1,6 @@ import { - Column, Index, - JoinTable, - ManyToMany, - OneToMany + JoinTable } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; @@ -18,8 +15,9 @@ import { JobPresetUpworkJobSearchCriterion, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmJobPresetRepository } from './repository/mikro-orm-job-preset.repository'; +import { MultiORMManyToMany, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('job_preset', { mikroOrmRepository: () => MikroOrmJobPresetRepository }) export class JobPreset extends TenantOrganizationBaseEntity implements IJobPreset { @@ -28,7 +26,7 @@ export class JobPreset extends TenantOrganizationBaseEntity implements IJobPrese @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() name?: string; /* @@ -39,7 +37,7 @@ export class JobPreset extends TenantOrganizationBaseEntity implements IJobPrese /** * Employee Job Criterions */ - @OneToMany(() => EmployeeUpworkJobsSearchCriterion, (it) => it.jobPreset, { + @MultiORMOneToMany(() => EmployeeUpworkJobsSearchCriterion, (it) => it.jobPreset, { onDelete: 'CASCADE' }) employeeCriterions?: IEmployeeUpworkJobsSearchCriterion[]; @@ -47,7 +45,7 @@ export class JobPreset extends TenantOrganizationBaseEntity implements IJobPrese /** * Job Criterions */ - @OneToMany(() => JobPresetUpworkJobSearchCriterion, (it) => it.jobPreset, { + @MultiORMOneToMany(() => JobPresetUpworkJobSearchCriterion, (it) => it.jobPreset, { onDelete: 'CASCADE' }) jobPresetCriterions?: IJobPresetUpworkJobSearchCriterion[]; @@ -61,8 +59,10 @@ export class JobPreset extends TenantOrganizationBaseEntity implements IJobPrese /** * Job Preset Employees */ - @ManyToMany(() => Employee, (it) => it.jobPresets, { - cascade: true + @MultiORMManyToMany(() => Employee, (it) => it.jobPresets, { + cascade: true, + owner: true, + pivotTable: 'employee_job_preset', }) @JoinTable({ name: 'employee_job_preset' diff --git a/packages/core/src/employee-job-preset/job-search-category/job-search-category.entity.ts b/packages/core/src/employee-job-preset/job-search-category/job-search-category.entity.ts index b801f8e6476..eec4916c7ce 100644 --- a/packages/core/src/employee-job-preset/job-search-category/job-search-category.entity.ts +++ b/packages/core/src/employee-job-preset/job-search-category/job-search-category.entity.ts @@ -1,4 +1,4 @@ -import { Column, Index, OneToMany } from 'typeorm'; +import { Index } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { @@ -13,7 +13,7 @@ import { JobPresetUpworkJobSearchCriterion, TenantOrganizationBaseEntity } from '../../core/entities/internal'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity, MultiORMOneToMany } from './../../core/decorators/entity'; import { MikroOrmJobSearchCategoryRepository } from './repository/mikro-orm-job-search-category.repository'; @MultiORMEntity('job_search_category', { mikroOrmRepository: () => MikroOrmJobSearchCategoryRepository }) @@ -23,7 +23,7 @@ export class JobSearchCategory extends TenantOrganizationBaseEntity implements I @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() name?: string; // Id of category in the job source (e.g. upwork) @@ -31,14 +31,14 @@ export class JobSearchCategory extends TenantOrganizationBaseEntity implements I @IsOptional() @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) jobSourceCategoryId?: string; @ApiProperty({ type: () => String, enum: JobPostSourceEnum }) @IsNotEmpty() @IsEnum(JobPostSourceEnum) @Index() - @Column({ + @MultiORMColumn({ default: JobPostSourceEnum.UPWORK, ...(isMySQL() ? { type: 'enum', enum: JobPostSourceEnum } : { type: 'text' }) }) @@ -53,7 +53,7 @@ export class JobSearchCategory extends TenantOrganizationBaseEntity implements I /** * EmployeeUpworkJobsSearchCriterion */ - @OneToMany(() => EmployeeUpworkJobsSearchCriterion, (it) => it.category, { + @MultiORMOneToMany(() => EmployeeUpworkJobsSearchCriterion, (it) => it.category, { onDelete: 'CASCADE' }) employeeCriterions?: IEmployeeUpworkJobsSearchCriterion[]; @@ -61,7 +61,7 @@ export class JobSearchCategory extends TenantOrganizationBaseEntity implements I /** * JobPresetUpworkJobSearchCriterion */ - @OneToMany(() => JobPresetUpworkJobSearchCriterion, (it) => it.category, { + @MultiORMOneToMany(() => JobPresetUpworkJobSearchCriterion, (it) => it.category, { onDelete: 'CASCADE' }) jobPresetCriterions?: IJobPresetUpworkJobSearchCriterion[]; diff --git a/packages/core/src/employee-job-preset/job-search-occupation/job-search-occupation.entity.ts b/packages/core/src/employee-job-preset/job-search-occupation/job-search-occupation.entity.ts index 33a9223e66b..92c688cb158 100644 --- a/packages/core/src/employee-job-preset/job-search-occupation/job-search-occupation.entity.ts +++ b/packages/core/src/employee-job-preset/job-search-occupation/job-search-occupation.entity.ts @@ -1,4 +1,4 @@ -import { Column, Index, OneToMany } from 'typeorm'; +import { Index } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { @@ -13,7 +13,7 @@ import { JobPresetUpworkJobSearchCriterion, TenantOrganizationBaseEntity } from '../../core/entities/internal'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity, MultiORMOneToMany } from './../../core/decorators/entity'; import { MikroOrmJobSearchOccupationRepository } from './repository/mikro-orm-job-search-occupation.repository'; @MultiORMEntity('job_search_occupation', { mikroOrmRepository: () => MikroOrmJobSearchOccupationRepository }) @@ -23,7 +23,7 @@ export class JobSearchOccupation extends TenantOrganizationBaseEntity implements @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() name?: string; // Id of occupation in the job source (e.g. upwork) @@ -31,14 +31,14 @@ export class JobSearchOccupation extends TenantOrganizationBaseEntity implements @IsOptional() @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) jobSourceOccupationId?: string; @ApiProperty({ type: () => String, enum: JobPostSourceEnum }) @IsNotEmpty() @IsEnum(JobPostSourceEnum) @Index() - @Column({ + @MultiORMColumn({ default: JobPostSourceEnum.UPWORK, ...(isMySQL() ? { type: 'enum', enum: JobPostSourceEnum } : { type: 'text' }) }) @@ -53,7 +53,7 @@ export class JobSearchOccupation extends TenantOrganizationBaseEntity implements /** * EmployeeUpworkJobsSearchCriterion */ - @OneToMany(() => EmployeeUpworkJobsSearchCriterion, (it) => it.occupation, { + @MultiORMOneToMany(() => EmployeeUpworkJobsSearchCriterion, (it) => it.occupation, { /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -62,7 +62,7 @@ export class JobSearchOccupation extends TenantOrganizationBaseEntity implements /** * JobPresetUpworkJobSearchCriterion */ - @OneToMany(() => JobPresetUpworkJobSearchCriterion, (it) => it.occupation, { + @MultiORMOneToMany(() => JobPresetUpworkJobSearchCriterion, (it) => it.occupation, { /** Database cascade action on delete. */ onDelete: 'CASCADE' }) diff --git a/packages/core/src/employee-level/employee-level.entity.ts b/packages/core/src/employee-level/employee-level.entity.ts index 341d58c29ce..c3889694343 100644 --- a/packages/core/src/employee-level/employee-level.entity.ts +++ b/packages/core/src/employee-level/employee-level.entity.ts @@ -1,10 +1,11 @@ -import { Column, ManyToMany, JoinTable } from 'typeorm'; +import { JoinTable } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsNotEmpty } from 'class-validator'; import { IEmployeeLevel, ITag } from '@gauzy/contracts'; import { Tag, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEmployeeLevelRepository } from './repository/mikro-orm-employee-level.repository'; +import { MultiORMManyToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('employee_level', { mikroOrmRepository: () => MikroOrmEmployeeLevelRepository }) export class EmployeeLevel extends TenantOrganizationBaseEntity implements IEmployeeLevel { @@ -12,7 +13,7 @@ export class EmployeeLevel extends TenantOrganizationBaseEntity implements IEmpl @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column() + @MultiORMColumn() level: string; /* @@ -24,9 +25,11 @@ export class EmployeeLevel extends TenantOrganizationBaseEntity implements IEmpl /** * Tag */ - @ManyToMany(() => Tag, (it) => it.employeeLevels, { + @MultiORMManyToMany(() => Tag, (it) => it.employeeLevels, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_employee_level', }) @JoinTable({ name: 'tag_employee_level' diff --git a/packages/core/src/employee-phone/employee-phone.entity.ts b/packages/core/src/employee-phone/employee-phone.entity.ts index aaef4c1516c..69bc81fed08 100644 --- a/packages/core/src/employee-phone/employee-phone.entity.ts +++ b/packages/core/src/employee-phone/employee-phone.entity.ts @@ -1,19 +1,20 @@ -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IEmployee, IEmployeePhone } from '@gauzy/contracts'; import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEmployeePhoneRepository } from './repository/mikro-orm-employee-phone.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('employee_phone', { mikroOrmRepository: () => MikroOrmEmployeePhoneRepository }) export class EmployeePhone extends TenantOrganizationBaseEntity implements IEmployeePhone { @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) type: string; @ApiProperty({ type: () => String, minLength: 4, maxLength: 12 }) @Index() - @Column() + @MultiORMColumn() phoneNumber: string; /* @@ -22,7 +23,7 @@ export class EmployeePhone extends TenantOrganizationBaseEntity implements IEmpl |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, (employee) => employee.phoneNumbers, { + @MultiORMManyToOne(() => Employee, (employee) => employee.phoneNumbers, { onDelete: 'CASCADE' }) employee?: IEmployee; @@ -30,6 +31,6 @@ export class EmployeePhone extends TenantOrganizationBaseEntity implements IEmpl @ApiProperty({ type: () => String }) @RelationId((it: EmployeePhone) => it.employee) @Index() - @Column() + @MultiORMColumn({ relationId: true }) employeeId?: IEmployee['id']; } diff --git a/packages/core/src/employee-proposal-template/employee-proposal-template.entity.ts b/packages/core/src/employee-proposal-template/employee-proposal-template.entity.ts index 91758ceb1c8..11ceeba99bb 100644 --- a/packages/core/src/employee-proposal-template/employee-proposal-template.entity.ts +++ b/packages/core/src/employee-proposal-template/employee-proposal-template.entity.ts @@ -1,12 +1,13 @@ -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IEmployee, IEmployeeProposalTemplate } from '@gauzy/contracts'; import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEmployeeProposalTemplateRepository } from './repository/mikro-orm-employee-proposal-template.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('employee_proposal_template', { mikroOrmRepository: () => MikroOrmEmployeeProposalTemplateRepository }) export class EmployeeProposalTemplate extends TenantOrganizationBaseEntity @@ -14,17 +15,17 @@ export class EmployeeProposalTemplate extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() name?: string; @ApiProperty({ type: () => String }) @Index({ fulltext: true }) - @Column({ type: 'text', nullable: true }) + @MultiORMColumn({ type: 'text', nullable: true }) content?: string; @ApiProperty({ type: () => Boolean }) @Index() - @Column({ default: false }) + @MultiORMColumn({ default: false }) isDefault?: boolean; /* @@ -33,7 +34,7 @@ export class EmployeeProposalTemplate extends TenantOrganizationBaseEntity |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { + @MultiORMManyToOne(() => Employee, { onDelete: 'CASCADE' }) employee?: IEmployee; @@ -41,6 +42,6 @@ export class EmployeeProposalTemplate extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: EmployeeProposalTemplate) => it.employee) @Index() - @Column() + @MultiORMColumn({ relationId: true }) employeeId?: string; } diff --git a/packages/core/src/employee-recurring-expense/employee-recurring-expense.entity.ts b/packages/core/src/employee-recurring-expense/employee-recurring-expense.entity.ts index 60e0f84964b..6c107da68a6 100644 --- a/packages/core/src/employee-recurring-expense/employee-recurring-expense.entity.ts +++ b/packages/core/src/employee-recurring-expense/employee-recurring-expense.entity.ts @@ -11,14 +11,15 @@ import { IsDate, IsOptional } from 'class-validator'; -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEmployeeRecurringExpenseRepository } from './repository/mikro-orm-employee-recurring-expense.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('employee_recurring_expense', { mikroOrmRepository: () => MikroOrmEmployeeRecurringExpenseRepository }) export class EmployeeRecurringExpense extends TenantOrganizationBaseEntity implements IEmployeeRecurringExpense { @@ -28,7 +29,7 @@ export class EmployeeRecurringExpense extends TenantOrganizationBaseEntity imple @IsNotEmpty() @Min(1) @Max(31) - @Column() + @MultiORMColumn() startDay: number; @ApiProperty({ type: () => Number, minimum: 1, maximum: 12 }) @@ -36,19 +37,19 @@ export class EmployeeRecurringExpense extends TenantOrganizationBaseEntity imple @IsNotEmpty() @Min(1) @Max(12) - @Column() + @MultiORMColumn() startMonth: number; @ApiProperty({ type: () => Number, minimum: 1 }) @IsNumber() @IsNotEmpty() @Min(0) - @Column() + @MultiORMColumn() startYear: number; @ApiProperty({ type: () => Date }) @IsDate() - @Column() + @MultiORMColumn() startDate: Date; @ApiProperty({ type: () => Number, minimum: 1, maximum: 31 }) @@ -56,7 +57,7 @@ export class EmployeeRecurringExpense extends TenantOrganizationBaseEntity imple @Optional() @Min(1) @Max(31) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endDay?: number; @ApiProperty({ type: () => Number, minimum: 1, maximum: 12 }) @@ -64,33 +65,33 @@ export class EmployeeRecurringExpense extends TenantOrganizationBaseEntity imple @Optional() @Min(1) @Max(12) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endMonth?: number; @ApiProperty({ type: () => Number, minimum: 1 }) @IsNumber() @Optional() @Min(0) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endYear?: number; @ApiProperty({ type: () => Date }) @IsDate() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endDate?: Date; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() categoryName: string; @ApiProperty({ type: () => Number }) @IsNumber() @IsNotEmpty() - @Column({ + @MultiORMColumn({ type: 'numeric', transformer: new ColumnNumericTransformerPipe() }) @@ -100,13 +101,13 @@ export class EmployeeRecurringExpense extends TenantOrganizationBaseEntity imple @IsEnum(CurrenciesEnum) @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() currency: string; @ApiProperty({ type: () => String }) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) parentRecurringExpenseId?: string; @@ -116,7 +117,7 @@ export class EmployeeRecurringExpense extends TenantOrganizationBaseEntity imple |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { + @MultiORMManyToOne(() => Employee, { onDelete: 'CASCADE' }) employee?: IEmployee; @@ -124,6 +125,6 @@ export class EmployeeRecurringExpense extends TenantOrganizationBaseEntity imple @ApiProperty({ type: () => String }) @RelationId((it: EmployeeRecurringExpense) => it.employee) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) employeeId: string; } diff --git a/packages/core/src/employee-setting/employee-setting.entity.ts b/packages/core/src/employee-setting/employee-setting.entity.ts index ea45a4fc1ee..bb0ab28d3f9 100644 --- a/packages/core/src/employee-setting/employee-setting.entity.ts +++ b/packages/core/src/employee-setting/employee-setting.entity.ts @@ -1,4 +1,4 @@ -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, @@ -13,8 +13,9 @@ import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEmployeeSettingRepository } from './repository/mikro-orm-employee-setting.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('employee_setting', { mikroOrmRepository: () => MikroOrmEmployeeSettingRepository }) export class EmployeeSetting extends TenantOrganizationBaseEntity implements IEmployeeSetting { @@ -24,34 +25,34 @@ export class EmployeeSetting extends TenantOrganizationBaseEntity implements IEm @IsNotEmpty() @Min(1) @Max(12) - @Column() + @MultiORMColumn() month: number; @ApiProperty({ type: () => Number, minimum: 1 }) @IsNumber() @IsNotEmpty() @Min(0) - @Column() + @MultiORMColumn() year: number; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() settingType: string; @ApiProperty({ type: () => Number }) @IsNumber() @IsNotEmpty() - @Column() + @MultiORMColumn() value: number; @ApiProperty({ type: () => String, enum: CurrenciesEnum }) @IsEnum(CurrenciesEnum) @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() currency: string; /* @@ -60,7 +61,7 @@ export class EmployeeSetting extends TenantOrganizationBaseEntity implements IEm |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, (employee) => employee.settings, { + @MultiORMManyToOne(() => Employee, (employee) => employee.settings, { onDelete: 'CASCADE' }) employee: IEmployee; @@ -70,6 +71,6 @@ export class EmployeeSetting extends TenantOrganizationBaseEntity implements IEm @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn({ relationId: true }) employeeId: string; } diff --git a/packages/core/src/employee/commands/handlers/employee.create.handler.ts b/packages/core/src/employee/commands/handlers/employee.create.handler.ts index 40ac72e3c6f..2069fb46529 100644 --- a/packages/core/src/employee/commands/handlers/employee.create.handler.ts +++ b/packages/core/src/employee/commands/handlers/employee.create.handler.ts @@ -1,10 +1,10 @@ +import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { ComponentLayoutStyleEnum, IEmployee, LanguagesEnum, RolesEnum } from '@gauzy/contracts'; -import { CommandBus, CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { environment } from '@gauzy/config'; import { isEmpty } from '@gauzy/common'; import { RequestContext } from './../../../core/context'; @@ -31,6 +31,13 @@ export class EmployeeCreateHandler private readonly _userService: UserService ) { } + /** + * Execute the employee creation command. + * + * @param command - The employee creation command. + * @returns The created employee. + * @throws SomeAppropriateException if an error occurs during the process. + */ public async execute(command: EmployeeCreateCommand): Promise { const { input, originUrl = environment.clientBaseUrl } = command; const languageCode = command.languageCode || LanguagesEnum.ENGLISH; @@ -61,26 +68,19 @@ export class EmployeeCreateHandler user }); + const { organizationId } = employee; + // 3. Assign organizations to the employee user - if (employee.organizationId) { - await this._userOrganizationService.addUserToOrganization( - employee.user, - employee.organizationId - ); + if (organizationId) { + await this._userOrganizationService.addUserToOrganization(user, organizationId); } // 4. Send welcome email to user register employee - this._emailService.welcomeUser( - employee.user, - languageCode, - employee.organizationId, - originUrl - ); + this._emailService.welcomeUser(user, languageCode, organizationId, originUrl); return employee; } else { try { - const { userId } = input; - const user = await this._userService.findOneByIdString(userId); + const user = await this._userService.findOneByIdString(input.userId); //2. Create employee for specific user return await this._employeeService.create({ diff --git a/packages/core/src/employee/employee.entity.ts b/packages/core/src/employee/employee.entity.ts index 1ce30d26333..976fbb534d7 100644 --- a/packages/core/src/employee/employee.entity.ts +++ b/packages/core/src/employee/employee.entity.ts @@ -1,17 +1,12 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { - Column, JoinColumn, JoinTable, - ManyToMany, - ManyToOne, - OneToOne, RelationId, - OneToMany, Index, } from 'typeorm'; import { IsOptional, IsString } from 'class-validator'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { CurrenciesEnum, IEmployee, @@ -27,7 +22,6 @@ import { IOrganizationEmploymentType, IInvoiceItem, IRequestApprovalEmployee, - IPayment, IOrganizationProject, IOrganizationContact, IEmployeeSetting, @@ -60,7 +54,6 @@ import { OrganizationPosition, OrganizationProject, OrganizationTeamEmployee, - Payment, RequestApprovalEmployee, Skill, Tag, @@ -76,72 +69,73 @@ import { } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; import { MikroOrmEmployeeRepository } from './repository/mikro-orm-employee.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany, MultiORMOneToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('employee', { mikroOrmRepository: () => MikroOrmEmployeeRepository }) export class Employee extends TenantOrganizationBaseEntity implements IEmployee { @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) valueDate?: Date; @ApiPropertyOptional({ type: () => String, maxLength: 200 }) - @Column({ length: 200, nullable: true }) + @MultiORMColumn({ length: 200, nullable: true }) short_description?: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) startedWorkOn?: Date; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endWork?: Date; @ApiProperty({ type: () => String, enum: PayPeriodEnum }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) payPeriod?: string; @ApiProperty({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) billRateValue?: number; @ApiProperty({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) minimumBillingRate?: number; @ApiProperty({ type: () => String, enum: CurrenciesEnum }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) billRateCurrency?: string; @ApiProperty({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) reWeeklyLimit?: number; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) offerDate?: Date; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) acceptDate?: Date; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) rejectDate?: Date; @ApiPropertyOptional({ type: () => String, maxLength: 500 }) - @Column({ length: 500, nullable: true }) + @MultiORMColumn({ length: 500, nullable: true }) employeeLevel?: string; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) anonymousBonus?: boolean; @ApiProperty({ type: () => Number }) - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe(), @@ -149,7 +143,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee averageIncome?: number; @ApiProperty({ type: () => Number }) - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe(), @@ -157,7 +151,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee averageBonus?: number; @ApiProperty({ type: () => Number }) - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', default: 0, @@ -166,7 +160,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee totalWorkHours?: number; @ApiProperty({ type: () => Number }) - @Column({ + @MultiORMColumn({ type: 'numeric', nullable: true, transformer: new ColumnNumericTransformerPipe(), @@ -174,79 +168,79 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee averageExpenses?: number; @ApiProperty({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_anonymous_bonus?: boolean; @ApiProperty({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_average_bonus?: boolean; @ApiProperty({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_average_expenses?: boolean; @ApiProperty({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_average_income?: boolean; @ApiProperty({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_billrate?: boolean; @ApiProperty({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_payperiod?: boolean; @ApiProperty({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_start_work_on?: boolean; @ApiProperty({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) isJobSearchActive?: boolean; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) linkedInUrl?: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) facebookUrl?: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) instagramUrl?: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) twitterUrl?: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) githubUrl?: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) gitlabUrl?: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) upworkUrl?: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) stackoverflowUrl?: string; @ApiProperty({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) isVerified?: boolean; @ApiProperty({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) isVetted?: boolean; @ApiProperty({ type: () => Number }) - @Column({ + @MultiORMColumn({ type: 'numeric', nullable: true, transformer: new ColumnNumericTransformerPipe(), @@ -254,7 +248,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee totalJobs?: number; @ApiProperty({ type: () => Number }) - @Column({ + @MultiORMColumn({ type: 'numeric', nullable: true, transformer: new ColumnNumericTransformerPipe(), @@ -263,14 +257,14 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee @ApiProperty({ type: () => String, minLength: 3, maxLength: 100 }) @Index({ unique: false }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) profile_link?: string; /** * Enabled/Disabled Time Tracking Feature */ @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ + @MultiORMColumn({ type: Boolean, nullable: true, default: false, @@ -281,7 +275,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee * Employee status (Online/Offline) */ @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ + @MultiORMColumn({ type: Boolean, nullable: true, default: false, @@ -289,7 +283,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee isOnline?: boolean; @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ + @MultiORMColumn({ type: Boolean, nullable: true, default: false, @@ -300,7 +294,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee * Employee time tracking status */ @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ + @MultiORMColumn({ type: Boolean, nullable: true, default: false, @@ -311,21 +305,21 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee * Enabled/Disabled Screen Capture Feature */ @ApiPropertyOptional({ type: () => Boolean, default: true }) - @Column({ default: true }) + @MultiORMColumn({ default: true }) allowScreenshotCapture?: boolean; /** Upwork ID */ @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) upworkId?: string; /** LinkedIn ID */ @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) linkedInId?: string; @@ -345,26 +339,28 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee * User */ @ApiProperty({ type: () => User }) - @OneToOne(() => User, (user) => user.employee, { + @MultiORMOneToOne(() => User, (it) => it.employee, { cascade: true, onDelete: 'CASCADE', + owner: true, }) @JoinColumn() user: IUser; - @ApiProperty({ type: () => String, readOnly: true }) + @ApiProperty({ type: () => String }) @RelationId((it: Employee) => it.user) @Index() - @Column() + @MultiORMColumn({ relationId: true }) readonly userId: string; /** * Contact */ @ApiProperty({ type: () => Contact }) - @OneToOne(() => Contact, (contact) => contact.employee, { + @MultiORMOneToOne(() => Contact, (contact) => contact.employee, { cascade: true, onDelete: 'SET NULL', + owner: true, }) @JoinColumn() contact?: IContact; @@ -372,14 +368,14 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee @ApiProperty({ type: () => String, readOnly: true }) @RelationId((it: Employee) => it.contact) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly contactId?: string; /** * Candidate */ @ApiProperty({ type: () => Candidate }) - @OneToOne(() => Candidate, (candidate) => candidate.employee) + @MultiORMOneToOne(() => Candidate, (candidate) => candidate.employee) candidate?: ICandidate; /* |-------------------------------------------------------------------------- @@ -389,14 +385,14 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee // Employee Organization Position @ApiProperty({ type: () => OrganizationPosition }) - @ManyToOne(() => OrganizationPosition, { nullable: true }) + @MultiORMManyToOne(() => OrganizationPosition, { nullable: true }) @JoinColumn() organizationPosition?: IOrganizationPosition; @ApiProperty({ type: () => String, readOnly: true }) @RelationId((it: Employee) => it.organizationPosition) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly organizationPositionId?: string; /* @@ -410,62 +406,57 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee type: () => OrganizationTeamEmployee, isArray: true, }) - @OneToMany(() => OrganizationTeamEmployee, (it) => it.employee) + @MultiORMOneToMany(() => OrganizationTeamEmployee, (it) => it.employee) teams?: IOrganizationTeam[]; /** * Estimations */ - @OneToMany(() => TaskEstimation, (it) => it.employee) + @MultiORMOneToMany(() => TaskEstimation, (it) => it.employee) estimations?: TaskEstimation[]; /** * Time Tracking (Timesheets) */ - @OneToMany(() => Timesheet, (it) => it.employee) + @MultiORMOneToMany(() => Timesheet, (it) => it.employee) timesheets?: ITimesheet[]; /** * Time Tracking (Time Logs) */ - @OneToMany(() => TimeLog, (it) => it.employee) + @MultiORMOneToMany(() => TimeLog, (it) => it.employee) timeLogs?: ITimeLog[]; /** * Time Tracking (Time Slots) */ - @OneToMany(() => TimeSlot, (it) => it.employee) + @MultiORMOneToMany(() => TimeSlot, (it) => it.employee) timeSlots?: ITimeSlot[]; @ApiPropertyOptional({ type: () => InvoiceItem, isArray: true }) - @OneToMany(() => InvoiceItem, (it) => it.employee, { + @MultiORMOneToMany(() => InvoiceItem, (it) => it.employee, { onDelete: 'SET NULL', }) @JoinColumn() invoiceItems?: IInvoiceItem[]; - @ApiPropertyOptional({ type: () => Payment, isArray: true }) - @OneToMany(() => Payment, (it) => it.recordedBy) - @JoinColumn() - payments?: IPayment[]; - @ApiPropertyOptional({ type: () => RequestApprovalEmployee, isArray: true }) - @OneToMany(() => RequestApprovalEmployee, (it) => it.employee) + @MultiORMOneToMany(() => RequestApprovalEmployee, (it) => it.employee) requestApprovals?: IRequestApprovalEmployee[]; @ApiPropertyOptional({ type: () => EmployeeSetting, isArray: true }) - @OneToMany(() => EmployeeSetting, (it) => it.employee) + @MultiORMOneToMany(() => EmployeeSetting, (it) => it.employee) settings?: IEmployeeSetting[]; @ApiPropertyOptional({ type: () => Expense, isArray: true }) - @OneToMany(() => Expense, (it) => it.employee) + @MultiORMOneToMany(() => Expense, (it) => it.employee) expenses?: IExpense[]; /** * Goal */ @ApiPropertyOptional({ type: () => Goal, isArray: true }) - @OneToMany(() => Goal, (it) => it.ownerEmployee, { + @MultiORMOneToMany(() => Goal, (it) => it.ownerEmployee, { onDelete: 'SET NULL', }) goals?: IGoal[]; @@ -474,7 +465,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee * Lead */ @ApiPropertyOptional({ type: () => Goal, isArray: true }) - @OneToMany(() => Goal, (it) => it.lead, { + @MultiORMOneToMany(() => Goal, (it) => it.lead, { onDelete: 'SET NULL', }) leads?: IGoal[]; @@ -483,7 +474,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee * Awards */ @ApiPropertyOptional({ type: () => EmployeeAward, isArray: true }) - @OneToMany(() => EmployeeAward, (it) => it.employee, { + @MultiORMOneToMany(() => EmployeeAward, (it) => it.employee, { onDelete: 'SET NULL', }) awards?: IEmployeeAward[]; @@ -492,7 +483,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee * Phone Numbers */ @ApiPropertyOptional({ type: () => EmployeePhone, isArray: true }) - @OneToMany(() => EmployeePhone, (it) => it.employee) + @MultiORMOneToMany(() => EmployeePhone, (it) => it.employee) phoneNumbers?: IEmployeePhone[]; /* @@ -504,9 +495,11 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee /** * Employee Organization Projects */ - @ManyToMany(() => OrganizationProject, (it) => it.members, { + @MultiORMManyToMany(() => OrganizationProject, (it) => it.members, { onUpdate: 'CASCADE', onDelete: 'CASCADE', + owner: true, + pivotTable: 'organization_project_employee', }) @JoinTable({ name: 'organization_project_employee', @@ -516,9 +509,11 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee /** * Employee Tags */ - @ManyToMany(() => Tag, (tag) => tag.employees, { + @MultiORMManyToMany(() => Tag, (tag) => tag.employees, { onUpdate: 'CASCADE', onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_employee', }) @JoinTable({ name: 'tag_employee', @@ -529,7 +524,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee * Employee Skills */ @ApiProperty({ type: () => Skill }) - @ManyToMany(() => Skill, (skill) => skill.employees, { + @MultiORMManyToMany(() => Skill, (skill) => skill.employees, { onUpdate: 'CASCADE', onDelete: 'CASCADE', }) @@ -539,7 +534,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee * Organization Departments */ @ApiProperty({ type: () => OrganizationDepartment }) - @ManyToMany(() => OrganizationDepartment, (it) => it.members, { + @MultiORMManyToMany(() => OrganizationDepartment, (it) => it.members, { onUpdate: 'CASCADE', onDelete: 'CASCADE', }) @@ -549,7 +544,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee * Organization Employment Types */ @ApiProperty({ type: () => OrganizationEmploymentType }) - @ManyToMany(() => OrganizationEmploymentType, (it) => it.members, { + @MultiORMManyToMany(() => OrganizationEmploymentType, (it) => it.members, { onUpdate: 'CASCADE', onDelete: 'CASCADE', }) @@ -559,13 +554,13 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee * Employee Job Presets */ @ApiProperty({ type: () => JobPreset }) - @ManyToMany(() => JobPreset, (jobPreset) => jobPreset.employees) + @MultiORMManyToMany(() => JobPreset, (jobPreset) => jobPreset.employees) jobPresets?: JobPreset[]; /** * Employee Organization Contacts */ - @ManyToMany(() => OrganizationContact, (it) => it.members, { + @MultiORMManyToMany(() => OrganizationContact, (it) => it.members, { onUpdate: 'CASCADE', onDelete: 'CASCADE', }) @@ -574,12 +569,14 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee /** * TimeOffPolicy */ - @ManyToMany( + @MultiORMManyToMany( () => TimeOffPolicy, (timeOffPolicy) => timeOffPolicy.employees, { onUpdate: 'CASCADE', onDelete: 'CASCADE', + pivotTable: 'time_off_policy_employee', + owner: true, } ) @JoinTable({ @@ -590,12 +587,14 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee /** * TimeOffRequest */ - @ManyToMany( + @MultiORMManyToMany( () => TimeOffRequest, (timeOffRequest) => timeOffRequest.employees, { onUpdate: 'CASCADE', onDelete: 'CASCADE', + owner: true, + pivotTable: 'time_off_request_employee', } ) @JoinTable({ @@ -606,7 +605,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee /** * Task */ - @ManyToMany(() => Task, (task) => task.members, { + @MultiORMManyToMany(() => Task, (task) => task.members, { onUpdate: 'CASCADE', onDelete: 'CASCADE', }) @@ -616,7 +615,7 @@ export class Employee extends TenantOrganizationBaseEntity implements IEmployee /** * Equipment Sharing */ - @ManyToMany(() => EquipmentSharing, (it) => it.employees, { + @MultiORMManyToMany(() => EquipmentSharing, (it) => it.employees, { onUpdate: 'CASCADE', onDelete: 'CASCADE', }) diff --git a/packages/core/src/employee/employee.service.ts b/packages/core/src/employee/employee.service.ts index a22d2f0cf44..47af7b50d3e 100644 --- a/packages/core/src/employee/employee.service.ts +++ b/packages/core/src/employee/employee.service.ts @@ -216,7 +216,7 @@ export class EmployeeService extends TenantAwareCrudService { const tenantId = RequestContext.currentTenantId(); // Create a query builder for the Employee entity - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); // Tables joins with relations query.innerJoin(`${query.alias}.user`, 'user'); diff --git a/packages/core/src/equipment-sharing-policy/equipment-sharing-policy.entity.ts b/packages/core/src/equipment-sharing-policy/equipment-sharing-policy.entity.ts index 6d5e699a6de..09d68bc9cd4 100644 --- a/packages/core/src/equipment-sharing-policy/equipment-sharing-policy.entity.ts +++ b/packages/core/src/equipment-sharing-policy/equipment-sharing-policy.entity.ts @@ -3,13 +3,14 @@ * E.g. for example, "Business Trip", "Borrow Items", ... * Approval Policy table has the many to one relationship to the Organization table and Tenant by organizationId and tenantId */ -import { Index, Column, OneToMany } from 'typeorm'; +import { Index } from 'typeorm'; import { IEquipmentSharing, IEquipmentSharingPolicy } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsNotEmpty } from 'class-validator'; import { EquipmentSharing, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEquipmentSharingPolicyRepository } from './repository/mikro-orm-equipment-sharing-policy.repository'; +import { MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('equipment_sharing_policy', { mikroOrmRepository: () => MikroOrmEquipmentSharingPolicyRepository }) export class EquipmentSharingPolicy extends TenantOrganizationBaseEntity implements IEquipmentSharingPolicy { @@ -18,12 +19,12 @@ export class EquipmentSharingPolicy extends TenantOrganizationBaseEntity impleme @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; /* @@ -36,7 +37,7 @@ export class EquipmentSharingPolicy extends TenantOrganizationBaseEntity impleme * EquipmentSharing */ @ApiProperty({ type: () => EquipmentSharing, isArray: true }) - @OneToMany(() => EquipmentSharing, (it) => it.equipmentSharingPolicy, { + @MultiORMOneToMany(() => EquipmentSharing, (it) => it.equipmentSharingPolicy, { onDelete: 'CASCADE' }) equipmentSharings?: IEquipmentSharing[]; diff --git a/packages/core/src/equipment-sharing/equipment-sharing.entity.ts b/packages/core/src/equipment-sharing/equipment-sharing.entity.ts index e442f517f0e..906f077318a 100644 --- a/packages/core/src/equipment-sharing/equipment-sharing.entity.ts +++ b/packages/core/src/equipment-sharing/equipment-sharing.entity.ts @@ -1,10 +1,7 @@ import { RelationId, - Column, - ManyToMany, JoinTable, JoinColumn, - ManyToOne, Index } from 'typeorm'; import { @@ -22,38 +19,39 @@ import { OrganizationTeam, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEquipmentSharingRepository } from './repository/mikro-orm-equipment-sharing.repository'; +import { MultiORMManyToMany, MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('equipment_sharing', { mikroOrmRepository: () => MikroOrmEquipmentSharingRepository }) export class EquipmentSharing extends TenantOrganizationBaseEntity implements IEquipmentSharing { @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) name: string; @ApiProperty({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) shareRequestDay: Date; @ApiProperty({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) shareStartDay: Date; @ApiProperty({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) shareEndDay: Date; - @Column() + @MultiORMColumn() status: number; @ApiProperty({ type: () => String, readOnly: true }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) createdBy: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) createdByName: string; /* @@ -66,7 +64,7 @@ export class EquipmentSharing extends TenantOrganizationBaseEntity * Equipment */ @ApiProperty({ type: () => Equipment }) - @ManyToOne(() => Equipment, (equipment) => equipment.equipmentSharings, { + @MultiORMManyToOne(() => Equipment, (equipment) => equipment.equipmentSharings, { onDelete: 'CASCADE' }) @JoinColumn() @@ -75,14 +73,14 @@ export class EquipmentSharing extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: EquipmentSharing) => it.equipment) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) equipmentId: IEquipment['id']; /** * Equipment */ @ApiProperty({ type: () => EquipmentSharingPolicy }) - @ManyToOne(() => EquipmentSharingPolicy, (it) => it.equipmentSharings, { + @MultiORMManyToOne(() => EquipmentSharingPolicy, (it) => it.equipmentSharings, { onDelete: 'CASCADE' }) @JoinColumn() @@ -91,7 +89,7 @@ export class EquipmentSharing extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: EquipmentSharing) => it.equipmentSharingPolicy) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) equipmentSharingPolicyId: IEquipmentSharingPolicy['id']; /* @@ -103,9 +101,11 @@ export class EquipmentSharing extends TenantOrganizationBaseEntity /** * Employee */ - @ManyToMany(() => Employee, (it) => it.equipmentSharings, { + @MultiORMManyToMany(() => Employee, (it) => it.equipmentSharings, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'equipment_shares_employees', }) @JoinTable({ name: 'equipment_shares_employees' @@ -115,9 +115,11 @@ export class EquipmentSharing extends TenantOrganizationBaseEntity /** * OrganizationTeam */ - @ManyToMany(() => OrganizationTeam, (it) => it.equipmentSharings, { + @MultiORMManyToMany(() => OrganizationTeam, (it) => it.equipmentSharings, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'equipment_shares_teams', }) @JoinTable({ name: 'equipment_shares_teams' diff --git a/packages/core/src/equipment/equipment.entity.ts b/packages/core/src/equipment/equipment.entity.ts index b4716282853..be4a5b64f4c 100644 --- a/packages/core/src/equipment/equipment.entity.ts +++ b/packages/core/src/equipment/equipment.entity.ts @@ -6,11 +6,7 @@ import { IImageAsset } from '@gauzy/contracts'; import { - Column, - OneToMany, - ManyToMany, JoinTable, - ManyToOne, JoinColumn } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; @@ -29,8 +25,9 @@ import { ImageAsset } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEquipmentRepository } from './repository/mikro-orm-equipment.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('equipment', { mikroOrmRepository: () => MikroOrmEquipmentRepository }) export class Equipment extends TenantOrganizationBaseEntity implements IEquipment { @@ -38,24 +35,24 @@ export class Equipment extends TenantOrganizationBaseEntity implements IEquipmen @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column() + @MultiORMColumn() type: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column() + @MultiORMColumn() serialNumber?: string; @ApiProperty({ type: () => Number }) @IsNumber() @IsOptional() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -65,7 +62,7 @@ export class Equipment extends TenantOrganizationBaseEntity implements IEquipmen @ApiProperty({ type: () => Number }) @IsNumber() @IsOptional() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -75,13 +72,13 @@ export class Equipment extends TenantOrganizationBaseEntity implements IEquipmen @ApiProperty({ type: () => String, enum: CurrenciesEnum }) @IsEnum(CurrenciesEnum) @IsOptional() - @Column() + @MultiORMColumn() currency: string; @ApiProperty({ type: () => Number }) @IsNumber() @IsOptional() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -91,7 +88,7 @@ export class Equipment extends TenantOrganizationBaseEntity implements IEquipmen @ApiProperty({ type: () => Boolean }) @IsBoolean() @IsOptional() - @Column() + @MultiORMColumn() autoApproveShare: boolean; /* @@ -104,7 +101,7 @@ export class Equipment extends TenantOrganizationBaseEntity implements IEquipmen * ImageAsset */ @ApiProperty({ type: () => ImageAsset }) - @ManyToOne(() => ImageAsset, (imageAsset) => imageAsset.equipmentImage, { + @MultiORMManyToOne(() => ImageAsset, (imageAsset) => imageAsset.equipmentImage, { onDelete: 'SET NULL' }) @JoinColumn() @@ -120,7 +117,7 @@ export class Equipment extends TenantOrganizationBaseEntity implements IEquipmen * EquipmentSharing */ @ApiProperty({ type: () => EquipmentSharing, isArray: true }) - @OneToMany(() => EquipmentSharing, (equipmentSharing) => equipmentSharing.equipment, { + @MultiORMOneToMany(() => EquipmentSharing, (equipmentSharing) => equipmentSharing.equipment, { onDelete: 'CASCADE' }) equipmentSharings: IEquipmentSharing[]; @@ -132,9 +129,11 @@ export class Equipment extends TenantOrganizationBaseEntity implements IEquipmen */ @ApiProperty({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.equipments, { + @MultiORMManyToMany(() => Tag, (tag) => tag.equipments, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_equipment', }) @JoinTable({ name: 'tag_equipment' }) tags: ITag[]; diff --git a/packages/core/src/estimate-email/estimate-email.entity.ts b/packages/core/src/estimate-email/estimate-email.entity.ts index 3264b422871..adb9273d74c 100644 --- a/packages/core/src/estimate-email/estimate-email.entity.ts +++ b/packages/core/src/estimate-email/estimate-email.entity.ts @@ -1,9 +1,8 @@ import { IEstimateEmail } from '@gauzy/contracts'; import { isMySQL } from '@gauzy/config'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column } from 'typeorm'; import { TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEstimateEmailRepository } from './repository/mikro-orm-estimate-email.repository'; @MultiORMEntity('estimate_email', { mikroOrmRepository: () => MikroOrmEstimateEmailRepository }) @@ -11,20 +10,20 @@ export class EstimateEmail extends TenantOrganizationBaseEntity implements IEstimateEmail { @ApiProperty({ type: () => String }) - @Column({ + @MultiORMColumn({ ...(isMySQL() ? { type: 'text' } : {}) }) token?: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() email?: string; @ApiProperty({ type: () => Date }) - @Column() + @MultiORMColumn() expireDate?: Date; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) convertAcceptedEstimates?: boolean; } diff --git a/packages/core/src/event-types/event-type.entity.ts b/packages/core/src/event-types/event-type.entity.ts index 554895235fc..bd9668c769d 100644 --- a/packages/core/src/event-types/event-type.entity.ts +++ b/packages/core/src/event-types/event-type.entity.ts @@ -1,10 +1,7 @@ import { - Column, Index, - ManyToOne, RelationId, JoinColumn, - ManyToMany, JoinTable } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; @@ -21,8 +18,9 @@ import { TenantOrganizationBaseEntity } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmEventTypeRepository } from './repository/mikro-orm-event-type.repository'; +import { MultiORMManyToMany, MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('event_type', { mikroOrmRepository: () => MikroOrmEventTypeRepository }) export class EventType extends TenantOrganizationBaseEntity implements IEventType { @@ -31,7 +29,7 @@ export class EventType extends TenantOrganizationBaseEntity implements IEventTyp @IsNumber() @IsNotEmpty() @Index() - @Column({ + @MultiORMColumn({ type: 'numeric', transformer: new ColumnNumericTransformerPipe() }) @@ -40,18 +38,18 @@ export class EventType extends TenantOrganizationBaseEntity implements IEventTyp @ApiProperty({ type: () => String }) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) durationUnit: string; @ApiProperty({ type: () => String }) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) title: string; @ApiPropertyOptional({ type: () => String }) @Index() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @@ -65,7 +63,7 @@ export class EventType extends TenantOrganizationBaseEntity implements IEventTyp * Employee */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { onDelete: 'CASCADE' }) + @MultiORMManyToOne(() => Employee, { onDelete: 'CASCADE' }) @JoinColumn() employee?: IEmployee; @@ -74,7 +72,7 @@ export class EventType extends TenantOrganizationBaseEntity implements IEventTyp @IsOptional() @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly employeeId?: string; /* @@ -87,9 +85,11 @@ export class EventType extends TenantOrganizationBaseEntity implements IEventTyp * Tag */ @ApiProperty({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.eventTypes, { + @MultiORMManyToMany(() => Tag, (tag) => tag.eventTypes, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_event_type', }) @JoinTable({ name: 'tag_event_type' diff --git a/packages/core/src/expense-categories/expense-category.entity.ts b/packages/core/src/expense-categories/expense-category.entity.ts index 00cb7954d09..f2916c5ee8b 100644 --- a/packages/core/src/expense-categories/expense-category.entity.ts +++ b/packages/core/src/expense-categories/expense-category.entity.ts @@ -1,10 +1,11 @@ -import { Column, ManyToMany, JoinTable, OneToMany } from 'typeorm'; +import { JoinTable } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; import { IExpense, IExpenseCategory, ITag } from '@gauzy/contracts'; import { Expense, Tag, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmExpenseCategoryRepository } from './repository/mikro-orm-expense-category.repository'; +import { MultiORMManyToMany, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('expense_category', { mikroOrmRepository: () => MikroOrmExpenseCategoryRepository }) export class ExpenseCategory extends TenantOrganizationBaseEntity implements IExpenseCategory { @@ -12,7 +13,7 @@ export class ExpenseCategory extends TenantOrganizationBaseEntity implements IEx @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column() + @MultiORMColumn() name: string; /* @@ -25,7 +26,7 @@ export class ExpenseCategory extends TenantOrganizationBaseEntity implements IEx * Expense */ @ApiPropertyOptional({ type: () => Expense, isArray: true }) - @OneToMany(() => Expense, (expense) => expense.category, { + @MultiORMOneToMany(() => Expense, (expense) => expense.category, { onDelete: 'SET NULL' }) expenses?: IExpense[]; @@ -40,9 +41,11 @@ export class ExpenseCategory extends TenantOrganizationBaseEntity implements IEx * Tag */ @ApiPropertyOptional({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.expenseCategories, { + @MultiORMManyToMany(() => Tag, (tag) => tag.expenseCategories, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_organization_expense_category', }) @JoinTable({ name: 'tag_organization_expense_category' diff --git a/packages/core/src/expense/expense.entity.ts b/packages/core/src/expense/expense.entity.ts index c475b563cd3..626ff58b854 100644 --- a/packages/core/src/expense/expense.entity.ts +++ b/packages/core/src/expense/expense.entity.ts @@ -1,12 +1,8 @@ import { - Column, Index, - ManyToOne, RelationId, JoinColumn, - ManyToMany, - JoinTable, - OneToMany + JoinTable } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { @@ -41,8 +37,9 @@ import { TenantOrganizationBaseEntity } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmExpenseRepository } from './repository/mikro-orm-expense.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('expense', { mikroOrmRepository: () => MikroOrmExpenseRepository }) export class Expense extends TenantOrganizationBaseEntity implements IExpense { @@ -51,7 +48,7 @@ export class Expense extends TenantOrganizationBaseEntity implements IExpense { @IsNumber() @IsNotEmpty() @Index() - @Column({ + @MultiORMColumn({ type: 'numeric', transformer: new ColumnNumericTransformerPipe() }) @@ -61,51 +58,51 @@ export class Expense extends TenantOrganizationBaseEntity implements IExpense { @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) typeOfExpense: string; @ApiPropertyOptional({ type: () => String }) @Index() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) notes?: string; @ApiProperty({ type: () => String, enum: CurrenciesEnum }) @IsEnum(CurrenciesEnum) @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() currency: string; @ApiPropertyOptional({ type: () => Date }) @IsDate() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) valueDate?: Date; @ApiPropertyOptional({ type: () => String }) @Index() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) purpose?: string; @ApiPropertyOptional({ type: () => String }) @Index() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) taxType?: string; @ApiPropertyOptional({ type: () => String }) @Index() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) taxLabel?: string; @ApiProperty({ type: () => Number }) @IsNumber() @Index() @IsOptional() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -115,22 +112,22 @@ export class Expense extends TenantOrganizationBaseEntity implements IExpense { @ApiPropertyOptional({ type: () => String }) @Index() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) receipt?: string; @ApiProperty({ type: () => Boolean }) @IsBoolean() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) splitExpense: boolean; @ApiPropertyOptional({ type: () => String, maxLength: 256 }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) reference?: string; @ApiPropertyOptional({ type: () => String, enum: ExpenseStatusesEnum }) - @Column({ + @MultiORMColumn({ type: 'simple-enum', nullable: true, enum: ExpenseStatusesEnum @@ -146,7 +143,7 @@ export class Expense extends TenantOrganizationBaseEntity implements IExpense { * Employee */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, (employee) => employee.expenses, { + @MultiORMManyToOne(() => Employee, (employee) => employee.expenses, { nullable: true, onDelete: 'CASCADE' }) @@ -158,14 +155,14 @@ export class Expense extends TenantOrganizationBaseEntity implements IExpense { @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) employeeId?: string; /** * OrganizationVendor */ @ApiProperty({ type: () => OrganizationVendor }) - @ManyToOne(() => OrganizationVendor, (vendor) => vendor.expenses, { + @MultiORMManyToOne(() => OrganizationVendor, (vendor) => vendor.expenses, { onDelete: 'SET NULL' }) @JoinColumn() @@ -175,14 +172,14 @@ export class Expense extends TenantOrganizationBaseEntity implements IExpense { @RelationId((it: Expense) => it.vendor) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) vendorId: string; /** * ExpenseCategory */ @ApiProperty({ type: () => ExpenseCategory }) - @ManyToOne(() => ExpenseCategory, (category) => category.expenses, { + @MultiORMManyToOne(() => ExpenseCategory, (category) => category.expenses, { onDelete: 'SET NULL' }) @JoinColumn() @@ -192,14 +189,14 @@ export class Expense extends TenantOrganizationBaseEntity implements IExpense { @RelationId((it: Expense) => it.category) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) categoryId: string; /** * Organization Project Relationship */ @ApiProperty({ type: () => OrganizationProject }) - @ManyToOne(() => OrganizationProject, (project) => project.expenses, { + @MultiORMManyToOne(() => OrganizationProject, (project) => project.expenses, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -217,14 +214,14 @@ export class Expense extends TenantOrganizationBaseEntity implements IExpense { @IsUUID() @RelationId((it: Expense) => it.project) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: string; /** * OrganizationContact */ @ApiProperty({ type: () => OrganizationContact }) - @ManyToOne(() => OrganizationContact, (contact) => contact.expenses, { + @MultiORMManyToOne(() => OrganizationContact, (contact) => contact.expenses, { nullable: true, onDelete: 'CASCADE' }) @@ -236,7 +233,7 @@ export class Expense extends TenantOrganizationBaseEntity implements IExpense { @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationContactId?: string; /* @@ -249,7 +246,7 @@ export class Expense extends TenantOrganizationBaseEntity implements IExpense { * InvoiceItem */ @ApiPropertyOptional({ type: () => InvoiceItem, isArray: true }) - @OneToMany(() => InvoiceItem, (invoiceItem) => invoiceItem.expense, { + @MultiORMOneToMany(() => InvoiceItem, (invoiceItem) => invoiceItem.expense, { onDelete: 'SET NULL' }) @JoinColumn() @@ -265,9 +262,11 @@ export class Expense extends TenantOrganizationBaseEntity implements IExpense { * Tag */ @ApiProperty({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.expenses, { + @MultiORMManyToMany(() => Tag, (tag) => tag.expenses, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_expense', }) @JoinTable({ name: 'tag_expense' diff --git a/packages/core/src/export-import/import-history/import-history.entity.ts b/packages/core/src/export-import/import-history/import-history.entity.ts index f778e2fe1d6..e213fb3bd22 100644 --- a/packages/core/src/export-import/import-history/import-history.entity.ts +++ b/packages/core/src/export-import/import-history/import-history.entity.ts @@ -1,10 +1,9 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column } from 'typeorm'; import { IsDate, IsEnum, IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; import { Exclude } from 'class-transformer'; import { IImportHistory, ImportStatusEnum } from '@gauzy/contracts'; import { TenantBaseEntity } from '../../core/entities/internal'; -import { MultiORMEntity } from '../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from '../../core/decorators/entity'; import { MikroOrmImportHistoryRepository } from './repository/mikro-orm-import-history.repository'; @MultiORMEntity('import-history', { mikroOrmRepository: () => MikroOrmImportHistoryRepository }) @@ -12,31 +11,31 @@ export class ImportHistory extends TenantBaseEntity implements IImportHistory { @ApiProperty({ type: () => String }) @IsNotEmpty() - @Column() + @MultiORMColumn() file: string; @Exclude({ toPlainOnly: true }) @ApiProperty({ type: () => String }) @IsNotEmpty() - @Column() + @MultiORMColumn() path: string; @ApiPropertyOptional({ type: () => Number }) @IsOptional() @IsNumber() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) size: number; @ApiProperty({ type: () => String, enum: ImportStatusEnum }) @IsNotEmpty() @IsEnum(ImportStatusEnum) - @Column() + @MultiORMColumn() status: ImportStatusEnum; @ApiPropertyOptional({ type: () => Date }) @IsOptional() @IsDate() - @Column({ default: () => 'CURRENT_TIMESTAMP' }) + @MultiORMColumn({ default: () => 'CURRENT_TIMESTAMP' }) importDate?: Date; public fullUrl?: string; diff --git a/packages/core/src/export-import/import-record/import-record.entity.ts b/packages/core/src/export-import/import-record/import-record.entity.ts index 791f9d5b178..1bef1df5556 100644 --- a/packages/core/src/export-import/import-record/import-record.entity.ts +++ b/packages/core/src/export-import/import-record/import-record.entity.ts @@ -1,8 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Column } from 'typeorm'; import { IsDate } from 'class-validator'; import { IImportRecord } from '@gauzy/contracts'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../../core/decorators/entity'; import { TenantBaseEntity } from '../../core/entities/internal'; import { MikroOrmImportRecordRepository } from './repository/mikro-orm-import-record.repository'; @@ -10,20 +9,20 @@ import { MikroOrmImportRecordRepository } from './repository/mikro-orm-import-re export class ImportRecord extends TenantBaseEntity implements IImportRecord { @ApiProperty({ type: () => String }) - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false }) entityType: string; @ApiProperty({ type: () => String }) - @Column({ nullable: false, type: 'uuid' }) + @MultiORMColumn({ nullable: false, type: 'uuid' }) sourceId: string; @ApiProperty({ type: () => String }) - @Column({ nullable: false, type: 'uuid' }) + @MultiORMColumn({ nullable: false, type: 'uuid' }) destinationId: string; @ApiProperty({ type: () => Date }) @IsDate() - @Column({ nullable: false, default: () => 'CURRENT_TIMESTAMP' }) + @MultiORMColumn({ nullable: false, default: () => 'CURRENT_TIMESTAMP' }) importDate?: Date; wasCreated?: boolean; diff --git a/packages/core/src/feature/feature-organization.entity.ts b/packages/core/src/feature/feature-organization.entity.ts index cb6382989b4..dcb24a8d813 100644 --- a/packages/core/src/feature/feature-organization.entity.ts +++ b/packages/core/src/feature/feature-organization.entity.ts @@ -1,18 +1,19 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Column, Index, JoinColumn, ManyToOne, RelationId } from 'typeorm'; +import { Index, JoinColumn, RelationId } from 'typeorm'; import { IFeature, IFeatureOrganization } from '@gauzy/contracts'; import { Feature, TenantOrganizationBaseEntity } from '../core/entities/internal'; import { IsString } from 'class-validator'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmFeatureOrganizationRepository } from './repository/mikro-orm-feature-organization.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('feature_organization', { mikroOrmRepository: () => MikroOrmFeatureOrganizationRepository }) export class FeatureOrganization extends TenantOrganizationBaseEntity implements IFeatureOrganization { - @Column({ default: true }) + @MultiORMColumn({ default: true }) isEnabled: boolean; /* @@ -25,16 +26,16 @@ export class FeatureOrganization extends TenantOrganizationBaseEntity implements * Feature */ @ApiProperty({ type: () => Feature }) - @ManyToOne(() => Feature, (feature) => feature.featureOrganizations, { + @MultiORMManyToOne(() => Feature, (it) => it.featureOrganizations, { onDelete: 'CASCADE' }) @JoinColumn() feature: IFeature; - @ApiProperty({ type: () => String, readOnly: true }) + @ApiProperty({ type: () => String }) @RelationId((it: FeatureOrganization) => it.feature) @IsString() @Index() - @Column() - readonly featureId: string; + @MultiORMColumn({ relationId: true }) + featureId: IFeature['id']; } diff --git a/packages/core/src/feature/feature.entity.ts b/packages/core/src/feature/feature.entity.ts index d431b44990f..8a1ead5999d 100644 --- a/packages/core/src/feature/feature.entity.ts +++ b/packages/core/src/feature/feature.entity.ts @@ -1,9 +1,6 @@ import { - Column, Index, JoinColumn, - ManyToOne, - OneToMany, RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; @@ -13,44 +10,45 @@ import { IFeatureOrganization } from '@gauzy/contracts'; import { BaseEntity, FeatureOrganization } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmFeatureRepository } from './repository/mikro-orm-feature.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('feature', { mikroOrmRepository: () => MikroOrmFeatureRepository }) export class Feature extends BaseEntity implements IFeature { @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() code: FeatureEnum; @ApiProperty({ type: () => Boolean, default: false }) - @Column({ default: false }) + @MultiORMColumn({ default: false }) isPaid?: boolean; @ApiProperty({ type: () => String, readOnly: true }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiProperty({ type: () => String, readOnly: true }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) image: string; @ApiProperty({ type: () => String, readOnly: true }) - @Column() + @MultiORMColumn() link: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) status: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) icon: string; isEnabled?: boolean; @@ -66,7 +64,7 @@ export class Feature extends BaseEntity implements IFeature { * Feature */ @ApiProperty({ type: () => Feature }) - @ManyToOne(() => Feature, (feature) => feature.children, { + @MultiORMManyToOne(() => Feature, (feature) => feature.children, { onDelete: 'CASCADE' }) parent: IFeature; @@ -74,7 +72,7 @@ export class Feature extends BaseEntity implements IFeature { @ApiProperty({ type: () => String }) @RelationId((it: Feature) => it.parent) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) parentId?: string; /* @@ -87,7 +85,7 @@ export class Feature extends BaseEntity implements IFeature { * FeatureOrganization */ @ApiProperty({ type: () => FeatureOrganization }) - @OneToMany(() => FeatureOrganization, (featureOrganization) => featureOrganization.feature, { + @MultiORMOneToMany(() => FeatureOrganization, (featureOrganization) => featureOrganization.feature, { cascade: true }) @JoinColumn() @@ -97,7 +95,7 @@ export class Feature extends BaseEntity implements IFeature { * Feature */ @ApiProperty({ type: () => Feature }) - @OneToMany(() => Feature, (feature) => feature.parent, { + @MultiORMOneToMany(() => Feature, (feature) => feature.parent, { cascade: true }) @JoinColumn({ name: 'parentId' }) diff --git a/packages/core/src/feature/feature.subscriber.ts b/packages/core/src/feature/feature.subscriber.ts index 65e3f82ca92..4f22433bec4 100644 --- a/packages/core/src/feature/feature.subscriber.ts +++ b/packages/core/src/feature/feature.subscriber.ts @@ -16,25 +16,22 @@ export class FeatureSubscriber implements EntitySubscriberInterface { } /** - * Called after entity is loaded from the database. + * Called after an entity is loaded from the database. * - * @param entity + * @param entity - The loaded Feature entity. + * @param event - The LoadEvent associated with the entity loading. */ - async afterLoad( - entity: Feature, - event?: LoadEvent - ): Promise { + async afterLoad(entity: Feature, event?: LoadEvent): Promise { try { + // Set a default status if not present if (!entity.status) { entity.status = shuffle(Object.values(FeatureStatusEnum))[0]; } - if (gauzyToggleFeatures.hasOwnProperty(entity.code)) { - entity.isEnabled = !!gauzyToggleFeatures[entity.code]; - } else { - entity.isEnabled = true; - } + // Check and set isEnabled based on gauzyToggleFeatures + entity.isEnabled = gauzyToggleFeatures.hasOwnProperty(entity.code) ? !!gauzyToggleFeatures[entity.code] : true; + // Set imageUrl based on the entity's image property if (entity.image) { const store = new FileStorage().setProvider(FileStorageProviderEnum.LOCAL); entity.imageUrl = await store.getProviderInstance().url(entity.image); diff --git a/packages/core/src/goal-general-setting/goal-general-setting.entity.ts b/packages/core/src/goal-general-setting/goal-general-setting.entity.ts index 61cf703ab0b..160f4748db5 100644 --- a/packages/core/src/goal-general-setting/goal-general-setting.entity.ts +++ b/packages/core/src/goal-general-setting/goal-general-setting.entity.ts @@ -1,41 +1,40 @@ import { IGoalGeneralSetting, GoalOwnershipEnum } from '@gauzy/contracts'; -import { Column } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum } from 'class-validator'; import { TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmGoalGeneralSettingRepository } from './repository/mikro-orm-goal-general-setting.repository'; @MultiORMEntity('goal_general_setting', { mikroOrmRepository: () => MikroOrmGoalGeneralSettingRepository }) export class GoalGeneralSetting extends TenantOrganizationBaseEntity implements IGoalGeneralSetting { @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() maxObjectives: number; @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() maxKeyResults: number; @ApiProperty({ type: () => Boolean }) - @Column() + @MultiORMColumn() employeeCanCreateObjective: boolean; @ApiProperty({ type: () => String, enum: GoalOwnershipEnum }) @IsEnum(GoalOwnershipEnum) - @Column() + @MultiORMColumn() canOwnObjectives: string; @ApiProperty({ type: () => String, enum: GoalOwnershipEnum }) @IsEnum(GoalOwnershipEnum) - @Column() + @MultiORMColumn() canOwnKeyResult: string; @ApiProperty({ type: () => Boolean }) - @Column() + @MultiORMColumn() krTypeKPI: boolean; @ApiProperty({ type: () => Boolean }) - @Column() + @MultiORMColumn() krTypeTask: boolean; } diff --git a/packages/core/src/goal-kpi-template/goal-kpi-template.entity.ts b/packages/core/src/goal-kpi-template/goal-kpi-template.entity.ts index 36943cc3e29..3bc16b40032 100644 --- a/packages/core/src/goal-kpi-template/goal-kpi-template.entity.ts +++ b/packages/core/src/goal-kpi-template/goal-kpi-template.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, JoinColumn } from 'typeorm'; +import { JoinColumn } from 'typeorm'; import { IGoalKPITemplate, KpiMetricEnum, @@ -10,46 +10,47 @@ import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmGoalKPITemplateRepository } from './repository/mikro-orm-goal-kpi-template.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('goal_kpi_template', { mikroOrmRepository: () => MikroOrmGoalKPITemplateRepository }) export class GoalKPITemplate extends TenantOrganizationBaseEntity implements IGoalKPITemplate { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiProperty({ type: () => String, enum: KpiMetricEnum }) - @Column() + @MultiORMColumn() @IsEnum(KpiMetricEnum) type: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() unit?: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() operator: string; @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { nullable: true }) + @MultiORMManyToOne(() => Employee, { nullable: true }) @JoinColumn() @IsOptional() lead?: IEmployee; @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() @IsOptional() currentValue?: number; @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() @IsOptional() targetValue?: number; } diff --git a/packages/core/src/goal-kpi/goal-kpi.entity.ts b/packages/core/src/goal-kpi/goal-kpi.entity.ts index 97ef75ef9e6..c25862e886b 100644 --- a/packages/core/src/goal-kpi/goal-kpi.entity.ts +++ b/packages/core/src/goal-kpi/goal-kpi.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, Index, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { IEmployee, IKPI, KpiMetricEnum } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsOptional, IsString } from 'class-validator'; @@ -6,39 +6,40 @@ import { Employee, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmGoalKPIRepository } from './repository/mikro-orm-goal-kpi.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('goal_kpi', { mikroOrmRepository: () => MikroOrmGoalKPIRepository }) export class GoalKPI extends TenantOrganizationBaseEntity implements IKPI { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiProperty({ type: () => String, enum: KpiMetricEnum }) - @Column() + @MultiORMColumn() @IsEnum(KpiMetricEnum) type: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() unit?: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() operator: string; @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() currentValue: number; @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() targetValue: number; /* @@ -51,7 +52,7 @@ export class GoalKPI extends TenantOrganizationBaseEntity implements IKPI { * Employee */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { nullable: true }) + @MultiORMManyToOne(() => Employee, { nullable: true }) lead?: IEmployee; @ApiProperty({ type: () => String, readOnly: true }) @@ -59,6 +60,6 @@ export class GoalKPI extends TenantOrganizationBaseEntity implements IKPI { @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) leadId?: string; } diff --git a/packages/core/src/goal-template/goal-template.entity.ts b/packages/core/src/goal-template/goal-template.entity.ts index 02cfd648555..3afaa5db38b 100644 --- a/packages/core/src/goal-template/goal-template.entity.ts +++ b/packages/core/src/goal-template/goal-template.entity.ts @@ -4,34 +4,34 @@ import { GoalTemplateCategoriesEnum, IKeyResultTemplate } from '@gauzy/contracts'; -import { Column, OneToMany } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum } from 'class-validator'; import { KeyResultTemplate, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmGoalTemplateRepository } from './repository/mikro-orm-goal-template.repository'; +import { MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('goal_template', { mikroOrmRepository: () => MikroOrmGoalTemplateRepository }) export class GoalTemplate extends TenantOrganizationBaseEntity implements IGoalTemplate { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String, enum: GoalLevelEnum }) @IsEnum(GoalLevelEnum) - @Column() + @MultiORMColumn() level: string; @ApiProperty({ type: () => String, enum: GoalTemplateCategoriesEnum }) @IsEnum(GoalTemplateCategoriesEnum) - @Column() + @MultiORMColumn() category: string; @ApiProperty({ type: () => KeyResultTemplate }) - @OneToMany(() => KeyResultTemplate, (keyResult) => keyResult.goal) + @MultiORMOneToMany(() => KeyResultTemplate, (keyResult) => keyResult.goal) keyResults?: IKeyResultTemplate[]; } diff --git a/packages/core/src/goal-time-frame/goal-time-frame.entity.ts b/packages/core/src/goal-time-frame/goal-time-frame.entity.ts index 2b14a9d0a31..504dfaebd1f 100644 --- a/packages/core/src/goal-time-frame/goal-time-frame.entity.ts +++ b/packages/core/src/goal-time-frame/goal-time-frame.entity.ts @@ -1,28 +1,27 @@ -import { Column } from 'typeorm'; import { IGoalTimeFrame, TimeFrameStatusEnum } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum } from 'class-validator'; import { TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmGoalTimeFrameRepository } from './repository/mikro-orm-goal-time-frame.repository'; @MultiORMEntity('goal_time_frame', { mikroOrmRepository: () => MikroOrmGoalTimeFrameRepository }) export class GoalTimeFrame extends TenantOrganizationBaseEntity implements IGoalTimeFrame { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String, enum: TimeFrameStatusEnum }) @IsEnum(TimeFrameStatusEnum) - @Column() + @MultiORMColumn() status: string; @ApiProperty({ type: () => Date }) - @Column() + @MultiORMColumn() startDate: Date; @ApiProperty({ type: () => Date }) - @Column() + @MultiORMColumn() endDate: Date; } diff --git a/packages/core/src/goal/goal.entity.ts b/packages/core/src/goal/goal.entity.ts index 8b672f7b317..448395af7d4 100644 --- a/packages/core/src/goal/goal.entity.ts +++ b/packages/core/src/goal/goal.entity.ts @@ -1,5 +1,5 @@ import { IGoal, GoalLevelEnum, IKeyResult, IOrganizationTeam, IEmployee } from '@gauzy/contracts'; -import { Column, OneToMany, ManyToOne, Index, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional, IsEnum, IsString } from 'class-validator'; import { @@ -8,32 +8,33 @@ import { OrganizationTeam, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmGoalRepository } from './repository/mikro-orm-goal.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('goal', { mikroOrmRepository: () => MikroOrmGoalRepository }) export class Goal extends TenantOrganizationBaseEntity implements IGoal { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() @IsOptional() description?: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() deadline: string; @ApiProperty({ type: () => String, enum: GoalLevelEnum }) @IsEnum(GoalLevelEnum) - @Column() + @MultiORMColumn() level: string; @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() progress: number; /* @@ -46,7 +47,7 @@ export class Goal extends TenantOrganizationBaseEntity implements IGoal { * OrganizationTeam */ @ApiProperty({ type: () => OrganizationTeam }) - @ManyToOne(() => OrganizationTeam, (team) => team.goals, { + @MultiORMManyToOne(() => OrganizationTeam, (team) => team.goals, { onDelete: 'CASCADE' }) ownerTeam?: IOrganizationTeam; @@ -56,14 +57,14 @@ export class Goal extends TenantOrganizationBaseEntity implements IGoal { @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) ownerTeamId?: string; /** * Owner Employee */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, (employee) => employee.goals, { + @MultiORMManyToOne(() => Employee, (employee) => employee.goals, { onDelete: 'CASCADE' }) ownerEmployee?: IEmployee; @@ -73,14 +74,14 @@ export class Goal extends TenantOrganizationBaseEntity implements IGoal { @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) ownerEmployeeId?: string; /** * Lead Employee */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, (employee) => employee.leads, { + @MultiORMManyToOne(() => Employee, (employee) => employee.leads, { onDelete: 'CASCADE' }) lead?: IEmployee; @@ -90,14 +91,14 @@ export class Goal extends TenantOrganizationBaseEntity implements IGoal { @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) leadId?: string; /** * KeyResult */ @ApiProperty({ type: () => KeyResult }) - @ManyToOne(() => KeyResult, (keyResult) => keyResult.id) + @MultiORMManyToOne(() => KeyResult, (keyResult) => keyResult.id) alignedKeyResult?: IKeyResult; @ApiProperty({ type: () => String }) @@ -105,7 +106,7 @@ export class Goal extends TenantOrganizationBaseEntity implements IGoal { @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) alignedKeyResultId?: string; /* @@ -118,7 +119,7 @@ export class Goal extends TenantOrganizationBaseEntity implements IGoal { * KeyResult */ @ApiProperty({ type: () => KeyResult, isArray: true }) - @OneToMany(() => KeyResult, (keyResult) => keyResult.goal, { + @MultiORMOneToMany(() => KeyResult, (keyResult) => keyResult.goal, { cascade: true }) keyResults?: IKeyResult[]; diff --git a/packages/core/src/image-asset/image-asset.entity.ts b/packages/core/src/image-asset/image-asset.entity.ts index 0375b167c2b..e5b087cb7a3 100644 --- a/packages/core/src/image-asset/image-asset.entity.ts +++ b/packages/core/src/image-asset/image-asset.entity.ts @@ -2,7 +2,6 @@ import { FileStorageProviderEnum, IEquipment, IImageAsset, IWarehouse } from '@g import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Exclude } from 'class-transformer'; import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; -import { Column, ManyToMany, OneToMany } from 'typeorm'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; import { Product, @@ -10,8 +9,9 @@ import { Equipment, Warehouse } from './../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmImageAssetRepository } from './repository/mikro-orm-image-asset.repository'; +import { MultiORMManyToMany, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('image_asset', { mikroOrmRepository: () => MikroOrmImageAssetRepository }) export class ImageAsset extends TenantOrganizationBaseEntity implements IImageAsset { @@ -19,37 +19,37 @@ export class ImageAsset extends TenantOrganizationBaseEntity implements IImageAs @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) name: string; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() - @Column() + @MultiORMColumn() url: string @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) thumb?: string; @ApiPropertyOptional({ type: () => Number, default: 0 }) @IsOptional() @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) width?: number; @ApiPropertyOptional({ type: () => Number, default: 0 }) @IsOptional() @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) height?: number; @ApiPropertyOptional({ type: () => Number }) @IsOptional() @IsNumber() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -58,18 +58,18 @@ export class ImageAsset extends TenantOrganizationBaseEntity implements IImageAs @ApiProperty({ type: () => Boolean }) @IsBoolean() - @Column({ default: false }) + @MultiORMColumn({ default: false }) isFeatured?: boolean; @ApiPropertyOptional({ type: () => String }) @IsOptional() @Exclude({ toPlainOnly: true }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) externalProviderId?: string; @ApiPropertyOptional({ type: () => String, enum: FileStorageProviderEnum }) @Exclude({ toPlainOnly: true }) - @Column({ + @MultiORMColumn({ type: 'simple-enum', nullable: true, enum: FileStorageProviderEnum @@ -91,7 +91,7 @@ export class ImageAsset extends TenantOrganizationBaseEntity implements IImageAs * Product */ @ApiProperty({ type: () => Product, isArray: true }) - @OneToMany(() => Product, (product) => product.featuredImage, { + @MultiORMOneToMany(() => Product, (product) => product.featuredImage, { onDelete: 'SET NULL' }) productFeaturedImage?: Product[]; @@ -100,7 +100,7 @@ export class ImageAsset extends TenantOrganizationBaseEntity implements IImageAs * Equipment */ @ApiProperty({ type: () => Equipment, isArray: true }) - @OneToMany(() => Equipment, (equipment) => equipment.image, { + @MultiORMOneToMany(() => Equipment, (equipment) => equipment.image, { onDelete: 'SET NULL' }) equipmentImage?: IEquipment[]; @@ -109,7 +109,7 @@ export class ImageAsset extends TenantOrganizationBaseEntity implements IImageAs * Warehouse */ @ApiProperty({ type: () => Warehouse, isArray: true }) - @OneToMany(() => Warehouse, (warehouse) => warehouse.logo, { + @MultiORMOneToMany(() => Warehouse, (warehouse) => warehouse.logo, { onDelete: 'SET NULL' }) warehouses?: IWarehouse[]; @@ -124,6 +124,6 @@ export class ImageAsset extends TenantOrganizationBaseEntity implements IImageAs * Product */ @ApiProperty({ type: () => Product, isArray: true }) - @ManyToMany(() => Product, (product) => product.gallery) + @MultiORMManyToMany(() => Product, (product) => product.gallery) productGallery?: Product[]; } diff --git a/packages/core/src/income/income.entity.ts b/packages/core/src/income/income.entity.ts index ebf0cf15510..ca6b956187b 100644 --- a/packages/core/src/income/income.entity.ts +++ b/packages/core/src/income/income.entity.ts @@ -1,10 +1,7 @@ import { - Column, Index, JoinColumn, RelationId, - ManyToOne, - ManyToMany, JoinTable } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; @@ -31,8 +28,9 @@ import { TenantOrganizationBaseEntity } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmIncomeRepository } from './repository/mikro-orm-income.repository'; +import { MultiORMManyToMany, MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('income', { mikroOrmRepository: () => MikroOrmIncomeRepository }) export class Income extends TenantOrganizationBaseEntity implements IIncome { @@ -41,7 +39,7 @@ export class Income extends TenantOrganizationBaseEntity implements IIncome { @IsNumber() @IsNotEmpty() @Index() - @Column({ + @MultiORMColumn({ type: 'numeric', transformer: new ColumnNumericTransformerPipe() }) @@ -51,30 +49,30 @@ export class Income extends TenantOrganizationBaseEntity implements IIncome { @IsEnum(CurrenciesEnum) @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() currency: string; @ApiPropertyOptional({ type: () => Date }) @IsDate() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) valueDate?: Date; @ApiPropertyOptional({ type: () => String }) @Index() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) notes?: string; @ApiProperty({ type: () => Boolean }) @IsBoolean() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) isBonus: boolean; @ApiPropertyOptional({ type: () => String, maxLength: 256 }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) reference?: string; /* @@ -86,7 +84,7 @@ export class Income extends TenantOrganizationBaseEntity implements IIncome { * Employee */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { nullable: true, onDelete: 'CASCADE' }) + @MultiORMManyToOne(() => Employee, { nullable: true, onDelete: 'CASCADE' }) @JoinColumn() employee?: IEmployee; @@ -95,14 +93,14 @@ export class Income extends TenantOrganizationBaseEntity implements IIncome { @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) employeeId?: string; /** * Client */ @ApiPropertyOptional({ type: () => () => OrganizationContact }) - @ManyToOne(() => OrganizationContact, (organizationContact) => organizationContact.incomes, { + @MultiORMManyToOne(() => OrganizationContact, (organizationContact) => organizationContact.incomes, { onDelete: 'SET NULL' }) @JoinColumn() @@ -113,7 +111,7 @@ export class Income extends TenantOrganizationBaseEntity implements IIncome { @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) clientId?: string; /* @@ -126,9 +124,11 @@ export class Income extends TenantOrganizationBaseEntity implements IIncome { * Tag */ @ApiProperty({ type: () => () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.incomes, { + @MultiORMManyToMany(() => Tag, (tag) => tag.incomes, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_income', }) @JoinTable({ name: 'tag_income' diff --git a/packages/core/src/integration-entity-setting-tied/integration-entity-setting-tied.entity.ts b/packages/core/src/integration-entity-setting-tied/integration-entity-setting-tied.entity.ts index f6da82f4635..cf5d5264259 100644 --- a/packages/core/src/integration-entity-setting-tied/integration-entity-setting-tied.entity.ts +++ b/packages/core/src/integration-entity-setting-tied/integration-entity-setting-tied.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, JoinColumn, RelationId, ManyToOne, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { IsBoolean, IsEnum, IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; import { IIntegrationEntitySetting, @@ -10,8 +10,9 @@ import { IntegrationEntitySetting, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmIntegrationEntitySettingTiedRepository } from './repository/mikro-orm-integration-entity-setting-tied.repository'; +import { MultiORMManyToOne } from 'core/decorators/entity/relations'; @MultiORMEntity('integration_entity_setting_tied', { mikroOrmRepository: () => MikroOrmIntegrationEntitySettingTiedRepository }) export class IntegrationEntitySettingTied extends TenantOrganizationBaseEntity implements IIntegrationEntitySettingTied { @@ -19,13 +20,13 @@ export class IntegrationEntitySettingTied extends TenantOrganizationBaseEntity i @ApiProperty({ type: () => String, enum: IntegrationEntity }) @IsNotEmpty() @IsEnum(IntegrationEntity) - @Column() + @MultiORMColumn() entity: IntegrationEntity; @ApiProperty({ type: () => Boolean }) @IsNotEmpty() @IsBoolean() - @Column() + @MultiORMColumn() sync: boolean; /* @@ -37,7 +38,7 @@ export class IntegrationEntitySettingTied extends TenantOrganizationBaseEntity i /** * IntegrationEntitySetting */ - @ManyToOne(() => IntegrationEntitySetting, (it) => it.tiedEntities, { + @MultiORMManyToOne(() => IntegrationEntitySetting, (it) => it.tiedEntities, { /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -49,6 +50,6 @@ export class IntegrationEntitySettingTied extends TenantOrganizationBaseEntity i @IsUUID() @RelationId((it: IntegrationEntitySettingTied) => it.integrationEntitySetting) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) integrationEntitySettingId?: IIntegrationEntitySetting['id']; } diff --git a/packages/core/src/integration-entity-setting/integration-entity-setting.entity.ts b/packages/core/src/integration-entity-setting/integration-entity-setting.entity.ts index eb0ddd98d77..557ad5009e5 100644 --- a/packages/core/src/integration-entity-setting/integration-entity-setting.entity.ts +++ b/packages/core/src/integration-entity-setting/integration-entity-setting.entity.ts @@ -1,10 +1,8 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { - Column, + JoinColumn, RelationId, - ManyToOne, - OneToMany, Index } from 'typeorm'; import { IsBoolean, IsEnum, IsNotEmpty, IsUUID } from 'class-validator'; @@ -19,8 +17,9 @@ import { IntegrationTenant, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmIntegrationEntitySettingRepository } from './repository/mikro-orm-integration-entity-setting.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('integration_entity_setting', { mikroOrmRepository: () => MikroOrmIntegrationEntitySettingRepository }) export class IntegrationEntitySetting extends TenantOrganizationBaseEntity implements IIntegrationEntitySetting { @@ -28,13 +27,13 @@ export class IntegrationEntitySetting extends TenantOrganizationBaseEntity imple @ApiProperty({ type: () => String, enum: IntegrationEntity }) @IsNotEmpty() @IsEnum(IntegrationEntity) - @Column() + @MultiORMColumn() entity: IntegrationEntity; @ApiProperty({ type: () => Boolean }) @IsNotEmpty() @IsBoolean() - @Column() + @MultiORMColumn() sync: boolean; /* @@ -47,7 +46,7 @@ export class IntegrationEntitySetting extends TenantOrganizationBaseEntity imple * IntegrationTenant */ @ApiPropertyOptional({ type: () => IntegrationTenant }) - @ManyToOne(() => IntegrationTenant, (it) => it.entitySettings, { + @MultiORMManyToOne(() => IntegrationTenant, (it) => it.entitySettings, { /** Database cascade action on delete. */ onDelete: 'CASCADE', }) @@ -58,7 +57,7 @@ export class IntegrationEntitySetting extends TenantOrganizationBaseEntity imple @IsUUID() @RelationId((it: IntegrationEntitySetting) => it.integration) @Index() - @Column() + @MultiORMColumn({ relationId: true }) integrationId?: IIntegrationTenant['id']; /* @@ -70,7 +69,7 @@ export class IntegrationEntitySetting extends TenantOrganizationBaseEntity imple /** * IntegrationEntitySettingTied */ - @OneToMany(() => IntegrationEntitySettingTied, (it) => it.integrationEntitySetting, { + @MultiORMOneToMany(() => IntegrationEntitySettingTied, (it) => it.integrationEntitySetting, { cascade: true }) @JoinColumn() diff --git a/packages/core/src/integration-map/integration-map.entity.ts b/packages/core/src/integration-map/integration-map.entity.ts index 899f29da137..e2ff1fe8d0f 100644 --- a/packages/core/src/integration-map/integration-map.entity.ts +++ b/packages/core/src/integration-map/integration-map.entity.ts @@ -1,31 +1,32 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Column, JoinColumn, RelationId, ManyToOne, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { IIntegrationMap, IIntegrationTenant, IntegrationEntity } from '@gauzy/contracts'; import { IsNotEmpty, IsUUID } from 'class-validator'; import { IntegrationTenant, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmIntegrationMapRepository } from './repository/mikro-orm-integration-map.repository'; +import { MultiORMManyToOne } from 'core/decorators/entity/relations'; @MultiORMEntity('integration_map', { mikroOrmRepository: () => MikroOrmIntegrationMapRepository }) export class IntegrationMap extends TenantOrganizationBaseEntity implements IIntegrationMap { @ApiProperty({ type: () => String, enum: IntegrationEntity }) @IsNotEmpty() - @Column() + @MultiORMColumn() entity: IntegrationEntity; @ApiProperty({ type: () => String }) @IsNotEmpty() - @Column() + @MultiORMColumn() sourceId: string; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsUUID() - @Column() + @MultiORMColumn() gauzyId: string; /* @@ -34,7 +35,7 @@ export class IntegrationMap extends TenantOrganizationBaseEntity implements IInt |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => IntegrationTenant }) - @ManyToOne(() => IntegrationTenant, (it) => it.entityMaps, { + @MultiORMManyToOne(() => IntegrationTenant, (it) => it.entityMaps, { /** Database cascade action on delete. */ onDelete: 'CASCADE', }) @@ -45,6 +46,6 @@ export class IntegrationMap extends TenantOrganizationBaseEntity implements IInt @IsUUID() @RelationId((it: IntegrationMap) => it.integration) @Index() - @Column() + @MultiORMColumn({ relationId: true }) integrationId: IIntegrationTenant['id']; } diff --git a/packages/core/src/integration-setting/integration-setting.entity.ts b/packages/core/src/integration-setting/integration-setting.entity.ts index 942077e2d1a..9f3266007e3 100644 --- a/packages/core/src/integration-setting/integration-setting.entity.ts +++ b/packages/core/src/integration-setting/integration-setting.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Column, JoinColumn, RelationId, ManyToOne, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { IsNotEmpty, IsUUID } from 'class-validator'; import { Exclude, Expose } from 'class-transformer'; import { IIntegrationSetting } from '@gauzy/contracts'; @@ -8,8 +8,9 @@ import { TenantOrganizationBaseEntity } from './../core/entities/internal'; import { IsSecret } from './../core/decorators'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmIntegrationSettingRepository } from './repository/mikro-orm-integration-setting.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('integration_setting', { mikroOrmRepository: () => MikroOrmIntegrationSettingRepository }) export class IntegrationSetting extends TenantOrganizationBaseEntity implements IIntegrationSetting { @@ -17,13 +18,13 @@ export class IntegrationSetting extends TenantOrganizationBaseEntity implements @Exclude({ toPlainOnly: true }) @ApiProperty({ type: () => String }) @IsNotEmpty() - @Column() + @MultiORMColumn() settingsName: string; @Exclude({ toPlainOnly: true }) @ApiProperty({ type: () => String }) @IsNotEmpty() - @Column() + @MultiORMColumn() settingsValue: string; /* @@ -35,7 +36,7 @@ export class IntegrationSetting extends TenantOrganizationBaseEntity implements /** * IntegrationTenant */ - @ManyToOne(() => IntegrationTenant, (it) => it.settings, { + @MultiORMManyToOne(() => IntegrationTenant, (it) => it.settings, { /** Database cascade action on delete. */ onDelete: 'CASCADE', }) @@ -46,7 +47,7 @@ export class IntegrationSetting extends TenantOrganizationBaseEntity implements @IsUUID() @RelationId((it: IntegrationSetting) => it.integration) @Index() - @Column() + @MultiORMColumn({ relationId: true }) integrationId?: IntegrationTenant['id']; /** diff --git a/packages/core/src/integration-tenant/integration-tenant.entity.ts b/packages/core/src/integration-tenant/integration-tenant.entity.ts index f9dc49eee6a..4791e44792f 100644 --- a/packages/core/src/integration-tenant/integration-tenant.entity.ts +++ b/packages/core/src/integration-tenant/integration-tenant.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, JoinColumn, ManyToOne, OneToMany, RelationId } from 'typeorm'; +import { Index, JoinColumn, RelationId } from 'typeorm'; import { IsDateString, IsEnum, IsNotEmpty, IsUUID } from 'class-validator'; import { IIntegration, IIntegrationEntitySetting, IIntegrationMap, IIntegrationSetting, IIntegrationTenant, IntegrationEnum } from '@gauzy/contracts'; import { @@ -9,8 +9,9 @@ import { IntegrationSetting, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmIntegrationTenantRepository } from './repository/mikro-orm-integration-tenant.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('integration_tenant', { mikroOrmRepository: () => MikroOrmIntegrationTenantRepository }) export class IntegrationTenant extends TenantOrganizationBaseEntity implements IIntegrationTenant { @@ -18,7 +19,7 @@ export class IntegrationTenant extends TenantOrganizationBaseEntity implements I @ApiProperty({ type: () => String, enum: IntegrationEnum }) @IsNotEmpty() @IsEnum(IntegrationEnum) - @Column() + @MultiORMColumn() name: IntegrationEnum; // Date when the integration was synced @@ -29,7 +30,7 @@ export class IntegrationTenant extends TenantOrganizationBaseEntity implements I }) @IsDateString() @Index() - @Column({ nullable: true, default: () => 'CURRENT_TIMESTAMP' }) + @MultiORMColumn({ nullable: true, default: () => 'CURRENT_TIMESTAMP' }) lastSyncedAt?: Date; /* @@ -41,7 +42,7 @@ export class IntegrationTenant extends TenantOrganizationBaseEntity implements I /** * Integration */ - @ManyToOne(() => Integration, { + @MultiORMManyToOne(() => Integration, { /** Indicates if relation column value can be nullable or not. */ nullable: true, @@ -56,7 +57,7 @@ export class IntegrationTenant extends TenantOrganizationBaseEntity implements I @IsUUID() @RelationId((it: IntegrationTenant) => it.integration) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) integrationId?: IIntegration['id']; /* @@ -68,7 +69,7 @@ export class IntegrationTenant extends TenantOrganizationBaseEntity implements I /** * IntegrationSetting */ - @OneToMany(() => IntegrationSetting, (it) => it.integration, { + @MultiORMOneToMany(() => IntegrationSetting, (it) => it.integration, { cascade: true }) @JoinColumn() @@ -77,7 +78,7 @@ export class IntegrationTenant extends TenantOrganizationBaseEntity implements I /** * IntegrationEntitySetting */ - @OneToMany(() => IntegrationEntitySetting, (it) => it.integration, { + @MultiORMOneToMany(() => IntegrationEntitySetting, (it) => it.integration, { cascade: true }) @JoinColumn() @@ -86,7 +87,7 @@ export class IntegrationTenant extends TenantOrganizationBaseEntity implements I /** * IntegrationMap */ - @OneToMany(() => IntegrationMap, (it) => it.integration, { + @MultiORMOneToMany(() => IntegrationMap, (it) => it.integration, { cascade: true }) @JoinColumn() diff --git a/packages/core/src/integration/github/repository/github-repository.entity.ts b/packages/core/src/integration/github/repository/github-repository.entity.ts index 1a9fa5f76e4..7534ae4c698 100644 --- a/packages/core/src/integration/github/repository/github-repository.entity.ts +++ b/packages/core/src/integration/github/repository/github-repository.entity.ts @@ -1,10 +1,11 @@ -import { Column, Index, JoinColumn, ManyToOne, OneToMany, RelationId } from 'typeorm'; +import { Index, JoinColumn, RelationId } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsBoolean, IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID } from 'class-validator'; import { IIntegrationTenant, IOrganizationGithubRepository, IOrganizationGithubRepositoryIssue, IOrganizationProject } from '@gauzy/contracts'; import { IntegrationTenant, OrganizationGithubRepositoryIssue, OrganizationProject, TenantOrganizationBaseEntity } from '../../../core/entities/internal'; -import { MultiORMEntity } from '../../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from '../../../core/decorators/entity'; import { MikroOrmOrganizationGithubRepositoryRepository } from './repository/mikro-orm-candidate.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../../../core/decorators/entity/relations'; @MultiORMEntity('organization_github_repository', { mikroOrmRepository: () => MikroOrmOrganizationGithubRepositoryRepository }) export class OrganizationGithubRepository extends TenantOrganizationBaseEntity implements IOrganizationGithubRepository { @@ -13,55 +14,55 @@ export class OrganizationGithubRepository extends TenantOrganizationBaseEntity i @IsNotEmpty() @IsNumber() @Index() - @Column() + @MultiORMColumn() repositoryId: number; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() fullName: string; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() owner: string; @ApiPropertyOptional({ type: () => Number }) @IsNotEmpty() @IsNumber() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) issuesCount: number; @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() @Index() - @Column({ nullable: true, default: true }) + @MultiORMColumn({ nullable: true, default: true }) hasSyncEnabled: boolean; @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() @Index() - @Column({ nullable: true, default: false }) + @MultiORMColumn({ nullable: true, default: false }) private: boolean; @ApiPropertyOptional({ type: () => String }) @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) status: string; /* @@ -72,7 +73,7 @@ export class OrganizationGithubRepository extends TenantOrganizationBaseEntity i /** What integration tenant sync to */ @ApiProperty({ type: () => IntegrationTenant }) - @ManyToOne(() => IntegrationTenant, { + @MultiORMManyToOne(() => IntegrationTenant, { /** Indicates if relation column value can be nullable or not. */ nullable: true, @@ -86,7 +87,7 @@ export class OrganizationGithubRepository extends TenantOrganizationBaseEntity i @IsUUID() @RelationId((it: OrganizationGithubRepository) => it.integration) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) integrationId: IIntegrationTenant['id']; /* @@ -96,13 +97,13 @@ export class OrganizationGithubRepository extends TenantOrganizationBaseEntity i */ /** Repository Sync Organization Projects */ - @OneToMany(() => OrganizationProject, (it) => it.repository, { + @MultiORMOneToMany(() => OrganizationProject, (it) => it.repository, { cascade: true }) projects?: IOrganizationProject[]; /** Repository Sync Organization Projects */ - @OneToMany(() => OrganizationGithubRepositoryIssue, (it) => it.repository, { + @MultiORMOneToMany(() => OrganizationGithubRepositoryIssue, (it) => it.repository, { cascade: true }) issues?: IOrganizationGithubRepositoryIssue[]; diff --git a/packages/core/src/integration/github/repository/issue/github-repository-issue.entity.ts b/packages/core/src/integration/github/repository/issue/github-repository-issue.entity.ts index f32117a19ae..6f479aa14fd 100644 --- a/packages/core/src/integration/github/repository/issue/github-repository-issue.entity.ts +++ b/packages/core/src/integration/github/repository/issue/github-repository-issue.entity.ts @@ -1,11 +1,12 @@ -import { Column, Index, JoinColumn, ManyToOne, RelationId } from 'typeorm'; +import { Index, JoinColumn, RelationId } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID } from 'class-validator'; import { IOrganizationGithubRepository, IOrganizationGithubRepositoryIssue } from '@gauzy/contracts'; import { TenantOrganizationBaseEntity } from '../../../../core/entities/internal'; -import { MultiORMEntity } from '../../../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from '../../../../core/decorators/entity'; import { OrganizationGithubRepository } from './../github-repository.entity'; import { MikroOrmOrganizationGithubRepositoryIssueRepository } from './repository/mikro-orm-github-repository-issue.repository'; +import { MultiORMManyToOne } from '../../../../core/decorators/entity/relations'; @MultiORMEntity('organization_github_repository_issue', { mikroOrmRepository: () => MikroOrmOrganizationGithubRepositoryIssueRepository }) export class OrganizationGithubRepositoryIssue extends TenantOrganizationBaseEntity implements IOrganizationGithubRepositoryIssue { @@ -14,14 +15,14 @@ export class OrganizationGithubRepositoryIssue extends TenantOrganizationBaseEnt @IsNotEmpty() @IsNumber() @Index() - @Column() + @MultiORMColumn() issueId: number; @ApiProperty({ type: () => Number }) @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() issueNumber: number; /* @@ -33,7 +34,7 @@ export class OrganizationGithubRepositoryIssue extends TenantOrganizationBaseEnt /** * Organization Github Repository */ - @ManyToOne(() => OrganizationGithubRepository, { + @MultiORMManyToOne(() => OrganizationGithubRepository, { /** Indicates if relation column value can be nullable or not. */ nullable: true, @@ -48,6 +49,6 @@ export class OrganizationGithubRepositoryIssue extends TenantOrganizationBaseEnt @IsUUID() @RelationId((it: OrganizationGithubRepositoryIssue) => it.repository) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) repositoryId?: IOrganizationGithubRepository['id']; } diff --git a/packages/core/src/integration/integration-type.entity.ts b/packages/core/src/integration/integration-type.entity.ts index 95768d7c5a6..d1ccbf95793 100644 --- a/packages/core/src/integration/integration-type.entity.ts +++ b/packages/core/src/integration/integration-type.entity.ts @@ -1,10 +1,11 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, ManyToMany, Unique } from 'typeorm'; +import { Unique } from 'typeorm'; import { IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; import { IIntegration, IIntegrationType } from '@gauzy/contracts'; import { BaseEntity, Integration } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmIntegrationTypeRepository } from './repository/mikro-orm-integration-type.repository'; +import { MultiORMManyToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('integration_type', { mikroOrmRepository: () => MikroOrmIntegrationTypeRepository }) @Unique(['name']) @@ -12,28 +13,28 @@ export class IntegrationType extends BaseEntity implements IIntegrationType { @ApiProperty({ type: () => String }) @IsNotEmpty() - @Column() + @MultiORMColumn() name: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) icon: string; @ApiProperty({ type: () => String }) @IsNotEmpty() - @Column() + @MultiORMColumn() groupName: string; @ApiProperty({ type: () => Number }) @IsNotEmpty() @IsNumber() - @Column() + @MultiORMColumn() order: number; /* @@ -41,7 +42,7 @@ export class IntegrationType extends BaseEntity implements IIntegrationType { | @ManyToMany |-------------------------------------------------------------------------- */ - @ManyToMany(() => Integration, (it) => it.integrationTypes, { + @MultiORMManyToMany(() => Integration, (it) => it.integrationTypes, { onUpdate: 'CASCADE', onDelete: 'CASCADE' }) diff --git a/packages/core/src/integration/integration.entity.ts b/packages/core/src/integration/integration.entity.ts index 3a5a1908177..c80e23778a9 100644 --- a/packages/core/src/integration/integration.entity.ts +++ b/packages/core/src/integration/integration.entity.ts @@ -1,12 +1,13 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, ManyToMany, JoinTable, Unique } from 'typeorm'; +import { JoinTable, Unique } from 'typeorm'; import { IsBoolean, IsNotEmpty, IsNumber, IsOptional } from 'class-validator'; import { IIntegration, IIntegrationType, ITag } from '@gauzy/contracts'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; import { BaseEntity, Tag } from '../core/entities/internal'; import { IntegrationType } from './integration-type.entity'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmIntegrationRepository } from './repository/mikro-orm-integration.repository'; +import { MultiORMManyToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('integration', { mikroOrmRepository: () => MikroOrmIntegrationRepository }) @Unique(['name']) @@ -14,56 +15,56 @@ export class Integration extends BaseEntity implements IIntegration { @ApiProperty({ type: () => String }) @IsNotEmpty() - @Column() // Define a unique constraint on the "name" column + @MultiORMColumn() // Define a unique constraint on the "name" column name: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) // Define a unique constraint on the "provider" column (E.g github, jira, hubstaff) + @MultiORMColumn({ nullable: true }) // Define a unique constraint on the "provider" column (E.g github, jira, hubstaff) provider: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) redirectUrl: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) imgSrc: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) @IsOptional() @IsBoolean() - @Column({ default: false }) + @MultiORMColumn({ default: false }) isComingSoon: boolean; @ApiPropertyOptional({ type: () => Boolean, default: false }) @IsOptional() @IsBoolean() - @Column({ default: false }) + @MultiORMColumn({ default: false }) isPaid: boolean; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) version: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) docUrl: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) @IsOptional() @IsBoolean() - @Column({ default: false }) + @MultiORMColumn({ default: false }) isFreeTrial: boolean; @ApiPropertyOptional({ type: () => Number }) @IsOptional() @IsNumber() - @Column({ + @MultiORMColumn({ nullable: true, default: 0, type: 'numeric', @@ -74,7 +75,7 @@ export class Integration extends BaseEntity implements IIntegration { @ApiPropertyOptional({ type: () => Number }) @IsOptional() @IsNumber() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) order: number; /** Additional fields */ @@ -87,9 +88,11 @@ export class Integration extends BaseEntity implements IIntegration { /** * */ - @ManyToMany(() => IntegrationType, (it) => it.integrations, { + @MultiORMManyToMany(() => IntegrationType, (it) => it.integrations, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'integration_integration_type', }) @JoinTable({ name: 'integration_integration_type' @@ -99,9 +102,11 @@ export class Integration extends BaseEntity implements IIntegration { /** * */ - @ManyToMany(() => Tag, (tag) => tag.integrations, { + @MultiORMManyToMany(() => Tag, (tag) => tag.integrations, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_integration', }) @JoinTable({ name: 'tag_integration' diff --git a/packages/core/src/invite/invite.entity.ts b/packages/core/src/invite/invite.entity.ts index 516282ee715..eee14afe2bc 100644 --- a/packages/core/src/invite/invite.entity.ts +++ b/packages/core/src/invite/invite.entity.ts @@ -11,12 +11,9 @@ import { import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Exclude } from 'class-transformer'; import { - Column, Index, JoinColumn, JoinTable, - ManyToMany, - ManyToOne, RelationId } from 'typeorm'; import { @@ -28,39 +25,40 @@ import { TenantOrganizationBaseEntity, User } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmInviteRepository } from './repository/mikro-orm-invite.repository'; +import { MultiORMManyToMany, MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('invite', { mikroOrmRepository: () => MikroOrmInviteRepository }) export class Invite extends TenantOrganizationBaseEntity implements IInvite { @ApiPropertyOptional({ type: () => String }) - @Column() + @MultiORMColumn() token: string; @ApiProperty({ type: () => String, minLength: 3, maxLength: 100 }) - @Column() + @MultiORMColumn() email: string; @ApiProperty({ type: () => String, enum: InviteStatusEnum }) - @Column() + @MultiORMColumn() status: InviteStatusEnum; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) expireDate: Date; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) actionDate?: Date; @ApiPropertyOptional({ type: () => String }) @Exclude({ toPlainOnly: true }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) public code?: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) public fullName?: string; public isExpired?: boolean; @@ -74,35 +72,35 @@ export class Invite extends TenantOrganizationBaseEntity implements IInvite { * Invited By User */ @ApiPropertyOptional({ type: () => User }) - @ManyToOne(() => User, { nullable: true, onDelete: 'CASCADE' }) + @MultiORMManyToOne(() => User, { nullable: true, onDelete: 'CASCADE' }) @JoinColumn() invitedBy?: IUser; @ApiProperty({ type: () => String }) @RelationId((it: Invite) => it.invitedBy) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) invitedById: string; /** * Invited User Role */ @ApiPropertyOptional({ type: () => Role }) - @ManyToOne(() => Role, { nullable: true, onDelete: 'CASCADE' }) + @MultiORMManyToOne(() => Role, { nullable: true, onDelete: 'CASCADE' }) @JoinColumn() role?: IRole; @ApiProperty({ type: () => String }) @RelationId((invite: Invite) => invite.role) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) roleId: string; /** * Invites belongs to user */ @ApiPropertyOptional({ type: () => Role }) - @ManyToOne(() => User, (it) => it.invites, { + @MultiORMManyToOne(() => User, (it) => it.invites, { onDelete: "SET NULL" }) @JoinColumn() @@ -111,7 +109,7 @@ export class Invite extends TenantOrganizationBaseEntity implements IInvite { @ApiProperty({ type: () => String }) @RelationId((invite: Invite) => invite.user) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) userId?: IUser['id']; /* @@ -123,7 +121,10 @@ export class Invite extends TenantOrganizationBaseEntity implements IInvite { * Organization Projects */ @ApiPropertyOptional({ type: () => OrganizationProject }) - @ManyToMany(() => OrganizationProject) + @MultiORMManyToMany(() => OrganizationProject, { + owner: true, + pivotTable: 'invite_organization_project', + }) @JoinTable({ name: 'invite_organization_project' }) @@ -133,7 +134,10 @@ export class Invite extends TenantOrganizationBaseEntity implements IInvite { * Organization Contacts */ @ApiPropertyOptional({ type: () => OrganizationContact }) - @ManyToMany(() => OrganizationContact) + @MultiORMManyToMany(() => OrganizationContact, { + owner: true, + pivotTable: 'invite_organization_contact', + }) @JoinTable({ name: 'invite_organization_contact' }) @@ -143,7 +147,10 @@ export class Invite extends TenantOrganizationBaseEntity implements IInvite { * Organization Departments */ @ApiPropertyOptional({ type: () => OrganizationDepartment }) - @ManyToMany(() => OrganizationDepartment) + @MultiORMManyToMany(() => OrganizationDepartment, { + owner: true, + pivotTable: 'invite_organization_department', + }) @JoinTable({ name: 'invite_organization_department' }) @@ -153,7 +160,10 @@ export class Invite extends TenantOrganizationBaseEntity implements IInvite { * Organization Teams */ @ApiPropertyOptional({ type: () => OrganizationTeam }) - @ManyToMany(() => OrganizationTeam) + @MultiORMManyToMany(() => OrganizationTeam, { + owner: true, + pivotTable: 'invite_organization_team', + }) @JoinTable({ name: 'invite_organization_team' }) diff --git a/packages/core/src/invite/invite.service.ts b/packages/core/src/invite/invite.service.ts index fc921b7f1d8..10daf4c2c3d 100644 --- a/packages/core/src/invite/invite.service.ts +++ b/packages/core/src/invite/invite.service.ts @@ -196,22 +196,22 @@ export class InviteService extends TenantAwareCrudService { const { items: existedInvites } = await this.findAll({ ...(isNotEmpty(teamIds) ? { - relations: { - teams: true - } - } + relations: { + teams: true + } + } : {}), where: { tenantId: RequestContext.currentTenantId(), ...(isNotEmpty(organizationId) ? { - organizationId - } + organizationId + } : {}), ...(isNotEmpty(emailIds) ? { - email: In(emailIds) - } + email: In(emailIds) + } : {}) } }); @@ -502,7 +502,7 @@ export class InviteService extends TenantAwareCrudService { if (typeof payload === 'object' && 'email' in payload) { if (payload.email === email) { - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.setFindOptions({ select: { id: true, @@ -523,8 +523,8 @@ export class InviteService extends TenantAwareCrudService { status: InviteStatusEnum.INVITED, ...(payload['code'] ? { - code: payload['code'] - } + code: payload['code'] + } : {}) }); qb.andWhere([ @@ -555,7 +555,7 @@ export class InviteService extends TenantAwareCrudService { const { email, code } = where; try { - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.setFindOptions({ select: { id: true, @@ -607,18 +607,18 @@ export class InviteService extends TenantAwareCrudService { return await super.findAll({ ...(options && options.skip ? { - skip: options.take * (options.skip - 1) - } + skip: options.take * (options.skip - 1) + } : {}), ...(options && options.take ? { - take: options.take - } + take: options.take + } : {}), ...(options && options.relations ? { - relations: options.relations - } + relations: options.relations + } : {}), where: { tenantId: RequestContext.currentTenantId(), @@ -626,15 +626,15 @@ export class InviteService extends TenantAwareCrudService { ...(isNotEmpty(options) && isNotEmpty(options.where) ? isNotEmpty(options.where.role) ? { - role: { - ...options.where.role - } - } + role: { + ...options.where.role + } + } : { - role: { - name: Not(RolesEnum.EMPLOYEE) - } - } + role: { + name: Not(RolesEnum.EMPLOYEE) + } + } : {}), /** * Organization invites filter by specific projects @@ -642,10 +642,10 @@ export class InviteService extends TenantAwareCrudService { ...(isNotEmpty(options) && isNotEmpty(options.where) ? isNotEmpty(options.where.projects) ? { - projects: { - id: In(options.where.projects.id) - } - } + projects: { + id: In(options.where.projects.id) + } + } : {} : {}), /** @@ -654,10 +654,10 @@ export class InviteService extends TenantAwareCrudService { ...(isNotEmpty(options) && isNotEmpty(options.where) ? isNotEmpty(options.where.teams) ? { - teams: { - id: In(options.where.teams.id) - } - } + teams: { + id: In(options.where.teams.id) + } + } : {} : {}) } @@ -674,7 +674,7 @@ export class InviteService extends TenantAwareCrudService { async findInviteOfCurrentUser() { const user = RequestContext.currentUser(); - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.setFindOptions({ select: { id: true, @@ -713,7 +713,7 @@ export class InviteService extends TenantAwareCrudService { async acceptMyInvitation(id: string, action: InviteActionEnum, origin: string, languageCode: LanguagesEnum) { const user = RequestContext.currentUser(); - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.innerJoin(`${query.alias}.teams`, 'teams'); query.setFindOptions({ select: { @@ -906,8 +906,8 @@ export class InviteService extends TenantAwareCrudService { tenant, ...(input.password ? { - hash: await this.authService.getPasswordHash(input.password) - } + hash: await this.authService.getPasswordHash(input.password) + } : {}) }); const entity = await this.typeOrmUserRepository.save(create); @@ -918,8 +918,8 @@ export class InviteService extends TenantAwareCrudService { await this.typeOrmUserRepository.update(entity.id, { ...(input.inviteId ? { - emailVerifiedAt: freshTimestamp() - } + emailVerifiedAt: freshTimestamp() + } : {}) }); diff --git a/packages/core/src/invoice-estimate-history/invoice-estimate-history.entity.ts b/packages/core/src/invoice-estimate-history/invoice-estimate-history.entity.ts index defda90c566..2ac16233467 100644 --- a/packages/core/src/invoice-estimate-history/invoice-estimate-history.entity.ts +++ b/packages/core/src/invoice-estimate-history/invoice-estimate-history.entity.ts @@ -2,9 +2,7 @@ import { IInvoice, IInvoiceEstimateHistory } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional, IsString } from 'class-validator'; import { - Column, JoinColumn, - ManyToOne, Index, RelationId, } from 'typeorm'; @@ -13,21 +11,22 @@ import { TenantOrganizationBaseEntity, User, } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmInvoiceEstimateHistoryRepository } from './repository/mikro-orm-invoice-estimate-history.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('invoice_estimate_history', { mikroOrmRepository: () => MikroOrmInvoiceEstimateHistoryRepository }) export class InvoiceEstimateHistory extends TenantOrganizationBaseEntity implements IInvoiceEstimateHistory { @ApiProperty({ type: () => String }) @IsString() - @Column() + @MultiORMColumn() action: string; @IsOptional() @ApiProperty({ type: () => String, required: false }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) title?: string; /* @@ -36,7 +35,7 @@ export class InvoiceEstimateHistory extends TenantOrganizationBaseEntity impleme |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => User }) - @ManyToOne(() => User, { + @MultiORMManyToOne(() => User, { onDelete: 'SET NULL', }) @JoinColumn() @@ -46,11 +45,11 @@ export class InvoiceEstimateHistory extends TenantOrganizationBaseEntity impleme @RelationId((it: InvoiceEstimateHistory) => it.user) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) userId: string; @ApiProperty({ type: () => Invoice }) - @ManyToOne(() => Invoice, (invoice) => invoice.invoiceItems, { + @MultiORMManyToOne(() => Invoice, (invoice) => invoice.invoiceItems, { onDelete: 'CASCADE', }) invoice: IInvoice; @@ -59,6 +58,6 @@ export class InvoiceEstimateHistory extends TenantOrganizationBaseEntity impleme @RelationId((it: InvoiceEstimateHistory) => it.invoice) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) invoiceId: string; } diff --git a/packages/core/src/invoice-item/invoice-item.entity.ts b/packages/core/src/invoice-item/invoice-item.entity.ts index cd5161753c7..ae1c922fb00 100644 --- a/packages/core/src/invoice-item/invoice-item.entity.ts +++ b/packages/core/src/invoice-item/invoice-item.entity.ts @@ -1,4 +1,4 @@ -import { Column, JoinColumn, ManyToOne, RelationId } from 'typeorm'; +import { JoinColumn, RelationId } from 'typeorm'; import { IEmployee, IExpense, @@ -20,20 +20,21 @@ import { TenantOrganizationBaseEntity } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmInvoiceItemRepository } from './repository/mikro-orm-invoice-item.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('invoice_item', { mikroOrmRepository: () => MikroOrmInvoiceItemRepository }) export class InvoiceItem extends TenantOrganizationBaseEntity implements IInvoiceItem { @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ + @MultiORMColumn({ type: 'numeric', transformer: new ColumnNumericTransformerPipe() }) @@ -41,7 +42,7 @@ export class InvoiceItem extends TenantOrganizationBaseEntity implements IInvoic @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ + @MultiORMColumn({ type: 'numeric', transformer: new ColumnNumericTransformerPipe() }) @@ -49,7 +50,7 @@ export class InvoiceItem extends TenantOrganizationBaseEntity implements IInvoic @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ + @MultiORMColumn({ type: 'numeric', transformer: new ColumnNumericTransformerPipe() }) @@ -57,12 +58,12 @@ export class InvoiceItem extends TenantOrganizationBaseEntity implements IInvoic @ApiPropertyOptional({ type: () => Boolean }) @IsBoolean() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) applyTax?: boolean; @ApiPropertyOptional({ type: () => Boolean }) @IsBoolean() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) applyDiscount?: boolean; /* @@ -73,7 +74,7 @@ export class InvoiceItem extends TenantOrganizationBaseEntity implements IInvoic // Invoice Item Belongs to Expense @ApiPropertyOptional({ type: () => Expense }) - @ManyToOne(() => Expense, (expense) => expense.invoiceItems, { + @MultiORMManyToOne(() => Expense, (expense) => expense.invoiceItems, { onDelete: 'SET NULL' }) @JoinColumn() @@ -83,12 +84,12 @@ export class InvoiceItem extends TenantOrganizationBaseEntity implements IInvoic @RelationId((it: InvoiceItem) => it.expense) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) expenseId?: string; // Invoice Item Belongs to Invoice @ApiPropertyOptional({ type: () => Invoice }) - @ManyToOne(() => Invoice, (invoice) => invoice.invoiceItems, { + @MultiORMManyToOne(() => Invoice, (invoice) => invoice.invoiceItems, { onDelete: 'SET NULL' }) @JoinColumn() @@ -98,12 +99,12 @@ export class InvoiceItem extends TenantOrganizationBaseEntity implements IInvoic @RelationId((it: InvoiceItem) => it.invoice) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) invoiceId?: string; // Invoice Item Belongs to Task @ApiPropertyOptional({ type: () => Task }) - @ManyToOne(() => Task, (task) => task.invoiceItems, { + @MultiORMManyToOne(() => Task, (task) => task.invoiceItems, { onDelete: 'SET NULL' }) @JoinColumn() @@ -113,12 +114,12 @@ export class InvoiceItem extends TenantOrganizationBaseEntity implements IInvoic @RelationId((it: InvoiceItem) => it.task) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) taskId?: string; // Invoice Item Belongs to Employee @ApiPropertyOptional({ type: () => Employee }) - @ManyToOne(() => Employee, (employee) => employee.invoiceItems, { + @MultiORMManyToOne(() => Employee, (employee) => employee.invoiceItems, { onDelete: 'SET NULL' }) @JoinColumn() @@ -128,12 +129,12 @@ export class InvoiceItem extends TenantOrganizationBaseEntity implements IInvoic @RelationId((it: InvoiceItem) => it.employee) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) employeeId?: string; // Invoice Item Belongs to Project @ApiPropertyOptional({ type: () => OrganizationProject }) - @ManyToOne(() => OrganizationProject, (it) => it.invoiceItems, { + @MultiORMManyToOne(() => OrganizationProject, (it) => it.invoiceItems, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -147,12 +148,12 @@ export class InvoiceItem extends TenantOrganizationBaseEntity implements IInvoic @IsOptional() @RelationId((it: InvoiceItem) => it.project) @IsUUID() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; // Invoice Item Belongs to Product @ApiPropertyOptional({ type: () => Product }) - @ManyToOne(() => Product, (product) => product.invoiceItems, { + @MultiORMManyToOne(() => Product, (product) => product.invoiceItems, { onDelete: 'SET NULL' }) @JoinColumn() @@ -162,6 +163,6 @@ export class InvoiceItem extends TenantOrganizationBaseEntity implements IInvoic @RelationId((it: InvoiceItem) => it.product) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) productId?: string; } diff --git a/packages/core/src/invoice/invoice.entity.ts b/packages/core/src/invoice/invoice.entity.ts index 1b2d84fa866..32334813d96 100644 --- a/packages/core/src/invoice/invoice.entity.ts +++ b/packages/core/src/invoice/invoice.entity.ts @@ -20,12 +20,8 @@ import { IsEnum } from 'class-validator'; import { - Column, JoinColumn, - OneToMany, - ManyToOne, Unique, - ManyToMany, RelationId, Index, JoinTable @@ -40,9 +36,10 @@ import { Tag, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { isMySQL } from '@gauzy/config'; import { MikroOrmInvoiceRepository } from './repository/mikro-orm-invoice.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('invoice', { mikroOrmRepository: () => MikroOrmInvoiceRepository }) @Unique(['invoiceNumber']) @@ -50,12 +47,12 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { @ApiProperty({ type: () => Date }) @IsDate() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) invoiceDate: Date; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ + @MultiORMColumn({ nullable: true, transformer: new ColumnNumericTransformerPipe(), ...(isMySQL() ? { type: 'bigint' } : { type: 'numeric' }) @@ -64,17 +61,17 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { @ApiProperty({ type: () => Date }) @IsDate() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) dueDate: Date; @ApiProperty({ type: () => String, enum: CurrenciesEnum }) @IsEnum(CurrenciesEnum) - @Column() + @MultiORMColumn() currency: string; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ + @MultiORMColumn({ type: 'numeric', transformer: new ColumnNumericTransformerPipe() }) @@ -82,12 +79,12 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { @ApiProperty({ type: () => Boolean }) @IsBoolean() - @Column({ type: Boolean, nullable: true }) + @MultiORMColumn({ type: Boolean, nullable: true }) paid: boolean; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -96,7 +93,7 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -106,13 +103,13 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column() + @MultiORMColumn() terms?: string; @ApiPropertyOptional({ type: () => Number }) @IsNumber() @IsOptional() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -122,62 +119,62 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column() + @MultiORMColumn() status?: string; @ApiPropertyOptional({ type: () => Boolean }) @IsBoolean() - @Column({ type: Boolean, nullable: true }) + @MultiORMColumn({ type: Boolean, nullable: true }) isEstimate?: boolean; @ApiPropertyOptional({ type: () => Boolean }) @IsBoolean() - @Column({ type: Boolean, nullable: true }) + @MultiORMColumn({ type: Boolean, nullable: true }) isAccepted?: boolean; @ApiProperty({ type: () => String, enum: DiscountTaxTypeEnum }) @IsEnum(DiscountTaxTypeEnum) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) discountType: DiscountTaxTypeEnum; @ApiProperty({ type: () => String, enum: DiscountTaxTypeEnum }) @IsEnum(DiscountTaxTypeEnum) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) taxType: DiscountTaxTypeEnum; @ApiProperty({ type: () => String, enum: DiscountTaxTypeEnum }) @IsEnum(DiscountTaxTypeEnum) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) tax2Type: DiscountTaxTypeEnum; @ApiPropertyOptional({ type: () => String, enum: InvoiceTypeEnum }) @IsEnum(InvoiceTypeEnum) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) invoiceType?: string; @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) sentTo?: string; @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) organizationContactId?: string; @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) internalNote?: string; @ApiPropertyOptional({ type: () => Number }) @IsNumber() @IsOptional() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -187,7 +184,7 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { @ApiPropertyOptional({ type: () => Number }) @IsNumber() @IsOptional() - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -196,13 +193,13 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { @ApiPropertyOptional({ type: () => Boolean }) @IsBoolean() - @Column({ type: Boolean, nullable: true }) + @MultiORMColumn({ type: Boolean, nullable: true }) hasRemainingAmountInvoiced?: boolean; @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column({ + @MultiORMColumn({ nullable: true, ...(isMySQL() ? { type: "text" } : {}) }) @@ -216,7 +213,7 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { */ // From Organization @ApiPropertyOptional({ type: () => () => Organization }) - @ManyToOne(() => Organization) + @MultiORMManyToOne(() => Organization) @JoinColumn() fromOrganization?: IOrganization; @@ -224,12 +221,12 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { @RelationId((it: Invoice) => it.fromOrganization) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) fromOrganizationId?: string; // To Contact @ApiPropertyOptional({ type: () => () => OrganizationContact }) - @ManyToOne(() => OrganizationContact, (contact) => contact.invoices, { + @MultiORMManyToOne(() => OrganizationContact, (contact) => contact.invoices, { onDelete: 'SET NULL' }) @JoinColumn() @@ -239,7 +236,7 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { @RelationId((it: Invoice) => it.toContact) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) toContactId?: string; /* @@ -249,7 +246,7 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { */ // Invoice Estimate Items @ApiPropertyOptional({ type: () => InvoiceItem, isArray: true }) - @OneToMany(() => InvoiceItem, (invoiceItem) => invoiceItem.invoice, { + @MultiORMOneToMany(() => InvoiceItem, (invoiceItem) => invoiceItem.invoice, { cascade: true }) @JoinColumn() @@ -257,13 +254,13 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { // Invoice Estimate Payments @ApiPropertyOptional({ type: () => Payment, isArray: true }) - @OneToMany(() => Payment, (payment) => payment.invoice) + @MultiORMOneToMany(() => Payment, (payment) => payment.invoice) @JoinColumn() payments?: IPayment[]; // Invoice Estimate History @ApiPropertyOptional({ type: () => InvoiceEstimateHistory, isArray: true }) - @OneToMany(() => InvoiceEstimateHistory, (invoiceEstimateHistory) => invoiceEstimateHistory.invoice, { + @MultiORMOneToMany(() => InvoiceEstimateHistory, (invoiceEstimateHistory) => invoiceEstimateHistory.invoice, { cascade: true }) @JoinColumn() @@ -275,9 +272,11 @@ export class Invoice extends TenantOrganizationBaseEntity implements IInvoice { |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Tag }) - @ManyToMany(() => Tag, (tag) => tag.invoices, { + @MultiORMManyToMany(() => Tag, (tag) => tag.invoices, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_invoice', }) @JoinTable({ name: 'tag_invoice' diff --git a/packages/core/src/invoice/invoice.service.ts b/packages/core/src/invoice/invoice.service.ts index 60a73316efb..fed8413e977 100644 --- a/packages/core/src/invoice/invoice.service.ts +++ b/packages/core/src/invoice/invoice.service.ts @@ -41,7 +41,7 @@ export class InvoiceService extends TenantAwareCrudService { */ async getHighestInvoiceNumber(): Promise { try { - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); return await query.select(`COALESCE(MAX(${query.alias}.invoiceNumber), 0)`, 'max').getRawOne(); } catch (error) { throw new BadRequestException(error); diff --git a/packages/core/src/keyresult-template/keyresult-template.entity.ts b/packages/core/src/keyresult-template/keyresult-template.entity.ts index 9399216a602..311d2cf39ea 100644 --- a/packages/core/src/keyresult-template/keyresult-template.entity.ts +++ b/packages/core/src/keyresult-template/keyresult-template.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, RelationId, JoinColumn, Index } from 'typeorm'; +import { RelationId, JoinColumn, Index } from 'typeorm'; import { IKeyResultTemplate, KeyResultTypeEnum, @@ -13,39 +13,40 @@ import { GoalTemplate, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmKeyResultTemplateRepository } from './repository/mikro-orm-keyresult-template.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('key_result_template', { mikroOrmRepository: () => MikroOrmKeyResultTemplateRepository }) export class KeyResultTemplate extends TenantOrganizationBaseEntity implements IKeyResultTemplate { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String, enum: KeyResultTypeEnum }) @IsEnum(KeyResultTypeEnum) - @Column() + @MultiORMColumn() type: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() unit?: string; @ApiProperty({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() targetValue?: number; @ApiProperty({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() initialValue: number; @ApiProperty({ type: () => String, enum: KeyResultDeadlineEnum }) @IsEnum(KeyResultDeadlineEnum) - @Column() + @MultiORMColumn() deadline: string; /* @@ -54,7 +55,7 @@ export class KeyResultTemplate extends TenantOrganizationBaseEntity implements I |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => GoalKPITemplate }) - @ManyToOne(() => GoalKPITemplate, { nullable: true }) + @MultiORMManyToOne(() => GoalKPITemplate, { nullable: true }) @JoinColumn({ name: 'kpiId' }) @IsOptional() kpi?: IGoalKPITemplate; @@ -63,12 +64,12 @@ export class KeyResultTemplate extends TenantOrganizationBaseEntity implements I @RelationId((it: KeyResultTemplate) => it.kpi) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) kpiId?: string; @ApiProperty({ type: () => GoalTemplate }) - @ManyToOne(() => GoalTemplate, (goalTemplate) => goalTemplate.keyResults, { + @MultiORMManyToOne(() => GoalTemplate, (goalTemplate) => goalTemplate.keyResults, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'goalId' }) @@ -79,6 +80,6 @@ export class KeyResultTemplate extends TenantOrganizationBaseEntity implements I @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly goalId?: string; } diff --git a/packages/core/src/keyresult-update/keyresult-update.entity.ts b/packages/core/src/keyresult-update/keyresult-update.entity.ts index b0fb3896800..62029409592 100644 --- a/packages/core/src/keyresult-update/keyresult-update.entity.ts +++ b/packages/core/src/keyresult-update/keyresult-update.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, RelationId, JoinColumn } from 'typeorm'; +import { RelationId, JoinColumn } from 'typeorm'; import { IKeyResult, IKeyResultUpdate, KeyResultUpdateStatusEnum } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum } from 'class-validator'; @@ -6,31 +6,32 @@ import { KeyResult, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmKeyResultUpdateRepository } from './repository/mikro-orm-keyresult-update.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('key_result_update', { mikroOrmRepository: () => MikroOrmKeyResultUpdateRepository }) export class KeyResultUpdate extends TenantOrganizationBaseEntity implements IKeyResultUpdate { @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() update: number; @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() progress: number; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() owner: string; @ApiProperty({ type: () => String, enum: KeyResultUpdateStatusEnum }) @IsEnum(KeyResultUpdateStatusEnum) - @Column() + @MultiORMColumn() status: string; @ApiProperty({ type: () => KeyResult }) - @ManyToOne(() => KeyResult, (keyResult) => keyResult.update, { + @MultiORMManyToOne(() => KeyResult, (keyResult) => keyResult.updates, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'keyResultId' }) @@ -38,6 +39,6 @@ export class KeyResultUpdate extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String, readOnly: true }) @RelationId((it: KeyResultUpdate) => it.keyResult) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) keyResultId?: string; } diff --git a/packages/core/src/keyresult/keyresult.entity.ts b/packages/core/src/keyresult/keyresult.entity.ts index e327ac40aae..02236301664 100644 --- a/packages/core/src/keyresult/keyresult.entity.ts +++ b/packages/core/src/keyresult/keyresult.entity.ts @@ -1,9 +1,6 @@ import { - Column, - ManyToOne, RelationId, JoinColumn, - OneToMany, Index } from 'typeorm'; import { @@ -27,72 +24,73 @@ import { Task, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmKeyResultRepository } from './repository/mikro-orm-keyresult.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('key_result', { mikroOrmRepository: () => MikroOrmKeyResultRepository }) export class KeyResult extends TenantOrganizationBaseEntity implements IKeyResult { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() @IsOptional() description?: string; @ApiProperty({ type: () => String, enum: KeyResultTypeEnum }) @IsEnum(KeyResultTypeEnum) - @Column() + @MultiORMColumn() type: string; @ApiProperty({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() targetValue?: number; @ApiProperty({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() initialValue: number; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() unit?: string; @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() update: number; @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() progress: number; @ApiProperty({ type: () => String, enum: KeyResultDeadlineEnum }) @IsEnum(KeyResultDeadlineEnum) - @Column() + @MultiORMColumn() deadline: string; @ApiProperty({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() hardDeadline?: Date; @ApiProperty({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() softDeadline?: Date; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() @IsOptional() status?: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() weight?: string; @@ -106,7 +104,7 @@ export class KeyResult extends TenantOrganizationBaseEntity * Owner Employee */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee) + @MultiORMManyToOne(() => Employee) @JoinColumn() owner: IEmployee; @@ -114,14 +112,14 @@ export class KeyResult extends TenantOrganizationBaseEntity @RelationId((it: KeyResult) => it.owner) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) ownerId: string; /** * Lead Employee */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { nullable: true }) + @MultiORMManyToOne(() => Employee, { nullable: true }) @JoinColumn() @IsOptional() lead?: IEmployee; @@ -131,14 +129,14 @@ export class KeyResult extends TenantOrganizationBaseEntity @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) leadId?: string; /** * Organization Project */ @ApiProperty({ type: () => OrganizationProject }) - @ManyToOne(() => OrganizationProject, { nullable: true }) + @MultiORMManyToOne(() => OrganizationProject, { nullable: true }) @JoinColumn({ name: 'projectId' }) @IsOptional() project?: IOrganizationProject; @@ -148,14 +146,14 @@ export class KeyResult extends TenantOrganizationBaseEntity @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly projectId?: string; /** * Task */ @ApiProperty({ type: () => Task }) - @ManyToOne(() => Task, { nullable: true }) + @MultiORMManyToOne(() => Task, { nullable: true }) @JoinColumn({ name: 'taskId' }) @IsOptional() task?: ITask; @@ -165,14 +163,14 @@ export class KeyResult extends TenantOrganizationBaseEntity @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly taskId?: string; /** * GoalKPI */ @ApiProperty({ type: () => GoalKPI }) - @ManyToOne(() => GoalKPI, { nullable: true }) + @MultiORMManyToOne(() => GoalKPI, { nullable: true }) @JoinColumn({ name: 'kpiId' }) @IsOptional() kpi?: IKPI; @@ -182,14 +180,14 @@ export class KeyResult extends TenantOrganizationBaseEntity @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly kpiId?: string; /** * Goal */ @ApiProperty({ type: () => Goal }) - @ManyToOne(() => Goal, (goal) => goal.keyResults, { + @MultiORMManyToOne(() => Goal, (goal) => goal.keyResults, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'goalId' }) @@ -200,7 +198,7 @@ export class KeyResult extends TenantOrganizationBaseEntity @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly goalId?: string; /* @@ -210,7 +208,7 @@ export class KeyResult extends TenantOrganizationBaseEntity */ @ApiProperty({ type: () => KeyResultUpdate }) - @OneToMany(() => KeyResultUpdate, (keyResultUpdate) => keyResultUpdate.keyResult, { + @MultiORMOneToMany(() => KeyResultUpdate, (keyResultUpdate) => keyResultUpdate.keyResult, { cascade: true }) updates?: KeyResultUpdate[]; diff --git a/packages/core/src/language/language.entity.ts b/packages/core/src/language/language.entity.ts index c28f63359ae..f00d7f8273e 100644 --- a/packages/core/src/language/language.entity.ts +++ b/packages/core/src/language/language.entity.ts @@ -1,37 +1,38 @@ -import { Column, Unique, OneToMany, JoinColumn } from 'typeorm'; +import { Unique, JoinColumn } from 'typeorm'; import { ILanguage, IOrganizationLanguage } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional } from 'class-validator'; import { BaseEntity, OrganizationLanguage } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmLanguageRepository } from './repository/mikro-orm-language.repository'; +import { MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('language', { mikroOrmRepository: () => MikroOrmLanguageRepository }) @Unique(['code']) export class Language extends BaseEntity implements ILanguage { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name?: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) @IsOptional() code?: string; @ApiProperty({ type: () => Boolean, default: true }) - @Column({ default: true, nullable: true }) + @MultiORMColumn({ default: true, nullable: true }) @IsOptional() is_system?: boolean; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() color?: string; - @OneToMany(() => OrganizationLanguage, (organizationLanguage) => organizationLanguage.language, { + @MultiORMOneToMany(() => OrganizationLanguage, (organizationLanguage) => organizationLanguage.language, { cascade: true }) @JoinColumn() diff --git a/packages/core/src/merchant/merchant.entity.ts b/packages/core/src/merchant/merchant.entity.ts index 9aa5e00da2e..abbefe1b8ac 100644 --- a/packages/core/src/merchant/merchant.entity.ts +++ b/packages/core/src/merchant/merchant.entity.ts @@ -7,12 +7,8 @@ import { IContact } from '@gauzy/contracts'; import { - Column, - ManyToOne, JoinColumn, JoinTable, - ManyToMany, - OneToOne, Index, RelationId } from 'typeorm'; @@ -24,38 +20,39 @@ import { Contact, Warehouse, } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmMerchantRepository } from './repository/mikro-orm-merchant.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('merchant', { mikroOrmRepository: () => MikroOrmMerchantRepository }) export class Merchant extends TenantOrganizationBaseEntity implements IMerchant { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() code: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() email: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) phone: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: true }) + @MultiORMColumn({ default: true }) active: boolean; @ApiProperty({ type: () => String }) - @Column({ default: CurrenciesEnum.USD }) + @MultiORMColumn({ default: CurrenciesEnum.USD }) currency: CurrenciesEnum; /* @@ -68,9 +65,10 @@ export class Merchant extends TenantOrganizationBaseEntity implements IMerchant * Contact */ @ApiProperty({ type: () => Contact }) - @OneToOne(() => Contact, { + @MultiORMOneToOne(() => Contact, { cascade: true, - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true }) @JoinColumn() contact?: IContact; @@ -78,7 +76,7 @@ export class Merchant extends TenantOrganizationBaseEntity implements IMerchant @ApiProperty({ type: () => String }) @RelationId((it: Merchant) => it.contact) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) contactId?: IContact['id']; /* @@ -91,14 +89,14 @@ export class Merchant extends TenantOrganizationBaseEntity implements IMerchant * ImageAsset */ @ApiProperty({ type: () => ImageAsset }) - @ManyToOne(() => ImageAsset, { cascade: true }) + @MultiORMManyToOne(() => ImageAsset, { cascade: true }) @JoinColumn() logo?: IImageAsset; @ApiProperty({ type: () => String }) @RelationId((it: Merchant) => it.logo) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) logoId?: IImageAsset['id']; /* @@ -111,9 +109,11 @@ export class Merchant extends TenantOrganizationBaseEntity implements IMerchant * Tag */ @ApiProperty({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.merchants, { + @MultiORMManyToMany(() => Tag, (tag) => tag.merchants, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_merchant', }) @JoinTable({ name: 'tag_merchant' @@ -124,8 +124,10 @@ export class Merchant extends TenantOrganizationBaseEntity implements IMerchant * Warehouses */ @ApiProperty({ type: () => Warehouse, isArray: true }) - @ManyToMany(() => Warehouse, (it) => it.merchants, { - onDelete: 'CASCADE' + @MultiORMManyToMany(() => Warehouse, (it) => it.merchants, { + onDelete: 'CASCADE', + owner: true, + pivotTable: 'warehouse_merchant', }) @JoinTable({ name: 'warehouse_merchant' diff --git a/packages/core/src/organization-award/organization-award.entity.ts b/packages/core/src/organization-award/organization-award.entity.ts index 5829e7fe2d2..cba0c93e0fd 100644 --- a/packages/core/src/organization-award/organization-award.entity.ts +++ b/packages/core/src/organization-award/organization-award.entity.ts @@ -1,9 +1,9 @@ -import { Column, Index } from 'typeorm'; +import { Index } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; import { IOrganizationAward } from '@gauzy/contracts'; import { TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationAwardRepository } from './repository/mikro-orm-organization-award.repository'; @MultiORMEntity('organization_award', { mikroOrmRepository: () => MikroOrmOrganizationAwardRepository }) @@ -13,12 +13,12 @@ export class OrganizationAward extends TenantOrganizationBaseEntity implements I @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column() + @MultiORMColumn() year: string; } diff --git a/packages/core/src/organization-contact/organization-contact.entity.ts b/packages/core/src/organization-contact/organization-contact.entity.ts index 8ec8261ee3b..5fec3900e07 100644 --- a/packages/core/src/organization-contact/organization-contact.entity.ts +++ b/packages/core/src/organization-contact/organization-contact.entity.ts @@ -1,13 +1,8 @@ import { - Column, Index, JoinColumn, - ManyToMany, - OneToMany, JoinTable, - RelationId, - OneToOne, - ManyToOne + RelationId } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsOptional, IsUUID } from 'class-validator'; @@ -41,8 +36,9 @@ import { TenantOrganizationBaseEntity, TimeLog } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationContactRepository } from './repository/mikro-orm-organization-contact.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany, MultiORMOneToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_contact', { mikroOrmRepository: () => MikroOrmOrganizationContactRepository }) export class OrganizationContact extends TenantOrganizationBaseEntity @@ -50,19 +46,19 @@ export class OrganizationContact extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) primaryEmail: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) primaryPhone: string; @ApiProperty({ type: () => String, enum: ContactOrganizationInviteStatus }) - @Column({ + @MultiORMColumn({ type: 'simple-enum', nullable: true, enum: ContactOrganizationInviteStatus @@ -70,11 +66,11 @@ export class OrganizationContact extends TenantOrganizationBaseEntity inviteStatus?: ContactOrganizationInviteStatus; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) notes?: string; @ApiProperty({ type: () => String, enum: ContactType }) - @Column({ + @MultiORMColumn({ type: 'simple-enum', nullable: false, enum: ContactType, @@ -83,15 +79,15 @@ export class OrganizationContact extends TenantOrganizationBaseEntity contactType: ContactType; @ApiPropertyOptional({ type: () => String, maxLength: 500 }) - @Column({ length: 500, nullable: true }) + @MultiORMColumn({ length: 500, nullable: true }) imageUrl?: string; @ApiPropertyOptional({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) budget?: number; @ApiPropertyOptional({ type: () => String }) - @Column({ + @MultiORMColumn({ type: 'simple-enum', nullable: true, enum: OrganizationContactBudgetTypeEnum, @@ -100,7 +96,7 @@ export class OrganizationContact extends TenantOrganizationBaseEntity budgetType?: OrganizationContactBudgetTypeEnum; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) createdBy?: string; /* @@ -113,11 +109,12 @@ export class OrganizationContact extends TenantOrganizationBaseEntity * Contact */ @ApiProperty({ type: () => Contact }) - @OneToOne(() => Contact, (contact) => contact.organizationContact, { + @MultiORMOneToOne(() => Contact, (contact) => contact.organizationContact, { cascade: true, /** Database cascade action on delete. */ - onDelete: 'SET NULL' + onDelete: 'SET NULL', + owner: true }) @JoinColumn() contact?: IContact; @@ -127,13 +124,13 @@ export class OrganizationContact extends TenantOrganizationBaseEntity @IsUUID() @RelationId((it: OrganizationContact) => it.contact) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) contactId?: IContact['id']; /** * ImageAsset */ - @ManyToOne(() => ImageAsset, { + @MultiORMManyToOne(() => ImageAsset, { /** Database cascade action on delete. */ onDelete: 'SET NULL', @@ -148,7 +145,7 @@ export class OrganizationContact extends TenantOrganizationBaseEntity @IsUUID() @RelationId((it: OrganizationContact) => it.image) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) imageId?: IImageAsset['id']; /* @@ -160,20 +157,20 @@ export class OrganizationContact extends TenantOrganizationBaseEntity * Organization Projects Relationship */ @ApiPropertyOptional({ type: () => OrganizationProject, isArray: true }) - @OneToMany(() => OrganizationProject, (it) => it.organizationContact, { + @MultiORMOneToMany(() => OrganizationProject, (it) => it.organizationContact, { cascade: true }) projects?: IOrganizationProject[]; // Organization Invoices @ApiPropertyOptional({ type: () => Invoice, isArray: true }) - @OneToMany(() => Invoice, (it) => it.toContact) + @MultiORMOneToMany(() => Invoice, (it) => it.toContact) @JoinColumn() invoices?: IInvoice[]; // Organization Payments @ApiPropertyOptional({ type: () => Payment, isArray: true }) - @OneToMany(() => Payment, (it) => it.organizationContact, { + @MultiORMOneToMany(() => Payment, (it) => it.organizationContact, { onDelete: 'SET NULL' }) @JoinColumn() @@ -181,7 +178,7 @@ export class OrganizationContact extends TenantOrganizationBaseEntity // Organization Proposals @ApiPropertyOptional({ type: () => Proposal, isArray: true }) - @OneToMany(() => Proposal, (it) => it.organizationContact) + @MultiORMOneToMany(() => Proposal, (it) => it.organizationContact) @JoinColumn() proposals?: IOrganizationProject[]; @@ -189,7 +186,7 @@ export class OrganizationContact extends TenantOrganizationBaseEntity * Expense */ @ApiPropertyOptional({ type: () => Expense, isArray: true }) - @OneToMany(() => Expense, (it) => it.organizationContact, { + @MultiORMOneToMany(() => Expense, (it) => it.organizationContact, { onDelete: 'SET NULL' }) expenses?: IExpense[]; @@ -198,7 +195,7 @@ export class OrganizationContact extends TenantOrganizationBaseEntity * Income */ @ApiPropertyOptional({ type: () => Income, isArray: true }) - @OneToMany(() => Income, (it) => it.client, { + @MultiORMOneToMany(() => Income, (it) => it.client, { onDelete: 'SET NULL' }) incomes?: IIncome[]; @@ -207,7 +204,7 @@ export class OrganizationContact extends TenantOrganizationBaseEntity * TimeLog */ @ApiPropertyOptional({ type: () => TimeLog, isArray: true }) - @OneToMany(() => TimeLog, (it) => it.organizationContact) + @MultiORMOneToMany(() => TimeLog, (it) => it.organizationContact) timeLogs?: ITimeLog[]; /* @@ -216,9 +213,11 @@ export class OrganizationContact extends TenantOrganizationBaseEntity |-------------------------------------------------------------------------- */ // Organization Contact Tags - @ManyToMany(() => Tag, (tag) => tag.organizationContacts, { + @MultiORMManyToMany(() => Tag, (tag) => tag.organizationContacts, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_organization_contact', }) @JoinTable({ name: 'tag_organization_contact' @@ -226,9 +225,11 @@ export class OrganizationContact extends TenantOrganizationBaseEntity tags: ITag[]; // Organization Contact Employees - @ManyToMany(() => Employee, (it) => it.organizationContacts, { + @MultiORMManyToMany(() => Employee, (it) => it.organizationContacts, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'organization_contact_employee', }) @JoinTable({ name: 'organization_contact_employee' diff --git a/packages/core/src/organization-contact/organization-contact.service.ts b/packages/core/src/organization-contact/organization-contact.service.ts index 87ba8c6308d..c6ba011e232 100644 --- a/packages/core/src/organization-contact/organization-contact.service.ts +++ b/packages/core/src/organization-contact/organization-contact.service.ts @@ -34,7 +34,7 @@ export class OrganizationContactService extends TenantAwareCrudService { try { - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.setFindOptions({ select: { id: true, diff --git a/packages/core/src/organization-department/organization-department.entity.ts b/packages/core/src/organization-department/organization-department.entity.ts index f4cd8e3357e..4d0cbc4ed58 100644 --- a/packages/core/src/organization-department/organization-department.entity.ts +++ b/packages/core/src/organization-department/organization-department.entity.ts @@ -6,15 +6,16 @@ import { } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; -import { Column, Index, JoinTable, ManyToMany } from 'typeorm'; +import { Index, JoinTable } from 'typeorm'; import { Candidate, Employee, Tag, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationDepartmentRepository } from './repository/mikro-orm-organization-department.repository'; +import { MultiORMManyToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_department', { mikroOrmRepository: () => MikroOrmOrganizationDepartmentRepository }) export class OrganizationDepartment extends TenantOrganizationBaseEntity implements IOrganizationDepartment { @@ -23,7 +24,7 @@ export class OrganizationDepartment extends TenantOrganizationBaseEntity impleme @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() name: string; /* |-------------------------------------------------------------------------- @@ -35,9 +36,11 @@ export class OrganizationDepartment extends TenantOrganizationBaseEntity impleme * Tag */ @ApiProperty({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.organizationDepartments, { + @MultiORMManyToMany(() => Tag, (tag) => tag.organizationDepartments, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_organization_department', }) @JoinTable({ name: 'tag_organization_department' @@ -48,8 +51,10 @@ export class OrganizationDepartment extends TenantOrganizationBaseEntity impleme * Employee */ @ApiProperty({ type: () => Employee, isArray: true }) - @ManyToMany(() => Employee, (employee) => employee.organizationDepartments, { - cascade: ['update'] + @MultiORMManyToMany(() => Employee, (employee) => employee.organizationDepartments, { + cascade: ['update'], + owner: true, + pivotTable: 'organization_department_employee', }) @JoinTable({ name: 'organization_department_employee' @@ -60,9 +65,11 @@ export class OrganizationDepartment extends TenantOrganizationBaseEntity impleme * Candidate */ @ApiProperty({ type: () => Candidate, isArray: true }) - @ManyToMany(() => Candidate, (candidate) => candidate.organizationDepartments, { + @MultiORMManyToMany(() => Candidate, (candidate) => candidate.organizationDepartments, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'candidate_department', }) @JoinTable({ name: 'candidate_department' diff --git a/packages/core/src/organization-document/organization-document.entity.ts b/packages/core/src/organization-document/organization-document.entity.ts index e4d75aa95d0..d4806a6a8b4 100644 --- a/packages/core/src/organization-document/organization-document.entity.ts +++ b/packages/core/src/organization-document/organization-document.entity.ts @@ -1,10 +1,11 @@ -import { Column, Index, JoinColumn, ManyToOne, RelationId } from 'typeorm'; +import { Index, JoinColumn, RelationId } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { IImageAsset as IDocumentAsset, IOrganizationDocument } from '@gauzy/contracts'; import { ImageAsset, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationDocumentRepository } from './repository/mikro-orm-organization-document.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_document', { mikroOrmRepository: () => MikroOrmOrganizationDocumentRepository }) export class OrganizationDocument extends TenantOrganizationBaseEntity implements IOrganizationDocument { @@ -12,13 +13,13 @@ export class OrganizationDocument extends TenantOrganizationBaseEntity implement @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) documentUrl: string; /* @@ -30,7 +31,7 @@ export class OrganizationDocument extends TenantOrganizationBaseEntity implement /** * Document Asset */ - @ManyToOne(() => ImageAsset, { + @MultiORMManyToOne(() => ImageAsset, { /** Database cascade action on delete. */ onDelete: 'SET NULL', @@ -45,6 +46,6 @@ export class OrganizationDocument extends TenantOrganizationBaseEntity implement @IsUUID() @RelationId((it: OrganizationDocument) => it.document) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) documentId?: IDocumentAsset['id']; } diff --git a/packages/core/src/organization-employment-type/organization-employment-type.entity.ts b/packages/core/src/organization-employment-type/organization-employment-type.entity.ts index fb2841055a9..f66a0cc5551 100644 --- a/packages/core/src/organization-employment-type/organization-employment-type.entity.ts +++ b/packages/core/src/organization-employment-type/organization-employment-type.entity.ts @@ -1,5 +1,5 @@ import { ICandidate, IEmployee, IOrganizationEmploymentType, ITag } from '@gauzy/contracts'; -import { Column, JoinTable, ManyToMany } from 'typeorm'; +import { JoinTable } from 'typeorm'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { Candidate, @@ -7,13 +7,14 @@ import { Tag, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationEmploymentTypeRepository } from './repository/mikro-orm-organization-employment-type.repository'; +import { MultiORMManyToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_employment_type', { mikroOrmRepository: () => MikroOrmOrganizationEmploymentTypeRepository }) export class OrganizationEmploymentType extends TenantOrganizationBaseEntity implements IOrganizationEmploymentType { - @Column() + @MultiORMColumn() name: string; /* @@ -23,9 +24,11 @@ export class OrganizationEmploymentType extends TenantOrganizationBaseEntity imp */ @ApiPropertyOptional({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.organizationEmploymentTypes, { + @MultiORMManyToMany(() => Tag, (tag) => tag.organizationEmploymentTypes, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_organization_employment_type', }) @JoinTable({ name: 'tag_organization_employment_type' @@ -36,8 +39,10 @@ export class OrganizationEmploymentType extends TenantOrganizationBaseEntity imp * Employee */ @ApiPropertyOptional({ type: () => Employee, isArray: true }) - @ManyToMany(() => Employee, (employee) => employee.organizationEmploymentTypes, { - cascade: ['update'] + @MultiORMManyToMany(() => Employee, (employee) => employee.organizationEmploymentTypes, { + cascade: ['update'], + owner: true, + pivotTable: 'organization_employment_type_employee', }) @JoinTable({ name: 'organization_employment_type_employee' @@ -48,9 +53,11 @@ export class OrganizationEmploymentType extends TenantOrganizationBaseEntity imp * Candidate */ @ApiPropertyOptional({ type: () => Candidate, isArray: true }) - @ManyToMany(() => Candidate, (candidate) => candidate.organizationEmploymentTypes, { + @MultiORMManyToMany(() => Candidate, (candidate) => candidate.organizationEmploymentTypes, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'candidate_employment_type', }) @JoinTable({ name: 'candidate_employment_type' diff --git a/packages/core/src/organization-language/organization-language.entity.ts b/packages/core/src/organization-language/organization-language.entity.ts index e583af6c1ca..d254dbd241e 100644 --- a/packages/core/src/organization-language/organization-language.entity.ts +++ b/packages/core/src/organization-language/organization-language.entity.ts @@ -1,4 +1,4 @@ -import { Column, Index, JoinColumn, ManyToOne, RelationId } from 'typeorm'; +import { Index, JoinColumn, RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { IOrganizationLanguage } from '@gauzy/contracts'; @@ -6,16 +6,19 @@ import { Language, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationLanguageRepository } from './repository/mikro-orm-organization-language.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_language', { mikroOrmRepository: () => MikroOrmOrganizationLanguageRepository }) export class OrganizationLanguage extends TenantOrganizationBaseEntity implements IOrganizationLanguage { @ApiProperty({ type: () => Language }) - @ManyToOne(() => Language, { + @MultiORMManyToOne(() => Language, { nullable: false, - onDelete: 'CASCADE' + onDelete: 'CASCADE', + referenceColumnName: 'code', + joinColumn: 'languageCode' }) @JoinColumn({ referencedColumnName: "code" }) language: Language; @@ -25,18 +28,18 @@ export class OrganizationLanguage extends TenantOrganizationBaseEntity implement @IsString() @IsOptional() @Index() - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false, relationId: true }) languageCode: string; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column() + @MultiORMColumn() level: string; } diff --git a/packages/core/src/organization-position/organization-position.entity.ts b/packages/core/src/organization-position/organization-position.entity.ts index 9748861146f..12a6f724c94 100644 --- a/packages/core/src/organization-position/organization-position.entity.ts +++ b/packages/core/src/organization-position/organization-position.entity.ts @@ -1,10 +1,11 @@ -import { Column, Index, ManyToMany, JoinTable } from 'typeorm'; +import { Index, JoinTable } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; import { IOrganizationPosition, ITag } from '@gauzy/contracts'; import { Tag, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationPositionRepository } from './repository/mikro-orm-organization-position.repository'; +import { MultiORMManyToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_position', { mikroOrmRepository: () => MikroOrmOrganizationPositionRepository }) export class OrganizationPosition extends TenantOrganizationBaseEntity implements IOrganizationPosition { @@ -13,7 +14,7 @@ export class OrganizationPosition extends TenantOrganizationBaseEntity implement @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() name: string; /* @@ -22,9 +23,11 @@ export class OrganizationPosition extends TenantOrganizationBaseEntity implement |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.organizationPositions, { + @MultiORMManyToMany(() => Tag, (tag) => tag.organizationPositions, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_organization_position', }) @JoinTable({ name: 'tag_organization_position' diff --git a/packages/core/src/organization-project/organization-project.entity.ts b/packages/core/src/organization-project/organization-project.entity.ts index 62f1bebbb5b..bbe2ba79fa3 100644 --- a/packages/core/src/organization-project/organization-project.entity.ts +++ b/packages/core/src/organization-project/organization-project.entity.ts @@ -1,10 +1,6 @@ import { - Column, Index, JoinColumn, - ManyToOne, - ManyToMany, - OneToMany, RelationId, JoinTable, } from 'typeorm'; @@ -58,66 +54,67 @@ import { TenantOrganizationBaseEntity, TimeLog } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationProjectRepository } from './repository/mikro-orm-organization-project.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_project', { mikroOrmRepository: () => MikroOrmOrganizationProjectRepository }) export class OrganizationProject extends TenantOrganizationBaseEntity implements IOrganizationProject { @Index() - @Column() + @MultiORMColumn() name: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) startDate?: Date; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endDate?: Date; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) billing: ProjectBillingEnum; @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) currency: CurrenciesEnum; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) public: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) owner: ProjectOwnerEnum; - @Column({ default: TaskListTypeEnum.GRID }) + @MultiORMColumn({ default: TaskListTypeEnum.GRID }) taskListType: TaskListTypeEnum; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) code?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) color?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) billable?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) billingFlat?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) openSource?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) projectUrl?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) openSourceProjectUrl?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) budget?: number; - @Column({ + @MultiORMColumn({ nullable: true, default: OrganizationProjectBudgetTypeEnum.COST, ...(isMySQL() ? @@ -127,31 +124,31 @@ export class OrganizationProject extends TenantOrganizationBaseEntity implements }) budgetType?: OrganizationProjectBudgetTypeEnum; - @Column({ nullable: true, default: 0 }) + @MultiORMColumn({ nullable: true, default: 0 }) membersCount?: number; - @Column({ length: 500, nullable: true }) + @MultiORMColumn({ length: 500, nullable: true }) imageUrl?: string; @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() @Index() - @Column({ default: true, nullable: true }) + @MultiORMColumn({ default: true, nullable: true }) isTasksAutoSync?: boolean; @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() @Index() - @Column({ default: true, nullable: true }) + @MultiORMColumn({ default: true, nullable: true }) isTasksAutoSyncOnLabel?: boolean; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) syncTag?: string; /* @@ -163,7 +160,7 @@ export class OrganizationProject extends TenantOrganizationBaseEntity implements /** * OrganizationGithubRepository Relationship */ - @ManyToOne(() => OrganizationGithubRepository, (it) => it.projects, { + @MultiORMManyToOne(() => OrganizationGithubRepository, (it) => it.projects, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -181,13 +178,13 @@ export class OrganizationProject extends TenantOrganizationBaseEntity implements @IsUUID() @RelationId((it: OrganizationProject) => it.repository) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) repositoryId?: IOrganizationGithubRepository['id']; /** * Organization Contact Relationship */ - @ManyToOne(() => OrganizationContact, (it) => it.projects, { + @MultiORMManyToOne(() => OrganizationContact, (it) => it.projects, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -208,13 +205,13 @@ export class OrganizationProject extends TenantOrganizationBaseEntity implements @IsUUID() @RelationId((it: OrganizationProject) => it.organizationContact) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationContactId?: IOrganizationContact['id']; /** * ImageAsset Relationship */ - @ManyToOne(() => ImageAsset, { + @MultiORMManyToOne(() => ImageAsset, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -235,7 +232,7 @@ export class OrganizationProject extends TenantOrganizationBaseEntity implements @IsUUID() @RelationId((it: OrganizationProject) => it.image) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) imageId?: IImageAsset['id']; /* @@ -247,73 +244,73 @@ export class OrganizationProject extends TenantOrganizationBaseEntity implements /** * Organization Tasks Relationship */ - @OneToMany(() => Task, (it) => it.project) + @MultiORMOneToMany(() => Task, (it) => it.project) tasks?: ITask[]; /** * TimeLog Relationship */ - @OneToMany(() => TimeLog, (it) => it.project) + @MultiORMOneToMany(() => TimeLog, (it) => it.project) timeLogs?: ITimeLog[]; /** * Organization Invoice Items Relationship */ - @OneToMany(() => InvoiceItem, (it) => it.project) + @MultiORMOneToMany(() => InvoiceItem, (it) => it.project) invoiceItems?: IInvoiceItem[]; /** * Organization Sprints Relationship */ - @OneToMany(() => OrganizationSprint, (it) => it.project) + @MultiORMOneToMany(() => OrganizationSprint, (it) => it.project) organizationSprints?: IOrganizationSprint[]; /** * Organization Payments Relationship */ - @OneToMany(() => Payment, (it) => it.project) + @MultiORMOneToMany(() => Payment, (it) => it.project) payments?: IPayment[]; /** * Expense Relationship */ - @OneToMany(() => Expense, (it) => it.project) + @MultiORMOneToMany(() => Expense, (it) => it.project) expenses?: IExpense[]; /** * Activity Relationship */ - @OneToMany(() => Activity, (it) => it.project) + @MultiORMOneToMany(() => Activity, (it) => it.project) activities?: IActivity[]; /** * Project Statuses */ - @OneToMany(() => TaskStatus, (it) => it.project) + @MultiORMOneToMany(() => TaskStatus, (it) => it.project) statuses?: ITaskStatus[]; /** * Project Related Issue Type Relationship */ - @OneToMany(() => TaskRelatedIssueType, (it) => it.project) + @MultiORMOneToMany(() => TaskRelatedIssueType, (it) => it.project) relatedIssueTypes?: ITaskRelatedIssueType[]; /** * Project Priorities Relationship */ - @OneToMany(() => TaskPriority, (it) => it.project) + @MultiORMOneToMany(() => TaskPriority, (it) => it.project) priorities?: ITaskPriority[]; /** * Project Sizes Relationship */ - @OneToMany(() => TaskSize, (it) => it.project) + @MultiORMOneToMany(() => TaskSize, (it) => it.project) sizes?: ITaskSize[]; /** * Project Versions Relationship */ - @OneToMany(() => TaskVersion, (it) => it.project) + @MultiORMOneToMany(() => TaskVersion, (it) => it.project) versions?: ITaskVersion[]; /* @@ -325,11 +322,13 @@ export class OrganizationProject extends TenantOrganizationBaseEntity implements /** * Tags Relationship */ - @ManyToMany(() => Tag, (it) => it.organizationProjects, { + @MultiORMManyToMany(() => Tag, (it) => it.organizationProjects, { /** Defines the database action to perform on update. */ onUpdate: 'CASCADE', /** Defines the database cascade action on delete. */ onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_organization_project', }) @JoinTable({ name: 'tag_organization_project', @@ -339,7 +338,7 @@ export class OrganizationProject extends TenantOrganizationBaseEntity implements /** * Project Members Relationship */ - @ManyToMany(() => Employee, (it) => it.projects, { + @MultiORMManyToMany(() => Employee, (it) => it.projects, { /** Defines the database action to perform on update. */ onUpdate: 'CASCADE', /** Defines the database cascade action on delete. */ @@ -350,11 +349,13 @@ export class OrganizationProject extends TenantOrganizationBaseEntity implements /** * Organization Teams Relationship */ - @ManyToMany(() => OrganizationTeam, (it) => it.projects, { + @MultiORMManyToMany(() => OrganizationTeam, (it) => it.projects, { /** Defines the database action to perform on update. */ onUpdate: 'CASCADE', /** Defines the database cascade action on delete. */ onDelete: 'CASCADE', + owner: true, + pivotTable: 'organization_project_team', }) @JoinTable({ name: 'organization_project_team' diff --git a/packages/core/src/organization-project/organization-project.service.ts b/packages/core/src/organization-project/organization-project.service.ts index d32be7f7f1a..8f91dea2252 100644 --- a/packages/core/src/organization-project/organization-project.service.ts +++ b/packages/core/src/organization-project/organization-project.service.ts @@ -38,7 +38,7 @@ export class OrganizationProjectService extends TenantAwareCrudService { - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.setFindOptions({ select: { id: true, @@ -169,7 +169,7 @@ export class OrganizationProjectService extends TenantAwareCrudService ): Promise> { // Create a query builder for the `OrganizationProject` entity - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); // Set find options (skip, take, and relations) query.skip(options && options.skip ? options.take * (options.skip - 1) : 0); diff --git a/packages/core/src/organization-recurring-expense/organization-recurring-expense.entity.ts b/packages/core/src/organization-recurring-expense/organization-recurring-expense.entity.ts index 2c038aec730..5068015a66c 100644 --- a/packages/core/src/organization-recurring-expense/organization-recurring-expense.entity.ts +++ b/packages/core/src/organization-recurring-expense/organization-recurring-expense.entity.ts @@ -15,9 +15,9 @@ import { IsBoolean } from 'class-validator'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { Column, Index } from 'typeorm'; +import { Index } from 'typeorm'; import { TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationRecurringExpenseRepository } from './repository/mikro-orm-organization-recurring-expense.repository'; @MultiORMEntity('organization_recurring_expense', { mikroOrmRepository: () => MikroOrmOrganizationRecurringExpenseRepository }) @@ -27,7 +27,7 @@ export class OrganizationRecurringExpense extends TenantOrganizationBaseEntity i @IsNotEmpty() @Min(1) @Max(31) - @Column() + @MultiORMColumn() startDay: number; @ApiProperty({ type: () => Number, minimum: 1, maximum: 12 }) @@ -35,19 +35,19 @@ export class OrganizationRecurringExpense extends TenantOrganizationBaseEntity i @IsNotEmpty() @Min(1) @Max(12) - @Column() + @MultiORMColumn() startMonth: number; @ApiProperty({ type: () => Number, minimum: 1 }) @IsNumber() @IsNotEmpty() @Min(0) - @Column() + @MultiORMColumn() startYear: number; @ApiProperty({ type: () => Date }) @IsDate() - @Column() + @MultiORMColumn() startDate: Date; @ApiProperty({ type: () => Number, minimum: 1, maximum: 31 }) @@ -55,7 +55,7 @@ export class OrganizationRecurringExpense extends TenantOrganizationBaseEntity i @IsOptional() @Min(1) @Max(31) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endDay: number; @ApiProperty({ type: () => Number, minimum: 1, maximum: 12 }) @@ -63,33 +63,33 @@ export class OrganizationRecurringExpense extends TenantOrganizationBaseEntity i @IsOptional() @Min(1) @Max(12) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endMonth: number; @ApiProperty({ type: () => Number, minimum: 1 }) @IsNumber() @IsOptional() @Min(0) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endYear: number; @ApiProperty({ type: () => Date }) @IsDate() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endDate?: Date; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() categoryName: string; @ApiProperty({ type: () => Number }) @IsNumber() @IsNotEmpty() - @Column({ + @MultiORMColumn({ type: 'numeric', transformer: new ColumnNumericTransformerPipe() }) @@ -99,18 +99,18 @@ export class OrganizationRecurringExpense extends TenantOrganizationBaseEntity i @IsEnum(CurrenciesEnum) @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() currency: string; @ApiProperty({ type: () => Boolean }) @IsBoolean() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) splitExpense: boolean; @ApiProperty({ type: () => String }) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) parentRecurringExpenseId?: string; } diff --git a/packages/core/src/organization-sprint/organization-sprint.entity.ts b/packages/core/src/organization-sprint/organization-sprint.entity.ts index f107795b5d1..7576aa8db00 100644 --- a/packages/core/src/organization-sprint/organization-sprint.entity.ts +++ b/packages/core/src/organization-sprint/organization-sprint.entity.ts @@ -1,4 +1,4 @@ -import { Column, OneToMany, JoinColumn, ManyToOne } from 'typeorm'; +import { JoinColumn } from 'typeorm'; import { IOrganizationSprint, SprintStartDayEnum } from '@gauzy/contracts'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { @@ -13,49 +13,44 @@ import { Task, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationSprintRepository } from './repository/mikro-orm-organization-sprint.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_sprint', { mikroOrmRepository: () => MikroOrmOrganizationSprintRepository }) export class OrganizationSprint extends TenantOrganizationBaseEntity implements IOrganizationSprint { @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() - @Column() + @MultiORMColumn() name: string; - @ApiProperty({ type: () => String }) - @IsString() - @IsNotEmpty() - @Column() - projectId: string; - @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) goal?: string; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ default: 7 }) + @MultiORMColumn({ default: 7 }) length: number; @ApiPropertyOptional({ type: () => Date }) @IsDate() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) startDate?: Date; @ApiPropertyOptional({ type: () => Date }) @IsDate() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) endDate?: Date; @ApiProperty({ type: () => Number, enum: SprintStartDayEnum }) @IsNumber() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) dayStart?: number; /* @@ -68,7 +63,7 @@ export class OrganizationSprint extends TenantOrganizationBaseEntity implements * OrganizationProject Relationship */ @ApiProperty({ type: () => OrganizationProject }) - @ManyToOne(() => OrganizationProject, (it) => it.organizationSprints, { + @MultiORMManyToOne(() => OrganizationProject, (it) => it.organizationSprints, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -78,6 +73,13 @@ export class OrganizationSprint extends TenantOrganizationBaseEntity implements @JoinColumn() project?: OrganizationProject; + + @ApiProperty({ type: () => String }) + @IsString() + @IsNotEmpty() + @MultiORMColumn({ relationId: true }) + projectId: string; + /* |-------------------------------------------------------------------------- | @OneToMany @@ -85,7 +87,7 @@ export class OrganizationSprint extends TenantOrganizationBaseEntity implements */ @ApiProperty({ type: () => Task }) - @OneToMany(() => Task, (task) => task.organizationSprint) + @MultiORMOneToMany(() => Task, (task) => task.organizationSprint) @JoinColumn() tasks?: Task[]; } diff --git a/packages/core/src/organization-task-setting/organization-task-setting.entity.ts b/packages/core/src/organization-task-setting/organization-task-setting.entity.ts index b8bd5d6c5a5..fce55c2245e 100644 --- a/packages/core/src/organization-task-setting/organization-task-setting.entity.ts +++ b/packages/core/src/organization-task-setting/organization-task-setting.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, Index, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { IOrganizationProject, IOrganizationTaskSetting, @@ -18,8 +18,9 @@ import { OrganizationTeam, TenantOrganizationBaseEntity, } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationTaskSettingRepository } from './repository/mikro-orm-organization-task-setting.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_task_setting', { mikroOrmRepository: () => MikroOrmOrganizationTaskSettingRepository }) export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implements IOrganizationTaskSetting { @@ -31,7 +32,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksPrivacyEnabled: boolean; /** @@ -41,7 +42,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksMultipleAssigneesEnabled: boolean; /** @@ -51,7 +52,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksManualTimeEnabled: boolean; /** @@ -61,7 +62,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksGroupEstimationEnabled: boolean; /** @@ -71,7 +72,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksEstimationInHoursEnabled: boolean; /** @@ -81,7 +82,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksEstimationInStoryPointsEnabled: boolean; /** @@ -91,7 +92,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksProofOfCompletionEnabled: boolean; /** @@ -101,7 +102,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => String, enum: TaskProofOfCompletionTypeEnum }) @IsOptional() @IsEnum(TaskProofOfCompletionTypeEnum) - @Column({ default: TaskProofOfCompletionTypeEnum.PRIVATE }) + @MultiORMColumn({ default: TaskProofOfCompletionTypeEnum.PRIVATE }) tasksProofOfCompletionType: TaskProofOfCompletionTypeEnum; /** @@ -111,7 +112,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksLinkedEnabled: boolean; /** @@ -121,7 +122,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksCommentsEnabled: boolean; /** @@ -131,7 +132,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksHistoryEnabled: boolean; /** @@ -141,7 +142,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksAcceptanceCriteriaEnabled: boolean; /** @@ -151,7 +152,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksDraftsEnabled: boolean; /** @@ -161,7 +162,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksNotifyLeftEnabled: boolean; /** @@ -170,7 +171,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Number }) @IsOptional() @IsNumber() - @Column({ default: 7 }) + @MultiORMColumn({ default: 7 }) tasksNotifyLeftPeriodDays: number; /** @@ -180,7 +181,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksAutoCloseEnabled: boolean; /** @@ -189,7 +190,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Number }) @IsOptional() @IsNumber() - @Column({ default: 7 }) + @MultiORMColumn({ default: 7 }) tasksAutoClosePeriodDays: number; /** @@ -199,7 +200,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksAutoArchiveEnabled: boolean; /** @@ -208,7 +209,7 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Number }) @IsOptional() @IsNumber() - @Column({ default: 7 }) + @MultiORMColumn({ default: 7 }) tasksAutoArchivePeriodDays: number; /** @@ -218,13 +219,13 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isTasksAutoStatusEnabled: boolean; /** * Organization Project */ - @ManyToOne(() => OrganizationProject, { + @MultiORMManyToOne(() => OrganizationProject, { /** Indicates if relation column value can be nullable or not. */ nullable: true, @@ -238,13 +239,13 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @IsUUID() @RelationId((it: OrganizationTaskSetting) => it.project) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; /** * Organization Team */ - @ManyToOne(() => OrganizationTeam, { + @MultiORMManyToOne(() => OrganizationTeam, { /** Indicates if relation column value can be nullable or not. */ nullable: true, @@ -258,6 +259,6 @@ export class OrganizationTaskSetting extends TenantOrganizationBaseEntity implem @IsUUID() @RelationId((it: OrganizationTaskSetting) => it.organizationTeam) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationTeamId?: IOrganizationTeam['id']; } diff --git a/packages/core/src/organization-team-employee/organization-team-employee.entity.ts b/packages/core/src/organization-team-employee/organization-team-employee.entity.ts index 01208f3d7c9..58328221db8 100644 --- a/packages/core/src/organization-team-employee/organization-team-employee.entity.ts +++ b/packages/core/src/organization-team-employee/organization-team-employee.entity.ts @@ -1,10 +1,11 @@ import { IEmployee, IOrganizationTeam, IOrganizationTeamEmployee, IRole, ITask } from '@gauzy/contracts'; -import { Column, ManyToOne, RelationId, Index } from 'typeorm'; +import { RelationId, Index } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsBoolean, IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; import { Employee, OrganizationTeam, Role, Task, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationTeamEmployeeRepository } from './repository/mikro-orm-organization-team-employee.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_team_employee', { mikroOrmRepository: () => MikroOrmOrganizationTeamEmployeeRepository }) export class OrganizationTeamEmployee extends TenantOrganizationBaseEntity implements IOrganizationTeamEmployee { @@ -14,7 +15,7 @@ export class OrganizationTeamEmployee extends TenantOrganizationBaseEntity imple @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ type: Boolean, nullable: true, default: true }) + @MultiORMColumn({ type: Boolean, nullable: true, default: true }) public isTrackingEnabled?: boolean; /* @@ -27,7 +28,7 @@ export class OrganizationTeamEmployee extends TenantOrganizationBaseEntity imple * member's active task */ @ApiProperty({ type: () => Task }) - @ManyToOne(() => Task, (it) => it.organizationTeamEmployees, { + @MultiORMManyToOne(() => Task, (it) => it.organizationTeamEmployees, { /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -38,14 +39,14 @@ export class OrganizationTeamEmployee extends TenantOrganizationBaseEntity imple @IsUUID() @RelationId((it: OrganizationTeamEmployee) => it.activeTask) @Index() - @Column({ type: String, nullable: true }) + @MultiORMColumn({ type: String, nullable: true, relationId: true }) public activeTaskId?: ITask['id']; /** * OrganizationTeam */ @ApiProperty({ type: () => OrganizationTeam }) - @ManyToOne(() => OrganizationTeam, (it) => it.members, { + @MultiORMManyToOne(() => OrganizationTeam, (it) => it.members, { /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -56,14 +57,14 @@ export class OrganizationTeamEmployee extends TenantOrganizationBaseEntity imple @IsUUID() @RelationId((it: OrganizationTeamEmployee) => it.organizationTeam) @Index() - @Column() + @MultiORMColumn({ relationId: true }) public organizationTeamId: IOrganizationTeam['id']; /** * Employee */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, (employee) => employee.teams, { + @MultiORMManyToOne(() => Employee, (employee) => employee.teams, { /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -72,14 +73,14 @@ export class OrganizationTeamEmployee extends TenantOrganizationBaseEntity imple @ApiProperty({ type: () => String }) @RelationId((it: OrganizationTeamEmployee) => it.employee) @Index() - @Column() + @MultiORMColumn({ relationId: true }) public employeeId: IEmployee['id']; /** * Role */ @ApiPropertyOptional({ type: () => Role }) - @ManyToOne(() => Role, { + @MultiORMManyToOne(() => Role, { /** Indicates if relation column value can be nullable or not. */ nullable: true, @@ -93,11 +94,11 @@ export class OrganizationTeamEmployee extends TenantOrganizationBaseEntity imple @IsUUID() @RelationId((it: OrganizationTeamEmployee) => it.role) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) public roleId?: IRole['id']; @ApiPropertyOptional({ type: () => Number }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) public order: number; } diff --git a/packages/core/src/organization-team-join-request/organization-team-join-request.entity.ts b/packages/core/src/organization-team-join-request/organization-team-join-request.entity.ts index db388c81198..d5a6ecb9d86 100644 --- a/packages/core/src/organization-team-join-request/organization-team-join-request.entity.ts +++ b/packages/core/src/organization-team-join-request/organization-team-join-request.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, JoinColumn, ManyToOne, RelationId } from 'typeorm'; +import { Index, JoinColumn, RelationId } from 'typeorm'; import { Exclude } from 'class-transformer'; import { IsEmail, IsEnum, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { @@ -9,8 +9,9 @@ import { OrganizationTeamJoinRequestStatusEnum } from '@gauzy/contracts'; import { OrganizationTeam, TenantOrganizationBaseEntity, User } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationTeamJoinRequestRepository } from './repository/mikro-orm-organization-team-join-request.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_team_join_request', { mikroOrmRepository: () => MikroOrmOrganizationTeamJoinRequestRepository }) export class OrganizationTeamJoinRequest extends TenantOrganizationBaseEntity @@ -19,43 +20,43 @@ export class OrganizationTeamJoinRequest extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @IsNotEmpty() @IsEmail() - @Column() + @MultiORMColumn() email: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) fullName: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) linkAddress: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) position: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsEnum(OrganizationTeamJoinRequestStatusEnum) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) status: OrganizationTeamJoinRequestStatusEnum; @Exclude({ toPlainOnly: true }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) code: string; @Exclude({ toPlainOnly: true }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) token: string; @Exclude({ toPlainOnly: true }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) expiredAt: Date; /** Additional fields */ @@ -70,7 +71,7 @@ export class OrganizationTeamJoinRequest extends TenantOrganizationBaseEntity /** * Join request belongs to user */ - @ManyToOne(() => User, { + @MultiORMManyToOne(() => User, { /** Indicates if relation column value can be nullable or not. */ nullable: true, @@ -85,13 +86,13 @@ export class OrganizationTeamJoinRequest extends TenantOrganizationBaseEntity @IsUUID() @RelationId((it: OrganizationTeamJoinRequest) => it.user) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) userId?: IUser['id']; /** * Join request belongs to organization team */ - @ManyToOne(() => OrganizationTeam, { + @MultiORMManyToOne(() => OrganizationTeam, { /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -102,6 +103,6 @@ export class OrganizationTeamJoinRequest extends TenantOrganizationBaseEntity @IsUUID() @RelationId((it: OrganizationTeamJoinRequest) => it.organizationTeam) @Index() - @Column() + @MultiORMColumn({ relationId: true }) organizationTeamId?: IOrganizationTeam['id']; } diff --git a/packages/core/src/organization-team-join-request/organization-team-join-request.service.ts b/packages/core/src/organization-team-join-request/organization-team-join-request.service.ts index 9747ed88a3b..9f12c51d811 100644 --- a/packages/core/src/organization-team-join-request/organization-team-join-request.service.ts +++ b/packages/core/src/organization-team-join-request/organization-team-join-request.service.ts @@ -168,7 +168,7 @@ export class OrganizationTeamJoinRequestService extends TenantAwareCrudService { const { email, token, code, organizationTeamId } = options; try { - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.setFindOptions({ select: { id: true, diff --git a/packages/core/src/organization-team/organization-team.entity.ts b/packages/core/src/organization-team/organization-team.entity.ts index 1bdb2680c86..163687483b1 100644 --- a/packages/core/src/organization-team/organization-team.entity.ts +++ b/packages/core/src/organization-team/organization-team.entity.ts @@ -1,10 +1,6 @@ import { - Column, Index, - OneToMany, - ManyToMany, JoinTable, - ManyToOne, JoinColumn, RelationId, } from 'typeorm'; @@ -52,8 +48,9 @@ import { TenantOrganizationBaseEntity, User, } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationTeamRepository } from './repository/mikro-orm-organization-team.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('organization_team', { mikroOrmRepository: () => MikroOrmOrganizationTeamRepository }) export class OrganizationTeam extends TenantOrganizationBaseEntity implements IOrganizationTeam { @@ -65,7 +62,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() name: string; /** @@ -74,7 +71,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) color?: string; /** @@ -83,7 +80,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) emoji?: string; /** @@ -92,7 +89,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) teamSize?: string; /** @@ -101,7 +98,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) logo?: string; /** @@ -110,7 +107,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) prefix?: string; /** @@ -120,7 +117,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO @ApiPropertyOptional({ type: () => Boolean, default: false }) @IsOptional() @IsBoolean() - @Column({ nullable: true, default: false }) + @MultiORMColumn({ nullable: true, default: false }) public?: boolean; /** @@ -130,7 +127,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO @IsOptional() @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) profile_link?: string; @@ -143,7 +140,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO /** * User */ - @ManyToOne(() => User, (it) => it.teams, { + @MultiORMManyToOne(() => User, (it) => it.teams, { /** Indicates if relation column value can be nullable or not. */ nullable: true, @@ -155,13 +152,13 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO @RelationId((it: OrganizationTeam) => it.createdBy) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) createdById?: IUser['id']; /** * ImageAsset */ - @ManyToOne(() => ImageAsset, { + @MultiORMManyToOne(() => ImageAsset, { /** Indicates if relation column value can be nullable or not. */ nullable: true, @@ -179,7 +176,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO @IsUUID() @RelationId((it: OrganizationTeam) => it.image) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) imageId?: IImageAsset['id']; /* @@ -191,7 +188,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO /** * OrganizationTeamEmployee */ - @OneToMany(() => OrganizationTeamEmployee, (it) => it.organizationTeam, { + @MultiORMOneToMany(() => OrganizationTeamEmployee, (it) => it.organizationTeam, { /** If set to true then it means that related object can be allowed to be inserted or updated in the database. */ cascade: true }) @@ -200,55 +197,55 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO /** * RequestApprovalTeam */ - @OneToMany(() => RequestApprovalTeam, (it) => it.team) + @MultiORMOneToMany(() => RequestApprovalTeam, (it) => it.team) requestApprovals?: IRequestApprovalTeam[]; /** * Goal */ - @OneToMany(() => Goal, (it) => it.ownerTeam) + @MultiORMOneToMany(() => Goal, (it) => it.ownerTeam) goals?: IGoal[]; /** * Team Statuses */ - @OneToMany(() => TaskStatus, (status) => status.organizationTeam) + @MultiORMOneToMany(() => TaskStatus, (status) => status.organizationTeam) statuses?: ITaskStatus[]; /** * Team Related Status type */ - @OneToMany(() => TaskRelatedIssueType, (it) => it.organizationTeam) + @MultiORMOneToMany(() => TaskRelatedIssueType, (it) => it.organizationTeam) relatedIssueTypes?: ITaskRelatedIssueType[]; /** * Team Priorities */ - @OneToMany(() => TaskPriority, (it) => it.organizationTeam) + @MultiORMOneToMany(() => TaskPriority, (it) => it.organizationTeam) priorities?: ITaskPriority[]; /** * Team Sizes */ - @OneToMany(() => TaskSize, (it) => it.organizationTeam) + @MultiORMOneToMany(() => TaskSize, (it) => it.organizationTeam) sizes?: ITaskSize[]; /** * Team Versions */ - @OneToMany(() => TaskVersion, (it) => it.organizationTeam) + @MultiORMOneToMany(() => TaskVersion, (it) => it.organizationTeam) versions?: ITaskVersion[]; /** * Team Labels */ - @OneToMany(() => Tag, (it) => it.organizationTeam) + @MultiORMOneToMany(() => Tag, (it) => it.organizationTeam) labels?: ITag[]; /** * Team Issue Types */ - @OneToMany(() => IssueType, (it) => it.organizationTeam) + @MultiORMOneToMany(() => IssueType, (it) => it.organizationTeam) issueTypes?: IIssueType[]; /* @@ -256,11 +253,13 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO | @ManyToMany |-------------------------------------------------------------------------- */ - @ManyToMany(() => Tag, (it) => it.organizationTeams, { + @MultiORMManyToMany(() => Tag, (it) => it.organizationTeams, { /** Defines the database action to perform on update. */ onUpdate: 'CASCADE', /** Defines the database cascade action on delete. */ - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_organization_team', }) @JoinTable({ name: 'tag_organization_team' @@ -270,11 +269,11 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO /** * Task */ - @ManyToMany(() => Task, (it) => it.teams, { + @MultiORMManyToMany(() => Task, (it) => it.teams, { /** Defines the database action to perform on update. */ onUpdate: 'CASCADE', /** Defines the database cascade action on delete. */ - onDelete: 'CASCADE' + onDelete: 'CASCADE', }) @JoinTable() tasks?: ITask[]; @@ -282,7 +281,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO /** * Equipment Sharing */ - @ManyToMany(() => EquipmentSharing, (it) => it.teams, { + @MultiORMManyToMany(() => EquipmentSharing, (it) => it.teams, { /** Defines the database action to perform on update. */ onUpdate: 'CASCADE', /** Defines the database cascade action on delete. */ @@ -293,7 +292,7 @@ export class OrganizationTeam extends TenantOrganizationBaseEntity implements IO /** * Organization Project */ - @ManyToMany(() => OrganizationProject, (it) => it.teams, { + @MultiORMManyToMany(() => OrganizationProject, (it) => it.teams, { /** Defines the database action to perform on update. */ onUpdate: 'CASCADE', /** Defines the database cascade action on delete. */ diff --git a/packages/core/src/organization-team/organization-team.service.ts b/packages/core/src/organization-team/organization-team.service.ts index 9acdfc12014..38aa42e79c3 100644 --- a/packages/core/src/organization-team/organization-team.service.ts +++ b/packages/core/src/organization-team/organization-team.service.ts @@ -392,8 +392,8 @@ export class OrganizationTeamService extends TenantAwareCrudService MikroOrmOrganizationVendorRepository }) export class OrganizationVendor extends TenantOrganizationBaseEntity implements IOrganizationVendor { @@ -13,25 +14,25 @@ export class OrganizationVendor extends TenantOrganizationBaseEntity implements @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) email?: string; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) phone?: string; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) website?: string; /* @@ -44,7 +45,7 @@ export class OrganizationVendor extends TenantOrganizationBaseEntity implements * Expense */ @ApiPropertyOptional({ type: () => Expense, isArray: true }) - @OneToMany(() => Expense, (it) => it.vendor) + @MultiORMOneToMany(() => Expense, (it) => it.vendor) expenses?: IExpense[]; /* @@ -57,9 +58,11 @@ export class OrganizationVendor extends TenantOrganizationBaseEntity implements * Tag */ @ApiPropertyOptional({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.organizationVendors, { + @MultiORMManyToMany(() => Tag, (tag) => tag.organizationVendors, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_organization_vendor' }) @JoinTable({ name: 'tag_organization_vendor' diff --git a/packages/core/src/organization/organization.entity.ts b/packages/core/src/organization/organization.entity.ts index 59186b139ad..0f0fc45c576 100644 --- a/packages/core/src/organization/organization.entity.ts +++ b/packages/core/src/organization/organization.entity.ts @@ -1,4 +1,4 @@ -import { Column, Index, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from 'typeorm'; +import { Index, JoinColumn, JoinTable, RelationId } from 'typeorm'; import { ApiPropertyOptional } from '@nestjs/swagger'; import { IsBoolean, IsNumber, IsOptional, IsString, IsUUID } from 'class-validator'; import { @@ -40,55 +40,56 @@ import { Tag, TenantBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmOrganizationRepository } from './repository/mikro-orm-organization.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('organization', { mikroOrmRepository: () => MikroOrmOrganizationRepository }) export class Organization extends TenantBaseEntity implements IOrganization { @Index() - @Column() + @MultiORMColumn() name: string; @Index() - @Column('boolean', { default: false }) + @MultiORMColumn('boolean', { default: false }) isDefault: boolean; @Index({ unique: false }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) profile_link: string; @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) banner: string; @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) totalEmployees: number; @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) short_description: string; @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) client_focus: string; @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) overview: string; - @Column({ length: 500, nullable: true }) + @MultiORMColumn({ length: 500, nullable: true }) imageUrl?: string; @Index() - @Column() + @MultiORMColumn() currency: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) valueDate?: Date; @Index() - @Column({ + @MultiORMColumn({ type: 'simple-enum', nullable: true, enum: DefaultValueDateTypeEnum, @@ -96,85 +97,85 @@ export class Organization extends TenantBaseEntity implements IOrganization { }) defaultValueDateType: DefaultValueDateTypeEnum; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) defaultAlignmentType?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) timeZone?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) regionCode?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) brandColor?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) dateFormat?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) officialName?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) startWeekOn?: WeekDaysEnum; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) taxId?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) numberFormat?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) minimumProjectSize?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) bonusType?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) bonusPercentage?: number; - @Column({ nullable: true, default: true }) + @MultiORMColumn({ nullable: true, default: true }) invitesAllowed?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_income?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_profits?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_bonuses_paid?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_total_hours?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_minimum_project_size?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_projects_count?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_clients_count?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_clients?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) show_employees_count?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) inviteExpiryPeriod?: number; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) fiscalStartDate?: Date; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) fiscalEndDate?: Date; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) registrationDate?: Date; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) futureDateAllowed?: boolean; /** @@ -184,7 +185,7 @@ export class Organization extends TenantBaseEntity implements IOrganization { * @default true * @type boolean */ - @Column({ default: true }) + @MultiORMColumn({ default: true }) allowManualTime?: boolean; /** @@ -194,7 +195,7 @@ export class Organization extends TenantBaseEntity implements IOrganization { * @default true * @type boolean */ - @Column({ default: true }) + @MultiORMColumn({ default: true }) allowModifyTime?: boolean; /** @@ -204,84 +205,84 @@ export class Organization extends TenantBaseEntity implements IOrganization { * @default true * @type boolean */ - @Column({ default: true }) + @MultiORMColumn({ default: true }) allowDeleteTime?: boolean; - @Column({ default: true }) + @MultiORMColumn({ default: true }) allowTrackInactivity?: boolean; - @Column({ default: 10 }) + @MultiORMColumn({ default: 10 }) inactivityTimeLimit?: number; - @Column({ default: 1 }) + @MultiORMColumn({ default: 1 }) activityProofDuration?: number; - @Column({ default: false }) + @MultiORMColumn({ default: false }) requireReason?: boolean; - @Column({ default: false }) + @MultiORMColumn({ default: false }) requireDescription?: boolean; - @Column({ default: false }) + @MultiORMColumn({ default: false }) requireProject?: boolean; - @Column({ default: false }) + @MultiORMColumn({ default: false }) requireTask?: boolean; - @Column({ default: false }) + @MultiORMColumn({ default: false }) requireClient?: boolean; - @Column({ default: 12 }) + @MultiORMColumn({ default: 12 }) timeFormat?: 12 | 24; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) separateInvoiceItemTaxAndDiscount?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) website?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) fiscalInformation?: string; - @Column({ default: CurrencyPosition.LEFT }) + @MultiORMColumn({ default: CurrencyPosition.LEFT }) currencyPosition?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) discountAfterTax?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) defaultStartTime?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) defaultEndTime?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) defaultInvoiceEstimateTerms?: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) convertAcceptedEstimates?: boolean; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) daysUntilDue?: number; - @Column({ default: false }) + @MultiORMColumn({ default: false }) isRemoveIdleTime?: boolean; - @Column({ default: true }) + @MultiORMColumn({ default: true }) allowScreenshotCapture?: boolean; /** Upwork Organization ID */ @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) upworkOrganizationId?: string; /** Upwork Organization Name */ @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) upworkOrganizationName?: string; /** @@ -290,7 +291,7 @@ export class Organization extends TenantBaseEntity implements IOrganization { @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ nullable: true, default: false }) + @MultiORMColumn({ nullable: true, default: false }) randomScreenshot?: boolean; /** @@ -299,7 +300,7 @@ export class Organization extends TenantBaseEntity implements IOrganization { @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ nullable: true, default: false }) + @MultiORMColumn({ nullable: true, default: false }) trackOnSleep?: boolean; /** @@ -308,7 +309,7 @@ export class Organization extends TenantBaseEntity implements IOrganization { @ApiPropertyOptional({ type: () => Number }) @IsOptional() @IsNumber() - @Column({ type: 'numeric', default: 10 }) + @MultiORMColumn({ type: 'numeric', default: 10 }) screenshotFrequency?: number; /** @@ -317,7 +318,7 @@ export class Organization extends TenantBaseEntity implements IOrganization { @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ nullable: true, default: false }) + @MultiORMColumn({ nullable: true, default: false }) enforced?: boolean; /* @@ -327,7 +328,7 @@ export class Organization extends TenantBaseEntity implements IOrganization { */ // Contact - @ManyToOne(() => Contact, (contact) => contact.organization, { + @MultiORMManyToOne(() => Contact, (contact) => contact.organization, { cascade: true, onDelete: 'SET NULL' }) @@ -335,13 +336,13 @@ export class Organization extends TenantBaseEntity implements IOrganization { @RelationId((it: Organization) => it.contact) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) readonly contactId?: string; /** * ImageAsset */ - @ManyToOne(() => ImageAsset, { + @MultiORMManyToOne(() => ImageAsset, { /** Database cascade action on delete. */ onDelete: 'SET NULL', @@ -356,7 +357,7 @@ export class Organization extends TenantBaseEntity implements IOrganization { @IsUUID() @RelationId((it: Organization) => it.image) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) imageId?: IImageAsset['id']; /* @@ -365,52 +366,52 @@ export class Organization extends TenantBaseEntity implements IOrganization { |-------------------------------------------------------------------------- */ - @OneToMany(() => Invoice, (invoice) => invoice.fromOrganization) + @MultiORMOneToMany(() => Invoice, (invoice) => invoice.fromOrganization) @JoinColumn() invoices?: IInvoice[]; - @OneToMany(() => Employee, (employee) => employee.organization) + @MultiORMOneToMany(() => Employee, (employee) => employee.organization) @JoinColumn() employees?: IEmployee[]; - @OneToMany(() => Deal, (deal) => deal.organization) + @MultiORMOneToMany(() => Deal, (deal) => deal.organization) @JoinColumn() deals?: IDeal[]; - @OneToMany(() => OrganizationAward, (award) => award.organization) + @MultiORMOneToMany(() => OrganizationAward, (award) => award.organization) @JoinColumn() awards?: IOrganizationAward[]; - @OneToMany(() => OrganizationLanguage, (language) => language.organization) + @MultiORMOneToMany(() => OrganizationLanguage, (language) => language.organization) @JoinColumn() languages?: IOrganizationLanguage[]; - @OneToMany(() => FeatureOrganization, (featureOrganization) => featureOrganization.organization) + @MultiORMOneToMany(() => FeatureOrganization, (featureOrganization) => featureOrganization.organization) featureOrganizations?: IFeatureOrganization[]; - @OneToMany(() => Payment, (payment) => payment.organization, { + @MultiORMOneToMany(() => Payment, (payment) => payment.organization, { onDelete: 'SET NULL' }) @JoinColumn() payments?: IPayment[]; - @OneToMany(() => OrganizationSprint, (sprint) => sprint.organization) + @MultiORMOneToMany(() => OrganizationSprint, (sprint) => sprint.organization) @JoinColumn() organizationSprints?: IOrganizationSprint[]; - @OneToMany(() => InvoiceEstimateHistory, (invoiceEstimateHistory) => invoiceEstimateHistory.organization, { + @MultiORMOneToMany(() => InvoiceEstimateHistory, (invoiceEstimateHistory) => invoiceEstimateHistory.organization, { onDelete: 'SET NULL' }) @JoinColumn() invoiceEstimateHistories?: IInvoiceEstimateHistory[]; - @OneToMany(() => AccountingTemplate, (accountingTemplate) => accountingTemplate.organization, { + @MultiORMOneToMany(() => AccountingTemplate, (accountingTemplate) => accountingTemplate.organization, { onDelete: 'SET NULL' }) @JoinColumn() accountingTemplates?: IAccountingTemplate[]; - @OneToMany(() => ReportOrganization, (reportOrganization) => reportOrganization.organization) + @MultiORMOneToMany(() => ReportOrganization, (reportOrganization) => reportOrganization.organization) @JoinColumn() reportOrganizations?: IReportOrganization[]; @@ -420,16 +421,18 @@ export class Organization extends TenantBaseEntity implements IOrganization { |-------------------------------------------------------------------------- */ // Tags - @ManyToMany(() => Tag, (it) => it.organizations, { + @MultiORMManyToMany(() => Tag, (it) => it.organizations, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_organization', }) @JoinTable({ name: 'tag_organization' }) tags: ITag[]; - @ManyToMany(() => Skill, (skill) => skill.organizations, { + @MultiORMManyToMany(() => Skill, (skill) => skill.organizations, { onUpdate: 'CASCADE', onDelete: 'CASCADE' }) diff --git a/packages/core/src/password-reset/password-reset.entity.ts b/packages/core/src/password-reset/password-reset.entity.ts index 9f968b8055f..8d500f3e1b2 100644 --- a/packages/core/src/password-reset/password-reset.entity.ts +++ b/packages/core/src/password-reset/password-reset.entity.ts @@ -1,10 +1,10 @@ -import { Index, Column, AfterLoad } from 'typeorm'; +import { Index, AfterLoad } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import * as moment from 'moment'; import { IPasswordReset } from '@gauzy/contracts'; import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; import { TenantBaseEntity } from './../core/entities/tenant-base.entity'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmPasswordResetRepository } from './repository/mikro-orm-password-reset.repository'; @MultiORMEntity('password_reset', { mikroOrmRepository: () => MikroOrmPasswordResetRepository }) @@ -15,7 +15,7 @@ export class PasswordReset extends TenantBaseEntity implements IPasswordReset { @IsNotEmpty() @IsEmail() @Index() - @Column() + @MultiORMColumn() email: string; /** */ @@ -23,7 +23,7 @@ export class PasswordReset extends TenantBaseEntity implements IPasswordReset { @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() token: string; /** */ diff --git a/packages/core/src/payment/payment.entity.ts b/packages/core/src/payment/payment.entity.ts index 34f7143db4a..888b659ec75 100644 --- a/packages/core/src/payment/payment.entity.ts +++ b/packages/core/src/payment/payment.entity.ts @@ -1,9 +1,6 @@ import { - Column, - ManyToOne, JoinColumn, JoinTable, - ManyToMany, RelationId, Index } from 'typeorm'; @@ -30,18 +27,19 @@ import { } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; import { IsOptional, IsUUID } from 'class-validator'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmPaymentRepository } from './repository/mikro-orm-payment.repository'; +import { MultiORMManyToMany, MultiORMManyToOne } from 'core/decorators/entity/relations'; @MultiORMEntity('payment', { mikroOrmRepository: () => MikroOrmPaymentRepository }) export class Payment extends TenantOrganizationBaseEntity implements IPayment { @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) paymentDate?: Date; @ApiPropertyOptional({ type: () => Number }) - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', transformer: new ColumnNumericTransformerPipe() @@ -49,15 +47,15 @@ export class Payment extends TenantOrganizationBaseEntity implements IPayment { amount?: number; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) note?: string; @ApiPropertyOptional({ type: () => String, enum: CurrenciesEnum }) - @Column() + @MultiORMColumn() currency?: string; @ApiPropertyOptional({ type: () => String, enum: PaymentMethodEnum }) - @Column({ + @MultiORMColumn({ type: 'simple-enum', nullable: true, enum: PaymentMethodEnum @@ -65,7 +63,7 @@ export class Payment extends TenantOrganizationBaseEntity implements IPayment { paymentMethod?: PaymentMethodEnum; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) overdue?: boolean; /* @@ -80,11 +78,11 @@ export class Payment extends TenantOrganizationBaseEntity implements IPayment { @ApiProperty({ type: () => String }) @RelationId((it: Payment) => it.employee) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) employeeId?: string; @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { + @MultiORMManyToOne(() => Employee, { onDelete: 'SET NULL' }) @JoinColumn() @@ -96,11 +94,11 @@ export class Payment extends TenantOrganizationBaseEntity implements IPayment { @ApiPropertyOptional({ type: () => String }) @RelationId((it: Payment) => it.invoice) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) invoiceId?: string; @ApiPropertyOptional({ type: () => Invoice }) - @ManyToOne(() => Invoice, (invoice) => invoice.payments, { + @MultiORMManyToOne(() => Invoice, (invoice) => invoice.payments, { onDelete: 'SET NULL' }) @JoinColumn() @@ -110,26 +108,26 @@ export class Payment extends TenantOrganizationBaseEntity implements IPayment { * User */ @ApiPropertyOptional({ type: () => User }) - @ManyToOne(() => User) + @MultiORMManyToOne(() => User) @JoinColumn() recordedBy?: IUser; @ApiProperty({ type: () => String }) @RelationId((it: Payment) => it.recordedBy) @Index() - @Column() - recordedById?: string; + @MultiORMColumn({ relationId: true }) + recordedById?: IUser['id']; /** * Organization Project Relationship */ @ApiPropertyOptional({ type: () => OrganizationProject }) - @ManyToOne(() => OrganizationProject, (it) => it.payments, { + @MultiORMManyToOne(() => OrganizationProject, (it) => it.payments, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, /** Defines the database cascade action on delete. */ - onDelete: 'SET NULL', + onDelete: 'SET NULL' }) @JoinColumn() project?: IOrganizationProject; @@ -142,14 +140,14 @@ export class Payment extends TenantOrganizationBaseEntity implements IPayment { @IsUUID() @RelationId((it: Payment) => it.project) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; /** * OrganizationContact */ @ApiPropertyOptional({ type: () => OrganizationContact }) - @ManyToOne(() => OrganizationContact, (organizationContact) => organizationContact.payments, { + @MultiORMManyToOne(() => OrganizationContact, (organizationContact) => organizationContact.payments, { onDelete: 'SET NULL' }) @JoinColumn() @@ -158,7 +156,7 @@ export class Payment extends TenantOrganizationBaseEntity implements IPayment { @ApiPropertyOptional({ type: () => String }) @RelationId((it: Payment) => it.organizationContact) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationContactId?: string; /* @@ -167,9 +165,11 @@ export class Payment extends TenantOrganizationBaseEntity implements IPayment { |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.payments, { + @MultiORMManyToMany(() => Tag, (tag) => tag.payments, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_payment' }) @JoinTable({ name: 'tag_payment' diff --git a/packages/core/src/payment/payment.service.ts b/packages/core/src/payment/payment.service.ts index ef3521c6dfd..cc94124a4a9 100644 --- a/packages/core/src/payment/payment.service.ts +++ b/packages/core/src/payment/payment.service.ts @@ -36,7 +36,7 @@ export class PaymentService extends TenantAwareCrudService { */ async getPayments(request: IGetPaymentInput) { // Create a query builder for the Payment entity - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); // Set up the find options for the query query.setFindOptions({ @@ -47,9 +47,9 @@ export class PaymentService extends TenantAwareCrudService { } : {}), join: { - alias: `${this.alias}`, + alias: `${this.tableName}`, leftJoin: { - project: `${this.alias}.project` + project: `${this.tableName}.project` } }, select: { @@ -91,7 +91,7 @@ export class PaymentService extends TenantAwareCrudService { */ async getDailyReportCharts(request: IGetPaymentInput) { // Create a query builder for the Payment entity - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); // Set up the find options for the query query.setFindOptions({ diff --git a/packages/core/src/payment/payment.subscriber.ts b/packages/core/src/payment/payment.subscriber.ts index afcc9db25b6..02b6bb3f2b0 100644 --- a/packages/core/src/payment/payment.subscriber.ts +++ b/packages/core/src/payment/payment.subscriber.ts @@ -13,16 +13,17 @@ export class PaymentSubscriber implements EntitySubscriberInterface { } /** - * Called before entity is inserted to the database. + * Called before an entity is inserted into the database. * - * @param event + * @param event - The InsertEvent associated with the entity insertion. */ beforeInsert(event: InsertEvent): void | Promise { try { - const entity = event.entity; - if (entity) { entity.recordedById = RequestContext.currentUserId(); } + if (event.entity) { + event.entity.recordedById = RequestContext.currentUserId(); + } } catch (error) { - console.log(error); + console.error("Error in beforeInsert:", error.message); } } -} \ No newline at end of file +} diff --git a/packages/core/src/pipeline-stage/pipeline-stage.entity.ts b/packages/core/src/pipeline-stage/pipeline-stage.entity.ts index 13b59af0a12..a199acd5266 100644 --- a/packages/core/src/pipeline-stage/pipeline-stage.entity.ts +++ b/packages/core/src/pipeline-stage/pipeline-stage.entity.ts @@ -1,33 +1,35 @@ import { IPipeline, IPipelineStage as IStage } from '@gauzy/contracts'; -import { Column, JoinColumn, ManyToOne, RelationId } from 'typeorm'; -import { IsNotEmpty, IsNumber, IsString, Min } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; +import { JoinColumn, RelationId } from 'typeorm'; +import { IsNotEmpty, IsNumber, IsOptional, IsString, Min } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Pipeline, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmPipelineStageRepository } from './repository/mikro-orm-pipeline-stage.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('pipeline_stage', { mikroOrmRepository: () => MikroOrmPipelineStageRepository }) export class PipelineStage extends TenantOrganizationBaseEntity implements IStage { - @ApiProperty({ type: () => String }) - @Column({ nullable: true, type: 'text' }) + @ApiPropertyOptional({ type: () => String }) + @IsOptional() @IsString() + @MultiORMColumn({ type: 'text', nullable: true }) public description: string; @ApiProperty({ type: () => Number, minimum: 1 }) - @Column({ type: 'int' }) - @Min(1) @IsNotEmpty() + @Min(1) + @MultiORMColumn({ type: 'int' }) @IsNumber() public index: number; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() - @Column() + @MultiORMColumn() public name: string; /* @@ -35,16 +37,21 @@ export class PipelineStage extends TenantOrganizationBaseEntity implements IStag | @ManyToOne |-------------------------------------------------------------------------- */ - @ApiProperty({ type: () => Pipeline }) - @ManyToOne(() => Pipeline, { onDelete: 'CASCADE' }) - @ApiProperty({ type: () => Pipeline }) + + /** + * Pipeline + */ + @MultiORMManyToOne(() => Pipeline, (it) => it.stages, { + /** Database cascade action on delete. */ + onDelete: 'CASCADE' + }) @JoinColumn() public pipeline: IPipeline; @ApiProperty({ type: () => String }) - @RelationId((it: PipelineStage) => it.pipeline) @IsNotEmpty() @IsString() - @Column() - public pipelineId: string; + @RelationId((it: PipelineStage) => it.pipeline) + @MultiORMColumn({ relationId: true }) + public pipelineId: IPipeline['id']; } diff --git a/packages/core/src/pipeline/pipeline.controller.ts b/packages/core/src/pipeline/pipeline.controller.ts index 02faf00373b..96c5f410c6b 100644 --- a/packages/core/src/pipeline/pipeline.controller.ts +++ b/packages/core/src/pipeline/pipeline.controller.ts @@ -1,5 +1,3 @@ -import { CrudController, PaginationParams } from './../core/crud'; -import { Pipeline } from './pipeline.entity'; import { Body, Controller, @@ -16,24 +14,35 @@ import { ValidationPipe } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { PipelineService } from './pipeline.service'; -import { ParseJsonPipe, UUIDValidationPipe } from './../shared/pipes'; import { DeepPartial } from 'typeorm'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import { IDeal, IPagination, IPipeline, PermissionsEnum } from '@gauzy/contracts'; +import { CrudController, PaginationParams } from './../core/crud'; +import { Pipeline } from './pipeline.entity'; +import { PipelineService } from './pipeline.service'; +import { UUIDValidationPipe } from './../shared/pipes'; import { Permissions } from './../shared/decorators'; import { PermissionGuard, TenantPermissionGuard } from './../shared/guards'; @ApiTags('Pipeline') -@UseGuards(TenantPermissionGuard) +@UseGuards(TenantPermissionGuard, PermissionGuard) +@Permissions(PermissionsEnum.EDIT_SALES_PIPELINES) @Controller() export class PipelineController extends CrudController { - public constructor(protected pipelineService: PipelineService) { + + constructor( + protected readonly pipelineService: PipelineService + ) { super(pipelineService); } + /** + * Paginate sales pipelines with permissions, validation, and filtering options. + * + * @param filter - The filtering options for pagination. + * @returns The paginated result of sales pipelines. + */ @Permissions(PermissionsEnum.VIEW_SALES_PIPELINES) - @UseGuards(PermissionGuard) @Get('pagination') @UsePipes(new ValidationPipe({ transform: true })) async pagination( @@ -42,38 +51,50 @@ export class PipelineController extends CrudController { return await this.pipelineService.pagination(filter); } + /** + * Find all sales pipelines with permissions, API documentation, and query parameter parsing. + * + * @param data - The query parameter data. + * @returns A paginated result of sales pipelines. + */ @ApiOperation({ summary: 'find all' }) @ApiResponse({ status: HttpStatus.OK, - description: 'Found records' + description: 'Found records', }) @Permissions(PermissionsEnum.VIEW_SALES_PIPELINES) - @UseGuards(PermissionGuard) @Get() public async findAll( - @Query('data', ParseJsonPipe) data: any + @Query() filter: PaginationParams ): Promise> { - const { relations = [], findInput: where = null } = data; - return this.pipelineService.findAll({ - relations, - where - }); + return await this.pipelineService.findAll(filter); } + /** + * Find deals for a specific sales pipeline with permissions, API documentation, and parameter validation. + * + * @param id - The identifier of the sales pipeline. + * @returns A paginated result of deals for the specified sales pipeline. + */ @ApiOperation({ summary: 'find deals' }) @ApiResponse({ status: HttpStatus.OK, - description: 'Found records' + description: 'Found records', }) @Permissions(PermissionsEnum.VIEW_SALES_PIPELINES) - @UseGuards(PermissionGuard) @Get(':id/deals') public async findDeals( @Param('id', UUIDValidationPipe) id: string ): Promise> { - return this.pipelineService.findDeals(id); + return await this.pipelineService.findDeals(id); } + /** + * Create a new record with permissions, API documentation, and HTTP status codes. + * + * @param entity - The data to create a new record. + * @returns The created record. + */ @ApiOperation({ summary: 'Create new record' }) @ApiResponse({ status: HttpStatus.CREATED, @@ -81,20 +102,25 @@ export class PipelineController extends CrudController { }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: - 'Invalid input, The response body may contain clues as to what went wrong' + description: 'Invalid input, The response body may contain clues as to what went wrong' }) @HttpCode(HttpStatus.CREATED) @Permissions(PermissionsEnum.EDIT_SALES_PIPELINES) - @UseGuards(PermissionGuard) @Post() async create( - @Body() entity: DeepPartial, - ...options: any[] + @Body() entity: DeepPartial ): Promise { - return super.create(entity, ...options); + return await this.pipelineService.create(entity); } + /** + * Update an existing record with permissions, API documentation, and HTTP status codes. + * + * @param id - The identifier of the record to update. + * @param entity - The data to update the existing record. + * @param options - Additional options if needed. + * @returns The updated record. + */ @ApiOperation({ summary: 'Update an existing record' }) @ApiResponse({ status: HttpStatus.CREATED, @@ -106,21 +132,25 @@ export class PipelineController extends CrudController { }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: - 'Invalid input, The response body may contain clues as to what went wrong' + description: 'Invalid input, The response body may contain clues as to what went wrong' }) @HttpCode(HttpStatus.ACCEPTED) @Permissions(PermissionsEnum.EDIT_SALES_PIPELINES) - @UseGuards(PermissionGuard) @Put(':id') async update( @Param('id', UUIDValidationPipe) id: string, - @Body() entity: QueryDeepPartialEntity, - ...options: any[] + @Body() entity: QueryDeepPartialEntity ): Promise { - return super.update(id, entity, ...options); + return await this.pipelineService.update(id, entity); } + /** + * Delete a record with permissions, API documentation, and HTTP status codes. + * + * @param id - The identifier of the record to delete. + * @param options - Additional options if needed. + * @returns The result of the deletion operation. + */ @ApiOperation({ summary: 'Delete record' }) @ApiResponse({ status: HttpStatus.NO_CONTENT, @@ -132,12 +162,10 @@ export class PipelineController extends CrudController { }) @HttpCode(HttpStatus.ACCEPTED) @Permissions(PermissionsEnum.EDIT_SALES_PIPELINES) - @UseGuards(PermissionGuard) @Delete(':id') async delete( @Param('id', UUIDValidationPipe) id: string, - ...options: any[] ): Promise { - return super.delete(id); + return await this.pipelineService.delete(id); } } diff --git a/packages/core/src/pipeline/pipeline.entity.ts b/packages/core/src/pipeline/pipeline.entity.ts index 48984b4e55b..c84780bf7df 100644 --- a/packages/core/src/pipeline/pipeline.entity.ts +++ b/packages/core/src/pipeline/pipeline.entity.ts @@ -1,43 +1,46 @@ import { IPipeline, IPipelineStage } from '@gauzy/contracts'; -import { - AfterInsert, - AfterLoad, - AfterUpdate, - BeforeInsert, - Column, - OneToMany -} from 'typeorm'; -import { IsNotEmpty, IsString } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { PipelineStage, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmPipelineRepository } from './repository/mikro-orm-pipeline.repository'; +import { MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('pipeline', { mikroOrmRepository: () => MikroOrmPipelineRepository }) export class Pipeline extends TenantOrganizationBaseEntity implements IPipeline { - @ApiProperty({ type: () => PipelineStage }) - @OneToMany(() => PipelineStage, ({ pipeline }) => pipeline, { - cascade: ['insert'] - }) - public stages: IPipelineStage[]; - - @Column({ nullable: true, type: 'text' }) - @ApiProperty({ type: () => String }) + @ApiPropertyOptional({ type: () => String }) + @IsOptional() @IsString() + @MultiORMColumn({ type: 'text', nullable: true }) public description: string; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() - @Column() + @MultiORMColumn() public name: string; + /* + |-------------------------------------------------------------------------- + | @OneToMany + |-------------------------------------------------------------------------- + */ + @ApiProperty({ type: () => PipelineStage }) + @MultiORMOneToMany(() => PipelineStage, (it) => it.pipeline, { + cascade: ['insert'] + }) + public stages: IPipelineStage[]; + + /* + |-------------------------------------------------------------------------- + | EventSubscriber + |-------------------------------------------------------------------------- + */ - @BeforeInsert() public __before_persist?(): void { const pipelineId = this.id ? { pipelineId: this.id } : {}; let index = 0; @@ -45,14 +48,6 @@ export class Pipeline extends TenantOrganizationBaseEntity implements IPipeline this.stages?.forEach((stage) => { Object.assign(stage, pipelineId, { index: ++index }); }); - } - - @AfterLoad() - @AfterInsert() - @AfterUpdate() - public __after_fetch?(): void { - if (this.stages) { - this.stages.sort(({ index: a }, { index: b }) => a - b); - } + console.log(this.stages); } } diff --git a/packages/core/src/pipeline/pipeline.service.ts b/packages/core/src/pipeline/pipeline.service.ts index aa5ae3fd861..94444d9fa98 100644 --- a/packages/core/src/pipeline/pipeline.service.ts +++ b/packages/core/src/pipeline/pipeline.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectConnection, InjectRepository } from '@nestjs/typeorm'; import { Connection, DeepPartial, FindManyOptions, FindOptionsWhere, Raw, UpdateResult } from 'typeorm'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import { IPipelineStage } from '@gauzy/contracts'; +import { IPagination, IPipeline, IPipelineStage } from '@gauzy/contracts'; import { isPostgres } from '@gauzy/config'; import { Pipeline } from './pipeline.entity'; import { Deal, PipelineStage, User } from './../core/entities/internal'; @@ -59,6 +59,12 @@ export class PipelineService extends TenantAwareCrudService { return { items, total }; } + /** + * + * @param id + * @param entity + * @returns + */ public async update( id: string | number | FindOptionsWhere, entity: QueryDeepPartialEntity @@ -79,17 +85,13 @@ export class PipelineService extends TenantAwareCrudService { const pipeline: Pipeline = await queryRunner.manager.create(Pipeline, { id: id as any, ...entity } as any); const updatedStages: IPipelineStage[] = pipeline.stages?.filter((stage: IPipelineStage) => stage.id) || []; - const deletedStages = await queryRunner.manager - .findBy(PipelineStage, { - pipelineId: id as any - }) - .then((stages: IPipelineStage[]) => { - const requestStageIds = updatedStages.map((updatedStage: IPipelineStage) => updatedStage.id); - return stages.filter((stage: IPipelineStage) => !requestStageIds.includes(stage.id)); - }); + const stages: IPipelineStage[] = await queryRunner.manager.findBy(PipelineStage, { + pipelineId: id as any + }); - const createdStages = - pipeline.stages?.filter((stage: IPipelineStage) => !updatedStages.includes(stage)) || []; + const requestStageIds = updatedStages.map((updatedStage: IPipelineStage) => updatedStage.id); + const deletedStages = stages.filter((stage: IPipelineStage) => !requestStageIds.includes(stage.id)); + const createdStages = pipeline.stages?.filter((stage: IPipelineStage) => !updatedStages.includes(stage)) || []; pipeline.__before_persist(); delete pipeline.stages; @@ -117,11 +119,12 @@ export class PipelineService extends TenantAwareCrudService { } /** + * Perform pagination with filtering based on the provided options. * - * @param filter - * @returns + * @param filter - The filtering options. + * @returns The paginated result. */ - public pagination(filter: FindManyOptions) { + public async pagination(filter: FindManyOptions): Promise> { if ('where' in filter) { const { where } = filter; const likeOperator = isPostgres() ? 'ILIKE' : 'LIKE'; @@ -133,15 +136,6 @@ export class PipelineService extends TenantAwareCrudService { const { description } = where; filter['where']['description'] = Raw((alias) => `${alias} ${likeOperator} '%${description}%'`); } - if ('isActive' in where) { - const { isActive } = where; - if (isActive === 'active') { - filter['where']['isActive'] = 1; - } - if (isActive === 'inactive') { - filter['where']['isActive'] = 0; - } - } if ('stages' in where) { const { stages } = where; filter['where']['stages'] = { @@ -149,6 +143,6 @@ export class PipelineService extends TenantAwareCrudService { }; } } - return super.paginate(filter); + return await super.paginate(filter); } } diff --git a/packages/core/src/pipeline/pipeline.subscriber.ts b/packages/core/src/pipeline/pipeline.subscriber.ts new file mode 100644 index 00000000000..7247330d71e --- /dev/null +++ b/packages/core/src/pipeline/pipeline.subscriber.ts @@ -0,0 +1,61 @@ +import { EntitySubscriberInterface, EventSubscriber, InsertEvent, LoadEvent } from "typeorm"; +import { Pipeline } from "./pipeline.entity"; + +@EventSubscriber() +export class PipelineSubscriber implements EntitySubscriberInterface { + + /** + * Indicates that this subscriber only listen to Pipeline events. + */ + listenTo() { + return Pipeline; + } + + /** + * Called before entity is inserted to the database. + * + * @param event + */ + beforeInsert(event: InsertEvent): void | Promise { + try { + const pipelineId = event.entity?.id ? { pipelineId: event.entity?.id } : {}; + let index = 0; + + event.entity?.stages?.forEach((stage) => { + Object.assign(stage, pipelineId, { index: ++index }); + }); + } catch (error) { + console.error("Error in beforeInsert:", error.message); + } + } + + /** + * Called after loading the Pipeline entity from the database. + * + * @param entity - The loaded Pipeline entity. + * @param event - The LoadEvent containing information about the load operation. + */ + afterLoad(entity: Pipeline, event?: LoadEvent): void | Promise { + this.__after_fetch(entity); + } + + /** + * Called after inserting the Pipeline entity into the database. + * + * @param event - The InsertEvent containing information about the insertion. + */ + afterInsert(event: InsertEvent): void | Promise { + this.__after_fetch(event.entity); + } + + /*** + * Internal method to be used after fetching the Pipeline entity. + * + * @param entity - The fetched Pipeline entity. + */ + private __after_fetch(entity: Pipeline): void { + if (entity.stages) { + entity.stages.sort(({ index: a }, { index: b }) => a - b); + } + } +} diff --git a/packages/core/src/product-category/product-category-translation.entity.ts b/packages/core/src/product-category/product-category-translation.entity.ts index 8113bf5aad3..563982cb8ad 100644 --- a/packages/core/src/product-category/product-category-translation.entity.ts +++ b/packages/core/src/product-category/product-category-translation.entity.ts @@ -1,24 +1,25 @@ -import { Column, ManyToOne, JoinColumn, RelationId, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { IProductTypeTranslation as IProductCategoryTranslation } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { ProductCategory, TranslationBase } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductCategoryTranslationRepository } from './repository/mikro-orm-product-category-translation.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('product_category_translation', { mikroOrmRepository: () => MikroOrmProductCategoryTranslationRepository }) export class ProductCategoryTranslation extends TranslationBase implements IProductCategoryTranslation { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiProperty({ type: () => String }) - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false }) languageCode: string; /* @@ -31,7 +32,7 @@ export class ProductCategoryTranslation extends TranslationBase * ProductCategory */ @ApiProperty({ type: () => ProductCategory }) - @ManyToOne(() => ProductCategory, (productCategory) => productCategory.translations, { + @MultiORMManyToOne(() => ProductCategory, (productCategory) => productCategory.translations, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) @@ -41,6 +42,6 @@ export class ProductCategoryTranslation extends TranslationBase @ApiProperty({ type: () => String }) @RelationId((it: ProductCategoryTranslation) => it.reference) @Index() - @Column() + @MultiORMColumn({ relationId: true }) referenceId: string; } diff --git a/packages/core/src/product-category/product-category.entity.ts b/packages/core/src/product-category/product-category.entity.ts index 6cd88020934..006781babf4 100644 --- a/packages/core/src/product-category/product-category.entity.ts +++ b/packages/core/src/product-category/product-category.entity.ts @@ -1,4 +1,4 @@ -import { Column, OneToMany, ManyToOne, JoinColumn, RelationId, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsOptional, IsString, IsUUID } from 'class-validator'; import { IImageAsset, IProductCategoryTranslatable } from '@gauzy/contracts'; @@ -8,8 +8,9 @@ import { ProductCategoryTranslation, TranslatableBase } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductCategoryRepository } from './repository/mikro-orm-product-category.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('product_category', { mikroOrmRepository: () => MikroOrmProductCategoryRepository }) export class ProductCategory extends TranslatableBase @@ -18,7 +19,7 @@ export class ProductCategory extends TranslatableBase @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) imageUrl: string; /* @@ -30,7 +31,7 @@ export class ProductCategory extends TranslatableBase /** * ImageAsset */ - @ManyToOne(() => ImageAsset, { + @MultiORMManyToOne(() => ImageAsset, { /** Database cascade action on delete. */ onDelete: 'SET NULL', @@ -45,7 +46,7 @@ export class ProductCategory extends TranslatableBase @IsUUID() @RelationId((it: ProductCategory) => it.image) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) imageId?: IImageAsset['id']; /* @@ -58,14 +59,14 @@ export class ProductCategory extends TranslatableBase * Product */ @ApiProperty({ type: () => Product, isArray: true }) - @OneToMany(() => Product, (product) => product.productCategory) + @MultiORMOneToMany(() => Product, (product) => product.productCategory) products: Product[]; /** * ProductCategoryTranslation */ @ApiProperty({ type: () => ProductCategoryTranslation, isArray: true }) - @OneToMany(() => ProductCategoryTranslation, (instance) => instance.reference, { + @MultiORMOneToMany(() => ProductCategoryTranslation, (instance) => instance.reference, { /** Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods. */ eager: true, diff --git a/packages/core/src/product-option/product-option-group-translation.entity.ts b/packages/core/src/product-option/product-option-group-translation.entity.ts index 5df3eca22aa..7105374cd49 100644 --- a/packages/core/src/product-option/product-option-group-translation.entity.ts +++ b/packages/core/src/product-option/product-option-group-translation.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, JoinColumn, RelationId, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { IProductOptionGroupTranslation, LanguagesEnum @@ -7,19 +7,20 @@ import { IsString, IsEnum } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { TenantOrganizationBaseEntity } from '../core/entities/internal'; import { ProductOptionGroup } from './product-option-group.entity'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductOptionGroupTranslationRepository } from './repository/mikro-orm-product-option-group-translation.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('product_option_group_translation', { mikroOrmRepository: () => MikroOrmProductOptionGroupTranslationRepository }) export class ProductOptionGroupTranslation extends TenantOrganizationBaseEntity implements IProductOptionGroupTranslation { @ApiProperty({ type: () => String }) @IsString() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String, enum: LanguagesEnum }) @IsEnum(LanguagesEnum) - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false }) languageCode: string; /* @@ -32,7 +33,7 @@ export class ProductOptionGroupTranslation extends TenantOrganizationBaseEntity * ProductOptionGroup */ @ApiProperty({ type: () => ProductOptionGroup }) - @ManyToOne(() => ProductOptionGroup, (group) => group.translations) + @MultiORMManyToOne(() => ProductOptionGroup, (group) => group.translations) @JoinColumn() reference: ProductOptionGroup; @@ -40,6 +41,6 @@ export class ProductOptionGroupTranslation extends TenantOrganizationBaseEntity @RelationId((it: ProductOptionGroupTranslation) => it.reference) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) referenceId: string; } diff --git a/packages/core/src/product-option/product-option-group.entity.ts b/packages/core/src/product-option/product-option-group.entity.ts index e4dd411db6f..8bc868427c2 100644 --- a/packages/core/src/product-option/product-option-group.entity.ts +++ b/packages/core/src/product-option/product-option-group.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, JoinColumn, OneToMany, RelationId, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { Product, TenantOrganizationBaseEntity @@ -10,14 +10,15 @@ import { ProductOption, ProductOptionGroupTranslation } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductOptionGroupRepository } from './repository/mikro-orm-product-option-group.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('product_option_group', { mikroOrmRepository: () => MikroOrmProductOptionGroupRepository }) export class ProductOptionGroup extends TenantOrganizationBaseEntity implements IProductOptionGroupTranslatable { @ApiProperty({ type: () => String }) @IsString() - @Column() + @MultiORMColumn() name: string; /* @@ -30,7 +31,7 @@ export class ProductOptionGroup extends TenantOrganizationBaseEntity implements * Product */ @ApiProperty({ type: () => Product }) - @ManyToOne(() => Product, (product) => product.optionGroups) + @MultiORMManyToOne(() => Product, (product) => product.optionGroups) @JoinColumn() product: Product; @@ -39,7 +40,7 @@ export class ProductOptionGroup extends TenantOrganizationBaseEntity implements @IsString() @IsOptional() @Index() - @Column() + @MultiORMColumn({ relationId: true }) productId: string; /* @@ -52,7 +53,7 @@ export class ProductOptionGroup extends TenantOrganizationBaseEntity implements * ProductOption */ @ApiProperty({ type: () => ProductOption, isArray: true }) - @OneToMany(() => ProductOption, (productOption) => productOption.group, { + @MultiORMOneToMany(() => ProductOption, (productOption) => productOption.group, { /** Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods. */ eager: true, }) @@ -62,7 +63,7 @@ export class ProductOptionGroup extends TenantOrganizationBaseEntity implements * ProductOptionGroupTranslation */ @ApiProperty({ type: () => ProductOptionGroupTranslation, isArray: true }) - @OneToMany(() => ProductOptionGroupTranslation, (translation) => translation.reference, { + @MultiORMOneToMany(() => ProductOptionGroupTranslation, (translation) => translation.reference, { /** Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods. */ eager: true, }) diff --git a/packages/core/src/product-option/product-option-translation.entity.ts b/packages/core/src/product-option/product-option-translation.entity.ts index 286510ac643..8b2ae06cda6 100644 --- a/packages/core/src/product-option/product-option-translation.entity.ts +++ b/packages/core/src/product-option/product-option-translation.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, JoinColumn, RelationId, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { IProductOptionTranslation, LanguagesEnum } from '@gauzy/contracts'; import { TenantOrganizationBaseEntity, @@ -6,24 +6,25 @@ import { } from '../core/entities/internal'; import { IsString, IsEnum } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductOptionTranslationRepository } from './repository/mikro-orm-product-option-translation.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('product_option_translation', { mikroOrmRepository: () => MikroOrmProductOptionTranslationRepository }) export class ProductOptionTranslation extends TenantOrganizationBaseEntity implements IProductOptionTranslation { @ApiProperty({ type: () => String }) @IsString() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiProperty({ type: () => String, enum: LanguagesEnum }) @IsEnum(LanguagesEnum) - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false }) languageCode: string; /* @@ -36,7 +37,7 @@ export class ProductOptionTranslation extends TenantOrganizationBaseEntity imple * ProductOption */ @ApiProperty({ type: () => ProductOption }) - @ManyToOne(() => ProductOption, (option) => option.translations) + @MultiORMManyToOne(() => ProductOption, (option) => option.translations) @JoinColumn() reference: ProductOption; @@ -44,6 +45,6 @@ export class ProductOptionTranslation extends TenantOrganizationBaseEntity imple @RelationId((it: ProductOptionTranslation) => it.reference) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) referenceId: string; } diff --git a/packages/core/src/product-option/product-option.entity.ts b/packages/core/src/product-option/product-option.entity.ts index 243c516afc3..347e8724829 100644 --- a/packages/core/src/product-option/product-option.entity.ts +++ b/packages/core/src/product-option/product-option.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToOne, JoinColumn, OneToMany, RelationId, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { IProductOptionTranslatable, IProductOptionTranslation } from '@gauzy/contracts'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsString } from 'class-validator'; @@ -7,20 +7,21 @@ import { ProductOptionTranslation } from '../core/entities/internal'; import { ProductOptionGroup } from './product-option-group.entity'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductOptionRepository } from './repository/mikro-orm-product-option.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('product_option', { mikroOrmRepository: () => MikroOrmProductOptionRepository }) export class ProductOption extends TenantOrganizationBaseEntity implements IProductOptionTranslatable { @ApiProperty({ type: () => String }) @IsString() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) code: string; /* @@ -33,7 +34,7 @@ export class ProductOption extends TenantOrganizationBaseEntity implements IProd * ProductOptionGroup */ @ApiPropertyOptional({ type: () => ProductOptionGroup }) - @ManyToOne(() => ProductOptionGroup, (group) => group.options) + @MultiORMManyToOne(() => ProductOptionGroup, (group) => group.options) @JoinColumn() group?: ProductOptionGroup; @@ -41,7 +42,7 @@ export class ProductOption extends TenantOrganizationBaseEntity implements IProd @RelationId((it: ProductOption) => it.group) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) groupId?: string; /* @@ -51,7 +52,7 @@ export class ProductOption extends TenantOrganizationBaseEntity implements IProd */ @ApiProperty({ type: () => ProductOptionTranslation, isArray: true }) - @OneToMany(() => ProductOptionTranslation, (translation) => translation.reference, { + @MultiORMOneToMany(() => ProductOptionTranslation, (translation) => translation.reference, { /** Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods. */ eager: true, }) diff --git a/packages/core/src/product-setting/product-setting.entity.ts b/packages/core/src/product-setting/product-setting.entity.ts index e4547b6e84d..ffb74ffd61a 100644 --- a/packages/core/src/product-setting/product-setting.entity.ts +++ b/packages/core/src/product-setting/product-setting.entity.ts @@ -1,46 +1,47 @@ import { ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, OneToOne, JoinColumn } from 'typeorm'; +import { JoinColumn } from 'typeorm'; import { IProductVariantSetting } from '@gauzy/contracts'; import { ProductVariant, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductVariantSettingRepository } from './repository/mikro-orm-product-setting.repository'; +import { MultiORMOneToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('product_variant_setting', { mikroOrmRepository: () => MikroOrmProductVariantSettingRepository }) export class ProductVariantSetting extends TenantOrganizationBaseEntity implements IProductVariantSetting { @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: false }) + @MultiORMColumn({ default: false }) isSubscription: boolean; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: false }) + @MultiORMColumn({ default: false }) isPurchaseAutomatically: boolean; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: true }) + @MultiORMColumn({ default: true }) canBeSold: boolean; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: true }) + @MultiORMColumn({ default: true }) canBePurchased: boolean; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: false }) + @MultiORMColumn({ default: false }) canBeCharged: boolean; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: false }) + @MultiORMColumn({ default: false }) canBeRented: boolean; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: false }) + @MultiORMColumn({ default: false }) isEquipment: boolean; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: false }) + @MultiORMColumn({ default: false }) trackInventory: boolean; /* @@ -52,8 +53,9 @@ export class ProductVariantSetting extends TenantOrganizationBaseEntity implemen /** * ProductVariant */ - @OneToOne(() => ProductVariant, (productVariant) => productVariant.setting, { - onDelete: 'CASCADE' + @MultiORMOneToOne(() => ProductVariant, (productVariant) => productVariant.setting, { + onDelete: 'CASCADE', + owner: true, }) @JoinColumn() productVariant: ProductVariant; diff --git a/packages/core/src/product-type/product-type-translation.entity.ts b/packages/core/src/product-type/product-type-translation.entity.ts index 624f1e3131c..4b764ad30ff 100644 --- a/packages/core/src/product-type/product-type-translation.entity.ts +++ b/packages/core/src/product-type/product-type-translation.entity.ts @@ -1,24 +1,25 @@ -import { Column, ManyToOne, JoinColumn, RelationId, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { IProductTypeTranslation } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { ProductType, TranslationBase } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductTypeTranslationRepository } from './repository/mikro-orm-product-type-translation.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('product_type_translation', { mikroOrmRepository: () => MikroOrmProductTypeTranslationRepository }) export class ProductTypeTranslation extends TranslationBase implements IProductTypeTranslation { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiProperty({ type: () => String }) - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false }) languageCode: string; /* @@ -31,7 +32,7 @@ export class ProductTypeTranslation extends TranslationBase * ProductType */ @ApiProperty({ type: () => ProductType }) - @ManyToOne(() => ProductType, (productType) => productType.translations, { + @MultiORMManyToOne(() => ProductType, (productType) => productType.translations, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) @@ -41,6 +42,6 @@ export class ProductTypeTranslation extends TranslationBase @ApiProperty({ type: () => String }) @RelationId((it: ProductTypeTranslation) => it.reference) @Index() - @Column() + @MultiORMColumn({ relationId: true }) referenceId: string; } diff --git a/packages/core/src/product-type/product-type.entity.ts b/packages/core/src/product-type/product-type.entity.ts index 5415b9fa5bd..855edc2ff80 100644 --- a/packages/core/src/product-type/product-type.entity.ts +++ b/packages/core/src/product-type/product-type.entity.ts @@ -1,4 +1,3 @@ -import { Column, OneToMany } from 'typeorm'; import { ProductTypesIconsEnum } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { @@ -6,14 +5,15 @@ import { ProductTypeTranslation, TranslatableBase } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductTypeRepository } from './repository/mikro-orm-product-type.repository'; +import { MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('product_type', { mikroOrmRepository: () => MikroOrmProductTypeRepository }) export class ProductType extends TranslatableBase { @ApiProperty({ type: () => String, enum: ProductTypesIconsEnum }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) icon: string; /* @@ -26,14 +26,14 @@ export class ProductType extends TranslatableBase { * Product */ @ApiProperty({ type: () => Product, isArray: true }) - @OneToMany(() => Product, (product) => product.productType) + @MultiORMOneToMany(() => Product, (product) => product.productType) products: Product[]; /** * ProductTypeTranslation */ @ApiProperty({ type: () => ProductTypeTranslation, isArray: true }) - @OneToMany(() => ProductTypeTranslation, (productTypeTranslation) => productTypeTranslation.reference, { + @MultiORMOneToMany(() => ProductTypeTranslation, (productTypeTranslation) => productTypeTranslation.reference, { /** Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods. */ eager: true, diff --git a/packages/core/src/product-variant-price/product-variant-price.entity.ts b/packages/core/src/product-variant-price/product-variant-price.entity.ts index 05d8070f68a..9bfe5d76b1f 100644 --- a/packages/core/src/product-variant-price/product-variant-price.entity.ts +++ b/packages/core/src/product-variant-price/product-variant-price.entity.ts @@ -1,4 +1,4 @@ -import { Column, OneToOne, JoinColumn } from 'typeorm'; +import { JoinColumn } from 'typeorm'; import { IProductVariantPrice, CurrenciesEnum } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsNumber, IsEnum } from 'class-validator'; @@ -6,30 +6,31 @@ import { ProductVariant, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductVariantPriceRepository } from './repository/mikro-orm-product-variant-price.repository'; +import { MultiORMOneToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('product_variant_price', { mikroOrmRepository: () => MikroOrmProductVariantPriceRepository }) export class ProductVariantPrice extends TenantOrganizationBaseEntity implements IProductVariantPrice { @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) unitCost: number; @ApiProperty({ type: () => String, enum: CurrenciesEnum }) @IsEnum(CurrenciesEnum) - @Column({ default: CurrenciesEnum.USD }) + @MultiORMColumn({ default: CurrenciesEnum.USD }) unitCostCurrency: string; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) retailPrice: number; @ApiProperty({ type: () => String, enum: CurrenciesEnum }) @IsEnum(CurrenciesEnum) - @Column({ default: CurrenciesEnum.USD }) + @MultiORMColumn({ default: CurrenciesEnum.USD }) retailPriceCurrency: string; /* @@ -41,8 +42,9 @@ export class ProductVariantPrice extends TenantOrganizationBaseEntity implements /** * ProductVariant */ - @OneToOne(() => ProductVariant, (productVariant) => productVariant.price, { - onDelete: 'CASCADE' + @MultiORMOneToOne(() => ProductVariant, (productVariant) => productVariant.price, { + onDelete: 'CASCADE', + owner: true }) @JoinColumn() productVariant: ProductVariant; diff --git a/packages/core/src/product-variant/product-variant.entity.ts b/packages/core/src/product-variant/product-variant.entity.ts index 7e83df2e71d..8cae87b2c6b 100644 --- a/packages/core/src/product-variant/product-variant.entity.ts +++ b/packages/core/src/product-variant/product-variant.entity.ts @@ -1,13 +1,8 @@ import { - Column, - ManyToOne, - OneToOne, RelationId, JoinColumn, - ManyToMany, JoinTable, - Index, - OneToMany + Index } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { @@ -29,39 +24,40 @@ import { TenantOrganizationBaseEntity, WarehouseProductVariant } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductVariantRepository } from './repository/mikro-orm-product-variant.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany, MultiORMOneToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('product_variant', { mikroOrmRepository: () => MikroOrmProductVariantRepository }) export class ProductVariant extends TenantOrganizationBaseEntity implements IProductVariant { @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) taxes: number; @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) notes: string; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) quantity: number; @ApiProperty({ type: () => String }) @IsEnum(BillingInvoicingPolicyEnum) - @Column({ default: BillingInvoicingPolicyEnum.QUANTITY_ORDERED }) + @MultiORMColumn({ default: BillingInvoicingPolicyEnum.QUANTITY_ORDERED }) billingInvoicingPolicy: string; @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) internalReference: string; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: true }) + @MultiORMColumn({ default: true }) enabled: boolean; /* @@ -73,27 +69,25 @@ export class ProductVariant extends TenantOrganizationBaseEntity implements IPro /** * ProductVariantPrice */ - @OneToOne(() => ProductVariantPrice, (variantPrice) => variantPrice.productVariant, { + @MultiORMOneToOne(() => ProductVariantPrice, (variantPrice) => variantPrice.productVariant, { /** Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods. */ eager: true, /** Database cascade action on delete. */ - onDelete: 'CASCADE' + onDelete: 'CASCADE', }) - @JoinColumn() price: IProductVariantPrice; /** * ProductVariantSetting */ - @OneToOne(() => ProductVariantSetting, (settings) => settings.productVariant, { + @MultiORMOneToOne(() => ProductVariantSetting, (settings) => settings.productVariant, { /** Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods. */ eager: true, /** Database cascade action on delete. */ - onDelete: 'CASCADE' + onDelete: 'CASCADE', }) - @JoinColumn() setting: IProductVariantSetting; /* @@ -106,7 +100,7 @@ export class ProductVariant extends TenantOrganizationBaseEntity implements IPro * Product */ @ApiProperty({ type: () => Product }) - @ManyToOne(() => Product, (product) => product.variants, { + @MultiORMManyToOne(() => Product, (product) => product.variants, { onDelete: 'CASCADE' }) @JoinColumn() @@ -116,14 +110,14 @@ export class ProductVariant extends TenantOrganizationBaseEntity implements IPro @RelationId((it: ProductVariant) => it.product) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) productId?: string; /** * ImageAsset */ @ApiProperty({ type: () => ImageAsset }) - @ManyToOne(() => ImageAsset, { + @MultiORMManyToOne(() => ImageAsset, { /** Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods. */ eager: true, }) @@ -134,7 +128,7 @@ export class ProductVariant extends TenantOrganizationBaseEntity implements IPro @RelationId((it: ProductVariant) => it.image) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) imageId?: string; /* @@ -147,7 +141,7 @@ export class ProductVariant extends TenantOrganizationBaseEntity implements IPro * ProductOption */ @ApiProperty({ type: () => WarehouseProductVariant, isArray: true }) - @OneToMany(() => WarehouseProductVariant, (warehouseProductVariant) => warehouseProductVariant.variant, { + @MultiORMOneToMany(() => WarehouseProductVariant, (warehouseProductVariant) => warehouseProductVariant.variant, { cascade: true }) warehouseProductVariants?: IWarehouseProductVariant[]; @@ -162,7 +156,7 @@ export class ProductVariant extends TenantOrganizationBaseEntity implements IPro * ProductOption */ @ApiProperty({ type: () => ProductOption }) - @ManyToMany(() => ProductOption, { eager: true }) + @MultiORMManyToMany(() => ProductOption, { eager: true }) @JoinTable() options: IProductOptionTranslatable[]; } diff --git a/packages/core/src/product/product-translation.entity.ts b/packages/core/src/product/product-translation.entity.ts index b24d0af35a4..c05647344f5 100644 --- a/packages/core/src/product/product-translation.entity.ts +++ b/packages/core/src/product/product-translation.entity.ts @@ -1,27 +1,28 @@ import { IProduct, IProductTranslation, LanguagesEnum } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsOptional, IsString } from 'class-validator'; -import { Column, Index, JoinColumn, ManyToOne, RelationId } from 'typeorm'; +import { Index, JoinColumn, RelationId } from 'typeorm'; import { TranslationBase } from '../core/entities/internal'; import { Product } from './product.entity'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductTranslationRepository } from './repository/mikro-orm-product-translation.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('product_translation', { mikroOrmRepository: () => MikroOrmProductTranslationRepository }) export class ProductTranslation extends TranslationBase implements IProductTranslation { @ApiProperty({ type: () => String }) @IsString() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiProperty({ type: () => String, enum: LanguagesEnum }) @IsEnum(LanguagesEnum) - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false }) languageCode: string; /* @@ -34,7 +35,7 @@ export class ProductTranslation extends TranslationBase implements IProductTrans * Product */ @ApiProperty({ type: () => Product }) - @ManyToOne(() => Product, (product) => product.translations, { + @MultiORMManyToOne(() => Product, (product) => product.translations, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) @@ -45,6 +46,6 @@ export class ProductTranslation extends TranslationBase implements IProductTrans @RelationId((it: ProductTranslation) => it.reference) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) referenceId: string; } diff --git a/packages/core/src/product/product.entity.ts b/packages/core/src/product/product.entity.ts index 0cfdea8ccde..a0d2155539c 100644 --- a/packages/core/src/product/product.entity.ts +++ b/packages/core/src/product/product.entity.ts @@ -1,10 +1,6 @@ import { - Column, - OneToMany, RelationId, JoinColumn, - ManyToOne, - ManyToMany, JoinTable, Index } from 'typeorm'; @@ -29,24 +25,25 @@ import { ProductOptionGroup, WarehouseProduct } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProductRepository } from './repository/mikro-orm-product.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('product', { mikroOrmRepository: () => MikroOrmProductRepository }) export class Product extends TranslatableBase implements IProductTranslatable { @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: true }) + @MultiORMColumn({ default: true }) enabled: boolean; @ApiProperty({ type: () => String }) @IsString() - @Column() + @MultiORMColumn() code: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) imageUrl: string; /* @@ -59,7 +56,7 @@ export class Product extends TranslatableBase implements IProductTranslatable { * ImageAsset */ @ApiProperty({ type: () => ImageAsset }) - @ManyToOne(() => ImageAsset, (imageAsset) => imageAsset.productFeaturedImage, { + @MultiORMManyToOne(() => ImageAsset, (imageAsset) => imageAsset.productFeaturedImage, { onDelete: 'SET NULL' }) @JoinColumn() @@ -69,14 +66,14 @@ export class Product extends TranslatableBase implements IProductTranslatable { @RelationId((it: Product) => it.featuredImage) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) featuredImageId?: string; /** * ProductType */ @ApiProperty({ type: () => ProductType }) - @ManyToOne(() => ProductType, (productType) => productType.products, { + @MultiORMManyToOne(() => ProductType, (productType) => productType.products, { onDelete: 'SET NULL' }) @JoinColumn() @@ -86,7 +83,7 @@ export class Product extends TranslatableBase implements IProductTranslatable { @RelationId((it: Product) => it.productType) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) productTypeId?: string; /** @@ -94,7 +91,7 @@ export class Product extends TranslatableBase implements IProductTranslatable { */ @ApiProperty({ type: () => ProductCategory }) - @ManyToOne(() => ProductCategory, (productCategory) => productCategory.products, { + @MultiORMManyToOne(() => ProductCategory, (productCategory) => productCategory.products, { onDelete: 'SET NULL' }) @JoinColumn() @@ -104,7 +101,7 @@ export class Product extends TranslatableBase implements IProductTranslatable { @RelationId((it: Product) => it.productCategory) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) productCategoryId?: string; /* |-------------------------------------------------------------------------- @@ -116,7 +113,7 @@ export class Product extends TranslatableBase implements IProductTranslatable { * ProductTranslation */ @ApiProperty({ type: () => ProductTranslation, isArray: true }) - @OneToMany(() => ProductTranslation, (productTranslation) => productTranslation.reference, { + @MultiORMOneToMany(() => ProductTranslation, (productTranslation) => productTranslation.reference, { /** Eager relations are always loaded automatically when relation's owner entity is loaded using find* methods. */ eager: true, @@ -129,7 +126,7 @@ export class Product extends TranslatableBase implements IProductTranslatable { * ProductVariant */ @ApiPropertyOptional({ type: () => ProductVariant, isArray: true }) - @OneToMany(() => ProductVariant, (productVariant) => productVariant.product, { + @MultiORMOneToMany(() => ProductVariant, (productVariant) => productVariant.product, { cascade: true }) variants?: ProductVariant[]; @@ -138,7 +135,7 @@ export class Product extends TranslatableBase implements IProductTranslatable { * ProductOptionGroup */ @ApiPropertyOptional({ type: () => ProductOptionGroup, isArray: true }) - @OneToMany(() => ProductOptionGroup, (productOptionGroup) => productOptionGroup.product, { + @MultiORMOneToMany(() => ProductOptionGroup, (productOptionGroup) => productOptionGroup.product, { cascade: true }) optionGroups?: ProductOptionGroup[]; @@ -147,7 +144,7 @@ export class Product extends TranslatableBase implements IProductTranslatable { * InvoiceItem */ @ApiPropertyOptional({ type: () => InvoiceItem, isArray: true }) - @OneToMany(() => InvoiceItem, (invoiceItem) => invoiceItem.product) + @MultiORMOneToMany(() => InvoiceItem, (invoiceItem) => invoiceItem.product) @JoinColumn() invoiceItems?: IInvoiceItem[]; @@ -155,7 +152,7 @@ export class Product extends TranslatableBase implements IProductTranslatable { * WarehouseProduct */ @ApiPropertyOptional({ type: () => WarehouseProduct, isArray: true }) - @OneToMany(() => WarehouseProduct, (warehouseProduct) => warehouseProduct.product, { + @MultiORMOneToMany(() => WarehouseProduct, (warehouseProduct) => warehouseProduct.product, { cascade: true }) @JoinColumn() @@ -171,9 +168,11 @@ export class Product extends TranslatableBase implements IProductTranslatable { * Tag */ @ApiProperty({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.products, { + @MultiORMManyToMany(() => Tag, (tag) => tag.products, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_product' }) @JoinTable({ name: 'tag_product' @@ -184,8 +183,10 @@ export class Product extends TranslatableBase implements IProductTranslatable { * ImageAsset */ @ApiProperty({ type: () => ImageAsset, isArray: true }) - @ManyToMany(() => ImageAsset, (imageAsset) => imageAsset.productGallery, { - cascade: false + @MultiORMManyToMany(() => ImageAsset, (imageAsset) => imageAsset.productGallery, { + cascade: false, + owner: true, + pivotTable: 'product_gallery_item' }) @JoinTable({ name: 'product_gallery_item' diff --git a/packages/core/src/proposal/proposal.entity.ts b/packages/core/src/proposal/proposal.entity.ts index 26d28f8776b..0131e91332a 100644 --- a/packages/core/src/proposal/proposal.entity.ts +++ b/packages/core/src/proposal/proposal.entity.ts @@ -1,10 +1,7 @@ import { - Column, Index, JoinColumn, RelationId, - ManyToOne, - ManyToMany, JoinTable } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; @@ -21,8 +18,9 @@ import { Tag, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmProposalRepository } from './repository/mikro-orm-proposal.repository'; +import { MultiORMManyToMany, MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('proposal', { mikroOrmRepository: () => MikroOrmProposalRepository }) export class Proposal extends TenantOrganizationBaseEntity @@ -30,23 +28,23 @@ export class Proposal extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) jobPostUrl: string; @ApiPropertyOptional({ type: () => Date }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) valueDate?: Date; @ApiPropertyOptional({ type: () => String }) - @Column() + @MultiORMColumn() jobPostContent?: string; @ApiPropertyOptional({ type: () => String }) - @Column() + @MultiORMColumn() proposalContent?: string; @ApiProperty({ type: () => String, enum: ProposalStatusEnum }) - @Column() + @MultiORMColumn() status?: ProposalStatusEnum; /* @@ -56,17 +54,17 @@ export class Proposal extends TenantOrganizationBaseEntity */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { nullable: true, onDelete: 'CASCADE' }) + @MultiORMManyToOne(() => Employee, { nullable: true, onDelete: 'CASCADE' }) @JoinColumn() employee: IEmployee; @ApiProperty({ type: () => String, readOnly: true }) @RelationId((it: Proposal) => it.employee) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) employeeId?: string; @ApiPropertyOptional({ type: () => OrganizationContact }) - @ManyToOne(() => OrganizationContact, (organizationContact) => organizationContact.proposals, { + @MultiORMManyToOne(() => OrganizationContact, (organizationContact) => organizationContact.proposals, { nullable: true, onDelete: 'CASCADE' }) @@ -75,7 +73,7 @@ export class Proposal extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: Proposal) => it.organizationContact) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationContactId?: string; /* @@ -85,9 +83,11 @@ export class Proposal extends TenantOrganizationBaseEntity */ // Tags @ApiProperty({ type: () => Tag }) - @ManyToMany(() => Tag, (tag) => tag.proposals, { + @MultiORMManyToMany(() => Tag, (tag) => tag.proposals, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_proposal' }) @JoinTable({ name: 'tag_proposal' diff --git a/packages/core/src/reports/report-category.entity.ts b/packages/core/src/reports/report-category.entity.ts index 9319fbe25d2..ea9bc067a6d 100644 --- a/packages/core/src/reports/report-category.entity.ts +++ b/packages/core/src/reports/report-category.entity.ts @@ -1,10 +1,11 @@ -import { Index, Column, OneToMany } from 'typeorm'; +import { Index } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsNotEmpty } from 'class-validator'; import { IReport, IReportCategory } from '@gauzy/contracts'; import { BaseEntity, Report } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmReportCategoryRepository } from './repository/mikro-orm-report-category.repository'; +import { MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('report_category', { mikroOrmRepository: () => MikroOrmReportCategoryRepository }) export class ReportCategory extends BaseEntity implements IReportCategory { @@ -13,16 +14,16 @@ export class ReportCategory extends BaseEntity implements IReportCategory { @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() name?: string; @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) iconClass?: string; @ApiProperty({ type: () => Report }) - @OneToMany(() => Report, (report) => report.category, { + @MultiORMOneToMany(() => Report, (report) => report.category, { cascade: true }) reports: IReport[]; diff --git a/packages/core/src/reports/report-organization.entity.ts b/packages/core/src/reports/report-organization.entity.ts index 0f02cbf34ce..9b54879d5ef 100644 --- a/packages/core/src/reports/report-organization.entity.ts +++ b/packages/core/src/reports/report-organization.entity.ts @@ -1,15 +1,16 @@ -import { Column, RelationId, ManyToOne, JoinColumn } from 'typeorm'; +import { RelationId, JoinColumn } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IReport, IReportOrganization } from '@gauzy/contracts'; import { Report, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmReportOrganizationRepository } from './repository/mikro-orm-report-organization.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('report_organization', { mikroOrmRepository: () => MikroOrmReportOrganizationRepository }) export class ReportOrganization extends TenantOrganizationBaseEntity implements IReportOrganization { @ApiProperty({ type: () => Report }) - @ManyToOne(() => Report, (report) => report.reportOrganizations, { + @MultiORMManyToOne(() => Report, (report) => report.reportOrganizations, { onDelete: 'CASCADE', }) @JoinColumn() @@ -17,9 +18,9 @@ export class ReportOrganization extends TenantOrganizationBaseEntity implements @ApiProperty({ type: () => String, readOnly: true }) @RelationId((report: ReportOrganization) => report.report) - @Column() + @MultiORMColumn({ relationId: true }) reportId?: string; - @Column({ default: true }) + @MultiORMColumn({ default: true, relationId: true }) isEnabled?: boolean; } diff --git a/packages/core/src/reports/report.entity.ts b/packages/core/src/reports/report.entity.ts index c6ea59a5917..41282c91db3 100644 --- a/packages/core/src/reports/report.entity.ts +++ b/packages/core/src/reports/report.entity.ts @@ -1,10 +1,8 @@ import { Index, - Column, + RelationId, - ManyToOne, - JoinColumn, - OneToMany + JoinColumn } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsNotEmpty, IsUUID } from 'class-validator'; @@ -12,8 +10,9 @@ import { IReport, IReportCategory, IReportOrganization } from '@gauzy/contracts' import { BaseEntity } from '../core/entities/internal'; import { ReportCategory } from './report-category.entity'; import { ReportOrganization } from './report-organization.entity'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmReportRepository } from './repository/mikro-orm-report.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('report', { mikroOrmRepository: () => MikroOrmReportRepository }) export class Report extends BaseEntity implements IReport { @@ -22,34 +21,34 @@ export class Report extends BaseEntity implements IReport { @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() name?: string; @ApiProperty({ type: () => String }) @IsString() @IsNotEmpty() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) slug?: string; @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) image?: string; @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) iconClass?: string; @ApiProperty({ type: () => String }) @IsString() - @Column({ default: false }) + @MultiORMColumn({ default: false }) showInMenu?: boolean; imageUrl?: string; @@ -61,7 +60,7 @@ export class Report extends BaseEntity implements IReport { */ @ApiProperty({ type: () => ReportCategory }) - @ManyToOne(() => ReportCategory, (it) => it.reports, { + @MultiORMManyToOne(() => ReportCategory, (it) => it.reports, { onDelete: 'CASCADE' }) @JoinColumn() @@ -71,7 +70,7 @@ export class Report extends BaseEntity implements IReport { @IsUUID() @RelationId((it: Report) => it.category) @Index() - @Column() + @MultiORMColumn({ relationId: true }) categoryId?: IReportCategory['id']; /* @@ -81,7 +80,7 @@ export class Report extends BaseEntity implements IReport { */ @ApiProperty({ type: () => ReportOrganization }) - @OneToMany(() => ReportOrganization, (it) => it.report, { + @MultiORMOneToMany(() => ReportOrganization, (it) => it.report, { cascade: true }) @JoinColumn() diff --git a/packages/core/src/reports/report.service.ts b/packages/core/src/reports/report.service.ts index 293e83dcd5d..eda20bb5c6e 100644 --- a/packages/core/src/reports/report.service.ts +++ b/packages/core/src/reports/report.service.ts @@ -77,9 +77,9 @@ export class ReportService extends CrudService { return await this.repository.find({ join: { - alias: this.alias, + alias: this.tableName, innerJoin: { - reportOrganizations: `${this.alias}.reportOrganizations`, + reportOrganizations: `${this.tableName}.reportOrganizations`, } }, where: { diff --git a/packages/core/src/request-approval-employee/request-approval-employee.entity.ts b/packages/core/src/request-approval-employee/request-approval-employee.entity.ts index 6be65b0bad2..3a73cfcf422 100644 --- a/packages/core/src/request-approval-employee/request-approval-employee.entity.ts +++ b/packages/core/src/request-approval-employee/request-approval-employee.entity.ts @@ -2,7 +2,7 @@ - Request Approval Employee table is the third table which will combine the employee table and the request approval table. - Request Approval Employee table has the many to one relationship to the RequestApproval table and the Employee table by requestApprovalId and employeeId */ -import { Column, ManyToOne, Index, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { IEmployee, IRequestApproval, IRequestApprovalEmployee } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; @@ -11,18 +11,19 @@ import { RequestApproval, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmRequestApprovalEmployeeRepository } from './repository/mikro-orm-request-approval-employee.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('request_approval_employee', { mikroOrmRepository: () => MikroOrmRequestApprovalEmployeeRepository }) export class RequestApprovalEmployee extends TenantOrganizationBaseEntity implements IRequestApprovalEmployee { @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) status: number; - @ManyToOne(() => RequestApproval, (requestApproval) => requestApproval.employeeApprovals, { + @MultiORMManyToOne(() => RequestApproval, (requestApproval) => requestApproval.employeeApprovals, { onDelete: 'CASCADE' }) public requestApproval!: IRequestApproval; @@ -32,10 +33,10 @@ export class RequestApprovalEmployee extends TenantOrganizationBaseEntity implem @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn({ relationId: true }) public requestApprovalId!: string; - @ManyToOne(() => Employee, (employee) => employee.requestApprovals, { + @MultiORMManyToOne(() => Employee, (employee) => employee.requestApprovals, { onDelete: 'CASCADE' }) public employee!: IEmployee; @@ -45,6 +46,6 @@ export class RequestApprovalEmployee extends TenantOrganizationBaseEntity implem @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn({ relationId: true }) public employeeId!: string; } diff --git a/packages/core/src/request-approval-team/request-approval-team.entity.ts b/packages/core/src/request-approval-team/request-approval-team.entity.ts index 048bb40fdf0..70d33e511d7 100644 --- a/packages/core/src/request-approval-team/request-approval-team.entity.ts +++ b/packages/core/src/request-approval-team/request-approval-team.entity.ts @@ -2,7 +2,7 @@ - Request Approval Employee table is the third table which will combine the employee table and the request approval table. - Request Approval Employee table has the many to one relationship to the RequestApproval table and the Employee table by requestApprovalId and employeeId */ -import { Column, ManyToOne, RelationId, Index } from 'typeorm'; +import { RelationId, Index } from 'typeorm'; import { IOrganizationTeam, IRequestApproval, IRequestApprovalTeam } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsString, IsNotEmpty, IsNumber } from 'class-validator'; @@ -11,18 +11,19 @@ import { RequestApproval, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmRequestApprovalTeamRepository } from './repository/mikro-orm-request-approval-team.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('request_approval_team', { mikroOrmRepository: () => MikroOrmRequestApprovalTeamRepository }) export class RequestApprovalTeam extends TenantOrganizationBaseEntity implements IRequestApprovalTeam { @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) status: number; - @ManyToOne(() => RequestApproval, (requestApproval) => requestApproval.teamApprovals, { + @MultiORMManyToOne(() => RequestApproval, (requestApproval) => requestApproval.teamApprovals, { onDelete: 'CASCADE' }) public requestApproval!: IRequestApproval; @@ -32,10 +33,10 @@ export class RequestApprovalTeam extends TenantOrganizationBaseEntity implements @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn({ relationId: true }) public requestApprovalId!: string; - @ManyToOne(() => OrganizationTeam, (team) => team.requestApprovals, { + @MultiORMManyToOne(() => OrganizationTeam, (team) => team.requestApprovals, { onDelete: 'CASCADE' }) public team!: IOrganizationTeam; @@ -45,6 +46,6 @@ export class RequestApprovalTeam extends TenantOrganizationBaseEntity implements @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn({ relationId: true }) public teamId!: string; } diff --git a/packages/core/src/request-approval/request-approval.entity.ts b/packages/core/src/request-approval/request-approval.entity.ts index dd785c2c215..62522161a14 100644 --- a/packages/core/src/request-approval/request-approval.entity.ts +++ b/packages/core/src/request-approval/request-approval.entity.ts @@ -7,12 +7,8 @@ */ import { Index, - Column, - OneToMany, RelationId, - ManyToOne, JoinColumn, - ManyToMany, JoinTable } from 'typeorm'; import { @@ -32,8 +28,9 @@ import { Tag, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmRequestApprovalRepository } from './repository/mikro-orm-request-approval.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('request_approval', { mikroOrmRepository: () => MikroOrmRequestApprovalRepository }) export class RequestApproval extends TenantOrganizationBaseEntity implements IRequestApproval { @@ -41,37 +38,37 @@ export class RequestApproval extends TenantOrganizationBaseEntity implements IRe @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) status: number; @ApiProperty({ type: () => String, readOnly: true }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) createdBy: string; @ApiProperty({ type: () => String }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) createdByName: string; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) min_count: number; @ApiProperty({ type: () => String, readOnly: true }) @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) requestId: string; @ApiProperty({ type: () => String, enum: ApprovalPolicyTypesStringEnum }) @IsEnum(ApprovalPolicyTypesStringEnum) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) requestType: string; /* @@ -84,7 +81,7 @@ export class RequestApproval extends TenantOrganizationBaseEntity implements IRe * ApprovalPolicy */ @ApiProperty({ type: () => ApprovalPolicy }) - @ManyToOne(() => ApprovalPolicy, { + @MultiORMManyToOne(() => ApprovalPolicy, { nullable: true, onDelete: 'CASCADE' }) @@ -95,7 +92,7 @@ export class RequestApproval extends TenantOrganizationBaseEntity implements IRe @RelationId((it: RequestApproval) => it.approvalPolicy) @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) approvalPolicyId: string; /* @@ -108,7 +105,7 @@ export class RequestApproval extends TenantOrganizationBaseEntity implements IRe * RequestApprovalEmployee */ @ApiPropertyOptional({ type: () => RequestApprovalEmployee, isArray: true }) - @OneToMany(() => RequestApprovalEmployee, (employeeApprovals) => employeeApprovals.requestApproval, { + @MultiORMOneToMany(() => RequestApprovalEmployee, (employeeApprovals) => employeeApprovals.requestApproval, { cascade: true }) employeeApprovals?: IRequestApprovalEmployee[]; @@ -117,7 +114,7 @@ export class RequestApproval extends TenantOrganizationBaseEntity implements IRe * RequestApprovalTeam */ @ApiPropertyOptional({ type: () => RequestApprovalTeam, isArray: true }) - @OneToMany(() => RequestApprovalTeam, (teamApprovals) => teamApprovals.requestApproval, { + @MultiORMOneToMany(() => RequestApprovalTeam, (teamApprovals) => teamApprovals.requestApproval, { cascade: true }) teamApprovals?: IRequestApprovalTeam[]; @@ -128,9 +125,11 @@ export class RequestApproval extends TenantOrganizationBaseEntity implements IRe |-------------------------------------------------------------------------- */ @ApiPropertyOptional({ type: () => RequestApprovalTeam, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.requestApprovals, { + @MultiORMManyToMany(() => Tag, (tag) => tag.requestApprovals, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_request_approval' }) @JoinTable({ name: 'tag_request_approval' diff --git a/packages/core/src/role-permission/repository/index.ts b/packages/core/src/role-permission/repository/index.ts new file mode 100644 index 00000000000..da3672ab825 --- /dev/null +++ b/packages/core/src/role-permission/repository/index.ts @@ -0,0 +1,2 @@ +export * from './mikro-orm-role-permission.repository'; +export * from './type-orm-role-permission.repository'; diff --git a/packages/core/src/role-permission/repository/type-orm-role-permission.repository.ts b/packages/core/src/role-permission/repository/type-orm-role-permission.repository.ts index 20db0284c56..1a53bd147de 100644 --- a/packages/core/src/role-permission/repository/type-orm-role-permission.repository.ts +++ b/packages/core/src/role-permission/repository/type-orm-role-permission.repository.ts @@ -1,4 +1,12 @@ +import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { RolePermission } from '../role-permission.entity'; -export class TypeOrmRolePermissionRepository extends Repository { } \ No newline at end of file +export class TypeOrmRolePermissionRepository extends Repository { + + constructor( + @InjectRepository(RolePermission) readonly repository: Repository + ) { + super(repository.target, repository.manager, repository.queryRunner); + } +} diff --git a/packages/core/src/role-permission/role-permission.entity.ts b/packages/core/src/role-permission/role-permission.entity.ts index 7ecf2da6661..1c900a7c5a8 100644 --- a/packages/core/src/role-permission/role-permission.entity.ts +++ b/packages/core/src/role-permission/role-permission.entity.ts @@ -3,10 +3,11 @@ import { IRolePermission } from '@gauzy/contracts'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { Role, TenantBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmRolePermissionRepository } from './repository/mikro-orm-role-permission.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('role_permission', { mikroOrmRepository: () => MikroOrmRolePermissionRepository }) export class RolePermission extends TenantBaseEntity @@ -14,15 +15,15 @@ export class RolePermission extends TenantBaseEntity @ApiProperty({ type: () => String, enum: PermissionsEnum }) @Index() - @Column() + @MultiORMColumn() permission: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ nullable: true, default: false }) + @MultiORMColumn({ nullable: true, default: false }) enabled: boolean; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; /* @@ -30,7 +31,7 @@ export class RolePermission extends TenantBaseEntity | @ManyToOne |-------------------------------------------------------------------------- */ - @ManyToOne(() => Role, (role) => role.rolePermissions, { + @MultiORMManyToOne(() => Role, (it) => it.rolePermissions, { onDelete: 'CASCADE' }) role: Role; @@ -38,6 +39,6 @@ export class RolePermission extends TenantBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: RolePermission) => it.role) @Index() - @Column() + @MultiORMColumn({ relationId: true }) roleId: string; } diff --git a/packages/core/src/role-permission/role-permission.module.ts b/packages/core/src/role-permission/role-permission.module.ts index 3bb441a312f..b6fa5084211 100644 --- a/packages/core/src/role-permission/role-permission.module.ts +++ b/packages/core/src/role-permission/role-permission.module.ts @@ -7,6 +7,7 @@ import { RolePermissionController } from './role-permission.controller'; import { RolePermission } from './role-permission.entity'; import { RolePermissionService } from './role-permission.service'; import { RoleModule } from './../role/role.module'; +import { TypeOrmRolePermissionRepository } from './repository'; @Module({ imports: [ @@ -19,7 +20,7 @@ import { RoleModule } from './../role/role.module'; CqrsModule ], controllers: [RolePermissionController], - providers: [RolePermissionService], - exports: [TypeOrmModule, MikroOrmModule, RolePermissionService] + providers: [RolePermissionService, TypeOrmRolePermissionRepository], + exports: [TypeOrmModule, MikroOrmModule, RolePermissionService, TypeOrmRolePermissionRepository] }) export class RolePermissionModule { } diff --git a/packages/core/src/role-permission/role-permission.service.ts b/packages/core/src/role-permission/role-permission.service.ts index 1e17523700e..e83f4e24c2d 100644 --- a/packages/core/src/role-permission/role-permission.service.ts +++ b/packages/core/src/role-permission/role-permission.service.ts @@ -1,5 +1,4 @@ import { Injectable, BadRequestException, NotAcceptableException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import { CommandBus } from '@nestjs/cqrs'; import { UpdateResult, FindManyOptions, Not, In, DeepPartial, FindOptionsWhere } from 'typeorm'; import { pluck } from 'underscore'; @@ -28,11 +27,8 @@ import { TypeOrmRolePermissionRepository } from './repository/type-orm-role-perm @Injectable() export class RolePermissionService extends TenantAwareCrudService { constructor( - @InjectRepository(RolePermission) - typeOrmRolePermissionRepository: TypeOrmRolePermissionRepository, - - mikroOrmRolePermissionRepository: MikroOrmRolePermissionRepository, - + readonly typeOrmRolePermissionRepository: TypeOrmRolePermissionRepository, + readonly mikroOrmRolePermissionRepository: MikroOrmRolePermissionRepository, private readonly roleService: RoleService, private readonly _commandBus: CommandBus ) { diff --git a/packages/core/src/role/repository/index.ts b/packages/core/src/role/repository/index.ts new file mode 100644 index 00000000000..3a37b04bd54 --- /dev/null +++ b/packages/core/src/role/repository/index.ts @@ -0,0 +1,2 @@ +export * from './mikro-orm-role.repository'; +export * from './type-orm-role.repository'; diff --git a/packages/core/src/role/repository/type-orm-role.repository.ts b/packages/core/src/role/repository/type-orm-role.repository.ts index 8dc7721e7d7..6350f6d39dc 100644 --- a/packages/core/src/role/repository/type-orm-role.repository.ts +++ b/packages/core/src/role/repository/type-orm-role.repository.ts @@ -1,4 +1,12 @@ +import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Role } from '../role.entity'; -export class TypeOrmRoleRepository extends Repository { } \ No newline at end of file +export class TypeOrmRoleRepository extends Repository { + + constructor( + @InjectRepository(Role) readonly repository: Repository + ) { + super(repository.target, repository.manager, repository.queryRunner); + } +} diff --git a/packages/core/src/role/role.controller.ts b/packages/core/src/role/role.controller.ts index 3eb4eda91e7..693a921874f 100644 --- a/packages/core/src/role/role.controller.ts +++ b/packages/core/src/role/role.controller.ts @@ -95,7 +95,7 @@ export class RoleController extends CrudController { }) @Get() async findAll(): Promise> { - return this.roleService.findAll(); + return await this.roleService.findAll(); } /** diff --git a/packages/core/src/role/role.entity.ts b/packages/core/src/role/role.entity.ts index 1b930c7b42c..26a60efd58d 100644 --- a/packages/core/src/role/role.entity.ts +++ b/packages/core/src/role/role.entity.ts @@ -1,10 +1,11 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, OneToMany } from 'typeorm'; +import { Index } from 'typeorm'; import { IsBoolean, IsNotEmpty, IsOptional } from 'class-validator'; import { RolesEnum, IRolePermission, IRole } from '@gauzy/contracts'; import { RolePermission, TenantBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmRoleRepository } from './repository/mikro-orm-role.repository'; +import { MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('role', { mikroOrmRepository: () => MikroOrmRoleRepository }) export class Role extends TenantBaseEntity implements IRole { @@ -12,13 +13,13 @@ export class Role extends TenantBaseEntity implements IRole { @ApiProperty({ type: () => String, enum: RolesEnum }) @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() name: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) @IsOptional() @IsBoolean() - @Column({ default: false }) + @MultiORMColumn({ default: false }) isSystem?: boolean; /* @@ -30,7 +31,7 @@ export class Role extends TenantBaseEntity implements IRole { /** * Role Permissions */ - @OneToMany(() => RolePermission, (it) => it.role, { + @MultiORMOneToMany(() => RolePermission, (it) => it.role, { cascade: true }) rolePermissions?: IRolePermission[]; diff --git a/packages/core/src/role/role.module.ts b/packages/core/src/role/role.module.ts index 1af01689a9a..aaded7a6d90 100644 --- a/packages/core/src/role/role.module.ts +++ b/packages/core/src/role/role.module.ts @@ -8,6 +8,7 @@ import { RoleService } from './role.service'; import { RoleController } from './role.controller'; import { RolePermissionModule } from './../role-permission/role-permission.module'; import { CommandHandlers } from './commands/handlers'; +import { TypeOrmRoleRepository } from './repository/type-orm-role.repository'; @Module({ imports: [ @@ -20,11 +21,16 @@ import { CommandHandlers } from './commands/handlers'; CqrsModule ], controllers: [RoleController], - providers: [RoleService, ...CommandHandlers], + providers: [ + ...CommandHandlers, + RoleService, + TypeOrmRoleRepository + ], exports: [ TypeOrmModule, MikroOrmModule, - RoleService + RoleService, + TypeOrmRoleRepository ] }) export class RoleModule { } diff --git a/packages/core/src/role/role.service.ts b/packages/core/src/role/role.service.ts index e25455aa334..69d97996e58 100644 --- a/packages/core/src/role/role.service.ts +++ b/packages/core/src/role/role.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import { CommandBus } from '@nestjs/cqrs'; import { DeleteResult, In, Not } from 'typeorm'; import { IRole, ITenant, RolesEnum, IRoleMigrateInput, IImportRecord, SYSTEM_DEFAULT_ROLES } from '@gauzy/contracts'; @@ -7,22 +6,24 @@ import { TenantAwareCrudService } from './../core/crud'; import { Role } from './role.entity'; import { RequestContext } from './../core/context'; import { ImportRecordUpdateOrCreateCommand } from './../export-import/import-record'; -import { MikroOrmRoleRepository } from './repository/mikro-orm-role.repository'; -import { TypeOrmRoleRepository } from './repository/type-orm-role.repository'; +import { MikroOrmRoleRepository, TypeOrmRoleRepository } from './repository'; @Injectable() export class RoleService extends TenantAwareCrudService { - constructor( - @InjectRepository(Role) - typeOrmRoleRepository: TypeOrmRoleRepository, - - mikroOrmRoleRepository: MikroOrmRoleRepository, + constructor( + readonly typeOrmRoleRepository: TypeOrmRoleRepository, + readonly mikroOrmRoleRepository: MikroOrmRoleRepository, private readonly _commandBus: CommandBus ) { super(typeOrmRoleRepository, mikroOrmRoleRepository); } + /** + * Creates multiple roles for each tenant and saves them. + * @param tenants - An array of tenants for which roles will be created. + * @returns A promise that resolves to an array of created roles. + */ async createBulk(tenants: ITenant[]): Promise { const roles: IRole[] = []; const rolesNames = Object.values(RolesEnum); diff --git a/packages/core/src/shared/pipes/column-numeric-transformer.pipe.ts b/packages/core/src/shared/pipes/column-numeric-transformer.pipe.ts index 2d428331bc9..f13503b54d8 100644 --- a/packages/core/src/shared/pipes/column-numeric-transformer.pipe.ts +++ b/packages/core/src/shared/pipes/column-numeric-transformer.pipe.ts @@ -3,25 +3,30 @@ import { isNullOrUndefined } from "@gauzy/common"; /** * Convert Non-integer numbers string to integer - * From https://github.com/typeorm/typeorm/issues/873#issuecomment-502294597 + * From https://github.com/typeorm/typeorm/issues/873#issuecomment-502294597 */ export class ColumnNumericTransformerPipe implements ValueTransformer { + /** + * Transforms a number to the database value. + * + * @param data - The input number. + * @returns The transformed number or null. + */ to(data?: number | null): number | null { - if (!isNullOrUndefined(data)) { - return data - } - return null + return isNullOrUndefined(data) ? null : data; } + /** + * Transforms a string to the entity property value. + * + * @param data - The input string. + * @returns The transformed number or null. + */ from(data?: string | null): number | null { if (!isNullOrUndefined(data)) { - const res = parseFloat(data) - if (isNaN(res)) { - return null - } else { - return res - } + const parsedValue = parseFloat(data); + return isNaN(parsedValue) ? null : parsedValue; } - return null + return null; } -} \ No newline at end of file +} diff --git a/packages/core/src/skills/skill.entity.ts b/packages/core/src/skills/skill.entity.ts index b1e59922a50..96523f58187 100644 --- a/packages/core/src/skills/skill.entity.ts +++ b/packages/core/src/skills/skill.entity.ts @@ -1,4 +1,4 @@ -import { Column, ManyToMany, JoinTable } from 'typeorm'; +import { JoinTable } from 'typeorm'; import { IEmployee, IOrganization, ISkill } from '@gauzy/contracts'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { @@ -6,23 +6,24 @@ import { Organization, TenantOrganizationBaseEntity } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmSkillRepository } from './repository/mikro-orm-skill.repository'; +import { MultiORMManyToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('skill', { mikroOrmRepository: () => MikroOrmSkillRepository }) export class Skill extends TenantOrganizationBaseEntity implements ISkill { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name?: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() color?: string; /* @@ -34,9 +35,11 @@ export class Skill extends TenantOrganizationBaseEntity /** * employees skills */ - @ManyToMany(() => Employee, (employee) => employee.skills, { + @MultiORMManyToMany(() => Employee, (employee) => employee.skills, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'skill_employee' }) @JoinTable({ name: 'skill_employee' @@ -46,9 +49,11 @@ export class Skill extends TenantOrganizationBaseEntity /** * organizations skills */ - @ManyToMany(() => Organization, (organization) => organization.skills, { + @MultiORMManyToMany(() => Organization, (organization) => organization.skills, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'skill_organization' }) @JoinTable({ name: 'skill_organization' diff --git a/packages/core/src/tags/tag.entity.ts b/packages/core/src/tags/tag.entity.ts index 8b3c7132ef2..bb7fea81365 100644 --- a/packages/core/src/tags/tag.entity.ts +++ b/packages/core/src/tags/tag.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, ManyToMany, ManyToOne, RelationId, Index } from 'typeorm'; +import { RelationId, Index } from 'typeorm'; import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { ICandidate, @@ -59,8 +59,9 @@ import { User, Warehouse } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmTagRepository } from './repository/mikro-orm-tag.repository'; +import { MultiORMManyToMany, MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('tag', { mikroOrmRepository: () => MikroOrmTagRepository }) export class Tag extends TenantOrganizationBaseEntity implements ITag { @@ -68,34 +69,34 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { @ApiProperty({ type: () => String, required: true }) @IsNotEmpty() @IsString() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String, required: true }) @IsNotEmpty() @IsString() - @Column() + @MultiORMColumn() color: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) textColor?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) icon?: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ default: false }) + @MultiORMColumn({ default: false }) isSystem?: boolean; fullIconUrl?: string; @@ -109,7 +110,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Organization Team */ - @ManyToOne(() => OrganizationTeam, (it) => it.labels, { + @MultiORMManyToOne(() => OrganizationTeam, (it) => it.labels, { /** Database cascade action on delete. */ onDelete: 'SET NULL', }) @@ -120,7 +121,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { @IsUUID() @RelationId((it: Tag) => it.organizationTeam) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationTeamId?: IOrganizationTeam['id']; /* @@ -132,7 +133,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Candidate */ - @ManyToMany(() => Candidate, (it) => it.tags, { + @MultiORMManyToMany(() => Candidate, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -141,7 +142,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Employee */ - @ManyToMany(() => Employee, (it) => it.tags, { + @MultiORMManyToMany(() => Employee, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -150,7 +151,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Equipment */ - @ManyToMany(() => Equipment, (it) => it.tags, { + @MultiORMManyToMany(() => Equipment, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -159,7 +160,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * EventType */ - @ManyToMany(() => EventType, (it) => it.tags, { + @MultiORMManyToMany(() => EventType, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -168,7 +169,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Income */ - @ManyToMany(() => Income, (it) => it.tags, { + @MultiORMManyToMany(() => Income, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -177,7 +178,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Expense */ - @ManyToMany(() => Expense, (it) => it.tags, { + @MultiORMManyToMany(() => Expense, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -186,7 +187,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Invoice */ - @ManyToMany(() => Invoice, (it) => it.tags, { + @MultiORMManyToMany(() => Invoice, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -195,7 +196,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Income */ - @ManyToMany(() => Task, (it) => it.tags, { + @MultiORMManyToMany(() => Task, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -204,7 +205,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Proposal */ - @ManyToMany(() => Proposal, (it) => it.tags, { + @MultiORMManyToMany(() => Proposal, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -213,7 +214,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * OrganizationVendor */ - @ManyToMany(() => OrganizationVendor, (it) => it.tags, { + @MultiORMManyToMany(() => OrganizationVendor, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -222,7 +223,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * OrganizationTeam */ - @ManyToMany(() => OrganizationTeam, (it) => it.tags, { + @MultiORMManyToMany(() => OrganizationTeam, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -231,7 +232,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * OrganizationProject */ - @ManyToMany(() => OrganizationProject, (it) => it.tags, { + @MultiORMManyToMany(() => OrganizationProject, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE', }) @@ -240,7 +241,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * OrganizationPosition */ - @ManyToMany(() => OrganizationPosition, (it) => it.tags, { + @MultiORMManyToMany(() => OrganizationPosition, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -249,7 +250,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * ExpenseCategory */ - @ManyToMany(() => ExpenseCategory, (it) => it.tags, { + @MultiORMManyToMany(() => ExpenseCategory, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -258,7 +259,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * OrganizationEmploymentType */ - @ManyToMany(() => OrganizationEmploymentType, (it) => it.tags, { + @MultiORMManyToMany(() => OrganizationEmploymentType, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -267,7 +268,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * EmployeeLevel */ - @ManyToMany(() => EmployeeLevel, (it) => it.tags, { + @MultiORMManyToMany(() => EmployeeLevel, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -276,7 +277,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * OrganizationDepartment */ - @ManyToMany(() => OrganizationDepartment, (it) => it.tags, { + @MultiORMManyToMany(() => OrganizationDepartment, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -285,7 +286,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * OrganizationContact */ - @ManyToMany(() => OrganizationContact, (it) => it.tags, { + @MultiORMManyToMany(() => OrganizationContact, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -294,7 +295,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Product */ - @ManyToMany(() => Product, (it) => it.tags, { + @MultiORMManyToMany(() => Product, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -303,7 +304,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Payment */ - @ManyToMany(() => Payment, (it) => it.tags, { + @MultiORMManyToMany(() => Payment, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -312,7 +313,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * RequestApproval */ - @ManyToMany(() => RequestApproval, (it) => it.tags, { + @MultiORMManyToMany(() => RequestApproval, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -321,7 +322,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * User */ - @ManyToMany(() => User, (it) => it.tags, { + @MultiORMManyToMany(() => User, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -330,7 +331,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Integration */ - @ManyToMany(() => Integration, (it) => it.tags, { + @MultiORMManyToMany(() => Integration, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -339,7 +340,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Merchant */ - @ManyToMany(() => Merchant, (it) => it.tags, { + @MultiORMManyToMany(() => Merchant, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -348,7 +349,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Warehouse */ - @ManyToMany(() => Warehouse, (it) => it.tags, { + @MultiORMManyToMany(() => Warehouse, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -357,7 +358,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { /** * Organization */ - @ManyToMany(() => Organization, (it) => it.tags, { + @MultiORMManyToMany(() => Organization, (it) => it.tags, { /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) diff --git a/packages/core/src/tags/tag.service.ts b/packages/core/src/tags/tag.service.ts index 01d80f30415..3bb1fefc7e3 100644 --- a/packages/core/src/tags/tag.service.ts +++ b/packages/core/src/tags/tag.service.ts @@ -37,7 +37,7 @@ export class TagService extends TenantAwareCrudService { * @returns */ async findTagsByLevel(input: ITagFindInput, relations: string[] = []): Promise> { - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); /** * Defines a special criteria to find specific relations. */ @@ -70,7 +70,7 @@ export class TagService extends TenantAwareCrudService { relations: string[] | FindOptionsRelations = [] ): Promise> { try { - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); /** * Defines a special criteria to find specific relations. */ diff --git a/packages/core/src/tasks/estimation/task-estimation.entity.ts b/packages/core/src/tasks/estimation/task-estimation.entity.ts index 009716b77c1..9482bb9ebe0 100644 --- a/packages/core/src/tasks/estimation/task-estimation.entity.ts +++ b/packages/core/src/tasks/estimation/task-estimation.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Column, Index, RelationId, ManyToOne } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { IsNumber } from 'class-validator'; import { IEmployee, ITask, ITaskEstimation } from '@gauzy/contracts'; import { @@ -7,26 +7,27 @@ import { TenantOrganizationBaseEntity, Task, } from './../../core/entities/internal'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../../core/decorators/entity'; import { MikroOrmTaskEstimationRepository } from './repository/mikro-orm-estimation.repository'; +import { MultiORMManyToOne } from '../../core/decorators/entity/relations'; @MultiORMEntity('task_estimation', { mikroOrmRepository: () => MikroOrmTaskEstimationRepository }) export class TaskEstimation extends TenantOrganizationBaseEntity implements ITaskEstimation { @ApiProperty({ type: () => Number }) @IsNumber() - @Column() + @MultiORMColumn() estimate: number; @ApiProperty({ type: () => String }) @RelationId((it: TaskEstimation) => it.employee) @Index() - @Column() + @MultiORMColumn({ relationId: true }) employeeId: IEmployee['id']; @ApiProperty({ type: () => String }) @RelationId((it: TaskEstimation) => it.task) @Index() - @Column() + @MultiORMColumn({ relationId: true }) taskId: ITask['id']; /* @@ -35,13 +36,13 @@ export class TaskEstimation extends TenantOrganizationBaseEntity implements ITas |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, (employee) => employee.estimations, { + @MultiORMManyToOne(() => Employee, (employee) => employee.estimations, { onDelete: 'CASCADE', }) employee?: IEmployee; @ApiProperty({ type: () => Task }) - @ManyToOne(() => Task, (task) => task.estimations, { + @MultiORMManyToOne(() => Task, (task) => task.estimations, { onDelete: 'CASCADE', }) task?: ITask; diff --git a/packages/core/src/tasks/issue-type/issue-type.entity.ts b/packages/core/src/tasks/issue-type/issue-type.entity.ts index 536e91ff4a1..53868f0b2d7 100644 --- a/packages/core/src/tasks/issue-type/issue-type.entity.ts +++ b/packages/core/src/tasks/issue-type/issue-type.entity.ts @@ -1,9 +1,7 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { - Column, Index, JoinColumn, - ManyToOne, RelationId, } from 'typeorm'; import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; @@ -19,8 +17,9 @@ import { OrganizationTeam, TenantOrganizationBaseEntity, } from './../../core/entities/internal'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../../core/decorators/entity'; import { MikroOrmIssueTypeRepository } from './repository/mikro-orm-issue-type.repository'; +import { MultiORMManyToOne } from '../../core/decorators/entity/relations'; @MultiORMEntity('issue_type', { mikroOrmRepository: () => MikroOrmIssueTypeRepository }) export class IssueType extends TenantOrganizationBaseEntity implements IIssueType { @@ -29,31 +28,31 @@ export class IssueType extends TenantOrganizationBaseEntity implements IIssueTyp @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() value: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) icon?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) color?: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ default: false, update: false }) + @MultiORMColumn({ default: false, update: false }) isSystem?: boolean; fullIconUrl?: string; @@ -67,7 +66,7 @@ export class IssueType extends TenantOrganizationBaseEntity implements IIssueTyp /** * Image Asset */ - @ManyToOne(() => ImageAsset, { + @MultiORMManyToOne(() => ImageAsset, { /** Database cascade action on delete. */ onDelete: 'SET NULL', @@ -82,13 +81,13 @@ export class IssueType extends TenantOrganizationBaseEntity implements IIssueTyp @IsUUID() @RelationId((it: IssueType) => it.image) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) imageId?: IImageAsset['id']; /** * Organization Project */ - @ManyToOne(() => OrganizationProject, { + @MultiORMManyToOne(() => OrganizationProject, { onDelete: 'CASCADE', }) project?: IOrganizationProject; @@ -98,13 +97,13 @@ export class IssueType extends TenantOrganizationBaseEntity implements IIssueTyp @IsUUID() @RelationId((it: IssueType) => it.project) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; /** * Organization Team */ - @ManyToOne(() => OrganizationTeam, { + @MultiORMManyToOne(() => OrganizationTeam, { onDelete: 'CASCADE', }) organizationTeam?: IOrganizationTeam; @@ -114,6 +113,6 @@ export class IssueType extends TenantOrganizationBaseEntity implements IIssueTyp @IsUUID() @RelationId((it: IssueType) => it.organizationTeam) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationTeamId?: IOrganizationTeam['id']; } diff --git a/packages/core/src/tasks/issue-type/issue-type.service.ts b/packages/core/src/tasks/issue-type/issue-type.service.ts index 0fb7e8f326e..a171a286efd 100644 --- a/packages/core/src/tasks/issue-type/issue-type.service.ts +++ b/packages/core/src/tasks/issue-type/issue-type.service.ts @@ -58,7 +58,7 @@ export class IssueTypeService extends TaskStatusPrioritySizeService { /** * Find at least one record or get global records */ - const cqb = this.typeOrmIssueTypeRepository.createQueryBuilder(this.alias); + const cqb = this.typeOrmIssueTypeRepository.createQueryBuilder(this.tableName); cqb.where((qb: SelectQueryBuilder) => { this.getFilterQuery(qb, params); }); @@ -67,7 +67,7 @@ export class IssueTypeService extends TaskStatusPrioritySizeService { /** * Find task issue types for given params */ - const query = this.typeOrmIssueTypeRepository.createQueryBuilder(this.alias); + const query = this.typeOrmIssueTypeRepository.createQueryBuilder(this.tableName); query.where((qb: SelectQueryBuilder) => { this.getFilterQuery(qb, params); }); diff --git a/packages/core/src/tasks/linked-issue/task-linked-issue.entity.ts b/packages/core/src/tasks/linked-issue/task-linked-issue.entity.ts index 918d99eedc4..bd0a3e1848b 100644 --- a/packages/core/src/tasks/linked-issue/task-linked-issue.entity.ts +++ b/packages/core/src/tasks/linked-issue/task-linked-issue.entity.ts @@ -1,9 +1,7 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { - Column, Index, JoinColumn, - ManyToOne, RelationId, } from 'typeorm'; import { IsEnum, IsUUID } from 'class-validator'; @@ -14,13 +12,14 @@ import { } from '@gauzy/contracts'; import { Task } from './../task.entity'; import { TenantOrganizationBaseEntity } from './../../core/entities/internal'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../../core/decorators/entity'; import { MikroOrmTaskLinkedIssueRepository } from './repository/mikro-orm-linked-issue.repository'; +import { MultiORMManyToOne } from '../../core/decorators/entity/relations'; @MultiORMEntity('task_linked_issues', { mikroOrmRepository: () => MikroOrmTaskLinkedIssueRepository }) export class TaskLinkedIssue extends TenantOrganizationBaseEntity implements ITaskLinkedIssue { @ApiProperty({ type: () => String, enum: TaskRelatedIssuesRelationEnum }) - @Column() + @MultiORMColumn() @IsEnum(TaskRelatedIssuesRelationEnum) action: TaskRelatedIssuesRelationEnum; @@ -30,7 +29,7 @@ export class TaskLinkedIssue extends TenantOrganizationBaseEntity implements ITa |-------------------------------------------------------------------------- */ @ApiPropertyOptional({ type: () => Task }) - @ManyToOne(() => Task) + @MultiORMManyToOne(() => Task) @JoinColumn() taskFrom?: ITask; @@ -38,14 +37,14 @@ export class TaskLinkedIssue extends TenantOrganizationBaseEntity implements ITa @IsUUID() @RelationId((it: TaskLinkedIssue) => it.taskFrom) @Index() - @Column() + @MultiORMColumn({ relationId: true }) taskFromId: ITask['id']; /** * Task Linked Issues */ @ApiPropertyOptional({ type: () => Object }) - @ManyToOne(() => Task, (it) => it.linkedIssues) + @MultiORMManyToOne(() => Task, (it) => it.linkedIssues) @JoinColumn() taskTo?: ITask; @@ -53,6 +52,6 @@ export class TaskLinkedIssue extends TenantOrganizationBaseEntity implements ITa @IsUUID() @RelationId((it: TaskLinkedIssue) => it.taskTo) @Index() - @Column() + @MultiORMColumn({ relationId: true }) taskToId: ITask['id']; } diff --git a/packages/core/src/tasks/priorities/priority.entity.ts b/packages/core/src/tasks/priorities/priority.entity.ts index c75c2ee1bba..7b817b02719 100644 --- a/packages/core/src/tasks/priorities/priority.entity.ts +++ b/packages/core/src/tasks/priorities/priority.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { IOrganizationProject, IOrganizationTeam, ITaskPriority } from '@gauzy/contracts'; import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { @@ -7,8 +7,9 @@ import { OrganizationTeam, TenantOrganizationBaseEntity, } from './../../core/entities/internal'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../../core/decorators/entity'; import { MikroOrmTaskPriorityRepository } from './repository/mikro-orm-task-priority.repository'; +import { MultiORMManyToOne } from '../../core/decorators/entity/relations'; @MultiORMEntity('task_priority', { mikroOrmRepository: () => MikroOrmTaskPriorityRepository }) export class TaskPriority extends TenantOrganizationBaseEntity implements ITaskPriority { @@ -17,31 +18,31 @@ export class TaskPriority extends TenantOrganizationBaseEntity implements ITaskP @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() value: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) icon?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) color?: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ default: false, update: false }) + @MultiORMColumn({ default: false, update: false }) isSystem?: boolean; fullIconUrl?: string; @@ -54,7 +55,7 @@ export class TaskPriority extends TenantOrganizationBaseEntity implements ITaskP /** * Organization Project Relationship */ - @ManyToOne(() => OrganizationProject, (it) => it.priorities, { + @MultiORMManyToOne(() => OrganizationProject, (it) => it.priorities, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -71,13 +72,13 @@ export class TaskPriority extends TenantOrganizationBaseEntity implements ITaskP @IsUUID() @RelationId((it: TaskPriority) => it.project) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; /** * Organization Team Relationship */ - @ManyToOne(() => OrganizationTeam, (team) => team.priorities, { + @MultiORMManyToOne(() => OrganizationTeam, (team) => team.priorities, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -94,6 +95,6 @@ export class TaskPriority extends TenantOrganizationBaseEntity implements ITaskP @IsUUID() @RelationId((it: TaskPriority) => it.organizationTeam) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationTeamId?: IOrganizationTeam['id']; } diff --git a/packages/core/src/tasks/related-issue-type/related-issue-type.entity.ts b/packages/core/src/tasks/related-issue-type/related-issue-type.entity.ts index 712cef64e1b..e9b3701feab 100644 --- a/packages/core/src/tasks/related-issue-type/related-issue-type.entity.ts +++ b/packages/core/src/tasks/related-issue-type/related-issue-type.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { IOrganizationProject, IOrganizationTeam, @@ -11,8 +11,9 @@ import { OrganizationTeam, TenantOrganizationBaseEntity, } from '../../core/entities/internal'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../../core/decorators/entity'; import { MikroOrmTaskRelatedIssueTypeRepository } from './repository/mikro-orm-related-issue-type.repository'; +import { MultiORMManyToOne } from '../../core/decorators/entity/relations'; @MultiORMEntity('task_related_issue_type', { mikroOrmRepository: () => MikroOrmTaskRelatedIssueTypeRepository }) export class TaskRelatedIssueType extends TenantOrganizationBaseEntity implements ITaskRelatedIssueType { @@ -21,31 +22,31 @@ export class TaskRelatedIssueType extends TenantOrganizationBaseEntity implement @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() value: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) icon?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) color?: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ default: false, update: false }) + @MultiORMColumn({ default: false, update: false }) isSystem?: boolean; fullIconUrl?: string; @@ -58,7 +59,7 @@ export class TaskRelatedIssueType extends TenantOrganizationBaseEntity implement /** * Organization Project Relationship */ - @ManyToOne(() => OrganizationProject, (it) => it.relatedIssueTypes, { + @MultiORMManyToOne(() => OrganizationProject, (it) => it.relatedIssueTypes, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -75,13 +76,13 @@ export class TaskRelatedIssueType extends TenantOrganizationBaseEntity implement @IsUUID() @RelationId((it: TaskRelatedIssueType) => it.project) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; /** * Organization Team Relationship */ - @ManyToOne(() => OrganizationTeam, (it) => it.relatedIssueTypes, { + @MultiORMManyToOne(() => OrganizationTeam, (it) => it.relatedIssueTypes, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -98,6 +99,6 @@ export class TaskRelatedIssueType extends TenantOrganizationBaseEntity implement @IsUUID() @RelationId((it: TaskRelatedIssueType) => it.organizationTeam) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationTeamId?: IOrganizationTeam['id']; } diff --git a/packages/core/src/tasks/repository/mikro-orm-task.repository.ts b/packages/core/src/tasks/repository/mikro-orm-task.repository.ts index baacf9d15f4..dfb97431a97 100644 --- a/packages/core/src/tasks/repository/mikro-orm-task.repository.ts +++ b/packages/core/src/tasks/repository/mikro-orm-task.repository.ts @@ -1,4 +1,5 @@ -import { EntityRepository } from '@mikro-orm/core'; +import { EntityRepository } from '@mikro-orm/knex'; import { Task } from '../task.entity'; -export class MikroOrmTaskRepository extends EntityRepository { } +export class MikroOrmTaskRepository extends EntityRepository { +} diff --git a/packages/core/src/tasks/sizes/size.entity.ts b/packages/core/src/tasks/sizes/size.entity.ts index 4d74999b710..e87bd366565 100644 --- a/packages/core/src/tasks/sizes/size.entity.ts +++ b/packages/core/src/tasks/sizes/size.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { IOrganizationProject, IOrganizationTeam, ITaskSize } from '@gauzy/contracts'; import { @@ -7,8 +7,9 @@ import { OrganizationTeam, TenantOrganizationBaseEntity, } from './../../core/entities/internal'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../../core/decorators/entity'; import { MikroOrmTaskSizeRepository } from './repository/mikro-orm-task-size.repository'; +import { MultiORMManyToOne } from '../../core/decorators/entity/relations'; @MultiORMEntity('task_size', { mikroOrmRepository: () => MikroOrmTaskSizeRepository }) export class TaskSize extends TenantOrganizationBaseEntity implements ITaskSize { @@ -17,31 +18,31 @@ export class TaskSize extends TenantOrganizationBaseEntity implements ITaskSize @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() value: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) icon?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) color?: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ default: false, update: false }) + @MultiORMColumn({ default: false, update: false }) isSystem?: boolean; fullIconUrl?: string; @@ -54,7 +55,7 @@ export class TaskSize extends TenantOrganizationBaseEntity implements ITaskSize /** * Organization Project Relationship */ - @ManyToOne(() => OrganizationProject, (it) => it.sizes, { + @MultiORMManyToOne(() => OrganizationProject, (it) => it.sizes, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -68,13 +69,13 @@ export class TaskSize extends TenantOrganizationBaseEntity implements ITaskSize @IsUUID() @RelationId((it: TaskSize) => it.project) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; /** * Organization Team Relationship */ - @ManyToOne(() => OrganizationTeam, (it) => it.sizes, { + @MultiORMManyToOne(() => OrganizationTeam, (it) => it.sizes, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -91,6 +92,6 @@ export class TaskSize extends TenantOrganizationBaseEntity implements ITaskSize @IsUUID() @RelationId((it: TaskSize) => it.organizationTeam) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationTeamId?: IOrganizationTeam['id']; } diff --git a/packages/core/src/tasks/statuses/status.entity.ts b/packages/core/src/tasks/statuses/status.entity.ts index c420d0cfa84..2147bd74972 100644 --- a/packages/core/src/tasks/statuses/status.entity.ts +++ b/packages/core/src/tasks/statuses/status.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { IOrganizationProject, IOrganizationTeam, ITaskStatus } from '@gauzy/contracts'; import { @@ -7,8 +7,9 @@ import { OrganizationTeam, TenantOrganizationBaseEntity } from '../../core/entities/internal'; -import { MultiORMEntity } from '../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from '../../core/decorators/entity'; import { MikroOrmTaskStatusRepository } from './repository/mikro-orm-task-status.repository'; +import { MultiORMManyToOne } from '../../core/decorators/entity/relations'; @MultiORMEntity('task_status', { mikroOrmRepository: () => MikroOrmTaskStatusRepository }) export class TaskStatus extends TenantOrganizationBaseEntity implements ITaskStatus { @@ -17,40 +18,40 @@ export class TaskStatus extends TenantOrganizationBaseEntity implements ITaskSta @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() value: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiPropertyOptional({ type: () => Number }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) order?: number; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) icon?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) color?: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ default: false, update: false }) + @MultiORMColumn({ default: false, update: false }) isSystem?: boolean; @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ default: false }) + @MultiORMColumn({ default: false }) isCollapsed?: boolean; /** @@ -66,7 +67,7 @@ export class TaskStatus extends TenantOrganizationBaseEntity implements ITaskSta /** * Organization Project Relationship */ - @ManyToOne(() => OrganizationProject, (it) => it.statuses, { + @MultiORMManyToOne(() => OrganizationProject, (it) => it.statuses, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -83,13 +84,13 @@ export class TaskStatus extends TenantOrganizationBaseEntity implements ITaskSta @IsUUID() @RelationId((it: TaskStatus) => it.project) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; /** * Organization Team */ - @ManyToOne(() => OrganizationTeam, (it) => it.statuses, { + @MultiORMManyToOne(() => OrganizationTeam, (it) => it.statuses, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -103,6 +104,6 @@ export class TaskStatus extends TenantOrganizationBaseEntity implements ITaskSta @IsUUID() @RelationId((it: TaskStatus) => it.organizationTeam) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationTeamId?: IOrganizationTeam['id']; } diff --git a/packages/core/src/tasks/task-status-priority-size.service.ts b/packages/core/src/tasks/task-status-priority-size.service.ts index 7f7c989927f..c8ac98ad99f 100644 --- a/packages/core/src/tasks/task-status-priority-size.service.ts +++ b/packages/core/src/tasks/task-status-priority-size.service.ts @@ -94,7 +94,7 @@ export class TaskStatusPrioritySizeService< /** * Find at least one record or get global records */ - const checkQueryBuilder = this.repository.createQueryBuilder(this.alias); + const checkQueryBuilder = this.repository.createQueryBuilder(this.tableName); checkQueryBuilder.where((qb: SelectQueryBuilder) => { // Apply filters based on request parameters @@ -112,8 +112,7 @@ export class TaskStatusPrioritySizeService< /** * Find task sizes/priorities for given params */ - const queryBuilder = this.repository.createQueryBuilder(this.alias); - + const queryBuilder = this.repository.createQueryBuilder(this.tableName); queryBuilder.where((qb: SelectQueryBuilder) => { // Apply filters based on request parameters this.getFilterQuery(qb, params); @@ -134,7 +133,7 @@ export class TaskStatusPrioritySizeService< * @returns */ async getDefaultEntities(): Promise> { - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.where((qb: SelectQueryBuilder) => { qb.andWhere( new Brackets((bck: WhereExpressionBuilder) => { diff --git a/packages/core/src/tasks/task.entity.ts b/packages/core/src/tasks/task.entity.ts index 840ad6e2c29..b61c09e0676 100644 --- a/packages/core/src/tasks/task.entity.ts +++ b/packages/core/src/tasks/task.entity.ts @@ -1,11 +1,7 @@ import { - Column, Index, JoinColumn, JoinTable, - ManyToMany, - ManyToOne, - OneToMany, RelationId, } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; @@ -56,31 +52,32 @@ import { TimeLog, User, } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmTaskRepository } from './repository/mikro-orm-task.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('task', { mikroOrmRepository: () => MikroOrmTaskRepository }) @Index('taskNumber', ['projectId', 'number'], { unique: true }) export class Task extends TenantOrganizationBaseEntity implements ITask { - @Column({ + @MultiORMColumn({ nullable: true, ...(isMySQL() ? { type: 'bigint' } : {}) }) number?: number; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) prefix?: string; @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() - @Column() + @MultiORMColumn() title: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ + @MultiORMColumn({ nullable: true, ...(isMySQL() ? { type: 'text' } : {}) }) @@ -90,40 +87,40 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @IsNotEmpty() @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) status?: TaskStatusEnum; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) priority?: TaskPriorityEnum; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) size?: TaskSizeEnum; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) issueType?: string; @ApiPropertyOptional({ type: () => Number }) @IsOptional() @IsNumber() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) estimate?: number; @ApiPropertyOptional({ type: () => Date }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) dueDate?: Date; /** @@ -132,23 +129,23 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() - @Column({ nullable: true, default: true }) + @MultiORMColumn({ nullable: true, default: true }) public?: boolean; @ApiPropertyOptional({ type: () => Date }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) startDate?: Date; @ApiPropertyOptional({ type: () => Date }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) resolvedAt?: Date; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) version?: string; /** @@ -167,7 +164,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @ApiPropertyOptional({ type: () => Task }) @IsOptional() @IsObject() - @ManyToOne(() => Task, (task) => task.children, { + @MultiORMManyToOne(() => Task, (task) => task.children, { onDelete: 'SET NULL', }) parent?: Task; @@ -176,7 +173,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsUUID() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) parentId?: Task['id']; /** @@ -185,7 +182,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @ApiPropertyOptional({ type: () => Object }) @IsOptional() @IsObject() - @ManyToOne(() => OrganizationProject, (it) => it.tasks, { + @MultiORMManyToOne(() => OrganizationProject, (it) => it.tasks, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -199,13 +196,13 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @IsUUID() @RelationId((it: Task) => it.project) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; /** * Creator */ - @ManyToOne(() => User, { + @MultiORMManyToOne(() => User, { nullable: true, onDelete: 'CASCADE', }) @@ -214,7 +211,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @RelationId((it: Task) => it.creator) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) creatorId?: IUser['id']; /** @@ -223,7 +220,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @ApiPropertyOptional({ type: () => Object }) @IsOptional() @IsObject() - @ManyToOne(() => OrganizationSprint, { onDelete: 'SET NULL' }) + @MultiORMManyToOne(() => OrganizationSprint, { onDelete: 'SET NULL' }) @JoinColumn() organizationSprint?: IOrganizationSprint; @@ -232,7 +229,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @IsUUID() @RelationId((it: Task) => it.organizationSprint) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationSprintId?: IOrganizationSprint['id']; /** @@ -241,7 +238,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @ApiPropertyOptional({ type: () => Object }) @IsOptional() @IsObject() - @ManyToOne(() => TaskStatus, { + @MultiORMManyToOne(() => TaskStatus, { onDelete: 'SET NULL', }) @JoinColumn() @@ -252,7 +249,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @IsUUID() @RelationId((it: Task) => it.taskStatus) @Index() - @Column({ nullable: true, type: 'varchar' }) + @MultiORMColumn({ nullable: true, type: 'varchar', relationId: true }) taskStatusId?: ITaskStatus['id']; /** @@ -261,7 +258,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @ApiPropertyOptional({ type: () => Object }) @IsOptional() @IsObject() - @ManyToOne(() => TaskSize, { + @MultiORMManyToOne(() => TaskSize, { onDelete: 'SET NULL', }) @JoinColumn() @@ -272,7 +269,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @IsUUID() @RelationId((it: Task) => it.taskSize) @Index() - @Column({ nullable: true, type: 'varchar' }) + @MultiORMColumn({ nullable: true, type: 'varchar', relationId: true }) taskSizeId?: ITaskSize['id']; /** @@ -281,7 +278,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @ApiPropertyOptional({ type: () => Object }) @IsOptional() @IsObject() - @ManyToOne(() => TaskPriority, { + @MultiORMManyToOne(() => TaskPriority, { onDelete: 'SET NULL', }) @JoinColumn() @@ -292,7 +289,7 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @IsUUID() @RelationId((it: Task) => it.taskPriority) @Index() - @Column({ nullable: true, type: 'varchar' }) + @MultiORMColumn({ nullable: true, type: 'varchar', relationId: true }) taskPriorityId?: ITaskPriority['id']; /* @@ -304,46 +301,46 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { /** * Organization Team Employees */ - @OneToMany(() => OrganizationTeamEmployee, (it) => it.activeTask) + @MultiORMOneToMany(() => OrganizationTeamEmployee, (it) => it.activeTask) organizationTeamEmployees?: OrganizationTeamEmployee[]; /** * Estimations */ - @OneToMany(() => TaskEstimation, (it) => it.task) + @MultiORMOneToMany(() => TaskEstimation, (it) => it.task) estimations?: TaskEstimation[]; /** * Children Tasks */ - @OneToMany(() => Task, (task) => task.parent) + @MultiORMOneToMany(() => Task, (task) => task.parent) children?: Task[]; /** * InvoiceItem */ - @OneToMany(() => InvoiceItem, (invoiceItem) => invoiceItem.task) + @MultiORMOneToMany(() => InvoiceItem, (invoiceItem) => invoiceItem.task) @JoinColumn() invoiceItems?: IInvoiceItem[]; /** * TimeLog */ - @OneToMany(() => TimeLog, (it) => it.task) + @MultiORMOneToMany(() => TimeLog, (it) => it.task) @JoinColumn() timeLogs?: ITimeLog[]; /** * Activity */ - @OneToMany(() => Activity, (activity) => activity.task) + @MultiORMOneToMany(() => Activity, (activity) => activity.task) @JoinColumn() activities?: IActivity[]; /** * Linked Task Issues */ - @OneToMany(() => TaskLinkedIssue, (it) => it.taskTo) + @MultiORMOneToMany(() => TaskLinkedIssue, (it) => it.taskTo) @JoinColumn() linkedIssues?: TaskLinkedIssue[]; @@ -359,9 +356,11 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @ApiPropertyOptional({ type: () => Array, isArray: true }) @IsOptional() @IsArray() - @ManyToMany(() => Tag, (tag) => tag.tasks, { + @MultiORMManyToMany(() => Tag, (tag) => tag.tasks, { onUpdate: 'CASCADE', onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_task' }) @JoinTable({ name: 'tag_task', @@ -374,9 +373,11 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @ApiPropertyOptional({ type: () => Array, isArray: true }) @IsOptional() @IsArray() - @ManyToMany(() => Employee, (employee) => employee.tasks, { + @MultiORMManyToMany(() => Employee, (employee) => employee.tasks, { onUpdate: 'CASCADE', onDelete: 'CASCADE', + owner: true, + pivotTable: 'task_employee' }) @JoinTable({ name: 'task_employee', @@ -389,9 +390,11 @@ export class Task extends TenantOrganizationBaseEntity implements ITask { @ApiPropertyOptional({ type: () => Array, isArray: true }) @IsOptional() @IsArray() - @ManyToMany(() => OrganizationTeam, (team) => team.tasks, { + @MultiORMManyToMany(() => OrganizationTeam, (team) => team.tasks, { onUpdate: 'CASCADE', onDelete: 'CASCADE', + owner: true, + pivotTable: 'task_team' }) @JoinTable({ name: 'task_team', diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index e22210445cc..e7c9aab5110 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -90,7 +90,9 @@ export class TaskService extends TenantAwareCrudService { const { organizationId, projectId, members } = where; const likeOperator = isPostgres() ? 'ILIKE' : 'LIKE'; - const query = this.repository.createQueryBuilder(this.alias); + + const query = this.createQueryBuilder(); + query.innerJoin(`${query.alias}.members`, 'members'); /** * If find options @@ -157,6 +159,8 @@ export class TaskService extends TenantAwareCrudService { } }) ); + + console.log('query.getSql', query.getSql()) const [items, total] = await query.getManyAndCount(); return { items, total }; } catch (error) { @@ -173,7 +177,7 @@ export class TaskService extends TenantAwareCrudService { */ async getAllTasksByEmployee(employeeId: IEmployee['id'], options: PaginationParams) { try { - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.leftJoin(`${query.alias}.members`, 'members'); query.leftJoin(`${query.alias}.teams`, 'teams'); /** @@ -238,7 +242,7 @@ export class TaskService extends TenantAwareCrudService { const { organizationId, projectId, members } = where; const likeOperator = isPostgres() ? 'ILIKE' : 'LIKE'; - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.leftJoin(`${query.alias}.teams`, 'teams'); /** @@ -372,7 +376,7 @@ export class TaskService extends TenantAwareCrudService { const tenantId = RequestContext.currentTenantId() || options.tenantId; const { organizationId, projectId } = options; - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); // Build the query to get the maximum task number query.select(p(`COALESCE(MAX("${query.alias}"."number"), 0)`), 'maxTaskNumber'); @@ -413,7 +417,7 @@ export class TaskService extends TenantAwareCrudService { */ public async unassignEmployeeFromTeamTasks(employeeId: string, organizationTeamId: string) { try { - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.leftJoinAndSelect(`${query.alias}.members`, 'members'); if (organizationTeamId) { query.leftJoinAndSelect(`${query.alias}.teams`, 'teams', 'teams.id = :organizationTeamId', { diff --git a/packages/core/src/tasks/versions/version.entity.ts b/packages/core/src/tasks/versions/version.entity.ts index fe5d6d46d3d..b50c48207fc 100644 --- a/packages/core/src/tasks/versions/version.entity.ts +++ b/packages/core/src/tasks/versions/version.entity.ts @@ -1,10 +1,11 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, ManyToOne, RelationId } from 'typeorm'; +import { Index, RelationId } from 'typeorm'; import { IOrganizationProject, IOrganizationTeam, ITaskVersion } from '@gauzy/contracts'; import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { OrganizationProject, OrganizationTeam, TenantOrganizationBaseEntity } from '../../core/entities/internal'; -import { MultiORMEntity } from '../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from '../../core/decorators/entity'; import { MikroOrmTaskVersionRepository } from './repository/mikro-orm-task-version.repository'; +import { MultiORMManyToOne } from '../../core/decorators/entity/relations'; @MultiORMEntity('task_version', { mikroOrmRepository: () => MikroOrmTaskVersionRepository }) export class TaskVersion extends TenantOrganizationBaseEntity implements ITaskVersion { @@ -13,31 +14,31 @@ export class TaskVersion extends TenantOrganizationBaseEntity implements ITaskVe @IsNotEmpty() @IsString() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() value: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) icon?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) color?: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) - @Column({ default: false, update: false }) + @MultiORMColumn({ default: false, update: false }) isSystem?: boolean; fullIconUrl?: string; @@ -50,7 +51,7 @@ export class TaskVersion extends TenantOrganizationBaseEntity implements ITaskVe /** * Organization Project Relationship */ - @ManyToOne(() => OrganizationProject, (it) => it.versions, { + @MultiORMManyToOne(() => OrganizationProject, (it) => it.versions, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -67,13 +68,13 @@ export class TaskVersion extends TenantOrganizationBaseEntity implements ITaskVe @IsUUID() @RelationId((it: TaskVersion) => it.project) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; /** * Organization Team Relationship */ - @ManyToOne(() => OrganizationTeam, (it) => it.versions, { + @MultiORMManyToOne(() => OrganizationTeam, (it) => it.versions, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -90,6 +91,6 @@ export class TaskVersion extends TenantOrganizationBaseEntity implements ITaskVe @IsUUID() @RelationId((it: TaskVersion) => it.organizationTeam) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationTeamId?: IOrganizationTeam['id']; } diff --git a/packages/core/src/tenant/tenant-setting/tenant-setting.entity.ts b/packages/core/src/tenant/tenant-setting/tenant-setting.entity.ts index c9f2ac93ea0..fc182e8c4fe 100644 --- a/packages/core/src/tenant/tenant-setting/tenant-setting.entity.ts +++ b/packages/core/src/tenant/tenant-setting/tenant-setting.entity.ts @@ -1,18 +1,17 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Column } from 'typeorm'; import { ITenant } from '@gauzy/contracts'; import { TenantBaseEntity } from '../../core/entities/internal'; -import { MultiORMEntity } from '../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from '../../core/decorators/entity'; import { MikroOrmTenantSettingRepository } from './repository/mikro-orm-tenant-setting.repository'; @MultiORMEntity('tenant_setting', { mikroOrmRepository: () => MikroOrmTenantSettingRepository }) export class TenantSetting extends TenantBaseEntity implements ITenant { @ApiProperty({ type: () => String }) - @Column({ nullable: false }) + @MultiORMColumn({ nullable: false }) name?: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) value?: string; } diff --git a/packages/core/src/tenant/tenant.entity.ts b/packages/core/src/tenant/tenant.entity.ts index bfb7e9c6306..c18fb8ce337 100644 --- a/packages/core/src/tenant/tenant.entity.ts +++ b/packages/core/src/tenant/tenant.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Column, Index, OneToMany, JoinColumn, ManyToOne, RelationId } from 'typeorm'; +import { Index, JoinColumn, RelationId } from 'typeorm'; import { IsOptional, IsUUID } from 'class-validator'; import { ITenant, @@ -17,19 +17,20 @@ import { Organization, RolePermission } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmTenantRepository } from './repository/mikro-orm-tenant.repository'; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('tenant', { mikroOrmRepository: () => MikroOrmTenantRepository }) export class Tenant extends BaseEntity implements ITenant { @ApiProperty({ type: () => String }) @Index() - @Column() + @MultiORMColumn() name?: string; @ApiPropertyOptional({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) logo?: string; /* @@ -41,7 +42,7 @@ export class Tenant extends BaseEntity implements ITenant { /** * ImageAsset */ - @ManyToOne(() => ImageAsset, { + @MultiORMManyToOne(() => ImageAsset, { /** Database cascade action on delete. */ onDelete: 'SET NULL', @@ -56,7 +57,7 @@ export class Tenant extends BaseEntity implements ITenant { @IsUUID() @RelationId((it: Tenant) => it.image) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) imageId?: IImageAsset['id']; /* @@ -65,26 +66,29 @@ export class Tenant extends BaseEntity implements ITenant { |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Organization }) - @OneToMany(() => Organization, (organization) => organization.tenant, { + @MultiORMOneToMany(() => Organization, (it) => it.tenant, { cascade: true }) @JoinColumn() organizations?: IOrganization[]; @ApiProperty({ type: () => RolePermission }) - @OneToMany(() => RolePermission, (rolePermission) => rolePermission.tenant, { + @MultiORMOneToMany(() => RolePermission, (it) => it.tenant, { cascade: true }) rolePermissions?: IRolePermission[]; + /** + * Array of feature organizations associated with the entity. + */ @ApiProperty({ type: () => FeatureOrganization }) - @OneToMany(() => FeatureOrganization, (featureOrganization) => featureOrganization.tenant, { - cascade: true + @MultiORMOneToMany(() => FeatureOrganization, (it) => it.tenant, { + cascade: true, }) featureOrganizations?: IFeatureOrganization[]; @ApiProperty({ type: () => ImportRecord }) - @OneToMany(() => ImportRecord, (importRecord) => importRecord.tenant, { + @MultiORMOneToMany(() => ImportRecord, (importRecord) => importRecord.tenant, { cascade: true }) importRecords?: IImportRecord[]; diff --git a/packages/core/src/tenant/tenant.subscriber.ts b/packages/core/src/tenant/tenant.subscriber.ts index 7ddc2024095..6636a73a195 100644 --- a/packages/core/src/tenant/tenant.subscriber.ts +++ b/packages/core/src/tenant/tenant.subscriber.ts @@ -1,9 +1,10 @@ -import { EntitySubscriberInterface, EventSubscriber, InsertEvent, LoadEvent, UpdateEvent } from "typeorm"; +import { EventSubscriber } from "typeorm"; +import { getTenantLogo } from "../core/utils"; import { Tenant } from "./tenant.entity"; -import { getTenantLogo } from "./../core/utils"; +import { BaseEntityEventSubscriber } from "../core/entities/subscribers/base-entity-event.subscriber"; @EventSubscriber() -export class TenantSubscriber implements EntitySubscriberInterface { +export class TenantSubscriber extends BaseEntityEventSubscriber { /** * Indicates that this subscriber only listen to Organization events. @@ -13,57 +14,62 @@ export class TenantSubscriber implements EntitySubscriberInterface { } /** - * Called after entity is loaded from the database. + * Executes after a Tenant entity is loaded. It updates the entity's logo + * using `updateTenantLogo`. Errors during this process are logged. * - * @param entity - * @param event + * @param entity The loaded Tenant entity. + * @returns {Promise} */ - afterLoad(entity: Tenant, event?: LoadEvent): void | Promise { + async afterEntityLoad(entity: Tenant): Promise { try { - if (!entity.logo) { - entity.logo = getTenantLogo(entity.name); - } - if (!!entity['image']) { - entity.logo = entity.image.fullUrl || entity.logo; - } + await this.updateTenantLogo(entity); } catch (error) { - console.log(error); + console.error('Error in afterEntityLoad:', error); } } /** - * Called before entity is inserted to the database. + * Invoked before creating a new Tenant entity. It sets or updates the logo + * through `updateTenantLogo`. Errors are logged for troubleshooting. * - * @param event + * @param entity The Tenant entity to be created. */ - beforeInsert(event: InsertEvent): void | Promise { + async beforeEntityCreate(entity: Tenant) { try { - if (event) { - const { entity } = event; - if (!entity.logo) { - entity.logo = getTenantLogo(entity.name); - } - } + await this.updateTenantLogo(entity); + } catch (error) { + console.error('Error in beforeEntityCreate:', error); + } + } + + /** + * + * @param entity + */ + async beforeEntityUpdate(entity: Tenant): Promise { + try { + await this.updateTenantLogo(entity); } catch (error) { - console.log(error); + console.error('Error in beforeEntityUpdate:', error); } } /** - * Called before entity is updated in the database. + * Updates the logo for a Tenant entity. * - * @param event + * @param entity - The Tenant entity for which the logo is to be updated. + * @returns A promise that resolves when the logo update is complete. */ - beforeUpdate(event: UpdateEvent): void | Promise { + async updateTenantLogo(entity: Tenant): Promise { try { - if (event) { - const { entity } = event; - if (!entity.logo) { - entity.logo = getTenantLogo(entity.name); - } + if (!entity.logo) { + entity.logo = getTenantLogo(entity.name); + } + if (!!entity['image']) { + entity.logo = entity.image.fullUrl || entity.logo; } } catch (error) { - console.log(error); + console.error('Error in updating tenant logo:', error); } } } diff --git a/packages/core/src/time-off-policy/time-off-policy.entity.ts b/packages/core/src/time-off-policy/time-off-policy.entity.ts index a0330ab5ec7..b9e2ac346c5 100644 --- a/packages/core/src/time-off-policy/time-off-policy.entity.ts +++ b/packages/core/src/time-off-policy/time-off-policy.entity.ts @@ -1,4 +1,4 @@ -import { Index, Column, ManyToMany, OneToMany } from 'typeorm'; +import { Index } from 'typeorm'; import { IEmployee, ITimeOff as ITimeOffRequest, @@ -11,8 +11,9 @@ import { TenantOrganizationBaseEntity, TimeOffRequest } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmTimeOffPolicyRepository } from './repository/mikro-orm-time-off-policy.repository'; +import { MultiORMManyToMany, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('time_off_policy', { mikroOrmRepository: () => MikroOrmTimeOffPolicyRepository }) export class TimeOffPolicy extends TenantOrganizationBaseEntity implements ITimeOffPolicy { @@ -21,24 +22,24 @@ export class TimeOffPolicy extends TenantOrganizationBaseEntity implements ITime @IsString() @IsNotEmpty() @Index() - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => Boolean }) @IsBoolean() - @Column() + @MultiORMColumn() requiresApproval: boolean; @ApiProperty({ type: () => Boolean }) @IsBoolean() - @Column() + @MultiORMColumn() paid: boolean; /** * TimeOffRequest */ @ApiPropertyOptional({ type: () => TimeOffRequest, isArray: true }) - @OneToMany(() => TimeOffRequest, (it) => it.policy, { + @MultiORMOneToMany(() => TimeOffRequest, (it) => it.policy, { onDelete: 'SET NULL' }) timeOffRequests?: ITimeOffRequest[]; @@ -49,7 +50,7 @@ export class TimeOffPolicy extends TenantOrganizationBaseEntity implements ITime |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Employee }) - @ManyToMany(() => Employee, (employee) => employee.timeOffPolicies, { + @MultiORMManyToMany(() => Employee, (employee) => employee.timeOffPolicies, { onUpdate: 'CASCADE', onDelete: 'CASCADE' }) diff --git a/packages/core/src/time-off-request/time-off-request.entity.ts b/packages/core/src/time-off-request/time-off-request.entity.ts index 15dc261b58c..fff66a58151 100644 --- a/packages/core/src/time-off-request/time-off-request.entity.ts +++ b/packages/core/src/time-off-request/time-off-request.entity.ts @@ -1,8 +1,5 @@ import { - Column, JoinColumn, - ManyToMany, - ManyToOne, RelationId, Index } from 'typeorm'; @@ -28,8 +25,9 @@ import { TenantOrganizationBaseEntity, TimeOffPolicy } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmTimeOffRequestRepository } from './repository/mikro-orm-time-off-request.repository'; +import { MultiORMManyToMany, MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('time_off_request', { mikroOrmRepository: () => MikroOrmTimeOffRequestRepository }) export class TimeOffRequest extends TenantOrganizationBaseEntity implements ITimeOffRequest { @@ -37,39 +35,39 @@ export class TimeOffRequest extends TenantOrganizationBaseEntity implements ITim @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) documentUrl?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiProperty({ type: () => Date }) @IsDate() - @Column() + @MultiORMColumn() start: Date; @ApiProperty({ type: () => Date }) @IsDate() - @Column() + @MultiORMColumn() end: Date; @ApiProperty({ type: () => Date }) @IsDate() - @Column() + @MultiORMColumn() requestDate: Date; @ApiProperty({ type: () => String, enum: StatusTypesEnum }) @IsEnum(StatusTypesEnum) - @Column() + @MultiORMColumn() status?: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) @IsOptional() @IsBoolean() - @Column({ nullable: true, default: false }) + @MultiORMColumn({ nullable: true, default: false }) isHoliday?: boolean; @@ -80,7 +78,7 @@ export class TimeOffRequest extends TenantOrganizationBaseEntity implements ITim */ // TimeOff Policy @ApiProperty({ type: () => TimeOffPolicy }) - @ManyToOne(() => TimeOffPolicy, (policy) => policy.timeOffRequests, { + @MultiORMManyToOne(() => TimeOffPolicy, (policy) => policy.timeOffRequests, { onDelete: 'CASCADE' }) @JoinColumn() @@ -90,13 +88,13 @@ export class TimeOffRequest extends TenantOrganizationBaseEntity implements ITim @IsUUID() @RelationId((it: TimeOffRequest) => it.policy) @Index() - @Column() + @MultiORMColumn({ relationId: true }) policyId?: string; /** * Document Asset */ - @ManyToOne(() => ImageAsset, { + @MultiORMManyToOne(() => ImageAsset, { /** Database cascade action on delete. */ onDelete: 'SET NULL', @@ -111,7 +109,7 @@ export class TimeOffRequest extends TenantOrganizationBaseEntity implements ITim @IsUUID() @RelationId((it: TimeOffRequest) => it.document) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) documentId?: IDocumentAsset['id']; /* @@ -120,7 +118,7 @@ export class TimeOffRequest extends TenantOrganizationBaseEntity implements ITim |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Employee }) - @ManyToMany(() => Employee, (employee) => employee.timeOffRequests, { + @MultiORMManyToMany(() => Employee, (employee) => employee.timeOffRequests, { onUpdate: 'CASCADE', onDelete: 'CASCADE' }) diff --git a/packages/core/src/time-off-request/time-off-request.service.ts b/packages/core/src/time-off-request/time-off-request.service.ts index e450d111faa..acea3141336 100644 --- a/packages/core/src/time-off-request/time-off-request.service.ts +++ b/packages/core/src/time-off-request/time-off-request.service.ts @@ -147,7 +147,7 @@ export class TimeOffRequestService extends TenantAwareCrudService MikroOrmActivityRepository }) export class Activity extends TenantOrganizationBaseEntity implements IActivity { @@ -29,13 +30,13 @@ export class Activity extends TenantOrganizationBaseEntity implements IActivity @IsOptional() @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) title: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ + @MultiORMColumn({ nullable: true, ...(isMySQL() ? { type: 'longtext' } : {}) }) @@ -46,49 +47,49 @@ export class Activity extends TenantOrganizationBaseEntity implements IActivity }) @IsOptional() @IsString() - @Column({ - type: isSqlite() || isBetterSqlite3() ? 'text' : 'json', - nullable: true + @MultiORMColumn({ + nullable: true, + type: isSqlite() || isBetterSqlite3() ? 'text' : 'json' }) metaData?: string | IURLMetaData; @ApiProperty({ type: () => 'date' }) @IsString() @Index() - @Column({ type: 'date', nullable: true }) + @MultiORMColumn({ type: 'date', nullable: true }) date: string; @ApiProperty({ type: () => 'time' }) @IsString() @Index() - @Column({ type: 'time', nullable: true }) + @MultiORMColumn({ type: 'time', nullable: true }) time: string; @ApiPropertyOptional({ type: () => Number, default: 0 }) @IsOptional() @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) duration?: number; @ApiPropertyOptional({ type: () => String, enum: ActivityType }) @IsOptional() @IsEnum(ActivityType) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) type?: string; @ApiPropertyOptional({ type: () => String, enum: TimeLogSourceEnum, default: TimeLogSourceEnum.WEB_TIMER }) @IsOptional() @IsEnum(TimeLogSourceEnum) @Index() - @Column({ default: TimeLogSourceEnum.WEB_TIMER }) + @MultiORMColumn({ default: TimeLogSourceEnum.WEB_TIMER }) source?: string; @ApiPropertyOptional({ type: () => 'timestamptz' }) @IsOptional() @IsDateString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) recordedAt?: Date; /* @@ -99,11 +100,9 @@ export class Activity extends TenantOrganizationBaseEntity implements IActivity /** * Employee */ - @ManyToOne(() => Employee, { - /** Indicates if the relation column value can be nullable or not. */ + @ApiProperty({ type: () => Employee }) + @MultiORMManyToOne(() => Employee, { nullable: false, - - /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) @JoinColumn() @@ -116,13 +115,14 @@ export class Activity extends TenantOrganizationBaseEntity implements IActivity @IsUUID() @RelationId((it: Activity) => it.employee) @Index() - @Column() + @MultiORMColumn({ relationId: true }) employeeId?: IEmployee['id']; /** * Organization Project Relationship */ - @ManyToOne(() => OrganizationProject, (it) => it.activities, { + @ApiProperty({ type: () => OrganizationProject }) + @MultiORMManyToOne(() => OrganizationProject, (it) => it.activities, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -140,13 +140,13 @@ export class Activity extends TenantOrganizationBaseEntity implements IActivity @IsUUID() @Index() @RelationId((it: Activity) => it.project) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; /** * Time Slot Activity */ - @ManyToOne(() => TimeSlot, (it) => it.activities, { + @MultiORMManyToOne(() => TimeSlot, (it) => it.activities, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -160,13 +160,13 @@ export class Activity extends TenantOrganizationBaseEntity implements IActivity @IsUUID() @Index() @RelationId((it: Activity) => it.timeSlot) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) timeSlotId?: ITimeSlot['id']; /** * Task Activity */ - @ManyToOne(() => Task, (it) => it.activities, { + @MultiORMManyToOne(() => Task, (it) => it.activities, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -181,6 +181,6 @@ export class Activity extends TenantOrganizationBaseEntity implements IActivity @IsUUID() @Index() @RelationId((it: Activity) => it.task) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) taskId?: ITask['id']; } diff --git a/packages/core/src/time-tracking/screenshot/screenshot.entity.ts b/packages/core/src/time-tracking/screenshot/screenshot.entity.ts index ef0cf961455..b844146a71a 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.entity.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.entity.ts @@ -1,32 +1,33 @@ -import { Column, RelationId, ManyToOne, Index, JoinColumn } from 'typeorm'; +import { RelationId, Index, JoinColumn } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsString, IsOptional, IsDateString, IsUUID, IsNotEmpty, IsEnum, IsBoolean } from 'class-validator'; import { Exclude } from 'class-transformer'; import { FileStorageProviderEnum, IScreenshot, ITimeSlot, IUser } from '@gauzy/contracts'; import { isBetterSqlite3, isSqlite } from '@gauzy/config'; -import { MultiORMEntity } from '../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from '../../core/decorators/entity'; import { TenantOrganizationBaseEntity, TimeSlot, User } from './../../core/entities/internal'; import { MikroOrmScreenshotRepository } from './repository/mikro-orm-screenshot.repository'; +import { MultiORMManyToOne } from '../../core/decorators/entity/relations'; @MultiORMEntity('screenshot', { mikroOrmRepository: () => MikroOrmScreenshotRepository }) export class Screenshot extends TenantOrganizationBaseEntity implements IScreenshot { @ApiProperty({ type: () => String }) @IsNotEmpty() @IsString() - @Column() + @MultiORMColumn() file: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) thumb?: string; @ApiPropertyOptional({ type: () => 'timestamptz' }) @IsOptional() @IsDateString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) recordedAt?: Date; @ApiPropertyOptional({ type: () => String, enum: FileStorageProviderEnum }) @@ -34,7 +35,7 @@ export class Screenshot extends TenantOrganizationBaseEntity implements IScreens @IsEnum(FileStorageProviderEnum) @Exclude({ toPlainOnly: true }) @Index() - @Column({ + @MultiORMColumn({ type: 'simple-enum', nullable: true, enum: FileStorageProviderEnum @@ -57,7 +58,7 @@ export class Screenshot extends TenantOrganizationBaseEntity implements IScreens @IsOptional() @IsBoolean() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) isWorkRelated?: boolean; /** @@ -70,7 +71,7 @@ export class Screenshot extends TenantOrganizationBaseEntity implements IScreens @IsOptional() @IsString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; /** @@ -82,7 +83,7 @@ export class Screenshot extends TenantOrganizationBaseEntity implements IScreens }) @IsOptional() @IsString() - @Column({ + @MultiORMColumn({ nullable: true, type: isSqlite() || isBetterSqlite3() ? 'text' : 'json' }) @@ -100,7 +101,7 @@ export class Screenshot extends TenantOrganizationBaseEntity implements IScreens /** * TimeSlot */ - @ManyToOne(() => TimeSlot, (it) => it.screenshots, { + @MultiORMManyToOne(() => TimeSlot, (it) => it.screenshots, { /** Indicates if relation column value can be nullable or not. */ nullable: true, @@ -115,13 +116,13 @@ export class Screenshot extends TenantOrganizationBaseEntity implements IScreens @IsUUID() @RelationId((it: Screenshot) => it.timeSlot) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) timeSlotId?: ITimeSlot['id']; /** * User */ - @ManyToOne(() => User, { + @MultiORMManyToOne(() => User, { /** Indicates if relation column value can be nullable or not. */ nullable: true, @@ -136,6 +137,6 @@ export class Screenshot extends TenantOrganizationBaseEntity implements IScreens @IsUUID() @RelationId((it: Screenshot) => it.user) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) userId?: IUser['id']; } diff --git a/packages/core/src/time-tracking/screenshot/screenshot.service.ts b/packages/core/src/time-tracking/screenshot/screenshot.service.ts index 541ecbb3cef..87d223505dc 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.service.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.service.ts @@ -35,7 +35,7 @@ export class ScreenshotService extends TenantAwareCrudService { async deleteScreenshot(id: IScreenshot['id'], options?: FindOptionsWhere): Promise { try { const tenantId = RequestContext.currentTenantId(); - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); query.setFindOptions({ where: { ...(options ? options : {}), diff --git a/packages/core/src/time-tracking/time-log/time-log.entity.ts b/packages/core/src/time-tracking/time-log/time-log.entity.ts index 087c9d50736..ce18511513a 100644 --- a/packages/core/src/time-tracking/time-log/time-log.entity.ts +++ b/packages/core/src/time-tracking/time-log/time-log.entity.ts @@ -1,4 +1,4 @@ -import { Column, RelationId, ManyToOne, JoinColumn, ManyToMany, Index, AfterLoad } from 'typeorm'; +import { RelationId, JoinColumn, Index, AfterLoad } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsBoolean, IsDateString, IsEnum, IsOptional, IsString, IsUUID } from 'class-validator'; import * as moment from 'moment'; @@ -25,8 +25,9 @@ import { Timesheet, TimeSlot } from './../../core/entities/internal'; -import { MultiORMEntity } from '../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from '../../core/decorators/entity'; import { MikroOrmTimeLogRepository } from './repository/mikro-orm-time-log.repository'; +import { MultiORMManyToMany, MultiORMManyToOne } from '../../core/decorators/entity/relations'; @MultiORMEntity('time_log', { mikroOrmRepository: () => MikroOrmTimeLogRepository }) export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { @@ -34,40 +35,40 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { @ApiProperty({ type: () => 'timestamptz' }) @IsDateString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) startedAt?: Date; @ApiProperty({ type: () => 'timestamptz' }) @IsDateString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) stoppedAt?: Date; /** * Edited timestamp column */ - @Column({ type: 'timestamp' }) + @MultiORMColumn({ type: 'timestamp' }) @IsDateString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) editedAt?: Date; @ApiProperty({ type: () => String, enum: TimeLogType, default: TimeLogType.TRACKED }) @IsEnum(TimeLogType) @Index() - @Column({ default: TimeLogType.TRACKED }) + @MultiORMColumn({ default: TimeLogType.TRACKED }) logType: TimeLogType; @ApiProperty({ type: () => String, enum: TimeLogSourceEnum, default: TimeLogSourceEnum.WEB_TIMER }) @IsEnum(TimeLogSourceEnum) @Index() - @Column({ default: TimeLogSourceEnum.WEB_TIMER }) + @MultiORMColumn({ default: TimeLogSourceEnum.WEB_TIMER }) source?: TimeLogSourceEnum; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ + @MultiORMColumn({ nullable: true, ...(isMySQL() ? { type: 'longtext' } : {}) }) @@ -76,28 +77,28 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) reason?: string; @ApiPropertyOptional({ type: () => Boolean, default: false }) @IsOptional() @IsBoolean() @Index() - @Column({ default: false }) + @MultiORMColumn({ default: false }) isBillable: boolean; @ApiPropertyOptional({ type: () => Boolean }) @IsOptional() @IsBoolean() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) isRunning?: boolean; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() @Index() - @Column({ update: false, nullable: true }) + @MultiORMColumn({ update: false, nullable: true }) version?: string; /** Additional fields */ @@ -119,7 +120,7 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { /** * Employee relationship */ - @ManyToOne(() => Employee, (it) => it.timeLogs, { + @MultiORMManyToOne(() => Employee, (it) => it.timeLogs, { /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -130,13 +131,13 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { @IsUUID() @RelationId((it: TimeLog) => it.employee) @Index() - @Column() + @MultiORMColumn({ relationId: true }) employeeId: IEmployee['id']; /** * Timesheet relationship */ - @ManyToOne(() => Timesheet, { + @MultiORMManyToOne(() => Timesheet, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -151,13 +152,13 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { @IsUUID() @RelationId((it: TimeLog) => it.timesheet) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) timesheetId?: ITimesheet['id']; /** * Organization Project Relationship */ - @ManyToOne(() => OrganizationProject, (it) => it.timeLogs, { + @MultiORMManyToOne(() => OrganizationProject, (it) => it.timeLogs, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -175,13 +176,13 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { @IsUUID() @RelationId((it: TimeLog) => it.project) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) projectId?: IOrganizationProject['id']; /** * Task */ - @ManyToOne(() => Task, (it) => it.timeLogs, { + @MultiORMManyToOne(() => Task, (it) => it.timeLogs, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -196,13 +197,13 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { @IsUUID() @RelationId((it: TimeLog) => it.task) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) taskId?: ITask['id']; /** * OrganizationContact */ - @ManyToOne(() => OrganizationContact, (it) => it.timeLogs, { + @MultiORMManyToOne(() => OrganizationContact, (it) => it.timeLogs, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -217,13 +218,13 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { @IsUUID() @RelationId((it: TimeLog) => it.organizationContact) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationContactId?: IOrganizationContact['id']; /** * Organization Team */ - @ManyToOne(() => OrganizationTeam, { + @MultiORMManyToOne(() => OrganizationTeam, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, @@ -238,7 +239,7 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { @IsUUID() @RelationId((it: TimeLog) => it.organizationTeam) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) organizationTeamId?: IOrganizationTeam['id']; /* @@ -251,7 +252,7 @@ export class TimeLog extends TenantOrganizationBaseEntity implements ITimeLog { * TimeSlot */ @ApiProperty({ type: () => TimeSlot, isArray: true }) - @ManyToMany(() => TimeSlot, (timeLogs) => timeLogs.timeLogs, { + @MultiORMManyToMany(() => TimeSlot, (timeLogs) => timeLogs.timeLogs, { /** Database cascade action on update. */ onUpdate: 'CASCADE', /** Database cascade action on delete. */ diff --git a/packages/core/src/time-tracking/time-log/time-log.service.ts b/packages/core/src/time-tracking/time-log/time-log.service.ts index 19d21528932..ca6187d8f7e 100644 --- a/packages/core/src/time-tracking/time-log/time-log.service.ts +++ b/packages/core/src/time-tracking/time-log/time-log.service.ts @@ -88,7 +88,7 @@ export class TimeLogService extends TenantAwareCrudService { */ async getTimeLogs(request: IGetTimeLogReportInput): Promise { // Create a query builder for the TimeLog entity - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); // Inner join with related entities (employee, timeSlots) query.innerJoin(`${query.alias}.employee`, 'employee'); @@ -611,7 +611,7 @@ export class TimeLogService extends TenantAwareCrudService { } // Create a query builder for the TimeLog entity - const query = this.repository.createQueryBuilder(this.alias); + const query = this.repository.createQueryBuilder(this.tableName); // Inner join with related entities (employee, timeSlots) query.innerJoin(`${query.alias}.employee`, 'employee'); diff --git a/packages/core/src/time-tracking/time-slot/time-slot-minute.entity.ts b/packages/core/src/time-tracking/time-slot/time-slot-minute.entity.ts index 5811029b860..8d0ef4bd6de 100644 --- a/packages/core/src/time-tracking/time-slot/time-slot-minute.entity.ts +++ b/packages/core/src/time-tracking/time-slot/time-slot-minute.entity.ts @@ -1,7 +1,5 @@ import { - Column, RelationId, - ManyToOne, Unique, Index, JoinColumn @@ -10,9 +8,10 @@ import { ITimeSlot, ITimeSlotMinute } from '@gauzy/contracts'; import { ApiProperty } from '@nestjs/swagger'; import { IsNumber, IsDateString, IsString } from 'class-validator'; import { TenantOrganizationBaseEntity } from './../../core/entities/internal'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../../core/decorators/entity'; import { TimeSlot } from './time-slot.entity'; import { MikroOrmTimeSlotMinuteRepository } from './repository/mikro-orm-time-slot-minute.repository'; +import { MultiORMManyToOne } from '../../core/decorators/entity/relations'; @MultiORMEntity('time_slot_minute', { mikroOrmRepository: () => MikroOrmTimeSlotMinuteRepository }) @Unique(['timeSlotId', 'datetime']) @@ -20,17 +19,17 @@ export class TimeSlotMinute extends TenantOrganizationBaseEntity implements ITim @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) keyboard?: number; @ApiProperty({ type: () => Number }) @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) mouse?: number; @ApiProperty({ type: () => 'timestamptz' }) @IsDateString() - @Column() + @MultiORMColumn() datetime?: Date; /* @@ -40,7 +39,7 @@ export class TimeSlotMinute extends TenantOrganizationBaseEntity implements ITim */ @ApiProperty({ type: () => TimeSlot }) - @ManyToOne(() => TimeSlot, (it) => it.timeSlotMinutes, { + @MultiORMManyToOne(() => TimeSlot, (it) => it.timeSlotMinutes, { onDelete: 'CASCADE' }) @JoinColumn() @@ -50,6 +49,6 @@ export class TimeSlotMinute extends TenantOrganizationBaseEntity implements ITim @RelationId((it: TimeSlotMinute) => it.timeSlot) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) readonly timeSlotId?: string; } diff --git a/packages/core/src/time-tracking/time-slot/time-slot.entity.ts b/packages/core/src/time-tracking/time-slot/time-slot.entity.ts index ecf9e11afa5..cf73d8e0b66 100644 --- a/packages/core/src/time-tracking/time-slot/time-slot.entity.ts +++ b/packages/core/src/time-tracking/time-slot/time-slot.entity.ts @@ -1,9 +1,5 @@ import { - Column, RelationId, - ManyToOne, - OneToMany, - ManyToMany, JoinTable, Index } from 'typeorm'; @@ -25,8 +21,9 @@ import { TimeLog } from './../../core/entities/internal'; import { TimeSlotMinute } from './time-slot-minute.entity'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../../core/decorators/entity'; import { MikroOrmTimeSlotRepository } from './repository/mikro-orm-time-slot.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany } from '../../core/decorators/entity/relations'; @MultiORMEntity('time_slot', { mikroOrmRepository: () => MikroOrmTimeSlotRepository }) export class TimeSlot extends TenantOrganizationBaseEntity @@ -36,33 +33,33 @@ export class TimeSlot extends TenantOrganizationBaseEntity @IsOptional() @IsNumber() @Index() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) duration?: number; @ApiPropertyOptional({ type: () => Number, default: 0 }) @IsOptional() @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) keyboard?: number; @ApiPropertyOptional({ type: () => Number, default: 0 }) @IsOptional() @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) mouse?: number; @ApiPropertyOptional({ type: () => Number, default: 0 }) @IsOptional() @IsNumber() @Index() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) overall?: number; @ApiProperty({ type: () => 'timestamptz' }) @IsNotEmpty() @IsDateString() @Index() - @Column() + @MultiORMColumn() startedAt: Date; /** Additional fields */ @@ -79,7 +76,7 @@ export class TimeSlot extends TenantOrganizationBaseEntity /** * Employee */ - @ManyToOne(() => Employee, (it) => it.timeSlots, { + @MultiORMManyToOne(() => Employee, (it) => it.timeSlots, { onDelete: 'CASCADE' }) employee?: IEmployee; @@ -89,7 +86,7 @@ export class TimeSlot extends TenantOrganizationBaseEntity @IsUUID() @RelationId((it: TimeSlot) => it.employee) @Index() - @Column() + @MultiORMColumn({ relationId: true }) employeeId: IEmployee['id']; /* @@ -101,13 +98,13 @@ export class TimeSlot extends TenantOrganizationBaseEntity /** * Screenshot */ - @OneToMany(() => Screenshot, (it) => it.timeSlot) + @MultiORMOneToMany(() => Screenshot, (it) => it.timeSlot) screenshots?: IScreenshot[]; /** * Activity */ - @OneToMany(() => Activity, (it) => it.timeSlot, { + @MultiORMOneToMany(() => Activity, (it) => it.timeSlot, { cascade: true }) activities?: IActivity[]; @@ -115,7 +112,7 @@ export class TimeSlot extends TenantOrganizationBaseEntity /** * TimeSlotMinute */ - @OneToMany(() => TimeSlotMinute, (it) => it.timeSlot, { + @MultiORMOneToMany(() => TimeSlotMinute, (it) => it.timeSlot, { cascade: true }) timeSlotMinutes?: ITimeSlotMinute[]; @@ -129,9 +126,11 @@ export class TimeSlot extends TenantOrganizationBaseEntity /** * TimeLog */ - @ManyToMany(() => TimeLog, (it) => it.timeSlots, { + @MultiORMManyToMany(() => TimeLog, (it) => it.timeSlots, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'time_slot_time_logs' }) @JoinTable({ name: 'time_slot_time_logs' diff --git a/packages/core/src/time-tracking/timesheet/timesheet.entity.ts b/packages/core/src/time-tracking/timesheet/timesheet.entity.ts index fd189fad68f..2c8edd1a805 100644 --- a/packages/core/src/time-tracking/timesheet/timesheet.entity.ts +++ b/packages/core/src/time-tracking/timesheet/timesheet.entity.ts @@ -1,7 +1,5 @@ import { - Column, RelationId, - ManyToOne, JoinColumn, Index, AfterLoad @@ -14,8 +12,9 @@ import { TenantOrganizationBaseEntity, User } from './../../core/entities/internal'; -import { MultiORMEntity } from './../../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../../core/decorators/entity'; import { MikroOrmTimesheetRepository } from './repository/mikro-orm-timesheet.repository'; +import { MultiORMManyToOne } from 'core/decorators/entity/relations'; @MultiORMEntity('timesheet', { mikroOrmRepository: () => MikroOrmTimesheetRepository }) export class Timesheet extends TenantOrganizationBaseEntity implements ITimesheet { @@ -23,81 +22,81 @@ export class Timesheet extends TenantOrganizationBaseEntity implements ITimeshee @ApiPropertyOptional({ type: () => Number, default: 0 }) @IsOptional() @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) duration?: number; @ApiPropertyOptional({ type: () => Number, default: 0 }) @IsOptional() @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) keyboard?: number; @ApiPropertyOptional({ type: () => Number, default: 0 }) @IsOptional() @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) mouse?: number; @ApiPropertyOptional({ type: () => Number, default: 0 }) @IsOptional() @IsNumber() - @Column({ default: 0 }) + @MultiORMColumn({ default: 0 }) overall?: number; @ApiProperty({ type: () => 'timestamptz' }) @IsDateString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) startedAt?: Date; @ApiProperty({ type: () => 'timestamptz' }) @IsDateString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) stoppedAt?: Date; @ApiPropertyOptional({ type: () => 'timestamptz' }) @IsOptional() @IsDateString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) approvedAt?: Date; @ApiPropertyOptional({ type: () => 'timestamptz' }) @IsOptional() @IsDateString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) submittedAt?: Date; @ApiPropertyOptional({ type: () => 'timestamptz' }) @IsOptional() @IsDateString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) lockedAt?: Date; /** * Edited timestamp column */ - @Column({ type: 'timestamp' }) + @MultiORMColumn({ type: 'timestamp' }) @IsDateString() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) editedAt?: Date; @ApiPropertyOptional({ type: () => Boolean, default: false }) @IsOptional() @IsBoolean() @Index() - @Column({ default: false }) + @MultiORMColumn({ default: false }) isBilled?: boolean; @ApiPropertyOptional({ type: () => String, enum: TimesheetStatus, default: TimesheetStatus.PENDING }) @IsOptional() @IsEnum(TimesheetStatus) @Index() - @Column({ default: TimesheetStatus.PENDING }) + @MultiORMColumn({ default: TimesheetStatus.PENDING }) status: string; /** Additional fields */ @@ -118,7 +117,7 @@ export class Timesheet extends TenantOrganizationBaseEntity implements ITimeshee /** * Employee */ - @ManyToOne(() => Employee, (it) => it.timesheets, { + @MultiORMManyToOne(() => Employee, (it) => it.timesheets, { /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @@ -129,13 +128,13 @@ export class Timesheet extends TenantOrganizationBaseEntity implements ITimeshee @IsUUID() @RelationId((it: Timesheet) => it.employee) @Index() - @Column() + @MultiORMColumn({ relationId: true }) employeeId?: IEmployee['id']; /** * Approve By User */ - @ManyToOne(() => User, { + @MultiORMManyToOne(() => User, { /** Indicates if the relation column value can be nullable or not. */ nullable: true, }) @@ -147,7 +146,7 @@ export class Timesheet extends TenantOrganizationBaseEntity implements ITimeshee @IsUUID() @RelationId((it: Timesheet) => it.approvedBy) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) approvedById?: IUser['id']; /** diff --git a/packages/core/src/user-organization/user-organization.entity.ts b/packages/core/src/user-organization/user-organization.entity.ts index 103eb2fe1d4..c882b739ce0 100644 --- a/packages/core/src/user-organization/user-organization.entity.ts +++ b/packages/core/src/user-organization/user-organization.entity.ts @@ -1,23 +1,22 @@ import { Index, - Column, JoinColumn, - RelationId, - ManyToOne + RelationId } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IUser, IUserOrganization } from '@gauzy/contracts'; import { IsUUID } from 'class-validator'; import { TenantOrganizationBaseEntity, User } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmUserOrganizationRepository } from './repository/mikro-orm-user-organization.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('user_organization', { mikroOrmRepository: () => MikroOrmUserOrganizationRepository }) export class UserOrganization extends TenantOrganizationBaseEntity implements IUserOrganization { @ApiProperty({ type: () => Boolean, default: true }) @Index() - @Column({ default: true }) + @MultiORMColumn({ default: true }) isDefault: boolean; @@ -30,7 +29,7 @@ export class UserOrganization extends TenantOrganizationBaseEntity implements IU /** * User */ - @ManyToOne(() => User, (it) => it.organizations, { + @MultiORMManyToOne(() => User, (it) => it.organizations, { onDelete: 'CASCADE' }) @JoinColumn() @@ -40,6 +39,6 @@ export class UserOrganization extends TenantOrganizationBaseEntity implements IU @RelationId((it: UserOrganization) => it.user) @IsUUID() @Index() - @Column() + @MultiORMColumn({ relationId: true }) userId: IUser['id']; } diff --git a/packages/core/src/user/user.entity.ts b/packages/core/src/user/user.entity.ts index 01591fd72b5..38d4f0ea6f7 100644 --- a/packages/core/src/user/user.entity.ts +++ b/packages/core/src/user/user.entity.ts @@ -4,17 +4,11 @@ import { ApiPropertyOptional } from '@nestjs/swagger'; import { - Column, Index, JoinColumn, - ManyToOne, RelationId, - ManyToMany, - JoinTable, - OneToOne, - OneToMany + JoinTable } from 'typeorm'; -import { Property } from '@mikro-orm/core'; import { Exclude } from 'class-transformer'; import { IsEmail, IsEnum, IsOptional, IsString, IsUUID } from 'class-validator'; import { @@ -41,10 +35,9 @@ import { TenantBaseEntity, UserOrganization } from '../core/entities/internal'; -import { MultiORMEntity } from './../core/decorators/entity'; -import { MikroManyToOne } from './../core/decorators/entity/relations/mikro-orm'; -import { TypeManyToOne } from './../core/decorators/entity/relations/type-orm'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmUserRepository } from './repository/mikro-orm-user.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany, MultiORMOneToOne } from './../core/decorators/entity/relations'; @MultiORMEntity('user', { mikroOrmRepository: () => MikroOrmUserRepository }) export class User extends TenantBaseEntity implements IUser { @@ -53,97 +46,85 @@ export class User extends TenantBaseEntity implements IUser { @IsOptional() @IsString() @Index() - @Column({ nullable: true }) - @Property({ nullable: true }) + @MultiORMColumn({ nullable: true }) thirdPartyId?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() @Index() - @Column({ nullable: true }) - @Property({ nullable: true }) + @MultiORMColumn({ nullable: true }) firstName?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() @Index() - @Column({ nullable: true }) - @Property({ nullable: true }) + @MultiORMColumn({ nullable: true }) lastName?: string; @ApiPropertyOptional({ type: () => String, minLength: 3, maxLength: 100 }) @IsOptional() @IsEmail() - @Index({ unique: false }) - @Column({ nullable: true }) - @Property({ nullable: true }) + @Index() + @MultiORMColumn({ nullable: true }) email?: string; @ApiPropertyOptional({ type: () => String, minLength: 4, maxLength: 12 }) @IsOptional() @IsString() @Index() - @Column({ nullable: true }) - @Property({ nullable: true }) + @MultiORMColumn({ nullable: true }) phoneNumber?: string; @ApiPropertyOptional({ type: () => String, minLength: 3, maxLength: 20 }) @IsOptional() @IsString() @Index({ unique: false }) - @Column({ nullable: true }) - @Property({ nullable: true }) + @MultiORMColumn({ nullable: true }) username?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() - @Column({ nullable: true }) - @Property({ nullable: true }) + @MultiORMColumn({ nullable: true }) timeZone?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() @Exclude({ toPlainOnly: true }) - @Column({ nullable: true }) - @Property({ nullable: true }) + @MultiORMColumn({ nullable: true }) hash?: string; @ApiPropertyOptional({ type: () => String }) @IsOptional() @IsString() @Exclude({ toPlainOnly: true }) - @Column({ insert: false, nullable: true }) - @Property({ nullable: true }) - public refreshToken?: string; + @MultiORMColumn({ insert: false, nullable: true }) + refreshToken?: string; @ApiPropertyOptional({ type: () => String, maxLength: 500 }) @IsOptional() @IsString() - @Column({ length: 500, nullable: true }) - @Property({ nullable: true }) + @MultiORMColumn({ length: 500, nullable: true }) imageUrl?: string; @ApiPropertyOptional({ type: () => String, enum: LanguagesEnum }) @IsOptional() @IsEnum(LanguagesEnum) - @Column({ nullable: true, default: LanguagesEnum.ENGLISH }) - @Property({ nullable: true }) + @MultiORMColumn({ nullable: true, default: LanguagesEnum.ENGLISH }) preferredLanguage?: string; @ApiPropertyOptional({ type: () => String, enum: ComponentLayoutStyleEnum }) @IsOptional() @IsEnum(ComponentLayoutStyleEnum) - @Column({ + @MultiORMColumn({ type: 'simple-enum', nullable: true, default: ComponentLayoutStyleEnum.TABLE, enum: ComponentLayoutStyleEnum }) - @Property({ nullable: true }) preferredComponentLayout?: ComponentLayoutStyleEnum; @@ -151,30 +132,26 @@ export class User extends TenantBaseEntity implements IUser { @IsOptional() @IsString() @Exclude({ toPlainOnly: true }) - @Column({ insert: false, nullable: true }) - @Property({ default: false, nullable: true }) - public code?: string; + @MultiORMColumn({ insert: false, nullable: true }) + code?: string; @ApiPropertyOptional({ type: () => Date }) @IsOptional() @Exclude({ toPlainOnly: true }) - @Column({ insert: false, nullable: true }) - @Property({ default: false, nullable: true }) - public codeExpireAt?: Date; + @MultiORMColumn({ insert: false, nullable: true }) + codeExpireAt?: Date; @ApiPropertyOptional({ type: () => Date }) @IsOptional() @Exclude({ toPlainOnly: true }) - @Column({ insert: false, nullable: true }) - @Property({ default: false, nullable: true }) - public emailVerifiedAt?: Date; + @MultiORMColumn({ insert: false, nullable: true }) + emailVerifiedAt?: Date; @ApiPropertyOptional({ type: () => String }) @IsOptional() @Exclude({ toPlainOnly: true }) - @Column({ insert: false, nullable: true }) - @Property({ default: false, nullable: true }) - public emailToken?: string; + @MultiORMColumn({ insert: false, nullable: true }) + emailToken?: string; name?: string; employeeId?: string; @@ -189,19 +166,12 @@ export class User extends TenantBaseEntity implements IUser { /** * Role */ - @MikroManyToOne(() => Role, { + @MultiORMManyToOne(() => Role, { /** Indicates if relation column value can be nullable or not. */ nullable: true, /** Database cascade action on delete. */ - deleteRule: 'set null' - }) - @TypeManyToOne(() => Role, { - /** Indicates if relation column value can be nullable or not. */ - nullable: true, - - /** Database cascade action on delete. */ - onDelete: 'SET NULL', + onDelete: 'SET NULL' }) @JoinColumn() role?: IRole; @@ -211,14 +181,13 @@ export class User extends TenantBaseEntity implements IUser { @IsUUID() @RelationId((it: User) => it.role) @Index() - @Column({ nullable: true }) - @Property({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) roleId?: string; /** * ImageAsset */ - @ManyToOne(() => ImageAsset, { + @MultiORMManyToOne(() => ImageAsset, { /** Database cascade action on delete. */ onDelete: 'SET NULL', @@ -233,8 +202,7 @@ export class User extends TenantBaseEntity implements IUser { @IsUUID() @RelationId((it: User) => it.image) @Index() - @Column({ nullable: true }) - @Property({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) imageId?: IImageAsset['id']; /* @@ -246,13 +214,13 @@ export class User extends TenantBaseEntity implements IUser { /** * Employee */ - @OneToOne(() => Employee, (employee: Employee) => employee.user) + @MultiORMOneToOne(() => Employee, (it: Employee) => it.user) employee?: IEmployee; /** * Candidate */ - @OneToOne(() => Candidate, (candidate: Candidate) => candidate.user) + @MultiORMOneToOne(() => Candidate, (candidate: Candidate) => candidate.user) candidate?: ICandidate; /* @@ -261,9 +229,11 @@ export class User extends TenantBaseEntity implements IUser { |-------------------------------------------------------------------------- */ // Tags - @ManyToMany(() => Tag, (tag) => tag.users, { + @MultiORMManyToMany(() => Tag, (tag) => tag.users, { onUpdate: 'CASCADE', - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true, + pivotTable: 'tag_user' }) @JoinTable({ name: 'tag_user' @@ -279,7 +249,7 @@ export class User extends TenantBaseEntity implements IUser { /** * UserOrganization */ - @OneToMany(() => UserOrganization, (it) => it.user, { + @MultiORMOneToMany(() => UserOrganization, (it) => it.user, { cascade: true }) @JoinColumn() @@ -288,13 +258,13 @@ export class User extends TenantBaseEntity implements IUser { /** * User belongs to invites */ - @OneToMany(() => Invite, (it) => it.user) + @MultiORMOneToMany(() => Invite, (it) => it.user) invites?: IInvite[]; /** * User belongs to teams */ - @OneToMany(() => OrganizationTeam, (it) => it.createdBy, { + @MultiORMOneToMany(() => OrganizationTeam, (it) => it.createdBy, { onDelete: 'CASCADE' }) teams?: IOrganizationTeam[]; diff --git a/packages/core/src/user/user.subscriber.ts b/packages/core/src/user/user.subscriber.ts index f6cebc58c75..9a183e0ed4e 100644 --- a/packages/core/src/user/user.subscriber.ts +++ b/packages/core/src/user/user.subscriber.ts @@ -13,40 +13,47 @@ export class UserSubscriber implements EntitySubscriberInterface { } /** - * Called before entity is inserted to the database. + * Called before the entity is inserted into the database. * - * @param event + * @param event - The insert event. */ - beforeInsert(event: InsertEvent): void | Promise { + async beforeInsert(event: InsertEvent): Promise { try { const entity = event.entity; - if (!entity.imageUrl) { - entity.imageUrl = getUserDummyImage(entity); - } + + // Set a default imageUrl using a dummy image if not provided + entity.imageUrl = entity.imageUrl || getUserDummyImage(entity); } catch (error) { - console.log(error); + console.error('Error in UserSubscriber beforeInsert hook:', error); } } /** - * Called after entity is loaded from the database. + * Called after the entity is loaded from the database. * - * @param entity - * @param event + * @param entity - The loaded entity. + * @param event - The load event. */ - afterLoad(entity: User, event?: LoadEvent): void | Promise { + async afterLoad(entity: User, event?: LoadEvent): Promise { try { + // Combine first name and last name into a single "name" property entity.name = [entity.firstName, entity.lastName].filter(Boolean).join(' '); - entity.employeeId = entity.employee ? entity.employee.id : null; + // Set employeeId based on the existence of the "employee" property + entity.employeeId = entity.employee?.id || null; + + // Set isEmailVerified based on the existence of "emailVerifiedAt" property if ('emailVerifiedAt' in entity) { entity.isEmailVerified = !!entity.emailVerifiedAt; } - if (!!entity['image']) { - entity.imageUrl = entity.image.fullUrl || entity.imageUrl; + + // Set imageUrl based on the existence of the "image" property + if (entity['image']) { + // Fall back to the existing imageUrl property if fullUrl is not available + entity.imageUrl = entity['image'].fullUrl || entity.imageUrl; } } catch (error) { - console.log('Error while retrieve user subscriber', error); + console.error('Error in UserSubscriber afterLoad hook:', error); } } } diff --git a/packages/core/src/warehouse/repository/type-orm-warehouse.repository.ts b/packages/core/src/warehouse/repository/type-orm-warehouse.repository.ts index c367f0190ff..258458509c6 100644 --- a/packages/core/src/warehouse/repository/type-orm-warehouse.repository.ts +++ b/packages/core/src/warehouse/repository/type-orm-warehouse.repository.ts @@ -1,4 +1,12 @@ +import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Warehouse } from '../warehouse.entity'; -export class TypeOrmWarehouseRepository extends Repository { } +export class TypeOrmWarehouseRepository extends Repository { + + constructor( + @InjectRepository(Warehouse) readonly repository: Repository + ) { + super(repository.target, repository.manager, repository.queryRunner); + } +} diff --git a/packages/core/src/warehouse/warehouse-product-variant.entity.ts b/packages/core/src/warehouse/warehouse-product-variant.entity.ts index 2cb7674062b..48a206d1b5b 100644 --- a/packages/core/src/warehouse/warehouse-product-variant.entity.ts +++ b/packages/core/src/warehouse/warehouse-product-variant.entity.ts @@ -1,19 +1,20 @@ -import { ManyToOne, JoinColumn, Column, RelationId, Index } from 'typeorm'; +import { JoinColumn, RelationId, Index } from 'typeorm'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsString } from 'class-validator'; import { IProductVariant, IWarehouseProduct, IWarehouseProductVariant } from '@gauzy/contracts'; import { ProductVariant, TenantOrganizationBaseEntity } from '../core/entities/internal'; import { WarehouseProduct } from './warehouse-product.entity'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmWarehouseProductVariantRepository } from './repository/mikro-orm-warehouse-product-variant.repository'; +import { MultiORMManyToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('warehouse_product_variant', { mikroOrmRepository: () => MikroOrmWarehouseProductVariantRepository }) export class WarehouseProductVariant extends TenantOrganizationBaseEntity implements IWarehouseProductVariant { @ApiPropertyOptional({ type: Number }) - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', default: 0, @@ -31,7 +32,7 @@ export class WarehouseProductVariant extends TenantOrganizationBaseEntity * ProductVariant */ @ApiProperty({ type: () => ProductVariant }) - @ManyToOne(() => ProductVariant, (productVariant) => productVariant.warehouseProductVariants, { + @MultiORMManyToOne(() => ProductVariant, (productVariant) => productVariant.warehouseProductVariants, { onDelete: 'CASCADE' }) @JoinColumn() @@ -41,13 +42,13 @@ export class WarehouseProductVariant extends TenantOrganizationBaseEntity @RelationId((it: WarehouseProductVariant) => it.variant) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) variantId: string; /** * WarehouseProduct */ - @ManyToOne(() => WarehouseProduct, (warehouseProduct) => warehouseProduct.variants, { + @MultiORMManyToOne(() => WarehouseProduct, (warehouseProduct) => warehouseProduct.variants, { onDelete: 'CASCADE' }) warehouseProduct: IWarehouseProduct; @@ -56,6 +57,6 @@ export class WarehouseProductVariant extends TenantOrganizationBaseEntity @RelationId((it: WarehouseProductVariant) => it.warehouseProduct) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) warehouseProductId: string; } diff --git a/packages/core/src/warehouse/warehouse-product.entity.ts b/packages/core/src/warehouse/warehouse-product.entity.ts index 35b5ef80642..ddcf1668be6 100644 --- a/packages/core/src/warehouse/warehouse-product.entity.ts +++ b/packages/core/src/warehouse/warehouse-product.entity.ts @@ -1,8 +1,5 @@ import { - ManyToOne, JoinColumn, - Column, - OneToMany, RelationId, Index } from 'typeorm'; @@ -20,15 +17,16 @@ import { TenantOrganizationBaseEntity } from '../core/entities/internal'; import { ColumnNumericTransformerPipe } from './../shared/pipes'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmWarehouseProductRepository } from './repository/mikro-orm-warehouse-product.repository '; +import { MultiORMManyToOne, MultiORMOneToMany } from '../core/decorators/entity/relations'; @MultiORMEntity('warehouse_product', { mikroOrmRepository: () => MikroOrmWarehouseProductRepository }) export class WarehouseProduct extends TenantOrganizationBaseEntity implements IWarehouseProduct { @ApiPropertyOptional({ type: Number }) - @Column({ + @MultiORMColumn({ nullable: true, type: 'numeric', default: 0, @@ -46,7 +44,7 @@ export class WarehouseProduct extends TenantOrganizationBaseEntity * Warehouse */ @ApiProperty({ type: () => Warehouse }) - @ManyToOne(() => Warehouse, (warehouse) => warehouse.products, { + @MultiORMManyToOne(() => Warehouse, (warehouse) => warehouse.products, { onDelete: 'CASCADE' }) @JoinColumn() @@ -55,14 +53,14 @@ export class WarehouseProduct extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: WarehouseProduct) => it.warehouse) @Index() - @Column() + @MultiORMColumn({ relationId: true }) warehouseId: string; /** * Product */ @ApiProperty({ type: () => Product }) - @ManyToOne(() => Product, (product) => product.warehouses, { + @MultiORMManyToOne(() => Product, (product) => product.warehouses, { onDelete: 'CASCADE' }) @JoinColumn() @@ -71,7 +69,7 @@ export class WarehouseProduct extends TenantOrganizationBaseEntity @ApiProperty({ type: () => String }) @RelationId((it: WarehouseProduct) => it.product) @Index() - @Column() + @MultiORMColumn({ relationId: true }) productId: string; /* @@ -80,7 +78,7 @@ export class WarehouseProduct extends TenantOrganizationBaseEntity |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => WarehouseProductVariant, isArray: true }) - @OneToMany(() => WarehouseProductVariant, (warehouseProductVariant) => warehouseProductVariant.warehouseProduct, { + @MultiORMOneToMany(() => WarehouseProductVariant, (warehouseProductVariant) => warehouseProductVariant.warehouseProduct, { cascade: true }) @JoinColumn() diff --git a/packages/core/src/warehouse/warehouse.controller.ts b/packages/core/src/warehouse/warehouse.controller.ts index d17589cc97b..c6c54b8e0f6 100644 --- a/packages/core/src/warehouse/warehouse.controller.ts +++ b/packages/core/src/warehouse/warehouse.controller.ts @@ -88,8 +88,7 @@ export class WarehouseController extends CrudController { }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: - 'Invalid input, The response body may contain clues as to what went wrong' + description: 'Invalid input, The response body may contain clues as to what went wrong' }) @Post('inventory/:warehouseId') async addWarehouseProducts( @@ -239,9 +238,7 @@ export class WarehouseController extends CrudController { @Param('id', UUIDValidationPipe) id: IWarehouse['id'], @Query() query: RelationsQueryDTO ): Promise { - return await this.warehouseService.findOneByIdString(id, { - relations: query.relations - }); + return await this.warehouseService.findById(id, query.relations); } /** @@ -279,8 +276,7 @@ export class WarehouseController extends CrudController { }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, - description: - 'Invalid input, The response body may contain clues as to what went wrong' + description: 'Invalid input, The response body may contain clues as to what went wrong' }) @HttpCode(HttpStatus.ACCEPTED) @Put(':id') @@ -290,7 +286,10 @@ export class WarehouseController extends CrudController { @Body() entity: UpdateWarehouseDTO ): Promise { try { - return await this.warehouseService.update(id, entity); + return await this.warehouseService.create({ + ...entity, + id + }); } catch (error) { throw new BadRequestException(error); } diff --git a/packages/core/src/warehouse/warehouse.entity.ts b/packages/core/src/warehouse/warehouse.entity.ts index 88e83083365..b8ad54994ae 100644 --- a/packages/core/src/warehouse/warehouse.entity.ts +++ b/packages/core/src/warehouse/warehouse.entity.ts @@ -1,13 +1,8 @@ import { - Column, - ManyToOne, JoinColumn, - ManyToMany, JoinTable, - OneToOne, Index, - RelationId, - OneToMany + RelationId } from 'typeorm'; import { ApiProperty, @@ -29,30 +24,31 @@ import { Merchant } from '../core/entities/internal'; import { WarehouseProduct } from './warehouse-product.entity'; -import { MultiORMEntity } from './../core/decorators/entity'; +import { MultiORMColumn, MultiORMEntity } from './../core/decorators/entity'; import { MikroOrmWarehouseRepository } from './repository/mikro-orm-warehouse.repository'; +import { MultiORMManyToMany, MultiORMManyToOne, MultiORMOneToMany, MultiORMOneToOne } from '../core/decorators/entity/relations'; @MultiORMEntity('warehouse', { mikroOrmRepository: () => MikroOrmWarehouseRepository }) export class Warehouse extends TenantOrganizationBaseEntity implements IWarehouse { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() code: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() email: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiPropertyOptional({ type: () => Boolean }) - @Column({ default: true }) + @MultiORMColumn({ default: true }) active: boolean; /* @@ -65,7 +61,7 @@ export class Warehouse extends TenantOrganizationBaseEntity implements IWarehous * ImageAsset */ @ApiProperty({ type: () => ImageAsset }) - @ManyToOne(() => ImageAsset, (imageAsset) => imageAsset.warehouses, { + @MultiORMManyToOne(() => ImageAsset, (imageAsset) => imageAsset.warehouses, { onDelete: 'SET NULL' }) @JoinColumn() @@ -74,7 +70,7 @@ export class Warehouse extends TenantOrganizationBaseEntity implements IWarehous @ApiProperty({ type: () => String }) @RelationId((it: Warehouse) => it.logo) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) logoId?: string; /* @@ -87,9 +83,10 @@ export class Warehouse extends TenantOrganizationBaseEntity implements IWarehous * Contact */ @ApiProperty({ type: () => Contact }) - @OneToOne(() => Contact, { + @MultiORMOneToOne(() => Contact, { cascade: true, - onDelete: 'CASCADE' + onDelete: 'CASCADE', + owner: true }) @JoinColumn() contact?: IContact; @@ -97,7 +94,7 @@ export class Warehouse extends TenantOrganizationBaseEntity implements IWarehous @ApiProperty({ type: () => String }) @RelationId((it: Warehouse) => it.contact) @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) contactId?: string; /* @@ -110,7 +107,7 @@ export class Warehouse extends TenantOrganizationBaseEntity implements IWarehous * WarehouseProduct */ @ApiProperty({ type: () => WarehouseProduct, isArray: true }) - @OneToMany(() => WarehouseProduct, (warehouseProduct) => warehouseProduct.warehouse, { + @MultiORMOneToMany(() => WarehouseProduct, (warehouseProduct) => warehouseProduct.warehouse, { cascade: true }) @JoinColumn() @@ -123,14 +120,20 @@ export class Warehouse extends TenantOrganizationBaseEntity implements IWarehous */ /** - * Tag + * Warehouse Tags */ - @ApiProperty({ type: () => Tag, isArray: true }) - @ManyToMany(() => Tag, (tag) => tag.warehouses, { + @MultiORMManyToMany(() => Tag, (it) => it.warehouses, { + /** Defines the database cascade action on update. */ onUpdate: 'CASCADE', - onDelete: 'CASCADE' + /** Defines the database cascade action on delete. */ + onDelete: 'CASCADE', + /** Indicates that this entity (Warehouse) is the owner side of the relationship. */ + owner: true, + /** Specifies the name of the pivot table in the database. */ + pivotTable: 'tag_warehouse' }) @JoinTable({ + /** Specifies the name of the pivot table in the database. */ name: 'tag_warehouse' }) tags?: ITag[]; @@ -138,8 +141,8 @@ export class Warehouse extends TenantOrganizationBaseEntity implements IWarehous /** * Merchants */ - @ApiProperty({ type: () => Warehouse, isArray: true }) - @ManyToMany(() => Merchant, (it) => it.warehouses, { + @MultiORMManyToMany(() => Merchant, (it) => it.warehouses, { + /** Defines the database cascade action on delete. */ onDelete: 'CASCADE' }) merchants?: IMerchant[]; diff --git a/packages/core/src/warehouse/warehouse.module.ts b/packages/core/src/warehouse/warehouse.module.ts index 114d3934c9c..c633924d7d8 100644 --- a/packages/core/src/warehouse/warehouse.module.ts +++ b/packages/core/src/warehouse/warehouse.module.ts @@ -9,6 +9,7 @@ import { Product } from './../core/entities/internal'; import { WarehouseProductVariant } from './warehouse-product-variant.entity'; import { WarehouseProduct } from './warehouse-product.entity'; import { WarehouseProductService } from './warehouse-product-service'; +import { TypeOrmWarehouseRepository } from './repository/type-orm-warehouse.repository'; import { RolePermissionModule } from '../role-permission/role-permission.module'; const forFeatureEntities = [ @@ -28,7 +29,7 @@ const forFeatureEntities = [ RolePermissionModule, ], controllers: [WarehouseController], - providers: [WarehouseService, WarehouseProductService], - exports: [WarehouseService, WarehouseProductService] + providers: [WarehouseService, WarehouseProductService, TypeOrmWarehouseRepository], + exports: [WarehouseService, WarehouseProductService, TypeOrmWarehouseRepository] }) export class WarehouseModule { } diff --git a/packages/core/src/warehouse/warehouse.service.ts b/packages/core/src/warehouse/warehouse.service.ts index 671368dd33a..7dda96ac936 100644 --- a/packages/core/src/warehouse/warehouse.service.ts +++ b/packages/core/src/warehouse/warehouse.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +import { IWarehouse } from '@gauzy/contracts'; import { TenantAwareCrudService } from './../core/crud'; import { MikroOrmWarehouseRepository } from './repository/mikro-orm-warehouse.repository'; import { TypeOrmWarehouseRepository } from './repository/type-orm-warehouse.repository'; @@ -8,11 +8,19 @@ import { Warehouse } from './warehouse.entity'; @Injectable() export class WarehouseService extends TenantAwareCrudService { constructor( - @InjectRepository(Warehouse) - typeOrmWarehouseRepository: TypeOrmWarehouseRepository, - - mikroOrmWarehouseRepository: MikroOrmWarehouseRepository, + readonly typeOrmWarehouseRepository: TypeOrmWarehouseRepository, + readonly mikroOrmWarehouseRepository: MikroOrmWarehouseRepository, ) { super(typeOrmWarehouseRepository, mikroOrmWarehouseRepository); } + + /** + * + * @param id + * @param relations + * @returns + */ + async findById(id: IWarehouse['id'], relations: string[] = []): Promise { + return await super.findOneByIdString(id, { relations }); + } } diff --git a/packages/plugins/changelog/src/changelog.entity.ts b/packages/plugins/changelog/src/changelog.entity.ts index e780cdb1443..aa18f365774 100644 --- a/packages/plugins/changelog/src/changelog.entity.ts +++ b/packages/plugins/changelog/src/changelog.entity.ts @@ -1,8 +1,7 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { IsBoolean, IsDate, IsOptional, IsString } from 'class-validator'; -import { Column } from 'typeorm'; import { IChangelog } from '@gauzy/contracts'; -import { MultiORMEntity, TenantOrganizationBaseEntity } from '@gauzy/core'; +import { MultiORMEntity, TenantOrganizationBaseEntity, MultiORMColumn } from '@gauzy/core'; import { MikroOrmChangelogRepository } from './repository/mikro-orm-changelog.repository'; @MultiORMEntity('changelog', { mikroOrmRepository: () => MikroOrmChangelogRepository }) @@ -10,41 +9,41 @@ export class Changelog extends TenantOrganizationBaseEntity implements IChangelo @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) icon?: string; @ApiPropertyOptional({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) title?: string; @ApiProperty({ type: () => Date }) @IsDate() @IsOptional() - @Column() + @MultiORMColumn() date?: Date; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column() + @MultiORMColumn() content?: string; @ApiPropertyOptional({ type: () => Boolean }) @IsBoolean() - @Column({ type: Boolean, nullable: true }) + @MultiORMColumn({ type: Boolean, nullable: true }) isFeature?: boolean; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) learnMoreUrl?: string; @ApiProperty({ type: () => String }) @IsString() @IsOptional() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) imageUrl?: string; } diff --git a/packages/plugins/integration-wakatime/src/lib/wakatime.entity.ts b/packages/plugins/integration-wakatime/src/lib/wakatime.entity.ts index 9fd78dee0cc..f013f629973 100644 --- a/packages/plugins/integration-wakatime/src/lib/wakatime.entity.ts +++ b/packages/plugins/integration-wakatime/src/lib/wakatime.entity.ts @@ -1,5 +1,6 @@ -import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; +import { Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; import { IWakatime } from '@gauzy/contracts'; +import { MultiORMColumn } from '@gauzy/core'; @Entity({ name: 'heartbeats' }) @Unique(['time', 'entities']) // named; multiple fields @@ -7,48 +8,48 @@ export class Wakatime implements IWakatime { @PrimaryGeneratedColumn() id: number; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) user_agent: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) type: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) employeeId: string; - @Column({ type: 'real' }) + @MultiORMColumn({ type: 'real' }) time: number; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) categories: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) dependencies: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) languages: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) machine: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) projects: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) branches: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) operating_systems: string; - @Column() + @MultiORMColumn() entities: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) editors: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) lines: string; - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) is_write: boolean; } diff --git a/packages/plugins/knowledge-base/src/help-center-article/help-center-article.entity.ts b/packages/plugins/knowledge-base/src/help-center-article/help-center-article.entity.ts index c19cc7915ed..bb49d0c3df2 100644 --- a/packages/plugins/knowledge-base/src/help-center-article/help-center-article.entity.ts +++ b/packages/plugins/knowledge-base/src/help-center-article/help-center-article.entity.ts @@ -1,37 +1,38 @@ -import { Column, ManyToOne, RelationId, Index, OneToMany } from 'typeorm'; +import { RelationId, Index } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; import { IHelpCenter, IHelpCenterArticle, IHelpCenterAuthor } from '@gauzy/contracts'; import { MultiORMEntity, TenantOrganizationBaseEntity } from '@gauzy/core'; import { HelpCenter, HelpCenterAuthor } from './../entities'; import { MikroOrmHelpCenterArticleRepository } from './repository/mikro-orm-help-center-article.repository'; +import { MultiORMManyToOne, MultiORMOneToMany, MultiORMColumn } from '@gauzy/core'; @MultiORMEntity('knowledge_base_article', { mikroOrmRepository: () => MikroOrmHelpCenterArticleRepository }) export class HelpCenterArticle extends TenantOrganizationBaseEntity implements IHelpCenterArticle { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) data: string; @ApiProperty({ type: () => Boolean }) - @Column() + @MultiORMColumn() draft: boolean; @ApiProperty({ type: () => Boolean }) - @Column() + @MultiORMColumn() privacy: boolean; @ApiProperty({ type: () => Number }) - @Column() + @MultiORMColumn() index: number; /* @@ -39,7 +40,7 @@ export class HelpCenterArticle extends TenantOrganizationBaseEntity | @ManyToOne |-------------------------------------------------------------------------- */ - @ManyToOne(() => HelpCenter, (center) => center.articles, { + @MultiORMManyToOne(() => HelpCenter, (center) => center.articles, { onDelete: 'CASCADE' }) category?: IHelpCenter; @@ -48,7 +49,7 @@ export class HelpCenterArticle extends TenantOrganizationBaseEntity @RelationId((it: HelpCenterArticle) => it.category) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) categoryId: string; /* @@ -56,7 +57,7 @@ export class HelpCenterArticle extends TenantOrganizationBaseEntity | @OneToMany |-------------------------------------------------------------------------- */ - @OneToMany(() => HelpCenterAuthor, (author) => author.article, { + @MultiORMOneToMany(() => HelpCenterAuthor, (author) => author.article, { cascade: true }) authors?: IHelpCenterAuthor[]; diff --git a/packages/plugins/knowledge-base/src/help-center-author/help-center-author.entity.ts b/packages/plugins/knowledge-base/src/help-center-author/help-center-author.entity.ts index a66dc54a266..7c28e567312 100644 --- a/packages/plugins/knowledge-base/src/help-center-author/help-center-author.entity.ts +++ b/packages/plugins/knowledge-base/src/help-center-author/help-center-author.entity.ts @@ -1,10 +1,11 @@ -import { Column, ManyToOne, RelationId, Index } from 'typeorm'; +import { RelationId, Index } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; import { IEmployee, IHelpCenterArticle, IHelpCenterAuthor } from '@gauzy/contracts'; import { MultiORMEntity, Employee, TenantOrganizationBaseEntity } from '@gauzy/core'; import { HelpCenterArticle } from './../entities'; import { MikroOrmHelpCenterAuthorRepository } from './repository/mikro-orm-help-center-author.repository'; +import { MultiORMManyToOne, MultiORMColumn } from '@gauzy/core'; @MultiORMEntity('knowledge_base_author', { mikroOrmRepository: () => MikroOrmHelpCenterAuthorRepository }) export class HelpCenterAuthor extends TenantOrganizationBaseEntity implements IHelpCenterAuthor { @@ -15,7 +16,7 @@ export class HelpCenterAuthor extends TenantOrganizationBaseEntity implements IH |-------------------------------------------------------------------------- */ @ApiProperty({ type: () => Employee }) - @ManyToOne(() => Employee, { + @MultiORMManyToOne(() => Employee, { onDelete: 'CASCADE' }) employee?: IEmployee; @@ -24,11 +25,11 @@ export class HelpCenterAuthor extends TenantOrganizationBaseEntity implements IH @RelationId((it: HelpCenterAuthor) => it.employee) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) employeeId: string; @ApiProperty({ type: () => HelpCenterArticle }) - @ManyToOne(() => HelpCenterArticle, (article) => article.authors, { + @MultiORMManyToOne(() => HelpCenterArticle, (article) => article.authors, { onDelete: 'CASCADE' }) article?: IHelpCenterArticle; @@ -37,6 +38,6 @@ export class HelpCenterAuthor extends TenantOrganizationBaseEntity implements IH @RelationId((it: HelpCenterAuthor) => it.article) @IsString() @Index() - @Column() + @MultiORMColumn({ relationId: true }) articleId: string; } diff --git a/packages/plugins/knowledge-base/src/help-center/help-center.entity.ts b/packages/plugins/knowledge-base/src/help-center/help-center.entity.ts index d7ed649ac17..737ed7ba6f3 100644 --- a/packages/plugins/knowledge-base/src/help-center/help-center.entity.ts +++ b/packages/plugins/knowledge-base/src/help-center/help-center.entity.ts @@ -1,49 +1,50 @@ -import { Column, ManyToOne, OneToMany, RelationId, Index } from 'typeorm'; +import { RelationId, Index } from 'typeorm'; import { ApiProperty } from '@nestjs/swagger'; import { IsOptional, IsString } from 'class-validator'; import { IHelpCenter, IHelpCenterArticle } from '@gauzy/contracts'; import { MultiORMEntity, TenantOrganizationBaseEntity } from '@gauzy/core'; import { HelpCenterArticle } from './../entities'; import { MikroOrmHelpCenterRepository } from './repository/mikro-orm-help-center.repository'; +import { MultiORMManyToOne, MultiORMOneToMany, MultiORMColumn } from '@gauzy/core'; @MultiORMEntity('knowledge_base', { mikroOrmRepository: () => MikroOrmHelpCenterRepository }) export class HelpCenter extends TenantOrganizationBaseEntity implements IHelpCenter { @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() name: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() flag: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() icon: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() privacy: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() language: string; @ApiProperty({ type: () => String }) - @Column() + @MultiORMColumn() color: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) description?: string; @ApiProperty({ type: () => String }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) data?: string; @ApiProperty({ type: () => Number }) - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true }) index: number; /* @@ -51,7 +52,7 @@ export class HelpCenter extends TenantOrganizationBaseEntity | @ManyToOne |-------------------------------------------------------------------------- */ - @ManyToOne(() => HelpCenter, (children) => children.children, { + @MultiORMManyToOne(() => HelpCenter, (children) => children.children, { onDelete: 'CASCADE' }) parent?: IHelpCenter; @@ -61,7 +62,7 @@ export class HelpCenter extends TenantOrganizationBaseEntity @IsString() @IsOptional() @Index() - @Column({ nullable: true }) + @MultiORMColumn({ nullable: true, relationId: true }) parentId?: string; /* @@ -69,12 +70,12 @@ export class HelpCenter extends TenantOrganizationBaseEntity | @OneToMany |-------------------------------------------------------------------------- */ - @OneToMany(() => HelpCenter, (children) => children.parent, { + @MultiORMOneToMany(() => HelpCenter, (children) => children.parent, { cascade: true }) children?: IHelpCenter[]; - @OneToMany(() => HelpCenterArticle, (article) => article.category, { + @MultiORMOneToMany(() => HelpCenterArticle, (article) => article.category, { cascade: true }) articles?: IHelpCenterArticle[]; diff --git a/packages/plugins/product-reviews/src/entities/product-review.entity.ts b/packages/plugins/product-reviews/src/entities/product-review.entity.ts index e13aa17f151..7ae0ba5d4ee 100644 --- a/packages/plugins/product-reviews/src/entities/product-review.entity.ts +++ b/packages/plugins/product-reviews/src/entities/product-review.entity.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { Column, PrimaryGeneratedColumn } from 'typeorm'; -import { MultiORMEntity } from '@gauzy/core'; +import { PrimaryGeneratedColumn } from 'typeorm'; +import { MultiORMEntity, MultiORMColumn } from '@gauzy/core'; import { MikroOrmProductReviewRepository } from './repository/mikro-orm-product-review.repository'; @MultiORMEntity('product_review', { mikroOrmRepository: () => MikroOrmProductReviewRepository }) @@ -8,9 +8,9 @@ export class ProductReview { @PrimaryGeneratedColumn('uuid') id?: string; - @Column('text') + @MultiORMColumn('text') body: string; - @Column() + @MultiORMColumn() rating: number; } From 2259cb594a3949c3bc12cae4374b143b966c8f7c Mon Sep 17 00:00:00 2001 From: Ruslan K Date: Fri, 8 Mar 2024 15:50:03 +0100 Subject: [PATCH 2/2] Update README.md --- README.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ee201a81165..5a1d38133eb 100644 --- a/README.md +++ b/README.md @@ -161,13 +161,11 @@ More information about our Server & Desktop Apps: ## 🧱 Technology Stack and Requirements -- [TypeScript](https://www.typescriptlang.org) language +- [TypeScript](https://www.typescriptlang.org) - [NodeJs](https://nodejs.org) / [NestJs](https://github.com/nestjs/nest) -- [Nx](https://nx.dev) -- [Angular](https://angular.io) -- [RxJS](http://reactivex.io/rxjs) -- [TypeORM](https://github.com/typeorm/typeorm) -- [Ngx-admin](https://github.com/akveo/ngx-admin) +- [Nx](https://nx.dev) / [Lerna](https://github.com/lerna/lerna) +- [Angular](https://angular.io) / [RxJS](http://reactivex.io/rxjs) / [Ngx-admin](https://github.com/akveo/ngx-admin) +- [TypeORM](https://github.com/typeorm/typeorm) / [MikroORM](https://github.com/mikro-orm/mikro-orm) / [Knex](https://github.com/knex/knex) For Production, we recommend: @@ -232,7 +230,7 @@ Notes: #### Optional / Recommended for Production -- Optionally (recommended for production) install and run [PostgreSQL](https://www.postgresql.org) version 14 or later. Note: other DB can be configured manually in TypeORM. The default DB is set to SQLite (recommended for testing/demo purposes only). +- Optionally (recommended for production) install and run [PostgreSQL](https://www.postgresql.org) version 14 or later (16.x recommended for production). Note: other DB can be configured manually in TypeORM / MikroORM / Knex. The default DB is set to SQLite (recommended for testing/demo purposes only). - Optionally (recommended for production) install and run [Redis](https://github.com/redis/redis). Notes: the platform will work without Redis using an in-memory caching strategy instead of a distributed one (recommended for testing/demo purposes only). Please note however that Redis is required for Jitsu. - Optionally (recommended for production) install and run [ElasticSearch](https://github.com/elastic/elasticsearch). Note: the platform will work without ElasticSearch using DB build-in search capabilities (recommended for testing/demo purposes only). - Optionally install and run [MinIO](https://github.com/minio/minio) or [LocalStack](https://github.com/localstack/localstack). Note: the platform will work without MinIO / LocalStack or other S3-compatible storage using local filesystem-based storage (recommended for testing/demo purposes only). For production, we recommend using Wasabi or AWS S3 storage or another S3-compatible cloud storage. @@ -244,7 +242,7 @@ Notes: - See [Setup Gauzy for Client Server](https://github.com/ever-co/ever-gauzy/wiki/Setup-Gauzy-for-Client-Server) for more information about production setup on your servers. - We recommend deploying to Kubernetes (k8s), either manually (see below) or with our [Ever Helm Charts](https://github.com/ever-co/ever-charts). - For the most simple deployment scenarios (e.g. for yourself or your small organization), check our [DigitalOcean App Platform configurations](https://github.com/ever-co/ever-gauzy/tree/develop/.do) and corresponding [GitHub Action](https://github.com/ever-co/ever-gauzy/blob/develop/.github/workflows/deploy-do-app-platform-stage.yml). -- Another variant to deploy Gauzy is to use DigitalOcean Droplets or any other virtual instance (with Ubuntu OS) and deploy using SCP/SSH, see for example following [GitHub Action](https://github.com/ever-co/ever-gauzy/blob/develop/.github/workflows/deploy-do-droplet-demo.yml) +- Another variant to deploy Gauzy is to use DigitalOcean Droplets or any other virtual instance (with Ubuntu OS) and deploy using SCP/SSH, for example following [GitHub Action](https://github.com/ever-co/ever-gauzy/blob/develop/.github/workflows/deploy-do-droplet-demo.yml) - For more complex deployment scenarios, please see [Kubernetes configurations](https://github.com/ever-co/ever-gauzy/tree/develop/.deploy/k8s), which we are using to deploy Gauzy into [DigitalOcean k8s cluster](https://www.digitalocean.com/products/kubernetes). - In addition, check [Gauzy Pulumi](https://github.com/ever-co/ever-gauzy-pulumi) project (WIP), it makes complex Clouds deployments possible with a single command (`pulumi up`). Note: it currently supports AWS EKS (Kubernetes) for development and production with Application Load Balancers and AWS RDS Serverless PostgreSQL DB deployments. We also implemented deployments to ECS EC2 and Fargate Clusters in the same Pulumi project.