mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 13:20:31 +00:00
feat: Bulk delete customers and expenses.
This commit is contained in:
@@ -55,7 +55,7 @@ export default [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
text: <T id={'customers'} />,
|
text: <T id={'customers'} />,
|
||||||
// href: '/',
|
href: '/customers',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: <T id={'new_customers'} />,
|
text: <T id={'new_customers'} />,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
query,
|
query,
|
||||||
validationResult,
|
validationResult,
|
||||||
} from 'express-validator';
|
} from 'express-validator';
|
||||||
import { pick } from 'lodash';
|
import { pick, difference } from 'lodash';
|
||||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||||
import {
|
import {
|
||||||
mapViewRolesToConditionals,
|
mapViewRolesToConditionals,
|
||||||
@@ -77,6 +77,10 @@ export default {
|
|||||||
this.deleteCustomer.validation,
|
this.deleteCustomer.validation,
|
||||||
asyncMiddleware(this.deleteCustomer.handler));
|
asyncMiddleware(this.deleteCustomer.handler));
|
||||||
|
|
||||||
|
router.delete('/',
|
||||||
|
this.deleteBulkCustomers.validation,
|
||||||
|
asyncMiddleware(this.deleteBulkCustomers.handler));
|
||||||
|
|
||||||
router.get('/',
|
router.get('/',
|
||||||
this.listCustomers.validation,
|
this.listCustomers.validation,
|
||||||
asyncMiddleware(this.listCustomers.handler));
|
asyncMiddleware(this.listCustomers.handler));
|
||||||
@@ -380,5 +384,40 @@ export default {
|
|||||||
|
|
||||||
return res.status(200).send();
|
return res.status(200).send();
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bulk delete customers.
|
||||||
|
*/
|
||||||
|
deleteBulkCustomers: {
|
||||||
|
validation: [
|
||||||
|
query('ids').isArray({ min: 2 }),
|
||||||
|
query('ids.*').isNumeric().toInt(),
|
||||||
|
],
|
||||||
|
async handler(req, res) {
|
||||||
|
const validationErrors = validationResult(req);
|
||||||
|
|
||||||
|
if (!validationErrors.isEmpty()) {
|
||||||
|
return res.boom.badData(null, {
|
||||||
|
code: 'validation_error', ...validationErrors,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const filter = { ...req.query };
|
||||||
|
const { Customer } = req.models;
|
||||||
|
|
||||||
|
const customers = await Customer.query().whereIn('id', filter.ids);
|
||||||
|
const storedCustomersIds = customers.map((customer) => customer.id);
|
||||||
|
|
||||||
|
const notFoundCustomers = difference(filter.ids, storedCustomersIds);
|
||||||
|
|
||||||
|
if (notFoundCustomers.length > 0) {
|
||||||
|
return res.status(404).send({
|
||||||
|
errors: [{ type: 'CUSTOMERS.NOT.FOUND', code: 200 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await Customer.query().whereIn('id', storedCustomersIds).delete();
|
||||||
|
|
||||||
|
return res.status(200).send({ ids: storedCustomersIds });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,6 +42,10 @@ export default {
|
|||||||
this.deleteExpense.validation,
|
this.deleteExpense.validation,
|
||||||
asyncMiddleware(this.deleteExpense.handler));
|
asyncMiddleware(this.deleteExpense.handler));
|
||||||
|
|
||||||
|
router.delete('/',
|
||||||
|
this.deleteBulkExpenses.validation,
|
||||||
|
asyncMiddleware(this.deleteBulkExpenses.handler));
|
||||||
|
|
||||||
router.post('/:id',
|
router.post('/:id',
|
||||||
this.updateExpense.validation,
|
this.updateExpense.validation,
|
||||||
asyncMiddleware(this.updateExpense.handler));
|
asyncMiddleware(this.updateExpense.handler));
|
||||||
@@ -195,7 +199,7 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { Expense, AccountTransaction } = req.models;
|
const { Expense, Account, AccountTransaction } = req.models;
|
||||||
const expense = await Expense.query().findById(id);
|
const expense = await Expense.query().findById(id);
|
||||||
const errorReasons = [];
|
const errorReasons = [];
|
||||||
|
|
||||||
@@ -624,4 +628,62 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes bulk expenses.
|
||||||
|
*/
|
||||||
|
deleteBulkExpenses: {
|
||||||
|
validation: [
|
||||||
|
query('ids').isArray({ min: 1 }),
|
||||||
|
query('ids.*').isNumeric().toInt(),
|
||||||
|
],
|
||||||
|
async handler(req, res) {
|
||||||
|
const validationErrors = validationResult(req);
|
||||||
|
|
||||||
|
if (!validationErrors.isEmpty()) {
|
||||||
|
return res.boom.badData(null, {
|
||||||
|
code: 'validation_error', ...validationErrors,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const filter = { ...req.query };
|
||||||
|
const { Expense, AccountTransaction, Account, MediaLink } = req.models;
|
||||||
|
|
||||||
|
const expenses = await Expense.query()
|
||||||
|
.whereIn('id', filter.ids)
|
||||||
|
|
||||||
|
const storedExpensesIds = expenses.map(e => e.id);
|
||||||
|
const notFoundExpenses = difference(filter.ids, storedExpensesIds);
|
||||||
|
|
||||||
|
if (notFoundExpenses.length > 0) {
|
||||||
|
return res.status(404).send({
|
||||||
|
errors: [{ type: 'EXPENSES.NOT.FOUND', code: 200 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteExpensesOper = Expense.query()
|
||||||
|
.whereIn('id', storedExpensesIds).delete();
|
||||||
|
|
||||||
|
const transactions = await AccountTransaction.query()
|
||||||
|
.whereIn('reference_type', ['Expense'])
|
||||||
|
.whereIn('reference_id', filter.ids)
|
||||||
|
|
||||||
|
const accountsDepGraph = await Account.depGraph().query().remember();
|
||||||
|
const journal = new JournalPoster(accountsDepGraph);
|
||||||
|
|
||||||
|
journal.loadEntries(transactions);
|
||||||
|
journal.removeEntries();
|
||||||
|
|
||||||
|
await MediaLink.query()
|
||||||
|
.where('model_name', 'Expense')
|
||||||
|
.whereIn('model_id', filter.ids)
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
deleteExpensesOper,
|
||||||
|
journal.deleteEntries(),
|
||||||
|
journal.saveBalance(),
|
||||||
|
]);
|
||||||
|
return res.status(200).send({ ids: filter.ids });
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
} from '~/dbInit';
|
} from '~/dbInit';
|
||||||
|
|
||||||
|
|
||||||
describe.only('routes: /accounts/', () => {
|
describe('routes: /accounts/', () => {
|
||||||
describe('POST `/accounts`', () => {
|
describe('POST `/accounts`', () => {
|
||||||
it('Should `name` be required.', async () => {
|
it('Should `name` be required.', async () => {
|
||||||
const res = await request()
|
const res = await request()
|
||||||
@@ -190,7 +190,7 @@ describe.only('routes: /accounts/', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.only('Should response success with correct data form.', async () => {
|
it('Should response success with correct data form.', async () => {
|
||||||
const account = await tenantFactory.create('account');
|
const account = await tenantFactory.create('account');
|
||||||
const res = await request()
|
const res = await request()
|
||||||
.post('/api/accounts')
|
.post('/api/accounts')
|
||||||
|
|||||||
@@ -209,4 +209,42 @@ describe('route: `/customers`', () => {
|
|||||||
expect(foundCustomer[0].displayName).equals('Ahmed Bouhuolia, Bigcapital');
|
expect(foundCustomer[0].displayName).equals('Ahmed Bouhuolia, Bigcapital');
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('DELETE: `customers`', () => {
|
||||||
|
it('Should response customers ids not found.', async () => {
|
||||||
|
const res = await request()
|
||||||
|
.delete('/api/customers')
|
||||||
|
.set('x-access-token', loginRes.body.token)
|
||||||
|
.set('organization-id', tenantWebsite.organizationId)
|
||||||
|
.query({
|
||||||
|
ids: [100, 200],
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
|
||||||
|
expect(res.status).equals(404);
|
||||||
|
expect(res.body.errors).include.something.deep.equals({
|
||||||
|
type: 'CUSTOMERS.NOT.FOUND', code: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should delete the given customers.', async () => {
|
||||||
|
const customer1 = await tenantFactory.create('customer');
|
||||||
|
const customer2 = await tenantFactory.create('customer');
|
||||||
|
|
||||||
|
const res = await request()
|
||||||
|
.delete('/api/customers')
|
||||||
|
.set('x-access-token', loginRes.body.token)
|
||||||
|
.set('organization-id', tenantWebsite.organizationId)
|
||||||
|
.query({
|
||||||
|
ids: [customer1.id, customer2.id],
|
||||||
|
})
|
||||||
|
.send();
|
||||||
|
|
||||||
|
const foundCustomers = await Customer.tenant().query()
|
||||||
|
.whereIn('id', [customer1.id, customer2.id]);
|
||||||
|
|
||||||
|
expect(res.status).equals(200);
|
||||||
|
expect(foundCustomers.length).equals(0);
|
||||||
|
});
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -678,4 +678,42 @@ describe('routes: /expenses/', () => {
|
|||||||
expect(foundExpenseCategory[0].amount).equals(3000);
|
expect(foundExpenseCategory[0].amount).equals(3000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('DELETE: `/api/expenses`', () => {
|
||||||
|
it('Should response not found expenses ids.', async () => {
|
||||||
|
const res = await request()
|
||||||
|
.delete('/api/expenses')
|
||||||
|
.set('x-access-token', loginRes.body.token)
|
||||||
|
.set('organization-id', tenantWebsite.organizationId)
|
||||||
|
.query({
|
||||||
|
ids: [100, 200],
|
||||||
|
})
|
||||||
|
.send({});
|
||||||
|
|
||||||
|
expect(res.status).equals(404);
|
||||||
|
expect(res.body.errors).include.something.deep.equals({
|
||||||
|
type: 'EXPENSES.NOT.FOUND', code: 200,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should delete the given expenses ids.', async () => {
|
||||||
|
const expense1 = await tenantFactory.create('expense');
|
||||||
|
const expense2 = await tenantFactory.create('expense');
|
||||||
|
|
||||||
|
const res = await request()
|
||||||
|
.delete('/api/expenses')
|
||||||
|
.set('x-access-token', loginRes.body.token)
|
||||||
|
.set('organization-id', tenantWebsite.organizationId)
|
||||||
|
.query({
|
||||||
|
ids: [expense1.id, expense2.id],
|
||||||
|
})
|
||||||
|
.send({});
|
||||||
|
|
||||||
|
const foundExpenses = await Expense.tenant().query()
|
||||||
|
.whereIn('id', [expense1.id, expense2.id]);
|
||||||
|
|
||||||
|
expect(res.status).equals(200);
|
||||||
|
expect(foundExpenses.length).equals(0);
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user