How to use accumulateProperty method in Best

Best JavaScript code snippet using best

captable.js

Source:captable.js Github

copy

Full Screen

...455 function visibleInvestors() {456 return captable.cells457 .filter(function(el) {458 return true;})459 .reduce(accumulateProperty('investor'), []);460 }461 function visibleSecurities() {462 return captable.cells463 .filter(function(el) {464 return el.security !== "";})465 .reduce(accumulateProperty('security'), []);466 }467 function grantSecurities() {468 var ok_security_types = ["Option"];469 return captable.securities.filter(function(el) {470 return ok_security_types.indexOf(el.attrs.security_type) != -1;471 });472 }473 this.grantSecurities = grantSecurities;474 function numUnissued(sec, asof, vesting) {475 var unissued = 0;476 var auth_securities = [];477 angular.forEach(captable.securities, function(sec) {478 if (sec && sec.attrs && calculate.primaryMeasure(479 sec.attrs.security_type) == "units" && sec.attrs.totalauth && sec.attrs.totalauth.toString().length > 0)480 {481 auth_securities.push(sec.name);482 }483 });484 var entry_filter = function(el) {485 return el && (el.investor || auth_securities.indexOf(el.security) !== -1);486 };487 var trans;488 if (asof)489 {490 var d = new Date(asof);491 // d is local date, but ledger is utc date, so offset d so the comparisons always work492 d.setMinutes(d.getMinutes() - d.getTimezoneOffset());493 if (vesting)494 {495 trans = captable.transactions.filter(function(tran) {496 return tran.effective_date <= d;497 }).reduce(accumulateProperty('transaction'), []);498 entry_filter = function(el) {499 return el && (el.investor || auth_securities.indexOf(el.security) !== -1) &&500 trans.indexOf(el.transaction) != -1;501 };502 }503 else504 {505 entry_filter = function(el) {506 return el && (el.investor || auth_securities.indexOf(el.security) !== -1) &&507 el.effective_date <= d;508 };509 }510 }511 angular.forEach(captable.ledger_entries.filter(entry_filter), function(entry) {512 if ((!entry.investor) && (entry.security == sec.name))513 {514 unissued += (Number(entry.credit) - Number(entry.debit));515 }516 });517 return unissued;518 }519 this.numUnissued = numUnissued;520 function selectedCellHistory() {521 var watches = Object.keys(History.history);522 var obj = History.history[watches[0]];523 var hist = obj.selectedCell;524 return hist;525 }526 this.selectedCellHistory = selectedCellHistory;527 function securityFor(obj) {528 return captable.securities.filter(function(el) {529 return el.name == obj.attrs.security;530 })[0];531 }532 function cellFor(inv, sec, create) {533 var cells = captable.cells534 .filter(function(cell) {535 return cell.investor == inv &&536 cell.security == sec &&537 (cell.a || cell.u || (cell.transactions.length > 0));538 });539 if (cells.length === 0 && create) {540 return createCell(inv, sec);541 } else if (cells.length == 1) {542 return cells[0];543 } else if (cells.length > 1) {544 // FIXME error, do cleanup?545 // There should never be 2 cells with the same inv and sec.546 return null;547 } else {548 return null;549 }550 }551 this.cellFor = cellFor;552 function grantCellFor(grant, kind, create) {553 var cells = captable.grantCells554 .filter(function(c) {555 return c.roots[0].transaction == grant &&556 c.kind == kind &&557 (c.u || c.transactions.length > 0);558 });559 if (cells.length === 0 && create) {560 return createGrantCell(grant, kind);561 } else if (cells.length == 1) {562 return cells[0];563 } else if (cells.length > 1) {564 // FIXME error, do cleanup?565 // There should never be 2 cells with the same inv and sec.566 return null;567 } else {568 return null;569 }570 }571 this.grantCellFor = grantCellFor;572 function grantRowInfoFor(sec) {573 var trans = captable.ledger_entries.filter(function(ent) {574 return ent.security == sec;575 }).reduce(accumulateProperty('transaction'), []);576 var res = captable.transactions.filter(function(tran) {577 return trans.indexOf(tran.transaction) != -1 && tran.kind == 'grant';578 });579 /*var grants = captable.grantCells.filter(function(c) {580 return c.kind == 'granted' && c.root.attrs.security == sec;581 });*/582 /*var rows = [];583 angular.forEach(res, function(tran) {584 var row = {};585 row.investor = tran.attrs.investor;586 row.grant = tran.transaction;587 rows.push(row);588 });*/589 return res;590 }591 this.grantRowInfoFor = grantRowInfoFor;592 function rowSum(inv, securities, asof, vesting) {593 if (!securities) securities = false;594 var red;595 if (asof)596 {597 red = function(prev, cur, idx, arr) {598 var tmp = getCellUnits(cur, asof, vesting);599 return prev + (calculate.isNumber(tmp) ? tmp : 0);600 };601 }602 else603 {604 red = function(prev, cur, idx, arr) {605 return prev + (calculate.isNumber(cur.u) ? cur.u : 0);606 };607 }608 return rowFor(inv)609 .filter(function(el) {610 return (typeof(securities) == "boolean" ? true :611 securities.indexOf(el.security) != -1);})612 .reduce(red, 0);613 }614 this.rowSum = rowSum;615 this.investorsIn = function(sec) {616 var names = captable.ledger_entries.filter(function(ent) {617 return ent.security == sec.name;618 }).reduce(accumulateProperty('investor'), []);619 var res = captable.investors.filter(function(inv) {620 return names.indexOf(inv.name) != -1;621 });622 return res;623 };624 this.grantsOf = function(sec) {625 var trans = captable.transactions.filter(function(tran) {626 return tran.kind == 'grant' && tran.attrs.security == sec.name;627 });628 return trans;629 };630 function cellsForLedger(entries) {631 var checked = {};632 var cells = [];633 for (var e in entries)634 {635 if (entries[e].security && entries[e].investor)636 {637 if (!checked[entries[e].security])638 checked[entries[e].security] = {};639 if (!checked[entries[e].security][entries[e].investor])640 {641 cells.push(cellFor(entries[e].investor,642 entries[e].security,643 true));644 checked[entries[e].security][entries[e].investor] = true;645 }646 }647 }648 return cells;649 }650 function cellsForTran(tran) {651 var invs = [];652 var secs = [];653 for (var a in tran.attrs)654 {655 if (a.indexOf('investor') != -1)656 {657 invs.push(a);658 }659 if (a.indexOf('security') != -1 && a.indexOf('type') == -1)660 {661 secs.push(a);662 }663 }664 return captable.cells.filter(function(cell) {665 var inv = false;666 var sec = false;667 for (var a in invs)668 {669 if (tran.attrs[invs[a]] == cell.investor)670 {671 inv = true;672 break;673 }674 }675 for (a in secs)676 {677 if (tran.attrs[secs[a]] == cell.security)678 {679 sec = true;680 break;681 }682 }683 return (inv || (invs.length === 0 && tran.kind != 'issue security')) && sec;684 });685 }686 function daysBetween (start, ended) {687 if (!start || !ended)688 {689 return 0;690 }691 var t1 = Math.floor(start.getTime() / 86400000);692 var t2 = Math.floor(ended.getTime() / 86400000);693 return t2 - t1;694 }695 this.daysBetween = daysBetween;696 function startDate() {697 return captable.transactions.reduce(minDate, null);698 }699 this.startDate = startDate;700 function minDate(prev, cur, idx, arr) {701 if (cur.effective_date && (!prev || cur.effective_date < prev))702 return cur.effective_date;703 return prev;704 }705 function maxDate(prev, cur, idx, arr) {706 if (!prev || cur.effective_date > prev)707 return cur.effective_date;708 return prev;709 }710 function lastDate() {711 return captable.ledger_entries.reduce(maxDate, null);712 }713 this.lastDate = lastDate;714 function updateDays() {715 captable.totalDays = daysBetween(startDate(), lastDate());716 }717 function transForCell(inv, sec) {718 // Investor identifying attributes719 var invs = $filter('getInvestorAttributes')();720 // Security identifying attributes721 var secs = $filter('getSecurityAttributes')();722 var trans = captable.transactions.filter(723 function(tran) {724 // Some transactions do not carry underlier data,725 // so we must get it from the security object,726 var security = securityFor(tran);727 var investor_matches = false;728 var security_matches = false;729 var has_investor_attribute = false;730 for (var a in invs)731 {732 if (tran.attrs[invs[a]])733 {734 has_investor_attribute = true;735 }736 if (tran.attrs[invs[a]] == inv)737 {738 investor_matches = true;739 break;740 }741 }742 for (a in secs)743 {744 if (tran.attrs[secs[a]] == sec ||745 (tran.kind == 'exercise' &&746 security.attrs[secs[a]] == sec))747 {748 security_matches = true;749 break;750 }751 }752 return (investor_matches ||753 (!has_investor_attribute &&754 tran.kind != 'issue security')755 ) &&756 security_matches;757 });758 var startingDate = trans.filter(function(tran) {759 return tran.kind != 'split';760 }).reduce(minDate, null);761 var trans2;762 if (startingDate)763 {764 trans2 = trans.filter(function(tran) {765 return tran.kind != 'split' || tran.effective_date > startingDate;766 });767 }768 else769 {770 // TODO: dead code? can you have a transaction without an effective date?771 // probably should be trans2 = []772 trans2 = trans.filter(function(tran) {773 return tran.kind != 'split';774 });775 }776 return trans2;777 }778 function netCreditFor(transaction, investor) {779 var trans = captable.transactions.filter(function(t) {780 return t.transaction == transaction ||781 (t.attrs.transaction_from && t.attrs.transaction_from == transaction);782 }).reduce(accumulateProperty('transaction'), []);783 var ledger_entries = captable.ledger_entries.filter(function(e) {784 return trans.indexOf(e.transaction) != -1 && e.investor == investor;785 });786 return sum_ledger(ledger_entries);787 }788 this.netCreditFor = netCreditFor;789 function secHasUnissued() {790 return function(sec) {791 return numUnissued(sec);792 };793 }794 function securitiesWithUnissuedUnits() {795 return captable.securities796 .filter(secHasUnissued(captable.securities));797 }798 this.securitiesWithUnissuedUnits = securitiesWithUnissuedUnits;799 function securityUnissuedPercentage(sec, securities, asof, vesting) {800 return 100 * (numUnissued(sec, asof, vesting) / totalOwnershipUnits());801 }802 this.securityUnissuedPercentage = securityUnissuedPercentage;803 function rowFor(inv) {804 return captable.cells805 .filter(function(cell) {806 return cell.investor == inv;807 });808 }809 this.rowFor = rowFor;810 function colFor(sec) {811 return captable.cells812 .filter(function(cell) {813 return cell.security == sec;814 });815 }816 function transForInv(inv) {817 return captable.transactions818 .filter(function(tran) {819 for (var k in tran.attrs)820 {821 if (k.indexOf('investor') != -1)822 {823 if (tran.attrs[k] == inv)824 return true;825 }826 }827 return false;828 });829 }830 this.transForInv = transForInv;831 function transForSec(sec) {832 return captable.transactions833 .filter(function(tran) {834 for (var k in tran.attrs)835 {836 if (k.indexOf('security') != -1)837 {838 if (tran.attrs[k] == sec)839 return true;840 }841 }842 return false;843 });844 }845 this.transForSec = transForSec;846 this.updateInvestorName = function(investor) {847 SWBrijj.procm('_ownership.rename_investor', investor.name, investor.new_name).then(function (data) {848 var cells = rowFor(investor.name);849 for (var c in cells)850 {851 cells[c].investor = investor.new_name;852 }853 var trans = transForInv(investor.name);854 for (var t in trans)855 {856 for (var a in trans[t].attrs)857 {858 if (a.indexOf('investor') != -1)859 {860 if (trans[t].attrs[a] == investor.name)861 {862 trans[t].attrs[a] = investor.new_name;863 }864 }865 }866 saveTransaction(trans[t], true);867 }868 investor.name = investor.new_name;869 }).except(function(x) {870 console.log(x);871 });872 };873 this.updateSecurityName = function(security) {874 if (captable.securities.some(function(sec) {875 return (sec.name === security.new_name);876 })) {877 // duplicated security name878 security.new_name = security.new_name + " (1)";879 return this.updateSecurityName(security);880 }881 var cells = colFor(security.name);882 for (var c in cells)883 {884 cells[c].security = security.new_name;885 }886 var trans = transForSec(security.name);887 for (var t in trans)888 {889 for (var a in trans[t].attrs)890 {891 if (a.indexOf('security') != -1)892 {893 if (trans[t].attrs[a] == security.name)894 {895 trans[t].attrs[a] = security.new_name;896 }897 }898 }899 saveTransaction(trans[t], true);900 }901 security.name = security.new_name;902 };903 function cellPrimaryMeasure(cell) {904 return calculate.primaryMeasure( cellSecurityType(cell) );905 }906 function cellSecurityType(cell) {907 if (cell && cell.security) {908 var secs = captable.securities909 .filter(function(el) {910 return el && el.name == cell.security && el.attrs;911 });912 if (secs.length > 0) return secs[0].attrs.security_type;913 }914 }915 function getCellUnits(cell, asof, vesting) {916 if (!cell) return;917 if (cellPrimaryMeasure(cell) == "units") {918 var entries = cell.ledger_entries;919 if (asof) {920 var d = new Date(asof);921 // d is local date, but ledger is utc date, so offset d so the comparisons always work922 d.setMinutes(d.getMinutes() - d.getTimezoneOffset());923 if (vesting)924 {925 var trans = cell.transactions.filter(function(tran) {926 return tran.effective_date <= d;927 }).reduce(accumulateProperty('transaction'), []);928 entries = cell.ledger_entries929 .filter(function(ent) {930 return trans.indexOf(ent.transaction) != -1;});931 }932 else if (cellSecurityType(cell) == 'Option')933 {934 entries = cell.ledger_entries935 .filter(function(ent) {936 return ent.effective_date <= d;});937 }938 }939 return sum_ledger(entries);940 }941 }942 this.getCellUnits = getCellUnits;943 this.cellSecurityType = cellSecurityType;944 function setCellUnits(cell) {945 cell.u = getCellUnits(cell, false);946 }947 function setGrantCellUnits(cell) {948 cell.u = Math.abs(getCellUnits(cell, false));949 }950 this.setCellUnits = setCellUnits;951 function setCellAmount(cell) {952 if (!cell) return;953 if (cellPrimaryMeasure(cell) == "amount") {954 cell.a = sum_ledger(cell.ledger_entries);955 } else if (["Option", "Warrant"].indexOf(956 cellSecurityType(cell)) != -1) {957 return;958 } else {959 var transactionkeys = [];960 angular.forEach(cell.transactions, function(tran) {961 transactionkeys.push(tran.transaction);962 });963 // TODO: should just be able to sum the ledger, instead of trying to figure out "plus" and "minus" trans964 var plus_trans = cell.transactions965 .filter(function(el) {966 return (el.attrs.investor == cell.investor && (transactionkeys.indexOf(el.attrs.transaction_from) == -1) &&967 el.kind != 'repurchase') ||968 el.attrs.investor_to == cell.investor;});969 var minus_trans = cell.transactions970 .filter(function(el) {971 return el.attrs.investor_from == cell.investor ;972 });973 cell.a = sum_transactions(plus_trans) - sum_transactions(minus_trans);974 }975 }976 this.setCellAmount = setCellAmount;977 function getCellAmount(cell, asof, vesting) {978 if (!cell) return;979 var d;980 if (cellPrimaryMeasure(cell) == "amount") {981 var entries = cell.ledger_entries;982 if (asof)983 {984 d = new Date(asof);985 // d is local date, but ledger is utc date, so offset d so the comparisons always work986 d.setMinutes(d.getMinutes() - d.getTimezoneOffset());987 if (vesting && cellSecurityType(cell)=='Option')988 {989 var trans = cell.transactions.filter(function(tran) {990 return tran.effective_date <= d;991 }).reduce(accumulateProperty('transaction'), []);992 entries = cell.ledger_entries993 .filter(function(ent) {994 return trans.indexOf(ent.transaction) != -1;});995 }996 else if (cellSecurityType(cell) == 'Option')997 {998 entries = cell.ledger_entries999 .filter(function(ent) {1000 return ent.effective_date <= d;});1001 }1002 }1003 return sum_ledger(entries);1004 } else if (["Option", "Warrant"].indexOf(1005 cellSecurityType(cell)) != -1) {1006 return;1007 } else {1008 var transactionkeys = [];1009 angular.forEach(cell.transactions, function(tran) {1010 transactionkeys.push(tran.transaction);1011 });1012 var plus_trans = cell.transactions1013 .filter(function(el) {1014 return (el.attrs.investor == cell.investor && (transactionkeys.indexOf(el.attrs.transaction_from) == -1) &&1015 el.kind != 'repurchase') ||1016 el.attrs.investor_to == cell.investor;});1017 var minus_trans = cell.transactions1018 .filter(function(el) {1019 return el.attrs.investor_from == cell.investor ;1020 });1021 if (asof)1022 {1023 d = new Date(asof);1024 // d is local date, but ledger is utc date, so offset d so the comparisons always work1025 d.setMinutes(d.getMinutes() - d.getTimezoneOffset());1026 plus_trans = plus_trans.filter(function(el) {1027 return el.effective_date <= d;1028 });1029 minus_trans = minus_trans.filter(function(el) {1030 return el.effective_date <= d;1031 });1032 }1033 return sum_transactions(plus_trans) - sum_transactions(minus_trans);1034 }1035 }1036 this.getCellAmount = getCellAmount;1037 function sum_ledger(entries) {1038 return entries.reduce(1039 function(prev, cur, index, arr) {1040 return prev + (cur.credit - cur.debit);1041 }, 0);1042 }1043 function sum_transactions(trans) {1044 return trans.reduce(sumTransactionAmount, 0);1045 }1046 this.sum_transactions = sum_transactions;1047 function transactionsAreDifferent(t1, t2) {1048 // only look at year, month day or dates1049 var t1date = new Date(t1.effective_date);1050 var t2date = new Date(t2.effective_date);1051 if (t1date.getUTCDate() != t2date.getUTCDate() ||1052 t1date.getUTCMonth() != t2date.getUTCMonth() ||1053 t1date.getUTCFullYear() != t2date.getUTCFullYear() ||1054 t1.evidence != t2.evidence) {1055 return true;1056 }1057 for (var a in t1.attrs) {1058 if (t1.attrs[a] && t1.attrs[a] != t2.attrs[a]) return true;1059 }1060 return false;1061 }1062 this.transactionsAreDifferent = transactionsAreDifferent;1063 function cleanCell(cell) {1064 var sec_obj = captable.securities1065 .filter(function(el) {1066 return el.name==cell.security && el.attrs.security_type;1067 })[0];1068 var defaultTran = newTransaction(1069 cell.security,1070 defaultKind(sec_obj.attrs.security_type),1071 cell.investor);1072 for (var t in cell.transactions)1073 {1074 if (!cell.transactions[t].transaction)1075 break;1076 var del = true;1077 if (transactionsAreDifferent(cell.transactions[t],1078 defaultTran)) {1079 del = false;1080 }1081 if (del)1082 {1083 deleteTransaction(cell.transactions[t], cell, true);1084 }1085 }1086 }1087 function updateCell(cell) {1088 cell.ledger_entries = cell.transactions = null;1089 cell.a = cell.u = null;1090 cell.transactions = transForCell(cell.investor, cell.security);1091 cell.ledger_entries = captable.ledger_entries.filter(1092 function(ent) {1093 return ent.investor == cell.investor &&1094 ent.security == cell.security;1095 });1096 setCellUnits(cell);1097 setCellAmount(cell);1098 cell.valid = validateCell(cell);1099 cleanCell(cell);1100 }1101 this.updateCell = updateCell;1102 function generateCells() {1103 angular.forEach(captable.investors, function(inv) {1104 angular.forEach(captable.securities, function(sec) {1105 var transactions = transForCell(inv.name, sec.name);1106 if (transactions.length > 0) {1107 var cell = nullCell();1108 cell.transactions = transactions;1109 cell.ledger_entries = captable.ledger_entries.filter(1110 function(ent) {1111 return ent.investor == inv.name &&1112 ent.security == sec.name;1113 });1114 cell.security = sec.name;1115 cell.investor = inv.name;1116 setCellUnits(cell);1117 setCellAmount(cell);1118 cell.valid = validateCell(cell);1119 captable.cells.push(cell);1120 }1121 });1122 // NOTE: this is a fn as a property b/c it makes sorting easy1123 inv.percentage = function() {1124 return investorSorting(inv.name);1125 };1126 });1127 }1128 // TODO: this is only needed on the grants page, so only generate when called1129 function generateGrantCells() {1130 var grants = captable.transactions1131 .filter(function(tran) {1132 return tran.kind == 'grant' &&1133 tran.attrs.security_type=='Option';1134 });1135 angular.forEach(grants, function(g) {1136 var root = g;1137 angular.forEach(grantColumns, function(col) {1138 var cell = nullGrantCell();1139 cell.roots = [root];1140 cell.roots = cell.roots.concat(1141 captable.transactions.filter(function(tran) {1142 return tran.kind == 'split' &&1143 tran.attrs.security == root.attrs.security && (tran.effective_date > root.effective_date);1144 })1145 );1146 cell.kind = col.name;1147 cell.investor = root.attrs.investor;1148 cell.security = root.attrs.security;1149 var root_ids = cell.roots1150 .reduce(accumulateProperty('transaction'), []);1151 cell.transactions = captable.transactions1152 .filter(col.tranFilter(root_ids));1153 var tran_ids = cell.transactions1154 .reduce(accumulateProperty('transaction'), []);1155 cell.ledger_entries = captable.ledger_entries1156 .filter(col.ledgerFilter(tran_ids, cell.investor, cell.security));1157 setGrantCellUnits(cell);1158 if (col.name == 'vested' && (cell.u == 0))1159 {1160 cell.u = null;1161 }1162 captable.grantCells.push(cell);1163 });1164 });1165 }1166 function linkUsers(investors, activities, logins) {1167 angular.forEach(investors, function(investor) {1168 angular.forEach(activities, function(activity) {1169 if (activity.email == investor.email) {1170 var act = activity.activity;1171 var time = activity.event_time;1172 investor[act] = time;1173 }1174 });1175 angular.forEach(logins, function (login) {1176 if (login.email == investor.email) {1177 investor.lastlogin = login.logintime;1178 }1179 });1180 });1181 }1182 function sortSecurities(securities) {1183 return securities.sort(securitySort);1184 }1185 function sortInvestors(investors) {1186 return investors.sort(percentageSort);1187 }1188 function securitySort(a,b) {1189 if (a.effective_date < b.effective_date)1190 return -1;1191 if (a.effective_date > b.effective_date)1192 return 1;1193 if (a.effective_date == b.effective_date) {1194 if (a.insertion_date < b.insertion_date)1195 return -1;1196 if (a.insertion_date > b.insertion_date)1197 return 1;1198 }1199 return 0;1200 }1201 function percentageSort(a,b) {1202 if (a.percentage() > b.percentage())1203 return -1;1204 if (a.percentage() < b.percentage())1205 return 1;1206 return 0;1207 }1208 function investorSorting(inv) {1209 if (inv === "") { return -100; } // keep new inv rows at bottom1210 return investorOwnershipPercentage(inv, false, false, false);1211 }1212 function splice_many(array, elements) {1213 var indices = elements1214 .map(function(el) {return array.indexOf(el);})1215 .filter(function(el) {return el!==-1;});1216 indices.sort(function(a, b){return b-a;});//descending order so splice won't affect later indices1217 return indices.map(function(idx) {return array.splice(idx, 1);});1218 }1219 function splice_many_by(array, filter_fn) {1220 return splice_many(array, array.filter(filter_fn));1221 }1222 /* saveTransaction1223 *1224 * Takes a new (no id) transaction or an instance of an1225 * existing transaction which we assume to have been modified.1226 *1227 * Send transaction to the database (_ownership.save_transaction).1228 *1229 * Database fn will...1230 * - upsert transaction into _ownership.draft_transactions1231 * - remove existing ledger entries, if any exist1232 * - parse transaction into ledger entries, and insert them1233 * - return new ledger entries to front-end1234 *1235 * This fn then updates the captable object with1236 * the new ledger entries.1237 *1238 */1239 function saveTransaction(tran, update, errorFunc) {1240 // or maybe add a save button for now1241 // TODO: return a promise instead of having errorFunc1242 for (var key in tran.attrs) {1243 if (tran.attrs[key] === null) {1244 delete tran.attrs[key];1245 }1246 }1247 if (typeof tran.effective_date == 'object') {1248 tran.effective_date = calculate.castDateString(tran.effective_date, $rootScope.settings.shortdate);1249 }1250 SWBrijj.procm('_ownership.save_transaction',1251 JSON.stringify(tran))1252 .then(function(new_entries) {1253 tran.effective_date = Date.parse(tran.effective_date);1254 if (new_entries.length < 1)1255 {1256 console.log("Error: no ledger entries");1257 return;1258 }1259 // the first item is a fake ledger entry with the correct transaction information1260 var transaction = new_entries.splice(0, 1)[0].transaction;1261 // splice the existing ledger_entries out of their arrays1262 // TODO: I think this logic only makes sense for transactions that aren't splits1263 // TODO: check if on a transaction, we're getting back split rows for other transactions1264 // if we are, are we handling them correctly?1265 var spliced = [];1266 for (var new_entry in new_entries)1267 {1268 if (spliced.indexOf(new_entries[new_entry].transaction) == -1)1269 {1270 spliced.push(new_entries[new_entry].transaction);1271 splice_many_by(captable.ledger_entries, function(el) {1272 return el.transaction == new_entries[new_entry].transaction;1273 });1274 }1275 captable.ledger_entries.push(new_entries[new_entry]);1276 }1277 // check if the transaction is one we already have1278 var found = false;1279 for (var i in captable.transactions)1280 {1281 if (captable.transactions[i].transaction == tran.transaction)1282 {1283 if (tran.transaction === null)1284 {1285 if ((captable.transactions[i].attrs.investor == tran.attrs.investor) &&1286 (captable.transactions[i].attrs.security == tran.attrs.security))1287 {//just in case multiple null transactions1288 captable.transactions[i].transaction = transaction;1289 captable.transactions[i].valid = validateTransaction(tran);1290 found = true;1291 }1292 }1293 else1294 {1295 captable.transactions[i].transaction = transaction;1296 captable.transactions[i].valid = validateTransaction(tran);1297 found = true;1298 }1299 }1300 }1301 tran.transaction = transaction;1302 if (!found)1303 {1304 tran.valid = validateTransaction(tran);1305 captable.transactions.push(tran);1306 }1307 if (update)1308 {1309 var cells = cellsForLedger(new_entries);1310 for (var c in cells)1311 {1312 updateCell(cells[c]);1313 }1314 }1315 updateDays();1316 //captable.ledger_entries.push.apply(captable., new_entries);1317 }).except(function(e) {1318 console.error(e);1319 if (errorFunc)1320 {1321 errorFunc();1322 }1323 });1324 }1325 this.saveTransaction = saveTransaction;1326 function deleteTransaction(tran, cell, hide) {1327 if (tran.transaction) {1328 SWBrijj.procm('_ownership.delete_transaction', tran.transaction)1329 .then(function(x) {1330 var res = x[0].delete_transaction;1331 if (res > 0) {1332 if (!hide)1333 {1334 $rootScope.$emit("notification:success",1335 "Transaction deleted");1336 }1337 var trans = captable.transactions.filter(function(t) {1338 return t.transaction == tran.transaction ||1339 (t.attrs.transaction_from && t.attrs.transaction_from == tran.transaction);1340 });1341 // generate a list of transaction ids1342 var ids = trans.reduce(1343 accumulateProperty('transaction'), []);1344 // generate a list of ledger entries1345 var entries = captable.ledger_entries.filter(function(ent) {1346 return ids.indexOf(ent.transaction) != -1 ||1347 ids.indexOf(ent.modifying_transactions) != -1;1348 });1349 splice_many(captable.transactions, trans);1350 splice_many(captable.ledger_entries, entries);1351 if (cell.transactions.length == 1)1352 {1353 splice_many(captable.cells, [cell]);1354 cell = null;1355 }1356 var cells = cellsForLedger(entries);1357 for (var c in cells)1358 {1359 updateCell(cells[c]);1360 }1361 } else {1362 if (!hide)1363 {1364 $rootScope.$emit("notification:fail",1365 "Oops, something went wrong.");1366 }1367 }1368 }).except(function(err) {1369 console.error(err);1370 if (!hide)1371 {1372 $rootScope.$emit("notification:fail",1373 "Oops, something went wrong.");1374 }1375 });1376 } else {1377 splice_many(captable.transactions, [tran]);1378 if (cell.transactions.length == 1)1379 {1380 splice_many(captable.cells, [cell]);1381 cell = null;1382 }1383 else1384 {1385 var cells = cellsForTran(tran);1386 for (var c in cells)1387 {1388 updateCell(cells[c]);1389 }1390 }1391 }1392 }1393 this.deleteTransaction = deleteTransaction;1394 this.deleteSecurityTransaction = function(tran, sec) {1395 SWBrijj.procm('_ownership.delete_transaction', tran.transaction)1396 .then(function(x) {1397 var res = x[0].delete_transaction;1398 if (res > 0) {1399 $rootScope.$emit("notification:success",1400 "Transaction deleted");1401 splice_many(captable.transactions, [tran]);1402 splice_many_by(captable.ledger_entries, function(el) {1403 return el.transaction == tran.transaction;1404 });1405 splice_many(sec.transactions, [tran]);1406 var cells = colFor(sec.name);1407 for (var c in cells)1408 {1409 updateCell(cells[c]);1410 }1411 } else {1412 $rootScope.$emit("notification:fail",1413 "Oops, something went wrong.");1414 }1415 }).except(function(err) {1416 console.log(err);1417 $rootScope.$emit("notification:fail",1418 "Oops, something went wrong.");1419 });1420 };1421 this.deleteSecurity = function(sec) {1422 SWBrijj.procm('_ownership.delete_security', sec.name)1423 .then(function(x) {1424 var res = x[0].delete_security;1425 if (res > 0) {1426 $rootScope.$emit("notification:success",1427 "Security deleted");1428 $rootScope.$broadcast("deleteSecurity");1429 var idx = captable.securities.indexOf(sec);1430 if (idx !== -1) { captable.securities.splice(idx, 1); }1431 splice_many_by(captable.cells,1432 function(el) {return el.security==sec.name;});1433 splice_many(captable.transactions, sec.transactions);1434 } else {1435 $rootScope.$emit("notification:fail",1436 "Oops, something went wrong.");1437 }1438 }).except(function(err) {1439 console.log(err);1440 $rootScope.$emit("notification:fail",1441 "Oops, something went wrong.");1442 });1443 };1444 this.removeInvestor = function(inv) {1445 SWBrijj.procm('_ownership.remove_investor', inv.name)1446 .then(function(x) {1447 var res = x[0].remove_investor;1448 if (res > 0) {1449 $rootScope.$emit("notification:success",1450 "Investor removed from captable.");1451 var idx = captable.investors.indexOf(inv);1452 if (idx !== -1) { captable.investors.splice(idx, 1); }1453 splice_many_by(captable.cells,1454 function(el) {return el.investor==inv.name;});1455 splice_many(captable.transactions, inv.transactions);1456 } else {1457 $rootScope.$emit("notification:fail",1458 "Sorry, We were unable to remove this investor.");1459 }1460 }).except(function(err) {1461 console.log(err);1462 $rootScope.$emit("notification:fail",1463 "Oops, something went wrong.");1464 });1465 };1466 function rowFromName(name) {1467 var row = new Investor();1468 row.new_name = row.name = name.name;1469 row.email = name.email;1470 row.access_level = name.level;1471 row.transactions = captable.transactions1472 .filter(function(el) {1473 return (row.name && el.attrs.investor == row.name) ||1474 (row.email && el.attrs.investor == row.email);1475 });1476 return row;1477 }1478 function initUI() {1479 $rootScope.$broadcast('captable:initui');1480 }1481 function attachEvidence(data) {1482 angular.forEach(captable.transactions, function(tran) {1483 tran.evidence_data = data.filter(function(el) {1484 return el.evidence==tran.evidence;1485 });1486 });1487 }1488 function reformatDate(obj) {1489 obj.date = calculate.timezoneOffset(obj.date);1490 }1491 function setVestingDates(obj) {1492 if (obj.vestingbegins) {1493 obj.vestingbegins =1494 calculate.timezoneOffset(obj.vestingbegins);1495 obj.vestingbeginsdisplay =1496 calculate.monthDiff(obj.vestingbegins, obj.date);1497 }1498 }1499 function logError(err) {1500 console.error(err);1501 }1502 function nullCell() {1503 return new Cell();1504 }1505 this.nullCell = nullCell;1506 function nullGrantCell() {1507 return new GrantCell();1508 }1509 function newCell(issue) {1510 var cell = new Cell();1511 cell.issue_type = issue.type;1512 return cell;1513 }1514 this.newCell = newCell;1515 function nullSecurity() {1516 return new Security();1517 }1518 this.nullSecurity = nullSecurity;1519 /* initAttrs1520 *1521 * Grab valid attribute keys from the attributes service1522 * for the given security type and transaction kind.1523 *1524 * Add said keys to obj.attrs1525 */1526 function initAttrs(obj, sec_type, kind) {1527 var attr_obj = attrs[sec_type][kind];1528 if (attr_obj) {1529 angular.forEach(Object.keys(attr_obj),1530 function(el) { obj.attrs[el] = null; });1531 if ((attr_obj.hasOwnProperty('physical')) && (obj.attrs.physical == null))1532 {1533 obj.attrs.physical = false;1534 }1535 }1536 }1537 function newTransaction(sec, kind, inv) {1538 var tran = new Transaction();1539 tran.kind = kind;1540 tran.company = $rootScope.navState.company;1541 tran.insertion_date = new Date();1542 // effective date needs to be midnight UTC of intended day1543 tran.effective_date = new Date();1544 // set to midnight local time (close enough)1545 tran.effective_date.setHours(0);1546 tran.effective_date.setMinutes(0);1547 tran.effective_date.setSeconds(0);1548 // convert to UTC / timezoneless1549 tran.effective_date.setMinutes(tran.effective_date.getMinutes() - tran.effective_date.getTimezoneOffset());1550 var sec_obj = captable.securities1551 .filter(function(el) {1552 return el.name==sec && el.attrs.security_type;1553 })[0];1554 initAttrs(tran, sec_obj.attrs.security_type, kind);1555 tran.attrs.security = sec;1556 tran.attrs.security_type = sec_obj.attrs.security_type;1557 angular.forEach(tran.attrs, function(value, key) {1558 if (sec_obj.attrs[key]) tran.attrs[key] = sec_obj.attrs[key];1559 });1560 if (tran.attrs.hasOwnProperty('investor'))1561 {1562 tran.attrs.investor = inv;1563 }1564 if (tran.attrs.hasOwnProperty('investor_from'))1565 {1566 tran.attrs.investor_from = inv;1567 }1568 return tran;1569 }1570 this.newTransaction = newTransaction;1571 this.newSecurity = function() {1572 var security = nullSecurity();1573 security.newName = security.name = "";1574 security.insertion_date = new Date(Date.now());1575 // effective date needs to be midnight UTC of intended day1576 security.effective_date = new Date(Date.now());1577 // set to midnight local time (close enough)1578 security.effective_date.setHours(0);1579 security.effective_date.setMinutes(0);1580 security.effective_date.setSeconds(0);1581 // convert to UTC / timezoneless1582 security.effective_date.setMinutes(security.effective_date.getMinutes() - security.effective_date.getTimezoneOffset());1583 initAttrs(security, 'Option', 'issue security');1584 security.attrs.security = security.name;1585 security.attrs.security_type = 'Option';1586 security.creating = true;1587 var tran = new Transaction();1588 tran.kind = 'issue security';1589 tran.company = $rootScope.navState.company;1590 tran.attrs = security.attrs;1591 // Silly future date so that the issue always appears1592 // on the leftmost side of the table1593 tran.insertion_date = new Date(2100, 1, 1);1594 tran.effective_date = security.effective_date;1595 security.transactions.push(tran);1596 return security;1597 };1598 this.addSecurity = function(security) {1599 // NOTE assume Option for now, user can change,1600 //var tran = newTransaction("Option", "issue security");1601 //tran.kind = "issue_security";1602 var tran = security.transactions[0]; //the transaction that was edited1603 if (captable.securities.some(function(sec) {1604 return (sec.name === tran.attrs.security);1605 })) {1606 // duplicated security name1607 security.transactions[0].attrs.security = tran.attrs.security + " (1)";1608 return this.addSecurity(security);1609 }1610 security.new_name = security.name = tran.attrs.security;1611 security.effective_date = tran.effective_date;1612 security.insertion_date = tran.insertion_date;1613 security.attrs = tran.attrs;1614 // FIXME should we be using AddTran1615 // which takes care of the ledger entries?1616 captable.transactions.push(tran);1617 security.creating = false;1618 captable.securities.push(security);1619 saveTransaction(tran);1620 };1621 this.addInvestor = function(name) {1622 var inv = new Investor();1623 inv.editable = true;1624 var currentrows =[];1625 angular.forEach(captable.investors, function(investor) {1626 currentrows.push(investor.name);1627 });1628 if (currentrows.indexOf(name) != -1) {1629 name += " (1)";1630 }1631 inv.new_name = inv.name = name;1632 inv.company = $rootScope.navState.company;1633 inv.percentage = function() {return investorSorting(inv.name);};1634 SWBrijj.procm('_ownership.add_investor', inv.name)1635 .then(function(x) {1636 captable.investors.push(inv);1637 }).except(function(err) {1638 console.log(err);1639 });1640 };1641 this.addTransaction = function(inv, sec, kind) {1642 var tran = newTransaction(sec, kind, inv);1643 captable.transactions.push(tran);1644 updateCell(this.cellFor(inv, sec, true));1645 return tran;1646 };1647 function defaultKind(sec) {1648 var options = Object.keys(attrs[sec]);1649 if (options.indexOf('grant') != -1)1650 return 'grant';1651 if (options.indexOf('purchase') != -1)1652 return 'purchase';1653 if (options.length == 1)1654 return options[0];1655 if (options.length === 0)1656 return null;1657 if (options.indexOf('issue security') === 0)1658 return options[1];1659 return options[0];1660 }1661 this.defaultKind = defaultKind;1662 function createCell(inv, sec) {1663 var c = new Cell();1664 c.investor = inv;1665 c.security = sec;1666 var sec_obj = captable.securities1667 .filter(function(el) { return el.name==sec; })[0];1668 if (!sec_obj.attrs || !sec_obj.attrs.security_type) {1669 return null;1670 } else {1671 var tran = newTransaction(sec, defaultKind(sec_obj.attrs.security_type), inv);1672 tran.active = true;1673 c.transactions.push(tran);1674 captable.cells.push(c);1675 return c;1676 }1677 }1678 this.createCell = createCell;1679 function createGrantCell(grant, kind) {1680 var col = grantColumns.filter(1681 function(g) {return g.name == kind;})[0];1682 if (!col) return null;1683 var root = captable.transactions1684 .filter(function(tran) {1685 return tran.transaction == grant;1686 })[0];1687 var c = nullGrantCell();1688 c.roots = [root];1689 c.roots = c.roots.concat(1690 captable.transactions.filter(function(tran) {1691 return tran.kind == 'split' &&1692 tran.attrs.security == root.attrs.security && (tran.effective_date > root.effective_date);1693 })1694 );1695 c.kind = kind;1696 c.investor = root.attrs.investor;1697 c.security = root.attrs.security;1698 var root_ids = c.roots1699 .reduce(accumulateProperty('transaction'), []);1700 c.transactions = captable.transactions1701 .filter(col.tranFilter(root_ids));1702 var tran_ids = c.transactions1703 .reduce(accumulateProperty('transaction'), []);1704 c.ledger_entries = captable.ledger_entries1705 .filter(col.ledgerFilter(tran_ids, c.investor, c.security));1706 setGrantCellUnits(c);1707 var sec_obj = captable.securities1708 .filter(function(el) {1709 return el.name == root.attrs.security;1710 })[0];1711 if (!sec_obj.attrs || !sec_obj.attrs.security_Type) {1712 return null;1713 } else {1714 if (col.tran_type) {1715 var tran = newTransaction(c.security,1716 col.tran_type,1717 c.investor);1718 tran.active = true;1719 c.transactions.push(tran);1720 }1721 captable.grantCells.push(c);1722 return c;1723 }1724 }1725 this.createGrantCell = createGrantCell;1726 function attachPariPassu(securities, links) {1727 angular.forEach(securities, function(iss) {1728 iss.paripassu = [];1729 angular.forEach(links, function(link) {1730 if (link.issue == iss.issue) {1731 iss.paripassu.push(link);1732 }1733 });1734 if (iss.paripassu.length === 0) {1735 iss.paripassu.push({"company": iss.company,1736 "issue": iss.issue,1737 "pariwith": null});1738 }1739 });1740 }1741 function secHasTran(name)1742 {1743 for (var t in captable.transactions)1744 {1745 if (captable.transactions[t].attrs.security == name &&1746 captable.transactions[t].kind != "issue security")1747 return true;1748 }1749 return false;1750 }1751 this.secLocked = function(sec) {1752 return secHasTran(sec.name);1753 };1754 /*1755 * Sum all ledger entries associated with equity.1756 *1757 * Sum all ledger entries associated with derivatives.1758 *1759 * Sum all ledger entries associated with warrants1760 * and convertible debt.1761 */1762 function totalOwnershipUnits(dilution, securities, asof, vesting, issuedOnly) {1763 if (!dilution) dilution = 1;1764 if (!securities) securities = false;1765 var entry_filter;1766 var ok_securities = [];1767 var auth_securities = [];1768 var trans = [];1769 if (dilution <= 0) {1770 var ok_types = ["Equity Common",1771 "Equity",1772 "Options"];1773 angular.forEach(captable.securities, function(sec) {1774 if (sec && sec.attrs &&1775 ok_types.indexOf(sec.attrs.security_type) !== -1 &&1776 (typeof(securities) == "boolean" ? true :1777 securities.indexOf(sec.name) != -1))1778 {1779 ok_securities.push(sec.name);1780 }1781 });1782 if (asof)1783 {1784 var d = new Date(asof);1785 // d is local date, but ledger is utc date, so offset d so the comparisons always work1786 d.setMinutes(d.getMinutes() - d.getTimezoneOffset());1787 if (vesting)1788 {1789 trans = captable.transactions.filter(function(tran) {1790 return tran.effective_date <= d;1791 }).reduce(accumulateProperty('transaction'), []);1792 entry_filter = function(el) {1793 return ok_securities.indexOf(el.security) !== -1 &&1794 trans.indexOf(el.transaction) != -1;1795 };1796 }1797 else1798 {1799 entry_filter = function(el) {1800 return ok_securities.indexOf(el.security) !== -1 &&1801 el.effective_date <= d;1802 };1803 }1804 }1805 else1806 {1807 entry_filter = function(el) {1808 return ok_securities.indexOf(el.security) !== -1;1809 };1810 }1811 } else if (dilution == 1) {1812 ok_securities = [];1813 auth_securities = [];1814 angular.forEach(captable.securities, function(sec) {1815 if (sec && sec.attrs &&1816 calculate.primaryMeasure(1817 sec.attrs.security_type) == "units" &&1818 (typeof(securities) == "boolean" ? true :1819 securities.indexOf(sec.name) != -1))1820 {1821 ok_securities.push(sec.name);1822 }1823 });1824 angular.forEach(captable.securities, function(sec) {1825 if (sec && sec.attrs &&1826 calculate.primaryMeasure(sec.attrs.security_type)1827 == "units" &&1828 sec.attrs.totalauth &&1829 sec.attrs.totalauth.toString().length > 0)1830 {1831 auth_securities.push(sec.name);1832 }1833 });1834 if (asof)1835 {1836 var d = new Date(asof);1837 // d is local date, but ledger is utc date, so offset d so the comparisons always work1838 d.setMinutes(d.getMinutes() - d.getTimezoneOffset());1839 if (vesting)1840 {1841 trans = captable.transactions.filter(function(tran) {1842 return tran.effective_date <= d;1843 }).reduce(accumulateProperty('transaction'), []);1844 entry_filter = function(el) {1845 return el && ok_securities.indexOf(el.security) !== -1 &&1846 (el.investor || auth_securities.indexOf(el.security) !== -1) &&1847 trans.indexOf(el.transaction) != -1;1848 };1849 }1850 else1851 {1852 entry_filter = function(el) {1853 return el && ok_securities.indexOf(el.security) !== -1 &&1854 (el.investor || auth_securities.indexOf(el.security) !== -1) &&1855 el.effective_date <= d;1856 };1857 }1858 }1859 else1860 {1861 entry_filter = function(el) {1862 return el && ok_securities.indexOf(el.security) !== -1 && (el.investor || auth_securities.indexOf(el.security) !== -1);1863 };1864 }1865 } else if (dilution >= 2) {1866 console.log("TODO",1867 "implement dilution scenarios involving conversion");1868 return totalOwnershipUnits(1);1869 }1870 var entries = captable.ledger_entries1871 if (issuedOnly)1872 {1873 entries = captable.ledger_entries.filter(function (entry) {1874 return (entry.investor);1875 });1876 }1877 var res = sum_ledger(entries.filter(entry_filter));1878 return res;1879 }1880 this.totalOwnershipUnits = totalOwnershipUnits;1881 function investorOwnershipPercentage(inv, securities, asof, vesting, issuedOnly) {1882 if (!securities) securities = false;1883 var red;1884 if (asof)1885 {1886 red = function(prev, cur, idx, arr) {1887 var tmp = getCellUnits(cur, asof, vesting);1888 return prev + (calculate.isNumber(tmp) ? tmp : 0);1889 };1890 }1891 else1892 {1893 red = sumCellUnits;1894 }1895 var x = captable.cells1896 .filter(function(el) { return el.investor == inv &&1897 (typeof(securities) == "boolean" ? true :1898 securities.indexOf(el.security) != -1); })1899 .reduce(red, 0);1900 var res = x / totalOwnershipUnits(1, securities, asof, vesting, issuedOnly) * 100;1901 return res != Infinity ? res : 0;1902 }1903 this.investorOwnershipPercentage = investorOwnershipPercentage;1904 function securityTotalUnits(sec, asof, vesting) {1905 if (!sec) return 0;1906 var red;1907 if (asof)1908 {1909 red = function(prev, cur, idx, arr) {1910 var tmp = getCellUnits(cur, asof, vesting);1911 return prev + (calculate.isNumber(tmp) ? tmp : 0);1912 };1913 }1914 else1915 {1916 red = sumCellUnits;1917 }1918 return captable.cells1919 .filter(function(el) { return el.security == sec.name; })1920 .reduce(red, 0);1921 }1922 this.securityTotalUnits = securityTotalUnits;1923 function securityUnitsFrom(sec, kind, inv) {1924 if (!(sec && kind)) return 0;1925 var trans = captable.transactions.filter(function(tran) {1926 return tran.attrs.security == sec.name &&1927 tran.kind == kind;1928 }).reduce(accumulateProperty('transaction'), []);1929 var entries = captable.ledger_entries.filter(function(ent) {1930 return trans.indexOf(ent.transaction) != -1 &&1931 (!inv || ent.investor == inv.name);1932 });1933 return sum_ledger(entries) || 0;1934 }1935 this.securityUnitsFrom = securityUnitsFrom;1936 function grantSubtotal(kind, sec) {1937 var cells = captable.grantCells.filter(function(c) {1938 return c.kind == kind && (!sec || c.security == sec);1939 });1940 return cells.reduce(sumCellUnits, 0);1941 }1942 this.grantSubtotal = grantSubtotal;1943 function unitsFrom(kind) {1944 if (!kind) return 0;1945 var trans = captable.transactions.filter(function(tran) {1946 return tran.kind == kind;1947 }).reduce(accumulateProperty('transaction'), []);1948 var entries = captable.ledger_entries.filter(function(ent) {1949 return trans.indexOf(ent.transaction) != -1;1950 });1951 return sum_ledger(entries) || 0;1952 }1953 this.unitsFrom = unitsFrom;1954 function accumulateProperty(prop) {1955 return function(prev, cur, idx, arr) {1956 if (prev.indexOf(cur[prop]) == -1) {1957 prev.push(cur[prop]);1958 }1959 return prev;1960 };1961 }1962 this.accumulateProperty = accumulateProperty;1963 function securityCurrentUnits(sec, inv) {1964 if (!sec) return 0;1965 var trans = captable.transactions.filter(function(tran) {1966 return tran.attrs.security == sec.name;1967 }).reduce(accumulateProperty('transaction'), []);1968 var d = new Date();1969 // d is local date, but ledger is utc date, so offset d so the comparisons always work1970 d.setMinutes(d.getMinutes() - d.getTimezoneOffset());1971 var entries = captable.ledger_entries.filter(function(ent) {1972 return trans.indexOf(ent.transaction) != -1 &&1973 ent.investor &&1974 (!inv || ent.investor == inv.name) &&1975 ent.effective_date <= d;1976 });1977 return sum_ledger(entries);1978 }1979 this.securityCurrentUnits = securityCurrentUnits;1980 function currentUnits() {1981 var trans = captable.transactions.reduce(1982 accumulateProperty('transaction'), []);1983 var d = new Date();1984 // d is local date, but ledger is utc date, so offset d so the comparisons always work1985 d.setMinutes(d.getMinutes() - d.getTimezoneOffset());1986 var entries = captable.ledger_entries.filter(function(ent) {1987 return trans.indexOf(ent.transaction) != -1 &&1988 ent.effective_date <= d;1989 });1990 return sum_ledger(entries);1991 }1992 this.currentUnits = currentUnits;1993 function securityTotalAmount(sec, asof, vesting) {1994 if (!sec) return 0;1995 var red;1996 if (asof)1997 {1998 red = function(prev, cur, idx, arr) {1999 var tmp = getCellAmount(cur, asof, vesting);2000 return prev + (calculate.isNumber(tmp) ? tmp : 0);2001 };2002 }2003 else2004 {2005 red = sumCellAmount;2006 }2007 return captable.cells2008 .filter(function(el) { return el.security == sec.name; })2009 .reduce(red, 0);2010 }2011 this.securityTotalAmount = securityTotalAmount;2012 function sumCellUnits(prev, cur, idx, arr) {2013 return prev + (calculate.isNumber(cur.u) ? cur.u : 0);2014 }2015 function sumCellAmount(prev, cur, idx, arr) {2016 return prev + (calculate.isNumber(cur.a) ? cur.a : 0);2017 }2018 function sumTransactionAmount(prev, cur, idx, arr) {2019 return prev + (calculate.isNumber(cur.attrs.amount) ?2020 Number(cur.attrs.amount) : 0);2021 }2022 function pingIntercomIfCaptableStarted() {2023 var earliestedit = new Date.today().addDays(1);2024 var duplicate = earliestedit;2025 angular.forEach(captable.securities, function(issue) {2026 if (issue.created &&2027 Date.compare(earliestedit, issue.created) > -1) {2028 earliestedit = issue.created;2029 }2030 });2031 if (earliestedit != duplicate) {2032 Intercom('update',2033 {company: {'captablestart_at':2034 parseInt(Date.parse(earliestedit)2035 .getTime()/1000, 10) } });2036 }2037 }2038 function populateListOfInvestorsWithoutAccessToTheCaptable() {2039 var emailedalready = [];2040 angular.forEach(captable.investors, function (row) {2041 if (row.emailkey !== null) {2042 emailedalready.push(row.emailkey);2043 }2044 });2045 // FIXME move to loadCaptable2046 SWBrijj.tblm('global.investor_list', ['email', 'name'])2047 .then(function(investors) {2048 angular.forEach(investors, function(investor, idx) {2049 if (emailedalready.indexOf(investor.email) == -1) {2050 var label = (investor.name ? investor.name : "") +2051 "(" + investor.email + ")";2052 captable.vInvestors.push(label);2053 }2054 });2055 });2056 }2057 var eligible_evidence = [];2058 this.getEligibleEvidence = function() {2059 return eligible_evidence;2060 };2061 function loadEligibleEvidence() {2062 SWBrijj.tblm('ownership.my_company_eligible_evidence')2063 .then(function(data) {2064 angular.forEach(data, function(x) {2065 if (x.tags) { x.tags = JSON.parse(x.tags); }2066 eligible_evidence.push(x);2067 });2068 angular.forEach(eligible_evidence, function(evidence1) {2069 angular.forEach(eligible_evidence, function(evidence2) {2070 if (evidence1.doc_id && !evidence2.doc_id && evidence1.original == evidence2.original) {2071 evidence1.tags = evidence2.tags;2072 }2073 });2074 });2075 }).except(logError);2076 }2077 if (role() == 'issuer') { loadEligibleEvidence(); }2078 function setTransactionEmail(tran) {2079 angular.forEach(captable.investors, function (row) {2080 if ((row.name == tran.investor) && row.email) {2081 tran.email = row.email;2082 }2083 });2084 if (!tran.email) { tran.email = null; }2085 }2086 this.setTransactionEmail = setTransactionEmail;2087 function autocalcThirdTranValue(tran) {2088 if (tran.units && tran.amount &&2089 tran.ppshare !== 0 && !tran.ppshare) {2090 tran.ppshare =2091 parseFloat(tran.amount) / parseFloat(tran.units);2092 }2093 else if (!tran.units && tran.units !== 0 &&2094 tran.amount && tran.ppshare) {2095 tran.units =2096 parseFloat(tran.amount) / parseFloat(tran.ppshare);2097 }2098 else if (tran.units && !tran.amount &&2099 tran.amount !== 0 && tran.ppshare) {2100 tran.amount =2101 parseFloat(tran.units) * parseFloat(tran.ppshare);2102 }2103 }2104 this.autocalcThirdTranValue = autocalcThirdTranValue;2105 this.displayAttr = function(key) {2106 return captable.attributes.filter(2107 function(el) { return el.name==key; })[0].display_name;2108 };2109 // TODO: move to the security object2110 this.isDebt = function(security) {2111 if (!security) return;2112 return security.attrs.security_type == "Debt" || security.attrs.security_type == "Safe" || security.attrs.security_type == "Convertible Debt";2113 };2114 this.isEquity = function(security) {2115 if (!security) return;2116 return security.attrs.security_type == "Equity" || security.attrs.security_type == "Equity Common";2117 };2118 this.isOption = function(security) {2119 if (!security) return;2120 return security.attrs.security_type == "Option";2121 };2122 this.isWarrant = function(security) {2123 if (!security) return;2124 return security.attrs.security_type == "Warrant";2125 };2126 function updateEvidenceInDB(obj, action) {2127 if (obj.transaction && obj.evidence_data) {2128 SWBrijj.procm('_ownership.upsert_transaction_evidence',2129 parseInt(obj.transaction),2130 JSON.stringify(obj.evidence_data)2131 ).then(function(r) {2132 void(r);2133 }).except(function(e) {2134 $rootScope.$emit("notification:fail",2135 "Something went wrong. Please try again.");2136 console.log(e);2137 });2138 }2139 }2140 this.updateEvidenceInDB = updateEvidenceInDB;2141 function evidenceEquals(ev1, ev2) {2142 return (ev1.doc_id && ev2.doc_id &&2143 ev1.doc_id==ev2.doc_id &&2144 ev1.investor==ev2.investor)2145 || (ev1.original && ev2.original &&2146 !ev1.doc_id && !ev2.doc_id &&2147 ev1.original==ev2.original);2148 }2149 this.evidenceEquals = evidenceEquals;2150 function addEvidence(ev) {2151 if (captable.evidence_object &&2152 captable.evidence_object.evidence_data) {2153 captable.evidence_object.evidence_data.push(ev);2154 }2155 }2156 this.addEvidence = addEvidence;2157 function removeEvidence(ev, obj) {2158 if (!obj) {2159 captable.evidence_object.evidence_data =2160 captable.evidence_object.evidence_data2161 .filter(function(x) {2162 return !evidenceEquals(ev, x);});2163 updateEvidenceInDB(captable.evidence_object, 'removed');2164 } else {2165 obj.evidence_data = obj.evidence_data2166 .filter(function(x) {2167 return !evidenceEquals(ev, x);2168 });2169 updateEvidenceInDB(obj, 'removed');2170 }2171 }2172 this.removeEvidence = removeEvidence;2173 this.toggleForEvidence = function(ev) {2174 if (!ev || !captable.evidence_object) {return;}2175 if (!captable.evidence_object.evidence_data) {2176 captable.evidence_object.evidence_data = [];2177 } else {2178 var action = "";2179 if (isEvidence(ev)) {2180 removeEvidence(ev);2181 action = "removed";2182 } else {2183 addEvidence(ev);2184 action = "added";2185 }2186 updateEvidenceInDB(captable.evidence_object, action);2187 }2188 };2189 function isEvidence(ev) {2190 if (captable.evidence_object &&2191 captable.evidence_object.evidence_data) {2192 return captable.evidence_object.evidence_data2193 .filter(function(x) {2194 return evidenceEquals(ev, x);2195 }).length>0;2196 } else {2197 return false;2198 }2199 }2200 this.isEvidence = isEvidence;2201 function validateTransaction(transaction) {2202 var correct = true;2203 if (!attrs)2204 {2205 return true;2206 }2207 if (!transaction.attrs || !transaction.attrs.security_type)2208 {2209 return false;2210 }2211 if (!attrs[transaction.attrs.security_type])2212 {2213 if (!attributes.isLoaded())2214 {//the data could be correct, but the attributes aren't filled in yet2215 return true;2216 }2217 return false;2218 }2219 for (var att in transaction.attrs)2220 {2221 if ((transaction.attrs[att]) && (String(transaction.attrs[att]).length > 0))2222 {2223 if (!attrs[transaction.attrs.security_type] || !attrs[transaction.attrs.security_type][transaction.kind] || !attrs[transaction.attrs.security_type][transaction.kind][att])2224 {2225 correct = false;2226 //console.log("Invalid attribute");2227 //console.log(att);2228 return correct;2229 }2230 if (att.indexOf('security_type') != -1)2231 {2232 if (!attrs.hasOwnProperty(transaction.attrs[att]))2233 {2234 correct = false;2235 return correct;2236 }2237 break;2238 }2239 switch(attrs[transaction.attrs.security_type][transaction.kind][att].type)2240 {2241 case "number":2242 case "fraction":2243 if (!calculate.isNumber(transaction.attrs[att]))2244 {2245 correct = false;2246 return correct;2247 }2248 break;2249 case "enum":2250 if (attrs[transaction.attrs.security_type][transaction.kind][att].labels.indexOf(transaction.attrs[att]) == -1)2251 {2252 correct = false;2253 return correct;2254 }2255 break;2256 case "date":2257 break;2258 default:2259 if ((attrs[transaction.attrs.security_type][transaction.kind][att].type) &&2260 (typeof(transaction.attrs[att]) != attrs[transaction.attrs.security_type][transaction.kind][att].type))2261 {2262 correct = false;2263 return correct;2264 }2265 }2266 }2267 }2268 for (att in attrs[transaction.attrs.security_type][transaction.kind])2269 {2270 if (attrs[transaction.attrs.security_type][transaction.kind][att].required)2271 {2272 if (!((transaction.attrs[att] != undefined) && (transaction.attrs[att] != null) &&2273 (String(transaction.attrs[att]).length > 0)))2274 {2275 correct = false;2276 return correct;2277 }2278 }2279 }2280 return correct;2281 }2282 this.validateTransaction = validateTransaction;2283 function validateCell(cell) {2284 if (!attrs)2285 {2286 return true;2287 }2288 var correct = true;2289 for (var t in cell.transactions)2290 {2291 correct = correct && (cell.transactions[t].valid || validateTransaction(cell.transactions[t]));2292 if (!correct)2293 return correct;2294 }2295 return correct;2296 }2297 this.validateCell = validateCell;2298 this.numGrantholders = function() {2299 return captable.grantCells.reduce(2300 accumulateProperty('investor'), []).length;2301 };2302 function toArrays() {2303 var res = [];2304 var security_row = ["Ownership", "", "Shareholder"];2305 var sub_header_row = ["Shares", "%", ""];2306 angular.forEach(captable.securities, function(sec) {2307 security_row.push(sec.name.replace(/,/g , ""), "");2308 sub_header_row.push($filter('issueUnitLabel')(sec.attrs.security_type),2309 'Total Paid');2310 });2311 res.push(security_row, sub_header_row);2312 angular.forEach(captable.investors, function(inv) {2313 var inv_row = [rowSum(inv.name).toString(),2314 (investorOwnershipPercentage(inv.name).toString() || "0.000") + "%",...

Full Screen

Full Screen

inv_captable.js

Source:inv_captable.js Github

copy

Full Screen

...103 $scope.$watch('ctFilter', function(newVal, oldVal) {104 switch (selectedThing()) {105 case "selectedCell":106 if ($scope.filteredSecurityList()107 .reduce(captable.accumulateProperty('name'), [])108 .indexOf($scope.selectedCell.security) == -1) {109 deselectCell();110 }111 return;112 case "selectedInvestor":113 return;114 case "selectedSecurity":115 if ($scope.filteredSecurityList()116 .indexOf($scope.selectedSecurity) == -1) {117 deselectSecurity();118 }119 return;120 }121 }, true);122 $scope.filteredSecurityList = function() {123 return $filter('filter')($scope.ct.securities, $scope.securityFilter);124 };125 $scope.filteredSecurityNames = function() {126 return $scope.filteredSecurityList()127 .reduce(captable.accumulateProperty('name'), []);128 };129 $scope.securityUnitLabel = function(security) {130 var type = $filter('issueUnitLabel')(security.attrs.security_type);131 return type;132 };133 $scope.selectedCell = null;134 $scope.selectedInvestor = null;135 $scope.selectedSecurity = null;136 function displayIntroSidebar() {137 $scope.sideBar = "home";138 }139 function displayInvestorDetails() {140 $scope.sideBar = 3;141 }...

Full Screen

Full Screen

transformer.js

Source:transformer.js Github

copy

Full Screen

...43 (acc, snap) => ({44 ...acc,45 [snap.name]: {46 ...acc[snap.name],47 commits: [...accumulateProperty(snap, acc[snap.name], 'commit')],48 commitDates: [...accumulateProperty(snap, acc[snap.name], 'commitDate')],49 environmentHashes: [50 ...accumulateProperty(snap, acc[snap.name], 'environmentHash', 'environmentHashes'),51 ],52 similarityHashes: [...accumulateProperty(snap, acc[snap.name], 'similarityHash', 'similarityHashes')],53 metrics: mergeMetrics(snap, acc[snap.name]),54 },55 }),56 {},57 );58 const benchmarks = Object.keys(benchesByKeys).map((key) => ({59 name: key,60 ...normalizeBenchmark(benchesByKeys[key]),61 }));62 return benchmarks;...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1var BestPractices = require('./bestPractices.js');2var bestPractices = new BestPractices();3var array = [1,2,3,4,5];4var result = bestPractices.accumulateProperty(array, 'length');5console.log(result);6var BestPractices = function() {7 this.accumulateProperty = function(collection, property) {8 var total = 0;9 for (var i = 0; i < collection.length; i++) {10 total += collection[i][property];11 }12 return total;13 };14};15module.exports = BestPractices;16import BestPractices from './bestPractices.js';17let bestPractices = new BestPractices();18let array = [1,2,3,4,5];19let result = bestPractices.accumulateProperty(array, 'length');20console.log(result);21class BestPractices {22 accumulateProperty(collection, property) {23 let total = 0;24 for (let i = 0; i < collection.length; i++) {25 total += collection[i][property];26 }27 return total;28 }29}30export default BestPractices;31import BestPractices from './bestPractices.js';32let bestPractices = new BestPractices();33let array = [1,2,3,4,5];34let result = bestPractices.accumulateProperty(array, 'length');35console.log(result);36class BestPractices {37 accumulateProperty = (collection, property) => {38 let total = 0;39 for (let i = 0; i < collection.length; i++) {40 total += collection[i][property];41 }42 return total;43 }44}45export default BestPractices;

Full Screen

Using AI Code Generation

copy

Full Screen

1var BestJS = require('bestjs');2var array = [1,2,3,4,5,6,7,8,9,10];3var sum = BestJS.accumulateProperty(array, "add", 0);4console.log("Sum of all values in array: " + sum);5var BestJS = require('bestjs');6var array = [1,2,3,4,5,6,7,8,9,10];7var sum = BestJS.accumulateProperty(array, "multiply", 1);8console.log("Product of all values in array: " + sum);9var BestJS = require('bestjs');10var array = [1,2,3,4,5,6,7,8,9,10];11var sum = BestJS.accumulateProperty(array, "subtract", 0);12console.log("Subtraction of all values in array: " + sum);13var BestJS = require('bestjs');14var array = [1,2,3,4,5,6,7,8,9,10];15var sum = BestJS.accumulateProperty(array, "divide", 1);16console.log("Division of all values in array: " + sum);17var BestJS = require('bestjs');18var array = [1,2,3,4,5,6,7,8,9,10];19var sum = BestJS.accumulateProperty(array, "mod", 0);20console.log("Mod of all values in array: " + sum);21var BestJS = require('bestjs');22var array = [1,2,3,4,5,6,7,8,9,10];23var sum = BestJS.accumulateProperty(array, "pow", 1);24console.log("Pow of all values in array: " + sum);25var BestJS = require('bestjs');

Full Screen

Using AI Code Generation

copy

Full Screen

1$(document).ready(function() {2 $('a.editable').best_in_place();3 $('a.editable').best_in_place().bind("ajax:success", function(evt, data, status, xhr) {4 console.log("data is " + data);5 console.log("status is " + status);6 console.log("xhr is " + xhr);7 console.log("evt is " + evt);8 });9});10user.accumulateProperty('balance', 10);

Full Screen

Using AI Code Generation

copy

Full Screen

1var BestProperty = require('./BestProperty');2var bestProperty = new BestProperty();3 {id: 1, name: 'House', price: 200000},4 {id: 2, name: 'Flat', price: 100000},5 {id: 3, name: 'Bungalow', price: 300000}6];7var maxPrice = bestProperty.accumulateProperty(properties, 'price', 'max');8console.log('Max price: ' + maxPrice);9var minPrice = bestProperty.accumulateProperty(properties, 'price', 'min');10console.log('Min price: ' + minPrice);11var totalPrice = bestProperty.accumulateProperty(properties, 'price', 'total');12console.log('Total price: ' + totalPrice);13var averagePrice = bestProperty.accumulateProperty(properties, 'price', 'average');14console.log('Average price: ' + averagePrice);15var maxId = bestProperty.accumulateProperty(properties, 'id', 'max');16console.log('Max id: ' + maxId);17var minId = bestProperty.accumulateProperty(properties, 'id', 'min');18console.log('Min id: ' + minId);19var totalId = bestProperty.accumulateProperty(properties, 'id', 'total');20console.log('Total id: ' + totalId);21var averageId = bestProperty.accumulateProperty(properties, 'id', 'average');22console.log('Average id: ' + averageId);

Full Screen

Using AI Code Generation

copy

Full Screen

1var BestBuyProduct = require('./BestBuyProduct');2var product = new BestBuyProduct();3product.accumulateProperty('price');4product.accumulateProperty('shippingCost');5product.accumulateProperty('totalCost');6product.printProperty('price');7product.printProperty('shippingCost');8product.printProperty('totalCost');9var Product = require('./Product');10var BestBuyProduct = function() {11 Product.call(this);12};13BestBuyProduct.prototype = Object.create(Product.prototype);14BestBuyProduct.prototype.accumulateProperty = function(prop) {15 this[prop] = this.data.reduce(function(prev, curr) {16 return prev + curr[prop];17 }, 0);18};19BestBuyProduct.prototype.printProperty = function(prop) {20 console.log(this[prop]);21};22module.exports = BestBuyProduct;23var Product = function() {24 this.data = [{25 "name": "Apple - MacBook Air® (Latest Model) - 13.3\" Display - Intel Core i5 - 8GB Memory - 128GB Flash Storage - Silver",26 }, {27 "name": "Apple - MacBook Air® (Latest Model) - 13.3\" Display - Intel Core i5 - 8GB Memory - 128GB Flash Storage - Space Gray",28 }, {29 "name": "Apple - MacBook Air® (Latest Model) - 13.3\" Display - Intel Core i5 - 8GB Memory - 128GB Flash Storage - Gold",30 }, {31 "name": "Apple - MacBook Air® (Latest Model) - 13.3\" Display - Intel Core i5 - 8GB Memory - 128GB Flash Storage - Rose Gold",32 }, {33 "name": "Apple - MacBook Air® (Latest Model) -

Full Screen

Using AI Code Generation

copy

Full Screen

1var bsl = new BestSellerList(2004);2var total = bsl.accumulateProperty("copiesSold");3print("Total copies sold in 2004: " + total);4var bsl = new BestSellerList(2004);5var total = bsl.accumulateProperty("pages");6print("Total pages in 2004: " + total);7var bsl = new BestSellerList(2004);8var total = bsl.accumulateProperty("pages", function(book){9 return book.price < 10;10});11print("Total pages in 2004: " + total);12var bsl = new BestSellerList(2004);13var total = bsl.accumulateProperty("pages", function(book){14 return book.price < 10;15});16print("Total pages in 2004: " + total);17var bsl = new BestSellerList(2004);18var total = bsl.accumulateProperty("pages", function(book){19 return book.price < 10;20});21print("Total pages in 2004: " + total);22var bsl = new BestSellerList(2004);23var total = bsl.accumulateProperty("pages", function(book){24 return book.price < 10;25});26print("Total

Full Screen

Using AI Code Generation

copy

Full Screen

1var bestCoffeeShop = require('./bestCoffeeShop');2var myBestCoffeeShop = new bestCoffeeShop.BestCoffeeShop();3myBestCoffeeShop.accumulateProperty('coffeeShops', 'coffeeShop');4console.log(myBestCoffeeShop.coffeeShops);5var bestCoffeeShop = require('./bestCoffeeShop');6var myBestCoffeeShop = new bestCoffeeShop.BestCoffeeShop();7myBestCoffeeShop.accumulateProperty('coffeeShops', 'coffeeShop');8console.log(myBestCoffeeShop.coffeeShops);9var bestCoffeeShop = require('./bestCoffeeShop');10var myBestCoffeeShop = new bestCoffeeShop.BestCoffeeShop();11myBestCoffeeShop.accumulateProperty('coffeeShops', 'coffeeShop');12console.log(myBestCoffeeShop.coffeeShops);13var bestCoffeeShop = require('./bestCoffeeShop');14var myBestCoffeeShop = new bestCoffeeShop.BestCoffeeShop();15myBestCoffeeShop.accumulateProperty('coffeeShops', 'coffeeShop');16console.log(myBestCoffeeShop.coffeeShops);17var bestCoffeeShop = require('./bestCoffeeShop');18var myBestCoffeeShop = new bestCoffeeShop.BestCoffeeShop();19myBestCoffeeShop.accumulateProperty('coffeeShops', 'coffeeShop');20console.log(myBestCoffeeShop.coffeeShops);21var bestCoffeeShop = require('./bestCoffeeShop');22var myBestCoffeeShop = new bestCoffeeShop.BestCoffeeShop();23myBestCoffeeShop.accumulateProperty('coffeeShops', 'coffeeShop');24console.log(myBestCoffeeShop.coffeeShops

Full Screen

Using AI Code Generation

copy

Full Screen

1var bestCustomer = new BestCustomer();2var result = bestCustomer.accumulateProperty(10, 20);3console.log(result);4var bestCustomer = new BestCustomer();5var result = bestCustomer.accumulateProperty(10, 20);6console.log(result);7class BestCustomerPrinter {8 constructor() {9 }10 printBestCustomer() {11 var bestCustomer = new BestCustomer();12 var result = bestCustomer.accumulateProperty(10, 20);13 console.log(result);14 }15}16var bestCustomerPrinter = new BestCustomerPrinter();17bestCustomerPrinter.printBestCustomer();18var bestCustomer = new BestCustomer();19var result = bestCustomer.accumulateProperty(10, 20);20console.log(result);21class BestCustomerPrinter {22 constructor() {23 }24 printBestCustomer() {25 var bestCustomer = new BestCustomer();26 var result = bestCustomer.accumulateProperty(10, 20);27 console.log(result);28 }29}30var bestCustomerPrinter = new BestCustomerPrinter();31bestCustomerPrinter.printBestCustomer();

Full Screen

Using AI Code Generation

copy

Full Screen

1var myModel = {2 address: {3 }4};5var myView = Backbone.View.extend({6 initialize: function() {7 this.render();8 },9 render: function() {10 this.$el.html("<div id='name'></div><div id='age'></div><div id='street'></div><div id='city'></div><div id='state'></div><div id='zip'></div>");11 this.$('#name').best_in_place(myModel, 'name');12 this.$('#age').best_in_place(myModel, 'age');13 this.$('#street').best_in_place(myModel, 'address.street');14 this.$('#city').best_in_place(myModel, 'address.city');15 this.$('#state').best_in_place(myModel, 'address.state');16 this.$('#zip').best_in_place(myModel, 'address.zip', {accumulateProperty: 'address'});17 }18});19var myViewInstance = new myView();20var myModel = {21 address: {22 }23};24var myView = Backbone.View.extend({25 initialize: function() {26 this.render();27 },28 render: function() {29 this.$el.html("<div id='name'></div><div id='age'></div><div id='street'></div><div id='city'></div><div id='state'></div><div id='zip'></

Full Screen

Automation Testing Tutorials

Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.

LambdaTest Learning Hubs:

YouTube

You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.

Run Best automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful