feat: bulk transcations delete

This commit is contained in:
Ahmed Bouhuolia
2025-11-03 21:40:24 +02:00
parent 8161439365
commit a0bc9db9a6
107 changed files with 2213 additions and 156 deletions

View 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[];
}

View File

@@ -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({

View File

@@ -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,

View File

@@ -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);
};
} }

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View 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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View 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 { 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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View 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;
}
}
}

View File

@@ -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);
}
} }

View File

@@ -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],
}) })

View File

@@ -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);
}
} }

View File

@@ -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;
}
}
}

View File

@@ -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[];
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View 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 { 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;
}
}
}

View File

@@ -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;
}
}
}

View 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 { 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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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 },
]; ];

View File

@@ -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>

View File

@@ -182,6 +182,7 @@ function AccountsActionsBar({
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={handleBulkDelete} onClick={handleBulkDelete}
/> />
<NavbarDivider />
</If> </If>
<Button <Button

View File

@@ -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 },
]; ];

View File

@@ -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,
})), })),

View File

@@ -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;
}; };

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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'} />

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
} }

View File

@@ -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 },
]; ];

View File

@@ -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>
) : ( ) : (

View File

@@ -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}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 },
]; ];

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,
},
]; ];

View File

@@ -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,
},
]; ];

View File

@@ -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}

View File

@@ -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>

View File

@@ -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;
}; };

View File

@@ -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);

View File

@@ -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 },
]; ];

View File

@@ -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,

View File

@@ -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}

View File

@@ -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>

View File

@@ -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;
}; };

View File

@@ -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);

View File

@@ -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 },
]; ];

View File

@@ -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,

View File

@@ -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}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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;
}; };

View File

@@ -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,
},
]; ];

View File

@@ -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>

View File

@@ -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.
*/ */

View File

@@ -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),

View File

@@ -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),

View File

@@ -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.
*/ */

View File

@@ -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.
*/ */

View File

@@ -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),

View File

@@ -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.
*/ */

View File

@@ -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.
*/ */

View File

@@ -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.

View File

@@ -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),

View File

@@ -14,3 +14,9 @@ export const resetBillsTableState = () => {
}; };
}; };
export const setBillsSelectedRows = (selectedRows) => {
return {
type: 'BILLS/SET_SELECTED_ROWS',
payload: selectedRows,
};
};

View File

@@ -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);
}, },

View File

@@ -14,4 +14,9 @@ export const resetCreditNoteTableState = () => {
}; };
}; };
export const setSelectedRowsItems = () => {}; export const setCreditNotesSelectedRows = (selectedRows) => {
return {
type: 'CREDIT_NOTES/SET_SELECTED_ROWS',
payload: selectedRows,
};
};

View File

@@ -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);
}, },

View File

@@ -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,
};
};

View File

@@ -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);
}, },

View File

@@ -14,4 +14,9 @@ export const resetInvoicesTableState= () => {
}; };
} }
export const setSelectedRowsItems = () => {}; export const setInvoicesSelectedRows = (selectedRows) => {
return {
type: 'INVOICES/SET_SELECTED_ROWS',
payload: selectedRows,
};
};

View File

@@ -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);
}, },

View File

@@ -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,
};
};

View File

@@ -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);
}, },

View File

@@ -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);
}, },

View File

@@ -14,4 +14,9 @@ export const resetVendorCreditTableState = () => {
}; };
}; };
export const setSelectedRowsItems = () => {}; export const setVendorCreditsSelectedRows = (selectedRows) => {
return {
type: 'VENDOR_CREDITS/SET_SELECTED_ROWS',
payload: selectedRows,
};
};

View File

@@ -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