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); }
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(); } }
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); }
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 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) { 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); }
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 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 }); }
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(); }
public static ChangeAccountDataResult SetAccountInitialBalance(int accountId, string newInitialBalanceString) { try { AuthenticationData authData = GetAuthenticationDataAndCulture(); FinancialAccount account = FinancialAccount.FromIdentity(accountId); if (!PrepareAccountChange(account, authData, false) || authData.CurrentOrganization.Parameters.FiscalBooksClosedUntilYear >= authData.CurrentOrganization.FirstFiscalYear) { return(new ChangeAccountDataResult { Result = ChangeAccountDataOperationsResult.NoPermission }); } Int64 desiredInitialBalanceCents = (Int64) (Double.Parse(newInitialBalanceString, NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.CurrentCulture) * 100.0); Int64 currentInitialBalanceCents = account.GetDeltaCents(new DateTime(1900, 1, 1), new DateTime(authData.CurrentOrganization.FirstFiscalYear, 1, 1)); Int64 deltaCents = desiredInitialBalanceCents - currentInitialBalanceCents; // Find or create "Initial Balances" transaction FinancialAccountRows testRows = FinancialAccountRows.ForOrganization(authData.CurrentOrganization, new DateTime(1900, 1, 1), new DateTime(authData.CurrentOrganization.FirstFiscalYear, 1, 1)); FinancialTransaction initialBalancesTransaction = null; foreach (FinancialAccountRow row in testRows) { if (row.Transaction.Description == "Initial Balances") { initialBalancesTransaction = row.Transaction; break; } } if (initialBalancesTransaction == null) { // create transaction initialBalancesTransaction = FinancialTransaction.Create(authData.CurrentOrganization.Identity, new DateTime(authData.CurrentOrganization.FirstFiscalYear - 1, 12, 31), "Initial Balances"); } Dictionary <int, Int64> recalcBase = initialBalancesTransaction.GetRecalculationBase(); int equityAccountId = authData.CurrentOrganization.FinancialAccounts.DebtsEquity.Identity; if (!recalcBase.ContainsKey(accountId)) { recalcBase[accountId] = 0; } if (!recalcBase.ContainsKey(equityAccountId)) { recalcBase[equityAccountId] = 0; } recalcBase[accountId] += deltaCents; recalcBase[equityAccountId] -= deltaCents; initialBalancesTransaction.RecalculateTransaction(recalcBase, authData.CurrentUser); return(new ChangeAccountDataResult { Result = ChangeAccountDataOperationsResult.Changed, NewData = (desiredInitialBalanceCents / 100.0).ToString("N2", CultureInfo.CurrentCulture) }); } catch (Exception weirdException) { SwarmDb.GetDatabaseForWriting() .CreateExceptionLogEntry(DateTime.UtcNow, "AccountPlan-SetInitBalance", weirdException); throw; } }
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."); } }
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 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)); }
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(); }