mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-24 08:39:49 +00:00
feat: bulk transcations delete
This commit is contained in:
43
packages/server/src/common/dtos/BulkDelete.dto.ts
Normal file
43
packages/server/src/common/dtos/BulkDelete.dto.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { IsArray, IsInt, ArrayNotEmpty } from 'class-validator';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class BulkDeleteDto {
|
||||||
|
@IsArray()
|
||||||
|
@ArrayNotEmpty()
|
||||||
|
@IsInt({ each: true })
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Array of IDs to delete',
|
||||||
|
type: [Number],
|
||||||
|
example: [1, 2, 3],
|
||||||
|
})
|
||||||
|
ids: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ValidateBulkDeleteResponseDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Number of items that can be deleted',
|
||||||
|
example: 2,
|
||||||
|
})
|
||||||
|
deletableCount: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Number of items that cannot be deleted',
|
||||||
|
example: 1,
|
||||||
|
})
|
||||||
|
nonDeletableCount: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'IDs of items that can be deleted',
|
||||||
|
type: [Number],
|
||||||
|
example: [1, 2],
|
||||||
|
})
|
||||||
|
deletableIds: number[];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'IDs of items that cannot be deleted',
|
||||||
|
type: [Number],
|
||||||
|
example: [3],
|
||||||
|
})
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -26,12 +26,17 @@ import { GetAccountTransactionResponseDto } from './dtos/GetAccountTransactionRe
|
|||||||
import { GetAccountTransactionsQueryDto } from './dtos/GetAccountTransactionsQuery.dto';
|
import { GetAccountTransactionsQueryDto } from './dtos/GetAccountTransactionsQuery.dto';
|
||||||
import { GetAccountsQueryDto } from './dtos/GetAccountsQuery.dto';
|
import { GetAccountsQueryDto } from './dtos/GetAccountsQuery.dto';
|
||||||
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
||||||
|
import {
|
||||||
|
BulkDeleteDto,
|
||||||
|
ValidateBulkDeleteResponseDto,
|
||||||
|
} from '@/common/dtos/BulkDelete.dto';
|
||||||
|
|
||||||
@Controller('accounts')
|
@Controller('accounts')
|
||||||
@ApiTags('Accounts')
|
@ApiTags('Accounts')
|
||||||
@ApiExtraModels(AccountResponseDto)
|
@ApiExtraModels(AccountResponseDto)
|
||||||
@ApiExtraModels(AccountTypeResponseDto)
|
@ApiExtraModels(AccountTypeResponseDto)
|
||||||
@ApiExtraModels(GetAccountTransactionResponseDto)
|
@ApiExtraModels(GetAccountTransactionResponseDto)
|
||||||
|
@ApiExtraModels(ValidateBulkDeleteResponseDto)
|
||||||
@ApiCommonHeaders()
|
@ApiCommonHeaders()
|
||||||
export class AccountsController {
|
export class AccountsController {
|
||||||
constructor(private readonly accountsApplication: AccountsApplication) { }
|
constructor(private readonly accountsApplication: AccountsApplication) { }
|
||||||
@@ -83,6 +88,37 @@ export class AccountsController {
|
|||||||
return this.accountsApplication.deleteAccount(id);
|
return this.accountsApplication.deleteAccount(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('validate-bulk-delete')
|
||||||
|
@ApiOperation({
|
||||||
|
summary:
|
||||||
|
'Validates which accounts can be deleted and returns counts of deletable and non-deletable accounts.',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description:
|
||||||
|
'Validation completed. Returns counts and IDs of deletable and non-deletable accounts.',
|
||||||
|
schema: {
|
||||||
|
$ref: getSchemaPath(ValidateBulkDeleteResponseDto),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
async validateBulkDeleteAccounts(
|
||||||
|
@Body() bulkDeleteDto: BulkDeleteDto,
|
||||||
|
): Promise<ValidateBulkDeleteResponseDto> {
|
||||||
|
return this.accountsApplication.validateBulkDeleteAccounts(
|
||||||
|
bulkDeleteDto.ids,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('bulk-delete')
|
||||||
|
@ApiOperation({ summary: 'Deletes multiple accounts in bulk.' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'The accounts have been successfully deleted.',
|
||||||
|
})
|
||||||
|
async bulkDeleteAccounts(@Body() bulkDeleteDto: BulkDeleteDto): Promise<void> {
|
||||||
|
return this.accountsApplication.bulkDeleteAccounts(bulkDeleteDto.ids);
|
||||||
|
}
|
||||||
|
|
||||||
@Post(':id/activate')
|
@Post(':id/activate')
|
||||||
@ApiOperation({ summary: 'Activate the given account.' })
|
@ApiOperation({ summary: 'Activate the given account.' })
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import { GetAccountsService } from './GetAccounts.service';
|
|||||||
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||||
import { AccountsExportable } from './AccountsExportable.service';
|
import { AccountsExportable } from './AccountsExportable.service';
|
||||||
import { AccountsImportable } from './AccountsImportable.service';
|
import { AccountsImportable } from './AccountsImportable.service';
|
||||||
|
import { BulkDeleteAccountsService } from './BulkDeleteAccounts.service';
|
||||||
|
import { ValidateBulkDeleteAccountsService } from './ValidateBulkDeleteAccounts.service';
|
||||||
|
|
||||||
const models = [RegisterTenancyModel(BankAccount)];
|
const models = [RegisterTenancyModel(BankAccount)];
|
||||||
|
|
||||||
@@ -40,7 +42,9 @@ const models = [RegisterTenancyModel(BankAccount)];
|
|||||||
GetAccountTransactionsService,
|
GetAccountTransactionsService,
|
||||||
GetAccountsService,
|
GetAccountsService,
|
||||||
AccountsExportable,
|
AccountsExportable,
|
||||||
AccountsImportable
|
AccountsImportable,
|
||||||
|
BulkDeleteAccountsService,
|
||||||
|
ValidateBulkDeleteAccountsService,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
AccountRepository,
|
AccountRepository,
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ import { GetAccountsService } from './GetAccounts.service';
|
|||||||
import { IFilterMeta } from '@/interfaces/Model';
|
import { IFilterMeta } from '@/interfaces/Model';
|
||||||
import { GetAccountTransactionResponseDto } from './dtos/GetAccountTransactionResponse.dto';
|
import { GetAccountTransactionResponseDto } from './dtos/GetAccountTransactionResponse.dto';
|
||||||
import { GetAccountsQueryDto } from './dtos/GetAccountsQuery.dto';
|
import { GetAccountsQueryDto } from './dtos/GetAccountsQuery.dto';
|
||||||
|
import { BulkDeleteAccountsService } from './BulkDeleteAccounts.service';
|
||||||
|
import { ValidateBulkDeleteAccountsService } from './ValidateBulkDeleteAccounts.service';
|
||||||
|
import { ValidateBulkDeleteResponseDto } from '@/common/dtos/BulkDelete.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountsApplication {
|
export class AccountsApplication {
|
||||||
@@ -37,6 +40,8 @@ export class AccountsApplication {
|
|||||||
private readonly getAccountService: GetAccount,
|
private readonly getAccountService: GetAccount,
|
||||||
private readonly getAccountTransactionsService: GetAccountTransactionsService,
|
private readonly getAccountTransactionsService: GetAccountTransactionsService,
|
||||||
private readonly getAccountsService: GetAccountsService,
|
private readonly getAccountsService: GetAccountsService,
|
||||||
|
private readonly bulkDeleteAccountsService: BulkDeleteAccountsService,
|
||||||
|
private readonly validateBulkDeleteAccountsService: ValidateBulkDeleteAccountsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,4 +133,22 @@ export class AccountsApplication {
|
|||||||
): Promise<Array<GetAccountTransactionResponseDto>> => {
|
): Promise<Array<GetAccountTransactionResponseDto>> => {
|
||||||
return this.getAccountTransactionsService.getAccountsTransactions(filter);
|
return this.getAccountTransactionsService.getAccountsTransactions(filter);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates which accounts can be deleted in bulk.
|
||||||
|
*/
|
||||||
|
public validateBulkDeleteAccounts = (
|
||||||
|
accountIds: number[],
|
||||||
|
): Promise<ValidateBulkDeleteResponseDto> => {
|
||||||
|
return this.validateBulkDeleteAccountsService.validateBulkDeleteAccounts(
|
||||||
|
accountIds,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple accounts in bulk.
|
||||||
|
*/
|
||||||
|
public bulkDeleteAccounts = (accountIds: number[]): Promise<void> => {
|
||||||
|
return this.bulkDeleteAccountsService.bulkDeleteAccounts(accountIds);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeleteAccount } from './DeleteAccount.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BulkDeleteAccountsService {
|
||||||
|
constructor(private readonly deleteAccountService: DeleteAccount) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple accounts.
|
||||||
|
* @param {number | Array<number>} accountIds - The account id or ids.
|
||||||
|
* @param {Knex.Transaction} trx - The transaction.
|
||||||
|
*/
|
||||||
|
async bulkDeleteAccounts(
|
||||||
|
accountIds: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const accountsIds = uniq(castArray(accountIds));
|
||||||
|
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(accountsIds)
|
||||||
|
.process(async (accountId: number) => {
|
||||||
|
await this.deleteAccountService.deleteAccount(accountId);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.errors && results.errors.length > 0) {
|
||||||
|
throw results.errors[0].raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { DeleteAccount } from './DeleteAccount.service';
|
||||||
|
import { ModelHasRelationsError } from '@/common/exceptions/ModelHasRelations.exception';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateBulkDeleteAccountsService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteAccountService: DeleteAccount,
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantKnex: () => Knex,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates which accounts from the provided IDs can be deleted.
|
||||||
|
* Uses the actual deleteAccount service to validate, ensuring the same validation logic.
|
||||||
|
* Uses a transaction that is always rolled back to ensure no database changes.
|
||||||
|
* @param {number[]} accountIds - Array of account IDs to validate
|
||||||
|
* @returns {Promise<{deletableCount: number, nonDeletableCount: number, deletableIds: number[], nonDeletableIds: number[]}>}
|
||||||
|
*/
|
||||||
|
public async validateBulkDeleteAccounts(accountIds: number[]): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
const trx = await this.tenantKnex().transaction({
|
||||||
|
isolationLevel: 'read uncommitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletableIds: number[] = [];
|
||||||
|
const nonDeletableIds: number[] = [];
|
||||||
|
|
||||||
|
for (const accountId of accountIds) {
|
||||||
|
try {
|
||||||
|
await this.deleteAccountService.deleteAccount(accountId);
|
||||||
|
deletableIds.push(accountId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ModelHasRelationsError) {
|
||||||
|
nonDeletableIds.push(accountId);
|
||||||
|
} else {
|
||||||
|
nonDeletableIds.push(accountId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.rollback();
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletableCount: deletableIds.length,
|
||||||
|
nonDeletableCount: nonDeletableIds.length,
|
||||||
|
deletableIds,
|
||||||
|
nonDeletableIds,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
28
packages/server/src/modules/Bills/BulkDeleteBills.service.ts
Normal file
28
packages/server/src/modules/Bills/BulkDeleteBills.service.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeleteBill } from './commands/DeleteBill.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BulkDeleteBillsService {
|
||||||
|
constructor(private readonly deleteBillService: DeleteBill) { }
|
||||||
|
|
||||||
|
async bulkDeleteBills(
|
||||||
|
billIds: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const billsIds = uniq(castArray(billIds));
|
||||||
|
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(billsIds)
|
||||||
|
.process(async (billId: number) => {
|
||||||
|
await this.deleteBillService.deleteBill(billId);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.errors && results.errors.length > 0) {
|
||||||
|
throw results.errors[0].raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { DeleteBill } from './commands/DeleteBill.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateBulkDeleteBillsService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteBillService: DeleteBill,
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantKnex: () => Knex,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public async validateBulkDeleteBills(billIds: number[]): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
const trx = await this.tenantKnex().transaction({
|
||||||
|
isolationLevel: 'read uncommitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletableIds: number[] = [];
|
||||||
|
const nonDeletableIds: number[] = [];
|
||||||
|
|
||||||
|
for (const billId of billIds) {
|
||||||
|
try {
|
||||||
|
await this.deleteBillService.deleteBill(billId);
|
||||||
|
deletableIds.push(billId);
|
||||||
|
} catch (error) {
|
||||||
|
nonDeletableIds.push(billId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.rollback();
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletableCount: deletableIds.length,
|
||||||
|
nonDeletableCount: nonDeletableIds.length,
|
||||||
|
deletableIds,
|
||||||
|
nonDeletableIds,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeleteCreditNoteService } from './commands/DeleteCreditNote.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BulkDeleteCreditNotesService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteCreditNoteService: DeleteCreditNoteService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async bulkDeleteCreditNotes(
|
||||||
|
creditNoteIds: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const notesIds = uniq(castArray(creditNoteIds));
|
||||||
|
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(notesIds)
|
||||||
|
.process(async (creditNoteId: number) => {
|
||||||
|
await this.deleteCreditNoteService.deleteCreditNote(creditNoteId);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.errors && results.errors.length > 0) {
|
||||||
|
throw results.errors[0].raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { DeleteCreditNoteService } from './commands/DeleteCreditNote.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateBulkDeleteCreditNotesService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteCreditNoteService: DeleteCreditNoteService,
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantKnex: () => Knex,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async validateBulkDeleteCreditNotes(creditNoteIds: number[]): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
const trx = await this.tenantKnex().transaction({
|
||||||
|
isolationLevel: 'read uncommitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletableIds: number[] = [];
|
||||||
|
const nonDeletableIds: number[] = [];
|
||||||
|
|
||||||
|
for (const creditNoteId of creditNoteIds) {
|
||||||
|
try {
|
||||||
|
await this.deleteCreditNoteService.deleteCreditNote(creditNoteId);
|
||||||
|
deletableIds.push(creditNoteId);
|
||||||
|
} catch (error) {
|
||||||
|
nonDeletableIds.push(creditNoteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.rollback();
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletableCount: deletableIds.length,
|
||||||
|
nonDeletableCount: nonDeletableIds.length,
|
||||||
|
deletableIds,
|
||||||
|
nonDeletableIds,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeleteExpense } from './commands/DeleteExpense.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BulkDeleteExpensesService {
|
||||||
|
constructor(private readonly deleteExpenseService: DeleteExpense) {}
|
||||||
|
|
||||||
|
async bulkDeleteExpenses(
|
||||||
|
expenseIds: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const expensesIds = uniq(castArray(expenseIds));
|
||||||
|
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(expensesIds)
|
||||||
|
.process(async (expenseId: number) => {
|
||||||
|
await this.deleteExpenseService.deleteExpense(expenseId);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.errors && results.errors.length > 0) {
|
||||||
|
throw results.errors[0].raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { DeleteExpense } from './commands/DeleteExpense.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateBulkDeleteExpensesService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteExpenseService: DeleteExpense,
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantKnex: () => Knex,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public async validateBulkDeleteExpenses(expenseIds: number[]): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
const trx = await this.tenantKnex().transaction({
|
||||||
|
isolationLevel: 'read uncommitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletableIds: number[] = [];
|
||||||
|
const nonDeletableIds: number[] = [];
|
||||||
|
|
||||||
|
for (const expenseId of expenseIds) {
|
||||||
|
try {
|
||||||
|
await this.deleteExpenseService.deleteExpense(expenseId);
|
||||||
|
deletableIds.push(expenseId);
|
||||||
|
} catch (error) {
|
||||||
|
nonDeletableIds.push(expenseId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.rollback();
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletableCount: deletableIds.length,
|
||||||
|
nonDeletableCount: nonDeletableIds.length,
|
||||||
|
deletableIds,
|
||||||
|
nonDeletableIds,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeleteItemCategoryService } from './commands/DeleteItemCategory.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BulkDeleteItemCategoriesService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteItemCategoryService: DeleteItemCategoryService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async bulkDeleteItemCategories(
|
||||||
|
itemCategoryIds: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const categoriesIds = uniq(castArray(itemCategoryIds));
|
||||||
|
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(categoriesIds)
|
||||||
|
.process(async (itemCategoryId: number) => {
|
||||||
|
await this.deleteItemCategoryService.deleteItemCategory(itemCategoryId);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.errors && results.errors.length > 0) {
|
||||||
|
throw results.errors[0].raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { DeleteItemCategoryService } from './commands/DeleteItemCategory.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateBulkDeleteItemCategoriesService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteItemCategoryService: DeleteItemCategoryService,
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantKnex: () => Knex,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async validateBulkDeleteItemCategories(itemCategoryIds: number[]): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
const trx = await this.tenantKnex().transaction({
|
||||||
|
isolationLevel: 'read uncommitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletableIds: number[] = [];
|
||||||
|
const nonDeletableIds: number[] = [];
|
||||||
|
|
||||||
|
for (const itemCategoryId of itemCategoryIds) {
|
||||||
|
try {
|
||||||
|
await this.deleteItemCategoryService.deleteItemCategory(itemCategoryId);
|
||||||
|
deletableIds.push(itemCategoryId);
|
||||||
|
} catch (error) {
|
||||||
|
nonDeletableIds.push(itemCategoryId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.rollback();
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletableCount: deletableIds.length,
|
||||||
|
nonDeletableCount: nonDeletableIds.length,
|
||||||
|
deletableIds,
|
||||||
|
nonDeletableIds,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
38
packages/server/src/modules/Items/BulkDeleteItems.service.ts
Normal file
38
packages/server/src/modules/Items/BulkDeleteItems.service.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeleteItemService } from './DeleteItem.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BulkDeleteItemsService {
|
||||||
|
constructor(private readonly deleteItemService: DeleteItemService) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple items.
|
||||||
|
* @param {number | Array<number>} itemIds - The item id or ids.
|
||||||
|
* @param {Knex.Transaction} trx - The transaction.
|
||||||
|
*/
|
||||||
|
async bulkDeleteItems(
|
||||||
|
itemIds: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const itemsIds = uniq(castArray(itemIds));
|
||||||
|
|
||||||
|
// Use PromisePool to delete items sequentially (concurrency: 1)
|
||||||
|
// to avoid potential database locks and maintain transaction integrity
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(itemsIds)
|
||||||
|
.process(async (itemId: number) => {
|
||||||
|
await this.deleteItemService.deleteItem(itemId, trx);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if there were any errors
|
||||||
|
if (results.errors && results.errors.length > 0) {
|
||||||
|
// If needed, you can throw an error here or handle errors individually
|
||||||
|
// For now, we'll let individual errors bubble up
|
||||||
|
throw results.errors[0].raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -29,6 +29,10 @@ import { ItemEstimatesResponseDto } from './dtos/ItemEstimatesResponse.dto';
|
|||||||
import { ItemBillsResponseDto } from './dtos/ItemBillsResponse.dto';
|
import { ItemBillsResponseDto } from './dtos/ItemBillsResponse.dto';
|
||||||
import { ItemReceiptsResponseDto } from './dtos/ItemReceiptsResponse.dto';
|
import { ItemReceiptsResponseDto } from './dtos/ItemReceiptsResponse.dto';
|
||||||
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
||||||
|
import {
|
||||||
|
BulkDeleteItemsDto,
|
||||||
|
ValidateBulkDeleteItemsResponseDto,
|
||||||
|
} from './dtos/BulkDeleteItems.dto';
|
||||||
|
|
||||||
@Controller('/items')
|
@Controller('/items')
|
||||||
@ApiTags('Items')
|
@ApiTags('Items')
|
||||||
@@ -39,6 +43,7 @@ import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
|||||||
@ApiExtraModels(ItemBillsResponseDto)
|
@ApiExtraModels(ItemBillsResponseDto)
|
||||||
@ApiExtraModels(ItemEstimatesResponseDto)
|
@ApiExtraModels(ItemEstimatesResponseDto)
|
||||||
@ApiExtraModels(ItemReceiptsResponseDto)
|
@ApiExtraModels(ItemReceiptsResponseDto)
|
||||||
|
@ApiExtraModels(ValidateBulkDeleteItemsResponseDto)
|
||||||
@ApiCommonHeaders()
|
@ApiCommonHeaders()
|
||||||
export class ItemsController extends TenantController {
|
export class ItemsController extends TenantController {
|
||||||
constructor(private readonly itemsApplication: ItemsApplicationService) {
|
constructor(private readonly itemsApplication: ItemsApplicationService) {
|
||||||
@@ -340,4 +345,35 @@ export class ItemsController extends TenantController {
|
|||||||
const itemId = parseInt(id, 10);
|
const itemId = parseInt(id, 10);
|
||||||
return this.itemsApplication.getItemReceiptsTransactions(itemId);
|
return this.itemsApplication.getItemReceiptsTransactions(itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('validate-bulk-delete')
|
||||||
|
@ApiOperation({
|
||||||
|
summary:
|
||||||
|
'Validates which items can be deleted and returns counts of deletable and non-deletable items.',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description:
|
||||||
|
'Validation completed. Returns counts and IDs of deletable and non-deletable items.',
|
||||||
|
schema: {
|
||||||
|
$ref: getSchemaPath(ValidateBulkDeleteItemsResponseDto),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
async validateBulkDeleteItems(
|
||||||
|
@Body() bulkDeleteDto: BulkDeleteItemsDto,
|
||||||
|
): Promise<ValidateBulkDeleteItemsResponseDto> {
|
||||||
|
return this.itemsApplication.validateBulkDeleteItems(bulkDeleteDto.ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('bulk-delete')
|
||||||
|
@ApiOperation({ summary: 'Deletes multiple items in bulk.' })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: 'The items have been successfully deleted.',
|
||||||
|
})
|
||||||
|
async bulkDeleteItems(
|
||||||
|
@Body() bulkDeleteDto: BulkDeleteItemsDto,
|
||||||
|
): Promise<void> {
|
||||||
|
return this.itemsApplication.bulkDeleteItems(bulkDeleteDto.ids);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
|||||||
import { InventoryAdjustmentsModule } from '../InventoryAdjutments/InventoryAdjustments.module';
|
import { InventoryAdjustmentsModule } from '../InventoryAdjutments/InventoryAdjustments.module';
|
||||||
import { ItemsExportable } from './ItemsExportable.service';
|
import { ItemsExportable } from './ItemsExportable.service';
|
||||||
import { ItemsImportable } from './ItemsImportable.service';
|
import { ItemsImportable } from './ItemsImportable.service';
|
||||||
|
import { BulkDeleteItemsService } from './BulkDeleteItems.service';
|
||||||
|
import { ValidateBulkDeleteItemsService } from './ValidateBulkDeleteItems.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -41,7 +43,9 @@ import { ItemsImportable } from './ItemsImportable.service';
|
|||||||
TransformerInjectable,
|
TransformerInjectable,
|
||||||
ItemsEntriesService,
|
ItemsEntriesService,
|
||||||
ItemsExportable,
|
ItemsExportable,
|
||||||
ItemsImportable
|
ItemsImportable,
|
||||||
|
BulkDeleteItemsService,
|
||||||
|
ValidateBulkDeleteItemsService,
|
||||||
],
|
],
|
||||||
exports: [ItemsEntriesService, ItemsExportable, ItemsImportable],
|
exports: [ItemsEntriesService, ItemsExportable, ItemsImportable],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { GetItemsService } from './GetItems.service';
|
|||||||
import { IItemsFilter } from './types/Items.types';
|
import { IItemsFilter } from './types/Items.types';
|
||||||
import { EditItemDto, CreateItemDto } from './dtos/Item.dto';
|
import { EditItemDto, CreateItemDto } from './dtos/Item.dto';
|
||||||
import { GetItemsQueryDto } from './dtos/GetItemsQuery.dto';
|
import { GetItemsQueryDto } from './dtos/GetItemsQuery.dto';
|
||||||
|
import { BulkDeleteItemsService } from './BulkDeleteItems.service';
|
||||||
|
import { ValidateBulkDeleteItemsService } from './ValidateBulkDeleteItems.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ItemsApplicationService {
|
export class ItemsApplicationService {
|
||||||
@@ -25,6 +27,8 @@ export class ItemsApplicationService {
|
|||||||
private readonly getItemService: GetItemService,
|
private readonly getItemService: GetItemService,
|
||||||
private readonly getItemsService: GetItemsService,
|
private readonly getItemsService: GetItemsService,
|
||||||
private readonly itemTransactionsService: ItemTransactionsService,
|
private readonly itemTransactionsService: ItemTransactionsService,
|
||||||
|
private readonly bulkDeleteItemsService: BulkDeleteItemsService,
|
||||||
|
private readonly validateBulkDeleteItemsService: ValidateBulkDeleteItemsService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,4 +138,27 @@ export class ItemsApplicationService {
|
|||||||
async getItemReceiptsTransactions(itemId: number): Promise<any> {
|
async getItemReceiptsTransactions(itemId: number): Promise<any> {
|
||||||
return this.itemTransactionsService.getItemReceiptTransactions(itemId);
|
return this.itemTransactionsService.getItemReceiptTransactions(itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates which items can be deleted in bulk.
|
||||||
|
* @param {number[]} itemIds - Array of item IDs to validate
|
||||||
|
* @returns {Promise<{deletableCount: number, nonDeletableCount: number, deletableIds: number[], nonDeletableIds: number[]}>}
|
||||||
|
*/
|
||||||
|
async validateBulkDeleteItems(itemIds: number[]): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
return this.validateBulkDeleteItemsService.validateBulkDeleteItems(itemIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple items in bulk.
|
||||||
|
* @param {number[]} itemIds - Array of item IDs to delete
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async bulkDeleteItems(itemIds: number[]): Promise<void> {
|
||||||
|
return this.bulkDeleteItemsService.bulkDeleteItems(itemIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { DeleteItemService } from './DeleteItem.service';
|
||||||
|
import { ModelHasRelationsError } from '@/common/exceptions/ModelHasRelations.exception';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateBulkDeleteItemsService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteItemService: DeleteItemService,
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantKnex: () => Knex,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates which items from the provided IDs can be deleted.
|
||||||
|
* Uses the actual deleteItem service to validate, ensuring the same validation logic.
|
||||||
|
* Uses a transaction that is always rolled back to ensure no database changes.
|
||||||
|
* @param {number[]} itemIds - Array of item IDs to validate
|
||||||
|
* @returns {Promise<{deletableCount: number, nonDeletableCount: number, deletableIds: number[], nonDeletableIds: number[]}>}
|
||||||
|
*/
|
||||||
|
public async validateBulkDeleteItems(itemIds: number[]): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
// Create a transaction that will be rolled back
|
||||||
|
const trx = await this.tenantKnex().transaction({
|
||||||
|
isolationLevel: 'read uncommitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletableIds: number[] = [];
|
||||||
|
const nonDeletableIds: number[] = [];
|
||||||
|
|
||||||
|
// Check each item to see if it can be deleted by attempting deletion in transaction
|
||||||
|
for (const itemId of itemIds) {
|
||||||
|
try {
|
||||||
|
// Attempt to delete the item using the deleteItem service with the transaction
|
||||||
|
// This will use the exact same validation logic as the actual delete
|
||||||
|
await this.deleteItemService.deleteItem(itemId, trx);
|
||||||
|
|
||||||
|
// If deletion succeeds, item is deletable
|
||||||
|
deletableIds.push(itemId);
|
||||||
|
} catch (error) {
|
||||||
|
// If error occurs, check the type of error
|
||||||
|
if (error instanceof ModelHasRelationsError) {
|
||||||
|
// Item has associated transactions/relations, cannot be deleted
|
||||||
|
nonDeletableIds.push(itemId);
|
||||||
|
} else {
|
||||||
|
// Other errors (e.g., item not found), also mark as non-deletable
|
||||||
|
nonDeletableIds.push(itemId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always rollback the transaction to ensure no changes are persisted
|
||||||
|
await trx.rollback();
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletableCount: deletableIds.length,
|
||||||
|
nonDeletableCount: nonDeletableIds.length,
|
||||||
|
deletableIds,
|
||||||
|
nonDeletableIds,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// Rollback in case of any error
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { IsArray, IsInt, ArrayNotEmpty } from 'class-validator';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class BulkDeleteItemsDto {
|
||||||
|
@IsArray()
|
||||||
|
@ArrayNotEmpty()
|
||||||
|
@IsInt({ each: true })
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Array of item IDs to delete',
|
||||||
|
type: [Number],
|
||||||
|
example: [1, 2, 3],
|
||||||
|
})
|
||||||
|
ids: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ValidateBulkDeleteItemsResponseDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Number of items that can be deleted',
|
||||||
|
example: 2,
|
||||||
|
})
|
||||||
|
deletableCount: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'Number of items that cannot be deleted',
|
||||||
|
example: 1,
|
||||||
|
})
|
||||||
|
nonDeletableCount: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'IDs of items that can be deleted',
|
||||||
|
type: [Number],
|
||||||
|
example: [1, 2],
|
||||||
|
})
|
||||||
|
deletableIds: number[];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: 'IDs of items that cannot be deleted',
|
||||||
|
type: [Number],
|
||||||
|
example: [3],
|
||||||
|
})
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeleteManualJournalService } from './commands/DeleteManualJournal.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BulkDeleteManualJournalsService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteManualJournalService: DeleteManualJournalService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async bulkDeleteManualJournals(
|
||||||
|
manualJournalIds: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const journalsIds = uniq(castArray(manualJournalIds));
|
||||||
|
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(journalsIds)
|
||||||
|
.process(async (manualJournalId: number) => {
|
||||||
|
await this.deleteManualJournalService.deleteManualJournal(
|
||||||
|
manualJournalId,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.errors && results.errors.length > 0) {
|
||||||
|
throw results.errors[0].raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { DeleteManualJournalService } from './commands/DeleteManualJournal.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateBulkDeleteManualJournalsService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteManualJournalService: DeleteManualJournalService,
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantKnex: () => Knex,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async validateBulkDeleteManualJournals(
|
||||||
|
manualJournalIds: number[],
|
||||||
|
): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
const trx = await this.tenantKnex().transaction({
|
||||||
|
isolationLevel: 'read uncommitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletableIds: number[] = [];
|
||||||
|
const nonDeletableIds: number[] = [];
|
||||||
|
|
||||||
|
for (const manualJournalId of manualJournalIds) {
|
||||||
|
try {
|
||||||
|
await this.deleteManualJournalService.deleteManualJournal(
|
||||||
|
manualJournalId,
|
||||||
|
);
|
||||||
|
deletableIds.push(manualJournalId);
|
||||||
|
} catch (error) {
|
||||||
|
nonDeletableIds.push(manualJournalId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.rollback();
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletableCount: deletableIds.length,
|
||||||
|
nonDeletableCount: nonDeletableIds.length,
|
||||||
|
deletableIds,
|
||||||
|
nonDeletableIds,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeletePaymentReceivedService } from './commands/DeletePaymentReceived.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BulkDeletePaymentReceivedService {
|
||||||
|
constructor(
|
||||||
|
private readonly deletePaymentReceivedService: DeletePaymentReceivedService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async bulkDeletePaymentReceived(
|
||||||
|
paymentReceiveIds: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const paymentsIds = uniq(castArray(paymentReceiveIds));
|
||||||
|
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(paymentsIds)
|
||||||
|
.process(async (paymentReceiveId: number) => {
|
||||||
|
await this.deletePaymentReceivedService.deletePaymentReceive(
|
||||||
|
paymentReceiveId,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.errors && results.errors.length > 0) {
|
||||||
|
throw results.errors[0].raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { DeletePaymentReceivedService } from './commands/DeletePaymentReceived.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateBulkDeletePaymentReceivedService {
|
||||||
|
constructor(
|
||||||
|
private readonly deletePaymentReceivedService: DeletePaymentReceivedService,
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantKnex: () => Knex,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async validateBulkDeletePaymentReceived(
|
||||||
|
paymentReceiveIds: number[],
|
||||||
|
): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
const trx = await this.tenantKnex().transaction({
|
||||||
|
isolationLevel: 'read uncommitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletableIds: number[] = [];
|
||||||
|
const nonDeletableIds: number[] = [];
|
||||||
|
|
||||||
|
for (const paymentReceiveId of paymentReceiveIds) {
|
||||||
|
try {
|
||||||
|
await this.deletePaymentReceivedService.deletePaymentReceive(
|
||||||
|
paymentReceiveId,
|
||||||
|
);
|
||||||
|
deletableIds.push(paymentReceiveId);
|
||||||
|
} catch (error) {
|
||||||
|
nonDeletableIds.push(paymentReceiveId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.rollback();
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletableCount: deletableIds.length,
|
||||||
|
nonDeletableCount: nonDeletableIds.length,
|
||||||
|
deletableIds,
|
||||||
|
nonDeletableIds,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeleteSaleEstimate } from './commands/DeleteSaleEstimate.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BulkDeleteSaleEstimatesService {
|
||||||
|
constructor(private readonly deleteSaleEstimateService: DeleteSaleEstimate) { }
|
||||||
|
|
||||||
|
async bulkDeleteSaleEstimates(
|
||||||
|
saleEstimateIds: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const estimatesIds = uniq(castArray(saleEstimateIds));
|
||||||
|
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(estimatesIds)
|
||||||
|
.process(async (saleEstimateId: number) => {
|
||||||
|
await this.deleteSaleEstimateService.deleteEstimate(saleEstimateId);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.errors && results.errors.length > 0) {
|
||||||
|
throw results.errors[0].raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { DeleteSaleEstimate } from './commands/DeleteSaleEstimate.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateBulkDeleteSaleEstimatesService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteSaleEstimateService: DeleteSaleEstimate,
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantKnex: () => Knex,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public async validateBulkDeleteSaleEstimates(saleEstimateIds: number[]): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
const trx = await this.tenantKnex().transaction({
|
||||||
|
isolationLevel: 'read uncommitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletableIds: number[] = [];
|
||||||
|
const nonDeletableIds: number[] = [];
|
||||||
|
|
||||||
|
for (const saleEstimateId of saleEstimateIds) {
|
||||||
|
try {
|
||||||
|
await this.deleteSaleEstimateService.deleteEstimate(saleEstimateId);
|
||||||
|
deletableIds.push(saleEstimateId);
|
||||||
|
} catch (error) {
|
||||||
|
nonDeletableIds.push(saleEstimateId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.rollback();
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletableCount: deletableIds.length,
|
||||||
|
nonDeletableCount: nonDeletableIds.length,
|
||||||
|
deletableIds,
|
||||||
|
nonDeletableIds,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeleteSaleInvoice } from './commands/DeleteSaleInvoice.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BulkDeleteSaleInvoicesService {
|
||||||
|
constructor(private readonly deleteSaleInvoiceService: DeleteSaleInvoice) { }
|
||||||
|
|
||||||
|
async bulkDeleteSaleInvoices(
|
||||||
|
saleInvoiceIds: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const invoicesIds = uniq(castArray(saleInvoiceIds));
|
||||||
|
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(invoicesIds)
|
||||||
|
.process(async (saleInvoiceId: number) => {
|
||||||
|
await this.deleteSaleInvoiceService.deleteSaleInvoice(saleInvoiceId);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.errors && results.errors.length > 0) {
|
||||||
|
throw results.errors[0].raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { DeleteSaleInvoice } from './commands/DeleteSaleInvoice.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateBulkDeleteSaleInvoicesService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteSaleInvoiceService: DeleteSaleInvoice,
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantKnex: () => Knex,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public async validateBulkDeleteSaleInvoices(saleInvoiceIds: number[]): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
const trx = await this.tenantKnex().transaction({
|
||||||
|
isolationLevel: 'read uncommitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletableIds: number[] = [];
|
||||||
|
const nonDeletableIds: number[] = [];
|
||||||
|
|
||||||
|
for (const saleInvoiceId of saleInvoiceIds) {
|
||||||
|
try {
|
||||||
|
await this.deleteSaleInvoiceService.deleteSaleInvoice(saleInvoiceId);
|
||||||
|
deletableIds.push(saleInvoiceId);
|
||||||
|
} catch (error) {
|
||||||
|
nonDeletableIds.push(saleInvoiceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.rollback();
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletableCount: deletableIds.length,
|
||||||
|
nonDeletableCount: nonDeletableIds.length,
|
||||||
|
deletableIds,
|
||||||
|
nonDeletableIds,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { PromisePool } from '@supercharge/promise-pool';
|
||||||
|
import { castArray, uniq } from 'lodash';
|
||||||
|
import { DeleteVendorCreditService } from './commands/DeleteVendorCredit.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class BulkDeleteVendorCreditsService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteVendorCreditService: DeleteVendorCreditService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async bulkDeleteVendorCredits(
|
||||||
|
vendorCreditIds: number | Array<number>,
|
||||||
|
trx?: Knex.Transaction,
|
||||||
|
): Promise<void> {
|
||||||
|
const creditsIds = uniq(castArray(vendorCreditIds));
|
||||||
|
|
||||||
|
const results = await PromisePool.withConcurrency(1)
|
||||||
|
.for(creditsIds)
|
||||||
|
.process(async (vendorCreditId: number) => {
|
||||||
|
await this.deleteVendorCreditService.deleteVendorCredit(vendorCreditId);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (results.errors && results.errors.length > 0) {
|
||||||
|
throw results.errors[0].raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { Knex } from 'knex';
|
||||||
|
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
|
||||||
|
import { DeleteVendorCreditService } from './commands/DeleteVendorCredit.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValidateBulkDeleteVendorCreditsService {
|
||||||
|
constructor(
|
||||||
|
private readonly deleteVendorCreditService: DeleteVendorCreditService,
|
||||||
|
@Inject(TENANCY_DB_CONNECTION)
|
||||||
|
private readonly tenantKnex: () => Knex,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public async validateBulkDeleteVendorCredits(
|
||||||
|
vendorCreditIds: number[],
|
||||||
|
): Promise<{
|
||||||
|
deletableCount: number;
|
||||||
|
nonDeletableCount: number;
|
||||||
|
deletableIds: number[];
|
||||||
|
nonDeletableIds: number[];
|
||||||
|
}> {
|
||||||
|
const trx = await this.tenantKnex().transaction({
|
||||||
|
isolationLevel: 'read uncommitted',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deletableIds: number[] = [];
|
||||||
|
const nonDeletableIds: number[] = [];
|
||||||
|
|
||||||
|
for (const vendorCreditId of vendorCreditIds) {
|
||||||
|
try {
|
||||||
|
await this.deleteVendorCreditService.deleteVendorCredit(
|
||||||
|
vendorCreditId,
|
||||||
|
);
|
||||||
|
deletableIds.push(vendorCreditId);
|
||||||
|
} catch (error) {
|
||||||
|
nonDeletableIds.push(vendorCreditId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.rollback();
|
||||||
|
|
||||||
|
return {
|
||||||
|
deletableCount: deletableIds.length,
|
||||||
|
nonDeletableCount: nonDeletableIds.length,
|
||||||
|
deletableIds,
|
||||||
|
nonDeletableIds,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await trx.rollback();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,6 +8,10 @@ const JournalPublishAlert = React.lazy(
|
|||||||
() => import('@/containers/Alerts/ManualJournals/JournalPublishAlert'),
|
() => import('@/containers/Alerts/ManualJournals/JournalPublishAlert'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const JournalBulkDeleteAlert = React.lazy(
|
||||||
|
() => import('@/containers/Alerts/ManualJournals/JournalBulkDeleteAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manual journals alerts.
|
* Manual journals alerts.
|
||||||
*/
|
*/
|
||||||
@@ -15,4 +19,5 @@ const JournalPublishAlert = React.lazy(
|
|||||||
export default [
|
export default [
|
||||||
{ name: 'journal-delete', component: JournalDeleteAlert },
|
{ name: 'journal-delete', component: JournalDeleteAlert },
|
||||||
{ name: 'journal-publish', component: JournalPublishAlert },
|
{ name: 'journal-publish', component: JournalPublishAlert },
|
||||||
|
{ name: 'journals-bulk-delete', component: JournalBulkDeleteAlert },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -102,13 +102,13 @@ export const StatusAccessor = (row) => {
|
|||||||
return (
|
return (
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={!!row.is_published}>
|
<Choose.When condition={!!row.is_published}>
|
||||||
<Tag round>
|
<Tag round minimal>
|
||||||
<T id={'published'} />
|
<T id={'published'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<Tag intent={Intent.WARNING} round>
|
<Tag intent={Intent.WARNING} round minimal>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ function AccountsActionsBar({
|
|||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
onClick={handleBulkDelete}
|
onClick={handleBulkDelete}
|
||||||
/>
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -10,9 +10,21 @@ const AccountInactivateAlert = React.lazy(
|
|||||||
const AccountActivateAlert = React.lazy(
|
const AccountActivateAlert = React.lazy(
|
||||||
() => import('@/containers/Alerts/Accounts/AccountActivateAlert'),
|
() => import('@/containers/Alerts/Accounts/AccountActivateAlert'),
|
||||||
);
|
);
|
||||||
|
const AccountBulkDeleteAlert = React.lazy(
|
||||||
|
() => import('@/containers/Alerts/Accounts/AccountBulkDeleteAlert'),
|
||||||
|
);
|
||||||
|
const AccountBulkActivateAlert = React.lazy(
|
||||||
|
() => import('@/containers/Alerts/Accounts/AccountBulkActivateAlert'),
|
||||||
|
);
|
||||||
|
const AccountBulkInactivateAlert = React.lazy(
|
||||||
|
() => import('@/containers/Alerts/Accounts/AccountBulkInactivateAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{ name: 'account-delete', component: AccountDeleteAlert },
|
{ name: 'account-delete', component: AccountDeleteAlert },
|
||||||
{ name: 'account-inactivate', component: AccountInactivateAlert },
|
{ name: 'account-inactivate', component: AccountInactivateAlert },
|
||||||
{ name: 'account-activate', component: AccountActivateAlert },
|
{ name: 'account-activate', component: AccountActivateAlert },
|
||||||
|
{ name: 'accounts-bulk-delete', component: AccountBulkDeleteAlert },
|
||||||
|
{ name: 'accounts-bulk-activate', component: AccountBulkActivateAlert },
|
||||||
|
{ name: 'accounts-bulk-inactivate', component: AccountBulkInactivateAlert },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import withSettings from '@/containers/Settings/withSettings';
|
|||||||
import withAlertsActions from '@/containers/Alert/withAlertActions';
|
import withAlertsActions from '@/containers/Alert/withAlertActions';
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||||
|
import withAccountsTableActions from './withAccountsTableActions';
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
|
|
||||||
@@ -40,6 +41,9 @@ function AccountsDataTable({
|
|||||||
|
|
||||||
// #withSettings
|
// #withSettings
|
||||||
accountsTableSize,
|
accountsTableSize,
|
||||||
|
|
||||||
|
// #withAccountsTableActions
|
||||||
|
setAccountsSelectedRows,
|
||||||
}) {
|
}) {
|
||||||
const { isAccountsLoading, isAccountsFetching, accounts } =
|
const { isAccountsLoading, isAccountsFetching, accounts } =
|
||||||
useAccountsChartContext();
|
useAccountsChartContext();
|
||||||
@@ -91,6 +95,12 @@ function AccountsDataTable({
|
|||||||
const [initialColumnsWidths, , handleColumnResizing] =
|
const [initialColumnsWidths, , handleColumnResizing] =
|
||||||
useMemorizedColumnsWidths(TABLES.ACCOUNTS);
|
useMemorizedColumnsWidths(TABLES.ACCOUNTS);
|
||||||
|
|
||||||
|
// Handle selected rows change.
|
||||||
|
const handleSelectedRowsChange = (selectedFlatRows) => {
|
||||||
|
const selectedIds = selectedFlatRows?.map((row) => row.original.id) || [];
|
||||||
|
setAccountsSelectedRows(selectedIds);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<DataTable
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
@@ -118,6 +128,7 @@ function AccountsDataTable({
|
|||||||
vListrowHeight={accountsTableSize == 'small' ? 40 : 42}
|
vListrowHeight={accountsTableSize == 'small' ? 40 : 42}
|
||||||
vListOverscanRowCount={0}
|
vListOverscanRowCount={0}
|
||||||
onCellClick={handleCellClick}
|
onCellClick={handleCellClick}
|
||||||
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
initialColumnsWidths={initialColumnsWidths}
|
initialColumnsWidths={initialColumnsWidths}
|
||||||
onColumnResizing={handleColumnResizing}
|
onColumnResizing={handleColumnResizing}
|
||||||
size={accountsTableSize}
|
size={accountsTableSize}
|
||||||
@@ -137,6 +148,7 @@ export default compose(
|
|||||||
withAlertsActions,
|
withAlertsActions,
|
||||||
withDrawerActions,
|
withDrawerActions,
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
|
withAccountsTableActions,
|
||||||
withSettings(({ accountsSettings }) => ({
|
withSettings(({ accountsSettings }) => ({
|
||||||
accountsTableSize: accountsSettings.tableSize,
|
accountsTableSize: accountsSettings.tableSize,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default (mapState) => {
|
|||||||
const mapped = {
|
const mapped = {
|
||||||
accountsTableState: getAccountsTableState(state, props),
|
accountsTableState: getAccountsTableState(state, props),
|
||||||
accountsTableStateChanged: accountsTableStateChanged(state, props),
|
accountsTableStateChanged: accountsTableStateChanged(state, props),
|
||||||
|
accountsSelectedRows: state.accounts?.selectedRows || [],
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ import { connect } from 'react-redux';
|
|||||||
import {
|
import {
|
||||||
setAccountsTableState,
|
setAccountsTableState,
|
||||||
resetAccountsTableState,
|
resetAccountsTableState,
|
||||||
|
setAccountsSelectedRows,
|
||||||
} from '@/store/accounts/accounts.actions';
|
} from '@/store/accounts/accounts.actions';
|
||||||
|
|
||||||
const mapActionsToProps = (dispatch) => ({
|
const mapActionsToProps = (dispatch) => ({
|
||||||
setAccountsTableState: (queries) => dispatch(setAccountsTableState(queries)),
|
setAccountsTableState: (queries) => dispatch(setAccountsTableState(queries)),
|
||||||
resetAccountsTableState: () => dispatch(resetAccountsTableState()),
|
resetAccountsTableState: () => dispatch(resetAccountsTableState()),
|
||||||
|
setAccountsSelectedRows: (selectedRows) =>
|
||||||
|
dispatch(setAccountsSelectedRows(selectedRows)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(null, mapActionsToProps);
|
export default connect(null, mapActionsToProps);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { Intent, Alert } from '@blueprintjs/core';
|
|||||||
import { queryCache } from 'react-query';
|
import { queryCache } from 'react-query';
|
||||||
import { FormattedMessage as T, AppToaster } from '@/components';
|
import { FormattedMessage as T, AppToaster } from '@/components';
|
||||||
|
|
||||||
import withAccountsActions from '@/containers/Accounts/withAccountsActions';
|
|
||||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
@@ -19,6 +18,7 @@ function AccountBulkActivateAlert({
|
|||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
|
|
||||||
|
// TODO: Implement bulk activate accounts hook and use it here
|
||||||
requestBulkActivateAccounts,
|
requestBulkActivateAccounts,
|
||||||
}) {
|
}) {
|
||||||
const [isLoading, setLoading] = useState(false);
|
const [isLoading, setLoading] = useState(false);
|
||||||
@@ -67,5 +67,4 @@ function AccountBulkActivateAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withAccountsActions,
|
|
||||||
)(AccountBulkActivateAlert);
|
)(AccountBulkActivateAlert);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage as T } from '@/components';
|
import { FormattedMessage as T } from '@/components';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
@@ -7,8 +7,8 @@ import { queryCache } from 'react-query';
|
|||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
|
|
||||||
import { handleDeleteErrors } from '@/containers/Accounts/utils';
|
import { handleDeleteErrors } from '@/containers/Accounts/utils';
|
||||||
|
import { useBulkDeleteAccounts } from '@/hooks/query/accounts';
|
||||||
|
|
||||||
import withAccountsActions from '@/containers/Accounts/withAccountsActions';
|
|
||||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
@@ -27,42 +27,34 @@ function AccountBulkDeleteAlert({
|
|||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
|
|
||||||
// #withAccountsActions
|
|
||||||
requestDeleteBulkAccounts,
|
|
||||||
}) {
|
}) {
|
||||||
|
const { mutateAsync: bulkDeleteAccounts, isLoading } = useBulkDeleteAccounts();
|
||||||
const [isLoading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const selectedRowsCount = 0;
|
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
};
|
};
|
||||||
// Handle confirm accounts bulk delete.
|
// Handle confirm accounts bulk delete.
|
||||||
const handleConfirmBulkDelete = () => {
|
const handleConfirmBulkDelete = () => {
|
||||||
setLoading(true);
|
bulkDeleteAccounts(accountsIds)
|
||||||
requestDeleteBulkAccounts(accountsIds)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get('the_accounts_has_been_successfully_deleted'),
|
message: intl.get('the_accounts_has_been_successfully_deleted'),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
queryCache.invalidateQueries('accounts-table');
|
queryCache.invalidateQueries('accounts-table');
|
||||||
|
closeAlert(name);
|
||||||
})
|
})
|
||||||
.catch((errors) => {
|
.catch((errors) => {
|
||||||
handleDeleteErrors(errors);
|
handleDeleteErrors(errors);
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
closeAlert(name);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
confirmButtonText={`${intl.get('delete')} (${selectedRowsCount})`}
|
confirmButtonText={
|
||||||
|
<T id={'delete_count'} values={{ count: accountsIds?.length || 0 }} />
|
||||||
|
}
|
||||||
icon="trash"
|
icon="trash"
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
@@ -80,5 +72,4 @@ function AccountBulkDeleteAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withAccountsActions,
|
|
||||||
)(AccountBulkDeleteAlert);
|
)(AccountBulkDeleteAlert);
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T } from '@/components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from '@/components';
|
||||||
|
|
||||||
|
import { useBulkDeleteBills } from '@/hooks/query/bills';
|
||||||
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bill bulk delete alert.
|
||||||
|
*/
|
||||||
|
function BillBulkDeleteAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { billsIds },
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: bulkDeleteBills, isLoading } = useBulkDeleteBills();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmBulkDelete = () => {
|
||||||
|
bulkDeleteBills(billsIds)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('the_bills_has_been_deleted_successfully'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('bills-table');
|
||||||
|
closeAlert(name);
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
// Handle errors
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={
|
||||||
|
<T id={'delete_count'} values={{ count: billsIds?.length || 0 }} />
|
||||||
|
}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_these_bills_you_will_not_able_restore_them'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(BillBulkDeleteAlert);
|
||||||
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T } from '@/components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from '@/components';
|
||||||
|
|
||||||
|
import { useBulkDeleteCreditNotes } from '@/hooks/query/creditNote';
|
||||||
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Credit note bulk delete alert.
|
||||||
|
*/
|
||||||
|
function CreditNoteBulkDeleteAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { creditNotesIds },
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: bulkDeleteCreditNotes, isLoading } = useBulkDeleteCreditNotes();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmBulkDelete = () => {
|
||||||
|
bulkDeleteCreditNotes(creditNotesIds)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('the_credit_notes_has_been_deleted_successfully'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('credit-notes-table');
|
||||||
|
closeAlert(name);
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
// Handle errors
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={
|
||||||
|
<T id={'delete_count'} values={{ count: creditNotesIds?.length || 0 }} />
|
||||||
|
}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_these_credit_notes_you_will_not_able_restore_them'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(CreditNoteBulkDeleteAlert);
|
||||||
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T } from '@/components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from '@/components';
|
||||||
|
|
||||||
|
import { useBulkDeleteEstimates } from '@/hooks/query/estimates';
|
||||||
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate bulk delete alert.
|
||||||
|
*/
|
||||||
|
function EstimateBulkDeleteAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { estimatesIds },
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: bulkDeleteEstimates, isLoading } = useBulkDeleteEstimates();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmBulkDelete = () => {
|
||||||
|
bulkDeleteEstimates(estimatesIds)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('the_estimates_has_been_deleted_successfully'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('estimates-table');
|
||||||
|
closeAlert(name);
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
// Handle errors
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={
|
||||||
|
<T id={'delete_count'} values={{ count: estimatesIds?.length || 0 }} />
|
||||||
|
}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_these_estimates_you_will_not_able_restore_them'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(EstimateBulkDeleteAlert);
|
||||||
|
|
||||||
@@ -3,8 +3,10 @@ import React from 'react';
|
|||||||
import { FormattedMessage as T } from '@/components';
|
import { FormattedMessage as T } from '@/components';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
|
|
||||||
|
import { useBulkDeleteExpenses } from '@/hooks/query/expenses';
|
||||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
@@ -15,44 +17,43 @@ import { compose } from '@/utils';
|
|||||||
*/
|
*/
|
||||||
function ExpenseBulkDeleteAlert({
|
function ExpenseBulkDeleteAlert({
|
||||||
closeAlert,
|
closeAlert,
|
||||||
|
|
||||||
// #withAlertStoreConnect
|
|
||||||
name,
|
name,
|
||||||
payload: { expenseId, selectedCount },
|
payload: { expensesIds },
|
||||||
isOpen,
|
isOpen,
|
||||||
}) {
|
}) {
|
||||||
// Handle confirm journals bulk delete.
|
const { mutateAsync: bulkDeleteExpenses, isLoading } = useBulkDeleteExpenses();
|
||||||
const handleConfirmBulkDelete = () => {
|
|
||||||
// requestDeleteBulkExpenses(bulkDelete)
|
const handleCancel = () => {
|
||||||
// .then(() => {
|
closeAlert(name);
|
||||||
// AppToaster.show({
|
|
||||||
// message: formatMessage(
|
|
||||||
// { id: 'the_expenses_have_been_deleted_successfully' },
|
|
||||||
// { count: selectedRowsCount },
|
|
||||||
// ),
|
|
||||||
// intent: Intent.SUCCESS,
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
// .catch((error) => {
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle cancel bulk delete alert.
|
const handleConfirmBulkDelete = () => {
|
||||||
const handleCancelBulkDelete = () => {
|
bulkDeleteExpenses(expensesIds)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('the_expenses_have_been_deleted_successfully'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('expenses-table');
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
// Handle errors
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
confirmButtonText={
|
confirmButtonText={
|
||||||
<T id={'delete_count'} values={{ count: selectedCount }} />
|
<T id={'delete_count'} values={{ count: expensesIds?.length || 0 }} />
|
||||||
}
|
}
|
||||||
icon="trash"
|
icon="trash"
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onCancel={handleCancelBulkDelete}
|
onCancel={handleCancel}
|
||||||
onConfirm={handleConfirmBulkDelete}
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
loading={isLoading}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
<T id={'once_delete_these_expenses_you_will_not_able_restore_them'} />
|
<T id={'once_delete_these_expenses_you_will_not_able_restore_them'} />
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T } from '@/components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from '@/components';
|
||||||
|
|
||||||
|
import { useBulkDeleteInvoices } from '@/hooks/query/invoices';
|
||||||
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice bulk delete alert.
|
||||||
|
*/
|
||||||
|
function InvoiceBulkDeleteAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { invoicesIds },
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: bulkDeleteInvoices, isLoading } = useBulkDeleteInvoices();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmBulkDelete = () => {
|
||||||
|
bulkDeleteInvoices(invoicesIds)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('the_invoices_has_been_deleted_successfully'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('invoices-table');
|
||||||
|
closeAlert(name);
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
// Handle errors
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={
|
||||||
|
<T id={'delete_count'} values={{ count: invoicesIds?.length || 0 }} />
|
||||||
|
}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_these_invoices_you_will_not_able_restore_them'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(InvoiceBulkDeleteAlert);
|
||||||
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { AppToaster, FormattedMessage as T } from '@/components';
|
import { AppToaster, FormattedMessage as T } from '@/components';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import { size } from 'lodash';
|
import { size } from 'lodash';
|
||||||
|
|
||||||
import withItemsActions from '@/containers/Items/withItemsActions';
|
import { useBulkDeleteItems } from '@/hooks/query/items';
|
||||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
@@ -21,14 +21,10 @@ function ItemBulkDeleteAlert({
|
|||||||
isOpen,
|
isOpen,
|
||||||
payload: { itemsIds },
|
payload: { itemsIds },
|
||||||
|
|
||||||
// #withItemsActions
|
|
||||||
requestDeleteBulkItems,
|
|
||||||
|
|
||||||
// #withAlertActions
|
// #withAlertActions
|
||||||
closeAlert,
|
closeAlert,
|
||||||
}) {
|
}) {
|
||||||
|
const { mutateAsync: bulkDeleteItems, isLoading } = useBulkDeleteItems();
|
||||||
const [isLoading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
// handle cancel item bulk delete alert.
|
// handle cancel item bulk delete alert.
|
||||||
const handleCancelBulkDelete = () => {
|
const handleCancelBulkDelete = () => {
|
||||||
@@ -36,19 +32,15 @@ function ItemBulkDeleteAlert({
|
|||||||
};
|
};
|
||||||
// Handle confirm items bulk delete.
|
// Handle confirm items bulk delete.
|
||||||
const handleConfirmBulkDelete = () => {
|
const handleConfirmBulkDelete = () => {
|
||||||
setLoading(true);
|
bulkDeleteItems(itemsIds)
|
||||||
requestDeleteBulkItems(itemsIds)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: intl.get('the_items_has_been_deleted_successfully'),
|
message: intl.get('the_items_has_been_deleted_successfully'),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
})
|
|
||||||
.catch((errors) => {})
|
|
||||||
.finally(() => {
|
|
||||||
setLoading(false);
|
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
});
|
})
|
||||||
|
.catch((errors) => { });
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
@@ -73,5 +65,4 @@ function ItemBulkDeleteAlert({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withAlertStoreConnect(),
|
withAlertStoreConnect(),
|
||||||
withAlertActions,
|
withAlertActions,
|
||||||
withItemsActions,
|
|
||||||
)(ItemBulkDeleteAlert);
|
)(ItemBulkDeleteAlert);
|
||||||
|
|||||||
@@ -1,43 +1,59 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T } from '@/components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from '@/components';
|
||||||
|
|
||||||
|
import { useBulkDeleteManualJournals } from '@/hooks/query/manualJournals';
|
||||||
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
function JournalBulkDeleteAlert({}) {
|
/**
|
||||||
// Handle confirm journals bulk delete.
|
* Manual journal bulk delete alert.
|
||||||
const handleConfirmBulkDelete = useCallback(() => {
|
*/
|
||||||
requestDeleteBulkManualJournals(bulkDelete)
|
function JournalBulkDeleteAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { journalsIds },
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: bulkDeleteManualJournals, isLoading } = useBulkDeleteManualJournals();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmBulkDelete = () => {
|
||||||
|
bulkDeleteManualJournals(journalsIds)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setBulkDelete(false);
|
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage(
|
message: intl.get('the_journals_has_been_deleted_successfully'),
|
||||||
{ id: 'the_journals_has_been_deleted_successfully' },
|
|
||||||
{ count: selectedRowsCount },
|
|
||||||
),
|
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
|
queryCache.invalidateQueries('manual-journals-table');
|
||||||
|
closeAlert(name);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((errors) => {
|
||||||
setBulkDelete(false);
|
// Handle errors
|
||||||
});
|
});
|
||||||
}, [
|
};
|
||||||
requestDeleteBulkManualJournals,
|
|
||||||
bulkDelete,
|
|
||||||
formatMessage,
|
|
||||||
selectedRowsCount,
|
|
||||||
]);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert
|
<Alert
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
confirmButtonText={
|
confirmButtonText={
|
||||||
<T id={'delete_count'} values={{ count: selectedRowsCount }} />
|
<T id={'delete_count'} values={{ count: journalsIds?.length || 0 }} />
|
||||||
}
|
}
|
||||||
icon="trash"
|
icon="trash"
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
isOpen={bulkDelete}
|
isOpen={isOpen}
|
||||||
onCancel={handleCancelBulkDelete}
|
onCancel={handleCancel}
|
||||||
onConfirm={handleConfirmBulkDelete}
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
loading={isLoading}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
<T id={'once_delete_these_journals_you_will_not_able_restore_them'} />
|
<T id={'once_delete_these_journals_you_will_not_able_restore_them'} />
|
||||||
@@ -45,3 +61,8 @@ function JournalBulkDeleteAlert({}) {
|
|||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(JournalBulkDeleteAlert);
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T } from '@/components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from '@/components';
|
||||||
|
|
||||||
|
import { useBulkDeletePaymentReceives } from '@/hooks/query/paymentReceives';
|
||||||
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment received bulk delete alert.
|
||||||
|
*/
|
||||||
|
function PaymentReceivedBulkDeleteAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { paymentsReceivedIds },
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: bulkDeletePaymentReceives, isLoading } = useBulkDeletePaymentReceives();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmBulkDelete = () => {
|
||||||
|
bulkDeletePaymentReceives(paymentsReceivedIds)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('the_payments_received_has_been_deleted_successfully'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('payments-received-table');
|
||||||
|
closeAlert(name);
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
// Handle errors
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={
|
||||||
|
<T id={'delete_count'} values={{ count: paymentsReceivedIds?.length || 0 }} />
|
||||||
|
}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_these_payments_received_you_will_not_able_restore_them'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(PaymentReceivedBulkDeleteAlert);
|
||||||
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T } from '@/components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { queryCache } from 'react-query';
|
||||||
|
import { AppToaster } from '@/components';
|
||||||
|
|
||||||
|
import { useBulkDeleteVendorCredits } from '@/hooks/query/vendorCredit';
|
||||||
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vendor credit bulk delete alert.
|
||||||
|
*/
|
||||||
|
function VendorCreditBulkDeleteAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { vendorCreditsIds },
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: bulkDeleteVendorCredits, isLoading } = useBulkDeleteVendorCredits();
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmBulkDelete = () => {
|
||||||
|
bulkDeleteVendorCredits(vendorCreditsIds)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('the_vendor_credits_has_been_deleted_successfully'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
queryCache.invalidateQueries('vendor-credits-table');
|
||||||
|
closeAlert(name);
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
// Handle errors
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={
|
||||||
|
<T id={'delete_count'} values={{ count: vendorCreditsIds?.length || 0 }} />
|
||||||
|
}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
onConfirm={handleConfirmBulkDelete}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<T id={'once_delete_these_vendor_credits_you_will_not_able_restore_them'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(VendorCreditBulkDeleteAlert);
|
||||||
|
|
||||||
@@ -82,6 +82,7 @@ function ExpenseForm({
|
|||||||
message: intl.get('amount_cannot_be_zero_or_empty'),
|
message: intl.get('amount_cannot_be_zero_or_empty'),
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
});
|
});
|
||||||
|
setSubmitting(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,15 @@ const ExpensePublishAlert = React.lazy(
|
|||||||
() => import('@/containers/Alerts/Expenses/ExpensePublishAlert'),
|
() => import('@/containers/Alerts/Expenses/ExpensePublishAlert'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ExpenseBulkDeleteAlert = React.lazy(
|
||||||
|
() => import('@/containers/Alerts/Expenses/ExpenseBulkDeleteAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts alert.
|
* Accounts alert.
|
||||||
*/
|
*/
|
||||||
export default [
|
export default [
|
||||||
{ name: 'expense-delete', component: ExpenseDeleteAlert },
|
{ name: 'expense-delete', component: ExpenseDeleteAlert },
|
||||||
{ name: 'expense-publish', component: ExpensePublishAlert },
|
{ name: 'expense-publish', component: ExpensePublishAlert },
|
||||||
|
{ name: 'expenses-bulk-delete', component: ExpenseBulkDeleteAlert },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export function ActionsCell(props) {
|
|||||||
*/
|
*/
|
||||||
export function PublishAccessor(row) {
|
export function PublishAccessor(row) {
|
||||||
return row.is_published ? (
|
return row.is_published ? (
|
||||||
<Tag round>
|
<Tag intent={Intent.SUCCESS} round minimal>
|
||||||
<T id={'published'} />
|
<T id={'published'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { DRAWERS } from '@/constants/drawers';
|
|||||||
function ItemsDataTable({
|
function ItemsDataTable({
|
||||||
// #withItemsActions
|
// #withItemsActions
|
||||||
setItemsTableState,
|
setItemsTableState,
|
||||||
|
setItemsSelectedRows,
|
||||||
|
|
||||||
// #withDialogAction
|
// #withDialogAction
|
||||||
openDialog,
|
openDialog,
|
||||||
@@ -81,6 +82,15 @@ function ItemsDataTable({
|
|||||||
[setItemsTableState],
|
[setItemsTableState],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle selected rows change.
|
||||||
|
const handleSelectedRowsChange = React.useCallback(
|
||||||
|
(selectedFlatRows) => {
|
||||||
|
const selectedIds = selectedFlatRows?.map((row) => row.original.id) || [];
|
||||||
|
setItemsSelectedRows(selectedIds);
|
||||||
|
},
|
||||||
|
[setItemsSelectedRows],
|
||||||
|
);
|
||||||
|
|
||||||
// Handle delete action Item.
|
// Handle delete action Item.
|
||||||
const handleDeleteItem = ({ id }) => {
|
const handleDeleteItem = ({ id }) => {
|
||||||
openAlert('item-delete', { itemId: id });
|
openAlert('item-delete', { itemId: id });
|
||||||
@@ -136,6 +146,8 @@ function ItemsDataTable({
|
|||||||
progressBarLoading={isItemsFetching}
|
progressBarLoading={isItemsFetching}
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
selectionColumn={true}
|
selectionColumn={true}
|
||||||
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
|
autoResetSelectedRows={false}
|
||||||
spinnerProps={{ size: 30 }}
|
spinnerProps={{ size: 30 }}
|
||||||
expandable={false}
|
expandable={false}
|
||||||
sticky={true}
|
sticky={true}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export const SellPriceCell = ({ cell: { value } }) => {
|
|||||||
|
|
||||||
export const ItemTypeAccessor = (row) => {
|
export const ItemTypeAccessor = (row) => {
|
||||||
return row.type_formatted ? (
|
return row.type_formatted ? (
|
||||||
<Tag round intent={Intent.NONE}>
|
<Tag round minimal intent={Intent.NONE}>
|
||||||
{row.type_formatted}
|
{row.type_formatted}
|
||||||
</Tag>
|
</Tag>
|
||||||
) : null;
|
) : null;
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { connect } from 'react-redux';
|
|||||||
import {
|
import {
|
||||||
setItemsTableState,
|
setItemsTableState,
|
||||||
resetItemsTableState,
|
resetItemsTableState,
|
||||||
|
setItemsSelectedRows,
|
||||||
} from '@/store/items/items.actions';
|
} from '@/store/items/items.actions';
|
||||||
|
|
||||||
export const mapDispatchToProps = (dispatch) => ({
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
setItemsTableState: (queries) => dispatch(setItemsTableState(queries)),
|
setItemsTableState: (queries) => dispatch(setItemsTableState(queries)),
|
||||||
resetItemsTableState: () => dispatch(resetItemsTableState()),
|
resetItemsTableState: () => dispatch(resetItemsTableState()),
|
||||||
|
setItemsSelectedRows: (selectedRows) => dispatch(setItemsSelectedRows(selectedRows)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps);
|
export default connect(null, mapDispatchToProps);
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ const BillLocatedLandedCostDeleteAlert = React.lazy(
|
|||||||
() => import('@/containers/Alerts/Bills/BillLocatedLandedCostDeleteAlert'),
|
() => import('@/containers/Alerts/Bills/BillLocatedLandedCostDeleteAlert'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const BillBulkDeleteAlert = React.lazy(
|
||||||
|
() => import('@/containers/Alerts/Bills/BillBulkDeleteAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{ name: 'bill-delete', component: BillDeleteAlert },
|
{ name: 'bill-delete', component: BillDeleteAlert },
|
||||||
{ name: 'bill-open', component: BillOpenAlert },
|
{ name: 'bill-open', component: BillOpenAlert },
|
||||||
@@ -19,4 +23,5 @@ export default [
|
|||||||
name: 'bill-located-cost-delete',
|
name: 'bill-located-cost-delete',
|
||||||
component: BillLocatedLandedCostDeleteAlert,
|
component: BillLocatedLandedCostDeleteAlert,
|
||||||
},
|
},
|
||||||
|
{ name: 'bills-bulk-delete', component: BillBulkDeleteAlert },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export function StatusAccessor(bill) {
|
|||||||
<div className={'status-accessor'}>
|
<div className={'status-accessor'}>
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={bill.is_fully_paid && bill.is_open}>
|
<Choose.When condition={bill.is_fully_paid && bill.is_open}>
|
||||||
<Tag round intent={Intent.SUCCESS}>
|
<Tag round minimal intent={Intent.SUCCESS}>
|
||||||
<T id={'paid'} />
|
<T id={'paid'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
@@ -114,18 +114,18 @@ export function StatusAccessor(bill) {
|
|||||||
<Choose.When condition={bill.is_open}>
|
<Choose.When condition={bill.is_open}>
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={bill.is_overdue}>
|
<Choose.When condition={bill.is_overdue}>
|
||||||
<Tag round intent={Intent.DANGER}>
|
<Tag round minimal intent={Intent.DANGER}>
|
||||||
{intl.get('overdue_by', { overdue: bill.overdue_days })}
|
{intl.get('overdue_by', { overdue: bill.overdue_days })}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<Tag round intent={Intent.WARNING}>
|
<Tag round minimal intent={Intent.WARNING}>
|
||||||
{intl.get('due_in', { due: bill.remaining_days })}
|
{intl.get('due_in', { due: bill.remaining_days })}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
<If condition={bill.is_partially_paid}>
|
<If condition={bill.is_partially_paid}>
|
||||||
<Tag round intent={Intent.PRIMARY}>
|
<Tag round minimal intent={Intent.PRIMARY}>
|
||||||
{intl.get('day_partially_paid', {
|
{intl.get('day_partially_paid', {
|
||||||
due: formattedAmount(bill.due_amount, bill.currency_code),
|
due: formattedAmount(bill.due_amount, bill.currency_code),
|
||||||
})}
|
})}
|
||||||
@@ -134,7 +134,7 @@ export function StatusAccessor(bill) {
|
|||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<Tag round>
|
<Tag round minimal>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
|
|||||||
@@ -81,19 +81,19 @@ export function StatusAccessor(creditNote) {
|
|||||||
<div>
|
<div>
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={creditNote.is_open}>
|
<Choose.When condition={creditNote.is_open}>
|
||||||
<Tag intent={Intent.WARNING} round>
|
<Tag intent={Intent.WARNING} round minimal>
|
||||||
<T id={'open'} />
|
<T id={'open'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.When condition={creditNote.is_closed}>
|
<Choose.When condition={creditNote.is_closed}>
|
||||||
<Tag intent={Intent.SUCCESS} round>
|
<Tag intent={Intent.SUCCESS} round minimal>
|
||||||
<T id={'closed'} />
|
<T id={'closed'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.When condition={creditNote.is_draft}>
|
<Choose.When condition={creditNote.is_draft}>
|
||||||
<Tag round>
|
<Tag round minimal>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ const ReconcileVendorCreditDeleteAlert = React.lazy(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const VendorCreditBulkDeleteAlert = React.lazy(
|
||||||
|
() =>
|
||||||
|
import('@/containers/Alerts/VendorCeditNotes/VendorCreditBulkDeleteAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vendor Credit notes alerts.
|
* Vendor Credit notes alerts.
|
||||||
*/
|
*/
|
||||||
@@ -43,4 +48,8 @@ export default [
|
|||||||
name: 'reconcile-vendor-delete',
|
name: 'reconcile-vendor-delete',
|
||||||
component: ReconcileVendorCreditDeleteAlert,
|
component: ReconcileVendorCreditDeleteAlert,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'vendor-credits-bulk-delete',
|
||||||
|
component: VendorCreditBulkDeleteAlert,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ const ReconcileCreditDeleteAlert = React.lazy(
|
|||||||
import('@/containers/Alerts/CreditNotes/ReconcileCreditNoteDeleteAlert'),
|
import('@/containers/Alerts/CreditNotes/ReconcileCreditNoteDeleteAlert'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const CreditNoteBulkDeleteAlert = React.lazy(
|
||||||
|
() => import('@/containers/Alerts/CreditNotes/CreditNoteBulkDeleteAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Credit notes alerts.
|
* Credit notes alerts.
|
||||||
*/
|
*/
|
||||||
@@ -38,4 +42,8 @@ export default [
|
|||||||
name: 'reconcile-credit-delete',
|
name: 'reconcile-credit-delete',
|
||||||
component: ReconcileCreditDeleteAlert,
|
component: ReconcileCreditDeleteAlert,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'credit-notes-bulk-delete',
|
||||||
|
component: CreditNoteBulkDeleteAlert,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import { DRAWERS } from '@/constants/drawers';
|
|||||||
function CreditNotesDataTable({
|
function CreditNotesDataTable({
|
||||||
// #withCreditNotesActions
|
// #withCreditNotesActions
|
||||||
setCreditNotesTableState,
|
setCreditNotesTableState,
|
||||||
|
setCreditNotesSelectedRows,
|
||||||
|
|
||||||
// #withAlertsActions
|
// #withAlertsActions
|
||||||
openAlert,
|
openAlert,
|
||||||
@@ -79,6 +80,15 @@ function CreditNotesDataTable({
|
|||||||
[setCreditNotesTableState],
|
[setCreditNotesTableState],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle selected rows change.
|
||||||
|
const handleSelectedRowsChange = React.useCallback(
|
||||||
|
(selectedFlatRows) => {
|
||||||
|
const selectedIds = selectedFlatRows?.map((row) => row.original.id) || [];
|
||||||
|
setCreditNotesSelectedRows(selectedIds);
|
||||||
|
},
|
||||||
|
[setCreditNotesSelectedRows],
|
||||||
|
);
|
||||||
|
|
||||||
// Display create note empty status instead of the table.
|
// Display create note empty status instead of the table.
|
||||||
if (isEmptyStatus) {
|
if (isEmptyStatus) {
|
||||||
return <CreditNoteEmptyStatus />;
|
return <CreditNoteEmptyStatus />;
|
||||||
@@ -128,6 +138,8 @@ function CreditNotesDataTable({
|
|||||||
headerLoading={isCreditNotesLoading}
|
headerLoading={isCreditNotesLoading}
|
||||||
progressBarLoading={isCreditNotesFetching}
|
progressBarLoading={isCreditNotesFetching}
|
||||||
onFetchData={handleDataTableFetchData}
|
onFetchData={handleDataTableFetchData}
|
||||||
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
|
autoResetSelectedRows={false}
|
||||||
manualSortBy={true}
|
manualSortBy={true}
|
||||||
selectionColumn={true}
|
selectionColumn={true}
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
|
|||||||
@@ -80,19 +80,19 @@ export function StatusAccessor(creditNote) {
|
|||||||
<div>
|
<div>
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={creditNote.is_open}>
|
<Choose.When condition={creditNote.is_open}>
|
||||||
<Tag intent={Intent.WARNING} round>
|
<Tag intent={Intent.WARNING} round minimal>
|
||||||
<T id={'open'} />
|
<T id={'open'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.When condition={creditNote.is_closed}>
|
<Choose.When condition={creditNote.is_closed}>
|
||||||
<Tag intent={Intent.SUCCESS} round>
|
<Tag intent={Intent.SUCCESS} round minimal>
|
||||||
<T id={'closed'} />
|
<T id={'closed'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.When condition={creditNote.is_draft}>
|
<Choose.When condition={creditNote.is_draft}>
|
||||||
<Tag intent={Intent.NONE} round>
|
<Tag intent={Intent.NONE} round minimal>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default (mapState) => {
|
|||||||
const mapped = {
|
const mapped = {
|
||||||
creditNoteTableState: getCreditNoteTableState(state, props),
|
creditNoteTableState: getCreditNoteTableState(state, props),
|
||||||
creditNoteTableStateChanged: isCreditNoteTableChanged(state, props),
|
creditNoteTableStateChanged: isCreditNoteTableChanged(state, props),
|
||||||
|
creditNotesSelectedRows: state.creditNotes?.selectedRows || [],
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { connect } from 'react-redux';
|
|||||||
import {
|
import {
|
||||||
setCreditNoteTableState,
|
setCreditNoteTableState,
|
||||||
resetCreditNoteTableState,
|
resetCreditNoteTableState,
|
||||||
|
setCreditNotesSelectedRows,
|
||||||
} from '@/store/CreditNote/creditNote.actions';
|
} from '@/store/CreditNote/creditNote.actions';
|
||||||
|
|
||||||
const mapDipatchToProps = (dispatch) => ({
|
const mapDipatchToProps = (dispatch) => ({
|
||||||
setCreditNotesTableState: (queries) =>
|
setCreditNotesTableState: (queries) =>
|
||||||
dispatch(setCreditNoteTableState(queries)),
|
dispatch(setCreditNoteTableState(queries)),
|
||||||
resetCreditNotesTableState: () => dispatch(resetCreditNoteTableState()),
|
resetCreditNotesTableState: () => dispatch(resetCreditNoteTableState()),
|
||||||
|
setCreditNotesSelectedRows: (selectedRows) => dispatch(setCreditNotesSelectedRows(selectedRows)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(null, mapDipatchToProps);
|
export default connect(null, mapDipatchToProps);
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ const EstimateRejectAlert = React.lazy(
|
|||||||
() => import('@/containers/Alerts/Estimates/EstimateRejectAlert'),
|
() => import('@/containers/Alerts/Estimates/EstimateRejectAlert'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const EstimateBulkDeleteAlert = React.lazy(
|
||||||
|
() => import('@/containers/Alerts/Estimates/EstimateBulkDeleteAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Estimates alert.
|
* Estimates alert.
|
||||||
*/
|
*/
|
||||||
@@ -22,4 +26,5 @@ export default [
|
|||||||
{ name: 'estimate-deliver', component: EstimateDeliveredAlert },
|
{ name: 'estimate-deliver', component: EstimateDeliveredAlert },
|
||||||
{ name: 'estimate-Approve', component: EstimateApproveAlert },
|
{ name: 'estimate-Approve', component: EstimateApproveAlert },
|
||||||
{ name: 'estimate-reject', component: EstimateRejectAlert },
|
{ name: 'estimate-reject', component: EstimateRejectAlert },
|
||||||
|
{ name: 'estimates-bulk-delete', component: EstimateBulkDeleteAlert },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import withEstimatesActions from './withEstimatesActions';
|
|||||||
import withSettings from '@/containers/Settings/withSettings';
|
import withSettings from '@/containers/Settings/withSettings';
|
||||||
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
import { useEstimatesListContext } from './EstimatesListProvider';
|
import { useEstimatesListContext } from './EstimatesListProvider';
|
||||||
import { useRefreshEstimates } from '@/hooks/query/estimates';
|
import { useRefreshEstimates } from '@/hooks/query/estimates';
|
||||||
@@ -43,6 +44,7 @@ import { compose } from '@/utils';
|
|||||||
import { DialogsName } from '@/constants/dialogs';
|
import { DialogsName } from '@/constants/dialogs';
|
||||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
import {
|
import {
|
||||||
BrandingThemeFormGroup,
|
BrandingThemeFormGroup,
|
||||||
BrandingThemeSelectButton,
|
BrandingThemeSelectButton,
|
||||||
@@ -57,6 +59,7 @@ function EstimateActionsBar({
|
|||||||
|
|
||||||
// #withEstimates
|
// #withEstimates
|
||||||
estimatesFilterRoles,
|
estimatesFilterRoles,
|
||||||
|
estimatesSelectedRows = [],
|
||||||
|
|
||||||
// #withSettings
|
// #withSettings
|
||||||
estimatesTableSize,
|
estimatesTableSize,
|
||||||
@@ -69,6 +72,9 @@ function EstimateActionsBar({
|
|||||||
|
|
||||||
// #withSettingsActions
|
// #withSettingsActions
|
||||||
addSetting,
|
addSetting,
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
openAlert,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@@ -116,6 +122,11 @@ function EstimateActionsBar({
|
|||||||
openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'SaleEstimate' });
|
openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'SaleEstimate' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle bulk estimates delete.
|
||||||
|
const handleBulkDelete = () => {
|
||||||
|
openAlert('estimates-bulk-delete', { estimatesIds: estimatesSelectedRows });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
@@ -150,13 +161,13 @@ function EstimateActionsBar({
|
|||||||
/>
|
/>
|
||||||
</AdvancedFilterPopover>
|
</AdvancedFilterPopover>
|
||||||
|
|
||||||
<If condition={false}>
|
<If condition={!isEmpty(estimatesSelectedRows)}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||||
text={<T id={'delete'} />}
|
text={<T id={'delete'} />}
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
// onClick={handleBulkDelete}
|
onClick={handleBulkDelete}
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
<Button
|
<Button
|
||||||
@@ -218,8 +229,10 @@ function EstimateActionsBar({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withEstimatesActions,
|
withEstimatesActions,
|
||||||
withSettingsActions,
|
withSettingsActions,
|
||||||
withEstimates(({ estimatesTableState }) => ({
|
withAlertActions,
|
||||||
|
withEstimates(({ estimatesTableState, estimatesSelectedRows }) => ({
|
||||||
estimatesFilterRoles: estimatesTableState.filterRoles,
|
estimatesFilterRoles: estimatesTableState.filterRoles,
|
||||||
|
estimatesSelectedRows: estimatesSelectedRows || [],
|
||||||
})),
|
})),
|
||||||
withSettings(({ estimatesSettings }) => ({
|
withSettings(({ estimatesSettings }) => ({
|
||||||
estimatesTableSize: estimatesSettings?.tableSize,
|
estimatesTableSize: estimatesSettings?.tableSize,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { DialogsName } from '@/constants/dialogs';
|
|||||||
function EstimatesDataTable({
|
function EstimatesDataTable({
|
||||||
// #withEstimatesActions
|
// #withEstimatesActions
|
||||||
setEstimatesTableState,
|
setEstimatesTableState,
|
||||||
|
setEstimatesSelectedRows,
|
||||||
|
|
||||||
// #withAlertsActions
|
// #withAlertsActions
|
||||||
openAlert,
|
openAlert,
|
||||||
@@ -126,6 +127,15 @@ function EstimatesDataTable({
|
|||||||
[setEstimatesTableState],
|
[setEstimatesTableState],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle selected rows change.
|
||||||
|
const handleSelectedRowsChange = useCallback(
|
||||||
|
(selectedFlatRows) => {
|
||||||
|
const selectedIds = selectedFlatRows?.map((row) => row.original.id) || [];
|
||||||
|
setEstimatesSelectedRows(selectedIds);
|
||||||
|
},
|
||||||
|
[setEstimatesSelectedRows],
|
||||||
|
);
|
||||||
|
|
||||||
// Display empty status instead of the table.
|
// Display empty status instead of the table.
|
||||||
if (isEmptyStatus) {
|
if (isEmptyStatus) {
|
||||||
return <EstimatesEmptyStatus />;
|
return <EstimatesEmptyStatus />;
|
||||||
@@ -140,6 +150,8 @@ function EstimatesDataTable({
|
|||||||
headerLoading={isEstimatesLoading}
|
headerLoading={isEstimatesLoading}
|
||||||
progressBarLoading={isEstimatesFetching}
|
progressBarLoading={isEstimatesFetching}
|
||||||
onFetchData={handleFetchData}
|
onFetchData={handleFetchData}
|
||||||
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
|
autoResetSelectedRows={false}
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
manualSortBy={true}
|
manualSortBy={true}
|
||||||
selectionColumn={true}
|
selectionColumn={true}
|
||||||
|
|||||||
@@ -22,27 +22,31 @@ import { safeCallback } from '@/utils';
|
|||||||
export const statusAccessor = (row) => (
|
export const statusAccessor = (row) => (
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={row.is_approved}>
|
<Choose.When condition={row.is_approved}>
|
||||||
<Tag intent={Intent.SUCCESS} round>
|
<Tag intent={Intent.SUCCESS} round minimal>
|
||||||
<T id={'approved'} />
|
<T id={'approved'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.When condition={row.is_rejected}>
|
<Choose.When condition={row.is_rejected}>
|
||||||
<Tag intent={Intent.DANGER} round>
|
<Tag intent={Intent.DANGER} round minimal>
|
||||||
<T id={'rejected'} />
|
<T id={'rejected'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.When condition={row.is_expired}>
|
<Choose.When condition={row.is_expired}>
|
||||||
<Tag intent={Intent.WARNING} round>
|
<Tag intent={Intent.WARNING} round minimal>
|
||||||
<T id={'estimate.status.expired'} />
|
<T id={'estimate.status.expired'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.When condition={row.is_delivered}>
|
<Choose.When condition={row.is_delivered}>
|
||||||
<Tag intent={Intent.SUCCESS} round>
|
<Tag intent={Intent.SUCCESS} round minimal>
|
||||||
<T id={'delivered'} />
|
<T id={'delivered'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<Tag round>
|
<Tag round minimal>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default (mapState) => {
|
|||||||
const mapped = {
|
const mapped = {
|
||||||
estimatesTableState: getEstimatesTableState(state, props),
|
estimatesTableState: getEstimatesTableState(state, props),
|
||||||
estimatesTableStateChanged: isEstimatesTableStateChanged(state, props),
|
estimatesTableStateChanged: isEstimatesTableStateChanged(state, props),
|
||||||
|
estimatesSelectedRows: state.estimates?.selectedRows || [],
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import { connect } from 'react-redux';
|
|||||||
import {
|
import {
|
||||||
setEstimatesTableState,
|
setEstimatesTableState,
|
||||||
resetEstimatesTableState,
|
resetEstimatesTableState,
|
||||||
|
setEstimatesSelectedRows,
|
||||||
} from '@/store/Estimate/estimates.actions';
|
} from '@/store/Estimate/estimates.actions';
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setEstimatesTableState: (state) => dispatch(setEstimatesTableState(state)),
|
setEstimatesTableState: (state) => dispatch(setEstimatesTableState(state)),
|
||||||
resetEstimatesTableState: () => dispatch(resetEstimatesTableState()),
|
resetEstimatesTableState: () => dispatch(resetEstimatesTableState()),
|
||||||
|
setEstimatesSelectedRows: (selectedRows) => dispatch(setEstimatesSelectedRows(selectedRows)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps);
|
export default connect(null, mapDispatchToProps);
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ const CancelBadDebtAlert = React.lazy(
|
|||||||
() => import('@/containers/Alerts/Invoices/CancelBadDebtAlert'),
|
() => import('@/containers/Alerts/Invoices/CancelBadDebtAlert'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const InvoiceBulkDeleteAlert = React.lazy(
|
||||||
|
() => import('@/containers/Alerts/Invoices/InvoiceBulkDeleteAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoices alert.
|
* Invoices alert.
|
||||||
*/
|
*/
|
||||||
@@ -19,4 +23,5 @@ export default [
|
|||||||
{ name: 'invoice-delete', component: InvoiceDeleteAlert },
|
{ name: 'invoice-delete', component: InvoiceDeleteAlert },
|
||||||
{ name: 'invoice-deliver', component: InvoiceDeliverAlert },
|
{ name: 'invoice-deliver', component: InvoiceDeliverAlert },
|
||||||
{ name: 'cancel-bad-debt', component: CancelBadDebtAlert },
|
{ name: 'cancel-bad-debt', component: CancelBadDebtAlert },
|
||||||
|
{ name: 'invoices-bulk-delete', component: InvoiceBulkDeleteAlert },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -34,11 +34,13 @@ import withInvoices from './withInvoices';
|
|||||||
import withInvoiceActions from './withInvoiceActions';
|
import withInvoiceActions from './withInvoiceActions';
|
||||||
import withSettings from '@/containers/Settings/withSettings';
|
import withSettings from '@/containers/Settings/withSettings';
|
||||||
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
import withSettingsActions from '@/containers/Settings/withSettingsActions';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
import { DialogsName } from '@/constants/dialogs';
|
||||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoices table actions bar.
|
* Invoices table actions bar.
|
||||||
@@ -49,6 +51,7 @@ function InvoiceActionsBar({
|
|||||||
|
|
||||||
// #withInvoices
|
// #withInvoices
|
||||||
invoicesFilterRoles,
|
invoicesFilterRoles,
|
||||||
|
invoicesSelectedRows = [],
|
||||||
|
|
||||||
// #withSettings
|
// #withSettings
|
||||||
invoicesTableSize,
|
invoicesTableSize,
|
||||||
@@ -61,6 +64,9 @@ function InvoiceActionsBar({
|
|||||||
|
|
||||||
// #withDrawerActions
|
// #withDrawerActions
|
||||||
openDrawer,
|
openDrawer,
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
openAlert,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@@ -112,6 +118,11 @@ function InvoiceActionsBar({
|
|||||||
openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'SaleInvoice' });
|
openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'SaleInvoice' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle bulk invoices delete.
|
||||||
|
const handleBulkDelete = () => {
|
||||||
|
openAlert('invoices-bulk-delete', { invoicesIds: invoicesSelectedRows });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
@@ -145,12 +156,13 @@ function InvoiceActionsBar({
|
|||||||
|
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
|
||||||
<If condition={false}>
|
<If condition={!isEmpty(invoicesSelectedRows)}>
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||||
text={<T id={'delete'} />}
|
text={<T id={'delete'} />}
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
|
onClick={handleBulkDelete}
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
<Button
|
<Button
|
||||||
@@ -211,8 +223,10 @@ function InvoiceActionsBar({
|
|||||||
export default compose(
|
export default compose(
|
||||||
withInvoiceActions,
|
withInvoiceActions,
|
||||||
withSettingsActions,
|
withSettingsActions,
|
||||||
|
withAlertActions,
|
||||||
withInvoices(({ invoicesTableState }) => ({
|
withInvoices(({ invoicesTableState }) => ({
|
||||||
invoicesFilterRoles: invoicesTableState.filterRoles,
|
invoicesFilterRoles: invoicesTableState.filterRoles,
|
||||||
|
invoicesSelectedRows: invoicesTableState?.selectedRows || [],
|
||||||
})),
|
})),
|
||||||
withSettings(({ invoiceSettings }) => ({
|
withSettings(({ invoiceSettings }) => ({
|
||||||
invoicesTableSize: invoiceSettings?.tableSize,
|
invoicesTableSize: invoiceSettings?.tableSize,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { DialogsName } from '@/constants/dialogs';
|
|||||||
function InvoicesDataTable({
|
function InvoicesDataTable({
|
||||||
// #withInvoicesActions
|
// #withInvoicesActions
|
||||||
setInvoicesTableState,
|
setInvoicesTableState,
|
||||||
|
setInvoicesSelectedRows,
|
||||||
|
|
||||||
// #withInvoices
|
// #withInvoices
|
||||||
invoicesTableState,
|
invoicesTableState,
|
||||||
@@ -125,6 +126,15 @@ function InvoicesDataTable({
|
|||||||
[setInvoicesTableState],
|
[setInvoicesTableState],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle selected rows change.
|
||||||
|
const handleSelectedRowsChange = useCallback(
|
||||||
|
(selectedFlatRows) => {
|
||||||
|
const selectedIds = selectedFlatRows?.map((row) => row.original.id) || [];
|
||||||
|
setInvoicesSelectedRows(selectedIds);
|
||||||
|
},
|
||||||
|
[setInvoicesSelectedRows],
|
||||||
|
);
|
||||||
|
|
||||||
// Display invoice empty status instead of the table.
|
// Display invoice empty status instead of the table.
|
||||||
if (isEmptyStatus) {
|
if (isEmptyStatus) {
|
||||||
return <InvoicesEmptyStatus />;
|
return <InvoicesEmptyStatus />;
|
||||||
@@ -141,6 +151,7 @@ function InvoicesDataTable({
|
|||||||
onFetchData={handleDataTableFetchData}
|
onFetchData={handleDataTableFetchData}
|
||||||
manualSortBy={true}
|
manualSortBy={true}
|
||||||
selectionColumn={true}
|
selectionColumn={true}
|
||||||
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
sticky={true}
|
sticky={true}
|
||||||
pagination={true}
|
pagination={true}
|
||||||
@@ -149,6 +160,7 @@ function InvoicesDataTable({
|
|||||||
pagesCount={pagination.pagesCount}
|
pagesCount={pagination.pagesCount}
|
||||||
autoResetSortBy={false}
|
autoResetSortBy={false}
|
||||||
autoResetPage={false}
|
autoResetPage={false}
|
||||||
|
autoResetSelectedRows={false}
|
||||||
TableLoadingRenderer={TableSkeletonRows}
|
TableLoadingRenderer={TableSkeletonRows}
|
||||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||||
ContextMenu={ActionsMenu}
|
ContextMenu={ActionsMenu}
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import { Intent, Tag, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
||||||
Intent,
|
|
||||||
Tag,
|
|
||||||
Menu,
|
|
||||||
MenuItem,
|
|
||||||
MenuDivider,
|
|
||||||
} from '@blueprintjs/core';
|
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import clsx from 'classnames';
|
import clsx from 'classnames';
|
||||||
import { CLASSES } from '@/constants/classes';
|
import { CLASSES } from '@/constants/classes';
|
||||||
@@ -30,36 +24,33 @@ export function InvoiceStatus({ invoice }) {
|
|||||||
return (
|
return (
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={invoice.is_fully_paid && invoice.is_delivered}>
|
<Choose.When condition={invoice.is_fully_paid && invoice.is_delivered}>
|
||||||
<Tag intent={Intent.SUCCESS} round>
|
<Tag intent={Intent.SUCCESS} round minimal>
|
||||||
<T id={'paid'} />
|
<T id={'paid'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.When condition={invoice.is_delivered}>
|
<Choose.When condition={invoice.is_delivered && invoice.is_overdue}>
|
||||||
<Choose>
|
<Tag intent={Intent.DANGER} round minimal>
|
||||||
<Choose.When condition={invoice.is_overdue}>
|
|
||||||
<Tag intent={Intent.DANGER} round>
|
|
||||||
{intl.get('overdue_by', { overdue: invoice.overdue_days })}
|
{intl.get('overdue_by', { overdue: invoice.overdue_days })}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.Otherwise>
|
|
||||||
<Tag intent={Intent.WARNING} round>
|
<Choose.When condition={invoice.is_delivered && !invoice.is_overdue}>
|
||||||
|
<Tag intent={Intent.WARNING} round minimal>
|
||||||
{intl.get('due_in', { due: invoice.remaining_days })}
|
{intl.get('due_in', { due: invoice.remaining_days })}
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.Otherwise>
|
</Choose.When>
|
||||||
</Choose>
|
|
||||||
|
|
||||||
<If condition={invoice.is_partially_paid}>
|
<Choose.When condition={invoice.is_partially_paid}>
|
||||||
<Tag intent={Intent.PRIMARY} round>
|
<Tag intent={Intent.PRIMARY} round minimal>
|
||||||
{intl.get('day_partially_paid', {
|
{intl.get('day_partially_paid', {
|
||||||
due: formattedAmount(invoice.due_amount, invoice.currency_code),
|
due: formattedAmount(invoice.due_amount, invoice.currency_code),
|
||||||
})}
|
})}
|
||||||
</Tag>
|
</Tag>
|
||||||
</If>
|
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<Tag round>
|
<Tag round minimal>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
setInvoicesTableState,
|
setInvoicesTableState,
|
||||||
resetInvoicesTableState
|
resetInvoicesTableState,
|
||||||
|
setInvoicesSelectedRows,
|
||||||
} from '@/store/Invoice/invoices.actions';
|
} from '@/store/Invoice/invoices.actions';
|
||||||
|
|
||||||
const mapDipatchToProps = (dispatch) => ({
|
const mapDipatchToProps = (dispatch) => ({
|
||||||
setInvoicesTableState: (queries) => dispatch(setInvoicesTableState(queries)),
|
setInvoicesTableState: (queries) => dispatch(setInvoicesTableState(queries)),
|
||||||
resetInvoicesTableState: () => dispatch(resetInvoicesTableState()),
|
resetInvoicesTableState: () => dispatch(resetInvoicesTableState()),
|
||||||
|
setInvoicesSelectedRows: (selectedRows) => dispatch(setInvoicesSelectedRows(selectedRows)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(null, mapDipatchToProps);
|
export default connect(null, mapDipatchToProps);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default (mapState) => {
|
|||||||
const mapped = {
|
const mapped = {
|
||||||
invoicesTableState: getInvoicesTableState(state, props),
|
invoicesTableState: getInvoicesTableState(state, props),
|
||||||
invoicesTableStateChanged: isInvoicesTableStateChanged(state, props),
|
invoicesTableStateChanged: isInvoicesTableStateChanged(state, props),
|
||||||
|
invoicesSelectedRows: state.invoices?.selectedRows || [],
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,9 +5,18 @@ const PaymentReceivedDeleteAlert = React.lazy(
|
|||||||
() => import('@/containers/Alerts/PaymentReceived/PaymentReceivedDeleteAlert'),
|
() => import('@/containers/Alerts/PaymentReceived/PaymentReceivedDeleteAlert'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const PaymentReceivedBulkDeleteAlert = React.lazy(
|
||||||
|
() =>
|
||||||
|
import('@/containers/Alerts/PaymentReceived/PaymentReceivedBulkDeleteAlert'),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PaymentReceives alert.
|
* PaymentReceives alert.
|
||||||
*/
|
*/
|
||||||
export default [
|
export default [
|
||||||
{ name: 'payment-received-delete', component: PaymentReceivedDeleteAlert },
|
{ name: 'payment-received-delete', component: PaymentReceivedDeleteAlert },
|
||||||
|
{
|
||||||
|
name: 'payments-received-bulk-delete',
|
||||||
|
component: PaymentReceivedBulkDeleteAlert,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -96,13 +96,13 @@ export function StatusAccessor(receipt) {
|
|||||||
return (
|
return (
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={receipt.is_closed}>
|
<Choose.When condition={receipt.is_closed}>
|
||||||
<Tag intent={Intent.SUCCESS} round>
|
<Tag intent={Intent.SUCCESS} round minimal>
|
||||||
<T id={'closed'} />
|
<T id={'closed'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
|
|
||||||
<Choose.Otherwise>
|
<Choose.Otherwise>
|
||||||
<Tag intent={Intent.WARNING} round>
|
<Tag intent={Intent.WARNING} round minimal>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</Tag>
|
</Tag>
|
||||||
</Choose.Otherwise>
|
</Choose.Otherwise>
|
||||||
|
|||||||
@@ -150,6 +150,40 @@ export function useInactivateAccount(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple accounts in bulk.
|
||||||
|
*/
|
||||||
|
export function useBulkDeleteAccounts(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(ids: number[]) => apiRequest.post('accounts/bulk-delete', { ids }),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates which accounts can be deleted in bulk.
|
||||||
|
*/
|
||||||
|
export function useValidateBulkDeleteAccounts(props) {
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(ids: number[]) =>
|
||||||
|
apiRequest.post('accounts/validate-bulk-delete', { ids }),
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve account transactions.
|
* Retrieve account transactions.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -121,6 +121,25 @@ export function useDeleteBill(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple bills in bulk.
|
||||||
|
*/
|
||||||
|
export function useBulkDeleteBills(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(ids: number[]) => apiRequest.post('bills/bulk-delete', { ids }),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const transformBillsResponse = (response) => ({
|
const transformBillsResponse = (response) => ({
|
||||||
bills: response.data.bills,
|
bills: response.data.bills,
|
||||||
pagination: transformPagination(response.data.pagination),
|
pagination: transformPagination(response.data.pagination),
|
||||||
|
|||||||
@@ -111,6 +111,25 @@ export function useDeleteCreditNote(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple credit notes in bulk.
|
||||||
|
*/
|
||||||
|
export function useBulkDeleteCreditNotes(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(ids: number[]) => apiRequest.post('credit-notes/bulk-delete', { ids }),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const transformCreditNotes = (res) => ({
|
const transformCreditNotes = (res) => ({
|
||||||
creditNotes: res.data.credit_notes,
|
creditNotes: res.data.credit_notes,
|
||||||
pagination: transformPagination(res.data.pagination),
|
pagination: transformPagination(res.data.pagination),
|
||||||
|
|||||||
@@ -124,6 +124,25 @@ export function useDeleteEstimate(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple sale estimates in bulk.
|
||||||
|
*/
|
||||||
|
export function useBulkDeleteEstimates(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(ids: number[]) => apiRequest.post('sale-estimates/bulk-delete', { ids }),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark the given estimate as delivered.
|
* Mark the given estimate as delivered.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -102,6 +102,25 @@ export function useDeleteExpense(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple expenses in bulk.
|
||||||
|
*/
|
||||||
|
export function useBulkDeleteExpenses(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(ids: number[]) => apiRequest.post('expenses/bulk-delete', { ids }),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edits the given expense.
|
* Edits the given expense.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -125,6 +125,25 @@ export function useDeleteInvoice(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple sale invoices in bulk.
|
||||||
|
*/
|
||||||
|
export function useBulkDeleteInvoices(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(ids: number[]) => apiRequest.post('sale-invoices/bulk-delete', { ids }),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const transformInvoices = (res) => ({
|
const transformInvoices = (res) => ({
|
||||||
invoices: res.data.sales_invoices,
|
invoices: res.data.sales_invoices,
|
||||||
pagination: transformPagination(res.data.pagination),
|
pagination: transformPagination(res.data.pagination),
|
||||||
|
|||||||
@@ -73,6 +73,25 @@ export function useDeleteItem(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple items in bulk.
|
||||||
|
*/
|
||||||
|
export function useBulkDeleteItems(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(ids: number[]) => apiRequest.post('items/bulk-delete', { ids }),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate the given item.
|
* Activate the given item.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -88,6 +88,25 @@ export function useDeleteJournal(props) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple manual journals in bulk.
|
||||||
|
*/
|
||||||
|
export function useBulkDeleteManualJournals(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(ids: number[]) => apiRequest.post('manual-journals/bulk-delete', { ids }),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publishes the given manual journal.
|
* Publishes the given manual journal.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -150,6 +150,25 @@ export function useDeletePaymentReceive(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple payments received in bulk.
|
||||||
|
*/
|
||||||
|
export function useBulkDeletePaymentReceives(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(ids: number[]) => apiRequest.post('payments-received/bulk-delete', { ids }),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve specific payment receive.
|
* Retrieve specific payment receive.
|
||||||
* @param {number} id - Payment receive.
|
* @param {number} id - Payment receive.
|
||||||
|
|||||||
@@ -113,6 +113,25 @@ export function useDeleteVendorCredit(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes multiple vendor credits in bulk.
|
||||||
|
*/
|
||||||
|
export function useBulkDeleteVendorCredits(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(ids: number[]) => apiRequest.post('vendor-credits/bulk-delete', { ids }),
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const transformVendorCreditsResponse = (response) => ({
|
const transformVendorCreditsResponse = (response) => ({
|
||||||
vendorCredits: response.data.vendor_credits,
|
vendorCredits: response.data.vendor_credits,
|
||||||
pagination: transformPagination(response.data.pagination),
|
pagination: transformPagination(response.data.pagination),
|
||||||
|
|||||||
@@ -14,3 +14,9 @@ export const resetBillsTableState = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setBillsSelectedRows = (selectedRows) => {
|
||||||
|
return {
|
||||||
|
type: 'BILLS/SET_SELECTED_ROWS',
|
||||||
|
payload: selectedRows,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const defaultTableQuery = {
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
tableState: defaultTableQuery,
|
tableState: defaultTableQuery,
|
||||||
|
selectedRows: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'bigcapital:bills';
|
const STORAGE_KEY = 'bigcapital:bills';
|
||||||
@@ -27,6 +28,10 @@ const CONFIG = {
|
|||||||
const reducerInstance = createReducer(initialState, {
|
const reducerInstance = createReducer(initialState, {
|
||||||
...createTableStateReducers('BILLS', defaultTableQuery),
|
...createTableStateReducers('BILLS', defaultTableQuery),
|
||||||
|
|
||||||
|
[`BILLS/SET_SELECTED_ROWS`]: (state, action) => {
|
||||||
|
state.selectedRows = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
[t.RESET]: () => {
|
[t.RESET]: () => {
|
||||||
purgeStoredState(CONFIG);
|
purgeStoredState(CONFIG);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,4 +14,9 @@ export const resetCreditNoteTableState = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setSelectedRowsItems = () => {};
|
export const setCreditNotesSelectedRows = (selectedRows) => {
|
||||||
|
return {
|
||||||
|
type: 'CREDIT_NOTES/SET_SELECTED_ROWS',
|
||||||
|
payload: selectedRows,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const defaultTableQuery = {
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
tableState: defaultTableQuery,
|
tableState: defaultTableQuery,
|
||||||
|
selectedRows: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'bigcapital:credit_notes';
|
const STORAGE_KEY = 'bigcapital:credit_notes';
|
||||||
@@ -27,6 +28,10 @@ const CONFIG = {
|
|||||||
const reducerInstance = createReducer(initialState, {
|
const reducerInstance = createReducer(initialState, {
|
||||||
...createTableStateReducers('CREDIT_NOTES', defaultTableQuery),
|
...createTableStateReducers('CREDIT_NOTES', defaultTableQuery),
|
||||||
|
|
||||||
|
[`CREDIT_NOTES/SET_SELECTED_ROWS`]: (state, action) => {
|
||||||
|
state.selectedRows = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
[t.RESET]: () => {
|
[t.RESET]: () => {
|
||||||
purgeStoredState(CONFIG);
|
purgeStoredState(CONFIG);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,3 +13,10 @@ export const resetEstimatesTableState = () => {
|
|||||||
type: t.ESTIMATES_TABLE_STATE_RESET,
|
type: t.ESTIMATES_TABLE_STATE_RESET,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setEstimatesSelectedRows = (selectedRows) => {
|
||||||
|
return {
|
||||||
|
type: 'ESTIMATES/SET_SELECTED_ROWS',
|
||||||
|
payload: selectedRows,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const defaultTableQuery = {
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
tableState: defaultTableQuery,
|
tableState: defaultTableQuery,
|
||||||
|
selectedRows: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'bigcapital:estimates';
|
const STORAGE_KEY = 'bigcapital:estimates';
|
||||||
@@ -27,6 +28,10 @@ const CONFIG = {
|
|||||||
const reducerInstance = createReducer(initialState, {
|
const reducerInstance = createReducer(initialState, {
|
||||||
...createTableStateReducers('ESTIMATES', defaultTableQuery),
|
...createTableStateReducers('ESTIMATES', defaultTableQuery),
|
||||||
|
|
||||||
|
[`ESTIMATES/SET_SELECTED_ROWS`]: (state, action) => {
|
||||||
|
state.selectedRows = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
[t.RESET]: () => {
|
[t.RESET]: () => {
|
||||||
purgeStoredState(CONFIG);
|
purgeStoredState(CONFIG);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,4 +14,9 @@ export const resetInvoicesTableState= () => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setSelectedRowsItems = () => {};
|
export const setInvoicesSelectedRows = (selectedRows) => {
|
||||||
|
return {
|
||||||
|
type: 'INVOICES/SET_SELECTED_ROWS',
|
||||||
|
payload: selectedRows,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const defaultTableQuery = {
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
tableState: defaultTableQuery,
|
tableState: defaultTableQuery,
|
||||||
|
selectedRows: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'bigcapital:invoices';
|
const STORAGE_KEY = 'bigcapital:invoices';
|
||||||
@@ -27,6 +28,10 @@ const CONFIG = {
|
|||||||
const reducerInstance = createReducer(initialState, {
|
const reducerInstance = createReducer(initialState, {
|
||||||
...createTableStateReducers('INVOICES', defaultTableQuery),
|
...createTableStateReducers('INVOICES', defaultTableQuery),
|
||||||
|
|
||||||
|
[`INVOICES/SET_SELECTED_ROWS`]: (state, action) => {
|
||||||
|
state.selectedRows = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
[t.RESET]: () => {
|
[t.RESET]: () => {
|
||||||
purgeStoredState(CONFIG);
|
purgeStoredState(CONFIG);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,3 +13,10 @@ export const resetPaymentReceivesTableState = () => {
|
|||||||
type: t.PAYMENT_RECEIVES_TABLE_STATE_RESET
|
type: t.PAYMENT_RECEIVES_TABLE_STATE_RESET
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const setPaymentReceivesSelectedRows = (selectedRows) => {
|
||||||
|
return {
|
||||||
|
type: 'PAYMENT_RECEIVES/SET_SELECTED_ROWS',
|
||||||
|
payload: selectedRows,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const defaultTableQuery = {
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
tableState: defaultTableQuery,
|
tableState: defaultTableQuery,
|
||||||
|
selectedRows: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'bigcapital:paymentReceives';
|
const STORAGE_KEY = 'bigcapital:paymentReceives';
|
||||||
@@ -27,6 +28,10 @@ const CONFIG = {
|
|||||||
const reducerInstance = createReducer(initialState, {
|
const reducerInstance = createReducer(initialState, {
|
||||||
...createTableStateReducers('PAYMENT_RECEIVES', defaultTableQuery),
|
...createTableStateReducers('PAYMENT_RECEIVES', defaultTableQuery),
|
||||||
|
|
||||||
|
[`PAYMENT_RECEIVES/SET_SELECTED_ROWS`]: (state, action) => {
|
||||||
|
state.selectedRows = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
[t.RESET]: () => {
|
[t.RESET]: () => {
|
||||||
purgeStoredState(CONFIG);
|
purgeStoredState(CONFIG);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const defaultTableQuery = {
|
|||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
tableState: defaultTableQuery,
|
tableState: defaultTableQuery,
|
||||||
|
selectedRows: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'bigcapital:vendor_credits';
|
const STORAGE_KEY = 'bigcapital:vendor_credits';
|
||||||
@@ -27,6 +28,10 @@ const CONFIG = {
|
|||||||
const reducerInstance = createReducer(initialState, {
|
const reducerInstance = createReducer(initialState, {
|
||||||
...createTableStateReducers('VENDOR_CREDITS', defaultTableQuery),
|
...createTableStateReducers('VENDOR_CREDITS', defaultTableQuery),
|
||||||
|
|
||||||
|
[`VENDOR_CREDITS/SET_SELECTED_ROWS`]: (state, action) => {
|
||||||
|
state.selectedRows = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
[t.RESET]: () => {
|
[t.RESET]: () => {
|
||||||
purgeStoredState(CONFIG);
|
purgeStoredState(CONFIG);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,4 +14,9 @@ export const resetVendorCreditTableState = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setSelectedRowsItems = () => {};
|
export const setVendorCreditsSelectedRows = (selectedRows) => {
|
||||||
|
return {
|
||||||
|
type: 'VENDOR_CREDITS/SET_SELECTED_ROWS',
|
||||||
|
payload: selectedRows,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -16,3 +16,13 @@ export const resetAccountsTableState = () => {
|
|||||||
type: t.ACCOUNTS_TABLE_STATE_RESET,
|
type: t.ACCOUNTS_TABLE_STATE_RESET,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the selected rows for accounts table.
|
||||||
|
*/
|
||||||
|
export const setAccountsSelectedRows = (selectedRows) => {
|
||||||
|
return {
|
||||||
|
type: 'ACCOUNTS/SET_SELECTED_ROWS',
|
||||||
|
payload: selectedRows,
|
||||||
|
};
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user