Skip to content

Commit

Permalink
feature: fix the status filter on the contracts and invoices page
Browse files Browse the repository at this point in the history
  • Loading branch information
LuukBlankenstijn committed Nov 17, 2024
1 parent 7a719d2 commit 60d0611
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 43 deletions.
4 changes: 2 additions & 2 deletions src/entity/activity/ContractActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class ContractActivity extends BaseActivity {
/** Contract related to this activity */
@ManyToOne(() => Contract, (contract) => contract.activities, {
onDelete: 'CASCADE',
})
})
@JoinColumn({ name: 'contractId' })
contract!: Contract;

Expand All @@ -25,7 +25,7 @@ export class ContractActivity extends BaseActivity {
enum: ContractStatus,
nullable: true,
update: false,
})
})
subType!: ContractStatus | null;

getRelatedEntity(): BaseEnt {
Expand Down
4 changes: 2 additions & 2 deletions src/entity/activity/InvoiceActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class InvoiceActivity extends BaseActivity {
/** Invoice related to this activity */
@ManyToOne(() => Invoice, (invoice) => invoice.activities, {
onDelete: 'CASCADE',
})
})
@JoinColumn({ name: 'invoiceId' })
invoice!: Invoice;

Expand All @@ -27,7 +27,7 @@ export class InvoiceActivity extends BaseActivity {
enum: InvoiceStatus,
nullable: true,
update: false,
})
})
subType!: InvoiceStatus | null;

getRelatedEntity(): BaseEnt {
Expand Down
4 changes: 2 additions & 2 deletions src/entity/activity/ProductInstanceActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class ProductInstanceActivity extends BaseActivity {
/** ProductInstance related to this activity */
@ManyToOne(() => ProductInstance, (productInstance) => productInstance.activities, {
onDelete: 'CASCADE',
})
})
@JoinColumn({ name: 'productInstanceId' })
productInstance!: ProductInstance;

Expand All @@ -27,7 +27,7 @@ export class ProductInstanceActivity extends BaseActivity {
enum: ProductInstanceStatus,
nullable: true,
update: false,
})
})
subType!: ProductInstanceStatus | null;

getRelatedEntity(): BaseEnt {
Expand Down
61 changes: 60 additions & 1 deletion src/helpers/filters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FindOptionsWhere, ILike, In } from 'typeorm';
import { Brackets, FindOptionsWhere, ILike, In, SelectQueryBuilder } from 'typeorm';
import { BaseEnt } from '../entity/BaseEnt';
import { ListOrFilter, ListParams } from '../controllers/ListParams';

Expand Down Expand Up @@ -76,3 +76,62 @@ export function addQueryWhereClause<T extends BaseEnt>(params: ListParams, searc

return conditions.length > 0 ? conditions : undefined;
}

export function addQueryBuilderFilters(queryBuilder: SelectQueryBuilder<any>, filters: ListOrFilter[]) {
filters.forEach(({ column, values }: ListOrFilter, index: number) => {
// only allows for one level deep relations
if (column.includes('.')) {
const alias = column.split('.')[0];

// only join if alias is not yet joined
const aliasExists = queryBuilder.expressionMap.aliases.some((a) => a.name === alias);
if (!aliasExists) {
if (alias === 'activities') {
// subquery to join only the latest related activity
queryBuilder.innerJoin(
`${queryBuilder.alias}.${alias}`,
alias,
`${alias}.createdAt = (
SELECT MAX(subQuery.updatedAt)
FROM ${queryBuilder.alias}_activity subQuery
WHERE subQuery.${queryBuilder.alias}Id = ${queryBuilder.alias}.id AND subQuery.subType IS NOT NULL
)`);
} else {
queryBuilder.innerJoin(`${queryBuilder.alias}.${alias}`, alias);
}
}
}
const paramName = `param_${index}`;
// avoid ambiguity in the where clause
if (!column.includes('.')) {
column = `${queryBuilder.alias}.${column}`;
}
queryBuilder.andWhere(`${column} IN (:...${paramName})`, { [paramName]: values });
});
}

export function addQueryBuilderSearch(queryBuilder: SelectQueryBuilder<any>, searchString: string, searchFields: string[]) {
searchFields.forEach((field) => {
// only allows for one level deep relations
if (field.includes('.')) {
const alias = field.split('.')[0];

// only join if alias is not yet joined
const aliasExists = queryBuilder.expressionMap.aliases.some((a) => a.name === alias);
if (!aliasExists) {
queryBuilder.innerJoin(`${queryBuilder.alias}.${alias}`, alias);
}
}
});
queryBuilder.andWhere(
new Brackets((qb) => {
searchFields.forEach(field => {
// avoid ambiguity in the where clause
if (!field.includes('.')) {
field = `${queryBuilder.alias}.${field}`;
}
qb.orWhere(`LOWER(${field}) LIKE LOWER(:value)`, { value: `%${searchString}%` });
});
}),
);
}
8 changes: 4 additions & 4 deletions src/pdfgenerator/PdfGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export default class PdfGenerator {
template = this.replaceAllSafe(template, '{{senderfunction}}', sender.function);

template = replaceAll(template, '{{dateday}}', date.getDate().toString());
template = replaceAll(template, '{{datemonth}}', (date.getMonth()+1).toString());
template = replaceAll(template, '{{datemonth}}', (date.getMonth() + 1).toString());
template = replaceAll(template, '{{dateyear}}', date.getFullYear().toString());

if (useInvoiceAddress) {
Expand All @@ -196,7 +196,7 @@ export default class PdfGenerator {
let dueDate = new Date(date);
dueDate.setDate(date.getDate() + 30);
template = replaceAll(template, '{{dueday}}', dueDate.getDate().toString());
template = replaceAll(template, '{{duemonth}}', (dueDate.getMonth()+1).toString());
template = replaceAll(template, '{{duemonth}}', (dueDate.getMonth() + 1).toString());
template = replaceAll(template, '{{dueyear}}', dueDate.getFullYear().toString());

return template;
Expand Down Expand Up @@ -416,7 +416,7 @@ export default class PdfGenerator {
let dueDate = new Date(invoice.startDate);
dueDate.setDate(invoice.startDate.getDate() + 30);
file = replaceAll(file, '{{dueday}}', dueDate.getDate().toString());
file = replaceAll(file, '{{duemonth}}', (dueDate.getMonth()+1).toString());
file = replaceAll(file, '{{duemonth}}', (dueDate.getMonth() + 1).toString());
file = replaceAll(file, '{{dueyear}}', dueDate.getFullYear().toString());

file = replaceAll(file, '{{debtornumber}}', `C${settings.recipient.id}`);
Expand Down Expand Up @@ -478,7 +478,7 @@ export default class PdfGenerator {
let dueDate = new Date(params.date);
dueDate.setDate(params.date.getDate() + 30);
file = replaceAll(file, '{{dueday}}', dueDate.getDate().toString());
file = replaceAll(file, '{{duemonth}}', (dueDate.getMonth()+1).toString());
file = replaceAll(file, '{{duemonth}}', (dueDate.getMonth() + 1).toString());
file = replaceAll(file, '{{dueyear}}', dueDate.getFullYear().toString());

file = replaceAll(file, '{{debtornumber}}', params.recipient.number);
Expand Down
36 changes: 20 additions & 16 deletions src/services/ContractService.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {
FindManyOptions, Repository,
Repository,
} from 'typeorm';
import { ListParams } from '../controllers/ListParams';
import { Contract } from '../entity/Contract';
import { User } from '../entity/User';
import { ApiError, HTTPStatus } from '../helpers/error';
import { ContractActivity } from '../entity/activity/ContractActivity';
// eslint-disable-next-line import/no-cycle
import ActivityService, { FullActivityParams } from './ActivityService';
import ContactService from './ContactService';
import CompanyService from './CompanyService';
Expand All @@ -15,7 +14,7 @@ import { CompanyStatus } from '../entity/enums/CompanyStatus';
import { ActivityType } from '../entity/enums/ActivityType';
import RawQueries, { RecentContract } from '../helpers/rawQueries';
import { ContractStatus } from '../entity/enums/ContractStatus';
import { addQueryWhereClause } from '../helpers/filters';
import { addQueryBuilderFilters, addQueryBuilderSearch } from '../helpers/filters';
import { Roles } from '../entity/enums/Roles';
import { ContractSummary } from '../entity/Summaries';
import {
Expand Down Expand Up @@ -59,22 +58,27 @@ export default class ContractService {
}

async getAllContracts(params: ListParams): Promise<ContractListResponse> {
const findOptions: FindManyOptions<Contract> = {
order: {
[params.sorting?.column ?? 'id']:
params.sorting?.direction ?? 'ASC',
},
};
const queryBuilder = this.repo.createQueryBuilder('contract');

queryBuilder
.skip(params.skip)
.take(params.take)
.orderBy(`${queryBuilder.alias}.${params.sorting?.column ?? 'id'}`, params.sorting?.direction ?? 'ASC')
// initial where to allow chaining andWhere() function calls
.where('1 = 1')
;

if (params.search) {
addQueryBuilderSearch(queryBuilder, params.search, ['title', 'company.name', 'contact.firstName', 'contact.lastNamePreposition', 'contact.lastName']);
}

findOptions.where = addQueryWhereClause<Contract>(params, ['title', 'company.name', 'contact.firstName', 'contact.lastNamePreposition', 'contact.lastName']);
if (params.filters && params.filters.length > 0) {
addQueryBuilderFilters(queryBuilder, params.filters);
}

return {
list: await this.repo.find({
...findOptions,
skip: params.skip,
take: params.take,
}),
count: await this.repo.count(findOptions),
list: await queryBuilder.getMany(),
count: await queryBuilder.clone().getCount(),
};
}

Expand Down
36 changes: 20 additions & 16 deletions src/services/InvoiceService.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
FindManyOptions, Repository,
Repository,
} from 'typeorm';
import { ListParams } from '../controllers/ListParams';
import { Invoice } from '../entity/Invoice';
import { ProductInstance } from '../entity/ProductInstance';
import { User } from '../entity/User';
import { ApiError, HTTPStatus } from '../helpers/error';
import { addQueryWhereClause } from '../helpers/filters';
import { addQueryBuilderFilters, addQueryBuilderSearch } from '../helpers/filters';
import ProductInstanceService from './ProductInstanceService';
import ActivityService, { FullActivityParams } from './ActivityService';
import RawQueries, { ExpiredInvoice } from '../helpers/rawQueries';
Expand Down Expand Up @@ -61,23 +61,27 @@ export default class InvoiceService {
}

async getAllInvoices(params: ListParams): Promise<InvoiceListResponse> {
const findOptions: FindManyOptions<Invoice> = {
order: {
[params.sorting?.column ?? 'id']:
params.sorting?.direction ?? 'ASC',
},
};
const queryBuilder = this.repo.createQueryBuilder('invoice');

queryBuilder
.skip(params.skip)
.take(params.take)
.orderBy(`${queryBuilder.alias}.${params.sorting?.column ?? 'id'}`, params.sorting?.direction ?? 'ASC')
// initial where() to allow chaining andWhere() function calls
.where('1 = 1')
;

if (params.search) {
addQueryBuilderSearch(queryBuilder, params.search, ['title', 'company.name']);
}

findOptions.where = addQueryWhereClause(params, ['title', 'company.name']);
if (params.filters && params.filters.length > 0) {
addQueryBuilderFilters(queryBuilder, params.filters);
}

return {
list: await this.repo.find({
...findOptions,
skip: params.skip,
take: params.take,
}),
count: await this.repo.count(findOptions),
lastSeen: await this.getTreasurerLastSeen(),
list: await queryBuilder.getMany(),
count: await queryBuilder.clone().getCount(),
};
}

Expand Down

0 comments on commit 60d0611

Please sign in to comment.