mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
feat: deleteIfNoRelations
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
export class ModelHasRelationsError extends Error {
|
||||
type: string;
|
||||
|
||||
constructor(type: string = 'ModelHasRelations', message?: string) {
|
||||
message = message || `Entity has relations`;
|
||||
super(message);
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
ExceptionFilter,
|
||||
Catch,
|
||||
ArgumentsHost,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import { ModelHasRelationsError } from '../exceptions/ModelHasRelations.exception';
|
||||
|
||||
@Catch(ModelHasRelationsError)
|
||||
export class ModelHasRelationsFilter implements ExceptionFilter {
|
||||
catch(exception: ModelHasRelationsError, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
const status = HttpStatus.CONFLICT;
|
||||
|
||||
response.status(status).json({
|
||||
errors: [
|
||||
{
|
||||
statusCode: status,
|
||||
type: exception.type || 'MODEL_HAS_RELATIONS',
|
||||
message: exception.message,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
7
packages/server/src/common/types/Objection.d.ts
vendored
Normal file
7
packages/server/src/common/types/Objection.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { QueryBuilder, Model } from 'objection';
|
||||
|
||||
declare module 'objection' {
|
||||
interface QueryBuilder<M extends Model, R = M[]> {
|
||||
deleteIfNoRelations(this: QueryBuilder<M, R>, ...args: any[]): Promise<any>;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import * as path from 'path';
|
||||
import './utils/moment-mysql';
|
||||
import { AppModule } from './modules/App/App.module';
|
||||
import { ServiceErrorFilter } from './common/filters/service-error.filter';
|
||||
import { ModelHasRelationsFilter } from './common/filters/model-has-relations.filter';
|
||||
import { ValidationPipe } from './common/pipes/ClassValidation.pipe';
|
||||
import { ToJsonInterceptor } from './common/interceptors/to-json.interceptor';
|
||||
|
||||
@@ -36,6 +37,7 @@ async function bootstrap() {
|
||||
SwaggerModule.setup('swagger', app, documentFactory);
|
||||
|
||||
app.useGlobalFilters(new ServiceErrorFilter());
|
||||
app.useGlobalFilters(new ModelHasRelationsFilter());
|
||||
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { QueryBuilder, Model } from 'objection';
|
||||
import { ModelHasRelationsError } from '@/common/exceptions/ModelHasRelations.exception';
|
||||
|
||||
interface PaginationResult<M extends Model> {
|
||||
results: M[];
|
||||
@@ -32,15 +33,44 @@ export class PaginationQueryBuilder<
|
||||
};
|
||||
}) as unknown as PaginationQueryBuilderType<M>;
|
||||
}
|
||||
|
||||
async deleteIfNoRelations({
|
||||
type,
|
||||
message,
|
||||
}: {
|
||||
type?: string;
|
||||
message?: string;
|
||||
}) {
|
||||
const relationMappings = this.modelClass().relationMappings;
|
||||
const relationNames = Object.keys(relationMappings || {});
|
||||
|
||||
if (relationNames.length === 0) {
|
||||
// No relations defined
|
||||
return this.delete();
|
||||
}
|
||||
const recordQuery = this.clone();
|
||||
|
||||
relationNames.forEach((relationName: string) => {
|
||||
recordQuery.withGraphFetched(relationName);
|
||||
});
|
||||
const record = await recordQuery;
|
||||
|
||||
const hasRelations = relationNames.some((name) => {
|
||||
const val = record[name];
|
||||
return Array.isArray(val) ? val.length > 0 : val != null;
|
||||
});
|
||||
if (!hasRelations) {
|
||||
return this.clone().delete();
|
||||
} else {
|
||||
throw new ModelHasRelationsError(type, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// New BaseQueryBuilder extending PaginationQueryBuilder
|
||||
export class BaseQueryBuilder<
|
||||
M extends Model,
|
||||
R = M[],
|
||||
> extends PaginationQueryBuilder<M, R> {
|
||||
// You can add more shared query methods here in the future
|
||||
|
||||
changeAmount(whereAttributes, attribute, amount) {
|
||||
const changeMethod = amount > 0 ? 'increment' : 'decrement';
|
||||
|
||||
|
||||
@@ -39,9 +39,6 @@ export class DeleteBranchService {
|
||||
.query()
|
||||
.findById(branchId)
|
||||
.throwIfNotFound();
|
||||
// .queryAndThrowIfHasRelations({
|
||||
// type: ERRORS.BRANCH_HAS_ASSOCIATED_TRANSACTIONS,
|
||||
// });
|
||||
|
||||
// Authorize the branch before deleting.
|
||||
await this.authorize(branchId);
|
||||
@@ -54,8 +51,10 @@ export class DeleteBranchService {
|
||||
trx,
|
||||
} as IBranchDeletePayload);
|
||||
|
||||
await this.branchModel().query().findById(branchId).delete();
|
||||
|
||||
await this.branchModel().query().findById(branchId).deleteIfNoRelations({
|
||||
type: ERRORS.BRANCH_HAS_ASSOCIATED_TRANSACTIONS,
|
||||
message: 'Branch has associated transactions',
|
||||
});
|
||||
// Triggers `onBranchCreate` event.
|
||||
await this.eventPublisher.emitAsync(events.warehouse.onEdited, {
|
||||
oldBranch,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { Customer } from '../models/Customer';
|
||||
import { events } from '@/common/events/events';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { ERRORS } from '../constants';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteCustomer {
|
||||
@@ -36,9 +37,6 @@ export class DeleteCustomer {
|
||||
.query()
|
||||
.findById(customerId)
|
||||
.throwIfNotFound();
|
||||
// .queryAndThrowIfHasRelations({
|
||||
// type: ERRORS.CUSTOMER_HAS_TRANSACTIONS,
|
||||
// });
|
||||
|
||||
// Triggers `onCustomerDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.customers.onDeleting, {
|
||||
@@ -49,8 +47,13 @@ export class DeleteCustomer {
|
||||
// Deletes the customer and associated entities under UOW transaction.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Delete the customer from the storage.
|
||||
await this.customerModel().query(trx).findById(customerId).delete();
|
||||
|
||||
await this.customerModel()
|
||||
.query(trx)
|
||||
.findById(customerId)
|
||||
.deleteIfNoRelations({
|
||||
type: ERRORS.CUSTOMER_HAS_TRANSACTIONS,
|
||||
message: 'Customer has associated transactions',
|
||||
});
|
||||
// Throws `onCustomerDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.customers.onDeleted, {
|
||||
customerId,
|
||||
|
||||
@@ -39,11 +39,8 @@ export class DeleteItemService {
|
||||
// Retrieve the given item or throw not found service error.
|
||||
const oldItem = await this.itemModel()
|
||||
.query()
|
||||
.findById(itemId)
|
||||
.throwIfNotFound();
|
||||
// .queryAndThrowIfHasRelations({
|
||||
// type: ERRORS.ITEM_HAS_ASSOCIATED_TRANSACTIONS,
|
||||
// });
|
||||
.findOne('id', itemId)
|
||||
.deleteIfNoRelations();
|
||||
|
||||
// Delete item in unit of work.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
IVendorEventDeletingPayload,
|
||||
} from '../types/Vendors.types';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { ERRORS } from '../constants';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteVendorService {
|
||||
@@ -34,9 +35,6 @@ export class DeleteVendorService {
|
||||
.query()
|
||||
.findById(vendorId)
|
||||
.throwIfNotFound();
|
||||
// .queryAndThrowIfHasRelations({
|
||||
// type: ERRORS.VENDOR_HAS_TRANSACTIONS,
|
||||
// });
|
||||
|
||||
// Triggers `onVendorDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.vendors.onDeleting, {
|
||||
@@ -47,8 +45,13 @@ export class DeleteVendorService {
|
||||
// Deletes vendor contact under unit-of-work.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Deletes the vendor contact from the storage.
|
||||
await this.vendorModel().query(trx).findById(vendorId).delete();
|
||||
|
||||
await this.vendorModel()
|
||||
.query(trx)
|
||||
.findById(vendorId)
|
||||
.deleteIfNoRelations({
|
||||
type: ERRORS.VENDOR_HAS_TRANSACTIONS,
|
||||
message: 'Vendor has associated transactions',
|
||||
});
|
||||
// Triggers `onVendorDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.vendors.onDeleted, {
|
||||
vendorId,
|
||||
|
||||
@@ -49,9 +49,6 @@ export class DeleteWarehouseService {
|
||||
.query()
|
||||
.findById(warehouseId)
|
||||
.throwIfNotFound();
|
||||
// .queryAndThrowIfHasRelations({
|
||||
// type: ERRORS.WAREHOUSE_HAS_ASSOCIATED_TRANSACTIONS,
|
||||
// });
|
||||
|
||||
// Validates the given warehouse before deleting.
|
||||
await this.authorize(warehouseId);
|
||||
@@ -70,8 +67,13 @@ export class DeleteWarehouseService {
|
||||
eventPayload,
|
||||
);
|
||||
// Deletes the given warehouse from the storage.
|
||||
await this.warehouseModel().query().findById(warehouseId).delete();
|
||||
|
||||
await this.warehouseModel()
|
||||
.query()
|
||||
.findById(warehouseId)
|
||||
.deleteIfNoRelations({
|
||||
type: ERRORS.WAREHOUSE_HAS_ASSOCIATED_TRANSACTIONS,
|
||||
message: 'Warehouse has associated transactions',
|
||||
});
|
||||
// Triggers `onWarehouseCreated`.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.warehouse.onDeleted,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// import { Model } from 'objection';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { BaseModel, BaseQueryBuilder } from '@/models/Model';
|
||||
import { Item } from '@/modules/Items/models/Item';
|
||||
import { Model } from 'objection';
|
||||
|
||||
export class Warehouse extends BaseModel {
|
||||
name!: string;
|
||||
@@ -45,128 +46,134 @@ export class Warehouse extends BaseModel {
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
// static get relationMappings() {
|
||||
// const SaleInvoice = require('models/SaleInvoice');
|
||||
// const SaleEstimate = require('models/SaleEstimate');
|
||||
// const SaleReceipt = require('models/SaleReceipt');
|
||||
// const Bill = require('models/Bill');
|
||||
// const VendorCredit = require('models/VendorCredit');
|
||||
// const CreditNote = require('models/CreditNote');
|
||||
// const InventoryTransaction = require('models/InventoryTransaction');
|
||||
// const WarehouseTransfer = require('models/WarehouseTransfer');
|
||||
// const InventoryAdjustment = require('models/InventoryAdjustment');
|
||||
static get relationMappings() {
|
||||
const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice');
|
||||
const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate');
|
||||
const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt');
|
||||
const { Bill } = require('../../Bills/models/Bill');
|
||||
const { VendorCredit } = require('../../VendorCredit/models/VendorCredit');
|
||||
const { CreditNote } = require('../../CreditNotes/models/CreditNote');
|
||||
const {
|
||||
InventoryTransaction,
|
||||
} = require('../../InventoryCost/models/InventoryTransaction');
|
||||
const {
|
||||
WarehouseTransfer,
|
||||
} = require('../../WarehousesTransfers/models/WarehouseTransfer');
|
||||
const {
|
||||
InventoryAdjustment,
|
||||
} = require('../../InventoryAdjutments/models/InventoryAdjustment');
|
||||
|
||||
// return {
|
||||
// /**
|
||||
// * Warehouse may belongs to associated sale invoices.
|
||||
// */
|
||||
// invoices: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: SaleInvoice.default,
|
||||
// join: {
|
||||
// from: 'warehouses.id',
|
||||
// to: 'sales_invoices.warehouseId',
|
||||
// },
|
||||
// },
|
||||
return {
|
||||
/**
|
||||
* Warehouse may belongs to associated sale invoices.
|
||||
*/
|
||||
invoices: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleInvoice,
|
||||
join: {
|
||||
from: 'warehouses.id',
|
||||
to: 'sales_invoices.warehouseId',
|
||||
},
|
||||
},
|
||||
|
||||
// /**
|
||||
// * Warehouse may belongs to associated sale estimates.
|
||||
// */
|
||||
// estimates: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: SaleEstimate.default,
|
||||
// join: {
|
||||
// from: 'warehouses.id',
|
||||
// to: 'sales_estimates.warehouseId',
|
||||
// },
|
||||
// },
|
||||
/**
|
||||
* Warehouse may belongs to associated sale estimates.
|
||||
*/
|
||||
estimates: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleEstimate,
|
||||
join: {
|
||||
from: 'warehouses.id',
|
||||
to: 'sales_estimates.warehouseId',
|
||||
},
|
||||
},
|
||||
|
||||
// /**
|
||||
// * Warehouse may belongs to associated sale receipts.
|
||||
// */
|
||||
// receipts: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: SaleReceipt.default,
|
||||
// join: {
|
||||
// from: 'warehouses.id',
|
||||
// to: 'sales_receipts.warehouseId',
|
||||
// },
|
||||
// },
|
||||
/**
|
||||
* Warehouse may belongs to associated sale receipts.
|
||||
*/
|
||||
receipts: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: SaleReceipt,
|
||||
join: {
|
||||
from: 'warehouses.id',
|
||||
to: 'sales_receipts.warehouseId',
|
||||
},
|
||||
},
|
||||
|
||||
// /**
|
||||
// * Warehouse may belongs to associated bills.
|
||||
// */
|
||||
// bills: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: Bill.default,
|
||||
// join: {
|
||||
// from: 'warehouses.id',
|
||||
// to: 'bills.warehouseId',
|
||||
// },
|
||||
// },
|
||||
/**
|
||||
* Warehouse may belongs to associated bills.
|
||||
*/
|
||||
bills: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: Bill,
|
||||
join: {
|
||||
from: 'warehouses.id',
|
||||
to: 'bills.warehouseId',
|
||||
},
|
||||
},
|
||||
|
||||
// /**
|
||||
// * Warehouse may belongs to associated credit notes.
|
||||
// */
|
||||
// creditNotes: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: CreditNote.default,
|
||||
// join: {
|
||||
// from: 'warehouses.id',
|
||||
// to: 'credit_notes.warehouseId',
|
||||
// },
|
||||
// },
|
||||
/**
|
||||
* Warehouse may belongs to associated credit notes.
|
||||
*/
|
||||
creditNotes: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: CreditNote,
|
||||
join: {
|
||||
from: 'warehouses.id',
|
||||
to: 'credit_notes.warehouseId',
|
||||
},
|
||||
},
|
||||
|
||||
// /**
|
||||
// * Warehouse may belongs to associated to vendor credits.
|
||||
// */
|
||||
// vendorCredit: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: VendorCredit.default,
|
||||
// join: {
|
||||
// from: 'warehouses.id',
|
||||
// to: 'vendor_credits.warehouseId',
|
||||
// },
|
||||
// },
|
||||
/**
|
||||
* Warehouse may belongs to associated to vendor credits.
|
||||
*/
|
||||
vendorCredit: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: VendorCredit,
|
||||
join: {
|
||||
from: 'warehouses.id',
|
||||
to: 'vendor_credits.warehouseId',
|
||||
},
|
||||
},
|
||||
|
||||
// /**
|
||||
// * Warehouse may belongs to associated to inventory transactions.
|
||||
// */
|
||||
// inventoryTransactions: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: InventoryTransaction.default,
|
||||
// join: {
|
||||
// from: 'warehouses.id',
|
||||
// to: 'inventory_transactions.warehouseId',
|
||||
// },
|
||||
// },
|
||||
/**
|
||||
* Warehouse may belongs to associated to inventory transactions.
|
||||
*/
|
||||
inventoryTransactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: InventoryTransaction,
|
||||
join: {
|
||||
from: 'warehouses.id',
|
||||
to: 'inventory_transactions.warehouseId',
|
||||
},
|
||||
},
|
||||
|
||||
// warehouseTransferTo: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: WarehouseTransfer.default,
|
||||
// join: {
|
||||
// from: 'warehouses.id',
|
||||
// to: 'warehouses_transfers.toWarehouseId',
|
||||
// },
|
||||
// },
|
||||
warehouseTransferTo: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: WarehouseTransfer,
|
||||
join: {
|
||||
from: 'warehouses.id',
|
||||
to: 'warehouses_transfers.toWarehouseId',
|
||||
},
|
||||
},
|
||||
|
||||
// warehouseTransferFrom: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: WarehouseTransfer.default,
|
||||
// join: {
|
||||
// from: 'warehouses.id',
|
||||
// to: 'warehouses_transfers.fromWarehouseId',
|
||||
// },
|
||||
// },
|
||||
warehouseTransferFrom: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: WarehouseTransfer,
|
||||
join: {
|
||||
from: 'warehouses.id',
|
||||
to: 'warehouses_transfers.fromWarehouseId',
|
||||
},
|
||||
},
|
||||
|
||||
// inventoryAdjustment: {
|
||||
// relation: Model.HasManyRelation,
|
||||
// modelClass: InventoryAdjustment.default,
|
||||
// join: {
|
||||
// from: 'warehouses.id',
|
||||
// to: 'inventory_adjustments.warehouseId',
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
inventoryAdjustment: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: InventoryAdjustment,
|
||||
join: {
|
||||
from: 'warehouses.id',
|
||||
to: 'inventory_adjustments.warehouseId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user