mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 04:10:32 +00:00
add server to monorepo.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
IProjectCreatedEventPayload,
|
||||
IProjectCreateDTO,
|
||||
IProjectCreatePOJO,
|
||||
IProjectCreatingEventPayload,
|
||||
IProjectStatus,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import { ProjectsValidator } from './ProjectsValidator';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export default class CreateProject {
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private validator: ProjectsValidator;
|
||||
|
||||
/**
|
||||
* Creates a new credit note.
|
||||
* @param {IProjectCreateDTO} creditNoteDTO
|
||||
*/
|
||||
public createProject = async (
|
||||
tenantId: number,
|
||||
projectDTO: IProjectCreateDTO
|
||||
): Promise<IProjectCreatePOJO> => {
|
||||
const { Project } = this.tenancy.models(tenantId);
|
||||
|
||||
// Validate customer existance.
|
||||
await this.validator.validateContactExists(tenantId, projectDTO.contactId);
|
||||
|
||||
// Triggers `onProjectCreate` event.
|
||||
await this.eventPublisher.emitAsync(events.project.onCreate, {
|
||||
tenantId,
|
||||
projectDTO,
|
||||
} as IProjectCreatedEventPayload);
|
||||
|
||||
// Creates a new project under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onProjectCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.project.onCreating, {
|
||||
tenantId,
|
||||
projectDTO,
|
||||
trx,
|
||||
} as IProjectCreatingEventPayload);
|
||||
|
||||
// Upsert the project object.
|
||||
const project = await Project.query(trx).upsertGraph({
|
||||
...projectDTO,
|
||||
status: IProjectStatus.InProgress,
|
||||
});
|
||||
// Triggers `onProjectCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.project.onCreated, {
|
||||
tenantId,
|
||||
projectDTO,
|
||||
project,
|
||||
trx,
|
||||
} as IProjectCreatedEventPayload);
|
||||
|
||||
return project;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
IProjectDeletedEventPayload,
|
||||
IProjectDeleteEventPayload,
|
||||
IProjectDeletingEventPayload,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export default class DeleteProject {
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Deletes the give project.
|
||||
* @param {number} projectId -
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteProject = async (tenantId: number, projectId: number) => {
|
||||
const { Project } = this.tenancy.models(tenantId);
|
||||
|
||||
// Triggers `onProjectDelete` event.
|
||||
await this.eventPublisher.emitAsync(events.project.onDelete, {
|
||||
tenantId,
|
||||
projectId,
|
||||
} as IProjectDeleteEventPayload);
|
||||
|
||||
// Validate customer existance.
|
||||
const oldProject = await Project.query().findById(projectId).throwIfNotFound();
|
||||
|
||||
// Deletes the given project under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onProjectDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.project.onDeleting, {
|
||||
tenantId,
|
||||
oldProject,
|
||||
trx,
|
||||
} as IProjectDeletingEventPayload);
|
||||
|
||||
// Deletes the project from the storage.
|
||||
await Project.query(trx).findById(projectId).delete();
|
||||
|
||||
// Triggers `onProjectDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.project.onDeleted, {
|
||||
tenantId,
|
||||
oldProject,
|
||||
trx,
|
||||
} as IProjectDeletedEventPayload);
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
IProjectEditDTO,
|
||||
IProjectEditedEventPayload,
|
||||
IProjectEditEventPayload,
|
||||
IProjectEditingEventPayload,
|
||||
IProjectEditPOJO,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import { ProjectsValidator } from './ProjectsValidator';
|
||||
|
||||
@Service()
|
||||
export default class EditProjectService {
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private projectsValidator: ProjectsValidator;
|
||||
|
||||
/**
|
||||
* Edits a new credit note.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} projectId -
|
||||
* @param {IProjectEditDTO} projectDTO -
|
||||
*/
|
||||
public editProject = async (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
projectDTO: IProjectEditDTO
|
||||
): Promise<IProjectEditPOJO> => {
|
||||
const { Project } = this.tenancy.models(tenantId);
|
||||
|
||||
// Validate customer existance.
|
||||
const oldProject = await Project.query().findById(projectId).throwIfNotFound();
|
||||
|
||||
// Validate the project's contact id existance.
|
||||
if (oldProject.contactId !== projectDTO.contactId) {
|
||||
await this.projectsValidator.validateContactExists(
|
||||
tenantId,
|
||||
projectDTO.contactId
|
||||
);
|
||||
}
|
||||
// Triggers `onProjectEdit` event.
|
||||
await this.eventPublisher.emitAsync(events.project.onEdit, {
|
||||
tenantId,
|
||||
oldProject,
|
||||
projectDTO,
|
||||
} as IProjectEditEventPayload);
|
||||
|
||||
// Edits the given project under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onProjectEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.project.onEditing, {
|
||||
tenantId,
|
||||
projectDTO,
|
||||
oldProject,
|
||||
trx,
|
||||
} as IProjectEditingEventPayload);
|
||||
|
||||
// Upsert the project object.
|
||||
const project = await Project.query(trx).upsertGraph({
|
||||
id: projectId,
|
||||
...projectDTO,
|
||||
});
|
||||
// Triggers `onProjectEdited` event.
|
||||
await this.eventPublisher.emitAsync(events.project.onEdited, {
|
||||
tenantId,
|
||||
oldProject,
|
||||
project,
|
||||
projectDTO,
|
||||
trx,
|
||||
} as IProjectEditedEventPayload);
|
||||
|
||||
return project;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { IProjectStatus } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
|
||||
@Service()
|
||||
export default class EditProjectStatusService {
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Edits a new credit note.
|
||||
* @param {number} projectId -
|
||||
* @param {IProjectStatus} status -
|
||||
*/
|
||||
public editProjectStatus = async (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
status: IProjectStatus
|
||||
) => {
|
||||
const { Project } = this.tenancy.models(tenantId);
|
||||
|
||||
// Validate customer existance.
|
||||
const oldProject = await Project.query()
|
||||
.findById(projectId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Edits the given project under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Upsert the project object.
|
||||
const project = await Project.query(trx).upsertGraph({
|
||||
id: projectId,
|
||||
status,
|
||||
});
|
||||
return project;
|
||||
});
|
||||
};
|
||||
}
|
||||
43
packages/server/src/services/Projects/Projects/GetProject.ts
Normal file
43
packages/server/src/services/Projects/Projects/GetProject.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { IProjectGetPOJO } from '@/interfaces';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ProjectDetailedTransformer } from './ProjectDetailedTransformer';
|
||||
|
||||
@Service()
|
||||
export default class GetProject {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve the project.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns {Promise<IProjectGetPOJO>}
|
||||
*/
|
||||
public getProject = async (
|
||||
tenantId: number,
|
||||
projectId: number
|
||||
): Promise<IProjectGetPOJO> => {
|
||||
const { Project } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the project.
|
||||
const project = await Project.query()
|
||||
.findById(projectId)
|
||||
.withGraphFetched('contact')
|
||||
.modify('totalExpensesDetails')
|
||||
.modify('totalBillsDetails')
|
||||
.modify('totalTasksDetails')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transformes and returns object.
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
project,
|
||||
new ProjectDetailedTransformer()
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { flatten, includes, isEmpty } from 'lodash';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ProjectBillableBillTransformer } from './ProjectBillableBillTransformer';
|
||||
import { ProjectBillableExpenseTransformer } from './ProjectBillableExpenseTransformer';
|
||||
import { ProjectBillableTaskTransformer } from './ProjectBillableTaskTransformer';
|
||||
import {
|
||||
ProjectBillableEntriesQuery,
|
||||
ProjectBillableEntry,
|
||||
ProjectBillableType,
|
||||
} from '@/interfaces';
|
||||
import { ProjectBillableGetter } from './_types';
|
||||
|
||||
@Service()
|
||||
export default class GetProjectBillableEntries {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Billable getter with type.
|
||||
* @get
|
||||
* @returns {ProjectBillableGetter[]}
|
||||
*/
|
||||
get billableGetters(): ProjectBillableGetter[] {
|
||||
return [
|
||||
{ type: ProjectBillableType.Task, getter: this.getProjectBillableTasks },
|
||||
{
|
||||
type: ProjectBillableType.Expense,
|
||||
getter: this.getProjectBillableExpenses,
|
||||
},
|
||||
{ type: ProjectBillableType.Bill, getter: this.getProjectBillableBills },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the billable entries of the given project.
|
||||
* @param {number} tenantId
|
||||
* @param {number} projectId
|
||||
* @param {ProjectBillableEntriesQuery} query -
|
||||
* @returns {}
|
||||
*/
|
||||
public getProjectBillableEntries = async (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
query: ProjectBillableEntriesQuery = {
|
||||
billableType: [],
|
||||
}
|
||||
): Promise<ProjectBillableEntry[]> => {
|
||||
const gettersOpers = this.billableGetters
|
||||
.filter(
|
||||
(billableGetter) =>
|
||||
includes(query.billableType, billableGetter.type) ||
|
||||
isEmpty(query.billableType)
|
||||
)
|
||||
.map((billableGetter) =>
|
||||
billableGetter.getter(tenantId, projectId, query)
|
||||
);
|
||||
const gettersResults = await Promise.all(gettersOpers);
|
||||
|
||||
return flatten(gettersResults);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the billable tasks of the given project.
|
||||
* @param {number} tenantId
|
||||
* @param {number} projectId
|
||||
* @param {ProjectBillableEntriesQuery} query
|
||||
* @returns {ProjectBillableEntry[]}
|
||||
*/
|
||||
private getProjectBillableTasks = async (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
query: ProjectBillableEntriesQuery
|
||||
): Promise<ProjectBillableEntry[]> => {
|
||||
const { Task } = this.tenancy.models(tenantId);
|
||||
|
||||
const billableTasks = await Task.query().where('projectId', projectId);
|
||||
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
billableTasks,
|
||||
new ProjectBillableTaskTransformer()
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the billable expenses of the given project.
|
||||
* @param {number} tenantId
|
||||
* @param {number} projectId
|
||||
* @param {ProjectBillableEntriesQuery} query
|
||||
* @returns
|
||||
*/
|
||||
private getProjectBillableExpenses = async (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
query: ProjectBillableEntriesQuery
|
||||
) => {
|
||||
const { Expense } = this.tenancy.models(tenantId);
|
||||
|
||||
const billableExpenses = await Expense.query()
|
||||
.where('projectId', projectId)
|
||||
.modify('filterByDateRange', null, query.toDate)
|
||||
.modify('filterByPublished');
|
||||
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
billableExpenses,
|
||||
new ProjectBillableExpenseTransformer()
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves billable bills of the given project.
|
||||
* @param {number} tenantId
|
||||
* @param {number} projectId
|
||||
* @param {ProjectBillableEntriesQuery} query
|
||||
*/
|
||||
private getProjectBillableBills = async (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
query: ProjectBillableEntriesQuery
|
||||
) => {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
const billableBills = await Bill.query()
|
||||
.where('projectId', projectId)
|
||||
.modify('published');
|
||||
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
billableBills,
|
||||
new ProjectBillableBillTransformer()
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { IProjectGetPOJO } from '@/interfaces';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ProjectDetailedTransformer } from './ProjectDetailedTransformer';
|
||||
|
||||
@Service()
|
||||
export default class GetProjects {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve the projects list.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns {Promise<IProjectGetPOJO[]>}
|
||||
*/
|
||||
public getProjects = async (tenantId: number): Promise<IProjectGetPOJO[]> => {
|
||||
const { Project } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve projects.
|
||||
const projects = await Project.query()
|
||||
.withGraphFetched('contact')
|
||||
.modify('totalExpensesDetails')
|
||||
.modify('totalBillsDetails')
|
||||
.modify('totalTasksDetails');
|
||||
|
||||
// Transformes and returns object.
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
projects,
|
||||
new ProjectDetailedTransformer()
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class ProjectBillableBill {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Increase the invoiced amount of the given bill.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} billId - Bill id.
|
||||
* @param {number} invoicedAmount - Invoiced amount.
|
||||
*/
|
||||
public increaseInvoicedBill = async (
|
||||
tenantId: number,
|
||||
billId: number,
|
||||
invoicedAmount: number
|
||||
) => {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
await Bill.query()
|
||||
.findById(billId)
|
||||
.increment('projectInvoicedAmount', invoicedAmount);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrease the invoiced amount of the given bill.
|
||||
* @param {number} tenantId
|
||||
* @param {number} billId - Bill id.
|
||||
* @param {number} invoiceHours - Invoiced amount.
|
||||
* @returns {}
|
||||
*/
|
||||
public decreaseInvoicedBill = async (
|
||||
tenantId: number,
|
||||
billId: number,
|
||||
invoiceHours: number
|
||||
) => {
|
||||
const { Bill } = this.tenancy.models(tenantId);
|
||||
|
||||
await Bill.query()
|
||||
.findById(billId)
|
||||
.decrement('projectInvoicedAmount', invoiceHours);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import async from 'async';
|
||||
import { Knex } from 'knex';
|
||||
import { Service } from 'typedi';
|
||||
import { ISaleInvoice, ISaleInvoiceDTO, ProjectLinkRefType } from '@/interfaces';
|
||||
import { ProjectBillableExpense } from './ProjectBillableExpense';
|
||||
import { filterEntriesByRefType } from './_utils';
|
||||
|
||||
@Service()
|
||||
export class ProjectBillableBill {
|
||||
@Inject()
|
||||
private projectBillableExpense: ProjectBillableExpense;
|
||||
|
||||
/**
|
||||
* Increases the invoiced amount of the given bills that associated
|
||||
* to the invoice entries.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleInvoice | ISaleInvoiceDTO} saleInvoiceDTO
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public increaseInvoicedBill = async (
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoice | ISaleInvoiceDTO,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
// Initiates a new queue for accounts balance mutation.
|
||||
const saveAccountsBalanceQueue = async.queue(
|
||||
this.increaseInvoicedExpenseQueue,
|
||||
10
|
||||
);
|
||||
const filteredEntries = filterEntriesByRefType(
|
||||
saleInvoiceDTO.entries,
|
||||
ProjectLinkRefType.Task
|
||||
);
|
||||
filteredEntries.forEach((entry) => {
|
||||
saveAccountsBalanceQueue.push({
|
||||
tenantId,
|
||||
projectRefId: entry.projectRefId,
|
||||
projectRefInvoicedAmount: entry.projectRefInvoicedAmount,
|
||||
trx,
|
||||
});
|
||||
});
|
||||
if (filteredEntries.length > 0) {
|
||||
await saveAccountsBalanceQueue.drain();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decreases the invoiced amount of the given bills that associated
|
||||
* to the invoice entries.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleInvoice | ISaleInvoiceDTO} saleInvoiceDTO
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public decreaseInvoicedBill = async (
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoice | ISaleInvoiceDTO,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
// Initiates a new queue for accounts balance mutation.
|
||||
const saveAccountsBalanceQueue = async.queue(
|
||||
this.decreaseInvoicedExpenseQueue,
|
||||
10
|
||||
);
|
||||
const filteredEntries = filterEntriesByRefType(
|
||||
saleInvoiceDTO.entries,
|
||||
ProjectLinkRefType.Task
|
||||
);
|
||||
filteredEntries.forEach((entry) => {
|
||||
saveAccountsBalanceQueue.push({
|
||||
tenantId,
|
||||
projectRefId: entry.projectRefId,
|
||||
projectRefInvoicedAmount: entry.projectRefInvoicedAmount,
|
||||
trx,
|
||||
});
|
||||
});
|
||||
if (filteredEntries.length > 0) {
|
||||
await saveAccountsBalanceQueue.drain();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue job increases the invoiced amount of the given bill.
|
||||
* @param {IncreaseInvoicedTaskQueuePayload} - payload
|
||||
*/
|
||||
private increaseInvoicedExpenseQueue = async ({
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx,
|
||||
}) => {
|
||||
await this.projectBillableExpense.increaseInvoicedExpense(
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue job decreases the invoiced amount of the given bill.
|
||||
* @param {IncreaseInvoicedTaskQueuePayload} - payload
|
||||
*/
|
||||
private decreaseInvoicedExpenseQueue = async ({
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx,
|
||||
}) => {
|
||||
await this.projectBillableExpense.decreaseInvoicedExpense(
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
ISaleInvoiceCreatedPayload,
|
||||
ISaleInvoiceDeletedPayload,
|
||||
ISaleInvoiceEditedPayload,
|
||||
} from '@/interfaces';
|
||||
import { ProjectBillableTask } from './ProjectBillableTasks';
|
||||
import { ProjectBillableExpense } from './ProjectBillableExpense';
|
||||
import { ProjectBillableExpenseInvoiced } from './ProjectBillableExpenseInvoiced';
|
||||
|
||||
@Service()
|
||||
export class ProjectBillableBillSubscriber {
|
||||
@Inject()
|
||||
private projectBillableExpenseInvoiced: ProjectBillableExpenseInvoiced;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
* @param bus
|
||||
*/
|
||||
attach(bus) {
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onCreated,
|
||||
this.handleIncreaseBillableBill
|
||||
);
|
||||
bus.subscribe(events.saleInvoice.onEdited, this.handleDecreaseBillableBill);
|
||||
bus.subscribe(events.saleInvoice.onDeleted, this.handleEditBillableBill);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the billable amount of expense.
|
||||
* @param {ISaleInvoiceCreatedPayload} payload -
|
||||
*/
|
||||
public handleIncreaseBillableBill = async ({
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
saleInvoiceDTO,
|
||||
trx,
|
||||
}: ISaleInvoiceCreatedPayload) => {
|
||||
await this.projectBillableExpenseInvoiced.increaseInvoicedExpense(
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decreases the billable amount of expense.
|
||||
* @param {ISaleInvoiceDeletedPayload} payload -
|
||||
*/
|
||||
public handleDecreaseBillableBill = async ({
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceDeletedPayload) => {
|
||||
await this.projectBillableExpenseInvoiced.decreaseInvoicedExpense(
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ISaleInvoiceEditedPayload} payload -
|
||||
*/
|
||||
public handleEditBillableBill = async ({
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
saleInvoiceDTO,
|
||||
trx,
|
||||
}: ISaleInvoiceEditedPayload) => {
|
||||
await this.projectBillableExpenseInvoiced.decreaseInvoicedExpense(
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
trx
|
||||
);
|
||||
await this.projectBillableExpenseInvoiced.increaseInvoicedExpense(
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { IBill } from '@/interfaces';
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
export class ProjectBillableBillTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'billableType',
|
||||
'billableId',
|
||||
'billableAmount',
|
||||
'billableAmountFormatted',
|
||||
'billableCurrency',
|
||||
'billableTransactionNo',
|
||||
'billableDate',
|
||||
'billableDateFormatted',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable type.
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableType = () => {
|
||||
return 'Bill';
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable id.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableId = (bill: IBill) => {
|
||||
return bill.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable amount.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableAmount = (bill: IBill) => {
|
||||
return bill.billableAmount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable amount formatted.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableAmountFormatted = (bill: IBill) => {
|
||||
return formatNumber(bill.billableAmount, {
|
||||
currencyCode: bill.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable currency.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableCurrency = (bill: IBill) => {
|
||||
return bill.currencyCode;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableTransactionNo = (bill: IBill) => {
|
||||
return bill.billNumber;
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable date.
|
||||
* @returns {Date}
|
||||
*/
|
||||
public billableDate = (bill: IBill) => {
|
||||
return bill.createdAt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable date formatted.
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableDateFormatted = (bill: IBill) => {
|
||||
return this.formatDate(bill.createdAt);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
@Service()
|
||||
export class ProjectBillableExpense {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Increase the invoiced amount of the given expense.
|
||||
* @param {number} tenantId
|
||||
* @param {number} expenseId
|
||||
* @param {number} invoicedAmount
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public increaseInvoicedExpense = async (
|
||||
tenantId: number,
|
||||
expenseId: number,
|
||||
invoicedAmount: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { Expense } = this.tenancy.models(tenantId);
|
||||
|
||||
await Expense.query(trx)
|
||||
.findById(expenseId)
|
||||
.increment('invoicedAmount', invoicedAmount);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrease the invoiced amount of the given expense.
|
||||
* @param {number} tenantId
|
||||
* @param {number} taskId
|
||||
* @param {number} invoiceHours
|
||||
* @param {Knex.Transaction} knex
|
||||
*/
|
||||
public decreaseInvoicedExpense = async (
|
||||
tenantId: number,
|
||||
expenseId: number,
|
||||
invoiceHours: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { Expense } = this.tenancy.models(tenantId);
|
||||
|
||||
await Expense.query(trx)
|
||||
.findById(expenseId)
|
||||
.decrement('invoicedAmount', invoiceHours);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import async from 'async';
|
||||
import { ISaleInvoice, ISaleInvoiceDTO, ProjectLinkRefType } from '@/interfaces';
|
||||
import { ProjectBillableExpense } from './ProjectBillableExpense';
|
||||
import { filterEntriesByRefType } from './_utils';
|
||||
|
||||
@Service()
|
||||
export class ProjectBillableExpenseInvoiced {
|
||||
@Inject()
|
||||
private projectBillableExpense: ProjectBillableExpense;
|
||||
|
||||
/**
|
||||
* Increases the invoiced amount of invoice entries that reference to
|
||||
* expense entries.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleInvoice | ISaleInvoiceDTO} saleInvoiceDTO
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public increaseInvoicedExpense = async (
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoice | ISaleInvoiceDTO,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
// Initiates a new queue for accounts balance mutation.
|
||||
const saveAccountsBalanceQueue = async.queue(
|
||||
this.increaseInvoicedExpenseQueue,
|
||||
10
|
||||
);
|
||||
const filteredEntries = filterEntriesByRefType(
|
||||
saleInvoiceDTO.entries,
|
||||
ProjectLinkRefType.Expense
|
||||
);
|
||||
filteredEntries.forEach((entry) => {
|
||||
saveAccountsBalanceQueue.push({
|
||||
tenantId,
|
||||
projectRefId: entry.projectRefId,
|
||||
projectRefInvoicedAmount: entry.projectRefInvoicedAmount,
|
||||
trx,
|
||||
});
|
||||
});
|
||||
if (filteredEntries.length > 0) {
|
||||
await saveAccountsBalanceQueue.drain();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decreases the invoiced amount of the given expenses from
|
||||
* the invoice entries.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleInvoice | ISaleInvoiceDTO} saleInvoiceDTO
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public decreaseInvoicedExpense = async (
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoice | ISaleInvoiceDTO,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
// Initiates a new queue for accounts balance mutation.
|
||||
const saveAccountsBalanceQueue = async.queue(
|
||||
this.decreaseInvoicedExpenseQueue,
|
||||
10
|
||||
);
|
||||
const filteredEntries = filterEntriesByRefType(
|
||||
saleInvoiceDTO.entries,
|
||||
ProjectLinkRefType.Expense
|
||||
);
|
||||
filteredEntries.forEach((entry) => {
|
||||
saveAccountsBalanceQueue.push({
|
||||
tenantId,
|
||||
projectRefId: entry.projectRefId,
|
||||
projectRefInvoicedAmount: entry.projectRefInvoicedAmount,
|
||||
trx,
|
||||
});
|
||||
});
|
||||
if (filteredEntries.length > 0) {
|
||||
await saveAccountsBalanceQueue.drain();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue job increases the invoiced amount of the given expense.
|
||||
* @param {IncreaseInvoicedTaskQueuePayload} - payload
|
||||
*/
|
||||
private increaseInvoicedExpenseQueue = async ({
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx,
|
||||
}) => {
|
||||
await this.projectBillableExpense.increaseInvoicedExpense(
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue job decreases the invoiced amount of the given expense.
|
||||
* @param {IncreaseInvoicedTaskQueuePayload} - payload
|
||||
*/
|
||||
private decreaseInvoicedExpenseQueue = async ({
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx,
|
||||
}) => {
|
||||
await this.projectBillableExpense.decreaseInvoicedExpense(
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
ISaleInvoiceCreatedPayload,
|
||||
ISaleInvoiceDeletedPayload,
|
||||
ISaleInvoiceEditedPayload,
|
||||
} from '@/interfaces';
|
||||
import { ProjectBillableExpenseInvoiced } from './ProjectBillableExpenseInvoiced';
|
||||
|
||||
@Service()
|
||||
export class ProjectBillableExpensesSubscriber {
|
||||
@Inject()
|
||||
private projectBillableExpenseInvoiced: ProjectBillableExpenseInvoiced;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
* @param bus
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onCreated,
|
||||
this.handleIncreaseBillableExpenses
|
||||
);
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onEdited,
|
||||
this.handleDecreaseBillableExpenses
|
||||
);
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onDeleted,
|
||||
this.handleEditBillableExpenses
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the billable amount of expense.
|
||||
* @param {ISaleInvoiceCreatedPayload} payload -
|
||||
*/
|
||||
public handleIncreaseBillableExpenses = async ({
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
trx,
|
||||
}: ISaleInvoiceCreatedPayload) => {
|
||||
await this.projectBillableExpenseInvoiced.increaseInvoicedExpense(
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decreases the billable amount of expense.
|
||||
* @param {ISaleInvoiceDeletedPayload} payload -
|
||||
*/
|
||||
public handleDecreaseBillableExpenses = async ({
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceDeletedPayload) => {
|
||||
await this.projectBillableExpenseInvoiced.increaseInvoicedExpense(
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decreases the old invoice and increases the new invoice DTO.
|
||||
* @param {ISaleInvoiceEditedPayload} payload -
|
||||
*/
|
||||
public handleEditBillableExpenses = async ({
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
oldSaleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceEditedPayload) => {
|
||||
await this.projectBillableExpenseInvoiced.decreaseInvoicedExpense(
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
trx
|
||||
);
|
||||
await this.projectBillableExpenseInvoiced.increaseInvoicedExpense(
|
||||
tenantId,
|
||||
saleInvoice,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { IExpense } from '@/interfaces';
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
export class ProjectBillableExpenseTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'billableType',
|
||||
'billableId',
|
||||
'billableAmount',
|
||||
'billableAmountFormatted',
|
||||
'billableCurrency',
|
||||
'billableTransactionNo',
|
||||
'billableDate',
|
||||
'billableDateFormatted',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the billable type.
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableType = () => {
|
||||
return 'Expense';
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the billable id.
|
||||
* @param {IExpense} expense
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableId = (expense: IExpense) => {
|
||||
return expense.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the billable amount of expense.
|
||||
* @param {IExpense} expense -
|
||||
* @returns {number}
|
||||
*/
|
||||
public billableAmount = (expense: IExpense) => {
|
||||
return expense.billableAmount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the billable formatted amount of expense.
|
||||
* @param {IExpense} expense
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableAmountFormatted = (expense: IExpense) => {
|
||||
return formatNumber(expense.billableAmount, {
|
||||
currencyCode: expense.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the currency of billable expense.
|
||||
* @param {IExpense} expense
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableCurrency = (expense: IExpense) => {
|
||||
return expense.currencyCode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable transaction number.
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableTransactionNo = () => {
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable date.
|
||||
* @returns {Date}
|
||||
*/
|
||||
public billableDate = (expense: IExpense) => {
|
||||
return expense.createdAt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable date formatted.
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableDateFormatted = (expense: IExpense) => {
|
||||
return this.formatDate(expense.createdAt);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { IProjectTask } from '@/interfaces';
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
export class ProjectBillableTaskTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'billableType',
|
||||
'billableId',
|
||||
'billableAmount',
|
||||
'billableAmountFormatted',
|
||||
'billableHours',
|
||||
'billableCurrency',
|
||||
'billableTransactionNo',
|
||||
'billableDate',
|
||||
'billableDateFormatted',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable type.
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableType = () => {
|
||||
return 'Task';
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable id.
|
||||
* @param {IProjectTask} task
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableId = (task: IProjectTask) => {
|
||||
return task.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable amount.
|
||||
* @param {IProjectTask} task
|
||||
* @returns {number}
|
||||
*/
|
||||
public billableAmount = (task: IProjectTask) => {
|
||||
return task.billableAmount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable amount formatted.
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableAmountFormatted = (task: IProjectTask) => {
|
||||
return formatNumber(task.billableAmount, {
|
||||
currencyCode: this.context.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable hours of the task.
|
||||
* @param {IProjectTask} task
|
||||
* @returns {number}
|
||||
*/
|
||||
public billableHours = (task: IProjectTask) => {
|
||||
return task.billableHours;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the currency of billable entry.
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableCurrency = () => {
|
||||
return this.context.baseCurrency;
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable transaction number.
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableTransactionNo = () => {
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable date.
|
||||
* @returns {Date}
|
||||
*/
|
||||
public billableDate = (task: IProjectTask) => {
|
||||
return task.createdAt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Billable date formatted.
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableDateFormatted = (task: IProjectTask) => {
|
||||
return this.formatDate(task.createdAt);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
@Service()
|
||||
export class ProjectBillableTask {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Increase the invoiced hours of the given task.
|
||||
* @param {number} tenantId
|
||||
* @param {number} taskId
|
||||
* @param {number} invoiceHours
|
||||
*/
|
||||
public increaseInvoicedTask = async (
|
||||
tenantId: number,
|
||||
taskId: number,
|
||||
invoiceHours: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { Task } = this.tenancy.models(tenantId);
|
||||
|
||||
await Task.query(trx)
|
||||
.findById(taskId)
|
||||
.increment('invoicedHours', invoiceHours);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrease the invoiced hours of the given task.
|
||||
* @param {number} tenantId
|
||||
* @param {number} taskId
|
||||
* @param {number} invoiceHours -
|
||||
* @returns {}
|
||||
*/
|
||||
public decreaseInvoicedTask = async (
|
||||
tenantId: number,
|
||||
taskId: number,
|
||||
invoiceHours: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { Task } = this.tenancy.models(tenantId);
|
||||
|
||||
await Task.query(trx)
|
||||
.findById(taskId)
|
||||
.decrement('invoicedHours', invoiceHours);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import async from 'async';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import { ISaleInvoice, ISaleInvoiceDTO, ProjectLinkRefType } from '@/interfaces';
|
||||
import { ProjectBillableTask } from './ProjectBillableTasks';
|
||||
import { filterEntriesByRefType } from './_utils';
|
||||
import { IncreaseInvoicedTaskQueuePayload } from './_types';
|
||||
|
||||
@Service()
|
||||
export class ProjectBillableTasksInvoiced {
|
||||
@Inject()
|
||||
private projectBillableTasks: ProjectBillableTask;
|
||||
|
||||
/**
|
||||
* Increases the invoiced amount of the given tasks that associated
|
||||
* to the invoice entries.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleInvoiceDTO} saleInvoiceDTO
|
||||
*/
|
||||
public increaseInvoicedTasks = async (
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceDTO | ISaleInvoice,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
// Initiate a new queue for accounts balance mutation.
|
||||
const saveAccountsBalanceQueue = async.queue(
|
||||
this.increaseInvoicedTaskQueue,
|
||||
10
|
||||
);
|
||||
const filteredEntries = filterEntriesByRefType(
|
||||
saleInvoiceDTO.entries,
|
||||
ProjectLinkRefType.Task
|
||||
);
|
||||
filteredEntries.forEach((entry) => {
|
||||
saveAccountsBalanceQueue.push({
|
||||
tenantId,
|
||||
projectRefId: entry.projectRefId,
|
||||
projectRefInvoicedAmount: entry.projectRefInvoicedAmount,
|
||||
trx,
|
||||
});
|
||||
});
|
||||
if (filteredEntries.length > 0) {
|
||||
await saveAccountsBalanceQueue.drain();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decreases the invoiced amount of the given tasks that associated
|
||||
* to the invoice entries.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleInvoiceDTO | ISaleInvoice} saleInvoiceDTO
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public decreaseInvoicedTasks = async (
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceDTO | ISaleInvoice,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
// Initiate a new queue for accounts balance mutation.
|
||||
const saveAccountsBalanceQueue = async.queue(
|
||||
this.decreaseInvoicedTaskQueue,
|
||||
10
|
||||
);
|
||||
const filteredEntries = filterEntriesByRefType(
|
||||
saleInvoiceDTO.entries,
|
||||
ProjectLinkRefType.Task
|
||||
);
|
||||
filteredEntries.forEach((entry) => {
|
||||
saveAccountsBalanceQueue.push({
|
||||
tenantId,
|
||||
projectRefId: entry.projectRefId,
|
||||
projectRefInvoicedAmount: entry.projectRefInvoicedAmount,
|
||||
trx,
|
||||
});
|
||||
});
|
||||
if (filteredEntries.length > 0) {
|
||||
await saveAccountsBalanceQueue.drain();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue job increases the invoiced amount of the given task.
|
||||
* @param {IncreaseInvoicedTaskQueuePayload} - payload
|
||||
*/
|
||||
private increaseInvoicedTaskQueue = async ({
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx,
|
||||
}: IncreaseInvoicedTaskQueuePayload) => {
|
||||
await this.projectBillableTasks.increaseInvoicedTask(
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Queue jobs decreases the invoiced amount of the given task.
|
||||
* @param {IncreaseInvoicedTaskQueuePayload} - payload
|
||||
*/
|
||||
private decreaseInvoicedTaskQueue = async ({
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx,
|
||||
}) => {
|
||||
await this.projectBillableTasks.decreaseInvoicedTask(
|
||||
tenantId,
|
||||
projectRefId,
|
||||
projectRefInvoicedAmount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
ISaleInvoiceCreatedPayload,
|
||||
ISaleInvoiceDeletedPayload,
|
||||
ISaleInvoiceEditedPayload,
|
||||
} from '@/interfaces';
|
||||
import { ProjectInvoiceValidator } from './ProjectInvoiceValidator';
|
||||
import { ProjectBillableTasksInvoiced } from './ProjectBillableTasksInvoiced';
|
||||
|
||||
@Service()
|
||||
export class ProjectBillableTasksSubscriber {
|
||||
@Inject()
|
||||
private projectBillableTasks: ProjectBillableTasksInvoiced;
|
||||
|
||||
@Inject()
|
||||
private projectBillableTasksValidator: ProjectInvoiceValidator;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
* @param bus
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onCreating,
|
||||
this.handleValidateInvoiceTasksRefs
|
||||
);
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onCreated,
|
||||
this.handleIncreaseBillableTasks
|
||||
);
|
||||
bus.subscribe(events.saleInvoice.onEdited, this.handleEditBillableTasks);
|
||||
bus.subscribe(
|
||||
events.saleInvoice.onDeleted,
|
||||
this.handleDecreaseBillableTasks
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the tasks refs ids existance.
|
||||
* @param {ISaleInvoiceCreatedPayload} payload -
|
||||
*/
|
||||
public handleValidateInvoiceTasksRefs = async ({
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
}: ISaleInvoiceCreatedPayload) => {
|
||||
await this.projectBillableTasksValidator.validateTasksRefsExistance(
|
||||
tenantId,
|
||||
saleInvoiceDTO
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle increase the invoiced tasks once the sale invoice be created.
|
||||
* @param {ISaleInvoiceCreatedPayload} payload -
|
||||
*/
|
||||
public handleIncreaseBillableTasks = async ({
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
}: ISaleInvoiceCreatedPayload) => {
|
||||
await this.projectBillableTasks.increaseInvoicedTasks(
|
||||
tenantId,
|
||||
saleInvoiceDTO
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle decrease the invoiced tasks once the sale invoice be deleted.
|
||||
* @param {ISaleInvoiceDeletedPayload} payload -
|
||||
*/
|
||||
public handleDecreaseBillableTasks = async ({
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceDeletedPayload) => {
|
||||
await this.projectBillableTasks.decreaseInvoicedTasks(
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle adjusting the invoiced tasks once the sale invoice be edited.
|
||||
* @param {ISaleInvoiceEditedPayload} payload -
|
||||
*/
|
||||
public handleEditBillableTasks = async ({
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
saleInvoiceDTO,
|
||||
trx,
|
||||
}: ISaleInvoiceEditedPayload) => {
|
||||
await this.projectBillableTasks.increaseInvoicedTasks(
|
||||
tenantId,
|
||||
saleInvoiceDTO,
|
||||
trx
|
||||
);
|
||||
await this.projectBillableTasks.decreaseInvoicedTasks(
|
||||
tenantId,
|
||||
oldSaleInvoice,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { sumBy } from 'lodash';
|
||||
import Project from 'models/Project';
|
||||
import { formatNumber } from 'utils';
|
||||
import { formatMinutes } from 'utils/formatMinutes';
|
||||
|
||||
export class ProjectDetailedTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'costEstimateFormatted',
|
||||
'deadlineFormatted',
|
||||
'contactDisplayName',
|
||||
'statusFormatted',
|
||||
|
||||
'totalActualHours',
|
||||
'totalActualHoursFormatted',
|
||||
'totalEstimateHours',
|
||||
'totalEstimateHoursFormatted',
|
||||
'totalInvoicedHours',
|
||||
'totalInvoicedHoursFormatted',
|
||||
'totalBillableHours',
|
||||
'totalBillableHoursFormatted',
|
||||
|
||||
'totalActualHoursAmount',
|
||||
'totalActualHoursAmountFormatted',
|
||||
'totalEstimateHoursAmount',
|
||||
'totalEstimateHoursAmountFormatted',
|
||||
'totalInvoicedHoursAmount',
|
||||
'totalInvoicedHoursAmountFormatted',
|
||||
'totalBillableHoursAmount',
|
||||
'totalBillableHoursAmountFormatted',
|
||||
|
||||
'totalExpenses',
|
||||
'totalExpensesFormatted',
|
||||
|
||||
'totalInvoicedExpenses',
|
||||
'totalInvoicedExpensesFormatted',
|
||||
|
||||
'totalBillableExpenses',
|
||||
'totalBillableExpensesFormatted',
|
||||
|
||||
'totalInvoiced',
|
||||
'totalInvoicedFormatted',
|
||||
|
||||
'totalBillable',
|
||||
'totalBillableFormatted',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude these attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['contact', 'tasks', 'expenses', 'bills'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted value of cost estimate.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public costEstimateFormatted = (project: Project) => {
|
||||
return formatNumber(project.costEstimate, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted value of the deadline date.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public deadlineFormatted = (project: Project) => {
|
||||
return this.formatDate(project.deadline);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the contact display name.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public contactDisplayName = (project: Project) => {
|
||||
return project.contact.displayName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted value of project's status.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public statusFormatted = (project: Project) => {
|
||||
return project.status;
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// # Tasks Hours
|
||||
// --------------------------------------------------------------
|
||||
/**
|
||||
* Total actual hours.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalActualHours = (project: Project) => {
|
||||
return sumBy(project.tasks, 'totalActualHours');
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted total actual hours.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public totalActualHoursFormatted = (project: Project) => {
|
||||
const hours = this.totalActualHours(project);
|
||||
return formatMinutes(hours);
|
||||
};
|
||||
|
||||
/**
|
||||
* Total Estimated hours.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalEstimateHours = (project: Project) => {
|
||||
return sumBy(project.tasks, 'totalEstimateHours');
|
||||
};
|
||||
|
||||
/**
|
||||
* Total estimate hours formatted.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public totalEstimateHoursFormatted = (project: Project) => {
|
||||
const hours = this.totalEstimateHours(project);
|
||||
return formatMinutes(hours);
|
||||
};
|
||||
|
||||
/**
|
||||
* Total invoiced hours.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalInvoicedHours = (project: Project) => {
|
||||
return sumBy(project.tasks, 'totalInvoicedHours');
|
||||
};
|
||||
|
||||
/**
|
||||
* Total invoiced hours formatted.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public totalInvoicedHoursFormatted = (project: Project) => {
|
||||
const hours = this.totalInvoicedHours(project);
|
||||
return formatMinutes(hours);
|
||||
};
|
||||
|
||||
/**
|
||||
* Total billable hours.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalBillableHours = (project: Project) => {
|
||||
const totalActualHours = this.totalActualHours(project);
|
||||
const totalInvoicedHours = this.totalInvoicedHours(project);
|
||||
|
||||
return Math.max(totalActualHours - totalInvoicedHours, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the billable hours formatted.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public totalBillableHoursFormatted = (project) => {
|
||||
const hours = this.totalBillableHours(project);
|
||||
return formatMinutes(hours);
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// # Tasks Hours Amount
|
||||
// --------------------------------------------------------------
|
||||
/**
|
||||
* Total amount of invoiced hours.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalActualHoursAmount = (project: Project) => {
|
||||
return sumBy(project.tasks, 'totalActualAmount');
|
||||
};
|
||||
|
||||
/**
|
||||
* Total amount of invoiced hours.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalActualHoursAmountFormatted = (project: Project) => {
|
||||
return formatNumber(this.totalActualHoursAmount(project), {
|
||||
currencyCode: this.context.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Total amount of estimated hours.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalEstimateHoursAmount = (project: Project) => {
|
||||
return sumBy(project.tasks, 'totalEstimateAmount');
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatted amount of total estimate hours.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public totalEstimateHoursAmountFormatted = (project: Project) => {
|
||||
return formatNumber(this.totalEstimateHoursAmount(project), {
|
||||
currencyCode: this.context.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Total amount of invoiced hours.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalInvoicedHoursAmount = (project) => {
|
||||
return sumBy(project.tasks, 'totalInvoicedAmount');
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatted total amount of invoiced hours.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalInvoicedHoursAmountFormatted = (project) => {
|
||||
return formatNumber(this.totalInvoicedHoursAmount(project), {
|
||||
currencyCode: this.context.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Total amount of billable hours.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalBillableHoursAmount = (project) => {
|
||||
const totalActualAmount = this.totalActualHoursAmount(project);
|
||||
const totalBillableAmount = this.totalInvoicedHoursAmount(project);
|
||||
|
||||
return Math.max(totalActualAmount, totalBillableAmount);
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatted total amount of billable hours.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public totalBillableHoursAmountFormatted = (project) => {
|
||||
return formatNumber(this.totalBillableHoursAmount(project), {
|
||||
currencyCode: this.context.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// # Expenses
|
||||
// --------------------------------------------------------------
|
||||
/**
|
||||
* Total expenses amount.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalExpenses = (project) => {
|
||||
const expensesTotal = sumBy(project.expenses, 'totalExpenses');
|
||||
const billsTotal = sumBy(project.bills, 'totalBills');
|
||||
|
||||
return expensesTotal + billsTotal;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatted total amount of expenses.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public totalExpensesFormatted = (project) => {
|
||||
return formatNumber(this.totalExpenses(project), {
|
||||
currencyCode: this.context.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Total amount of invoiced expenses.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalInvoicedExpenses = (project: Project) => {
|
||||
const totalInvoicedExpenses = sumBy(
|
||||
project.expenses,
|
||||
'totalInvoicedExpenses'
|
||||
);
|
||||
const totalInvoicedBills = sumBy(project.bills, 'totalInvoicedBills');
|
||||
|
||||
return totalInvoicedExpenses + totalInvoicedBills;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatted total amount of invoiced expenses.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public totalInvoicedExpensesFormatted = (project: Project) => {
|
||||
return formatNumber(this.totalInvoicedExpenses(project), {
|
||||
currencyCode: this.context.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Total amount of billable expenses.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalBillableExpenses = (project: Project) => {
|
||||
const totalInvoiced = this.totalInvoicedExpenses(project);
|
||||
const totalInvoice = this.totalExpenses(project);
|
||||
|
||||
return totalInvoice - totalInvoiced;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatted total amount of billable expenses.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public totalBillableExpensesFormatted = (project: Project) => {
|
||||
return formatNumber(this.totalBillableExpenses(project), {
|
||||
currencyCode: this.context.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// # Total
|
||||
// --------------------------------------------------------------
|
||||
/**
|
||||
* Total invoiced amount.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalInvoiced = (project: Project) => {
|
||||
const invoicedExpenses = this.totalInvoicedExpenses(project);
|
||||
const invoicedTasks = this.totalInvoicedHoursAmount(project);
|
||||
|
||||
return invoicedExpenses + invoicedTasks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatted amount of total invoiced.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalInvoicedFormatted = (project: Project) => {
|
||||
return formatNumber(this.totalInvoiced(project), {
|
||||
currencyCode: this.context.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Total billable amount.
|
||||
* @param {Project} project
|
||||
* @returns {number}
|
||||
*/
|
||||
public totalBillable = (project: Project) => {
|
||||
const billableExpenses = this.totalBillableExpenses(project);
|
||||
const billableTasks = this.totalBillableHoursAmount(project);
|
||||
|
||||
return billableExpenses + billableTasks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatted amount of billable total.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public totalBillableFormatted = (project: Project) => {
|
||||
return formatNumber(this.totalBillable(project), {
|
||||
currencyCode: this.context.baseCurrency,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ISaleInvoiceCreateDTO, ProjectLinkRefType } from '@/interfaces';
|
||||
import { difference, isEmpty } from 'lodash';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export class ProjectInvoiceValidator {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Validate the tasks refs ids existance.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleInvoiceCreateDTO} saleInvoiceDTO
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async validateTasksRefsExistance(
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO
|
||||
) {
|
||||
const { Task } = this.tenancy.models(tenantId);
|
||||
|
||||
// Filters the invoice entries that have `Task` type and not empty ref. id.
|
||||
const tasksRefs = saleInvoiceDTO.entries.filter(
|
||||
(entry) =>
|
||||
entry?.projectRefType === ProjectLinkRefType.Task &&
|
||||
!isEmpty(entry?.projectRefId)
|
||||
);
|
||||
//
|
||||
if (!tasksRefs.length || (tasksRefs.length && !saleInvoiceDTO.projectId)) {
|
||||
return;
|
||||
}
|
||||
const tasksRefsIds = tasksRefs.map((ref) => ref.projectRefId);
|
||||
|
||||
const tasks = await Task.query()
|
||||
.whereIn('id', tasksRefsIds)
|
||||
.where('projectId', saleInvoiceDTO.projectId);
|
||||
|
||||
const tasksIds = tasks.map((task) => task.id);
|
||||
const notFoundTasksIds = difference(tasksIds, tasksRefsIds);
|
||||
|
||||
if (!notFoundTasksIds.length) {
|
||||
throw new ServiceError(ERRORS.ITEM_ENTRIES_REF_IDS_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import Project from 'models/Project';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
export class ProjectTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'costEstimateFormatted',
|
||||
'deadlineFormatted',
|
||||
'contactDisplayName',
|
||||
'statusFormatted',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude these attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['contact'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted value of cost estimate.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public costEstimateFormatted = (project: Project) => {
|
||||
return formatNumber(project.costEstimate, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted value of the deadline date.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public deadlineFormatted = (project: Project) => {
|
||||
return this.formatDate(project.deadline);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the contact display name.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public contactDisplayName = (project: Project) => {
|
||||
return project.contact.displayName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted value of project's status.
|
||||
* @param {Project} project
|
||||
* @returns {string}
|
||||
*/
|
||||
public statusFormatted = (project: Project) => {
|
||||
return project.status;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
IProjectCreateDTO,
|
||||
IProjectCreatePOJO,
|
||||
IProjectEditPOJO,
|
||||
IProjectGetPOJO,
|
||||
IProjectStatus,
|
||||
IVendorsFilter,
|
||||
ProjectBillableEntriesQuery,
|
||||
ProjectBillableEntry,
|
||||
} from '@/interfaces';
|
||||
import CreateProject from './CreateProject';
|
||||
import DeleteProject from './DeleteProject';
|
||||
import GetProject from './GetProject';
|
||||
import EditProjectService from './EditProject';
|
||||
import GetProjects from './GetProjects';
|
||||
import EditProjectStatusService from './EditProjectStatus';
|
||||
import GetProjectBillableEntries from './GetProjectBillableEntries';
|
||||
|
||||
@Service()
|
||||
export class ProjectsApplication {
|
||||
@Inject()
|
||||
private createProjectService: CreateProject;
|
||||
|
||||
@Inject()
|
||||
private editProjectService: EditProjectService;
|
||||
|
||||
@Inject()
|
||||
private deleteProjectService: DeleteProject;
|
||||
|
||||
@Inject()
|
||||
private getProjectService: GetProject;
|
||||
|
||||
@Inject()
|
||||
private getProjectsService: GetProjects;
|
||||
|
||||
@Inject()
|
||||
private editProjectStatusService: EditProjectStatusService;
|
||||
|
||||
@Inject()
|
||||
private getProjectBillable: GetProjectBillableEntries;
|
||||
|
||||
/**
|
||||
* Creates a new project.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {IProjectCreateDTO} projectDTO - Create project DTO.
|
||||
* @return {Promise<IProjectCreatePOJO>}
|
||||
*/
|
||||
public createProject = (
|
||||
tenantId: number,
|
||||
projectDTO: IProjectCreateDTO
|
||||
): Promise<IProjectCreatePOJO> => {
|
||||
return this.createProjectService.createProject(tenantId, projectDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits details of the given vendor.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} vendorId - Vendor id.
|
||||
* @param {IProjectCreateDTO} projectDTO - Create project DTO.
|
||||
* @returns {Promise<IVendor>}
|
||||
*/
|
||||
public editProject = (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
projectDTO: IProjectCreateDTO
|
||||
): Promise<IProjectEditPOJO> => {
|
||||
return this.editProjectService.editProject(tenantId, projectId, projectDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the given project.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public deleteProject = (
|
||||
tenantId: number,
|
||||
projectId: number
|
||||
): Promise<void> => {
|
||||
return this.deleteProjectService.deleteProject(tenantId, projectId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the vendor details.
|
||||
* @param {number} tenantId
|
||||
* @param {number} projectId
|
||||
* @returns {Promise<IProjectGetPOJO>}
|
||||
*/
|
||||
public getProject = (
|
||||
tenantId: number,
|
||||
projectId: number
|
||||
): Promise<IProjectGetPOJO> => {
|
||||
return this.getProjectService.getProject(tenantId, projectId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the vendors paginated list.
|
||||
* @param {number} tenantId
|
||||
* @param {IVendorsFilter} filterDTO
|
||||
* @returns {Promise<IProjectGetPOJO[]>}
|
||||
*/
|
||||
public getProjects = (
|
||||
tenantId: number,
|
||||
filterDTO: IVendorsFilter
|
||||
): Promise<IProjectGetPOJO[]> => {
|
||||
return this.getProjectsService.getProjects(tenantId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits the given project status.
|
||||
* @param {number} tenantId
|
||||
* @param {number} projectId
|
||||
* @param {IProjectStatus} status
|
||||
* @returns {Promise<IProject>}
|
||||
*/
|
||||
public editProjectStatus = (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
status: IProjectStatus
|
||||
) => {
|
||||
return this.editProjectStatusService.editProjectStatus(
|
||||
tenantId,
|
||||
projectId,
|
||||
status
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the billable entries of the given project.
|
||||
* @param {number} tenantId
|
||||
* @param {number} projectId
|
||||
* @param {ProjectBillableEntriesQuery} query
|
||||
* @returns {Promise<ProjectBillableEntry[]>}
|
||||
*/
|
||||
public getProjectBillableEntries = (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
query?: ProjectBillableEntriesQuery
|
||||
): Promise<ProjectBillableEntry[]> => {
|
||||
return this.getProjectBillable.getProjectBillableEntries(
|
||||
tenantId,
|
||||
projectId,
|
||||
query
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
|
||||
@Service()
|
||||
export class ProjectsValidator {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Validate contact exists.
|
||||
* @param {number} tenantId
|
||||
* @param {number} contactId
|
||||
*/
|
||||
public async validateContactExists(tenantId: number, contactId: number) {
|
||||
const { Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
// Validate customer existance.
|
||||
await Contact.query()
|
||||
.modify('customer')
|
||||
.findById(contactId)
|
||||
.throwIfNotFound();
|
||||
}
|
||||
}
|
||||
22
packages/server/src/services/Projects/Projects/_types.ts
Normal file
22
packages/server/src/services/Projects/Projects/_types.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
ProjectBillableEntriesQuery,
|
||||
ProjectBillableEntry,
|
||||
ProjectBillableType,
|
||||
} from '@/interfaces';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export interface IncreaseInvoicedTaskQueuePayload {
|
||||
tenantId: number;
|
||||
projectRefId: number;
|
||||
projectRefInvoicedAmount: number;
|
||||
trx?: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface ProjectBillableGetter {
|
||||
type: ProjectBillableType;
|
||||
getter: (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
query: ProjectBillableEntriesQuery
|
||||
) => Promise<ProjectBillableEntry[]>;
|
||||
}
|
||||
8
packages/server/src/services/Projects/Projects/_utils.ts
Normal file
8
packages/server/src/services/Projects/Projects/_utils.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { IItemEntry, IItemEntryDTO } from '@/interfaces';
|
||||
|
||||
export const filterEntriesByRefType = (
|
||||
entries: (IItemEntry | IItemEntryDTO)[],
|
||||
projectRefType: string
|
||||
) => {
|
||||
return entries.filter((entry) => entry.projectRefType === projectRefType);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum ERRORS {
|
||||
ITEM_ENTRIES_REF_IDS_NOT_FOUND = 'ITEM_ENTRIES_REF_IDS_NOT_FOUND',
|
||||
}
|
||||
74
packages/server/src/services/Projects/Tasks/CreateTask.ts
Normal file
74
packages/server/src/services/Projects/Tasks/CreateTask.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
ICreateTaskDTO,
|
||||
IProjectTaskCreatePOJO,
|
||||
ITaskCreatedEventPayload,
|
||||
ITaskCreateEventPayload,
|
||||
ITaskCreatingEventPayload,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class CreateTaskService {
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Creates a new task.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} projectId - Project id.
|
||||
* @param {ICreateTaskDTO} taskDTO - Project's task DTO.
|
||||
* @returns {Promise<IProjectTaskCreatePOJO>}
|
||||
*/
|
||||
public createTask = async (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
taskDTO: ICreateTaskDTO
|
||||
): Promise<IProjectTaskCreatePOJO> => {
|
||||
const { Task, Project } = this.tenancy.models(tenantId);
|
||||
|
||||
// Validate project existance.
|
||||
const project = await Project.query().findById(projectId).throwIfNotFound();
|
||||
|
||||
// Triggers `onProjectTaskCreate` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTask.onCreate, {
|
||||
tenantId,
|
||||
taskDTO,
|
||||
} as ITaskCreateEventPayload);
|
||||
|
||||
// Creates a new project under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onProjectTaskCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTask.onCreating, {
|
||||
tenantId,
|
||||
taskDTO,
|
||||
trx,
|
||||
} as ITaskCreatingEventPayload);
|
||||
|
||||
const task = await Task.query().insert({
|
||||
...taskDTO,
|
||||
actualHours: 0,
|
||||
projectId,
|
||||
});
|
||||
// Triggers `onProjectTaskCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTask.onCreated, {
|
||||
tenantId,
|
||||
taskDTO,
|
||||
task,
|
||||
trx,
|
||||
} as ITaskCreatedEventPayload);
|
||||
|
||||
return task;
|
||||
});
|
||||
};
|
||||
}
|
||||
64
packages/server/src/services/Projects/Tasks/DeleteTask.ts
Normal file
64
packages/server/src/services/Projects/Tasks/DeleteTask.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
ITaskDeletedEventPayload,
|
||||
ITaskDeleteEventPayload,
|
||||
ITaskDeletingEventPayload,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class DeleteTaskService {
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Deletes the give project.
|
||||
* @param {number} projectId -
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteTask = async (
|
||||
tenantId: number,
|
||||
taskId: number
|
||||
): Promise<void> => {
|
||||
const { Task } = this.tenancy.models(tenantId);
|
||||
|
||||
// Validate customer existance.
|
||||
const oldTask = await Task.query().findById(taskId).throwIfNotFound();
|
||||
|
||||
// Triggers `onDeleteProjectTask` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTask.onDelete, {
|
||||
tenantId,
|
||||
taskId,
|
||||
} as ITaskDeleteEventPayload);
|
||||
|
||||
// Deletes the given project under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onProjectDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTask.onDeleting, {
|
||||
tenantId,
|
||||
oldTask,
|
||||
trx,
|
||||
} as ITaskDeletingEventPayload);
|
||||
|
||||
// Deletes the project object from the storage.
|
||||
await Task.query(trx).findById(taskId).delete();
|
||||
|
||||
// Triggers `onProjectDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTask.onDeleted, {
|
||||
tenantId,
|
||||
oldTask,
|
||||
trx,
|
||||
} as ITaskDeletedEventPayload);
|
||||
});
|
||||
};
|
||||
}
|
||||
77
packages/server/src/services/Projects/Tasks/EditTask.ts
Normal file
77
packages/server/src/services/Projects/Tasks/EditTask.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
IEditTaskDTO,
|
||||
IProjectTaskEditPOJO,
|
||||
ITaskEditedEventPayload,
|
||||
ITaskEditEventPayload,
|
||||
ITaskEditingEventPayload,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class EditTaskService {
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Edits a new credit note.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} taskId -
|
||||
* @param {IEditTaskDTO} taskDTO -
|
||||
* @returns {IProjectTaskEditPOJO}
|
||||
*/
|
||||
public editTask = async (
|
||||
tenantId: number,
|
||||
taskId: number,
|
||||
taskDTO: IEditTaskDTO
|
||||
): Promise<IProjectTaskEditPOJO> => {
|
||||
const { Task } = this.tenancy.models(tenantId);
|
||||
|
||||
// Validate task existance.
|
||||
const oldTask = await Task.query().findById(taskId).throwIfNotFound();
|
||||
|
||||
// Triggers `onProjectTaskEdit` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTask.onEdit, {
|
||||
tenantId,
|
||||
taskId,
|
||||
taskDTO,
|
||||
} as ITaskEditEventPayload);
|
||||
|
||||
// Edits the given project under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onProjectTaskEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTask.onEditing, {
|
||||
tenantId,
|
||||
oldTask,
|
||||
taskDTO,
|
||||
trx,
|
||||
} as ITaskEditingEventPayload);
|
||||
|
||||
// Upsert the project's task object.
|
||||
const task = await Task.query(trx).upsertGraph({
|
||||
id: taskId,
|
||||
...taskDTO,
|
||||
});
|
||||
// Triggers `onProjectTaskEdited` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTask.onEdited, {
|
||||
tenantId,
|
||||
oldTask,
|
||||
taskDTO,
|
||||
task,
|
||||
trx,
|
||||
} as ITaskEditedEventPayload);
|
||||
|
||||
return task;
|
||||
});
|
||||
};
|
||||
}
|
||||
33
packages/server/src/services/Projects/Tasks/GetTask.ts
Normal file
33
packages/server/src/services/Projects/Tasks/GetTask.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { IProjectTaskGetPOJO } from '@/interfaces';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { TaskTransformer } from './TaskTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetTaskService {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve the tasks list.
|
||||
* @param {number} tenantId - Tenant Id.
|
||||
* @param {number} taskId - Task Id.
|
||||
* @returns {Promise<IProjectTaskGetPOJO>}
|
||||
*/
|
||||
public getTask = async (
|
||||
tenantId: number,
|
||||
taskId: number
|
||||
): Promise<IProjectTaskGetPOJO> => {
|
||||
const { Task } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the project.
|
||||
const task = await Task.query().findById(taskId).throwIfNotFound();
|
||||
|
||||
// Transformes and returns object.
|
||||
return this.transformer.transform(tenantId, task, new TaskTransformer());
|
||||
};
|
||||
}
|
||||
33
packages/server/src/services/Projects/Tasks/GetTasks.ts
Normal file
33
packages/server/src/services/Projects/Tasks/GetTasks.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { IProjectTaskGetPOJO } from '@/interfaces';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { TaskTransformer } from './TaskTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetTasksService {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve the tasks list.
|
||||
* @param {number} tenantId - Tenant Id.
|
||||
* @param {number} taskId - Task Id.
|
||||
* @returns {}
|
||||
*/
|
||||
public getTasks = async (
|
||||
tenantId: number,
|
||||
projectId: number
|
||||
): Promise<IProjectTaskGetPOJO[]> => {
|
||||
const { Task } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the project.
|
||||
const tasks = await Task.query().where('projectId', projectId);
|
||||
|
||||
// Transformes and returns object.
|
||||
return this.transformer.transform(tenantId, tasks, new TaskTransformer());
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatMinutes } from 'utils/formatMinutes';
|
||||
|
||||
export class TaskTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'estimateHoursFormatted',
|
||||
'actualHoursFormatted',
|
||||
'invoicedHoursFormatted',
|
||||
'billableHoursFormatted',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted estimate hours.
|
||||
* @returns {string}
|
||||
*/
|
||||
public estimateHoursFormatted = (task): string => {
|
||||
return formatMinutes(task.estimateHours);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted actual hours.
|
||||
* @returns {string}
|
||||
*/
|
||||
public actualHoursFormatted = (task): string => {
|
||||
return formatMinutes(task.actualHours);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted billable hours.
|
||||
* @returns {string}
|
||||
*/
|
||||
public billableHoursFormatted = (task): string => {
|
||||
return formatMinutes(task.billableHours);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retreives the formatted invoiced hours.
|
||||
* @returns {string}
|
||||
*/
|
||||
public invoicedHoursFormatted = (task): string => {
|
||||
return formatMinutes(task.invoicedHours);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
ICreateTaskDTO,
|
||||
IEditTaskDTO,
|
||||
IProjectTaskCreatePOJO,
|
||||
IProjectTaskEditPOJO,
|
||||
IProjectTaskGetPOJO,
|
||||
} from '@/interfaces';
|
||||
import { CreateTaskService } from './CreateTask';
|
||||
import { DeleteTaskService } from './DeleteTask';
|
||||
import { GetTaskService } from './GetTask';
|
||||
import { EditTaskService } from './EditTask';
|
||||
import { GetTasksService } from './GetTasks';
|
||||
|
||||
@Service()
|
||||
export class TasksApplication {
|
||||
@Inject()
|
||||
private createTaskService: CreateTaskService;
|
||||
|
||||
@Inject()
|
||||
private editTaskService: EditTaskService;
|
||||
|
||||
@Inject()
|
||||
private deleteTaskService: DeleteTaskService;
|
||||
|
||||
@Inject()
|
||||
private getTaskService: GetTaskService;
|
||||
|
||||
@Inject()
|
||||
private getTasksService: GetTasksService;
|
||||
|
||||
/**
|
||||
* Creates a new task associated to specific project.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} project - Project id.
|
||||
* @param {ICreateTaskDTO} taskDTO - Create project DTO.
|
||||
* @return {Promise<IProjectTaskCreatePOJO>}
|
||||
*/
|
||||
public createTask = (
|
||||
tenantId: number,
|
||||
projectId: number,
|
||||
taskDTO: ICreateTaskDTO
|
||||
): Promise<IProjectTaskCreatePOJO> => {
|
||||
return this.createTaskService.createTask(tenantId, projectId, taskDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits details of the given task.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} vendorId - Vendor id.
|
||||
* @param {IEditTaskDTO} projectDTO - Create project DTO.
|
||||
* @returns {Promise<IProjectTaskEditPOJO>}
|
||||
*/
|
||||
public editTask = (
|
||||
tenantId: number,
|
||||
taskId: number,
|
||||
taskDTO: IEditTaskDTO
|
||||
): Promise<IProjectTaskEditPOJO> => {
|
||||
return this.editTaskService.editTask(tenantId, taskId, taskDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the given task.
|
||||
* @param {number} tenantId
|
||||
* @param {number} taskId - Task id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public deleteTask = (tenantId: number, taskId: number): Promise<void> => {
|
||||
return this.deleteTaskService.deleteTask(tenantId, taskId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the given task details.
|
||||
* @param {number} tenantId
|
||||
* @param {number} taskId
|
||||
* @returns {Promise<IProjectTaskGetPOJO>}
|
||||
*/
|
||||
public getTask = (
|
||||
tenantId: number,
|
||||
taskId: number
|
||||
): Promise<IProjectTaskGetPOJO> => {
|
||||
return this.getTaskService.getTask(tenantId, taskId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the vendors paginated list.
|
||||
* @param {number} tenantId
|
||||
* @param {IVendorsFilter} filterDTO
|
||||
* @returns {Promise<IProjectTaskGetPOJO[]>}
|
||||
*/
|
||||
public getTasks = (
|
||||
tenantId: number,
|
||||
projectId: number
|
||||
): Promise<IProjectTaskGetPOJO[]> => {
|
||||
return this.getTasksService.getTasks(tenantId, projectId);
|
||||
};
|
||||
}
|
||||
5
packages/server/src/services/Projects/Tasks/constants.ts
Normal file
5
packages/server/src/services/Projects/Tasks/constants.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum ProjectTaskChargeType {
|
||||
Fixed = 'FIXED',
|
||||
Time = 'TIME',
|
||||
NonChargable = 'NON_CHARGABLE',
|
||||
}
|
||||
72
packages/server/src/services/Projects/Times/CreateTime.ts
Normal file
72
packages/server/src/services/Projects/Times/CreateTime.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
IProjectTimeCreatedEventPayload,
|
||||
IProjectTimeCreateDTO,
|
||||
IProjectTimeCreateEventPayload,
|
||||
IProjectTimeCreatePOJO,
|
||||
IProjectTimeCreatingEventPayload,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class CreateTimeService {
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Creates a new time.
|
||||
* @param {number} taskId -
|
||||
* @param {IProjectTimeCreateDTO} timeDTO -
|
||||
* @returns {Promise<IProjectTimeCreatePOJO>}
|
||||
*/
|
||||
public createTime = async (
|
||||
tenantId: number,
|
||||
taskId: number,
|
||||
timeDTO: IProjectTimeCreateDTO
|
||||
): Promise<IProjectTimeCreatePOJO> => {
|
||||
const { Time, Task } = this.tenancy.models(tenantId);
|
||||
|
||||
const task = await Task.query().findById(taskId).throwIfNotFound();
|
||||
|
||||
// Triggers `onProjectTimeCreate` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTime.onCreate, {
|
||||
tenantId,
|
||||
timeDTO,
|
||||
} as IProjectTimeCreateEventPayload);
|
||||
|
||||
// Creates a new project under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onProjectTimeCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTime.onCreating, {
|
||||
tenantId,
|
||||
timeDTO,
|
||||
trx,
|
||||
} as IProjectTimeCreatingEventPayload);
|
||||
|
||||
const time = await Time.query().insert({
|
||||
...timeDTO,
|
||||
taskId,
|
||||
projectId: task.projectId,
|
||||
});
|
||||
|
||||
// Triggers `onProjectTimeCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTime.onCreated, {
|
||||
tenantId,
|
||||
time,
|
||||
trx,
|
||||
} as IProjectTimeCreatedEventPayload);
|
||||
|
||||
return time;
|
||||
});
|
||||
};
|
||||
}
|
||||
61
packages/server/src/services/Projects/Times/DeleteTime.ts
Normal file
61
packages/server/src/services/Projects/Times/DeleteTime.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
IProjectTimeDeletedEventPayload,
|
||||
IProjectTimeDeleteEventPayload,
|
||||
IProjectTimeDeletingEventPayload,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class DeleteTimeService {
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Deletes the give task's time that associated to the given project.
|
||||
* @param {number} projectId -
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteTime = async (tenantId: number, timeId: number) => {
|
||||
const { Time } = this.tenancy.models(tenantId);
|
||||
|
||||
// Validate customer existance.
|
||||
const oldTime = await Time.query().findById(timeId).throwIfNotFound();
|
||||
|
||||
// Triggers `onProjectDelete` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTime.onDelete, {
|
||||
tenantId,
|
||||
timeId,
|
||||
} as IProjectTimeDeleteEventPayload);
|
||||
|
||||
// Deletes the given project under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onProjectDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTime.onDeleting, {
|
||||
tenantId,
|
||||
oldTime,
|
||||
trx,
|
||||
} as IProjectTimeDeletingEventPayload);
|
||||
|
||||
// Upsert the project object.
|
||||
await Time.query(trx).findById(timeId).delete();
|
||||
|
||||
// Triggers `onProjectDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTime.onDeleted, {
|
||||
tenantId,
|
||||
oldTime,
|
||||
trx,
|
||||
} as IProjectTimeDeletedEventPayload);
|
||||
});
|
||||
};
|
||||
}
|
||||
76
packages/server/src/services/Projects/Times/EditTime.ts
Normal file
76
packages/server/src/services/Projects/Times/EditTime.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
IProjectTimeEditDTO,
|
||||
IProjectTimeEditedEventPayload,
|
||||
IProjectTimeEditEventPayload,
|
||||
IProjectTimeEditingEventPayload,
|
||||
IProjectTimeEditPOJO,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
|
||||
@Service()
|
||||
export class EditTimeService {
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Edits the given project's time that associated to the given task.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} taskId - Task id.
|
||||
* @returns {Promise<IProjectTimeEditPOJO>}
|
||||
*/
|
||||
public editTime = async (
|
||||
tenantId: number,
|
||||
timeId: number,
|
||||
timeDTO: IProjectTimeEditDTO
|
||||
): Promise<IProjectTimeEditPOJO> => {
|
||||
const { Time } = this.tenancy.models(tenantId);
|
||||
|
||||
// Validate customer existance.
|
||||
const oldTime = await Time.query().findById(timeId).throwIfNotFound();
|
||||
|
||||
// Triggers `onProjectEdit` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTime.onEdit, {
|
||||
tenantId,
|
||||
oldTime,
|
||||
timeDTO,
|
||||
} as IProjectTimeEditEventPayload);
|
||||
|
||||
// Edits the given project under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onProjectEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTime.onEditing, {
|
||||
tenantId,
|
||||
timeDTO,
|
||||
oldTime,
|
||||
trx,
|
||||
} as IProjectTimeEditingEventPayload);
|
||||
|
||||
// Upsert the task's time object.
|
||||
const time = await Time.query(trx).upsertGraphAndFetch({
|
||||
id: timeId,
|
||||
...timeDTO,
|
||||
});
|
||||
// Triggers `onProjectEdited` event.
|
||||
await this.eventPublisher.emitAsync(events.projectTime.onEdited, {
|
||||
tenantId,
|
||||
oldTime,
|
||||
timeDTO,
|
||||
time,
|
||||
trx,
|
||||
} as IProjectTimeEditedEventPayload);
|
||||
|
||||
return time;
|
||||
});
|
||||
};
|
||||
}
|
||||
37
packages/server/src/services/Projects/Times/GetTime.ts
Normal file
37
packages/server/src/services/Projects/Times/GetTime.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { IProjectTimeGetPOJO } from '@/interfaces';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { TimeTransformer } from './TimeTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetTimeService {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve the tasks list.
|
||||
* @param {number} tenantId - Tenant Id.
|
||||
* @param {number} taskId - Task Id.
|
||||
* @returns {Promise<IProjectTimeGetPOJO>}
|
||||
*/
|
||||
public getTime = async (
|
||||
tenantId: number,
|
||||
timeId: number
|
||||
): Promise<IProjectTimeGetPOJO> => {
|
||||
const { Time } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the project.
|
||||
const time = await Time.query()
|
||||
.findById(timeId)
|
||||
.withGraphFetched('project.contact')
|
||||
.withGraphFetched('task')
|
||||
.throwIfNotFound();
|
||||
|
||||
// Transformes and returns object.
|
||||
return this.transformer.transform(tenantId, time, new TimeTransformer());
|
||||
};
|
||||
}
|
||||
36
packages/server/src/services/Projects/Times/GetTimes.ts
Normal file
36
packages/server/src/services/Projects/Times/GetTimes.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IProjectTimeGetPOJO } from '@/interfaces';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { TimeTransformer } from './TimeTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetTimelineService {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve the tasks list.
|
||||
* @param {number} tenantId - Tenant Id.
|
||||
* @param {number} taskId - Task Id.
|
||||
* @returns {Promise<IProjectTimeGetPOJO[]>}
|
||||
*/
|
||||
public getTimeline = async (
|
||||
tenantId: number,
|
||||
projectId: number
|
||||
): Promise<IProjectTimeGetPOJO[]> => {
|
||||
const { Time } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the project.
|
||||
const times = await Time.query()
|
||||
.where('projectId', projectId)
|
||||
.withGraphFetched('project.contact')
|
||||
.withGraphFetched('task');
|
||||
|
||||
// Transformes and returns object.
|
||||
return this.transformer.transform(tenantId, times, new TimeTransformer());
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export class SyncActualTimeTask {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Increases the actual time of the given task.
|
||||
* @param {number} tenantId
|
||||
* @param {number} taskId
|
||||
* @param {number} actualHours
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public increaseActualTimeTask = async (
|
||||
tenantId: number,
|
||||
taskId: number,
|
||||
actualHours: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { Task } = this.tenancy.models(tenantId);
|
||||
|
||||
await Task.query(trx)
|
||||
.findById(taskId)
|
||||
.increment('actualHours', actualHours);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decreases the actual time of the given task.
|
||||
* @param {number} tenantId
|
||||
* @param {number} taskId
|
||||
* @param {number} actualHours
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public decreaseActualTimeTask = async (
|
||||
tenantId: number,
|
||||
taskId: number,
|
||||
actualHours: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { Task } = this.tenancy.models(tenantId);
|
||||
|
||||
await Task.query(trx)
|
||||
.findById(taskId)
|
||||
.decrement('actualHours', actualHours);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
IProjectTimeCreatedEventPayload,
|
||||
IProjectTimeDeletedEventPayload,
|
||||
IProjectTimeEditedEventPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { SyncActualTimeTask } from './SyncActualTimeTask';
|
||||
|
||||
@Service()
|
||||
export class SyncActualTimeTaskSubscriber {
|
||||
@Inject()
|
||||
private syncActualTimeTask: SyncActualTimeTask;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
* @param bus
|
||||
*/
|
||||
attach(bus) {
|
||||
bus.subscribe(
|
||||
events.projectTime.onCreated,
|
||||
this.handleIncreaseActualTimeOnTimeCreate
|
||||
);
|
||||
bus.subscribe(
|
||||
events.projectTime.onDeleted,
|
||||
this.handleDecreaseActaulTimeOnTimeDelete
|
||||
);
|
||||
bus.subscribe(
|
||||
events.projectTime.onEdited,
|
||||
this.handleAdjustActualTimeOnTimeEdited
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles increasing the actual time of the task once time entry be created.
|
||||
* @param {IProjectTimeCreatedEventPayload} payload -
|
||||
*/
|
||||
private handleIncreaseActualTimeOnTimeCreate = async ({
|
||||
tenantId,
|
||||
time,
|
||||
trx,
|
||||
}: IProjectTimeCreatedEventPayload) => {
|
||||
await this.syncActualTimeTask.increaseActualTimeTask(
|
||||
tenantId,
|
||||
time.taskId,
|
||||
time.duration,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle decreasing the actual time of the tsak once time entry be deleted.
|
||||
* @param {IProjectTimeDeletedEventPayload} payload
|
||||
*/
|
||||
private handleDecreaseActaulTimeOnTimeDelete = async ({
|
||||
tenantId,
|
||||
oldTime,
|
||||
trx,
|
||||
}: IProjectTimeDeletedEventPayload) => {
|
||||
await this.syncActualTimeTask.decreaseActualTimeTask(
|
||||
tenantId,
|
||||
oldTime.taskId,
|
||||
oldTime.duration,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle adjusting the actual time of the task once time be edited.
|
||||
* @param {IProjectTimeEditedEventPayload} payload -
|
||||
*/
|
||||
private handleAdjustActualTimeOnTimeEdited = async ({
|
||||
tenantId,
|
||||
time,
|
||||
oldTime,
|
||||
trx,
|
||||
}: IProjectTimeEditedEventPayload) => {
|
||||
await this.syncActualTimeTask.decreaseActualTimeTask(
|
||||
tenantId,
|
||||
oldTime.taskId,
|
||||
oldTime.duration,
|
||||
trx
|
||||
);
|
||||
await this.syncActualTimeTask.increaseActualTimeTask(
|
||||
tenantId,
|
||||
time.taskId,
|
||||
time.duration,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import Time from 'models/Time';
|
||||
import { formatMinutes } from 'utils/formatMinutes';
|
||||
|
||||
export class TimeTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return ['projectName', 'taskName', 'customerName', 'durationFormatted'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['project', 'task'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the project name that associated to the time entry.
|
||||
* @param {Time} time
|
||||
* @returns {string}
|
||||
*/
|
||||
public projectName = (time: Time) => {
|
||||
return time.project.name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the task name that associated to the time entry.
|
||||
* @param {Time} time
|
||||
* @returns {string}
|
||||
*/
|
||||
public taskName = (time: Time) => {
|
||||
return time.task.name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the customer name that associated to the task of the time entry.
|
||||
* @param {Time} time
|
||||
* @returns {string}
|
||||
*/
|
||||
public customerName = (time: Time) => {
|
||||
return time?.project?.contact?.displayName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted duration.
|
||||
* @param {Time} time
|
||||
* @returns {string}
|
||||
*/
|
||||
public durationFormatted = (time: Time) => {
|
||||
return formatMinutes(time.duration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { CreateTimeService } from './CreateTime';
|
||||
import { EditTimeService } from './EditTime';
|
||||
import { GetTimelineService } from './GetTimes';
|
||||
import { GetTimeService } from './GetTime';
|
||||
import { DeleteTimeService } from './DeleteTime';
|
||||
import {
|
||||
IProjectTimeCreateDTO,
|
||||
IProjectTimeCreatePOJO,
|
||||
IProjectTimeEditDTO,
|
||||
IProjectTimeEditPOJO,
|
||||
IProjectTimeGetPOJO,
|
||||
} from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export class TimesApplication {
|
||||
@Inject()
|
||||
private createTimeService: CreateTimeService;
|
||||
|
||||
@Inject()
|
||||
private editTimeService: EditTimeService;
|
||||
|
||||
@Inject()
|
||||
private deleteTimeService: DeleteTimeService;
|
||||
|
||||
@Inject()
|
||||
private getTimeService: GetTimeService;
|
||||
|
||||
@Inject()
|
||||
private getTimelineService: GetTimelineService;
|
||||
|
||||
/**
|
||||
* Creates a new time for specific project's task.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {IProjectTimeCreateDTO} timeDTO - Create project's time DTO.
|
||||
* @return {Promise<IProjectTimeCreatePOJO>}
|
||||
*/
|
||||
public createTime = (
|
||||
tenantId: number,
|
||||
taskId: number,
|
||||
timeDTO: IProjectTimeCreateDTO
|
||||
): Promise<IProjectTimeCreatePOJO> => {
|
||||
return this.createTimeService.createTime(tenantId, taskId, timeDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits details of the given task.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} vendorId - Vendor id.
|
||||
* @param {IProjectCreateDTO} projectDTO - Create project DTO.
|
||||
* @returns {Promise<IProjectTimeEditPOJO>}
|
||||
*/
|
||||
public editTime = (
|
||||
tenantId: number,
|
||||
timeId: number,
|
||||
taskDTO: IProjectTimeEditDTO
|
||||
): Promise<IProjectTimeEditPOJO> => {
|
||||
return this.editTimeService.editTime(tenantId, timeId, taskDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the given task.
|
||||
* @param {number} tenantId
|
||||
* @param {number} taskId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public deleteTime = (tenantId: number, timeId: number): Promise<void> => {
|
||||
return this.deleteTimeService.deleteTime(tenantId, timeId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the given task details.
|
||||
* @param {number} tenantId
|
||||
* @param {number} timeId
|
||||
* @returns {Promise<IProjectTimeGetPOJO>}
|
||||
*/
|
||||
public getTime = (
|
||||
tenantId: number,
|
||||
timeId: number
|
||||
): Promise<IProjectTimeGetPOJO> => {
|
||||
return this.getTimeService.getTime(tenantId, timeId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the vendors paginated list.
|
||||
* @param {number} tenantId
|
||||
* @param {IVendorsFilter} filterDTO
|
||||
* @returns {Promise<IProjectTimeGetPOJO[]>}
|
||||
*/
|
||||
public getTimeline = (
|
||||
tenantId: number,
|
||||
projectId: number
|
||||
): Promise<IProjectTimeGetPOJO[]> => {
|
||||
return this.getTimelineService.getTimeline(tenantId, projectId);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user