mirror of
https://github.com/aclindsa/moneygo.git
synced 2024-12-26 23:42:29 -05:00
Move 'status' from transactions to splits
This allows for a transaction to clear one account before the other (and mirrors how Gnucash, and I suspect most other pieces of software, do it)
This commit is contained in:
parent
11c7f199c4
commit
b37a20536f
12
gnucash.go
12
gnucash.go
@ -92,6 +92,7 @@ type GnucashTransaction struct {
|
|||||||
|
|
||||||
type GnucashSplit struct {
|
type GnucashSplit struct {
|
||||||
SplitId string `xml:"http://www.gnucash.org/XML/split id"`
|
SplitId string `xml:"http://www.gnucash.org/XML/split id"`
|
||||||
|
Status string `xml:"http://www.gnucash.org/XML/split reconciled-state"`
|
||||||
AccountId string `xml:"http://www.gnucash.org/XML/split account"`
|
AccountId string `xml:"http://www.gnucash.org/XML/split account"`
|
||||||
Memo string `xml:"http://www.gnucash.org/XML/split memo"`
|
Memo string `xml:"http://www.gnucash.org/XML/split memo"`
|
||||||
Amount string `xml:"http://www.gnucash.org/XML/split quantity"`
|
Amount string `xml:"http://www.gnucash.org/XML/split quantity"`
|
||||||
@ -211,11 +212,20 @@ func ImportGnucash(r io.Reader) (*GnucashImport, error) {
|
|||||||
t := new(Transaction)
|
t := new(Transaction)
|
||||||
t.Description = gt.Description
|
t.Description = gt.Description
|
||||||
t.Date = gt.DatePosted.Date.Time
|
t.Date = gt.DatePosted.Date.Time
|
||||||
t.Status = Imported
|
|
||||||
for j := range gt.Splits {
|
for j := range gt.Splits {
|
||||||
gs := gt.Splits[j]
|
gs := gt.Splits[j]
|
||||||
s := new(Split)
|
s := new(Split)
|
||||||
s.Memo = gs.Memo
|
s.Memo = gs.Memo
|
||||||
|
|
||||||
|
switch gs.Status {
|
||||||
|
default: // 'n', or not present
|
||||||
|
s.Status = Imported
|
||||||
|
case "c":
|
||||||
|
s.Status = Cleared
|
||||||
|
case "y":
|
||||||
|
s.Status = Reconciled
|
||||||
|
}
|
||||||
|
|
||||||
account, ok := accountMap[gs.AccountId]
|
account, ok := accountMap[gs.AccountId]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("Unable to find account: %s", gs.AccountId)
|
return nil, fmt.Errorf("Unable to find account: %s", gs.AccountId)
|
||||||
|
@ -109,7 +109,6 @@ func AccountImportHandler(w http.ResponseWriter, r *http.Request, user *User, ac
|
|||||||
var transactions []Transaction
|
var transactions []Transaction
|
||||||
for _, transaction := range itl.Transactions {
|
for _, transaction := range itl.Transactions {
|
||||||
transaction.UserId = user.UserId
|
transaction.UserId = user.UserId
|
||||||
transaction.Status = Imported
|
|
||||||
|
|
||||||
if !transaction.Valid() {
|
if !transaction.Valid() {
|
||||||
sqltransaction.Rollback()
|
sqltransaction.Rollback()
|
||||||
@ -122,6 +121,7 @@ func AccountImportHandler(w http.ResponseWriter, r *http.Request, user *User, ac
|
|||||||
// and fixup the SecurityId to be a valid one for this user's actual
|
// and fixup the SecurityId to be a valid one for this user's actual
|
||||||
// securities instead of a placeholder from the import
|
// securities instead of a placeholder from the import
|
||||||
for _, split := range transaction.Splits {
|
for _, split := range transaction.Splits {
|
||||||
|
split.Status = Imported
|
||||||
if split.AccountId != -1 {
|
if split.AccountId != -1 {
|
||||||
if split.AccountId != importedAccount.AccountId {
|
if split.AccountId != importedAccount.AccountId {
|
||||||
sqltransaction.Rollback()
|
sqltransaction.Rollback()
|
||||||
|
@ -35,11 +35,11 @@ var Big = require('big.js');
|
|||||||
var models = require('../models');
|
var models = require('../models');
|
||||||
var Security = models.Security;
|
var Security = models.Security;
|
||||||
var Account = models.Account;
|
var Account = models.Account;
|
||||||
|
var SplitStatus = models.SplitStatus;
|
||||||
|
var SplitStatusList = models.SplitStatusList;
|
||||||
|
var SplitStatusMap = models.SplitStatusMap;
|
||||||
var Split = models.Split;
|
var Split = models.Split;
|
||||||
var Transaction = models.Transaction;
|
var Transaction = models.Transaction;
|
||||||
var TransactionStatus = models.TransactionStatus;
|
|
||||||
var TransactionStatusList = models.TransactionStatusList;
|
|
||||||
var TransactionStatusMap = models.TransactionStatusMap;
|
|
||||||
var Error = models.Error;
|
var Error = models.Error;
|
||||||
|
|
||||||
var getAccountDisplayName = require('../utils').getAccountDisplayName;
|
var getAccountDisplayName = require('../utils').getAccountDisplayName;
|
||||||
@ -70,6 +70,7 @@ const TransactionRow = React.createClass({
|
|||||||
for (var i = 0; i < this.props.transaction.Splits.length; i++) {
|
for (var i = 0; i < this.props.transaction.Splits.length; i++) {
|
||||||
if (this.props.transaction.Splits[i].AccountId == this.props.account.AccountId) {
|
if (this.props.transaction.Splits[i].AccountId == this.props.account.AccountId) {
|
||||||
thisAccountSplit = this.props.transaction.Splits[i];
|
thisAccountSplit = this.props.transaction.Splits[i];
|
||||||
|
status = SplitStatusMap[this.props.transaction.Splits[i].Status];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,7 +90,6 @@ const TransactionRow = React.createClass({
|
|||||||
var amount = security.Symbol + " " + thisAccountSplit.Amount.toFixed(security.Precision);
|
var amount = security.Symbol + " " + thisAccountSplit.Amount.toFixed(security.Precision);
|
||||||
if (this.props.transaction.hasOwnProperty("Balance"))
|
if (this.props.transaction.hasOwnProperty("Balance"))
|
||||||
balance = security.Symbol + " " + this.props.transaction.Balance.toFixed(security.Precision);
|
balance = security.Symbol + " " + this.props.transaction.Balance.toFixed(security.Precision);
|
||||||
status = TransactionStatusMap[this.props.transaction.Status];
|
|
||||||
number = thisAccountSplit.Number;
|
number = thisAccountSplit.Number;
|
||||||
} else {
|
} else {
|
||||||
var amount = security.Symbol + " " + (new Big(0.0)).toFixed(security.Precision);
|
var amount = security.Symbol + " " + (new Big(0.0)).toFixed(security.Precision);
|
||||||
@ -216,19 +216,12 @@ const AddEditTransactionModal = React.createClass({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleStatusChange: function(status) {
|
|
||||||
if (status.hasOwnProperty('StatusId')) {
|
|
||||||
this.setState({
|
|
||||||
transaction: react_update(this.state.transaction, {
|
|
||||||
Status: {$set: status.StatusId}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleAddSplit: function() {
|
handleAddSplit: function() {
|
||||||
|
var split = new Split();
|
||||||
|
split.Status = SplitStatus.Entered;
|
||||||
this.setState({
|
this.setState({
|
||||||
transaction: react_update(this.state.transaction, {
|
transaction: react_update(this.state.transaction, {
|
||||||
Splits: {$push: [new Split()]}
|
Splits: {$push: [split]}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -248,6 +241,15 @@ const AddEditTransactionModal = React.createClass({
|
|||||||
transaction: transaction
|
transaction: transaction
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
handleUpdateStatus: function(status, split) {
|
||||||
|
var transaction = this.state.transaction;
|
||||||
|
transaction.Splits[split] = react_update(transaction.Splits[split], {
|
||||||
|
Status: {$set: status.StatusId}
|
||||||
|
});
|
||||||
|
this.setState({
|
||||||
|
transaction: transaction
|
||||||
|
});
|
||||||
|
},
|
||||||
handleUpdateMemo: function(split) {
|
handleUpdateMemo: function(split) {
|
||||||
var transaction = this.state.transaction;
|
var transaction = this.state.transaction;
|
||||||
transaction.Splits[split] = react_update(transaction.Splits[split], {
|
transaction.Splits[split] = react_update(transaction.Splits[split], {
|
||||||
@ -345,6 +347,10 @@ const AddEditTransactionModal = React.createClass({
|
|||||||
var j = i;
|
var j = i;
|
||||||
return function() {self.handleUpdateNumber(j);};
|
return function() {self.handleUpdateNumber(j);};
|
||||||
})();
|
})();
|
||||||
|
var updateStatusFn = (function() {
|
||||||
|
var j = i;
|
||||||
|
return function(status) {self.handleUpdateStatus(status, j);};
|
||||||
|
})();
|
||||||
var updateMemoFn = (function() {
|
var updateMemoFn = (function() {
|
||||||
var j = i;
|
var j = i;
|
||||||
return function() {self.handleUpdateMemo(j);};
|
return function() {self.handleUpdateMemo(j);};
|
||||||
@ -374,7 +380,17 @@ const AddEditTransactionModal = React.createClass({
|
|||||||
value={s.Number}
|
value={s.Number}
|
||||||
onChange={updateNumberFn}
|
onChange={updateNumberFn}
|
||||||
ref={"number-"+i} /></Col>
|
ref={"number-"+i} /></Col>
|
||||||
<Col xs={5}><FormControl
|
<Col xs={1}>
|
||||||
|
<Combobox
|
||||||
|
suggest
|
||||||
|
data={SplitStatusList}
|
||||||
|
valueField='StatusId'
|
||||||
|
textField='Name'
|
||||||
|
defaultValue={s.Status}
|
||||||
|
onSelect={updateStatusFn}
|
||||||
|
ref={"status-"+i} />
|
||||||
|
</Col>
|
||||||
|
<Col xs={4}><FormControl
|
||||||
type="text"
|
type="text"
|
||||||
value={s.Memo}
|
value={s.Memo}
|
||||||
onChange={updateMemoFn}
|
onChange={updateMemoFn}
|
||||||
@ -424,23 +440,11 @@ const AddEditTransactionModal = React.createClass({
|
|||||||
ref="description"/>
|
ref="description"/>
|
||||||
</Col>
|
</Col>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup>
|
|
||||||
<Col componentClass={ControlLabel} xs={2}>Status</Col>
|
|
||||||
<Col xs={10}>
|
|
||||||
<Combobox
|
|
||||||
suggest
|
|
||||||
data={TransactionStatusList}
|
|
||||||
valueField='StatusId'
|
|
||||||
textField='Name'
|
|
||||||
defaultValue={this.state.transaction.Status}
|
|
||||||
onSelect={this.handleStatusChange}
|
|
||||||
ref="status" />
|
|
||||||
</Col>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<Grid fluid={true}><Row>
|
<Grid fluid={true}><Row>
|
||||||
<span className="split-header col-xs-1">#</span>
|
<span className="split-header col-xs-1">#</span>
|
||||||
<span className="split-header col-xs-5">Memo</span>
|
<span className="split-header col-xs-1">Status</span>
|
||||||
|
<span className="split-header col-xs-4">Memo</span>
|
||||||
<span className="split-header col-xs-3">Account</span>
|
<span className="split-header col-xs-3">Account</span>
|
||||||
<span className="split-header col-xs-2">Amount</span>
|
<span className="split-header col-xs-2">Amount</span>
|
||||||
</Row>
|
</Row>
|
||||||
@ -680,10 +684,11 @@ module.exports = React.createClass({
|
|||||||
},
|
},
|
||||||
handleNewTransactionClicked: function() {
|
handleNewTransactionClicked: function() {
|
||||||
var newTransaction = new Transaction();
|
var newTransaction = new Transaction();
|
||||||
newTransaction.Status = TransactionStatus.Entered;
|
|
||||||
newTransaction.Date = new Date();
|
newTransaction.Date = new Date();
|
||||||
newTransaction.Splits.push(new Split());
|
newTransaction.Splits.push(new Split());
|
||||||
newTransaction.Splits.push(new Split());
|
newTransaction.Splits.push(new Split());
|
||||||
|
newTransaction.Splits[0].Status = SplitStatus.Entered;
|
||||||
|
newTransaction.Splits[1].Status = SplitStatus.Entered;
|
||||||
newTransaction.Splits[0].AccountId = this.props.accounts[this.props.selectedAccount].AccountId;
|
newTransaction.Splits[0].AccountId = this.props.accounts[this.props.selectedAccount].AccountId;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
|
54
js/models.js
54
js/models.js
@ -202,11 +202,32 @@ Account.prototype.isRootAccount = function() {
|
|||||||
return this.ParentAccountId == empty_account.ParentAccountId;
|
return this.ParentAccountId == empty_account.ParentAccountId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SplitStatus = {
|
||||||
|
Imported: 1,
|
||||||
|
Entered: 2,
|
||||||
|
Cleared: 3,
|
||||||
|
Reconciled: 4,
|
||||||
|
Voided: 5
|
||||||
|
}
|
||||||
|
var SplitStatusList = [];
|
||||||
|
for (var type in SplitStatus) {
|
||||||
|
if (SplitStatus.hasOwnProperty(type)) {
|
||||||
|
SplitStatusList.push({'StatusId': SplitStatus[type], 'Name': type});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var SplitStatusMap = {};
|
||||||
|
for (var status in SplitStatus) {
|
||||||
|
if (SplitStatus.hasOwnProperty(status)) {
|
||||||
|
SplitStatusMap[SplitStatus[status]] = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Split() {
|
function Split() {
|
||||||
this.SplitId = -1;
|
this.SplitId = -1;
|
||||||
this.TransactionId = -1;
|
this.TransactionId = -1;
|
||||||
this.AccountId = -1;
|
this.AccountId = -1;
|
||||||
this.SecurityId = -1;
|
this.SecurityId = -1;
|
||||||
|
this.Status = -1;
|
||||||
this.Number = "";
|
this.Number = "";
|
||||||
this.Memo = "";
|
this.Memo = "";
|
||||||
this.Amount = new Big(0.0);
|
this.Amount = new Big(0.0);
|
||||||
@ -219,6 +240,7 @@ Split.prototype.toJSONobj = function() {
|
|||||||
json_obj.TransactionId = this.TransactionId;
|
json_obj.TransactionId = this.TransactionId;
|
||||||
json_obj.AccountId = this.AccountId;
|
json_obj.AccountId = this.AccountId;
|
||||||
json_obj.SecurityId = this.SecurityId;
|
json_obj.SecurityId = this.SecurityId;
|
||||||
|
json_obj.Status = this.Status;
|
||||||
json_obj.Number = this.Number;
|
json_obj.Number = this.Number;
|
||||||
json_obj.Memo = this.Memo;
|
json_obj.Memo = this.Memo;
|
||||||
json_obj.Amount = this.Amount.toFixed();
|
json_obj.Amount = this.Amount.toFixed();
|
||||||
@ -235,6 +257,8 @@ Split.prototype.fromJSONobj = function(json_obj) {
|
|||||||
this.AccountId = json_obj.AccountId;
|
this.AccountId = json_obj.AccountId;
|
||||||
if (json_obj.hasOwnProperty("SecurityId"))
|
if (json_obj.hasOwnProperty("SecurityId"))
|
||||||
this.SecurityId = json_obj.SecurityId;
|
this.SecurityId = json_obj.SecurityId;
|
||||||
|
if (json_obj.hasOwnProperty("Status"))
|
||||||
|
this.Status = json_obj.Status;
|
||||||
if (json_obj.hasOwnProperty("Number"))
|
if (json_obj.hasOwnProperty("Number"))
|
||||||
this.Number = json_obj.Number;
|
this.Number = json_obj.Number;
|
||||||
if (json_obj.hasOwnProperty("Memo"))
|
if (json_obj.hasOwnProperty("Memo"))
|
||||||
@ -253,31 +277,10 @@ Split.prototype.isSplit = function() {
|
|||||||
this.SecurityId != empty_split.SecurityId;
|
this.SecurityId != empty_split.SecurityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TransactionStatus = {
|
|
||||||
Imported: 1,
|
|
||||||
Entered: 2,
|
|
||||||
Cleared: 3,
|
|
||||||
Reconciled: 4,
|
|
||||||
Voided: 5
|
|
||||||
}
|
|
||||||
var TransactionStatusList = [];
|
|
||||||
for (var type in TransactionStatus) {
|
|
||||||
if (TransactionStatus.hasOwnProperty(type)) {
|
|
||||||
TransactionStatusList.push({'StatusId': TransactionStatus[type], 'Name': type});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var TransactionStatusMap = {};
|
|
||||||
for (var status in TransactionStatus) {
|
|
||||||
if (TransactionStatus.hasOwnProperty(status)) {
|
|
||||||
TransactionStatusMap[TransactionStatus[status]] = status;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Transaction() {
|
function Transaction() {
|
||||||
this.TransactionId = -1;
|
this.TransactionId = -1;
|
||||||
this.UserId = -1;
|
this.UserId = -1;
|
||||||
this.Description = "";
|
this.Description = "";
|
||||||
this.Status = -1;
|
|
||||||
this.Date = new Date();
|
this.Date = new Date();
|
||||||
this.Splits = [];
|
this.Splits = [];
|
||||||
}
|
}
|
||||||
@ -287,7 +290,6 @@ Transaction.prototype.toJSON = function() {
|
|||||||
json_obj.TransactionId = this.TransactionId;
|
json_obj.TransactionId = this.TransactionId;
|
||||||
json_obj.UserId = this.UserId;
|
json_obj.UserId = this.UserId;
|
||||||
json_obj.Description = this.Description;
|
json_obj.Description = this.Description;
|
||||||
json_obj.Status = this.Status;
|
|
||||||
json_obj.Date = this.Date.toJSON();
|
json_obj.Date = this.Date.toJSON();
|
||||||
json_obj.Splits = [];
|
json_obj.Splits = [];
|
||||||
for (var i = 0; i < this.Splits.length; i++)
|
for (var i = 0; i < this.Splits.length; i++)
|
||||||
@ -304,8 +306,6 @@ Transaction.prototype.fromJSON = function(json_input) {
|
|||||||
this.UserId = json_obj.UserId;
|
this.UserId = json_obj.UserId;
|
||||||
if (json_obj.hasOwnProperty("Description"))
|
if (json_obj.hasOwnProperty("Description"))
|
||||||
this.Description = json_obj.Description;
|
this.Description = json_obj.Description;
|
||||||
if (json_obj.hasOwnProperty("Status"))
|
|
||||||
this.Status = json_obj.Status;
|
|
||||||
if (json_obj.hasOwnProperty("Date")) {
|
if (json_obj.hasOwnProperty("Date")) {
|
||||||
this.Date = json_obj.Date
|
this.Date = json_obj.Date
|
||||||
if (typeof this.Date === 'string') {
|
if (typeof this.Date === 'string') {
|
||||||
@ -541,9 +541,9 @@ module.exports = models = {
|
|||||||
AccountTypeList: AccountTypeList,
|
AccountTypeList: AccountTypeList,
|
||||||
SecurityType: SecurityType,
|
SecurityType: SecurityType,
|
||||||
SecurityTypeList: SecurityTypeList,
|
SecurityTypeList: SecurityTypeList,
|
||||||
TransactionStatus: TransactionStatus,
|
SplitStatus: SplitStatus,
|
||||||
TransactionStatusList: TransactionStatusList,
|
SplitStatusList: SplitStatusList,
|
||||||
TransactionStatusMap: TransactionStatusMap,
|
SplitStatusMap: SplitStatusMap,
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
BogusPassword: "password"
|
BogusPassword: "password"
|
||||||
|
4
ofx.go
4
ofx.go
@ -43,7 +43,6 @@ func (i *OFXImport) GetAddCurrency(isoname string) (*Security, error) {
|
|||||||
func (i *OFXImport) AddTransaction(tran *ofxgo.Transaction, account *Account) error {
|
func (i *OFXImport) AddTransaction(tran *ofxgo.Transaction, account *Account) error {
|
||||||
var t Transaction
|
var t Transaction
|
||||||
|
|
||||||
t.Status = Imported
|
|
||||||
t.Date = tran.DtPosted.UTC()
|
t.Date = tran.DtPosted.UTC()
|
||||||
t.RemoteId = tran.FiTID.String()
|
t.RemoteId = tran.FiTID.String()
|
||||||
// TODO CorrectFiTID/CorrectAction?
|
// TODO CorrectFiTID/CorrectAction?
|
||||||
@ -85,6 +84,9 @@ func (i *OFXImport) AddTransaction(tran *ofxgo.Transaction, account *Account) er
|
|||||||
s1.Amount = amt.FloatString(security.Precision)
|
s1.Amount = amt.FloatString(security.Precision)
|
||||||
s2.Amount = amt.Neg(amt).FloatString(security.Precision)
|
s2.Amount = amt.Neg(amt).FloatString(security.Precision)
|
||||||
|
|
||||||
|
s1.Status = Imported
|
||||||
|
s2.Status = Imported
|
||||||
|
|
||||||
s1.AccountId = account.AccountId
|
s1.AccountId = account.AccountId
|
||||||
s2.AccountId = -1
|
s2.AccountId = -1
|
||||||
s1.SecurityId = -1
|
s1.SecurityId = -1
|
||||||
|
@ -14,9 +14,18 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Imported int64 = 1
|
||||||
|
Entered = 2
|
||||||
|
Cleared = 3
|
||||||
|
Reconciled = 4
|
||||||
|
Voided = 5
|
||||||
|
)
|
||||||
|
|
||||||
type Split struct {
|
type Split struct {
|
||||||
SplitId int64
|
SplitId int64
|
||||||
TransactionId int64
|
TransactionId int64
|
||||||
|
Status int64
|
||||||
|
|
||||||
// One of AccountId and SecurityId must be -1
|
// One of AccountId and SecurityId must be -1
|
||||||
// In normal splits, AccountId will be valid and SecurityId will be -1. The
|
// In normal splits, AccountId will be valid and SecurityId will be -1. The
|
||||||
@ -51,20 +60,11 @@ func (s *Split) Valid() bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
Imported int64 = 1
|
|
||||||
Entered = 2
|
|
||||||
Cleared = 3
|
|
||||||
Reconciled = 4
|
|
||||||
Voided = 5
|
|
||||||
)
|
|
||||||
|
|
||||||
type Transaction struct {
|
type Transaction struct {
|
||||||
TransactionId int64
|
TransactionId int64
|
||||||
UserId int64
|
UserId int64
|
||||||
RemoteId string // unique ID from server, for detecting duplicates
|
RemoteId string // unique ID from server, for detecting duplicates
|
||||||
Description string
|
Description string
|
||||||
Status int64
|
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Splits []*Split `db:"-"`
|
Splits []*Split `db:"-"`
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user