Ejemplo n.º 1
0
        public void Close (Salary salary)
        {
            if (!Open)
            {
                throw new InvalidOperationException("Payroll adjustment #" + this.Identity.ToString() + " cannot be closed; is already closed");
            }

            SwarmDb.GetDatabaseForWriting().ClosePayrollAdjustment(this.Identity, salary.Identity);
            base.Open = false;
            base.SalaryId = salary.Identity;
        }
Ejemplo n.º 2
0
        /// <summary>
        ///     Processes all payroll for all organizations system-wide. Should run on the 1st of every month.
        /// </summary>
        public static void ProcessMonthly()
        {
            DateTime today = DateTime.UtcNow;

            string lastRun = Persistence.Key["LastSalaryRun"];

            string expectedLastRun = today.ToString("yyyyMM", CultureInfo.InvariantCulture);

            if (lastRun != null &&
                String.Compare(lastRun, expectedLastRun, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase) >= 0)
            {
                // nothing to do, return

                return;
            }

            Persistence.Key["LastSalaryRun"] = expectedLastRun;

            // Process the payroll for all organizations. Assume payday is 25th.
            // TODO: Different payday per organization?

            Payroll  payroll = GetAll();
            DateTime payday  = new DateTime(today.Year, today.Month, 25);

            foreach (PayrollItem payrollItem in payroll)
            {
                Salary salary = Salary.Create(payrollItem, payday);
                SwarmopsLog.CreateEntry(payrollItem.Person, new SalaryCreatedLogEntry(salary));

                // TODO: CREATE SALARY SPECIFICATION, SEND TO PERSON
            }

            // If this is January, also send the annual statements for last year

            if (today.Month == 1)
            {
                Salaries.CreateAnnualStatements(today.Year - 1);
            }
        }
Ejemplo n.º 3
0
        private static void AddUnpaidSalaries(Payouts payoutList, Organization organization)
        {
            Int64 taxTotalCents = 0;

            Salaries   salaries     = Salaries.ForOrganization(organization);
            List <int> identityList = new List <int>();
            DateTime   payDay       = Constants.DateTimeHigh;

            foreach (Salary salary in salaries)
            {
                if (!salary.Attested)
                {
                    continue;
                }

                if (!salary.NetPaid)
                {
                    PayrollItem payrollItem = salary.PayrollItem;
                    Person      employee    = payrollItem.Person;

                    BasicPayout basicPayout = new BasicPayout(0, organization.Identity, employee.BankName,
                                                              employee.BankClearing + " / " + employee.BankAccount,
                                                              "[Loc]Financial_SalarySpecification|[Date]" +
                                                              salary.PayoutDate.ToString(CultureInfo.InvariantCulture),
                                                              salary.NetSalaryCents, salary.PayoutDate, false, DateTime.Now, 0);
                    Payout payout = Payout.FromBasic(basicPayout);
                    payout.RecipientPerson = employee;

                    payout.DependentSalariesNet.Add(salary);

                    payoutList.Add(payout);

                    if (payDay > salary.PayoutDate)
                    {
                        payDay = salary.PayoutDate;
                    }
                }

                if (!salary.TaxPaid)
                {
                    taxTotalCents += salary.TaxTotalCents;
                    identityList.Add(salary.Identity);

                    if (payDay > salary.PayoutDate)
                    {
                        payDay = salary.PayoutDate;
                    }
                }
            }

            if (taxTotalCents > 0)
            {
                // Add the summarized tax line, too

                BasicPayout basicPayout = new BasicPayout(0, organization.Identity, "[Loc]Financial_TheTaxMan", "SEBG 5050-1055",  // HACK: Get tax account from something
                                                          organization.TaxPaymentOcr, taxTotalCents, payDay, false, DateTime.Now, 0);
                Payout payout = Payout.FromBasic(basicPayout);

                foreach (int salaryId in identityList)
                {
                    payout.DependentSalariesTax.Add(Salary.FromIdentity(salaryId));
                }

                payoutList.Add(payout);
            }
        }
Ejemplo n.º 4
0
        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;
                }
            }

            // calculate tax

            double subtractiveTax = TaxLevels.GetTax(payrollItem.Country, payrollItem.SubtractiveTaxLevelId,
                                                     payCents / 100.0);

            if (subtractiveTax < 1.0)
            {
                // this is a percentage and not an absolute number

                subtractiveTax = payCents * subtractiveTax;
            }
            Int64 subtractiveTaxCents = (Int64)(subtractiveTax * 100);

            Int64 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;
                }
            }

            // 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 net is negative, create rollover adjustment

            if (payCents < 0)
            {
                PayrollAdjustment rollover1 = PayrollAdjustment.Create(payrollItem, PayrollAdjustmentType.NetAdjustment,
                                                                       -payCents,
                                                                       "Deficit rolls over to next salary");

                rollover1.Close(salary);

                PayrollAdjustment rollover2 = PayrollAdjustment.Create(payrollItem, PayrollAdjustmentType.NetAdjustment,
                                                                       payCents, "Deficit rolled over from " +
                                                                       payoutDate.ToString("yyyy-MM-dd"));

                // keep rollover2 open, so the deficit from this salary is carried to the next

                salary.NetSalaryCents = 0;
            }

            // 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);
            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;
            }

            return(salary);
        }
Ejemplo n.º 5
0
        private static void AddUnpaidSalaries(Payouts payoutList, int organizationId)
        {
            Int64 taxTotalCents = 0;

            Salaries   salaries     = Salaries.ForOrganization(Organization.FromIdentity(organizationId));
            List <int> identityList = new List <int>();
            DateTime   payDay       = DateTime.MaxValue;

            foreach (Salary salary in salaries)
            {
                if (!salary.Attested)
                {
                    continue;
                }

                if (!salary.NetPaid)
                {
                    PayrollItem payrollItem = salary.PayrollItem;
                    Person      employee    = payrollItem.Person;

                    BasicPayout basicPayout = new BasicPayout(0, organizationId, employee.BankName,
                                                              employee.BankClearing + " / " + employee.BankAccount, "[Loc]Financial_SalarySpecification|[Date]" + salary.PayoutDate.ToString(CultureInfo.InvariantCulture),
                                                              salary.NetSalaryCents, salary.PayoutDate, false, DateTime.Now, 0);
                    Payout payout = Payout.FromBasic(basicPayout);

                    payout.DependentSalariesNet.Add(salary);

                    payoutList.Add(payout);

                    if (payDay > salary.PayoutDate)
                    {
                        payDay = salary.PayoutDate;
                    }
                }

                if (!salary.TaxPaid)
                {
                    taxTotalCents += salary.TaxTotalCents;
                    identityList.Add(salary.Identity);

                    if (payDay > salary.PayoutDate)
                    {
                        payDay = salary.PayoutDate;
                    }
                }
            }

            if (taxTotalCents > 0)
            {
                // Add the summarized tax line, too

                string referenceString = string.Empty;

                if (identityList.Count == 1)
                {
                    referenceString = "[Loc]Financial_TaxSpecification|" + identityList[0].ToString();
                }
                else
                {
                    identityList.Sort();
                    referenceString = "[Loc]Financial_TaxesSpecification|" + Formatting.GenerateRangeString(identityList);
                }


                BasicPayout basicPayout = new BasicPayout(0, organizationId, "[Loc]Financial_TheTaxMan",
                                                          string.Empty, referenceString,
                                                          taxTotalCents, payDay, false, DateTime.Now, 0);
                Payout payout = Payout.FromBasic(basicPayout);

                foreach (int salaryId in identityList)
                {
                    payout.DependentSalariesTax.Add(Salary.FromIdentity(salaryId));
                }

                payoutList.Add(payout);
            }
        }
Ejemplo n.º 6
0
 public static PayrollAdjustments ForSalary(Salary salary)
 {
     return(FromArray(SwarmDb.GetDatabaseForReading().GetPayrollAdjustments(salary)));
 }
Ejemplo n.º 7
0
        private void LoadDependencies()
        {
            if (this.DependentCashAdvancesPayback != null && Identity == 0)
            {
                return; // if inited and identity zero, return
            }

            this.DependentExpenseClaims       = new ExpenseClaims();
            this.DependentInvoices            = new InboundInvoices();
            this.DependentSalariesNet         = new Salaries();
            this.DependentSalariesTax         = new Salaries();
            this.DependentCashAdvancesPayout  = new CashAdvances();
            this.DependentCashAdvancesPayback = new CashAdvances();

            if (Identity == 0)
            {
                return; // never progress past here if identity zero
            }

            BasicFinancialDependency[] dependencies = SwarmDb.GetDatabaseForReading().GetPayoutDependencies(Identity);

            foreach (BasicFinancialDependency dependency in dependencies)
            {
                switch (dependency.DependencyType)
                {
                case FinancialDependencyType.ExpenseClaim:
                    this.DependentExpenseClaims.Add(ExpenseClaim.FromIdentity(dependency.ForeignId));
                    break;

                case FinancialDependencyType.InboundInvoice:
                    this.DependentInvoices.Add(InboundInvoice.FromIdentity(dependency.ForeignId));
                    break;

                case FinancialDependencyType.Salary:
                    Salary salary = Salary.FromIdentity(dependency.ForeignId);
                    if (salary.NetSalaryCents == AmountCents || this.CreatedDateTime > new DateTime(2015, 11, 1))
                    {
                        this.DependentSalariesNet.Add(salary);
                    }
                    else     // LEGACY
                    {
                        this.DependentSalariesTax.Add(salary);
                    }
                    break;

                case FinancialDependencyType.SalaryTax:
                    Salary salaryTax = Salary.FromIdentity(dependency.ForeignId);
                    this.DependentSalariesTax.Add(salaryTax);
                    break;

                case FinancialDependencyType.CashAdvance:
                    this.DependentCashAdvancesPayout.Add(CashAdvance.FromIdentity(dependency.ForeignId));
                    break;

                case FinancialDependencyType.CashAdvancePayback:
                    this.DependentCashAdvancesPayback.Add(CashAdvance.FromIdentity(dependency.ForeignId));
                    break;

                default:
                    throw new NotImplementedException(
                              "Unknown financial dependency type in Payout.LoadDependencies(): " + dependency);
                }
            }
        }
Ejemplo n.º 8
0
        public static Payout CreateFromProtoIdentity(Person creator, string protoIdentity)
        {
            string[] components = protoIdentity.Split('|');
            int      payoutId   = 0;

            // The components can EITHER be a series of expense claims OR a single invoice.

            if (components.Length == 0)
            {
                // nothing to construct. Exception or return null?

                return(null);
            }
            if (components[0][0] == 'A')
            {
                // Cash advance(s) to be paid out.

                string     bank           = string.Empty;
                string     account        = string.Empty;
                List <int> identityList   = new List <int>();
                Int64      amountCents    = 0;
                int        organizationId = 0;

                foreach (string component in components)
                {
                    int         advanceId = Int32.Parse(component.Substring(1));
                    CashAdvance advance   = CashAdvance.FromIdentity(advanceId);
                    identityList.Add(advanceId);
                    organizationId = advance.OrganizationId;
                    Organization organization = Organization.FromIdentity(advance.OrganizationId);

                    if (bank.Length < 1)
                    {
                        Person asker = advance.Person;
                        bank    = asker.BankName;
                        account = asker.BankAccount;
                    }

                    amountCents += advance.AmountCents;

                    advance.PaidOut = true;
                    // advance.Open remains true until the advance is repaid

                    SwarmopsLogEntry.Create(creator,
                                            new PayoutCreatedLogEntry(creator, advance.Person, organization,
                                                                      organization.Currency, amountCents / 100.0,
                                                                      "Cash Advance Paid Out"),
                                            advance.Person, advance);


                    OutboundComm.CreateNotificationOfFinancialValidation(advance.Budget, advance.Person,
                                                                         advance.AmountCents / 100.0, advance.Description, NotificationResource.CashAdvance_PaidOut);
                }

                string referenceString = string.Empty;

                if (identityList.Count == 1)
                {
                    referenceString = "Cash Advance #" + identityList[0].ToString(CultureInfo.InvariantCulture);
                }
                else
                {
                    identityList.Sort();
                    referenceString = "Cash Advances " + Formatting.GenerateRangeString(identityList);
                }

                payoutId = SwarmDb.GetDatabaseForWriting().CreatePayout(organizationId, bank, account,
                                                                        referenceString, amountCents, DateTime.Today.AddDays(1),
                                                                        creator.Identity);

                foreach (int advanceId in identityList)
                {
                    SwarmDb.GetDatabaseForWriting()
                    .CreatePayoutDependency(payoutId, FinancialDependencyType.CashAdvance,
                                            advanceId);
                }
            }
            else if (components[0][0] == 'C')
            {
                // Expense claims, possibly followed up by cash advance paybacks

                Person       beneficiaryPerson = null;
                Organization organization      = null;
                string       bank                       = string.Empty;
                string       account                    = string.Empty;
                List <int>   claimIdentityList          = new List <int>();
                List <int>   advancePaybackIdentityList = new List <int>();
                Int64        amountCents                = 0;
                int          organizationId             = 0;

                foreach (string component in components)
                {
                    int foreignId = Int32.Parse(component.Substring(1));

                    if (component[0] == 'C')
                    {
                        ExpenseClaim claim = ExpenseClaim.FromIdentity(foreignId);
                        claimIdentityList.Add(foreignId);

                        if (bank.Length < 1)
                        {
                            Person claimer = claim.Claimer;
                            bank           = claimer.BankName;
                            account        = claimer.BankAccount;
                            organizationId = claim.OrganizationId;
                        }

                        beneficiaryPerson = claim.Claimer;
                        organization      = claim.Organization;
                        amountCents      += claim.AmountCents;

                        claim.Repaid = true;
                        claim.Close();

                        OutboundComm.CreateNotificationOfFinancialValidation(claim.Budget, claim.Claimer,
                                                                             claim.AmountCents / 100.0, claim.Description, NotificationResource.ExpenseClaim_PaidOut);
                    }
                    else if (component[0] == 'a')
                    {
                        CashAdvance advancePayback = CashAdvance.FromIdentity(foreignId);
                        advancePaybackIdentityList.Add(foreignId);

                        amountCents        -= advancePayback.AmountCents;
                        advancePayback.Open = false;
                    }
                }

                string referenceString = string.Empty;

                if (claimIdentityList.Count == 1)
                {
                    referenceString = "Expense Claim #" + claimIdentityList[0].ToString(CultureInfo.InvariantCulture);
                }
                else
                {
                    claimIdentityList.Sort();
                    referenceString = "Expense Claims " + Formatting.GenerateRangeString(claimIdentityList);
                }

                SwarmopsLogEntry.Create(creator,
                                        new PayoutCreatedLogEntry(creator, beneficiaryPerson, organization,
                                                                  organization.Currency, amountCents / 100.0,
                                                                  referenceString),
                                        beneficiaryPerson);

                payoutId = SwarmDb.GetDatabaseForWriting().CreatePayout(organizationId, bank, account,
                                                                        referenceString, amountCents, DateTime.Today.AddDays(1),
                                                                        creator.Identity);

                foreach (int claimId in claimIdentityList)
                {
                    SwarmDb.GetDatabaseForWriting()
                    .CreatePayoutDependency(payoutId, FinancialDependencyType.ExpenseClaim,
                                            claimId);
                }

                foreach (int advancePaybackId in advancePaybackIdentityList)
                {
                    SwarmDb.GetDatabaseForWriting()
                    .CreatePayoutDependency(payoutId, FinancialDependencyType.CashAdvancePayback,
                                            advancePaybackId);
                }
            }
            else if (components[0][0] == 'I')
            {
                // There is just one invoice per payout

                InboundInvoice invoice = InboundInvoice.FromIdentity(Int32.Parse(components[0].Substring(1)));

                DateTime expectedPayment = invoice.DueDate;

                if (expectedPayment < DateTime.Today)
                {
                    expectedPayment = DateTime.Today;
                }

                payoutId = SwarmDb.GetDatabaseForWriting()
                           .CreatePayout(invoice.OrganizationId, string.Empty, invoice.PayToAccount,
                                         invoice.Ocr.Length > 0 ? "OCR " + invoice.Ocr : "Ref# " + invoice.InvoiceReference,
                                         invoice.AmountCents, expectedPayment,
                                         creator.Identity);

                SwarmDb.GetDatabaseForWriting()
                .CreatePayoutDependency(payoutId, FinancialDependencyType.InboundInvoice,
                                        invoice.Identity);

                invoice.Open = false;
            }
            else if (components[0][0] == 'S')
            {
                // Salary, net payment

                Salary salary = Salary.FromIdentity(Int32.Parse(components[0].Substring(1)));

                payoutId = SwarmDb.GetDatabaseForWriting()
                           .CreatePayout(salary.PayrollItem.OrganizationId, salary.PayrollItem.Person.BankName,
                                         salary.PayrollItem.Person.BankAccount,
                                         "Salary " + salary.PayoutDate.ToString("yyyy-MMM"),
                                         salary.NetSalaryCents, salary.PayoutDate,
                                         creator.Identity);

                SwarmDb.GetDatabaseForWriting().CreatePayoutDependency(payoutId, FinancialDependencyType.Salary,
                                                                       salary.Identity);

                salary.NetPaid = true;
            }
            else if (components[0][0] == 'T')
            {
                // Tax payment for multiple salaries.

                List <int> identityList   = new List <int>();
                Int64      amountCents    = 0;
                int        organizationId = 0;
                DateTime   payDay         = DateTime.Today.AddDays(1);

                foreach (string component in components)
                {
                    int    salaryId = Int32.Parse(component.Substring(1));
                    Salary salary   = Salary.FromIdentity(salaryId);
                    identityList.Add(salaryId);

                    if (organizationId == 0)
                    {
                        organizationId = salary.PayrollItem.OrganizationId;
                        payDay         = salary.PayoutDate;
                    }

                    amountCents += salary.TaxTotalCents;

                    salary.TaxPaid = true;
                }


                Organization organization = Organization.FromIdentity(organizationId);
                identityList.Sort();

                payoutId = SwarmDb.GetDatabaseForWriting()
                           .CreatePayout(organization.Identity, "The Tax Man", organization.Parameters.TaxAccount,
                                         organization.Parameters.TaxOcr,
                                         amountCents, payDay, creator.Identity);

                foreach (int salaryId in identityList)
                {
                    SwarmDb.GetDatabaseForWriting().CreatePayoutDependency(payoutId, FinancialDependencyType.SalaryTax,
                                                                           salaryId);
                }
            }
            else
            {
                throw new NotImplementedException();
            }

            return(FromIdentity(payoutId));
        }
Ejemplo n.º 9
0
        public static Payout CreateBitcoinPayoutFromPrototype(Organization organization, Payout prototype, string transactionHash)
        {
            string[] components = prototype.ProtoIdentity.Split('|');
            int      payoutId   = 0;

            // This function is made for complex bitcoin payouts and will typically take many different types of payouts to many people at once.

            if (components.Length == 0)
            {
                // nothing to construct. Exception or return null?

                return(null);
            }

            payoutId = SwarmDb.GetDatabaseForWriting().CreatePayout(organization.Identity, "Bitcoin network", "Multiple",
                                                                    transactionHash, prototype.AmountCents, DateTime.UtcNow, 0);

            foreach (string component in components)
            {
                int foreignId = Int32.Parse(component.Substring(1));

                switch (component[0])
                {
                case 'A':
                    // Cash advance
                    CashAdvance advance = CashAdvance.FromIdentity(foreignId);
                    advance.PaidOut = true;

                    SwarmopsLogEntry.Create(null,
                                            new PayoutCreatedLogEntry(null, advance.Person, organization,
                                                                      organization.Currency, advance.AmountCents / 100.0,
                                                                      "Cash Advance Paid Out"),
                                            advance.Person, advance);

                    OutboundComm.CreateNotificationOfFinancialValidation(advance.Budget, advance.Person,
                                                                         advance.AmountCents / 100.0, advance.Description, NotificationResource.CashAdvance_PaidOut);
                    SwarmDb.GetDatabaseForWriting()
                    .CreatePayoutDependency(payoutId, FinancialDependencyType.CashAdvance,
                                            foreignId);
                    break;

                case 'a':
                    // This is a negative record - payback of cash advance
                    CashAdvance advancePayback = CashAdvance.FromIdentity(foreignId);
                    advancePayback.Open = false;

                    SwarmDb.GetDatabaseForWriting()
                    .CreatePayoutDependency(payoutId, FinancialDependencyType.CashAdvancePayback,
                                            foreignId);

                    break;

                case 'C':
                    // Expense claim
                    ExpenseClaim claim = ExpenseClaim.FromIdentity(foreignId);
                    claim.Repaid = true;
                    claim.Close();

                    OutboundComm.CreateNotificationOfFinancialValidation(claim.Budget, claim.Claimer,
                                                                         claim.AmountCents / 100.0, claim.Description, NotificationResource.ExpenseClaim_PaidOut);
                    SwarmDb.GetDatabaseForWriting()
                    .CreatePayoutDependency(payoutId, FinancialDependencyType.ExpenseClaim,
                                            foreignId);

                    break;

                case 'I':
                    // Invoice
                    InboundInvoice invoice         = InboundInvoice.FromIdentity(foreignId);
                    DateTime       expectedPayment = invoice.DueDate;

                    if (expectedPayment < DateTime.Today)
                    {
                        expectedPayment = DateTime.Today;
                    }

                    SwarmDb.GetDatabaseForWriting()
                    .CreatePayoutDependency(payoutId, FinancialDependencyType.InboundInvoice,
                                            invoice.Identity);

                    // TODO: NOTIFY PAID?

                    invoice.Open = false;
                    break;

                case 'S':
                    // Salary net

                    Salary salaryNet = Salary.FromIdentity(foreignId);
                    SwarmDb.GetDatabaseForWriting().CreatePayoutDependency(payoutId, FinancialDependencyType.Salary,
                                                                           salaryNet.Identity);
                    salaryNet.NetPaid = true;
                    break;

                case 'T':
                    // Tax payout, typically for multiple salaries

                    Salary salaryTax = Salary.FromIdentity(foreignId);
                    SwarmDb.GetDatabaseForWriting().CreatePayoutDependency(payoutId, FinancialDependencyType.SalaryTax,
                                                                           salaryTax.Identity);
                    salaryTax.TaxPaid = true;

                    break;

                default:
                    throw new NotImplementedException();
                }
            }

            // Return the new object by reloading it from database

            return(Payout.FromIdentity(payoutId));
        }
Ejemplo n.º 10
0
 public TaskSalary (Salary salary): base (salary.Identity, salary.PayoutDate.ToString("yyyy-MMM") + " salary for " + salary.PayrollItem.PersonCanonical, salary.PayoutDate.AddDays(-(salary.PayoutDate.Day-1)), salary.PayoutDate.AddDays(-7))
 {
     // empty ctor
 }
Ejemplo n.º 11
0
        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);
        }
Ejemplo n.º 12
0
        public static PaymentTransferInfo FromObject(IHasIdentity financialObject, Money amountToPay = null)
        {
            if (financialObject is ExpenseClaim)
            {
                ExpenseClaim claim = financialObject as ExpenseClaim;

                if (amountToPay == null)
                {
                    return(FromObject(claim.Claimer, new Money(claim.AmountCents, claim.Organization.Currency)));
                }
                else
                {
                    return(FromObject(claim.Claimer, amountToPay));
                }
            }

            if (financialObject is CashAdvance)
            {
                CashAdvance advance = financialObject as CashAdvance;

                if (amountToPay == null)
                {
                    return(FromObject(advance.Person, new Money(advance.AmountCents, advance.Organization.Currency)));
                }
                else
                {
                    return(FromObject(advance.Person, amountToPay));
                }
            }

            if (financialObject is Salary)
            {
                Salary salary = financialObject as Salary;
                return(FromObject(salary.PayrollItem.Person, new Money(salary.NetSalaryCents, salary.PayrollItem.Organization.Currency)));
            }

            if (financialObject is InboundInvoice)
            {
                // TODO: Create Supplier object, read from that instead

                InboundInvoice      invoice = financialObject as InboundInvoice;
                PaymentTransferInfo result  = new PaymentTransferInfo();

                // For now, do a switch on the pay-to-account string, before the payment targets are implemented
                // on invoices and expense claims:

                result.LocalizedPaymentInformation = new Dictionary <string, string>();

                if (invoice.PayToAccount.StartsWith("IBAN"))
                {
                    result.TargetType = PaymentTargetType.InternationalBankTransfer;

                    result.LocalizedPaymentInformation[
                        Logic_Financial_PaymentTransferInfo.PaymentTargetField_Iban] =
                        invoice.PayToAccount.Substring(5);

                    // this should have bic too but we don't have that information before payment targets
                    // are properly implemented
                }
                else if (invoice.PayToAccount.StartsWith("SEBG"))
                {
                    result.TargetType = PaymentTargetType.DomesticBankGiro; // for now
                    result.LocalizedPaymentInformation[
                        Logic_Financial_PaymentTransferInfo.PaymentTargetField_GiroNumber] =
                        invoice.PayToAccount.Substring(5);

                    // Check for SEBG OCR availability

                    if (invoice.InvoiceReference.Length > 2 && Formatting.CheckLuhnChecksum(invoice.InvoiceReference) &&
                        Formatting.CheckLuhnChecksum(invoice.PayToAccount))
                    {
                        result.OcrAvailable = true;
                        result.OcrData      = new string[3];
                        result.OcrData[0]   = invoice.InvoiceReference + " #";
                        result.OcrData[1]   = string.Format("{0} {1:00}   {2} >", // three spaces between the cents and the checksum
                                                            invoice.AmountCents / 100, invoice.AmountCents % 100,
                                                            Formatting.GetLuhnChecksum(invoice.AmountCents.ToString(CultureInfo.InvariantCulture)));
                        result.OcrData[2] = Formatting.CleanNumber(invoice.PayToAccount) + "#41#";
                    }
                }
                else
                {
                    // Assume domestic giro in lack of other information

                    result.TargetType = PaymentTargetType.DomesticBankGiro;
                    result.LocalizedPaymentInformation[
                        Logic_Financial_PaymentTransferInfo.PaymentTargetField_GiroNumber] =
                        invoice.PayToAccount; // the whole string, as opposed to case above
                }

                result.LocalizedPaymentMethodName =
                    Logic_Financial_PaymentTransferInfo.ResourceManager.GetString("PaymentTargetType_" +
                                                                                  result.TargetType.ToString());
                if (invoice.HasNativeCurrency)
                {
                    result.Currency       = invoice.NativeCurrencyAmount.Currency;
                    result.CurrencyAmount = result.Currency.Code + " " +
                                            (invoice.NativeCurrencyAmount.Cents / 100.0).ToString("N2");
                }
                else
                {
                    result.Currency       = invoice.Organization.Currency;
                    result.CurrencyAmount = result.Currency.Code + " " + (invoice.AmountCents / 100.0).ToString("N2");
                }

                result.Country = null; // TODO: Set to Supplier's PaymentTarget country

                result.Recipient = invoice.Supplier;

                return(result);
            }

            if (financialObject is Person)
            {
                Person person = financialObject as Person;

                // For now, assume the bank/clearing/account triplet
                // TODO: Implement payment targets on teh Person object

                PaymentTransferInfo result = new PaymentTransferInfo();
                result.TargetType = PaymentTargetType.DomesticBankTransfer;

                // We must know the amount at this point

                if (amountToPay == null)
                {
                    throw new ArgumentNullException("amountToPay", @"Cannot determine payment transfer information without knowing origin currency");
                }

                if (string.IsNullOrEmpty(person.BankName) || string.IsNullOrEmpty(person.BankClearing) ||
                    string.IsNullOrEmpty(person.BankAccount))
                {
                    throw new NotImplementedException("Cannot provide payment transfer information for Person #" + person.Identity.ToString());
                }

                result.TargetType = PaymentTargetType.DomesticBankTransfer;
                result.Country    = person.CountryId == 0? null: person.Country;
                result.Currency   = amountToPay.Currency; // TODO: The currency will need to come from the payment method instead

                result.CurrencyAmount = result.Currency.Code + " " + (amountToPay.ToCurrency(result.Currency).Cents / 100.0).ToString("N2");

                result.LocalizedPaymentMethodName =
                    Logic_Financial_PaymentTransferInfo.ResourceManager.GetString("PaymentTargetType_" +
                                                                                  result.TargetType.ToString());
                result.Recipient = person.Canonical;

                result.LocalizedPaymentInformation = new Dictionary <string, string>();
                result.LocalizedPaymentInformation[
                    Logic_Financial_PaymentTransferInfo.PaymentTargetField_ClearingCode] =
                    person.BankName + " " + person.BankClearing;
                result.LocalizedPaymentInformation[
                    Logic_Financial_PaymentTransferInfo.PaymentTargetField_AccountNumber] = person.BankAccount;

                return(result);
            }

            throw new NotImplementedException("Unknown payment information");
        }