Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Jan
kresus
Commits
f6b7f824
Commit
f6b7f824
authored
May 21, 2016
by
Benjamin Bouvier
Browse files
Fixes #413: Remember amounts of transactions older than the import date of the
account to resync balance;
parent
265864d9
Changes
5
Hide whitespace changes
Inline
Side-by-side
client/components/shared/add-bank-form.js
View file @
f6b7f824
...
...
@@ -89,7 +89,7 @@ export default class NewBankForm extends React.Component {
this
.
domPassword
().
select
();
break
;
case
Errors
.
INVALID_PARAMETERS
:
alert
(
$t
(
'
client.sync.invalid_parameters
'
,
{
content
:
err
.
content
}));
alert
(
$t
(
'
client.sync.invalid_parameters
'
,
{
content
:
err
.
content
||
'
?
'
}));
break
;
case
Errors
.
EXPIRED_PASSWORD
:
alert
(
$t
(
'
client.sync.expired_password
'
));
...
...
server/lib/accounts-manager.js
View file @
f6b7f824
...
...
@@ -115,10 +115,14 @@ async function mergeAccounts(old, kid) {
await
old
.
updateAttributes
(
newAccount
);
}
function
sumOpAmounts
(
acc
,
op
)
{
return
acc
+
+
op
.
amount
;
}
export
default
class
AccountManager
{
constructor
()
{
this
.
newAccounts
=
[]
;
this
.
newAccounts
Map
=
new
Map
;
}
async
retrieveAndAddAccountsByAccess
(
access
)
{
...
...
@@ -126,6 +130,12 @@ export default class AccountManager {
}
async
retrieveAccountsByAccess
(
access
,
shouldAddNewAccounts
)
{
if
(
this
.
newAccountsMap
.
size
)
{
log
.
warn
(
`At the start of retrieveAccountsByAccess, newAccountsMap
should be empty.`
);
this
.
newAccountsMap
.
clear
();
}
if
(
!
access
.
hasPassword
())
{
log
.
warn
(
"
Skipping accounts fetching -- password isn't present
"
);
let
errcode
=
getErrorCode
(
'
NO_PASSWORD
'
);
...
...
@@ -142,7 +152,8 @@ export default class AccountManager {
iban
:
accountWeboob
.
iban
,
title
:
accountWeboob
.
label
,
initialAmount
:
accountWeboob
.
balance
,
lastChecked
:
new
Date
()
lastChecked
:
new
Date
(),
importDate
:
new
Date
()
};
if
(
currency
.
isKnown
(
accountWeboob
.
currency
))
{
account
.
currency
=
accountWeboob
.
currency
;
...
...
@@ -168,10 +179,31 @@ export default class AccountManager {
}
if
(
shouldAddNewAccounts
)
{
log
.
info
(
'
New account found.
'
);
log
.
info
(
'
New account found, saving it as per request.
'
);
let
newAccountInfo
=
{
account
:
null
,
balanceOffset
:
0
};
// Consider all the operations that could have been inserted
// before the fix in #405.
let
existingOperations
=
await
Operation
.
byAccount
(
account
);
if
(
existingOperations
.
length
)
{
let
offset
=
existingOperations
.
reduce
(
sumOpAmounts
,
0
);
newAccountInfo
.
balanceOffset
+=
offset
;
}
// Save the account in DB and in the new accounts map.
let
newAccount
=
await
Account
.
create
(
account
);
this
.
newAccounts
.
push
(
newAccount
);
newAccountInfo
.
account
=
newAccount
;
this
.
newAccountsMap
.
set
(
newAccount
.
accountNumber
,
newAccountInfo
);
continue
;
}
log
.
info
(
'
Unknown account found, not saving as per request.
'
);
}
}
...
...
@@ -188,6 +220,25 @@ export default class AccountManager {
let
now
=
moment
().
format
(
'
YYYY-MM-DDTHH:mm:ss.000Z
'
);
let
allAccounts
=
await
Account
.
byAccess
(
access
);
let
accountMap
=
new
Map
;
for
(
let
account
of
allAccounts
)
{
if
(
this
.
newAccountsMap
.
has
(
account
.
accountNumber
))
{
let
oldEntry
=
this
.
newAccountsMap
.
get
(
account
.
accountNumber
);
accountMap
.
set
(
account
.
accountNumber
,
oldEntry
);
continue
;
}
accountMap
.
set
(
account
.
accountNumber
,
{
account
,
balanceOffset
:
0
});
}
// Eagerly clear state.
this
.
newAccountsMap
.
clear
();
// Normalize source information
for
(
let
sourceOp
of
sourceOps
)
{
...
...
@@ -210,16 +261,10 @@ export default class AccountManager {
operations
.
push
(
operation
);
}
let
allAccounts
=
await
Account
.
byAccess
(
access
);
let
accountSet
=
new
Set
;
for
(
let
account
of
allAccounts
)
{
accountSet
.
add
(
account
.
accountNumber
);
}
let
newOperations
=
[];
for
(
let
operation
of
operations
)
{
// Ignore operations coming from unknown accounts.
if
(
!
account
Set
.
has
(
operation
.
bankAccount
))
if
(
!
account
Map
.
has
(
operation
.
bankAccount
))
continue
;
// Ignore operations already known in database.
...
...
@@ -227,7 +272,16 @@ export default class AccountManager {
if
(
similarOperations
&&
similarOperations
.
length
)
continue
;
// It is definitely a new operation.
newOperations
.
push
(
operation
);
// Remember amounts of transactions older than the import, to
// resync balance.
let
accountInfo
=
accountMap
.
get
(
operation
.
bankAccount
);
let
opDate
=
new
Date
(
operation
.
date
);
if
(
+
opDate
<=
+
accountInfo
.
account
.
importDate
)
{
accountInfo
.
balanceOffset
+=
+
operation
.
amount
;
}
}
// Create the new operations
...
...
@@ -235,36 +289,29 @@ export default class AccountManager {
if
(
numNewOperations
)
{
log
.
info
(
`
${
newOperations
.
length
}
new operations found!`
);
}
for
(
let
operationToCreate
of
newOperations
)
{
await
Operation
.
create
(
operationToCreate
);
}
// Carry over all the triggers on new operations.
if
(
this
.
newAccounts
&&
this
.
newAccounts
.
length
)
{
log
.
info
(
'
Updating initial amount of newly imported accounts...
'
);
}
let
reducer
=
(
sum
,
op
)
=>
sum
+
op
.
amount
;
for
(
let
account
of
this
.
newAccounts
)
{
// Consider all the operations we've just inserted and the
// operations that could have been inserted before the fix in #405.
let
relatedOperations
=
Operation
.
byAccount
(
account
);
if
(
!
relatedOperations
.
length
)
continue
;
let
offset
=
relatedOperations
.
reduce
(
reducer
,
0
);
account
.
initialAmount
-=
offset
;
await
account
.
save
();
// Update account balances.
for
(
let
{
account
,
balanceOffset
}
of
accountMap
.
values
())
{
if
(
balanceOffset
)
{
log
.
info
(
`Account
${
account
.
title
}
initial balance is going to be resynced, by an
offset of
${
balanceOffset
}
.`
);
account
.
initialAmount
-=
balanceOffset
;
await
account
.
save
();
}
}
// Carry over all the triggers on new operations.
log
.
info
(
"
Updating 'last checked' for linked accounts...
"
);
for
(
let
account
of
allAccounts
)
{
await
account
.
updateAttributes
({
lastChecked
:
new
Date
()
});
}
log
.
info
(
'
Informing user new operations have been imported...
'
);
// Don't show the notification after importing a new account.
if
(
numNewOperations
>
0
&&
this
.
newAccounts
.
length
===
0
)
{
if
(
numNewOperations
>
0
)
{
/* eslint-disable camelcase */
let
count
=
{
smart_count
:
numNewOperations
};
...
...
@@ -285,8 +332,5 @@ export default class AccountManager {
access
.
fetchStatus
=
'
OK
'
;
await
access
.
save
();
log
.
info
(
'
Post process: done.
'
);
// reset state
this
.
newAccounts
=
[];
}
}
server/lib/sources/mock.js
View file @
f6b7f824
...
...
@@ -4,7 +4,6 @@ import moment from 'moment' ;
import
{
makeLogger
,
KError
}
from
'
../../helpers
'
;
import
errors
from
'
../../shared/errors.json
'
;
let
log
=
makeLogger
(
'
sources/mock
'
);
const
TIME_TO_GENERATE_OPERATIONS_MS
=
500
;
...
...
@@ -184,28 +183,28 @@ let selectRandomAccount = uuid => {
let
generate
=
uuid
=>
{
let
operations
=
[];
let
count
=
5
;
let
i
=
count
;
let
i
=
5
;
while
(
i
--
)
{
operations
.
push
(
generateOne
(
selectRandomAccount
(
uuid
)));
}
while
(
rand
(
0
,
100
)
>
70
&&
count
<
8
)
{
while
(
rand
(
0
,
100
)
>
70
&&
i
<
3
)
{
operations
.
push
(
generateOne
(
selectRandomAccount
(
uuid
)));
count
++
;
i
++
;
}
// Generate exact same operations imported at the same time
// These operations shall not be considered as duplicates.
if
(
rand
(
0
,
100
)
>
85
&&
operations
.
length
)
{
log
.
info
(
'
Generate a similar but non-duplicate operation.
'
);
operations
.
push
(
operations
[
0
]);
count
++
;
}
// Generate always the same operation, so that it is considered
// as a duplicate.
if
(
rand
(
0
,
100
)
>
70
)
{
log
.
info
(
'
Generate a possibly duplicate operation.
'
);
let
duplicateOperation
=
{
title
:
'
This is a duplicate operation
'
,
amount
:
'
13.37
'
,
...
...
@@ -219,12 +218,33 @@ let generate = uuid => {
date
=
date
.
add
(
1
,
'
days
'
);
}
duplicateOperation
.
date
=
date
.
format
(
'
YYYY-MM-DDT00:00:00.000[Z]
'
);
log
.
info
(
'
Generated a duplicate operation
'
);
operations
.
push
(
duplicateOperation
);
count
++
;
}
log
.
info
(
`generated
${
count
}
fake operations`
);
// Sometimes generate a very old operation, probably older than the oldest
// one.
if
(
rand
(
0
,
100
)
>
90
)
{
log
.
info
(
'
Generate a very old transaction to trigger balance resync.
'
);
let
op
=
{
title
:
'
Ye Olde Transaction
'
,
raw
:
'
Ye Olde Transaction - for #413 testing
'
,
amount
:
'
42.12
'
,
account
:
hashAccount
(
uuid
).
main
,
date
:
new
Date
(
'
01/01/2000
'
)
};
operations
.
push
(
op
);
}
log
.
info
(
`Generated
${
operations
.
length
}
fake operations:`
);
let
accountMap
=
new
Map
;
for
(
let
op
of
operations
)
{
let
prev
=
accountMap
.
has
(
op
.
account
)
?
accountMap
.
get
(
op
.
account
)
:
[
0
,
0
];
accountMap
.
set
(
op
.
account
,
[
prev
[
0
]
+
1
,
prev
[
1
]
+
+
op
.
amount
]);
}
for
(
let
[
account
,
[
num
,
amount
]]
of
accountMap
)
{
log
.
info
(
`-
${
num
}
new operations (
${
amount
}
) for account
${
account
}
.`
);
}
return
operations
;
};
...
...
server/models/account.js
View file @
f6b7f824
...
...
@@ -17,7 +17,8 @@ let Account = americano.getModel('bankaccount', {
iban
:
String
,
initialAmount
:
Number
,
currency
:
String
,
lastChecked
:
Date
lastChecked
:
Date
,
importDate
:
Date
});
Account
=
promisifyModel
(
Account
);
...
...
server/models/migrations.js
View file @
f6b7f824
...
...
@@ -43,6 +43,10 @@ async function updateCustomFields(access, changeFn) {
}
}
function
reduceOperationsDate
(
oldest
,
operation
)
{
return
Math
.
min
(
oldest
,
+
new
Date
(
operation
.
dateImport
));
}
let
migrations
=
[
async
function
m1
()
{
...
...
@@ -213,6 +217,29 @@ website format.`);
await
b
.
destroy
();
log
.
info
(
'
\t
done!
'
);
}
},
async
function
m6
()
{
log
.
info
(
'
Ensure "importDate" field is present in accounts.
'
);
let
accounts
=
await
Account
.
all
();
for
(
let
a
of
accounts
)
{
if
(
typeof
a
.
importDate
!==
'
undefined
'
)
continue
;
log
.
info
(
`\t
${
a
.
accountNumber
}
has no importDate.`
);
let
ops
=
await
Operation
.
byAccount
(
a
);
let
dateNumber
=
Date
.
now
();
if
(
ops
.
length
)
{
dateNumber
=
ops
.
reduce
(
reduceOperationsDate
,
Date
.
now
());
}
a
.
importDate
=
new
Date
(
dateNumber
);
await
a
.
save
();
log
.
info
(
`\tImport date for
${
a
.
title
}
(
${
a
.
accountNumber
}
):
${
a
.
importDate
}
`
);
}
}
];
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment