public static ExpenseClaim Create(Person claimer, Organization organization, FinancialAccount budget, DateTime expenseDate, string description, Int64 amountCents) { ExpenseClaim newClaim = FromIdentityAggressive(SwarmDb.GetDatabaseForWriting().CreateExpenseClaim(claimer.Identity, organization.Identity, budget.Identity, expenseDate, description, amountCents)); // Create the financial transaction with rows string transactionDescription = "Expense #" + newClaim.Identity + ": " + description; // TODO: Localize if (transactionDescription.Length > 64) { transactionDescription = transactionDescription.Substring(0, 61) + "..."; } FinancialTransaction transaction = FinancialTransaction.Create(organization.Identity, DateTime.Now, transactionDescription); transaction.AddRow(organization.FinancialAccounts.DebtsExpenseClaims, -amountCents, claimer); transaction.AddRow(budget, amountCents, claimer); // Make the transaction dependent on the expense claim transaction.Dependency = newClaim; // Create notifications OutboundComm.CreateNotificationAttestationNeeded(budget, claimer, string.Empty, (double)amountCents / 100.0, description, NotificationResource.ExpenseClaim_Created); // Slightly misplaced logic, but failsafer here OutboundComm.CreateNotificationFinancialValidationNeeded(organization, (double)amountCents / 100.0, NotificationResource.Receipts_Filed); SwarmopsLogEntry.Create(claimer, new ExpenseClaimFiledLogEntry(claimer /*filing person*/, claimer /*beneficiary*/, (double)amountCents / 100.0, budget, description), newClaim); return(newClaim); }
public static InboundInvoice Create (Organization organization, DateTime dueDate, Int64 amountCents, FinancialAccount budget, string supplier, string description, string payToAccount, string ocr, string invoiceReference, Person creatingPerson) { InboundInvoice newInvoice = FromIdentity(SwarmDb.GetDatabaseForWriting(). CreateInboundInvoice(organization.Identity, dueDate, budget.Identity, supplier, payToAccount, ocr, invoiceReference, amountCents, creatingPerson.Identity)); newInvoice.Description = description; // Not in original schema; not cause for schema update // Create a corresponding financial transaction with rows FinancialTransaction transaction = FinancialTransaction.Create(organization.Identity, DateTime.Now, "Invoice #" + newInvoice.Identity + " from " + supplier); transaction.AddRow(organization.FinancialAccounts.DebtsInboundInvoices, -amountCents, creatingPerson); transaction.AddRow(budget, amountCents, creatingPerson); // Make the transaction dependent on the inbound invoice transaction.Dependency = newInvoice; // Create notification (slightly misplaced logic, but this is failsafest place) OutboundComm.CreateNotificationAttestationNeeded(budget, creatingPerson, supplier, (double)amountCents / 100.0, description, NotificationResource.InboundInvoice_Created); // Slightly misplaced logic, but failsafer here SwarmopsLogEntry.Create(creatingPerson, new InboundInvoiceCreatedLogEntry(creatingPerson, supplier, description, (double)amountCents / 100.0, budget), newInvoice); return newInvoice; }
private void AddRow(int financialAccountId, Int64 amountCents, int personId) { if (this.DateTime.Year <= FinancialAccount.FromIdentity(financialAccountId).Organization.Parameters.FiscalBooksClosedUntilYear) { // Recurse down into continuation transactions to write row in first nonclosed year FinancialTransaction transactionContinued = this.ContinuedTransaction; if (transactionContinued == null) { // No continuation; create one transactionContinued = FinancialTransaction.Create(this.OrganizationId, DateTime.Now, "Continued Tx #" + this.Identity.ToString()); transactionContinued.AddRow(financialAccountId, amountCents, personId); transactionContinued.Dependency = this; } else { // Recurse transactionContinued.AddRow(financialAccountId, amountCents, personId); } return; } SwarmDb.GetDatabaseForWriting().CreateFinancialTransactionRow(Identity, financialAccountId, amountCents, personId); }
private static FinancialAccountRows RowsNotInVatReport(FinancialAccount account, DateTime endDateTime) { Organization organization = account.Organization; SwarmDb dbRead = SwarmDb.GetDatabaseForReading(); FinancialAccountRows rowsIntermediate = FinancialAccountRows.FromArray( SwarmDb.GetDatabaseForReading().GetAccountRowsNotInVatReport(account.Identity, endDateTime)); FinancialAccountRows rowsFinal = new FinancialAccountRows(); foreach (FinancialAccountRow row in rowsIntermediate) { // Check if this row _closes_ an _existing_ VAT report, in which case it should _not_ be included int vatReportOpenId = dbRead.GetVatReportIdFromCloseTransaction(row.FinancialTransactionId); int vatReportCloseId = dbRead.GetVatReportIdFromOpenTransaction(row.FinancialTransactionId); if (vatReportOpenId == 0 && vatReportCloseId == 0) { // This particular transaction doesn't close an existing VAT report rowsFinal.Add(row); } } return(rowsFinal); }
public static InboundInvoice Create(Organization organization, DateTime dueDate, Int64 amountCents, FinancialAccount budget, string supplier, string description, string payToAccount, string ocr, string invoiceReference, Person creatingPerson) { InboundInvoice newInvoice = FromIdentity(SwarmDb.GetDatabaseForWriting(). CreateInboundInvoice(organization.Identity, dueDate, budget.Identity, supplier, payToAccount, ocr, invoiceReference, amountCents, creatingPerson.Identity)); newInvoice.Description = description; // Not in original schema; not cause for schema update // Create a corresponding financial transaction with rows FinancialTransaction transaction = FinancialTransaction.Create(organization.Identity, DateTime.Now, "Invoice #" + newInvoice.Identity + " from " + supplier); transaction.AddRow(organization.FinancialAccounts.DebtsInboundInvoices, -amountCents, creatingPerson); transaction.AddRow(budget, amountCents, creatingPerson); // Make the transaction dependent on the inbound invoice transaction.Dependency = newInvoice; // Create notification (slightly misplaced logic, but this is failsafest place) OutboundComm.CreateNotificationAttestationNeeded(budget, creatingPerson, supplier, amountCents / 100.0, description, NotificationResource.InboundInvoice_Created); // Slightly misplaced logic, but failsafer here SwarmopsLogEntry.Create(creatingPerson, new InboundInvoiceCreatedLogEntry(creatingPerson, supplier, description, amountCents / 100.0, budget), newInvoice); return(newInvoice); }
public static ExpenseClaim Create(Person claimer, Organization organization, FinancialAccount budget, DateTime expenseDate, string description, Int64 amountCents) { ExpenseClaim newClaim = FromIdentityAggressive (SwarmDb.GetDatabaseForWriting().CreateExpenseClaim (claimer.Identity, organization.Identity, budget.Identity, expenseDate, description, amountCents)); // Create the financial transaction with rows string transactionDescription = "Expense #" + newClaim.Identity + ": " + description; // TODO: Localize if (transactionDescription.Length > 64) { transactionDescription = transactionDescription.Substring(0, 61) + "..."; } FinancialTransaction transaction = FinancialTransaction.Create(organization.Identity, DateTime.Now, transactionDescription); transaction.AddRow(organization.FinancialAccounts.DebtsExpenseClaims, -amountCents, claimer); transaction.AddRow(budget, amountCents, claimer); // Make the transaction dependent on the expense claim transaction.Dependency = newClaim; // Create notifications OutboundComm.CreateNotificationAttestationNeeded(budget, claimer, string.Empty, (double)amountCents / 100.0, description, NotificationResource.ExpenseClaim_Created); // Slightly misplaced logic, but failsafer here OutboundComm.CreateNotificationFinancialValidationNeeded(organization, (double) amountCents/100.0, NotificationResource.Receipts_Filed); SwarmopsLogEntry.Create(claimer, new ExpenseClaimFiledLogEntry(claimer /*filing person*/, claimer /*beneficiary*/, (double) amountCents/100.0, budget, description), newClaim); return newClaim; }
private FinancialTransactionRow AddRow(int financialAccountId, Int64 amountCents, int personId) { // private function that actually executes the row adding FinancialAccount account = FinancialAccount.FromIdentity(financialAccountId); if (DateTime.Year <= account.Organization.Parameters.FiscalBooksClosedUntilYear) { // Recurse down into continuation transactions to write row in first nonclosed year FinancialTransactionRow newRow = null; FinancialTransaction transactionContinued = ContinuedTransaction; if (transactionContinued == null) { // No continuation; create one transactionContinued = Create(OrganizationId, DateTime.Now, "Continued Tx #" + Identity); newRow = transactionContinued.AddRow(financialAccountId, amountCents, personId); transactionContinued.Dependency = this; } else { // Recurse newRow = transactionContinued.AddRow(financialAccountId, amountCents, personId); } return(newRow); } FinancialTransactionRow addedRow = FinancialTransactionRow.FromIdentityAggressive(SwarmDb.GetDatabaseForWriting() .CreateFinancialTransactionRow(Identity, financialAccountId, amountCents, personId)); // If we're running from web, and this was a P&L account, then also notify the server that the P&L has changed // (doing this here means that the server can get pinged multiple times, but that's more defensive coding than // having to remember doing it everywhere at the UI level) if (account.AccountType == FinancialAccountType.Income || account.AccountType == FinancialAccountType.Cost) { if (SupportFunctions.OperatingTopology == OperatingTopology.FrontendWeb) { SocketMessage newMessage = new SocketMessage { MessageType = "ProfitLossChanged", OrganizationId = account.Organization.Identity, FinancialTransactionId = this.Identity }; newMessage.SendUpstream(); } } return(addedRow); }
public void BindToTransactionAndClose(FinancialTransaction transaction, Person bindingPerson) { Dictionary <int, Int64> accountDebitLookup = new Dictionary <int, Int64>(); Organization organization = Organization; accountDebitLookup[organization.FinancialAccounts.DebtsExpenseClaims.Identity] = 0; accountDebitLookup[organization.FinancialAccounts.DebtsInboundInvoices.Identity] = 0; accountDebitLookup[organization.FinancialAccounts.DebtsSalary.Identity] = 0; accountDebitLookup[organization.FinancialAccounts.DebtsTax.Identity] = 0; accountDebitLookup[organization.FinancialAccounts.AssetsOutstandingCashAdvances.Identity] = 0; if (this.DependentExpenseClaims.Count > 0) { accountDebitLookup[organization.FinancialAccounts.DebtsExpenseClaims.Identity] += this.DependentExpenseClaims.TotalAmountCents; } if (this.DependentInvoices.Count > 0) { accountDebitLookup[organization.FinancialAccounts.DebtsInboundInvoices.Identity] += this.DependentInvoices.TotalAmountCents; } if (this.DependentSalariesNet.Count > 0) { accountDebitLookup[organization.FinancialAccounts.DebtsSalary.Identity] += this.DependentSalariesNet.TotalAmountCentsNet; } if (this.DependentSalariesTax.Count > 0) { accountDebitLookup[organization.FinancialAccounts.DebtsTax.Identity] += this.DependentSalariesTax.TotalAmountCentsTax; } if (this.DependentCashAdvancesPayout.Count > 0) { accountDebitLookup[organization.FinancialAccounts.AssetsOutstandingCashAdvances.Identity] += this.DependentCashAdvancesPayout.TotalAmountCents; } if (this.DependentCashAdvancesPayback.Count > 0) { accountDebitLookup[organization.FinancialAccounts.AssetsOutstandingCashAdvances.Identity] -= // observe the minus this.DependentCashAdvancesPayback.TotalAmountCents; } foreach (int financialAccountId in accountDebitLookup.Keys) { if (accountDebitLookup[financialAccountId] != 0) { transaction.AddRow(FinancialAccount.FromIdentity(financialAccountId), accountDebitLookup[financialAccountId], bindingPerson); } } transaction.Dependency = this; Open = false; }
public static FinancialAccountDocument Create(FinancialAccount account, FinancialAccountDocumentType documentType, Person uploader, DateTime concernsStart, DateTime concernsEnd, string rawText) { return(FromIdentityAggressive( SwarmDb.GetDatabaseForWriting().CreateFinancialAccountDocument( account.Identity, documentType, uploader == null? 0: uploader.Identity, concernsStart, concernsEnd, rawText ))); }
public static Parley Create (Organization organization, Person person, FinancialAccount budgetInitial, string name, Geography geography, string description, string informationUrl, DateTime startDate, DateTime endDate, Int64 budgetCents, Int64 guaranteeCents, Int64 attendanceFeeCents) { Parley newParley = FromIdentity(SwarmDb.GetDatabaseForWriting().CreateParley(organization.Identity, person.Identity, -(budgetInitial.Identity), name, geography.Identity, description, informationUrl, startDate, endDate, budgetCents, guaranteeCents, attendanceFeeCents)); PWEvents.CreateEvent(EventSource.PirateWeb, EventType.ParleyCreated, person.Identity, organization.Identity, 0, 0, newParley.Identity, string.Empty); return newParley; }
public static OutboundInvoice Create (Organization organization, Person createdByPerson, DateTime dueDate, FinancialAccount budget, string customerName, string invoiceAddressMail, string invoiceAddressPaper, Currency currency, bool domestic, string theirReference) { OutboundInvoice invoice = FromIdentity (SwarmDb.GetDatabaseForWriting().CreateOutboundInvoice(organization.Identity, createdByPerson != null? createdByPerson.Identity : 0, dueDate, budget.Identity, customerName, invoiceAddressPaper, invoiceAddressMail, currency.Identity, string.Empty, domestic, Authentication.CreateRandomPassword(6), theirReference)); // Set reference invoice.Reference = Formatting.AddLuhnChecksum(Formatting.ReverseString(DateTime.Now.ToString("yyyyMMddHHmm")) + invoice.Identity.ToString()); return invoice; }
public ExpenseClaimFiledLogEntry(Person filingPerson, Person beneficiaryPerson, double amount, FinancialAccount budget, string reason) { this.Amount = amount; this.Currency = budget.Organization.Currency.Code; this.DateTime = DateTime.UtcNow; this.Description = reason; this.OrganizationId = budget.OrganizationId; this.FinancialAccountId = budget.Identity; this.FinancialAccountName = budget.Name; // redundancy in case of future name changes this.OwnerPersonId = budget.OwnerPersonId; this.OwnerPersonName = budget.OwnerPersonId != 0 ? budget.Owner.Name : string.Empty; this.ActingPersonId = filingPerson.Identity; // do not save name for data retention reasons this.BeneficiaryPersonId = beneficiaryPerson.Identity; }
public static CashAdvance Create(Organization organization, Person forPerson, Person createdByPerson, Int64 amountCents, FinancialAccount budget, string description) { CashAdvance newAdvance = FromIdentityAggressive(SwarmDb.GetDatabaseForWriting().CreateCashAdvance(forPerson.Identity, createdByPerson.Identity, organization.Identity, budget.Identity, amountCents, description)); OutboundComm.CreateNotificationAttestationNeeded(budget, forPerson, string.Empty, (double) amountCents/100.0, description, NotificationResource.CashAdvance_Requested); // Slightly misplaced logic, but failsafer here SwarmopsLogEntry.Create(forPerson, new CashAdvanceRequestedLogEntry(createdByPerson, forPerson, (double) amountCents/100.0, budget, description), newAdvance); return newAdvance; }
public InboundInvoiceCreatedLogEntry(Person creatingPerson, string supplier, string description, double amount, FinancialAccount budget) { this.Amount = amount; this.Currency = budget.Organization.Currency.Code; this.DateTime = DateTime.UtcNow; this.Description = description; this.OrganizationId = budget.OrganizationId; this.FinancialAccountId = budget.Identity; this.FinancialAccountName = budget.Name; // redundancy in case of future name changes this.OwnerPersonId = budget.OwnerPersonId; this.OwnerPersonName = budget.OwnerPersonId != 0 ? budget.Owner.Name : string.Empty; this.ActingPersonId = creatingPerson.Identity; // do not save name for data retention reasons this.BeneficiaryPersonId = 0; this.Supplier = supplier; }
public static void CheckColdStorageForOrganization(Organization organization) { FinancialAccount coldRoot = organization.FinancialAccounts.AssetsBitcoinCold; if (coldRoot == null) { return; // no cold assets to check } // Construct the address-to-account map only once for the entire recursion chain Dictionary <string, int> addressAccountLookup = GetAddressAccountLookup(organization); // Recurse CheckColdStorageRecurse(coldRoot, addressAccountLookup); }
private static void GetAddressAccountLookupRecurse(FinancialAccount account, Dictionary <string, int> result) { if (account == null) { return; } foreach (FinancialAccount child in account.Children) { GetAddressAccountLookupRecurse(child, result); } if (!string.IsNullOrEmpty(account.BitcoinAddress) || IsValidBitcoinAddress(account.Name)) // TODO: Add a special property for the address instead of using name { result [account.Name] = account.Identity; } }
public Int64 this [FinancialAccount account] { get { Int64 result = 0; FinancialTransactionRows rows = Rows; foreach (FinancialTransactionRow row in rows) { if (row.FinancialAccountId == account.Identity) { result += row.AmountCents; } } return(result); } }
public static CashAdvance Create(Organization organization, Person forPerson, Person createdByPerson, Int64 amountCents, FinancialAccount budget, string description) { CashAdvance newAdvance = FromIdentityAggressive(SwarmDb.GetDatabaseForWriting().CreateCashAdvance(forPerson.Identity, createdByPerson.Identity, organization.Identity, budget.Identity, amountCents, description)); OutboundComm.CreateNotificationAttestationNeeded(budget, forPerson, string.Empty, amountCents / 100.0, description, NotificationResource.CashAdvance_Requested); // Slightly misplaced logic, but failsafer here SwarmopsLogEntry.Create(forPerson, new CashAdvanceRequestedLogEntry(createdByPerson, forPerson, amountCents / 100.0, budget, description), newAdvance); return(newAdvance); }
public FinancialAccount this[OrganizationFinancialAccountType accountType] { get { int accountId = SwarmDb.GetDatabaseForReading().GetOrganizationFinancialAccountId(_organizationId, accountType); if (accountId == 0) { return(null); // not set } return(FinancialAccount.FromIdentity(accountId)); } set { SwarmDb.GetDatabaseForWriting().SetOrganizationFinancialAccountId(_organizationId, accountType, value.Identity); } }
public static OutboundInvoice Create(Organization organization, Person createdByPerson, DateTime dueDate, FinancialAccount budget, string customerName, string invoiceAddressMail, string invoiceAddressPaper, Currency currency, bool domestic, string theirReference) { OutboundInvoice invoice = FromIdentity(SwarmDb.GetDatabaseForWriting() .CreateOutboundInvoice(organization.Identity, createdByPerson != null ? createdByPerson.Identity : 0, dueDate, budget.Identity, customerName, invoiceAddressPaper, invoiceAddressMail, currency.Identity, string.Empty, domestic, Authentication.CreateRandomPassword(6), theirReference)); // Set reference invoice.Reference = Formatting.AddLuhnChecksum(Formatting.ReverseString(DateTime.Now.ToString("yyyyMMddHHmm")) + invoice.Identity); return(invoice); }
public static FinancialAccountDocuments ForAccount(FinancialAccount account, FinancialAccountDocumentType typeRequested = FinancialAccountDocumentType.Unknown) { FinancialAccountDocuments result = FromArray(SwarmDb.GetDatabaseForReading().GetFinancialAccountDocuments(account)); result.Sort(new FinancialAccountDocumentsSorterByTypeThenDate()); if (typeRequested != FinancialAccountDocumentType.Unknown) { FinancialAccountDocuments subset = new FinancialAccountDocuments(); foreach (FinancialAccountDocument document in result) { if (document.Type == typeRequested) { subset.Add(document); } } return(subset); } return(result); }
public void AddRow(FinancialAccount account, Int64 amountCents, Person person) { AddRow(account.Identity, amountCents, person != null ? person.Identity : 0); }
public static OutboundComm CreateNotificationAttestationNeeded(FinancialAccount budget, Person concernedPerson, string supplier, double amountRequested, string purpose, NotificationResource notification) { NotificationPayload payload = new NotificationPayload(notification.ToString()); payload.Strings[NotificationString.CurrencyCode] = budget.Organization.Currency.Code; payload.Strings[NotificationString.OrganizationName] = budget.Organization.Name; payload.Strings[NotificationString.BudgetAmountFloat] = amountRequested.ToString(CultureInfo.InvariantCulture); payload.Strings[NotificationString.RequestPurpose] = purpose; payload.Strings[NotificationString.Description] = purpose; payload.Strings[NotificationString.Supplier] = supplier; payload.Strings[NotificationString.BudgetName] = budget.Name; payload.Strings[NotificationString.ConcernedPersonName] = concernedPerson.Canonical; OutboundComm comm = OutboundComm.Create(null, null, budget.Organization, CommResolverClass.Unknown, null, CommTransmitterClass.CommsTransmitterMail, new PayloadEnvelope(payload).ToXml(), OutboundCommPriority.Low); if (budget.OwnerPersonId == 0) { comm.AddRecipient(Person.FromIdentity(1)); // Add admin - not good, should be org admins // HACK // TODO } else { comm.AddRecipient(budget.Owner); } comm.Resolved = true; return comm; }
public void AddRow (FinancialAccount account, double amount, Person person) { AddRow (account, (Int64) (amount * 100), person); }
public static PayrollItem Create(Person person, Organization organization, DateTime employedDate, Person reportsToPerson, Country country, Int64 baseSalaryCents, FinancialAccount budget, double additiveTaxLevel, int subtractiveTaxLevelId, bool isContractor) { int payrollItemId = SwarmDb.GetDatabaseForWriting() .CreatePayrollItem(person.Identity, organization.Identity, employedDate, reportsToPerson.Identity, country.Identity, baseSalaryCents, budget.Identity, additiveTaxLevel, subtractiveTaxLevelId, isContractor); return(FromIdentityAggressive(payrollItemId)); }
public void SetBudget(FinancialAccount budget, Person settingPerson) { base.BudgetId = budget.Identity; SwarmDb.GetDatabaseForWriting().SetInboundInvoiceBudget(Identity, budget.Identity); UpdateTransaction(settingPerson); }
public void AddRow (FinancialAccount account, Int64 amountCents, Person person) { AddRow(account.Identity, amountCents, person != null ? person.Identity : 0); }
public Int64 this[FinancialAccount account] { get { Int64 result = 0; FinancialTransactionRows rows = this.Rows; foreach (FinancialTransactionRow row in rows) { if (row.FinancialAccountId == account.Identity) { result += row.AmountCents; } } return result; } }
public void SetBudget(FinancialAccount budget, Person settingPerson) { SwarmDb.GetDatabaseForWriting().SetExpenseClaimBudget(this.Identity, budget.Identity); base.BudgetId = budget.Identity; UpdateFinancialTransaction(settingPerson); }
public void SetBudget (FinancialAccount budget, Person settingPerson) { SwarmDb.GetDatabaseForWriting().SetExpenseClaimBudget (this.Identity, budget.Identity); base.BudgetId = budget.Identity; UpdateFinancialTransaction(settingPerson); }
public static ExpenseClaim Create(Person claimer, Organization organization, FinancialAccount budget, DateTime expenseDate, string description, Int64 amountCents, Int64 vatCents, ExpenseClaimGroup group = null) { ExpenseClaim newClaim = FromIdentityAggressive(SwarmDb.GetDatabaseForWriting() .CreateExpenseClaim(claimer.Identity, organization?.Identity ?? 0, budget?.Identity ?? 0, expenseDate, description, amountCents)); // budget can be 0 initially if created with a group if (vatCents > 0) { newClaim.VatCents = vatCents; } if (group != null) { newClaim.Group = group; } if (budget != null && organization != null) { // Create the financial transaction with rows string transactionDescription = "Expense #" + newClaim.OrganizationSequenceId + ": " + description; // TODO: Localize if (transactionDescription.Length > 64) { transactionDescription = transactionDescription.Substring(0, 61) + "..."; } DateTime expenseTxDate = expenseDate; int ledgersClosedUntil = organization.Parameters.FiscalBooksClosedUntilYear; if (ledgersClosedUntil >= expenseDate.Year) { expenseTxDate = DateTime.UtcNow; // If ledgers are closed for the actual expense time, account now } FinancialTransaction transaction = FinancialTransaction.Create(organization.Identity, expenseTxDate, transactionDescription); transaction.AddRow(organization.FinancialAccounts.DebtsExpenseClaims, -amountCents, claimer); if (vatCents > 0) { transaction.AddRow(budget, amountCents - vatCents, claimer); transaction.AddRow(organization.FinancialAccounts.AssetsVatInboundUnreported, vatCents, claimer); } else { transaction.AddRow(budget, amountCents, claimer); } // Make the transaction dependent on the expense claim transaction.Dependency = newClaim; // Create notifications OutboundComm.CreateNotificationAttestationNeeded(budget, claimer, string.Empty, newClaim.BudgetAmountCents / 100.0, description, NotificationResource.ExpenseClaim_Created); // Slightly misplaced logic, but failsafer here OutboundComm.CreateNotificationFinancialValidationNeeded(organization, newClaim.AmountCents / 100.0, NotificationResource.Receipts_Filed); SwarmopsLogEntry.Create(claimer, new ExpenseClaimFiledLogEntry(claimer /*filing person*/, claimer /*beneficiary*/, newClaim.BudgetAmountCents / 100.0, vatCents / 100.0, budget, description), newClaim); // Clear a cache FinancialAccount.ClearAttestationAdjustmentsCache(organization); } return(newClaim); }
public void AddRow(FinancialAccount account, double amount, Person person) { AddRow(account, (Int64)(amount * 100), person); }
public static AnnualReport Create(Organization organization, int year, FinancialAccountType accountType) { AnnualReport report = new AnnualReport(); report.Organization = organization; report.Year = year; report._accountType = accountType; // Get accounts FinancialAccounts accounts = FinancialAccounts.ForOrganization(organization, accountType); // Remove unwanted accounts FinancialAccount resultsAccount = organization.FinancialAccounts.CostsYearlyResult; foreach (FinancialAccount account in accounts) { // For now, just remove the "results" account. TODO: Remove inactive accounts, too. if (account.Identity == resultsAccount.Identity) { accounts.Remove(account); break; } } // Build tree (there should be a template for this) report._treeMap = new Dictionary <int, List <FinancialAccount> >(); foreach (FinancialAccount account in accounts) { if (!report._treeMap.ContainsKey(account.ParentIdentity)) { report._treeMap[account.ParentIdentity] = new List <FinancialAccount>(); } report._treeMap[account.ParentIdentity].Add(account); } FinancialAccounts orderedList = new FinancialAccounts(); // This list is guaranteed to have parents before children report.PopulateOrderedList(orderedList, 0); // recursively add nodes parents-first report.PopulateLookups(orderedList); // populate the lookup tables for results per account report.PopulateTotals(); report.ReportLines = new List <AnnualReportLine>(); report.RecurseAddLines(report.ReportLines, 0); // Aggregate accounts, if appropriate if (report._treeMap[0].Count > 3) { // regroup list report.AggregateAccounts(); } return(report); }
private static void CheckColdStorageRecurse(FinancialAccount parent, Dictionary <string, int> addressAccountLookup) { foreach (FinancialAccount child in parent.Children) { CheckColdStorageRecurse(child, addressAccountLookup); } // After recursing, get all transactions for this account and verify against our records // is the account name a valid bitcoin address on the main network? string address = parent.BitcoinAddress; if (string.IsNullOrEmpty(address)) { if (BitcoinUtility.IsValidBitcoinAddress(parent.Name.Trim())) { parent.BitcoinAddress = address = parent.Name.Trim(); } else { return; // not a bitcoin address but something else; do not process } } Organization organization = parent.Organization; bool organizationLedgerUsesBitcoin = organization.Currency.IsBitcoinCore; JObject addressData = JObject.Parse( new WebClient().DownloadString("https://blockchain.info/address/" + address + "?format=json&api_key=" + SystemSettings.BlockchainSwarmopsApiKey)); //int transactionCount = (int) (addressData["n_tx"]); foreach (JObject txJson in addressData["txs"]) { FinancialTransaction ourTx = null; Dictionary <Int64, Int64> satoshisLookup = new Dictionary <long, long>(); // map from ledgercents to satoshis BlockchainTransaction blockchainTx = BlockchainTransaction.FromBlockchainInfoJson(txJson); try { ourTx = FinancialTransaction.FromBlockchainHash(parent.Organization, blockchainTx.TransactionHash); // If the transaction was fetched fine, we have already seen this transaction, but need to re-check it } catch (ArgumentException) { // We didn't have this transaction, so we need to create it ourTx = FinancialTransaction.Create(parent.Organization, blockchainTx.TransactionDateTimeUtc, "Blockchain tx"); ourTx.BlockchainHash = blockchainTx.TransactionHash; // Did we lose or gain money? // Find all in- and outpoints, determine which are ours (hot and cold wallet) and which aren't } Dictionary <int, long> transactionReconstructedRows = new Dictionary <int, long>(); // Note the non-blockchain rows in this tx, keep them for reconstruction foreach (FinancialTransactionRow row in ourTx.Rows) { if (!addressAccountLookup.ContainsValue(row.FinancialAccountId)) // not optimal but n is small { // This is not a bitcoin address account, so note it for reconstruction if (!transactionReconstructedRows.ContainsKey(row.FinancialAccountId)) { transactionReconstructedRows[row.FinancialAccountId] = 0; // init } transactionReconstructedRows[row.FinancialAccountId] += row.AmountCents; } else { // this is a known blockchain row, note its ledgered value in satoshis if (!organizationLedgerUsesBitcoin) { Money nativeMoney = row.AmountForeignCents; if (nativeMoney != null && nativeMoney.Currency.IsBitcoinCore) // it damn well should be, but just checking { satoshisLookup[row.AmountCents] = row.AmountForeignCents.Cents; } } } } // Reconstruct the blockchain rows: input, output, fees, in that order // -- inputs foreach (BlockchainTransactionRow inputRow in blockchainTx.Inputs) { if (addressAccountLookup.ContainsKey(inputRow.Address)) { // this input is ours int financialAccountId = addressAccountLookup[inputRow.Address]; if (!transactionReconstructedRows.ContainsKey(financialAccountId)) { transactionReconstructedRows[financialAccountId] = 0; // initialize } if (organizationLedgerUsesBitcoin) { transactionReconstructedRows[financialAccountId] += -inputRow.ValueSatoshis; // note the negation! } else { Int64 ledgerCents = new Money(inputRow.ValueSatoshis, Currency.BitcoinCore, ourTx.DateTime).ToCurrency( organization.Currency).Cents; transactionReconstructedRows[financialAccountId] += -ledgerCents; // note the negation! satoshisLookup[ledgerCents] = inputRow.ValueSatoshis; } } } // -- outputs foreach (BlockchainTransactionRow outputRow in blockchainTx.Outputs) { if (addressAccountLookup.ContainsKey(outputRow.Address)) { // this output is ours int financialAccountId = addressAccountLookup[outputRow.Address]; if (!transactionReconstructedRows.ContainsKey(financialAccountId)) { transactionReconstructedRows[financialAccountId] = 0; // initialize } if (organizationLedgerUsesBitcoin) { transactionReconstructedRows[financialAccountId] += outputRow.ValueSatoshis; } else { Int64 ledgerCents = new Money(outputRow.ValueSatoshis, Currency.BitcoinCore, ourTx.DateTime).ToCurrency( organization.Currency).Cents; transactionReconstructedRows[financialAccountId] += ledgerCents; satoshisLookup[ledgerCents] = outputRow.ValueSatoshis; } } } // -- fees if (addressAccountLookup.ContainsKey(blockchainTx.Inputs[0].Address)) { // if the first input is ours, we're paying the fee (is there any case where this is not true?) if (organizationLedgerUsesBitcoin) { transactionReconstructedRows[organization.FinancialAccounts.CostsBitcoinFees.Identity] = blockchainTx.FeeSatoshis; } else { Int64 feeLedgerCents = new Money(blockchainTx.FeeSatoshis, Currency.BitcoinCore, blockchainTx.TransactionDateTimeUtc).ToCurrency(organization.Currency).Cents; transactionReconstructedRows[organization.FinancialAccounts.CostsBitcoinFees.Identity] = feeLedgerCents; } } // Rewrite the transaction (called always, but the function won't do anything if everything matches) ourTx.RecalculateTransaction(transactionReconstructedRows, /* loggingPerson*/ null); // Finally, add foreign cents, if any if (!organizationLedgerUsesBitcoin) { foreach (FinancialTransactionRow row in ourTx.Rows) { if (addressAccountLookup.ContainsValue(row.Account.Identity)) // "ContainsValue" is bad, but n is low { // Do we have a foreign amount for this row already? Money foreignMoney = row.AmountForeignCents; if (foreignMoney == null || foreignMoney.Cents == 0) { // no we didn't; create one if (satoshisLookup.ContainsKey(row.AmountCents)) { row.AmountForeignCents = new Money(satoshisLookup[row.AmountCents], Currency.BitcoinCore, ourTx.DateTime); } else if (satoshisLookup.ContainsKey(-row.AmountCents)) // the negative counterpart { row.AmountForeignCents = new Money(-satoshisLookup[-row.AmountCents], Currency.BitcoinCore, ourTx.DateTime); } else { // There's a last case which may happen if the row is an addition to a previous row; if so, calculate row.AmountForeignCents = new Money(row.AmountCents, organization.Currency, ourTx.DateTime).ToCurrency(Currency.BitcoinCore); } } } } } } }
public static Salary Create(PayrollItem payrollItem, DateTime payoutDate) { // Load the existing adjustments. PayrollAdjustments adjustments = PayrollAdjustments.ForPayrollItem(payrollItem); Int64 payCents = payrollItem.BaseSalaryCents; // Apply all before-tax adjustments foreach (PayrollAdjustment adjustment in adjustments) { if (adjustment.Type == PayrollAdjustmentType.GrossAdjustment) { payCents += adjustment.AmountCents; } } Int64 subtractiveTaxCents = 0; Int64 additiveTaxCents = 0; if (!payrollItem.IsContractor) { // calculate tax Money grossInOrgCurrency = new Money { Cents = payCents, Currency = payrollItem.Organization.Currency, ValuationDateTime = DateTime.UtcNow }; Money grossInTaxCurrency = grossInOrgCurrency.ToCurrency(payrollItem.Country.Currency); Money subtractiveTax = TaxLevels.GetTax(payrollItem.Country, payrollItem.SubtractiveTaxLevelId, grossInTaxCurrency); Money subtractiveTaxInOrgCurrency = subtractiveTax.ToCurrency(payrollItem.Organization.Currency); subtractiveTaxCents = (Int64)(subtractiveTaxInOrgCurrency.Cents); additiveTaxCents = (Int64)(payCents * payrollItem.AdditiveTaxLevel); payCents -= subtractiveTaxCents; } // Apply all after-tax adjustments foreach (PayrollAdjustment adjustment in adjustments) { if (adjustment.Type == PayrollAdjustmentType.NetAdjustment) { payCents += adjustment.AmountCents; } } // If net is negative, create rollover adjustment PayrollAdjustment rolloverAdjustment = null; if (payCents < 0) { rolloverAdjustment = PayrollAdjustment.Create(payrollItem, PayrollAdjustmentType.NetAdjustment, -payCents, "Deficit rolls over to next salary"); PayrollAdjustment.Create(payrollItem, PayrollAdjustmentType.NetAdjustment, payCents, "Deficit rolled over from " + payoutDate.ToString("yyyy-MM-dd")); // keep second rollover open, so the deficit from this salary is carried to the next payCents = 0; } // Create salary, close adjustments Salary salary = Create(payrollItem, payoutDate, payCents, subtractiveTaxCents, additiveTaxCents); // For each adjustment, close and bind to salary foreach (PayrollAdjustment adjustment in adjustments) { adjustment.Close(salary); } if (rolloverAdjustment != null) { rolloverAdjustment.Close(salary); } // Add the financial transaction FinancialTransaction transaction = FinancialTransaction.Create(payrollItem.OrganizationId, DateTime.Now, "Salary #" + salary.Identity + ": " + payrollItem.PersonCanonical + " " + salary.PayoutDate.ToString("yyyy-MMM", CultureInfo.InvariantCulture)); transaction.AddRow(payrollItem.Budget, salary.CostTotalCents, null); transaction.AddRow(payrollItem.Organization.FinancialAccounts.DebtsSalary, -salary.NetSalaryCents, null); if (salary.TaxTotalCents != 0) { transaction.AddRow(payrollItem.Organization.FinancialAccounts.DebtsTax, -salary.TaxTotalCents, null); } transaction.Dependency = salary; // Finally, check if net and/or tax are zero, and if so, mark them as already-paid (i.e. not due for payment) if (salary.NetSalaryCents == 0) { salary.NetPaid = true; } if (salary.TaxTotalCents == 0) { salary.TaxPaid = true; } // Clear a cache FinancialAccount.ClearAttestationAdjustmentsCache(payrollItem.Organization); return(salary); }
public void SetBudget (FinancialAccount budget, Person settingPerson) { base.BudgetId = budget.Identity; SwarmDb.GetDatabaseForWriting().SetInboundInvoiceBudget(this.Identity, budget.Identity); UpdateTransaction(settingPerson); }
public static VatReport Create(Organization organization, int year, int startMonth, int monthCount) { VatReport newReport = CreateDbRecord(organization, year, startMonth, monthCount); DateTime endDate = new DateTime(year, startMonth, 1).AddMonths(monthCount); FinancialAccount vatInbound = organization.FinancialAccounts.AssetsVatInboundUnreported; FinancialAccount vatOutbound = organization.FinancialAccounts.DebtsVatOutboundUnreported; FinancialAccount sales = organization.FinancialAccounts.IncomeSales; FinancialAccountRows inboundRows = RowsNotInVatReport(vatInbound, endDate); FinancialAccountRows outboundRows = RowsNotInVatReport(vatOutbound, endDate); FinancialAccountRows turnoverRows = RowsNotInVatReport(sales, endDate); Dictionary <int, bool> transactionsIncludedLookup = new Dictionary <int, bool>(); newReport.AddVatReportItemsFromAccountRows(inboundRows, transactionsIncludedLookup); newReport.AddVatReportItemsFromAccountRows(outboundRows, transactionsIncludedLookup); newReport.AddVatReportItemsFromAccountRows(turnoverRows, transactionsIncludedLookup); newReport.Release(); // Create financial TX that moves this VAT from unreported to reported Int64 differenceCents = newReport.VatInboundCents - newReport.VatOutboundCents; if (differenceCents != 0 && newReport.VatInboundCents > 0) { // if there's anything to report FinancialTransaction vatReportTransaction = FinancialTransaction.Create(organization, endDate.AddDays(4).AddHours(9), newReport.Description); if (newReport.VatInboundCents > 0) { vatReportTransaction.AddRow(organization.FinancialAccounts.AssetsVatInboundUnreported, -newReport.VatInboundCents, null); } if (newReport.VatOutboundCents > 0) { vatReportTransaction.AddRow(organization.FinancialAccounts.DebtsVatOutboundUnreported, newReport.VatOutboundCents, null); // not negative, because our number is sign-different from the bookkeeping's } if (differenceCents < 0) // outbound > inbound { vatReportTransaction.AddRow(organization.FinancialAccounts.DebtsVatOutboundReported, differenceCents, null); // debt, so negative as in our variable } else // inbound > outbound { vatReportTransaction.AddRow(organization.FinancialAccounts.AssetsVatInboundReported, differenceCents, null); // asset, so positive as in our variable } vatReportTransaction.Dependency = newReport; newReport.OpenTransaction = vatReportTransaction; } else { newReport.Open = false; // nothing to close, no tx created } return(newReport); }
public void SetBudget(FinancialAccount newBudget, Person settingPerson) { this.Budget = newBudget; // ignore settingPerson }
public static OutboundComm CreateNotificationOfFinancialValidation(FinancialAccount budget, Person concernedPerson, double amountRequested, string purpose, NotificationResource notification) { NotificationPayload payload = new NotificationPayload(notification.ToString()); payload.Strings[NotificationString.CurrencyCode] = budget.Organization.Currency.Code; payload.Strings[NotificationString.OrganizationName] = budget.Organization.Name; payload.Strings[NotificationString.BudgetAmountFloat] = amountRequested.ToString(CultureInfo.InvariantCulture); payload.Strings[NotificationString.RequestPurpose] = purpose; payload.Strings[NotificationString.BudgetName] = budget.Name; payload.Strings[NotificationString.ConcernedPersonName] = concernedPerson.Canonical; OutboundComm comm = OutboundComm.Create(null, null, budget.Organization, CommResolverClass.Unknown, null, CommTransmitterClass.CommsTransmitterMail, new PayloadEnvelope(payload).ToXml(), OutboundCommPriority.Low); comm.AddRecipient(concernedPerson); comm.Resolved = true; return comm; }
public static FinancialAccount Create(Organization organization, string newAccountName, FinancialAccountType accountType, FinancialAccount parentAccount) { return(Create(organization.Identity, newAccountName, accountType, parentAccount == null ? 0 : parentAccount.Identity)); }
public FinancialTransactionRow AddRow(FinancialAccount account, Int64 amountCents, Person person) { return(AddRow(account.Identity, amountCents, person != null ? person.Identity : 0)); }