protected void ButtonInvoice_Click(object sender, EventArgs e) { if (_authority.HasPermission(Permission.CanPayOutMoney, Organization.PPSEid, -1, Authorization.Flag.ExactOrganization)) { foreach (string indexString in this.GridDebts.SelectedIndexes) { // Creating the invoice closes the expense claims. int index = Int32.Parse(indexString); string protoIdentity = (string)this.GridDebts.MasterTableView.DataKeyValues[index]["ProtoIdentity"]; string[] identityStrings = protoIdentity.Split(','); ExpenseClaim template = ExpenseClaim.FromIdentity(Int32.Parse(identityStrings[0])); CultureInfo oldCulture = CultureInfo.CurrentUICulture; Thread.CurrentThread.CurrentUICulture = new CultureInfo(Organization.PPSE.DefaultCountry.Culture); OutboundInvoice invoice = OutboundInvoice.Create(Organization.PPSE, _currentUser, DateTime.Today.AddDays(30), Organization.PPSE.FinancialAccounts.DebtsExpenseClaims, template.Claimer.Name, template.Claimer.Email, template.Claimer.Street + "\r\n" + template.Claimer.PostalCodeAndCity, Organization.PPSE.DefaultCountry.Currency, true, GetLocalResourceObject("InvoiceLiterals.ReclaimedCashAdvance").ToString()); Thread.CurrentThread.CurrentUICulture = oldCulture; foreach (string idString in identityStrings) { int claimId = Int32.Parse(idString); ExpenseClaim claim = ExpenseClaim.FromIdentity(claimId); invoice.AddItem("Exp #" + claim.Identity.ToString() + ": " + claim.Description, -claim.AmountCents); claim.Repaid = true; claim.Open = false; } // Create transaction FinancialTransaction transaction = FinancialTransaction.Create(Organization.PPSEid, DateTime.Now, "Outbound Invoice #" + invoice.Identity + ": " + template.ClaimerCanonical); transaction.AddRow(Organization.PPSE.FinancialAccounts.DebtsExpenseClaims, -invoice.AmountCents, _currentUser); transaction.AddRow(Organization.PPSE.FinancialAccounts.AssetsOutboundInvoices, invoice.AmountCents, _currentUser); transaction.Dependency = invoice; // Create event Activizr.Logic.Support.PWEvents.CreateEvent(EventSource.PirateWeb, EventType.OutboundInvoiceCreated, _currentUser.Identity, 1, 1, 0, invoice.Identity, protoIdentity); } PopulateGrid(); this.GridDebts.Rebind(); } }
protected void ChargeBudget(FinancialAccount budget, double amount, string comment) { FinancialTransaction newTransaction = FinancialTransaction.Create(1, DateTime.Now, comment); newTransaction.AddRow(budget, amount, _currentUser); newTransaction.AddRow(Organization.FromIdentity(Organization.PPSEid).FinancialAccounts.CostsInfrastructure, -amount, _currentUser); }
public override void Run() { HotBitcoinAddressUnspent utxoToReturn = HotBitcoinAddressUnspent.FromIdentity(UtxoIdentity); HotBitcoinAddress utxoAddress = utxoToReturn.Address; BitcoinSecret secretKey = utxoAddress.PrivateKey; // TODO: Verify that the utxoAddress is an EchoTest address, i.e. has second path component == BitcoinUtility.BitcoinEchoTestIndex string returnAddress = BitcoinUtility.GetInputAddressesForTransaction(BitcoinChain.Cash, utxoToReturn.TransactionHash)[0]; // assumes at least one input address -- not coinbase // Return the money BitcoinTransactionInputs inputs = utxoToReturn.AsInputs; Int64 satoshisToReturn = utxoToReturn.AmountSatoshis; Coin[] coins = inputs.Coins; ICoin[] iCoins = coins; ISecret[] privateKeys = utxoToReturn.AsInputs.PrivateKeys; TransactionBuilder txBuilder = new TransactionBuilder(); txBuilder = txBuilder.SendFees(new Satoshis(BitcoinUtility.EchoFeeSatoshis)); txBuilder = txBuilder.AddCoins(iCoins); txBuilder = txBuilder.AddKeys(privateKeys); if (returnAddress.StartsWith("1")) { txBuilder = txBuilder.Send(new BitcoinPubKeyAddress(returnAddress), new Satoshis(utxoToReturn.AmountSatoshis - BitcoinUtility.EchoFeeSatoshis)); } else if (returnAddress.StartsWith("3")) { txBuilder = txBuilder.Send(new BitcoinScriptAddress(returnAddress, Network.Main), new Satoshis(utxoToReturn.AmountSatoshis - BitcoinUtility.EchoFeeSatoshis)); } else { throw new ArgumentException("Unrecognized address format"); } Transaction tx = txBuilder.BuildTransaction(true, SigHash.ForkId | SigHash.All); BitcoinUtility.BroadcastTransaction(tx, BitcoinChain.Cash); utxoToReturn.Delete(); utxoAddress.UpdateTotal(); // Update the ledger string tx2Description = "Bitcoin echo test repayment"; FinancialTransaction ledgerTx2 = FinancialTransaction.Create(this.Organization, DateTime.UtcNow, tx2Description); ledgerTx2.AddRow(this.Organization.FinancialAccounts.DebtsOther, satoshisToReturn, this.Person); ledgerTx2.AddRow(this.Organization.FinancialAccounts.AssetsBitcoinHot, -satoshisToReturn, this.Person); ledgerTx2.BlockchainHash = tx.GetHash().ToString(); }
public void CloseBudget(Person closingPerson) { Int64 remainingFunds = -Budget.GetDeltaCents(CreatedDateTime, DateTime.Now); FinancialTransaction transaction = FinancialTransaction.Create(OrganizationId, DateTime.Now, "Closing conference #" + Identity); transaction.AddRow(Budget, remainingFunds, closingPerson); transaction.AddRow(Budget.Parent, -remainingFunds, closingPerson); }
public void SendInvoice() { DateTime invoiceDue = DateTime.Today.AddDays(14); DateTime maxInvoiceDue = this.Parley.StartDate.AddDays(-10); if (invoiceDue > maxInvoiceDue) { invoiceDue = maxInvoiceDue; } OutboundInvoice invoice = OutboundInvoice.Create(this.Parley.Organization, this.Parley.Person, invoiceDue, this.Parley.Budget, this.Person.Name, this.Person.Mail, string.Empty, this.Parley.Organization.DefaultCountry.Currency, true, string.Empty); invoice.AddItem("Deltagarkostnad " + this.Parley.Name, this.Parley.AttendanceFeeCents); // TODO: Localize ParleyOptions options = this.Options; foreach (ParleyOption option in options) { invoice.AddItem(option.Description, option.AmountCents); } // Create the financial transaction with rows FinancialTransaction transaction = FinancialTransaction.Create(this.Parley.OrganizationId, DateTime.Now, "Outbound Invoice #" + invoice.Identity + " to " + this.Person.Name); transaction.AddRow(this.Parley.Organization.FinancialAccounts.AssetsOutboundInvoices, invoice.AmountCents, null); transaction.AddRow(this.Parley.Budget, -invoice.AmountCents, null); // Make the transaction dependent on the outbound invoice transaction.Dependency = invoice; // Create the event for PirateBot-Mono to send off mails PWEvents.CreateEvent(EventSource.PirateWeb, EventType.OutboundInvoiceCreated, this.PersonId, this.Parley.OrganizationId, 1, this.PersonId, invoice.Identity, string.Empty); // Update the attendee as invoiced base.Invoiced = true; base.OutboundInvoiceId = invoice.Identity; SwarmDb.GetDatabaseForWriting().SetParleyAttendeeInvoiced(this.Identity, invoice.Identity); }
public static void MatchTransactionOpenVatReport(int transactionId, int vatReportId) { if (transactionId == 0 || vatReportId == 0) { return; } AuthenticationData authData = GetAuthenticationDataAndCulture(); if ( !authData.Authority.HasAccess(new Access(authData.CurrentOrganization, AccessAspect.BookkeepingDetails))) { throw new UnauthorizedAccessException(); } FinancialTransaction transaction = FinancialTransaction.FromIdentity(transactionId); VatReport vatReport = VatReport.FromIdentity(vatReportId); if (authData.CurrentOrganization.Identity != transaction.OrganizationId || authData.CurrentOrganization.Identity != vatReport.OrganizationId) { throw new InvalidOperationException("Organization mismatch"); } Int64 diffCents = vatReport.VatInboundCents - vatReport.VatOutboundCents; if (transaction.Rows.AmountCentsTotal != diffCents) { throw new InvalidOperationException("Amount mismatch"); } // Go ahead and close vatReport.CloseTransaction = transaction; // throws if already closed vatReport.Open = false; if (diffCents > 0) { // Positive amount - asset account transaction.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsVatInboundReported, -diffCents, null); } else { // Negative amount - liability account transaction.AddRow(authData.CurrentOrganization.FinancialAccounts.DebtsVatOutboundReported, -diffCents, null); } }
public static AjaxInputCallResult CreateTransaction(string dateTimeString, string amountString, string description, int budgetId) { AuthenticationData authData = GetAuthenticationDataAndCulture(); FinancialAccount budget = FinancialAccount.FromIdentity(budgetId); if (budget.OrganizationId != authData.CurrentOrganization.Identity) { throw new UnauthorizedAccessException(); } if (!authData.Authority.HasAccess(new Access(authData.CurrentOrganization, AccessAspect.BookkeepingDetails))) { throw new UnauthorizedAccessException(); } Int64 amountCents = Formatting.ParseDoubleStringAsCents(amountString); DateTime txTime = DateTime.Parse(dateTimeString); // TODO: Return better error codes/messages if one of these fail FinancialTransaction transaction = FinancialTransaction.Create(authData.CurrentOrganization, txTime, description); transaction.AddRow(budget, amountCents, authData.CurrentUser); AjaxInputCallResult result = new AjaxInputCallResult { Success = true, ObjectIdentity = transaction.Identity, NewValue = (amountCents / 100.0).ToString("N2") }; return(result); }
public static AjaxCallResult ResyncSatoshisInLedger(string itemId) { AuthenticationData authData = GetAuthenticationDataAndCulture(); if (!authData.Authority.HasAccess(new Access(authData.CurrentOrganization, AccessAspect.BookkeepingDetails))) { throw new UnauthorizedAccessException(); } Int64 cashSatoshisInLedger = authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot.GetForeignCurrencyBalanceDeltaCents( Constants.DateTimeLow, Constants.DateTimeHigh).Cents; Int64 cashSatoshisInHotwallet = HotBitcoinAddresses.GetSatoshisInHotwallet(authData.CurrentOrganization)[BitcoinChain.Cash]; Int64 adjustment = cashSatoshisInHotwallet - cashSatoshisInLedger; // positive if ledger needs upward adjustment FinancialTransaction adjustmentTx = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, Resources.Pages.Ledgers.EndOfMonth_LedgerBitcoinBalanceTransactionDescription); adjustmentTx.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, 0, authData.CurrentUser).AmountForeignCents = new Money(adjustment, Currency.BitcoinCash); return(new AjaxCallResult { Success = true, DisplayMessage = String.Format(Resources.Pages.Ledgers.EndOfMonth_Dialog_LedgerBitcoinBalanceMismatch, cashSatoshisInHotwallet / 100.0, cashSatoshisInLedger / 100.0) }); }
protected void ButtonCreateTransaction_Click(object sender, EventArgs e) { if (!_authority.HasPermission(Permission.CanDoEconomyTransactions, _organizationId, -1, Authorization.Flag.ExactOrganization)) { ScriptManager.RegisterStartupScript(this, Page.GetType(), "getlost", "alert ('You do not have access to financial records.');", true); return; } int accountId = Int32.Parse(this.DropAccountsCreate.SelectedValue); if (accountId == 0) { ScriptManager.RegisterStartupScript(this, Page.GetType(), "getlost", "alert ('Please select an account.');", true); return; } double amount = Double.Parse(this.TextAmountCreate.Text, new CultureInfo("sv-SE")); string description = this.TextDescriptionCreate.Text; DateTime transactionDateTime = (DateTime)this.DateCreate.SelectedDate; FinancialTransaction transaction = FinancialTransaction.Create(Organization.PPSEid, transactionDateTime, description); transaction.AddRow(FinancialAccount.FromIdentity(accountId), amount, _currentUser); // As the RadWindowManager and RadAjaxUpdate are part of the UpdatePanel we're rewriting, we // need to make the client call the function only when the ajax call has completed. We set // 200ms for this, but pretty much any amount of time should be ok, as long as it's delayed // past the actual ajax rewrite. ScriptManager.RegisterStartupScript(this, Page.GetType(), "finishthejob", "ShowTransactionFormDelayed (" + transaction.Identity + ");", true); }
protected void ButtonExecute_Click(object sender, EventArgs e) { if (!_authority.HasPermission(Permission.CanDoEconomyTransactions, thisInvoice.OrganizationId, -1, Authorization.Flag.ExactOrganization)) { throw new UnauthorizedAccessException("Access Denied"); } if (this.RadioManualMap.Checked) { int transactionId = Int32.Parse(this.DropTransactions.SelectedValue); FinancialTransaction transaction = FinancialTransaction.FromIdentity(transactionId); Payment payment = Payment.CreateSingle(thisInvoice.Organization, transaction.DateTime, thisInvoice.Currency, thisInvoice.AmountCents, thisInvoice, _currentUser); payment.AddInformation(PaymentInformationType.Freeform, "Mapped manually in PirateWeb"); transaction.AddRow(thisInvoice.Organization.FinancialAccounts.AssetsOutboundInvoices, -thisInvoice.AmountCents, _currentUser); transaction.Dependency = payment.Group; } else if (this.RadioCreditInvoice.Checked) { thisInvoice.Credit(_currentUser); } ClientScript.RegisterStartupScript(Page.GetType(), "mykey", "CloseAndRebind();", true); }
public static void BalanceTransactionManually(int transactionId, int accountId) { if (transactionId == 0 || accountId == 0) { return; } AuthenticationData authData = GetAuthenticationDataAndCulture(); if ( !authData.Authority.HasAccess(new Access(authData.CurrentOrganization, AccessAspect.BookkeepingDetails))) { throw new UnauthorizedAccessException(); } FinancialTransaction transaction = FinancialTransaction.FromIdentity(transactionId); FinancialAccount account = FinancialAccount.FromIdentity(accountId); if (transaction.OrganizationId != authData.CurrentOrganization.Identity || account.OrganizationId != authData.CurrentOrganization.Identity) { throw new UnauthorizedAccessException(); } transaction.AddRow(account, -transaction.Rows.AmountCentsTotal, authData.CurrentUser); }
public static void MatchTransactionOpenPayout(int transactionId, int payoutId) { if (transactionId == 0 || payoutId == 0) { return; } AuthenticationData authData = GetAuthenticationDataAndCulture(); if ( !authData.Authority.HasAccess(new Access(authData.CurrentOrganization, AccessAspect.BookkeepingDetails))) { throw new UnauthorizedAccessException(); } FinancialTransaction transaction = FinancialTransaction.FromIdentity(transactionId); Payout payout = Payout.FromIdentity(payoutId); if (transaction.OrganizationId != authData.CurrentOrganization.Identity || payout.OrganizationId != authData.CurrentOrganization.Identity) { throw new UnauthorizedAccessException(); } Int64 transactionCents = transaction.Rows.AmountCentsTotal; Int64 payoutCents = payout.AmountCents; FinancialAccount forexSpillAccount = authData.CurrentOrganization.FinancialAccounts.IncomeCurrencyFluctuations; if (forexSpillAccount == null && payoutCents != -transactionCents) // the tx-negative is because it's a payout { throw new InvalidOperationException("Need forex gain/loss accounts for this operation"); // TODO: Autocreate? } if ((-transactionCents) > payoutCents) // the tx-negative is because it's a payout { // This is a forex loss, not a gain which is the default forexSpillAccount = authData.CurrentOrganization.FinancialAccounts.CostsCurrencyFluctuations; } if (-transactionCents != payoutCents) { // Forex adjust transaction.AddRow(forexSpillAccount, -(payoutCents + transactionCents), // plus because transactionCents is negative authData.CurrentUser); // Adds the forex adjustment so we can bind payout to tx and close } // The amounts should match now if (transaction.Rows.AmountCentsTotal != -payout.AmountCents) { throw new InvalidOperationException(); } payout.BindToTransactionAndClose(transaction, authData.CurrentUser); }
public static void MatchTransactionOpenPayoutForeign(int transactionId, int payoutId) { // This is like the non-foreign version except this one chalks up the difference to forex gain/loss accounts if (transactionId == 0 || payoutId == 0) { return; } AuthenticationData authData = GetAuthenticationDataAndCulture(); if ( !authData.Authority.HasAccess(new Access(authData.CurrentOrganization, AccessAspect.BookkeepingDetails))) { throw new UnauthorizedAccessException(); } FinancialAccount forexSpillAccount = authData.CurrentOrganization.FinancialAccounts.IncomeCurrencyFluctuations; if (forexSpillAccount == null) { throw new InvalidOperationException("Need forex gain/loss accounts for this operation"); // TODO: Autocreate? } FinancialTransaction transaction = FinancialTransaction.FromIdentity(transactionId); Payout payout = Payout.FromIdentity(payoutId); if (transaction.OrganizationId != authData.CurrentOrganization.Identity || payout.OrganizationId != authData.CurrentOrganization.Identity) { throw new UnauthorizedAccessException(); } if (-transaction.Rows.AmountCentsTotal > payout.AmountCents) { // This is a forex loss, not a gain which is the default forexSpillAccount = authData.CurrentOrganization.FinancialAccounts.CostsCurrencyFluctuations; } transaction.AddRow(forexSpillAccount, -(payout.AmountCents + transaction.Rows.AmountCentsTotal), // plus because AmountCentsTotal is negative authData.CurrentUser); // Adds the forex adjustment so we can bind payout to tx and close if (transaction.Rows.AmountCentsTotal != -payout.AmountCents) { throw new InvalidOperationException(); } payout.BindToTransactionAndClose(transaction, authData.CurrentUser); }
protected void ButtonAutoBalance_Click(object sender, EventArgs e) { Person currentUser = Person.FromIdentity(Int32.Parse(HttpContext.Current.User.Identity.Name)); Authority authority = currentUser.GetAuthority(); if (!authority.HasPermission(Permission.CanDoEconomyTransactions, Int32.Parse(this.DropOrganizations.SelectedValue), -1, Authorization.Flag.ExactOrganization)) { ScriptManager.RegisterStartupScript(this, Page.GetType(), "validationfailed", "alert ('You do not have access to changing financial records.');", true); return; } if (this.DropAutoBalanceAccount.SelectedValue == "0") { ScriptManager.RegisterStartupScript(this, Page.GetType(), "validationfailed", "alert ('Please select an account to auto-balance against.');", true); return; } FinancialAccount autoBalanceAccount = FinancialAccount.FromIdentity(Int32.Parse(this.DropAutoBalanceAccount.SelectedValue)); if (this.GridTransactions.SelectedIndexes.Count == 0) { ScriptManager.RegisterStartupScript(this, Page.GetType(), "validationfailed", "alert ('Please select one or more transactions to auto-balance.');", true); return; } foreach (string indexString in this.GridTransactions.SelectedIndexes) { int index = Int32.Parse(indexString); int transactionId = (int)this.GridTransactions.MasterTableView.DataKeyValues[index]["Identity"]; FinancialTransaction transaction = FinancialTransaction.FromIdentity(transactionId); transaction.AddRow(autoBalanceAccount, -transaction.Rows.AmountCentsTotal, currentUser); } PopulateGrid(); this.GridTransactions.Rebind(); this.DropAutoBalanceAccount.SelectedValue = "0"; }
public static bool AddTransactionRow(int txId, int accountId, string amountString) { AuthenticationData authData = GetAuthenticationDataAndCulture(); if ( !authData.Authority.HasAccess(new Access(authData.CurrentOrganization, AccessAspect.Bookkeeping, AccessType.Write))) { return(false); // fail } FinancialTransaction transaction = FinancialTransaction.FromIdentity(txId); if (authData.CurrentOrganization.Parameters.FiscalBooksClosedUntilYear >= transaction.DateTime.Year) { return(false); // can't edit closed books } FinancialAccount account = FinancialAccount.FromIdentity(accountId); Double amountFloat = Double.Parse(amountString); Int64 amountCents = (Int64)(amountFloat * 100.0); if (account.OrganizationId != authData.CurrentOrganization.Identity) { throw new InvalidOperationException("Account/Organization mismatch"); } if (amountCents == 0) { return(false); } transaction.AddRow(account, amountCents, authData.CurrentUser); return(true); }
protected void Page_Load(object sender, EventArgs e) { // THIS CODE IS TAKEN FROM PAYPAL'S TEMPLATE. Person notifyPerson = Person.FromIdentity(1); //Post back to either sandbox or live string strLive = "https://www.paypal.com/cgi-bin/webscr"; HttpWebRequest req = (HttpWebRequest)WebRequest.Create(strLive); //Set values for the request back req.Method = "POST"; req.ContentType = "application/x-www-form-urlencoded"; byte[] param = Request.BinaryRead(HttpContext.Current.Request.ContentLength); string strRequest = Encoding.ASCII.GetString(param); string originalRequest = strRequest; strRequest += "&cmd=_notify-validate"; req.ContentLength = strRequest.Length; //Send the request to PayPal and get the response StreamWriter streamOut = new StreamWriter(req.GetRequestStream(), System.Text.Encoding.ASCII); streamOut.Write(strRequest); streamOut.Close(); StreamReader streamIn = new StreamReader(req.GetResponse().GetResponseStream()); string strResponse = streamIn.ReadToEnd(); streamIn.Close(); // Decode parameters List <string> temp = new List <string>(); Dictionary <string, string> parameters = new Dictionary <string, string>(); string[] parameterStrings = originalRequest.Split('&'); foreach (string parameter in parameterStrings) { string[] data = parameter.Split('='); string dataKey = Server.UrlDecode(data[0]); string dataValue = Server.UrlDecode(data[1]); parameters[dataKey] = dataValue; temp.Add(string.Format("{0,-20} {1}", dataKey, dataValue)); } notifyPerson.SendNotice("Paypal listener point A", String.Join("\r\n", temp.ToArray()), 1); bool proceed = true; if (strResponse == "INVALID") { notifyPerson.SendNotice("Paypal listener FAIL - Response is INVALID", string.Empty, 1); proceed = false; } if (!parameters.ContainsKey("payment_status")) { notifyPerson.SendNotice("Paypal listener FAIL - payment_status was not supplied", string.Empty, 1); proceed = false; } else if (parameters["payment_status"] != "Completed" && parameters["payment_status"] != "Refunded") { notifyPerson.SendNotice("Paypal listener FAIL - payment_status is not Completed / Refunded", string.Empty, 1); proceed = false; } if (parameters["receiver_email"] != "*****@*****.**") { notifyPerson.SendNotice("Paypal listener FAIL - receiver_email is not [email protected]", string.Empty, 1); proceed = false; // HACK -- adjust for multiple orgs } if (parameters["mc_currency"] != "SEK") { notifyPerson.SendNotice("Paypal listener FAIL - mc_currency is not SEK", string.Empty, 1); proceed = false; // HACK -- adjust for multiple orgs } if (!parameters.ContainsKey("mc_gross") || String.IsNullOrEmpty(parameters["mc_gross"])) { notifyPerson.SendNotice("Paypal listener FAIL - mc_net is null or empty", string.Empty, 1); proceed = false; } if (strResponse == "VERIFIED" && proceed) { string transactionId = parameters["txn_id"]; string identifier = null; if (parameters.ContainsKey("invoice")) { identifier = parameters["invoice"]; } OutboundInvoice invoice = null; if (!String.IsNullOrEmpty(identifier)) { invoice = OutboundInvoice.FromReference(identifier); notifyPerson.SendNotice("Paypal listener - invoice identified as #" + invoice.Identity.ToString(), string.Empty, 1); } string grossString = parameters["mc_gross"]; string feeString = "0.0"; if (parameters.ContainsKey("mc_fee") && !string.IsNullOrEmpty(parameters["mc_fee"])) { feeString = parameters["mc_fee"]; } string comment = "Paypal "; if (parameters["payment_status"] == "Completed") { comment += parameters["txn_type"]; } else { // Refund comment += "Refund"; } if (parameters.ContainsKey("item_name")) { comment = parameters["item_name"]; } if (invoice != null) { comment = "Outbound Invoice #" + invoice.Identity.ToString() + " payment Paypal"; } DateTime dateTime = DateTime.ParseExact(parameters["payment_date"].Replace("PDT", "-07").Replace("PST", "-08"), "HH:mm:ss MMM dd, yyyy zz", CultureInfo.InvariantCulture).ToLocalTime(); Organization org = Organization.PPSE; Int64 feeCents = Int64.Parse(feeString.Replace(".", "")); Int64 grossCents = Int64.Parse(grossString.Replace(".", "")); Int64 amountCents = grossCents - feeCents; FinancialTransaction transaction = FinancialTransaction.ImportWithStub(org.Identity, dateTime, org.FinancialAccounts.AssetsPaypal.Identity, amountCents, comment, transactionId, 0); // TODO: Convert to cents if (transaction != null) { // The transaction was created. Examine if the autobook criteria are true. if (amountCents < 0 && parameters["payment_status"] == "Completed") { // Do not balance the transaction -- will have to be balanced manually } else if (amountCents > 0) { if (invoice != null) { Int64 expectedNetCents = invoice.AmountCents; transaction.AddRow(org.FinancialAccounts.CostsBankFees, expectedNetCents - amountCents, null); // can be negative transaction.AddRow(org.FinancialAccounts.AssetsOutboundInvoices, -expectedNetCents, null); // Hack: Add a line to the outbound invoice HERE with the PayPal surcharge invoice.AddItem("PayPal/Credit Card surcharge, 5%", (Int64)(invoice.AmountCents * 0.05)); Payment payment = Payment.CreateSingle(invoice.Organization, DateTime.Now, invoice.Currency, expectedNetCents, invoice, null); // HACK HACK HACK: says we always received the expected amount payment.AddInformation(PaymentInformationType.RefundInformation, "PayPal " + transactionId); payment.AddInformation(PaymentInformationType.Name, parameters["first_name"] + " " + parameters["last_name"]); payment.AddInformation(PaymentInformationType.Email, parameters["payer_email"]); transaction.Dependency = payment.Group; } else if (feeCents > 0) { // This is always an autodeposit transaction.AddRow(org.FinancialAccounts.CostsBankFees, feeCents, null); transaction.AddRow(org.FinancialAccounts.IncomeDonations, -grossCents, null); } else { transaction.AddRow(org.FinancialAccounts.IncomeDonations, -amountCents, null); } } } } }
protected void ButtonSubmitInvoice_Click(object sender, EventArgs e) { // If args were invalid, abort if (!Page.IsValid) { return; } if (this.TextNewItemDescription.Text.Length > 0) { AddItemsInEdit(false); } // Read the form data string customer = this.TextCustomer.Text; string paperAddress = this.TextPaperAddress.Text; string mailAddress = this.TextMailAddress.Text; int organizationId = Int32.Parse(this.DropOrganizations.SelectedValue); bool domestic = this.CheckDomestic.Checked; string theirReference = this.TextTheirReference.Text; FinancialAccount budget = this.DropBudgets.SelectedFinancialAccount; DateTime created = DateTime.Now; DateTime dueDate = (DateTime)this.DatePicker.SelectedDate; string items = (string)ViewState["Items"]; // If no line items, abort if (items.Length < 2) { Page.ClientScript.RegisterStartupScript(typeof(Page), "FailMessage", @"alert ('Add line items before sending the invoice.');", true); return; } // Create the invoice record OutboundInvoice newInvoice = OutboundInvoice.Create(Organization.FromIdentity(organizationId), _currentUser, dueDate, budget, customer, mailAddress, paperAddress, Currency.FromCode("SEK"), domestic, theirReference); // Add the invoice items string[] itemArray = items.Split('|'); for (int index = 0; index < itemArray.Length; index += 2) { double amount = Double.Parse(itemArray[index + 1], CultureInfo.InvariantCulture); newInvoice.AddItem(itemArray[index], amount); } // Create the financial transaction with rows FinancialTransaction transaction = FinancialTransaction.Create(organizationId, created, "Outbound Invoice #" + newInvoice.Identity + " to " + customer); transaction.AddRow(Organization.FromIdentity(organizationId).FinancialAccounts.AssetsOutboundInvoices, newInvoice.AmountCents, _currentUser); transaction.AddRow(budget, -newInvoice.AmountCents, _currentUser); // Make the transaction dependent on the outbound invoice transaction.Dependency = newInvoice; // Create the event for PirateBot-Mono to send off mails Activizr.Logic.Support.PWEvents.CreateEvent(EventSource.PirateWeb, EventType.OutboundInvoiceCreated, _currentUser.Identity, organizationId, 1, _currentUser.Identity, newInvoice.Identity, string.Empty); Page.ClientScript.RegisterStartupScript(typeof(Page), "OkMessage", @"alert ('The outbound invoice was registered and will be sent shortly.');", true); // Clear the text fields this.TextCustomer.Text = string.Empty; this.TextMailAddress.Text = string.Empty; this.TextPaperAddress.Text = string.Empty; this.TextTheirReference.Text = string.Empty; ViewState["Items"] = string.Empty; this.TextNewItemDescription.Text = string.Empty; this.TextNewItemAmount.Text = "0,00"; this.LiteralLeftItemSpacer.Text = " "; this.LiteralItems.Text = string.Empty; this.GridOutboundInvoices.Reload(); }
protected void ProcessImportedData(ImportResult import) { FinancialAccount assetAccount = FinancialAccount.FromIdentity(Int32.Parse(this.DropAssetAccount.SelectedValue)); FinancialAccount autoDepositAccount = FinancialAccount.FromIdentity(Int32.Parse(this.DropAutoDeposits.SelectedValue)); FinancialAccount autoWithdrawalAccount = FinancialAccount.FromIdentity(Int32.Parse(this.DropAutoWithdrawals.SelectedValue)); int autoDepositLimit = Int32.Parse(this.TextDepositLimit.Text); int autoWithdrawalLimit = Int32.Parse(this.TextWithdrawalLimit.Text); int organizationId = Int32.Parse(this.DropOrganizations.SelectedValue); Organization organization = Organization.FromIdentity(organizationId); int importedTransactionCount = 0; foreach (ImportedRow row in import.Rows) { // Each row is at least a stub, probably more. // If too old, ignore. if (row.DateTime < new DateTime(2008, 12, 4)) { continue; } string importKey = row.SuppliedTransactionId; // If importKey is empty, construct a hash from the data fields. if (string.IsNullOrEmpty(importKey)) { string hashKey = row.HashBase + row.Comment + (row.AmountCentsNet / 100.0).ToString(CultureInfo.InvariantCulture) + row.CurrentBalance.ToString(CultureInfo.InvariantCulture) + row.DateTime.ToString("yyyy-MM-dd-hh-mm-ss"); importKey = SHA1.Hash(hashKey).Replace(" ", ""); } if (importKey.Length > 30) { importKey = importKey.Substring(0, 30); } Int64 amountCents = row.AmountCentsNet; if (amountCents == 0) { amountCents = row.AmountCentsGross; } FinancialTransaction transaction = FinancialTransaction.ImportWithStub(organizationId, row.DateTime, assetAccount.Identity, amountCents, row.Comment, importKey, _currentUser.Identity); if (transaction != null) { // The transaction was created. Examine if the autobook criteria are true. importedTransactionCount++; FinancialAccounts accounts = FinancialAccounts.FromBankTransactionTag(row.Comment); if (accounts.Count == 1) { // This is a labelled local donation. Geography geography = accounts[0].AssignedGeography; FinancialAccount localAccount = accounts[0]; transaction.AddRow(organization.FinancialAccounts.IncomeDonations, -amountCents, _currentUser); transaction.AddRow(organization.FinancialAccounts.CostsLocalDonationTransfers, amountCents, _currentUser); transaction.AddRow(localAccount, -amountCents, _currentUser); Activizr.Logic.Support.PWEvents.CreateEvent(EventSource.PirateWeb, EventType.LocalDonationReceived, _currentUser.Identity, organizationId, geography.Identity, 0, transaction.Identity, localAccount.Identity.ToString()); } else if (row.Comment.ToLowerInvariant().StartsWith("bg 451-0061 ")) // TODO: Organization.Parameters.FinancialTrackedTransactionPrefix { // Check for previously imported payment group PaymentGroup group = PaymentGroup.FromTag(organization, "SEBGM" + DateTime.Today.Year.ToString() + // TODO: Get tagging from org row.Comment.Substring(11)); if (group != null) { // There was a previously imported and not yet closed payment group matching this transaction // Close the payment group and match the transaction against accounts receivable transaction.Dependency = group; group.Open = false; transaction.AddRow(organization.FinancialAccounts.AssetsOutboundInvoices, -amountCents, _currentUser); } } else if (amountCents < 0) { if ((-amountCents) < autoWithdrawalLimit * 100) { // Book against autoWithdrawal account. transaction.AddRow(autoWithdrawalAccount, -amountCents, _currentUser); } } else if (amountCents > 0) { if (row.Fee < 0) { // This is always an autodeposit, if there is a fee (which is never > 0.0) transaction.AddRow(organization.FinancialAccounts.CostsBankFees, -row.Fee, _currentUser); transaction.AddRow(autoDepositAccount, -row.AmountCentsGross, _currentUser); } else if (amountCents < autoDepositLimit * 100) { // Book against autoDeposit account. transaction.AddRow(autoDepositAccount, -amountCents, _currentUser); } } } } // Import complete. Examine if we expect more transactions -- if the imported balance differs from // the database balance: double databaseAccountBalance = assetAccount.BalanceTotal; bool mismatch = false; if (databaseAccountBalance != import.CurrentBalance) { mismatch = true; } string message = importedTransactionCount.ToString() + " transactions were imported."; if (importedTransactionCount == 0) { message = "No transactions were imported. "; } else if (importedTransactionCount == 1) { message = "One transaction was imported. "; } if (import.CurrentBalance > 0) { if (mismatch) { message += " Transactions are missing from the database. Import more transactions."; } else { message += " The account balance is up to date. No further import is necessary."; ScriptManager.RegisterStartupScript(this, Page.GetType(), "alldone", "alert ('The account balance is up to date. No further import is necessary.');", true); // Auto-match open payouts against new transactions Payouts.AutomatchAgainstUnbalancedTransactions(organization); } } this.LabelImportResultText.Text = message; }
protected void ButtonCreate_Click(object sender, EventArgs e) // TODO { // The data has been validated client-side already. We'll throw unfriendly exceptions if invalid data is passed here. // People who choose to disable JavaScript and then submit bad input almost deserve to be hurt. Int64 amountCents = this.CurrencyAmount.Cents; Int64 amountVatCents = this.CurrencyVat.Cents; string description = this.TextPurpose.Text; DateTime dueDate = DateTime.Parse(this.TextDueDate.Text); FinancialAccount budget = this.ComboBudgets.SelectedAccount; // sanity check if (budget.Organization.Identity != CurrentOrganization.Identity) { throw new InvalidOperationException("Budget-organization mismatch; won't file invoice"); } // Get documents; check that documents have been uploaded Documents documents = Documents.RecentFromDescription(this.FileUpload.GuidString); if (documents.Count == 0) { throw new InvalidOperationException("No documents uploaded"); } OutboundInvoice newInvoice = OutboundInvoice.Create(CurrentOrganization, dueDate, budget, this.TextClient.Text, string.Empty, string.Empty, CurrentOrganization.Currency, false, this.TextReference.Text, CurrentUser); newInvoice.AddItem(this.TextPurpose.Text, amountCents); // TODO: VAT -- needs to be PER ITEM, and dbfields must update for this, quite a large work item, do not short circuit hack this documents.SetForeignObjectForAll(newInvoice); // Create financial transaction in the ledger (this logic should not be in the presentation layer at all, move it to a better OutboundInvoice.Create that takes OutboundInvoiceItems as parameter) FinancialTransaction txOut = FinancialTransaction.Create(CurrentOrganization, DateTime.UtcNow, "Outbound Invoice #" + newInvoice.OrganizationSequenceId.ToString("N0")); txOut.AddRow(CurrentOrganization.FinancialAccounts.AssetsOutboundInvoices, amountCents, CurrentUser); if (amountVatCents > 0) { txOut.AddRow(CurrentOrganization.FinancialAccounts.DebtsVatOutboundUnreported, -amountVatCents, CurrentUser); txOut.AddRow(budget, -(amountCents - amountVatCents), CurrentUser); // Sales value } else { txOut.AddRow(budget, -amountCents, CurrentUser); } // Make the transaction dependent on the inbound invoice txOut.Dependency = newInvoice; // If invoice is denominated in a non-presentation currency, record the native values for proper payment if (this.CurrencyAmount.NonPresentationCurrencyUsed) { Money currencyEntered = this.CurrencyAmount.NonPresentationCurrencyAmount; newInvoice.NativeCurrencyAmount = currencyEntered; } // Display success message this._invoiceId = newInvoice.OrganizationSequenceId; // a property returns the localized string // Reset all fields for next invoice this.FileUpload.Reset(); this.TextClient.Text = String.Empty; this.TextPurpose.Text = String.Empty; this.TextReference.Text = String.Empty; this.CurrencyAmount.Cents = 0; this.CurrencyVat.Cents = 0; this.TextDueDate.Text = DateTime.Today.AddDays(30).ToShortDateString(); // Use current culture // the easyUI combo fields should reset automatically on form submission unless we explicitly reconstruct this.TextClient.Focus(); }
protected void Page_Load(object sender, EventArgs e) { FinancialAccount bankAccount = FinancialAccount.FromIdentity(29); Organization euroPirates = Organization.FromIdentity(7); FinancialTransactions unbalancedTransactions = FinancialTransactions.GetUnbalanced(euroPirates); // assume all in bank account foreach (FinancialTransaction transaction in unbalancedTransactions) { if (transaction.Description[0] == 'H' && Char.IsDigit(transaction.Description[1]) && transaction.Rows.BalanceCentsDelta > 0) { // outbound intl invoice. Add an untracked intl invoice 10 days prior, map it to this OutboundInvoice newInvoice = OutboundInvoice.Create(euroPirates, transaction.DateTime, euroPirates.FinancialAccounts.IncomeSales, "Untracked", "*****@*****.**", string.Empty, euroPirates.Currency, false, string.Empty, Person.FromIdentity(1)); newInvoice.AddItem("Untracked item", transaction.Rows.BalanceCentsDelta); // Doesn't close // Add transaction // Create the financial transaction with rows FinancialTransaction invoiceTransaction = FinancialTransaction.Create(euroPirates.Identity, transaction.DateTime.AddDays(-10), "Outbound Invoice #" + newInvoice.Identity); invoiceTransaction.AddRow(euroPirates.FinancialAccounts.AssetsOutboundInvoices, newInvoice.AmountCents, null); invoiceTransaction.AddRow(euroPirates.FinancialAccounts.IncomeSales, -newInvoice.AmountCents, null); invoiceTransaction.Dependency = newInvoice; transaction.AddRow(euroPirates.FinancialAccounts.AssetsOutboundInvoices, -newInvoice.AmountCents, null); Payment payment = Payment.CreateSingle(euroPirates, transaction.DateTime, euroPirates.Currency, newInvoice.AmountCents, newInvoice, null); transaction.Dependency = payment.Group; payment.Group.Open = false; } if ((transaction.Description == "Kostnad" || transaction.Description == "Bank charges") && transaction.Rows.BalanceCentsDelta < 0 && transaction.Rows.BalanceCentsDelta > -125000) { // Bank fee. transaction.AddRow(euroPirates.FinancialAccounts.CostsBankFees, -transaction.Rows.BalanceCentsDelta, null); } if (transaction.Description.StartsWith("Paypal ")) { // Bank fee. transaction.AddRow(euroPirates.FinancialAccounts.CostsBankFees, -transaction.Rows.BalanceCentsDelta, null); } } Payouts.AutomatchAgainstUnbalancedTransactions(euroPirates); }
public static void MatchTransactionOpenOutboundInvoice(int transactionId, int invoiceId) { if (transactionId == 0 || invoiceId == 0) { return; } AuthenticationData authData = GetAuthenticationDataAndCulture(); if ( !authData.Authority.HasAccess(new Access(authData.CurrentOrganization, AccessAspect.BookkeepingDetails))) { throw new UnauthorizedAccessException(); } FinancialTransaction transaction = FinancialTransaction.FromIdentity(transactionId); OutboundInvoice outboundInvoice = OutboundInvoice.FromIdentity(invoiceId); if (transaction.OrganizationId != authData.CurrentOrganization.Identity || outboundInvoice.OrganizationId != authData.CurrentOrganization.Identity) { throw new UnauthorizedAccessException(); } Int64 transactionCents = transaction.Rows.AmountCentsTotal; Int64 invoiceCents = outboundInvoice.AmountCents; FinancialAccount forexSpillAccount = authData.CurrentOrganization.FinancialAccounts.IncomeCurrencyFluctuations; if (forexSpillAccount == null && invoiceCents != transactionCents) { throw new InvalidOperationException("Need forex gain/loss accounts for this operation"); // TODO: Autocreate? } if (transaction.Rows.AmountCentsTotal < outboundInvoice.AmountCents) { // This is a forex loss, not a gain which is the default forexSpillAccount = authData.CurrentOrganization.FinancialAccounts.CostsCurrencyFluctuations; } // Close invoice Payment payment = Payment.CreateSingle(authData.CurrentOrganization, transaction.DateTime, authData.CurrentOrganization.Currency, outboundInvoice.AmountCents, outboundInvoice, authData.CurrentUser); // TODO: Notify? // TODO: Log? // Close transaction if (transactionCents != invoiceCents) { transaction.AddRow(forexSpillAccount, invoiceCents - transactionCents, authData.CurrentUser); // Adds the forex adjustment so we can bind payout to tx and close } transaction.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsOutboundInvoices, -outboundInvoice.AmountCents, authData.CurrentUser); }
public static ImportExternalTransactionDataResults ImportExternalTransactionData(ExternalBankData import, ImportExternalTransactionDataArgs args) { FinancialAccount assetAccount = args.Account; FinancialAccount autoDepositAccount = args.Organization.FinancialAccounts.IncomeDonations; int autoDepositLimit = 0; // Disabled; TODO: this.CurrentOrganization.Parameters.AutoDonationLimit; bool autosetInitialBalance = false; ImportExternalTransactionDataResults result = new ImportExternalTransactionDataResults(); int count = 0; int progressUpdateInterval = import.Records.Length / 40; Int64 importedCentsTotal = 0; if (progressUpdateInterval > 100) { progressUpdateInterval = 100; } ProgressBarBackend progressDisplay = new ProgressBarBackend(args.Guid); Currency organizationCurrency = assetAccount.Organization.Currency; Currency accountCurrency = assetAccount.ForeignCurrency; if (accountCurrency == null) { accountCurrency = organizationCurrency; } FinancialAccountRows existingRows = assetAccount.GetRows(Constants.DateTimeLow, Constants.DateTimeHigh); // gets all if (existingRows.Count == 0) { autosetInitialBalance = true; } foreach (ExternalBankDataRecord row in import.Records) { // Update progress. count++; if (progressUpdateInterval < 2 || count % progressUpdateInterval == 0) { int percent = (count * 99) / import.Records.Length; progressDisplay.Set(percent); } // Update high- and low-water marks. if (row.DateTime < result.EarliestTransaction) { result.EarliestTransaction = row.DateTime; } if (row.DateTime > result.LatestTransaction) { result.LatestTransaction = row.DateTime; } string importKey = row.ImportHash; Int64 amountCents = row.TransactionNetCents; if (amountCents == 0) // defensive programming - these _should_ be duplicated in the interpreter if no "fee" field { amountCents = row.TransactionGrossCents; } Int64 foreignCents = amountCents; importedCentsTotal += amountCents; if (accountCurrency.Identity != organizationCurrency.Identity) { amountCents = new Money(amountCents, accountCurrency, row.DateTime).ToCurrency(organizationCurrency).Cents; } FinancialTransaction transaction = FinancialTransaction.ImportWithStub(args.Organization.Identity, row.DateTime, assetAccount.Identity, amountCents, row.Description, importKey, Sha256.Compute(row.RawData), args.CurrentUser.Identity); if (transaction != null) { // The transaction was created. result.TransactionsImported++; // If non-presentation currency, log the account currency amount as well. if (accountCurrency.Identity != organizationCurrency.Identity) { transaction.Rows[0].AmountForeignCents = new Money(foreignCents, accountCurrency); } if (row.Description.ToLowerInvariant().StartsWith(args.Organization.IncomingPaymentTag)) { // Check for previously imported payment group // TODO: MAKE FLEXIBLE - CALL PAYMENTREADERINTERFACE! // HACK HACK HACK HACK PaymentGroup group = PaymentGroup.FromTag(args.Organization, "SEBGM" + DateTime.Today.Year + // TODO: Get tags from org row.Description.Substring(args.Organization.IncomingPaymentTag.Length).Trim()); if (group != null && group.Open) { // There was a previously imported and not yet closed payment group matching this transaction // Close the payment group and match the transaction against accounts receivable transaction.Dependency = group; group.Open = false; transaction.AddRow(args.Organization.FinancialAccounts.AssetsOutboundInvoices, -amountCents, args.CurrentUser); } } else if (amountCents < 0) { // Autowithdrawal mechanisms removed, condition kept because of downstream else-if conditions } else if (amountCents > 0) { if (row.FeeCents < 0) { // This is always an autodeposit, if there is a fee (which is never > 0.0) transaction.AddRow(args.Organization.FinancialAccounts.CostsBankFees, -row.FeeCents, args.CurrentUser); transaction.AddRow(autoDepositAccount, -row.TransactionGrossCents, args.CurrentUser); } else if (amountCents < autoDepositLimit * 100) { // Book against autoDeposit account. transaction.AddRow(autoDepositAccount, -amountCents, args.CurrentUser); } } } else { // Transaction was not imported; assume duplicate result.DuplicateTransactions++; } } // Import complete. Return true if the bookkeeping account matches the bank data. Int64 databaseAccountBalanceCents; if (accountCurrency.Identity == organizationCurrency.Identity) { databaseAccountBalanceCents = assetAccount.BalanceTotalCents; } else { // foreign-currency account databaseAccountBalanceCents = assetAccount.ForeignCurrencyBalance.Cents; } // Subtract any transactions made after the most recent imported transaction. // This is necessary in case of Paypal and others which continuously feed the // bookkeeping account with new transactions; it will already have fed transactions // beyond the end-of-file. Int64 beyondEofCents = assetAccount.GetDeltaCents(result.LatestTransaction.AddSeconds(1), DateTime.Now.AddDays(2)); // Caution: the "AddSeconds(1)" is not foolproof, there may be other new txs on the same second. if (databaseAccountBalanceCents - beyondEofCents == import.LatestAccountBalanceCents) { Payouts.AutomatchAgainstUnbalancedTransactions(args.Organization); OutboundInvoices.AutomatchAgainstUnbalancedTransactions(args.Organization, args.CurrentUser); result.AccountBalanceMatchesBank = true; result.BalanceMismatchCents = 0; } else { result.AccountBalanceMatchesBank = false; result.BalanceMismatchCents = (databaseAccountBalanceCents - beyondEofCents) - import.LatestAccountBalanceCents; if (autosetInitialBalance) { Int64 newInitialBalanceCents = -result.BalanceMismatchCents; Money initialBalance = new Money(newInitialBalanceCents, accountCurrency); assetAccount.InitialBalance = initialBalance; result.InitialBalanceCents = newInitialBalanceCents; result.InitialBalanceCurrencyCode = accountCurrency.Code; // make an approximation of conversion rate set for initial balance in presentation to tell user initialBalance.ValuationDateTime = new DateTime(assetAccount.Organization.FirstFiscalYear, 1, 1); result.BalanceMismatchCents = initialBalance.ToCurrency(assetAccount.Organization.Currency).Cents; } } result.CurrencyCode = args.Organization.Currency.Code; GuidCache.Set(args.Guid + "-Results", result); return(result); }
static public AjaxCallResult ProcessTransactionReceived(string guid, string txHash) { BitcoinChain chain = BitcoinChain.Cash; AuthenticationData authData = GetAuthenticationDataAndCulture(); // just to make sure we're called properly string bitcoinAddress = (string)GuidCache.Get(guid); if (BitcoinUtility.TestUnspents(chain, bitcoinAddress)) { HotBitcoinAddressUnspents unspents = HotBitcoinAddress.FromAddress(chain, bitcoinAddress).Unspents; // TODO: Update the HotBitcoinAddress with the new amount? HotBitcoinAddressUnspent unspent = null; Int64 satoshisReceived = 0; foreach (HotBitcoinAddressUnspent potentialUnspent in unspents) { if (potentialUnspent.TransactionHash == txHash) { satoshisReceived = potentialUnspent.AmountSatoshis; unspent = potentialUnspent; } } if (unspent == null) // Supplied transaction hash was not found in collection { Debugger.Break(); // TODO: Something else than break the debugger } Swarmops.Logic.Financial.Money moneyReceived = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.BitcoinCash); // Make sure that the hotwallet native currency is bitcoin cash authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot.ForeignCurrency = Currency.BitcoinCash; // Create success message and ledger transaction string successMessage = string.Empty; FinancialTransaction testTransaction = null; try { testTransaction = FinancialTransaction.FromBlockchainHash(authData.CurrentOrganization, txHash); // We've already seen this donation! Something is seriously bogus here Debugger.Break(); return(new AjaxCallResult() { DisplayMessage = successMessage, Success = true }); } catch (ArgumentException) { // This exception is expected - the transaction should not yet exist } if (authData.CurrentOrganization.Currency.IsBitcoinCash) { // The ledger is native bitcoin cash, so units are Satoshis FinancialTransaction ledgerTx = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, "Donation (bitcoin to hotwallet)"); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.IncomeDonations, -satoshisReceived, authData.CurrentUser); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, satoshisReceived, authData.CurrentUser); ledgerTx.BlockchainHash = txHash; if (satoshisReceived % 100 == 0) { successMessage = string.Format(Resources.Pages.Financial.Donate_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N0")); } else { successMessage = string.Format(Resources.Pages.Financial.Donate_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N2")); } } else { // The ledger is NOT native bitcoin, so we'll need to convert currencies long orgNativeCents = moneyReceived.ToCurrency(authData.CurrentOrganization.Currency).Cents; FinancialTransaction ledgerTx = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, "Donation (bitcoin to hotwallet)"); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.IncomeDonations, -orgNativeCents, authData.CurrentUser); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, orgNativeCents, authData.CurrentUser).AmountForeignCents = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.BitcoinCash); ledgerTx.BlockchainHash = txHash; successMessage = string.Format(Resources.Pages.Financial.Donate_FundsReceived, authData.CurrentOrganization.Currency.DisplayCode, orgNativeCents / 100.0, satoshisReceived / 100.0); } return(new AjaxCallResult() { DisplayMessage = successMessage, Success = true }); // TODO: Ack donation via mail? // TODO: Notify CFO/etc of donation? } return(new AjaxCallResult() { Success = false }); }
public void Attest(Person attester) { if (Attested) { return; } // If needed, create new account for parley FinancialAccount ourBudget; FinancialAccount parentBudget; int year = DateTime.Today.Year; if (base.BudgetId < 0) // no account created yet { ourBudget = FinancialAccount.Create(Budget.Organization, "Conf: " + Name, FinancialAccountType.Cost, Budget); parentBudget = Budget; base.BudgetId = ourBudget.Identity; ourBudget.Owner = Person; SwarmDb.GetDatabaseForWriting().SetParleyBudget(Identity, ourBudget.Identity); } else { // The budget has been created already - we should already be initialized. Verify this // by checking that we were already attested once. if (!AttestedOnce) { throw new InvalidOperationException( "Budget exists despite parley not having been attested. This should not be possible."); } ourBudget = Budget; parentBudget = ParentBudget; } ourBudget.SetBudgetCents(DateTime.Today.Year, -BudgetCents); parentBudget.SetBudgetCents(DateTime.Today.Year, parentBudget.GetBudgetCents(year) + BudgetCents); // cost budgets are negative // Reserve the guarantee money FinancialTransaction guaranteeFundsTx = FinancialTransaction.Create(OrganizationId, DateTime.Now, "Conference #" + Identity + " Guarantee"); guaranteeFundsTx.AddRow(Budget, -GuaranteeCents, attester); guaranteeFundsTx.AddRow(Budget.Parent, GuaranteeCents, attester); // Finally, set as attested PWEvents.CreateEvent( EventSource.PirateWeb, EventType.ParleyAttested, attester.Identity, OrganizationId, 0, 0, Identity, string.Empty); base.Attested = true; SwarmDb.GetDatabaseForWriting().SetParleyAttested(Identity, true); SwarmDb.GetDatabaseForWriting().CreateFinancialValidation(FinancialValidationType.Attestation, FinancialDependencyType.Parley, Identity, DateTime.Now, attester.Identity, (double)(GuaranteeDecimal)); }
static public AjaxCallResult ProcessTransactionReceived(string guid, string txHash) { AuthenticationData authData = GetAuthenticationDataAndCulture(); // just to make sure we're called properly BitcoinChain chain = BitcoinChain.Cash; string bitcoinAddress = (string)GuidCache.Get(guid); if (BitcoinUtility.TestUnspents(chain, bitcoinAddress)) { HotBitcoinAddressUnspents unspents = HotBitcoinAddress.FromAddress(chain, bitcoinAddress).Unspents; Int64 satoshisReceived = unspents.Last().AmountSatoshis; if (unspents.Last().TransactionHash != txHash && txHash.Length > 0) { // Race condition. Debugger.Break(); } HotBitcoinAddressUnspent utxoToReturn = unspents.Last(); Swarmops.Logic.Financial.Money moneyReceived = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.BitcoinCash); // Create success message and ledger transaction string successMessage = string.Empty; // TODO: Get the tx, get the input string returnAddress = BitcoinUtility.GetInputAddressesForTransaction(chain, txHash) [0]; // assumes at least one input address // Return the money, too. Set fee for a 300-byte transaction. ReturnBitcoinEchoUtxoOrder backendOrder = new ReturnBitcoinEchoUtxoOrder(utxoToReturn); backendOrder.Create(authData.CurrentOrganization, authData.CurrentUser); string tx1Description = "Bitcoin technical echo test (will be repaid immediately)"; if (authData.CurrentOrganization.Currency.IsBitcoinCash) { // The ledger is native bitcoin, so cent units are satoshis FinancialTransaction ledgerTx1 = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, tx1Description); ledgerTx1.AddRow(authData.CurrentOrganization.FinancialAccounts.DebtsOther, -(satoshisReceived), authData.CurrentUser); ledgerTx1.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, satoshisReceived, authData.CurrentUser); ledgerTx1.BlockchainHash = txHash; // The return payment will be logged when made, so its hash can be recorded if (satoshisReceived % 100 == 0) { successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N0")); } else { successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N2")); } } else { // The ledger is NOT native bitcoin, so we'll need to convert currencies long orgNativeCents = moneyReceived.ToCurrency(authData.CurrentOrganization.Currency).Cents; FinancialTransaction ledgerTx1 = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, tx1Description); ledgerTx1.AddRow(authData.CurrentOrganization.FinancialAccounts.DebtsOther, -orgNativeCents, authData.CurrentUser); ledgerTx1.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, orgNativeCents, authData.CurrentUser).AmountForeignCents = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.BitcoinCash); ledgerTx1.BlockchainHash = txHash; // The second transaction is logged when executed in the back-end order successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceived, authData.CurrentOrganization.Currency.DisplayCode, orgNativeCents / 100.0, satoshisReceived / 100.0); } return(new AjaxCallResult() { DisplayMessage = successMessage, Success = true }); // TODO: Ack donation via mail? // TODO: Notify CFO/etc of donation? } return(new AjaxCallResult() { Success = false }); }
static public AjaxCallResult CheckTransactionReceived(string guid, string txHash) { AuthenticationData authData = GetAuthenticationDataAndCulture(); // just to make sure we're called properly string bitcoinAddress = (string)GuidCache.Get(guid); if (BitcoinUtility.TestUnspents(bitcoinAddress)) { HotBitcoinAddressUnspents unspents = HotBitcoinAddress.FromAddress(bitcoinAddress).Unspents; // TODO: Update the HotBitcoinAddress with the new amount? Int64 satoshisReceived = unspents.Last().AmountSatoshis; if (unspents.Last().TransactionHash != txHash && txHash.Length > 0) { // Race condition. Debugger.Break(); } Swarmops.Logic.Financial.Money moneyReceived = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.Bitcoin); // Make sure that the hotwallet native currency is bitcoin authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot.ForeignCurrency = Currency.Bitcoin; // Create success message and ledger transaction string successMessage = string.Empty; // TODO: Get the tx, get the input string returnAddress = BitcoinUtility.GetInputAddressesForTransaction(txHash) [0]; // assumes at least one input address if (authData.CurrentOrganization.Currency.IsBitcoin) { // The ledger is native bitcoin, so units are Satoshis FinancialTransaction ledgerTx = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, "Bitcoin echo test (will be repaid immediately)"); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.DebtsOther, -satoshisReceived, authData.CurrentUser); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, satoshisReceived, authData.CurrentUser); ledgerTx.BlockchainHash = txHash; if (satoshisReceived % 100 == 0) { successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N0")); } else { successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceivedNative, (satoshisReceived / 100.0).ToString("N2")); } // TODO: Log the payout back, as an inbound invoice for immediate payout } else { // The ledger is NOT native bitcoin, so we'll need to convert currencies long orgNativeCents = moneyReceived.ToCurrency(authData.CurrentOrganization.Currency).Cents; FinancialTransaction ledgerTx = FinancialTransaction.Create(authData.CurrentOrganization, DateTime.UtcNow, "Bitcoin echo test (will be repaid immediately)"); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.DebtsOther, -orgNativeCents, authData.CurrentUser); ledgerTx.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsBitcoinHot, orgNativeCents, authData.CurrentUser).AmountForeignCents = new Swarmops.Logic.Financial.Money(satoshisReceived, Currency.Bitcoin); ledgerTx.BlockchainHash = txHash; successMessage = string.Format(Resources.Pages.Admin.BitcoinEchoTest_FundsReceived, authData.CurrentOrganization.Currency.DisplayCode, orgNativeCents / 100.0, satoshisReceived / 100.0); // TODO: Create a payout back for this amount -- needs to be specified in bitcoin -- as an inbound invoice } return(new AjaxCallResult() { DisplayMessage = successMessage, Success = true }); // TODO: Ack donation via mail? // TODO: Notify CFO/etc of donation? } return(new AjaxCallResult() { Success = false }); }
public static AjaxCallResult MarkDirectPurchase(int transactionId, int budgetId, string vatAmountString, string newDescription, string guid) { if (transactionId == 0 | budgetId == 0) { return(new AjaxCallResult { Success = false }); } AuthenticationData authData = GetAuthenticationDataAndCulture(); if ( !authData.Authority.HasAccess(new Access(authData.CurrentOrganization, AccessAspect.BookkeepingDetails))) { throw new UnauthorizedAccessException(); } FinancialAccount budget = FinancialAccount.FromIdentity(budgetId); if (authData.CurrentOrganization.Identity != budget.Organization.Identity) { throw new UnauthorizedAccessException(); } newDescription = newDescription.Trim(); if (newDescription.Length < 1) { return(new AjaxCallResult { Success = false, DisplayMessage = Resources.Pages.Ledgers.BalanceTransactions_Error_NeedDescription }); } Documents docs = Documents.RecentFromDescription(guid).WhereNotAssociated; if (docs.Count < 1) { return(new AjaxCallResult { Success = false, DisplayMessage = Resources.Pages.Ledgers.BalanceTransactions_Error_NeedDocumentation }); } Int64 vatCents = 0; bool vatEnabled = authData.CurrentOrganization.VatEnabled; if (vatEnabled) { try { vatCents = Swarmops.Logic.Support.Formatting.ParseDoubleStringAsCents(vatAmountString); } catch (ArgumentException) { return(new AjaxCallResult { Success = false, DisplayMessage = Resources.Pages.Ledgers.BalanceTransactions_Error_VatAmountParseError }); throw; } } // We're FINALLY ready to update the transaction FinancialTransaction tx = FinancialTransaction.FromIdentity(transactionId); tx.Description = newDescription; docs.SetForeignObjectForAll(tx); Int64 centsDiff = tx.Rows.AmountCentsTotal; if (vatEnabled) { tx.AddRow(authData.CurrentOrganization.FinancialAccounts.AssetsVatInboundUnreported, vatCents, authData.CurrentUser); tx.AddRow(budget, (-centsDiff) - vatCents, authData.CurrentUser); } else { tx.AddRow(budget, -centsDiff, authData.CurrentUser); } return(new AjaxCallResult { Success = true }); }
public static ResyncResults ExecuteResync(string guid) { AuthenticationData authenticationData = GetAuthenticationDataAndCulture(); if ( !authenticationData.Authority.HasAccess(new Access(authenticationData.CurrentOrganization, AccessAspect.Bookkeeping, AccessType.Write))) { throw new UnauthorizedAccessException(); } ResyncResults results = new ResyncResults(); ExternalBankMismatchingDateTime[] mismatchArray = (ExternalBankMismatchingDateTime[]) HttpContext.Current.Session["LedgersResync" + guid + "MismatchArray"]; FinancialAccount account = (FinancialAccount)HttpContext.Current.Session["LedgersResync" + guid + "Account"]; long autoDepositDonationCents = 1000 * 100; FinancialAccount autoDonationAccount = account.Organization.FinancialAccounts.IncomeDonations; if (authenticationData.CurrentOrganization.Identity != account.OrganizationId) { throw new InvalidOperationException("Mismatching org"); } foreach (ExternalBankMismatchingDateTime mismatchDateTime in mismatchArray) { foreach ( ExternalBankMismatchingRecordDescription mismatchingRecord in mismatchDateTime.MismatchingRecords) { for (int index = 0; index < mismatchingRecord.MasterCents.Length; index++) { results.RecordsTotal++; long cents = mismatchingRecord.MasterCents[index]; bool unhandled = false; bool handlable = true; FinancialTransaction tx = mismatchingRecord.Transactions[index]; if (tx != null && tx.Dependency != null) { unhandled = true; IHasIdentity dependency = tx.Dependency; if (dependency is PaymentGroup && mismatchingRecord.ResyncActions[index] == ExternalBankMismatchResyncAction.RewriteSwarmops) { if (cents == (dependency as PaymentGroup).SumCents) { // Amount checks out with dependency; rewrite requested; this is handlable on auto Dictionary <int, long> newTx = new Dictionary <int, long>(); newTx[account.Identity] = cents; newTx[account.Organization.FinancialAccounts.AssetsOutboundInvoices.Identity] = -cents; tx.RecalculateTransaction(newTx, authenticationData.CurrentUser); unhandled = false; } } handlable = false; // need to fix this } if (handlable) { switch (mismatchingRecord.ResyncActions[index]) { case ExternalBankMismatchResyncAction.DeleteSwarmops: if (tx == null) { throw new InvalidOperationException( "Can't have Delete op on a null transaction"); } tx.Description = tx.Description + " (killed/zeroed in resync)"; tx.RecalculateTransaction(new Dictionary <int, long>(), authenticationData.CurrentUser); // zeroes out break; case ExternalBankMismatchResyncAction.RewriteSwarmops: if (tx == null) { throw new InvalidOperationException( "Can't have Rewrite op on a null transaction"); } Dictionary <int, long> newTx = new Dictionary <int, long>(); newTx[account.Identity] = cents; if (cents > 0 && cents < autoDepositDonationCents) { newTx[autoDonationAccount.Identity] = -cents; // negative; P&L account } tx.RecalculateTransaction(newTx, authenticationData.CurrentUser); break; case ExternalBankMismatchResyncAction.CreateSwarmops: if (tx != null) { throw new InvalidOperationException("Transaction seems to already exist"); } tx = FinancialTransaction.Create(account.OwnerPersonId, mismatchDateTime.DateTime, mismatchingRecord.Description); tx.AddRow(account, cents, authenticationData.CurrentUser); if (cents > 0 && cents < autoDepositDonationCents) { tx.AddRow(autoDonationAccount, -cents, authenticationData.CurrentUser); } break; case ExternalBankMismatchResyncAction.NoAction: // no action break; default: // not handled unhandled = true; break; } } if (unhandled) { results.RecordsFail++; } else { results.RecordsSuccess++; } } } } return(results); }
protected void Page_Load(object sender, EventArgs e) { this.PageAccessRequired = new Access(this.CurrentOrganization, AccessAspect.Bookkeeping, AccessType.Write); this.PageTitle = "Close Ledgers"; this.PageIcon = "iconshock-calculator-lock"; // Check if on a closable year if (this.CurrentOrganization.Parameters.EconomyEnabled == false || this.CurrentOrganization.Parameters.FiscalBooksClosedUntilYear == DateTime.Today.Year - 1) { this.PanelCannotClose.Visible = true; this.PanelSuccess.Visible = false; this.LabelCannotCloseLedgersReason.Text = "Ledgers are already closed as far as possible. [LOC]"; return; // a return out of Page_Load is kind of unusual, see it as a "break" or "abort" } // Check if all transactions are balanced, so we can close FinancialTransactions unbalancedTransactions = FinancialTransactions.GetUnbalanced(this.CurrentOrganization); // TODO: this fn should move to Organization int closingYear = this.CurrentOrganization.Parameters.FiscalBooksClosedUntilYear + 1; bool hasOpenTxForClosingYear = false; foreach (FinancialTransaction unbalancedTransaction in unbalancedTransactions) { if (unbalancedTransaction.DateTime.Year <= closingYear) { hasOpenTxForClosingYear = true; } } if (hasOpenTxForClosingYear) { this.PanelCannotClose.Visible = true; this.PanelSuccess.Visible = false; return; // a return out of Page_Load is kind of unusual, see it as a "break" or "abort" } // Start actually closing the ledgers // First, roll over virtual balances. //if (false) // if this.CurrentOrganization.Parameters.VirtualBankingEnabled //{ // FinancialAccount rootAccount = FinancialAccount.FromIdentity(29); // HACK: Hardcoded account; should be _organization.FinancialAccount.CostsVirtualBankingRoot // FinancialAccount tempAccount = FinancialAccount.FromIdentity(98); // HACK: Hardcoded account; should be _organization.FinancialAccount.AssetsVirtualRollover // FinancialAccounts localAccounts = rootAccount.GetTree(); // foreach (FinancialAccount account in localAccounts) // { // Int64 currentBalanceCents = account.GetDeltaCents(new DateTime(closingYear, 1, 1), new DateTime(closingYear+1, 1, 1)); // Int64 budgetCents = -account.GetBudgetCents(closingYear); // Int64 carryOverCents = budgetCents - currentBalanceCents; // if (carryOverCents != 0) // { // FinancialTransaction transactionOldYear = FinancialTransaction.Create(1, // new DateTime(closingYear, 12, // 31, 23, 50, // 00), // "Budgetrest " + account.Name); // // HACK: Localize rollover label // transactionOldYear.AddRow(account, carryOverCents, null); // transactionOldYear.AddRow(tempAccount, -carryOverCents, null); // FinancialTransaction transactionNewYear = FinancialTransaction.Create(1, // new DateTime(closingYear + 1, // 1, 1, 0, 10, 0), // "Budgetrest " + // closingYear.ToString() + " " + // account.Name); // transactionNewYear.AddRow(account, -carryOverCents, null); // transactionNewYear.AddRow(tempAccount, carryOverCents, null); // } // } //} // Then, actually close the ledgers. FinancialAccounts accounts = FinancialAccounts.ForOrganization(this.CurrentOrganization); Int64 balanceDeltaCents = 0; Int64 resultsDeltaCents = 0; foreach (FinancialAccount account in accounts) { Int64 accountBalanceCents; if (account.AccountType == FinancialAccountType.Asset || account.AccountType == FinancialAccountType.Debt) { accountBalanceCents = account.GetDeltaCents(new DateTime(2006, 1, 1), new DateTime(closingYear + 1, 1, 1)); balanceDeltaCents += accountBalanceCents; } else { accountBalanceCents = account.GetDeltaCents(new DateTime(closingYear, 1, 1), new DateTime(closingYear + 1, 1, 1)); resultsDeltaCents += accountBalanceCents; } } if (balanceDeltaCents == -resultsDeltaCents && closingYear < DateTime.Today.Year) { FinancialTransaction resultTransaction = FinancialTransaction.Create(this.CurrentOrganization.Identity, new DateTime(closingYear, 12, 31, 23, 59, 00), "Årets resultat " + closingYear.ToString()); // TODO: Localize string resultTransaction.AddRow(this.CurrentOrganization.FinancialAccounts.CostsYearlyResult, -resultsDeltaCents, null); resultTransaction.AddRow(this.CurrentOrganization.FinancialAccounts.DebtsEquity, -balanceDeltaCents, null); // Ledgers are now at zero-sum for the year's result accounts and from the start up until end-of-closing-year for the balance accounts. Organization.PPSE.Parameters.FiscalBooksClosedUntilYear = closingYear; } else { Console.WriteLine("NOT creating transaction."); } }