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