Commit f299c6d6 authored by Antoine's avatar Antoine
Browse files

Update balance computation and introduce outstanding balance computation

parent 0ba8c1d4
......@@ -14,7 +14,9 @@ import {
UNKNOWN_OPERATION_TYPE as UNKNOWN_OPERATION_TYPE_,
formatDate as formatDate_,
MIN_WEBOOB_VERSION as MIN_WEBOOB_VERSION_,
validatePassword as validatePassword_
validatePassword as validatePassword_,
shouldIncludeInBalance as shouldIncludeInBalance_,
shouldIncludeInOutstandingSum as shouldIncludeInOutstandingSum_
} from '../shared/helpers.js';
export const maybeHas = maybeHas_;
......@@ -27,6 +29,8 @@ export const UNKNOWN_OPERATION_TYPE = UNKNOWN_OPERATION_TYPE_;
export const formatDate = formatDate_;
export const MIN_WEBOOB_VERSION = MIN_WEBOOB_VERSION_;
export const validatePassword = validatePassword_;
export const shouldIncludeInBalance = shouldIncludeInBalance_;
export const shouldIncludeInOutstandingSum = shouldIncludeInOutstandingSum_;
export const AlertTypes = ['balance', 'transaction'];
......
......@@ -87,6 +87,8 @@ export class Account {
// Make sure to update `updateFrom` if you add any fields here.
this.operationIds = [];
this.balance = this.initialBalance;
// The sum of the amount of transactions not yet taken into account in the balance.
this.outstandingSum = 0;
}
static updateFrom(arg, defaultCurrency, previousAccount) {
......@@ -96,6 +98,7 @@ export class Account {
newAccount.operationIds = previousAccount.operationIds;
newAccount.balance =
previousAccount.balance - previousAccount.initialBalance + newAccount.initialBalance;
newAccount.outstandingSum = previousAccount.outstandingSum;
return newAccount;
}
......
import u from 'updeep';
import moment from 'moment';
import {
assert,
......@@ -9,7 +10,9 @@ import {
NONE_CATEGORY_ID,
translate as $t,
UNKNOWN_ACCOUNT_TYPE,
displayLabel
displayLabel,
shouldIncludeInBalance,
shouldIncludeInOutstandingSum
} from '../helpers';
import { Account, Access, Alert, Bank, Operation } from '../models';
......@@ -656,20 +659,29 @@ function addOperations(state, pOperations) {
let accountsMapUpdate = {};
let operationMapUpdate = {};
let today = moment();
for (let op of operations) {
if (typeof accountsMapUpdate[op.accountId] === 'undefined') {
let account = accountById(state, op.accountId);
accountsMapUpdate[op.accountId] = {
let operation = new Operation(op);
if (typeof accountsMapUpdate[operation.accountId] === 'undefined') {
let account = accountById(state, operation.accountId);
accountsMapUpdate[operation.accountId] = {
operationIds: account.operationIds.slice(),
balance: account.balance
balance: account.balance,
outstandingSum: account.outstandingSum,
type: account.type
};
}
let accountUpdate = accountsMapUpdate[op.accountId];
accountUpdate.balance += op.amount;
accountUpdate.operationIds.push(op.id);
let accountUpdate = accountsMapUpdate[operation.accountId];
operationMapUpdate[op.id] = new Operation(op);
if (shouldIncludeInBalance(operation, today, accountUpdate.type)) {
accountUpdate.balance += operation.amount;
} else if (shouldIncludeInOutstandingSum(operation)) {
accountUpdate.outstandingSum += operation.amount;
}
accountUpdate.operationIds.push(operation.id);
operationMapUpdate[operation.id] = operation;
}
let newState = updateOperationsMap(state, operationMapUpdate);
......@@ -884,9 +896,19 @@ function removeOperation(state, operationId) {
let op = operationById(state, operationId);
let account = accountById(state, op.accountId);
let { balance, outstandingSum } = account;
let today = moment();
if (shouldIncludeInBalance(op, today, account.type)) {
balance -= op.amount;
} else if (shouldIncludeInOutstandingSum(op)) {
outstandingSum -= op.amount;
}
let newState = updateAccountFields(state, account.id, {
operationIds: u.reject(id => id === operationId),
balance: account.balance - op.amount
balance,
outstandingSum
});
return updateOperationsMap(newState, u.omit(`${operationId}`));
......
......@@ -8,7 +8,9 @@ import {
UNKNOWN_OPERATION_TYPE as UNKNOWN_OPERATION_TYPE_,
UNKNOWN_ACCOUNT_TYPE as UNKNOWN_ACCOUNT_TYPE_,
formatDate as formatDate_,
MIN_WEBOOB_VERSION as MIN_WEBOOB_VERSION_
MIN_WEBOOB_VERSION as MIN_WEBOOB_VERSION_,
shouldIncludeInBalance as shouldIncludeInBalance_,
shouldIncludeInOutstandingSum as shouldIncludeInOutstandingSum_
} from './shared/helpers.js';
import errors from './shared/errors.json';
......@@ -22,6 +24,8 @@ export const UNKNOWN_ACCOUNT_TYPE = UNKNOWN_ACCOUNT_TYPE_;
export const setupTranslator = setupTranslator_;
export const formatDate = formatDate_;
export const MIN_WEBOOB_VERSION = MIN_WEBOOB_VERSION_;
export const shouldIncludeInBalance = shouldIncludeInBalance_;
export const shouldIncludeInOutstandingSum = shouldIncludeInOutstandingSum_;
export function makeLogger(prefix) {
return new Logger(prefix);
......
......@@ -19,7 +19,8 @@ import {
currency,
assert,
displayLabel,
UNKNOWN_OPERATION_TYPE
UNKNOWN_OPERATION_TYPE,
shouldIncludeInBalance
} from '../helpers';
import AsyncQueue from './async-queue';
......@@ -389,7 +390,7 @@ merging as per request`);
// Resync balance only if we are sure that the operation is a new one.
let accountImportDate = new Date(account.importDate);
accountInfo.balanceOffset = providerOrphans
.filter(op => +op.debitDate < +accountImportDate)
.filter(op => shouldIncludeInBalance(op, accountImportDate, account.type))
.reduce((sum, op) => sum + op.amount, 0);
}
}
......@@ -461,13 +462,12 @@ to be resynced, by an offset of ${balanceOffset}.`);
if (typeof retrievedAccount !== 'undefined') {
let realBalance = retrievedAccount.initialBalance;
let operations = await Transactions.byAccount(userId, account);
let operationsSum = operations.reduce((amount, op) => amount + op.amount, 0);
let kresusBalance = operationsSum + account.initialBalance;
let kresusBalance = await account.computeBalance();
let balanceDelta = realBalance - kresusBalance;
if (Math.abs(realBalance - kresusBalance) > 0.01) {
log.info(`Updating balance for account ${accountNumber}`);
let initialBalance = realBalance - operationsSum;
if (Math.abs(balanceDelta) > 0.001) {
log.info(`Updating balance for account ${account.accountNumber}`);
let initialBalance = account.initialBalance + balanceDelta;
return await Accounts.update(userId, account.id, { initialBalance });
}
} else {
......
import * as cozydb from 'cozydb';
import { assert, makeLogger, promisify, promisifyModel, UNKNOWN_ACCOUNT_TYPE } from '../helpers';
import moment from 'moment';
import {
assert,
makeLogger,
promisify,
promisifyModel,
UNKNOWN_ACCOUNT_TYPE,
shouldIncludeInBalance,
shouldIncludeInOutstandingSum
} from '../helpers';
import Transactions from './transactions';
......@@ -142,7 +152,19 @@ Account.updateAttributes = function() {
Account.prototype.computeBalance = async function computeBalance() {
let userId = await this.getUserId();
let ops = await Transactions.byAccount(userId, this);
let s = ops.reduce((sum, op) => sum + op.amount, this.initialBalance);
let today = moment();
let s = ops
.filter(op => shouldIncludeInBalance(op, today, this.type))
.reduce((sum, op) => sum + op.amount, this.initialBalance);
return Math.round(s * 100) / 100;
};
Account.prototype.computeOutstandingSum = async function computeOutstandingSum() {
let userId = await this.getUserId();
let ops = await Transactions.byAccount(userId, this);
let s = ops
.filter(op => shouldIncludeInOutstandingSum(op))
.reduce((sum, op) => sum + op.amount, 0);
return Math.round(s * 100) / 100;
};
......
......@@ -10,6 +10,9 @@ import Polyglot from 'node-polyglot';
import { format as currencyFormatter, findCurrency } from 'currency-formatter';
import moment from 'moment';
import ACCOUNT_TYPES from './account-types.json';
import OPERATION_TYPES from './operation-types.json';
export function maybeHas(obj, prop) {
return obj && obj.hasOwnProperty(prop);
}
......@@ -132,3 +135,21 @@ const PASSPHRASE_VALIDATION_REGEXP = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/;
export function validatePassword(password) {
return PASSPHRASE_VALIDATION_REGEXP.test(password);
}
const DEFERRED_CARD_TYPE = OPERATION_TYPES.find(type => type.name === 'type.deferred_card');
const SUMMARY_CARD_TYPE = OPERATION_TYPES.find(type => type.name === 'type.card_summary');
const ACCOUNT_TYPE_CARD = ACCOUNT_TYPES.find(type => type.name === 'account-type.card');
export const shouldIncludeInBalance = (op, balanceDate, accountType) => {
let opDebitMoment = moment(op.debitDate || op.date);
return (
opDebitMoment.isSameOrBefore(balanceDate, 'day') &&
(op.type !== DEFERRED_CARD_TYPE.name || accountType === ACCOUNT_TYPE_CARD.name)
);
};
export const shouldIncludeInOutstandingSum = op => {
let opDebitMoment = moment(op.debitDate || op.date);
let today = moment();
return opDebitMoment.isAfter(today, 'day') && op.type !== SUMMARY_CARD_TYPE.name;
};
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment