Adds salary specifications with detailed accordion view
Introduces new salary specification feature with interactive accordion component Implements detailed salary breakdown including: - Salary specification JSON data model - Salary specification page with printable view - Accordion component for expanding/collapsing salary details - Localization support for new salary labels Enhances employee salary transparency and detail presentation
This commit is contained in:
parent
f3c54dde35
commit
a1059adf06
14 changed files with 1613 additions and 46 deletions
|
|
@ -52,7 +52,24 @@
|
|||
</swp-edit-section>
|
||||
</swp-card>
|
||||
|
||||
<!-- Tillæg -->
|
||||
<!-- Provision -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>@Model.LabelCommission</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-edit-section>
|
||||
<swp-edit-row id="card-productcommission">
|
||||
<swp-edit-label>@Model.LabelProductCommission</swp-edit-label>
|
||||
<input type="text" id="value-productcommission" data-type="number" value="@Model.ProductCommission" readonly>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row id="card-servicecommission">
|
||||
<swp-edit-label>@Model.LabelServiceCommission</swp-edit-label>
|
||||
<input type="text" id="value-servicecommission" data-type="number" value="@Model.ServiceCommission" readonly>
|
||||
</swp-edit-row>
|
||||
</swp-edit-section>
|
||||
</swp-card>
|
||||
|
||||
<!-- Tillaeg -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>@Model.LabelSupplements</swp-card-title>
|
||||
|
|
@ -72,47 +89,111 @@
|
|||
</swp-edit-row>
|
||||
</swp-edit-section>
|
||||
</swp-card>
|
||||
|
||||
<!-- Provision -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>@Model.LabelCommission</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-edit-section>
|
||||
<swp-edit-row id="card-productcommission">
|
||||
<swp-edit-label>@Model.LabelProductCommission</swp-edit-label>
|
||||
<input type="text" id="value-productcommission" data-type="number" value="@Model.ProductCommission" readonly>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row id="card-servicecommission">
|
||||
<swp-edit-label>@Model.LabelServiceCommission</swp-edit-label>
|
||||
<input type="text" id="value-servicecommission" data-type="number" value="@Model.ServiceCommission" readonly>
|
||||
</swp-edit-row>
|
||||
</swp-edit-section>
|
||||
</swp-card>
|
||||
</swp-card-column>
|
||||
|
||||
<swp-card class="salary-history">
|
||||
<swp-card-header>
|
||||
<swp-card-title>@Model.LabelSalaryHistory</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-data-table>
|
||||
<swp-data-table-header>
|
||||
<swp-data-table-cell>@Model.LabelPeriod</swp-data-table-cell>
|
||||
<swp-data-table-cell>@Model.LabelGrossSalary</swp-data-table-cell>
|
||||
<swp-data-table-cell></swp-data-table-cell>
|
||||
</swp-data-table-header>
|
||||
@foreach (var item in Model.SalaryHistory)
|
||||
{
|
||||
<swp-data-table-row>
|
||||
<swp-data-table-cell>@item.Period</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@item.GrossSalary</swp-data-table-cell>
|
||||
<swp-data-table-cell><i class="ph ph-caret-right"></i></swp-data-table-cell>
|
||||
</swp-data-table-row>
|
||||
}
|
||||
</swp-data-table>
|
||||
</swp-card>
|
||||
</swp-detail-grid>
|
||||
|
||||
<!-- Loenspecifikationer (full width accordion) -->
|
||||
<swp-card class="salary-specifications">
|
||||
<swp-card-header>
|
||||
<swp-card-title>@Model.LabelSpecifications</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-accordion id="salary-specifications-accordion">
|
||||
@foreach (var spec in Model.Specifications)
|
||||
{
|
||||
<swp-accordion-item data-period="@spec.PeriodKey">
|
||||
<swp-accordion-header>
|
||||
<swp-accordion-info>
|
||||
<swp-accordion-title>@spec.Period</swp-accordion-title>
|
||||
<swp-accordion-meta>@spec.Weeks.Sum(w => w.NormalHours + w.OvertimeHours)t</swp-accordion-meta>
|
||||
</swp-accordion-info>
|
||||
<swp-accordion-summary>
|
||||
<swp-summary-item>
|
||||
<swp-summary-value>@spec.GrossSalaryFormatted</swp-summary-value>
|
||||
<swp-summary-label>@Model.LabelTotal</swp-summary-label>
|
||||
</swp-summary-item>
|
||||
<swp-accordion-toggle>
|
||||
<i class="ph ph-caret-down"></i>
|
||||
</swp-accordion-toggle>
|
||||
</swp-accordion-summary>
|
||||
</swp-accordion-header>
|
||||
<swp-accordion-content>
|
||||
<!-- Config row -->
|
||||
<swp-config-row>
|
||||
<swp-config-item>
|
||||
<swp-config-label>@Model.LabelNormalRate:</swp-config-label>
|
||||
<swp-config-value class="mono">@spec.Config.HourlyRateFormatted</swp-config-value>
|
||||
</swp-config-item>
|
||||
<swp-config-item>
|
||||
<swp-config-label>@Model.LabelWeeklyNorm:</swp-config-label>
|
||||
<swp-config-value>@spec.Config.WeeklyHoursFormatted</swp-config-value>
|
||||
</swp-config-item>
|
||||
<swp-config-item>
|
||||
<swp-config-label>@Model.LabelOvertimeMultiplier:</swp-config-label>
|
||||
<swp-config-value>@spec.Config.OvertimeFormatted</swp-config-value>
|
||||
</swp-config-item>
|
||||
<swp-config-item>
|
||||
<swp-config-label>@Model.LabelMinimum:</swp-config-label>
|
||||
<swp-config-value class="mono">@spec.Config.MinimumFormatted</swp-config-value>
|
||||
</swp-config-item>
|
||||
<swp-config-item>
|
||||
<swp-config-label>@Model.LabelProvision:</swp-config-label>
|
||||
<swp-config-value>@spec.Config.CommissionFormatted</swp-config-value>
|
||||
</swp-config-item>
|
||||
</swp-config-row>
|
||||
|
||||
<!-- Weeks table -->
|
||||
<swp-accordion-table>
|
||||
<swp-data-table class="specification-weeks">
|
||||
<swp-data-table-header>
|
||||
<swp-data-table-cell>@Model.LabelWeek</swp-data-table-cell>
|
||||
<swp-data-table-cell>Timer</swp-data-table-cell>
|
||||
<swp-data-table-cell>Overtid</swp-data-table-cell>
|
||||
<swp-data-table-cell>Ferie</swp-data-table-cell>
|
||||
<swp-data-table-cell>Services</swp-data-table-cell>
|
||||
<swp-data-table-cell>Produkter</swp-data-table-cell>
|
||||
<swp-data-table-cell>Minimum</swp-data-table-cell>
|
||||
<swp-data-table-cell>Provision</swp-data-table-cell>
|
||||
<swp-data-table-cell>I alt</swp-data-table-cell>
|
||||
</swp-data-table-header>
|
||||
@foreach (var week in spec.Weeks)
|
||||
{
|
||||
<swp-data-table-row>
|
||||
<swp-data-table-cell>Uge @week.WeekNumber</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@week.NormalHoursFormatted</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono @(week.HasOvertime ? "warning" : "")">@week.OvertimeHoursFormatted</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@week.VacationDaysFormatted</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@week.ServiceRevenueFormatted</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@week.ProductRevenueFormatted</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@week.MinimumThresholdFormatted</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono @(week.HasCommission ? "highlight" : "")">@week.CommissionFormatted</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@week.TotalPayFormatted</swp-data-table-cell>
|
||||
</swp-data-table-row>
|
||||
}
|
||||
<swp-data-table-footer>
|
||||
<swp-data-table-cell>TOTAL</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@(spec.Weeks.Sum(w => w.NormalHours))t</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@(spec.Weeks.Sum(w => w.OvertimeHours))t</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@(spec.Weeks.Sum(w => w.VacationDays)) dg</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@(spec.Weeks.Sum(w => w.ServiceRevenue).ToString("N0", System.Globalization.CultureInfo.GetCultureInfo("da-DK"))) kr</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@(spec.Weeks.Sum(w => w.ProductRevenue).ToString("N0", System.Globalization.CultureInfo.GetCultureInfo("da-DK"))) kr</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">-</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@(spec.Weeks.Sum(w => w.Commission).ToString("N2", System.Globalization.CultureInfo.GetCultureInfo("da-DK"))) kr</swp-data-table-cell>
|
||||
<swp-data-table-cell class="mono">@(spec.Weeks.Sum(w => w.TotalPay).ToString("N2", System.Globalization.CultureInfo.GetCultureInfo("da-DK"))) kr</swp-data-table-cell>
|
||||
</swp-data-table-footer>
|
||||
</swp-data-table>
|
||||
</swp-accordion-table>
|
||||
<swp-accordion-footer>
|
||||
<a href="/medarbejdere/loenspecifikation/@spec.PeriodKey" target="_blank" class="swp-btn secondary">
|
||||
<i class="ph ph-file-text"></i>
|
||||
Se lønberegning
|
||||
</a>
|
||||
</swp-accordion-footer>
|
||||
</swp-accordion-content>
|
||||
</swp-accordion-item>
|
||||
}
|
||||
</swp-accordion>
|
||||
</swp-card>
|
||||
|
||||
<!-- Rates drawer -->
|
||||
<div id="rates-drawer" data-drawer="lg">
|
||||
<swp-drawer-header>
|
||||
|
|
@ -177,7 +258,7 @@
|
|||
</swp-data-row>
|
||||
</swp-data-table>
|
||||
|
||||
<!-- Tillæg -->
|
||||
<!-- Tillaeg -->
|
||||
<swp-data-section>
|
||||
<swp-section-label>@Model.LabelSupplements</swp-section-label>
|
||||
<swp-data-table>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
|
|
@ -6,18 +7,23 @@ namespace PlanTempus.Application.Features.Employees.Components;
|
|||
public class EmployeeDetailSalaryViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
|
||||
public EmployeeDetailSalaryViewComponent(ILocalizationService localization)
|
||||
public EmployeeDetailSalaryViewComponent(ILocalizationService localization, IWebHostEnvironment environment)
|
||||
{
|
||||
_localization = localization;
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var employee = EmployeeDetailCatalog.Get(key);
|
||||
var salaryData = LoadSalarySpecifications();
|
||||
|
||||
var model = new EmployeeDetailSalaryViewModel
|
||||
{
|
||||
// Salary specifications from JSON
|
||||
Specifications = salaryData.Specifications,
|
||||
// Data
|
||||
BankAccount = employee.BankAccount,
|
||||
TaxCard = employee.TaxCard,
|
||||
|
|
@ -89,7 +95,21 @@ public class EmployeeDetailSalaryViewComponent : ViewComponent
|
|||
ProductCommissionValue = employee.ProductCommissionValue,
|
||||
ServiceCommissionValue = employee.ServiceCommissionValue,
|
||||
|
||||
// Mock salary history
|
||||
// Labels for specifications
|
||||
LabelSpecifications = _localization.Get("employees.detail.salary.specifications"),
|
||||
LabelWeek = _localization.Get("employees.detail.salary.week"),
|
||||
LabelNormalHours = _localization.Get("employees.detail.salary.normalhours"),
|
||||
LabelOvertimeHours = _localization.Get("employees.detail.salary.overtimehours"),
|
||||
LabelVacationDays = _localization.Get("employees.detail.salary.vacationdays"),
|
||||
LabelServiceRevenue = _localization.Get("employees.detail.salary.servicerevenue"),
|
||||
LabelProductRevenue = _localization.Get("employees.detail.salary.productrevenue"),
|
||||
LabelMinimumThreshold = _localization.Get("employees.detail.salary.minimumthreshold"),
|
||||
LabelTotal = _localization.Get("employees.detail.salary.total"),
|
||||
LabelWeeklyNorm = _localization.Get("employees.detail.salary.weeklynorm"),
|
||||
LabelOvertimeMultiplier = _localization.Get("employees.detail.salary.overtimemultiplier"),
|
||||
LabelMinimum = _localization.Get("employees.detail.salary.minimum"),
|
||||
|
||||
// Mock salary history (kept for backwards compatibility)
|
||||
SalaryHistory = new List<SalaryHistoryItem>
|
||||
{
|
||||
new() { Period = "Januar 2026", GrossSalary = "34.063,50 kr" },
|
||||
|
|
@ -102,10 +122,23 @@ public class EmployeeDetailSalaryViewComponent : ViewComponent
|
|||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private SalarySpecificationRoot LoadSalarySpecifications()
|
||||
{
|
||||
var jsonPath = Path.Combine(_environment.ContentRootPath, "Features", "Employees", "Data", "salarySpecificationMock.json");
|
||||
var json = System.IO.File.ReadAllText(jsonPath);
|
||||
return JsonSerializer.Deserialize<SalarySpecificationRoot>(json, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
})!;
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeDetailSalaryViewModel
|
||||
{
|
||||
// Salary Specifications (from JSON)
|
||||
public List<SalarySpecificationDto> Specifications { get; init; } = new();
|
||||
|
||||
// Data
|
||||
public required string BankAccount { get; init; }
|
||||
public required string TaxCard { get; init; }
|
||||
|
|
@ -160,6 +193,20 @@ public class EmployeeDetailSalaryViewModel
|
|||
public required string LabelProductCommissionFull { get; init; }
|
||||
public required string LabelServiceCommissionFull { get; init; }
|
||||
|
||||
// Labels for specifications accordion
|
||||
public required string LabelSpecifications { get; init; }
|
||||
public required string LabelWeek { get; init; }
|
||||
public required string LabelNormalHours { get; init; }
|
||||
public required string LabelOvertimeHours { get; init; }
|
||||
public required string LabelVacationDays { get; init; }
|
||||
public required string LabelServiceRevenue { get; init; }
|
||||
public required string LabelProductRevenue { get; init; }
|
||||
public required string LabelMinimumThreshold { get; init; }
|
||||
public required string LabelTotal { get; init; }
|
||||
public required string LabelWeeklyNorm { get; init; }
|
||||
public required string LabelOvertimeMultiplier { get; init; }
|
||||
public required string LabelMinimum { get; init; }
|
||||
|
||||
// Rate values (for drawer inputs)
|
||||
public required string NormalRateValue { get; init; }
|
||||
public required string OvertimeRateValue { get; init; }
|
||||
|
|
@ -186,3 +233,70 @@ public class SalaryHistoryItem
|
|||
public required string Period { get; init; }
|
||||
public required string GrossSalary { get; init; }
|
||||
}
|
||||
|
||||
// DTOs for salary specification JSON
|
||||
public class SalarySpecificationRoot
|
||||
{
|
||||
public List<SalarySpecificationDto> Specifications { get; init; } = new();
|
||||
}
|
||||
|
||||
public class SalarySpecificationDto
|
||||
{
|
||||
public required string Period { get; init; }
|
||||
public required string PeriodKey { get; init; }
|
||||
public decimal GrossSalary { get; init; }
|
||||
public decimal BasePay { get; init; }
|
||||
public decimal OvertimePay { get; init; }
|
||||
public decimal ServiceCommission { get; init; }
|
||||
public decimal ProductCommission { get; init; }
|
||||
public SalaryConfigDto Config { get; init; } = new();
|
||||
public List<SalaryWeekDto> Weeks { get; init; } = new();
|
||||
|
||||
// Formatted values for display
|
||||
public string GrossSalaryFormatted => GrossSalary.ToString("N2", System.Globalization.CultureInfo.GetCultureInfo("da-DK")) + " kr";
|
||||
public string BasePayFormatted => BasePay.ToString("N2", System.Globalization.CultureInfo.GetCultureInfo("da-DK")) + " kr";
|
||||
public string OvertimePayFormatted => OvertimePay.ToString("N2", System.Globalization.CultureInfo.GetCultureInfo("da-DK")) + " kr";
|
||||
public string TotalCommission => (ServiceCommission + ProductCommission).ToString("N2", System.Globalization.CultureInfo.GetCultureInfo("da-DK")) + " kr";
|
||||
}
|
||||
|
||||
public class SalaryConfigDto
|
||||
{
|
||||
public decimal HourlyRate { get; init; }
|
||||
public int WeeklyHours { get; init; }
|
||||
public decimal OvertimeMultiplier { get; init; }
|
||||
public decimal MinimumPerHour { get; init; }
|
||||
public int ServiceCommissionPct { get; init; }
|
||||
public int ProductCommissionPct { get; init; }
|
||||
|
||||
// Formatted values
|
||||
public string HourlyRateFormatted => HourlyRate.ToString("N0", System.Globalization.CultureInfo.GetCultureInfo("da-DK")) + " kr";
|
||||
public string WeeklyHoursFormatted => WeeklyHours + "t/uge";
|
||||
public string OvertimeFormatted => "+" + Math.Round((OvertimeMultiplier - 1) * 100) + "%";
|
||||
public string MinimumFormatted => MinimumPerHour.ToString("N0", System.Globalization.CultureInfo.GetCultureInfo("da-DK")) + " kr/time";
|
||||
public string CommissionFormatted => ServiceCommissionPct + "% services · " + ProductCommissionPct + "% produkter";
|
||||
}
|
||||
|
||||
public class SalaryWeekDto
|
||||
{
|
||||
public int WeekNumber { get; init; }
|
||||
public int NormalHours { get; init; }
|
||||
public int OvertimeHours { get; init; }
|
||||
public int VacationDays { get; init; }
|
||||
public decimal ServiceRevenue { get; init; }
|
||||
public decimal ProductRevenue { get; init; }
|
||||
public decimal MinimumThreshold { get; init; }
|
||||
public decimal Commission { get; init; }
|
||||
public decimal TotalPay { get; init; }
|
||||
|
||||
// Formatted values
|
||||
public string NormalHoursFormatted => NormalHours + "t";
|
||||
public string OvertimeHoursFormatted => OvertimeHours + "t";
|
||||
public string VacationDaysFormatted => VacationDays + " dg";
|
||||
public string ServiceRevenueFormatted => ServiceRevenue.ToString("N0", System.Globalization.CultureInfo.GetCultureInfo("da-DK")) + " kr";
|
||||
public string ProductRevenueFormatted => ProductRevenue.ToString("N0", System.Globalization.CultureInfo.GetCultureInfo("da-DK")) + " kr";
|
||||
public string MinimumThresholdFormatted => MinimumThreshold.ToString("N0", System.Globalization.CultureInfo.GetCultureInfo("da-DK")) + " kr";
|
||||
public string CommissionFormatted => Commission.ToString("N2", System.Globalization.CultureInfo.GetCultureInfo("da-DK")) + " kr";
|
||||
public string TotalPayFormatted => TotalPay.ToString("N2", System.Globalization.CultureInfo.GetCultureInfo("da-DK")) + " kr";
|
||||
public bool HasOvertime => OvertimeHours > 0;
|
||||
public bool HasCommission => Commission > 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,330 @@
|
|||
{
|
||||
"specifications": [
|
||||
{
|
||||
"period": "Januar 2026",
|
||||
"periodKey": "2026-01",
|
||||
"grossSalary": 34063.50,
|
||||
"basePay": 28800.00,
|
||||
"overtimePay": 1800.00,
|
||||
"serviceCommission": 2463.50,
|
||||
"productCommission": 1000.00,
|
||||
"config": {
|
||||
"hourlyRate": 185,
|
||||
"weeklyHours": 37,
|
||||
"overtimeMultiplier": 1.5,
|
||||
"minimumPerHour": 220,
|
||||
"serviceCommissionPct": 15,
|
||||
"productCommissionPct": 15
|
||||
},
|
||||
"weeks": [
|
||||
{
|
||||
"weekNumber": 1,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 2,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 12500,
|
||||
"productRevenue": 3200,
|
||||
"minimumThreshold": 8580,
|
||||
"commission": 1068.00,
|
||||
"totalPay": 8643.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 2,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 10800,
|
||||
"productRevenue": 2100,
|
||||
"minimumThreshold": 8140,
|
||||
"commission": 712.50,
|
||||
"totalPay": 7558.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 3,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 3,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 14200,
|
||||
"productRevenue": 4500,
|
||||
"minimumThreshold": 8800,
|
||||
"commission": 1485.00,
|
||||
"totalPay": 9318.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 4,
|
||||
"normalHours": 32,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 1,
|
||||
"serviceRevenue": 9200,
|
||||
"productRevenue": 1800,
|
||||
"minimumThreshold": 7040,
|
||||
"commission": 594.00,
|
||||
"totalPay": 6544.50
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"period": "December 2025",
|
||||
"periodKey": "2025-12",
|
||||
"grossSalary": 31845.00,
|
||||
"basePay": 27380.00,
|
||||
"overtimePay": 555.00,
|
||||
"serviceCommission": 2910.00,
|
||||
"productCommission": 1000.00,
|
||||
"config": {
|
||||
"hourlyRate": 185,
|
||||
"weeklyHours": 37,
|
||||
"overtimeMultiplier": 1.5,
|
||||
"minimumPerHour": 220,
|
||||
"serviceCommissionPct": 15,
|
||||
"productCommissionPct": 15
|
||||
},
|
||||
"weeks": [
|
||||
{
|
||||
"weekNumber": 49,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 11200,
|
||||
"productRevenue": 2800,
|
||||
"minimumThreshold": 8140,
|
||||
"commission": 879.00,
|
||||
"totalPay": 7724.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 50,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 2,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 13500,
|
||||
"productRevenue": 3600,
|
||||
"minimumThreshold": 8580,
|
||||
"commission": 1278.00,
|
||||
"totalPay": 8833.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 51,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 12800,
|
||||
"productRevenue": 2400,
|
||||
"minimumThreshold": 8140,
|
||||
"commission": 1059.00,
|
||||
"totalPay": 7904.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 52,
|
||||
"normalHours": 30,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 2,
|
||||
"serviceRevenue": 8500,
|
||||
"productRevenue": 1500,
|
||||
"minimumThreshold": 6600,
|
||||
"commission": 510.00,
|
||||
"totalPay": 6060.00
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"period": "November 2025",
|
||||
"periodKey": "2025-11",
|
||||
"grossSalary": 33290.25,
|
||||
"basePay": 27380.00,
|
||||
"overtimePay": 1110.00,
|
||||
"serviceCommission": 3550.25,
|
||||
"productCommission": 1250.00,
|
||||
"config": {
|
||||
"hourlyRate": 185,
|
||||
"weeklyHours": 37,
|
||||
"overtimeMultiplier": 1.5,
|
||||
"minimumPerHour": 220,
|
||||
"serviceCommissionPct": 15,
|
||||
"productCommissionPct": 15
|
||||
},
|
||||
"weeks": [
|
||||
{
|
||||
"weekNumber": 45,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 11800,
|
||||
"productRevenue": 3100,
|
||||
"minimumThreshold": 8140,
|
||||
"commission": 1014.00,
|
||||
"totalPay": 7859.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 46,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 4,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 15200,
|
||||
"productRevenue": 4200,
|
||||
"minimumThreshold": 9020,
|
||||
"commission": 1557.00,
|
||||
"totalPay": 9667.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 47,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 10500,
|
||||
"productRevenue": 2600,
|
||||
"minimumThreshold": 8140,
|
||||
"commission": 744.00,
|
||||
"totalPay": 7589.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 48,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 12300,
|
||||
"productRevenue": 2900,
|
||||
"minimumThreshold": 8140,
|
||||
"commission": 1059.00,
|
||||
"totalPay": 7904.00
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"period": "Oktober 2025",
|
||||
"periodKey": "2025-10",
|
||||
"grossSalary": 32156.75,
|
||||
"basePay": 27380.00,
|
||||
"overtimePay": 832.50,
|
||||
"serviceCommission": 2944.25,
|
||||
"productCommission": 1000.00,
|
||||
"config": {
|
||||
"hourlyRate": 185,
|
||||
"weeklyHours": 37,
|
||||
"overtimeMultiplier": 1.5,
|
||||
"minimumPerHour": 220,
|
||||
"serviceCommissionPct": 15,
|
||||
"productCommissionPct": 15
|
||||
},
|
||||
"weeks": [
|
||||
{
|
||||
"weekNumber": 40,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 10200,
|
||||
"productRevenue": 2400,
|
||||
"minimumThreshold": 8140,
|
||||
"commission": 669.00,
|
||||
"totalPay": 7514.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 41,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 3,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 13800,
|
||||
"productRevenue": 3200,
|
||||
"minimumThreshold": 8800,
|
||||
"commission": 1230.00,
|
||||
"totalPay": 9063.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 42,
|
||||
"normalHours": 0,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 5,
|
||||
"serviceRevenue": 0,
|
||||
"productRevenue": 0,
|
||||
"minimumThreshold": 0,
|
||||
"commission": 0,
|
||||
"totalPay": 0
|
||||
},
|
||||
{
|
||||
"weekNumber": 43,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 11500,
|
||||
"productRevenue": 2700,
|
||||
"minimumThreshold": 8140,
|
||||
"commission": 909.00,
|
||||
"totalPay": 7754.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 44,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 10900,
|
||||
"productRevenue": 2500,
|
||||
"minimumThreshold": 8140,
|
||||
"commission": 789.00,
|
||||
"totalPay": 7634.00
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"period": "September 2025",
|
||||
"periodKey": "2025-09",
|
||||
"grossSalary": 34520.00,
|
||||
"basePay": 27380.00,
|
||||
"overtimePay": 1387.50,
|
||||
"serviceCommission": 4252.50,
|
||||
"productCommission": 1500.00,
|
||||
"config": {
|
||||
"hourlyRate": 185,
|
||||
"weeklyHours": 37,
|
||||
"overtimeMultiplier": 1.5,
|
||||
"minimumPerHour": 220,
|
||||
"serviceCommissionPct": 15,
|
||||
"productCommissionPct": 15
|
||||
},
|
||||
"weeks": [
|
||||
{
|
||||
"weekNumber": 36,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 2,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 14500,
|
||||
"productRevenue": 4800,
|
||||
"minimumThreshold": 8580,
|
||||
"commission": 1608.00,
|
||||
"totalPay": 9163.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 37,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 3,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 16200,
|
||||
"productRevenue": 5100,
|
||||
"minimumThreshold": 8800,
|
||||
"commission": 1875.00,
|
||||
"totalPay": 9708.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 38,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 12100,
|
||||
"productRevenue": 3200,
|
||||
"minimumThreshold": 8140,
|
||||
"commission": 1074.00,
|
||||
"totalPay": 7919.00
|
||||
},
|
||||
{
|
||||
"weekNumber": 39,
|
||||
"normalHours": 37,
|
||||
"overtimeHours": 0,
|
||||
"vacationDays": 0,
|
||||
"serviceRevenue": 11800,
|
||||
"productRevenue": 2900,
|
||||
"minimumThreshold": 8140,
|
||||
"commission": 984.00,
|
||||
"totalPay": 7829.00
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,461 @@
|
|||
@page "/medarbejdere/loenspecifikation/{period}"
|
||||
@model PlanTempus.Application.Features.Employees.Pages.SalarySpecificationModel
|
||||
@{
|
||||
Layout = null;
|
||||
var spec = Model.Specification!;
|
||||
var culture = System.Globalization.CultureInfo.GetCultureInfo("da-DK");
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="da">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Lønspecifikation – @spec.Period</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
/* Print setup */
|
||||
@@page { size: A4; margin: 14mm; }
|
||||
@@media print {
|
||||
.no-print { display: none !important; }
|
||||
.page-break { break-after: page; page-break-after: always; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
}
|
||||
|
||||
/* Base */
|
||||
:root {
|
||||
--ink: #0f172a;
|
||||
--muted: #475569;
|
||||
--line: #e2e8f0;
|
||||
--soft: #f8fafc;
|
||||
--accent: #0ea5e9;
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
||||
color: var(--ink);
|
||||
background: #fff;
|
||||
}
|
||||
.sheet {
|
||||
max-width: 210mm;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.hdr {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 14px 0 10px;
|
||||
border-bottom: 2px solid var(--line);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.brand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.title {
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
letter-spacing: .2px;
|
||||
margin: 0;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.subtitle {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
.meta {
|
||||
text-align: right;
|
||||
min-width: 260px;
|
||||
}
|
||||
.meta .pill {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: var(--soft);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.meta .kv {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
gap: 4px 12px;
|
||||
justify-content: end;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
.meta .kv b { color: var(--ink); font-weight: 600; }
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.card .hd {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 12px;
|
||||
background: linear-gradient(0deg, var(--soft), #fff);
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.card .hd h2 {
|
||||
font-size: 13px;
|
||||
margin: 0;
|
||||
letter-spacing: .2px;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.card .bd { padding: 12px; }
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr .8fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* Total */
|
||||
.total {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--soft);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.total .label {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .25px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.total .big {
|
||||
font-size: 28px;
|
||||
font-weight: 900;
|
||||
font-family: var(--font-mono);
|
||||
margin: 0;
|
||||
line-height: 1.05;
|
||||
}
|
||||
.total .big small {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
}
|
||||
th, td {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
vertical-align: top;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
color: var(--muted);
|
||||
font-weight: 700;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .2px;
|
||||
background: var(--soft);
|
||||
}
|
||||
td.num, th.num { text-align: right; }
|
||||
td.num { font-family: var(--font-mono); }
|
||||
tr:last-child td { border-bottom: none; }
|
||||
|
||||
.note {
|
||||
margin: 8px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.ftr {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid var(--line);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
font-size: 10.5px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.avoid-break { break-inside: avoid; page-break-inside: avoid; }
|
||||
|
||||
/* Print button */
|
||||
.print-btn {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 10px 20px;
|
||||
background: #0ea5e9;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.print-btn:hover { background: #0284c7; }
|
||||
</style>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@@phosphor-icons/web@@2.1.2/src/regular/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<button class="print-btn no-print" onclick="window.print()">
|
||||
<i class="ph ph-printer"></i>
|
||||
Udskriv
|
||||
</button>
|
||||
|
||||
<div class="sheet">
|
||||
<!-- Header -->
|
||||
<div class="hdr">
|
||||
<div class="brand">
|
||||
<p class="title">Lønspecifikation</p>
|
||||
<p class="subtitle">Periode: <b>@spec.Period</b></p>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<div class="pill">Medarbejdernr.: <b>@Model.EmployeeNumber</b></div>
|
||||
<div class="kv">
|
||||
<span>Medarbejder:</span><b>@Model.EmployeeName</b>
|
||||
<span>Afdeling:</span><b>@Model.Department</b>
|
||||
<span>Ansættelse:</span><b>@Model.EmploymentType</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Total -->
|
||||
<section class="total avoid-break">
|
||||
<div>
|
||||
<div class="label">Bruttoløn (@spec.Period)</div>
|
||||
<p class="big">@spec.GrossSalary.ToString("N2", culture) <small>kr</small></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Grid: Summary + Balances -->
|
||||
<section class="grid">
|
||||
<div class="card avoid-break">
|
||||
<div class="hd">
|
||||
<h2>Samlet lønopgørelse</h2>
|
||||
</div>
|
||||
<div class="bd">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Løndel</th>
|
||||
<th class="num">Beløb</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Grundløn inkl. overarbejde</td>
|
||||
<td class="num">@((spec.BasePay + spec.OvertimePay).ToString("N2", culture)) kr</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Provision i alt</td>
|
||||
<td class="num">@((spec.ServiceCommission + spec.ProductCommission).ToString("N2", culture)) kr</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Bruttoløn</b></td>
|
||||
<td class="num"><b>@spec.GrossSalary.ToString("N2", culture) kr</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card avoid-break">
|
||||
<div class="hd">
|
||||
<h2>Satser</h2>
|
||||
</div>
|
||||
<div class="bd">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th class="num">Værdi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Timeløn</td>
|
||||
<td class="num">@spec.Config.HourlyRate.ToString("N0", culture) kr</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Normtid</td>
|
||||
<td class="num">@spec.Config.WeeklyHours t/uge</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Overtidstillæg</td>
|
||||
<td class="num">@spec.Config.OvertimeFormatted</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Minimum pr. time</td>
|
||||
<td class="num">@spec.Config.MinimumPerHour.ToString("N0", culture) kr</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Quick Summary -->
|
||||
<section class="card avoid-break">
|
||||
<div class="hd">
|
||||
<h2>Hurtigt resumé</h2>
|
||||
</div>
|
||||
<div class="bd">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nøglepunkt</th>
|
||||
<th class="num">Værdi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Normaltimer</td>
|
||||
<td class="num">@spec.Weeks.Sum(w => w.NormalHours) t</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Overarbejde</td>
|
||||
<td class="num">@spec.Weeks.Sum(w => w.OvertimeHours) t</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Provision (services + produkter)</td>
|
||||
<td class="num">@((spec.ServiceCommission + spec.ProductCommission).ToString("N2", culture)) kr</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Feriedage</td>
|
||||
<td class="num">@spec.Weeks.Sum(w => w.VacationDays) dage</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="ftr">
|
||||
<div>Overblik</div>
|
||||
<div style="text-align:right">Lønspecifikation · @spec.Period</div>
|
||||
</div>
|
||||
|
||||
<div class="page-break"></div>
|
||||
|
||||
<!-- Page 2: Details -->
|
||||
<section class="card">
|
||||
<div class="hd">
|
||||
<h2>Arbejdstid pr. uge</h2>
|
||||
</div>
|
||||
<div class="bd">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Uge</th>
|
||||
<th class="num">Normaltimer</th>
|
||||
<th class="num">Overtid</th>
|
||||
<th class="num">Services</th>
|
||||
<th class="num">Produkter</th>
|
||||
<th class="num">Provision</th>
|
||||
<th class="num">I alt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var week in spec.Weeks)
|
||||
{
|
||||
<tr>
|
||||
<td>Uge @week.WeekNumber</td>
|
||||
<td class="num">@week.NormalHours t</td>
|
||||
<td class="num">@week.OvertimeHours t</td>
|
||||
<td class="num">@week.ServiceRevenue.ToString("N0", culture) kr</td>
|
||||
<td class="num">@week.ProductRevenue.ToString("N0", culture) kr</td>
|
||||
<td class="num">@week.Commission.ToString("N2", culture) kr</td>
|
||||
<td class="num">@week.TotalPay.ToString("N2", culture) kr</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td><b>I alt</b></td>
|
||||
<td class="num"><b>@spec.Weeks.Sum(w => w.NormalHours) t</b></td>
|
||||
<td class="num"><b>@spec.Weeks.Sum(w => w.OvertimeHours) t</b></td>
|
||||
<td class="num"><b>@spec.Weeks.Sum(w => w.ServiceRevenue).ToString("N0", culture) kr</b></td>
|
||||
<td class="num"><b>@spec.Weeks.Sum(w => w.ProductRevenue).ToString("N0", culture) kr</b></td>
|
||||
<td class="num"><b>@spec.Weeks.Sum(w => w.Commission).ToString("N2", culture) kr</b></td>
|
||||
<td class="num"><b>@spec.Weeks.Sum(w => w.TotalPay).ToString("N2", culture) kr</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="note">
|
||||
Satser: Normal @spec.Config.HourlyRate.ToString("N0", culture) kr/time.
|
||||
Overtid (@((spec.Config.OvertimeMultiplier - 1) * 100)%) @((spec.Config.HourlyRate * spec.Config.OvertimeMultiplier).ToString("N2", culture)) kr/time.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="hd">
|
||||
<h2>Provision</h2>
|
||||
</div>
|
||||
<div class="bd">
|
||||
<p class="note" style="margin-top:0">
|
||||
<b>Services:</b> @spec.Config.ServiceCommissionPct% af omsætning over minimum (@spec.Config.MinimumPerHour.ToString("N0", culture) kr/time).<br/>
|
||||
<b>Produkter:</b> @spec.Config.ProductCommissionPct% af salg.
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Uge</th>
|
||||
<th class="num">Service oms.</th>
|
||||
<th class="num">Produkt oms.</th>
|
||||
<th class="num">Minimum</th>
|
||||
<th class="num">Provision</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var week in spec.Weeks)
|
||||
{
|
||||
<tr>
|
||||
<td>Uge @week.WeekNumber</td>
|
||||
<td class="num">@week.ServiceRevenue.ToString("N0", culture) kr</td>
|
||||
<td class="num">@week.ProductRevenue.ToString("N0", culture) kr</td>
|
||||
<td class="num">@week.MinimumThreshold.ToString("N0", culture) kr</td>
|
||||
<td class="num">@week.Commission.ToString("N2", culture) kr</td>
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
<td><b>I alt</b></td>
|
||||
<td class="num"><b>@spec.Weeks.Sum(w => w.ServiceRevenue).ToString("N0", culture) kr</b></td>
|
||||
<td class="num"><b>@spec.Weeks.Sum(w => w.ProductRevenue).ToString("N0", culture) kr</b></td>
|
||||
<td class="num">-</td>
|
||||
<td class="num"><b>@spec.Weeks.Sum(w => w.Commission).ToString("N2", culture) kr</b></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="ftr">
|
||||
<div>Detaljer</div>
|
||||
<div style="text-align:right">Lønspecifikation · @spec.Period</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Pages;
|
||||
|
||||
public class SalarySpecificationModel : PageModel
|
||||
{
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
|
||||
public SalarySpecificationModel(IWebHostEnvironment environment)
|
||||
{
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
public SalarySpecificationDto? Specification { get; private set; }
|
||||
public string EmployeeName { get; private set; } = "Emma Larsen";
|
||||
public string EmployeeNumber { get; private set; } = "EMP-001";
|
||||
public string Department { get; private set; } = "Frisør";
|
||||
public string EmploymentType { get; private set; } = "Fuldtid (37 t/uge)";
|
||||
|
||||
public IActionResult OnGet(string period)
|
||||
{
|
||||
var specs = LoadSalarySpecifications();
|
||||
Specification = specs.FirstOrDefault(s => s.PeriodKey == period);
|
||||
|
||||
if (Specification == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Page();
|
||||
}
|
||||
|
||||
private List<SalarySpecificationDto> LoadSalarySpecifications()
|
||||
{
|
||||
var jsonPath = Path.Combine(_environment.ContentRootPath, "Features", "Employees", "Data", "salarySpecificationMock.json");
|
||||
var json = System.IO.File.ReadAllText(jsonPath);
|
||||
var root = JsonSerializer.Deserialize<SalarySpecificationRoot>(json, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
return root?.Specifications ?? new List<SalarySpecificationDto>();
|
||||
}
|
||||
}
|
||||
|
|
@ -494,7 +494,19 @@
|
|||
"saturdaysupplementfull": "8-21 Lørdage (udenfor arbejdstid)",
|
||||
"commission": "Provisionsberegning",
|
||||
"productcommissionfull": "Provision på produktsalg",
|
||||
"servicecommissionfull": "Provision på servicesalg"
|
||||
"servicecommissionfull": "Provision på servicesalg",
|
||||
"specifications": "Lønspecifikationer",
|
||||
"week": "Uge",
|
||||
"normalhours": "Normtimer",
|
||||
"overtimehours": "Overtid",
|
||||
"vacationdays": "Feriedage",
|
||||
"servicerevenue": "Services",
|
||||
"productrevenue": "Produkter",
|
||||
"minimumthreshold": "Minimum",
|
||||
"total": "I alt",
|
||||
"weeklynorm": "Normtid",
|
||||
"overtimemultiplier": "Overtid",
|
||||
"minimum": "Minimum"
|
||||
},
|
||||
"hr": {
|
||||
"contractdocuments": "Kontrakt & Dokumenter",
|
||||
|
|
|
|||
|
|
@ -494,7 +494,19 @@
|
|||
"saturdaysupplementfull": "8-21 Saturdays (outside working hours)",
|
||||
"commission": "Commission calculation",
|
||||
"productcommissionfull": "Commission on product sales",
|
||||
"servicecommissionfull": "Commission on service sales"
|
||||
"servicecommissionfull": "Commission on service sales",
|
||||
"specifications": "Salary specifications",
|
||||
"week": "Week",
|
||||
"normalhours": "Normal hours",
|
||||
"overtimehours": "Overtime",
|
||||
"vacationdays": "Vacation days",
|
||||
"servicerevenue": "Services",
|
||||
"productrevenue": "Products",
|
||||
"minimumthreshold": "Minimum",
|
||||
"total": "Total",
|
||||
"weeklynorm": "Weekly norm",
|
||||
"overtimemultiplier": "Overtime",
|
||||
"minimum": "Minimum"
|
||||
},
|
||||
"hr": {
|
||||
"contractdocuments": "Contract & Documents",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
<link rel="stylesheet" href="~/css/waitlist.css">
|
||||
<link rel="stylesheet" href="~/css/tabs.css">
|
||||
<link rel="stylesheet" href="~/css/controls.css">
|
||||
<link rel="stylesheet" href="~/css/accordion.css">
|
||||
<link rel="stylesheet" href="~/css/cash.css">
|
||||
<link rel="stylesheet" href="~/css/auth.css">
|
||||
<link rel="stylesheet" href="~/css/account.css">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue