Skip to content

Commit

Permalink
feat(cash): delete cash payments
Browse files Browse the repository at this point in the history
This commit implements the server-side logic and code for deleting cash
payments.  An integration tests shows that the functionality is intact.
This goes halfway to addressing #2159.
  • Loading branch information
jniles committed Oct 9, 2017
1 parent fc3247e commit e433c26
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 52 deletions.
3 changes: 0 additions & 3 deletions .bowerrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
{
"directory": "client/vendor",
"analytics": false,
"resolvers": [
"bower-npm-resolver"
]
}
31 changes: 30 additions & 1 deletion server/controllers/finance/cash.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ function update(req, res, next) {
/**
* GET /cash/:checkin/:invoiceUuid
* Check if the invoice is paid
* TODO(@jniles) - this should use a more intelligent system to see if an
* invoice is referenced ... probably by scanning the ledgers for any
* referencing transactions.
*/
function checkInvoicePayment(req, res, next) {
const bid = db.bid(req.params.invoiceUuid);
Expand All @@ -259,8 +262,34 @@ function checkInvoicePayment(req, res, next) {
* @function safelyDeleteCashPayment
*
* @description
* This function deletes the cash payment
* This function deletes the cash payment from the system. It assumes that
* checks have already been made for referencing transactions.
*/
function safelyDeleteCashPayment(uuid) {
const DELETE_TRANSACTION = `
DELETE FROM posting_journal WHERE record_uuid = ?;
`;

const DELETE_CASH_PAYMENT = `
DELETE FROM cash WHERE uuid = ?;
`;

const DELETE_TRANSACTION_HISTORY = `
DELETE FROM transaction_history WHERE record_uuid = ?;
`;

const DELETE_DOCUMENT_MAP = `
DELETE FROM document_map WHERE uuid = ?;
`;

const binaryUuid = db.bid(uuid);
const transaction = db.transaction();

transaction
.addQuery(DELETE_TRANSACTION, binaryUuid)
.addQuery(DELETE_TRANSACTION_HISTORY, binaryUuid)
.addQuery(DELETE_CASH_PAYMENT, binaryUuid)
.addQuery(DELETE_DOCUMENT_MAP, binaryUuid);

return transaction.execute();
}
161 changes: 161 additions & 0 deletions server/controllers/finance/transactions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/**
* @overview transactions.js
*
* @description
* This module contains helper functions for operating on transactions. These
* helper functions
*
* @requires lib/db
* @requires lib/errors/BadRequest
*
* @requires controllers/finance/cash
* @requires controllers/finance/vouchers
* @requires controllers/finance/patientInvoice
*/

const db = require('../../lib/db');
const BadRequest = require('../../lib/errors/BadRequest');

const Cash = require('./cash');

// const Invoices = require('./patientInvoice');
// const Vouchers = require('./vouchers');

// this wraps up the safe deletion methods.
// TODO(@jniles) - move these to the `indentifiers` as suggested by @sfount
const safeDeletionMethods = {
CP : Cash.safelyDeleteCashPayment,
// IV : Invoices.safelyDeleteInvoice, TODO(@jniles)
// VO : Vouchers.safelyDeleteVoucher, TODO(@jniles)
};

exports.deleteTransaction = deleteTransaction;

/**
* @function getTransactionReferences
*
* @description
* This function will find the uuids of any transactions that reference the
* provided transaction's uuid.
*
* @param {String} transactionUuid - the record_uuid of the transaction
*/
function getTransactionReferences(transactionUuid) {
const sql = `
SELECT DISTINCT uuid, text FROM (
SELECT dm.uuid, dm.text
FROM posting_journal AS j JOIN document_map AS dm ON
j.reference_uuid = dm.uuid
WHERE j.reference_uuid = ?
UNION ALL
SELECT dm.uuid, dm.text
FROM general_ledger AS g JOIN document_map AS dm ON
g.reference_uuid = dm.uuid
WHERE g.reference_uuid = ?
)c;
`;

const buid = db.bid(transactionUuid);

return db.exec(sql, [buid, buid]);
}

/**
* @function parseDocumentMapString
*
* @description
* This function parses the document map identifier and returns the safe
* deletion method associated with this document.
*
* @param {String} text - the text of the document map
*
* @returns {Function} the safe deletion method associated with the module
*/
function parseDocumentMapString(text) {
const key = text.split('.').shift();
return safeDeletionMethods[key];
}

/**
* @function getTransactionRecords
*
* @description
* Returns the transaction from the posting journal and general_ledger.
*/
function getTransactionRecords(uuid) {
const sql = `
SELECT BUID(j.uuid) AS uuid, trans_id, BUID(record_uuid) AS record_uuid,
trans_date, debit_equiv, credit_equiv, currency_id,
BUID(reference_uuid) AS reference_uuid,
BUID(entity_uuid) AS entity_uuid, 0 AS posted,
document_map.text AS identifier
FROM posting_journal AS j JOIN document_map ON
j.record_uuid = document_map.uuid
WHERE record_uuid = ?
UNION ALL
SELECT BUID(j.uuid) AS uuid, trans_id, BUID(record_uuid) AS record_uuid,
trans_date, debit_equiv, credit_equiv, currency_id,
BUID(reference_uuid) AS reference_uuid,
BUID(entity_uuid) AS entity_uuid, 0 AS posted,
document_map.text AS identifier
FROM general_ledger AS j JOIN document_map ON
j.record_uuid = document_map.uuid
WHERE record_uuid = ?
`;

return db.exec(sql, [db.bid(uuid), db.bid(uuid)]);
}


/**
* @function deleteTransation
*
* @description
* This function is the HTTP handler for the delete transactions route.
*
* DELETE /transactions/:uuid
*/
function deleteTransaction(req, res, next) {
const { uuid } = req.params;
let transaction;

// get all the rows of the transaction
getTransactionRecords(uuid)
.then(rows => {
transaction = rows;

// TODO(@jniles) - i18n
const isPosted = transaction[0].posted;
if (isPosted) {
throw new BadRequest('This transaction is already posted');
}

// check if the transaction has references elsewhere
return getTransactionReferences(uuid);
})
.then(references => {
// TODO(@jniles) - i18n
const isReferenced = references.length > 0;
if (isReferenced) {
throw new BadRequest('This transaction is referenced');
}

const documentMapText = transaction[0].identifier;

// route to do the correct safe deletion method.
const safeDeleteFn = parseDocumentMapString(documentMapText);

// run the safe deletion method
return safeDeleteFn(uuid);
})
.then(() => {
res.sendStatus(201);
})
.catch(next)
.done();
}

2 changes: 1 addition & 1 deletion server/models/updates/service_uuid.sql
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
-- UPDATE SERVICES WHICH DOESN'T HAVE UUID
UPDATE `service` SET `uuid` = HUID(UUID()) WHERE `uuid` IS NULL;
UPDATE `service` SET `uuid` = HUID(UUID()) WHERE `uuid` IS NULL;
3 changes: 0 additions & 3 deletions sh/build-database.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ mysql -u $DB_USER -p$DB_PASS $DB_NAME < server/models/bhima.sql &> /dev/null
echo "[build] test data"
mysql -u $DB_USER -p$DB_PASS $DB_NAME < test/data.sql &> /dev/null

echo "[update] account type to account category"
mysql -u $DB_USER -p$DB_PASS $DB_NAME < server/models/updates/account_type.sql &> /dev/null

echo "[update] service uuid identifiers"
mysql -u $DB_USER -p$DB_PASS $DB_NAME < server/models/updates/service_uuid.sql &> /dev/null

Expand Down
43 changes: 26 additions & 17 deletions test/data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@ INSERT INTO `project` VALUES (1,'Test Project A','TPA',1,1,0),(2,'Test Project B

-- Accounts
INSERT INTO `account` VALUES
(3626,4,1,1000,'Test Capital Account',0,0,NULL,NULL,'2015-11-04 13:25:12',1,NULL,NULL,NULL,1,1),
(3626,6,1,1000,'Test Capital Account',0,0,NULL,NULL,'2015-11-04 13:25:12',1,NULL,NULL,NULL,1,1),
(3627,3,1,1100,'Test Capital One',3626,0,NULL,NULL,'2015-11-04 13:26:13',1,1,NULL,0,NULL,0),
(3628,3,1,1200,'Test Capital Two',3626,0,NULL,NULL,'2015-11-04 13:27:13',1,1,NULL,0,NULL,0),
(3629,4,1,40000,'Test Balance Accounts',0,0,NULL,NULL,'2015-11-04 13:29:11',4,NULL,NULL,NULL,1,1),
(3630,3,1,41001,'Test Debtor Accounts1',3629,0,NULL,NULL,'2015-11-04 13:30:46',4,NULL,NULL,NULL,NULL,0),
(3631,3,1,41002,'Test Debtor Accounts2',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1),
(3635,3,1,41003,'Test Debtor Accounts3',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1),
(3636,4,1,46000,'Test Inventory Accounts',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1),
(3637,3,1,46001,'First Test Item Account',3636,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,0),
(3638,3,1,47001,'Test Debtor Group Account',3626,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,0,NULL,0),
(3639,4,1,57000,'Test Income Accounts',0,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,0,NULL,0),
(3640,3,1,57003,'Test Gain Account',3639,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,0,NULL,0),
(3641,4,1,67000,'Test Expense Accounts',0,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,0,NULL,0),
(3642,3,1,67003,'Test Loss Account',3641,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,0,NULL,0),
(3643,3,1,42001,'Test Creditor Accounts1',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1),
(3644,3,1,42002,'Test Creditor Accounts2',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1),
(3645,3,1,42003,'Test Creditor Accounts3',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1);
(3629,6,1,40000,'Test Balance Accounts',0,0,NULL,NULL,'2015-11-04 13:29:11',4,NULL,NULL,NULL,1,1),
(3630,1,1,41001,'Test Debtor Accounts1',3629,0,NULL,NULL,'2015-11-04 13:30:46',4,NULL,NULL,NULL,NULL,0),
(3631,1,1,41002,'Test Debtor Accounts2',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1),
(3635,1,1,41003,'Test Debtor Accounts3',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1),
(3636,6,1,46000,'Test Inventory Accounts',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1),
(3637,1,1,46001,'First Test Item Account',3636,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,0),
(3638,1,1,47001,'Test Debtor Group Account',3626,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,0,NULL,0),
(3639,6,1,57000,'Test Income Accounts',0,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,0,NULL,0),
(3640,1,1,57003,'Test Gain Account',3639,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,0,NULL,0),
(3641,6,1,67000,'Test Expense Accounts',0,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,0,NULL,0),
(3642,5,1,67003,'Test Loss Account',3641,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,0,NULL,0),
(3643,1,1,42001,'Test Creditor Accounts1',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1),
(3644,1,1,42002,'Test Creditor Accounts2',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1),
(3645,1,1,42003,'Test Creditor Accounts3',3629,0,NULL,NULL,'2015-11-04 13:32:22',4,NULL,NULL,NULL,NULL,1);

-- attach gain/loss accounts to the enterprise
UPDATE enterprise SET `gain_account_id` = 3640, `loss_account_id` = 3641;
Expand Down Expand Up @@ -226,6 +226,11 @@ CALL CreateFiscalYear(1, @fiscalYear2016, 1, 'Test Fiscal Year 2017', 12, DATE('
-- give test permission to both projects
INSERT INTO `project_permission` VALUES (1,1,1),(2,1,2),(3,2,1), (4, 4, 1);

-- exchange rate for the current date
INSERT INTO `exchange_rate` VALUES
(1,1,1,900.0000, DATE('2016-01-01')),
(2,1,1,930.0000, NOW());

INSERT INTO `cash_box` (id, label, project_id, is_auxiliary) VALUES
(1,'Test Primary Cashbox A',1,0),
(2,'Test Aux Cashbox A',1,1),
Expand Down Expand Up @@ -387,10 +392,14 @@ CALL PostInvoice(@second_invoice);

-- cash payment
SET @cash_payment = HUID('2e1332b7-3e63-411e-827d-42ad585ff517');
SET @cash_payment_2 = HUID('2e1332b7-3e23-411e-527d-42ac585ff517');

-- @todo Make sure this is in the posting_journal
INSERT INTO cash (uuid, project_id, reference, date, debtor_uuid, currency_id, amount, user_id, cashbox_id, description, is_caution) VALUES
(@cash_payment, 1, 1, '2016-01-09 14:33:13', HUID('3be232f9-a4b9-4af6-984c-5d3f87d5c107'), 1, 100, 1, 2, "Some cool description", 1);
(@cash_payment, 1, 1, '2016-01-09 14:33:13', HUID('3be232f9-a4b9-4af6-984c-5d3f87d5c107'), 1, 100, 1, 2, "Some cool description", 1),
(@cash_payment_2, 1, 2, '2016-01-10 15:33:00', HUID('3be232f9-a4b9-4af6-984c-5d3f87d5c107'), 1, 25, 1, 2, "This will be deleted in tests", 1);

-- FIXME(@jniles): this is kind of a hack
CALL PostCash(@cash_payment_2);


INSERT INTO cash_item (uuid, cash_uuid, amount, invoice_uuid) VALUES
Expand Down
4 changes: 2 additions & 2 deletions test/end-to-end/cash/registry.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const components = require('../shared/components');
describe('Payments Registry', CashPaymentsRegistryTests);

function CashPaymentsRegistryTests() {
const PAYMENT_INSIDE_REGISTRY = 3;
const PAYMENT_INSIDE_REGISTRY = 4;
const PAYMENT_PRIMARY_CASHBOX = 0;
const DEBTOR_GROUP = 'First Test Debtor Group';
let modal;
Expand Down Expand Up @@ -42,7 +42,7 @@ function CashPaymentsRegistryTests() {
GU.expectRowCount('payment-registry', DEFAULT_PAYMENTS_FOR_TODAY);
});

it('finds three payments for all time', () => {
it(`finds ${PAYMENT_INSIDE_REGISTRY} payments for all time`, () => {
modal.switchToDefaultFilterTab();
modal.setPeriod('allTime');
modal.submit();
Expand Down
28 changes: 14 additions & 14 deletions test/integration/accounts.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global expect, chai, agent */
/* global expect, agent */

const helpers = require('./helpers');

Expand Down Expand Up @@ -104,17 +104,17 @@ describe('(/accounts) Accounts', function () {
});

it('GET /accounts/:id/balance returns an object with zero as balance, debit and credit', function () {
return agent.get(`/accounts/${FETCHABLE_ACCOUNT_ID}/balance`)
.then(function (res){
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.not.be.empty;
expect(res.body).to.have.all.keys('account_id', 'debit', 'credit', 'balance');
expect(res.body.debit).to.equal(0);
expect(res.body.credit).to.equal(0);
expect(res.body.balance).to.equal(0);
})
.catch(helpers.handler);
return agent.get(`/accounts/${FETCHABLE_ACCOUNT_ID}/balance`)
.then(function (res){
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.not.be.empty;
expect(res.body).to.have.all.keys('account_id', 'debit', 'credit', 'balance');
expect(res.body.debit).to.equal(0);
expect(res.body.credit).to.equal(0);
expect(res.body.balance).to.equal(0);
})
.catch(helpers.handler);
});

it('GET /accounts/:id/balance?journal=1 returns the balance of a provided account_id, scans the journal also', function () {
Expand Down Expand Up @@ -168,7 +168,7 @@ describe('(/accounts) Accounts', function () {
helpers.api.errored(res, 404);
})
.catch(helpers.handler);
});
});

it('DELETE /accounts/:id Deletes the newly added account', function () {
return agent.delete('/accounts/'+ newAccount.id)
Expand All @@ -181,7 +181,7 @@ describe('(/accounts) Accounts', function () {
it('DELETE /accounts/:id Imprevent the deletion Of Account Parent Who have Children ', function () {
return agent.delete('/accounts/'+ FETCHABLE_ACCOUNT_ID)
.then(function (res) {
expect(res).to.have.status(400);
expect(res).to.have.status(400);
})
.catch(helpers.handler);
});
Expand Down
Loading

0 comments on commit e433c26

Please sign in to comment.