diff --git a/static/account_register.js b/static/account_register.js
index e6f780a..8284135 100644
--- a/static/account_register.js
+++ b/static/account_register.js
@@ -1,5 +1,6 @@
// Import all the objects we want to use from ReactBootstrap
+var Alert = ReactBootstrap.Alert;
var Modal = ReactBootstrap.Modal;
var Pagination = ReactBootstrap.Pagination;
@@ -127,12 +128,16 @@ const AmountInput = React.createClass({
var symbol = "?";
if (this.props.security)
symbol = this.props.security.Symbol;
+ var bsStyle = "";
+ if (this.props.bsStyle)
+ bsStyle = this.props.bsStyle;
return (
);
}
@@ -142,7 +147,10 @@ const AddEditTransactionModal = React.createClass({
_getInitialState: function(props) {
// Ensure we can edit this without screwing up other copies of it
var t = props.transaction.deepCopy();
- return {transaction: t};
+ return {
+ errorAlert: [],
+ transaction: t
+ };
},
getInitialState: function() {
return this._getInitialState(this.props);
@@ -232,6 +240,24 @@ const AddEditTransactionModal = React.createClass({
});
},
handleSubmit: function() {
+ var errorString = ""
+ var imbalancedSecurityList = this.state.transaction.imbalancedSplitSecurities(this.props.account_map);
+ if (imbalancedSecurityList.length > 0)
+ errorString = "Transaction must balance"
+ for (var i = 0; i < this.state.transaction.Splits.length; i++) {
+ var s = this.state.transaction.Splits[i];
+ if (!(s.AccountId in this.props.account_map)) {
+ errorString = "All accounts must be valid"
+ }
+ }
+
+ if (errorString.length > 0) {
+ this.setState({
+ errorAlert: (Error Saving Transaction: {errorString})
+ });
+ return;
+ }
+
if (this.props.onSubmit != null)
this.props.onSubmit(this.state.transaction);
},
@@ -250,13 +276,25 @@ const AddEditTransactionModal = React.createClass({
);
}
+ var imbalancedSecurityList = this.state.transaction.imbalancedSplitSecurities(this.props.account_map);
+ var imbalancedSecurityMap = {};
+ for (i = 0; i < imbalancedSecurityList.length; i++)
+ imbalancedSecurityMap[imbalancedSecurityList[i]] = i;
+
splits = [];
for (var i = 0; i < this.state.transaction.Splits.length; i++) {
var self = this;
var s = this.state.transaction.Splits[i];
var security = null;
- if (this.props.account_map[s.AccountId])
+ var amountValidation = "";
+ var accountValidation = "";
+ if (s.AccountId in this.props.account_map) {
security = this.props.security_map[this.props.account_map[s.AccountId].SecurityId];
+ if (security.SecurityId in imbalancedSecurityMap)
+ amountValidation = "error";
+ } else {
+ accountValidation = "has-error";
+ }
// Define all closures for calling split-updating functions
var deleteSplitFn = (function() {
@@ -307,12 +345,14 @@ const AddEditTransactionModal = React.createClass({
value={s.AccountId}
includeRoot={false}
onSelect={updateAccountFn}
- ref={"account-"+i} />
+ ref={"account-"+i}
+ className={accountValidation}/>
+ ref={"amount-"+i}
+ bsStyle={amountValidation}/>
{deleteSplitButton}
));
@@ -367,6 +407,7 @@ const AddEditTransactionModal = React.createClass({
bsStyle="success">
+ {this.state.errorAlert}
diff --git a/static/accounts.js b/static/accounts.js
index 8cf5022..2f8fc06 100644
--- a/static/accounts.js
+++ b/static/accounts.js
@@ -31,6 +31,9 @@ const AccountCombobox = React.createClass({
},
render: function() {
var accounts = getAccountDisplayList(this.props.accounts, this.props.includeRoot, this.props.rootName);
+ var className = "";
+ if (this.props.className)
+ className = this.props.className;
return (
+ ref="account"
+ className={className} />
);
}
});
diff --git a/static/models.js b/static/models.js
index 7a47434..38dfa88 100644
--- a/static/models.js
+++ b/static/models.js
@@ -323,6 +323,33 @@ Transaction.prototype.deepCopy = function() {
return t;
}
+Transaction.prototype.imbalancedSplitSecurities = function(account_map) {
+ // Return a list of SecurityIDs for those securities that aren't balanced
+ // in this transaction's splits. If a split's AccountId is invalid, that
+ // split is ignored, so those must be checked elsewhere
+ var splitBalances = {};
+ const emptySplit = new Split();
+ for (var i = 0; i < this.Splits.length; i++) {
+ split = this.Splits[i];
+ if (split.AccountId == emptySplit.AccountId) {
+ continue;
+ }
+ var securityId = account_map[split.AccountId].SecurityId;
+ if (securityId in splitBalances) {
+ splitBalances[securityId] = split.Amount.plus(splitBalances[securityId]);
+ } else {
+ splitBalances[securityId] = split.Amount.plus(0);
+ }
+ }
+ var imbalancedIDs = [];
+ for (var id in splitBalances) {
+ if (!splitBalances[id].eq(0)) {
+ imbalancedIDs.push(id);
+ }
+ }
+ return imbalancedIDs;
+}
+
function Error() {
this.ErrorId = -1;
this.ErrorString = "";
diff --git a/static/stylesheet.css b/static/stylesheet.css
index 6f3e0d8..e6a8e77 100644
--- a/static/stylesheet.css
+++ b/static/stylesheet.css
@@ -115,3 +115,22 @@ div.accounttree-root div {
.skinny-pagination {
margin: 0px;
}
+
+/* Make Combobox support .has-error class */
+.has-error.rw-widget {
+ border-color: #843534;
+}
+.has-error.rw-widget.rw-state-focus {
+ border-color: #843534;
+ box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.075) inset, 0px 0px 6px #CE8483;
+}
+.has-error.rw-widget > .rw-select {
+ border-left: 1px solid #843534;
+ color: #A94442;
+ background-color: #F2DEDE;
+}
+
+/* Fix Alert Spacing inside */
+.alert.saving-transaction-alert {
+ margin: 20px 0 0 0;
+}