1 Commits

Author SHA1 Message Date
Daniel Krähenbühl d924ae0c30 feat: salary section — Salarium iframe, Swiss net pay calculator
- Salary nav group in sidebar with flyout and expanded dropdown
- Salarium page (/salarium): embedded BFS Salarium iframe for official
  Swiss salary comparisons (no public API available, iframe only)
- Pay calculator (/lohn-analyse): Swiss net salary calculator with all
  mandatory deductions: AHV/IV/EO (5.3%), ALV (1.1%), BVK (0.5275%),
  cantonal FAK rates, income tax estimate; built with Angular signals
- Salary development page (/lohn-entwicklung): placeholder (planned)
- i18n: all keys in DE/EN/FR/IT
2026-05-25 22:46:30 +02:00
13 changed files with 921 additions and 4 deletions
+6
View File
@@ -14,6 +14,9 @@ import { ExpenseList } from './expenses/expense-list/expense-list';
import { Profile } from './profile/profile'; import { Profile } from './profile/profile';
import { Settings } from './settings/settings'; import { Settings } from './settings/settings';
import { Calendar } from './calendar/calendar'; import { Calendar } from './calendar/calendar';
import { Salarium } from './salary/salarium/salarium';
import { SalaryAnalyse } from './salary/analyse/analyse';
import { SalaryEntwicklung } from './salary/entwicklung/entwicklung';
export const routes: Routes = [ export const routes: Routes = [
{ path: 'login', component: Login }, { path: 'login', component: Login },
{ path: 'register', component: Register }, { path: 'register', component: Register },
@@ -34,6 +37,9 @@ export const routes: Routes = [
{ path: 'profile', component: Profile }, { path: 'profile', component: Profile },
{ path: 'settings', component: Settings }, { path: 'settings', component: Settings },
{ path: 'calendar', component: Calendar }, { path: 'calendar', component: Calendar },
{ path: 'salarium', component: Salarium },
{ path: 'lohn-analyse', component: SalaryAnalyse },
{ path: 'lohn-entwicklung', component: SalaryEntwicklung },
], ],
}, },
{ path: '**', redirectTo: 'dashboard' }, { path: '**', redirectTo: 'dashboard' },
@@ -157,6 +157,64 @@
} }
</li> </li>
<!-- Lohn -->
<li class="relative">
@if (sidebarService.collapsed()) {
<!-- Collapsed: icon button opens flyout -->
<button (click)="sidebarService.toggleFlyout('salary')"
[class]="sidebarService.openFlyout() === 'salary' ? 'bg-gray-100 dark:bg-gray-700' : ''"
class="relative flex items-center justify-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path fill="currentColor" d="M4 19v2c0 .5523.44772 1 1 1h14c.5523 0 1-.4477 1-1v-2H4Z"/>
<path fill="currentColor" fill-rule="evenodd" d="M9 3c0-.55228.44772-1 1-1h8c.5523 0 1 .44772 1 1v3c0 .55228-.4477 1-1 1h-2v1h2c.5096 0 .9376.38314.9939.88957L19.8951 17H4.10498l.90116-8.11043C5.06241 8.38314 5.49047 8 6.00002 8H12V7h-2c-.55228 0-1-.44772-1-1V3Zm1.01 8H8.00002v2.01H10.01V11Zm.99 0h2.01v2.01H11V11Zm5.01 0H14v2.01h2.01V11Zm-8.00998 3H10.01v2.01H8.00002V14ZM13.01 14H11v2.01h2.01V14Zm.99 0h2.01v2.01H14V14ZM11 4h6v1h-6V4Z" clip-rule="evenodd"/>
</svg>
@if (sidebarService.openFlyout() !== 'salary') {
<span class="pointer-events-none absolute left-full ml-3 top-1/2 -translate-y-1/2 px-2 py-1 text-xs font-medium text-white bg-gray-900 dark:bg-gray-700 rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity duration-150 z-50">
{{ 'sidebar.salary' | translate }}
</span>
}
</button>
<!-- Flyout -->
@if (sidebarService.openFlyout() === 'salary') {
<div class="absolute left-full top-0 ml-2 z-50 w-44 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg py-1">
<p class="px-3 py-2 text-xs font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider">{{ 'sidebar.salary' | translate }}</p>
<a routerLink="/salarium" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
{{ 'sidebar.salarium' | translate }}
</a>
<a routerLink="/lohn-analyse" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
{{ 'sidebar.salary_analyse' | translate }}
</a>
<a routerLink="/lohn-entwicklung" (click)="sidebarService.closeFlyout(); sidebarService.closeMobile()"
class="flex items-center px-3 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md mx-1">
{{ 'sidebar.salary_entwicklung' | translate }}
</a>
</div>
}
} @else {
<!-- Expanded: Angular-controlled dropdown -->
<button type="button" (click)="sidebarService.toggleSalary()"
class="flex items-center p-2 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 group">
<svg class="w-6 h-6 flex-shrink-0 text-gray-400 group-hover:text-gray-900 dark:group-hover:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path fill="currentColor" d="M4 19v2c0 .5523.44772 1 1 1h14c.5523 0 1-.4477 1-1v-2H4Z"/>
<path fill="currentColor" fill-rule="evenodd" d="M9 3c0-.55228.44772-1 1-1h8c.5523 0 1 .44772 1 1v3c0 .55228-.4477 1-1 1h-2v1h2c.5096 0 .9376.38314.9939.88957L19.8951 17H4.10498l.90116-8.11043C5.06241 8.38314 5.49047 8 6.00002 8H12V7h-2c-.55228 0-1-.44772-1-1V3Zm1.01 8H8.00002v2.01H10.01V11Zm.99 0h2.01v2.01H11V11Zm5.01 0H14v2.01h2.01V11Zm-8.00998 3H10.01v2.01H8.00002V14ZM13.01 14H11v2.01h2.01V14Zm.99 0h2.01v2.01H14V14ZM11 4h6v1h-6V4Z" clip-rule="evenodd"/>
</svg>
<span class="flex-1 ml-3 text-left whitespace-nowrap">{{ 'sidebar.salary' | translate }}</span>
<svg [class.rotate-180]="sidebarService.salaryOpen()" class="w-4 h-4 transition-transform duration-200" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</button>
@if (sidebarService.salaryOpen()) {
<ul class="py-2 space-y-2">
<li><a routerLink="/salarium" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.salarium' | translate }}</a></li>
<li><a routerLink="/lohn-analyse" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.salary_analyse' | translate }}</a></li>
<li><a routerLink="/lohn-entwicklung" routerLinkActive="!bg-violet-50 !text-violet-700 dark:!bg-violet-900/20 dark:!text-violet-300" (click)="sidebarService.closeMobile()" class="flex items-center p-2 pl-11 w-full text-sm font-normal text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700">{{ 'sidebar.salary_entwicklung' | translate }}</a></li>
</ul>
}
}
</li>
</ul> </ul>
<!-- Mobile: Notifications, Theme, Profile, Logout (hidden on desktop — those are in the navbar) --> <!-- Mobile: Notifications, Theme, Profile, Logout (hidden on desktop — those are in the navbar) -->
@@ -0,0 +1,434 @@
<div class="p-4 sm:p-6 lg:p-8">
<!-- Header -->
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{{ 'lohn_analyse.title' | translate }}</h1>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ 'lohn_analyse.subtitle' | translate }}</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-[360px_1fr] gap-6 items-start">
<!-- ── Eingaben ────────────────────────────────────────────────────────── -->
<div class="space-y-4">
<!-- Grundlagen -->
<div class="rounded-lg bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700 p-5">
<h2 class="text-base font-semibold text-gray-900 dark:text-white mb-4">
{{ 'lohn_analyse.section_basics' | translate }}
</h2>
<div class="space-y-4">
<!-- Monatslohn -->
<div>
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
{{ 'lohn_analyse.label_monatslohn' | translate }}
</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-sm text-gray-500 dark:text-gray-400 pointer-events-none">CHF</span>
<input type="number" min="0" step="100"
[ngModel]="monatslohn()"
(ngModelChange)="setMonatslohn($event)"
class="block w-full rounded-lg border border-gray-300 bg-gray-50 pl-12 pr-3 py-2.5 text-sm text-gray-900 placeholder-gray-400
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500" />
</div>
</div>
<!-- Anzahl Monate -->
<div>
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
{{ 'lohn_analyse.label_monate' | translate }}
</label>
<div class="flex gap-2">
<button (click)="setAnzahlMonate('12')"
[class]="anzahlMonate() === 12
? 'flex-1 rounded-lg bg-violet-700 px-3 py-2 text-sm font-medium text-white dark:bg-violet-600'
: 'flex-1 rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'">
12 {{ 'lohn_analyse.monate' | translate }}
</button>
<button (click)="setAnzahlMonate('13')"
[class]="anzahlMonate() === 13
? 'flex-1 rounded-lg bg-violet-700 px-3 py-2 text-sm font-medium text-white dark:bg-violet-600'
: 'flex-1 rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'">
13 {{ 'lohn_analyse.monate' | translate }}
</button>
</div>
</div>
<!-- Kanton -->
<div>
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
{{ 'lohn_analyse.label_kanton' | translate }}
</label>
<div class="relative">
<select [ngModel]="kanton()" (ngModelChange)="setKanton($event)"
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500 appearance-none pr-10">
@for (c of cantons; track c) {
<option [value]="c">{{ ('canton_names.' + c) | translate }} ({{ c }})</option>
}
</select>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<svg class="w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 9-7 7-7-7"/>
</svg>
</span>
</div>
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
{{ 'lohn_analyse.fak_rate_hint' | translate }} <span class="font-medium text-violet-700 dark:text-violet-400">{{ pct(result().fakRate) }}</span>
</p>
</div>
<!-- Abrechnungsverfahren -->
<div>
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
{{ 'lohn_analyse.label_verfahren' | translate }}
</label>
<select [ngModel]="verfahren()" (ngModelChange)="setVerfahren($event)"
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500 appearance-none">
<option value="ordentlich">{{ 'lohn_analyse.verfahren_ordentlich' | translate }}</option>
<option value="vereinfacht">{{ 'lohn_analyse.verfahren_vereinfacht' | translate }}</option>
<option value="quellensteuer">{{ 'lohn_analyse.verfahren_quellensteuer' | translate }}</option>
</select>
@if (verfahren() !== 'ordentlich') {
<p class="mt-1.5 flex items-center gap-1.5 text-xs text-amber-600 dark:text-amber-400">
<svg class="w-3.5 h-3.5 flex-shrink-0" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v4m0 4h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/>
</svg>
{{ 'lohn_analyse.verfahren_hint' | translate }}
</p>
}
</div>
</div>
</div>
<!-- Optionale Sätze -->
<div class="rounded-lg bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700">
<button (click)="showOptional.set(!showOptional())"
class="flex w-full items-center justify-between px-5 py-3.5 text-sm font-medium text-gray-900 dark:text-white">
<span>{{ 'lohn_analyse.section_optional' | translate }}</span>
<svg [class.rotate-180]="showOptional()" class="w-4 h-4 text-gray-400 transition-transform duration-200" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 9-7 7-7-7"/>
</svg>
</button>
@if (showOptional()) {
<div class="border-t border-gray-100 dark:border-gray-700 px-5 pb-5 pt-4 space-y-4">
<p class="text-xs text-gray-500 dark:text-gray-400">{{ 'lohn_analyse.optional_hint' | translate }}</p>
<!-- AG -->
<p class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
{{ 'lohn_analyse.beitraege_ag' | translate }}
</p>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
{{ 'lohn_analyse.ktv' | translate }} (%)
</label>
<input type="number" min="0" max="10" step="0.05"
[ngModel]="agKtvPct()"
(ngModelChange)="setAgKtvPct($event)"
placeholder="0.00"
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500" />
</div>
<div>
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
{{ 'lohn_analyse.bu' | translate }} (%)
</label>
<input type="number" min="0" max="10" step="0.05"
[ngModel]="agBuPct()"
(ngModelChange)="setAgBuPct($event)"
placeholder="0.00"
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500" />
</div>
</div>
<!-- AN -->
<p class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
{{ 'lohn_analyse.abzuege_an' | translate }}
</p>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
{{ 'lohn_analyse.ktv' | translate }} (%)
</label>
<input type="number" min="0" max="10" step="0.05"
[ngModel]="anKtvPct()"
(ngModelChange)="setAnKtvPct($event)"
placeholder="0.00"
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500" />
</div>
<div>
<label class="block mb-1.5 text-xs font-medium text-gray-700 dark:text-gray-300">
{{ 'lohn_analyse.nbu' | translate }} (%)
</label>
<input type="number" min="0" max="10" step="0.05"
[ngModel]="anNbuPct()"
(ngModelChange)="setAnNbuPct($event)"
placeholder="0.00"
class="block w-full rounded-lg border border-gray-300 bg-gray-50 px-3 py-2.5 text-sm text-gray-900
focus:border-violet-600 focus:outline-none focus:ring-2 focus:ring-violet-300
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:focus:border-violet-500 dark:focus:ring-violet-500" />
</div>
</div>
</div>
}
</div>
</div><!-- end inputs -->
<!-- ── Ergebnisse ─────────────────────────────────────────────────────── -->
<div class="space-y-4">
<!-- KPI-Zusammenfassung -->
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<!-- Bruttolohn -->
<div class="rounded-lg bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700 p-4">
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
{{ 'lohn_analyse.bruttolohn' | translate }}
</p>
<p class="text-xl font-bold text-gray-900 dark:text-white">
CHF {{ result().grossMonthly | number:'1.2-2' }}
</p>
<p class="text-xs text-gray-400 dark:text-gray-500 mt-0.5">
CHF {{ result().grossAnnual | number:'1.2-2' }} / {{ 'lohn_analyse.jahr' | translate }}
</p>
</div>
<!-- Totalaufwand AG -->
<div class="rounded-lg bg-amber-50 border border-amber-200 dark:bg-amber-900/20 dark:border-amber-800 p-4">
<p class="text-xs font-medium text-amber-700 dark:text-amber-400 mb-1">
{{ 'lohn_analyse.totalaufwand_ag' | translate }}
</p>
<p class="text-xl font-bold text-amber-700 dark:text-amber-400">
CHF {{ result().totalCostMonthly | number:'1.2-2' }}
</p>
<p class="text-xs text-amber-600 dark:text-amber-500 mt-0.5">
CHF {{ result().totalCostAnnual | number:'1.2-2' }} / {{ 'lohn_analyse.jahr' | translate }}
</p>
</div>
<!-- Nettolohn AN -->
<div class="rounded-lg bg-emerald-50 border border-emerald-200 dark:bg-emerald-900/20 dark:border-emerald-800 p-4">
<p class="text-xs font-medium text-emerald-700 dark:text-emerald-400 mb-1">
{{ 'lohn_analyse.nettolohn' | translate }}
</p>
<p class="text-xl font-bold text-emerald-700 dark:text-emerald-400">
CHF {{ result().netMonthly | number:'1.2-2' }}
</p>
<p class="text-xs text-emerald-600 dark:text-emerald-500 mt-0.5">
CHF {{ result().netAnnual | number:'1.2-2' }} / {{ 'lohn_analyse.jahr' | translate }}
</p>
</div>
</div>
<!-- Aufschlüsselung Arbeitgeber -->
<div class="rounded-lg bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700 overflow-hidden">
<div class="px-5 py-3.5 border-b border-gray-100 dark:border-gray-700">
<h2 class="text-base font-semibold text-gray-900 dark:text-white">
{{ 'lohn_analyse.section_ag' | translate }}
</h2>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
<th class="px-5 py-3 text-left">{{ 'lohn_analyse.col_position' | translate }}</th>
<th class="px-4 py-3 text-right">{{ 'lohn_analyse.col_satz' | translate }}</th>
<th class="px-4 py-3 text-right">{{ 'lohn_analyse.col_monat' | translate }}</th>
<th class="px-5 py-3 text-right">{{ 'lohn_analyse.col_jahr' | translate }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
<!-- Bruttolohn -->
<tr class="bg-gray-50 dark:bg-gray-700/30">
<td class="px-5 py-3 font-medium text-gray-900 dark:text-white">{{ 'lohn_analyse.bruttolohn' | translate }}</td>
<td class="px-4 py-3 text-right text-gray-400 dark:text-gray-500"></td>
<td class="px-4 py-3 text-right font-medium text-gray-900 dark:text-white">{{ result().grossMonthly | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right font-medium text-gray-900 dark:text-white">{{ result().grossAnnual | number:'1.2-2' }}</td>
</tr>
<!-- AHV/IV/EO -->
<tr>
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.ahv_iv_eo' | translate }}</td>
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(0.053) }}</td>
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agAhv | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agAhv * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
<!-- ALV -->
<tr>
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.alv' | translate }}</td>
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(0.011) }}</td>
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agAlv | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agAlv * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
<!-- FAK -->
<tr>
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">
{{ 'lohn_analyse.fak' | translate }}
<span class="ml-1.5 text-xs text-gray-400 dark:text-gray-500">({{ kanton() }})</span>
</td>
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(result().fakRate) }}</td>
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agFak | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agFak * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
<!-- VK -->
<tr>
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.vk' | translate }}</td>
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(0.005275) }}</td>
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agVk | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agVk * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
<!-- KTV AG (optional) -->
@if (agKtvPct() > 0) {
<tr>
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.ktv' | translate }}</td>
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(agKtvPct() / 100) }}</td>
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agKtvAmt | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agKtvAmt * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
}
<!-- BU AG (optional) -->
@if (agBuPct() > 0) {
<tr>
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.bu' | translate }}</td>
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(agBuPct() / 100) }}</td>
<td class="px-4 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agBuAmt | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right text-gray-700 dark:text-gray-300">{{ result().agBuAmt * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
}
<!-- Total Beiträge AG -->
<tr class="bg-amber-50 dark:bg-amber-900/10">
<td class="px-5 py-3 font-medium text-amber-800 dark:text-amber-300">{{ 'lohn_analyse.total_beitraege_ag' | translate }}</td>
<td class="px-4 py-3 text-right text-amber-700 dark:text-amber-400">{{ pct(result().agTotalRate) }}</td>
<td class="px-4 py-3 text-right font-medium text-amber-800 dark:text-amber-300">{{ result().agTotal | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right font-medium text-amber-800 dark:text-amber-300">{{ result().agTotal * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
<!-- Totalaufwand -->
<tr class="bg-amber-100 dark:bg-amber-900/20 border-t-2 border-amber-200 dark:border-amber-700">
<td class="px-5 py-3.5 font-semibold text-amber-900 dark:text-amber-200">{{ 'lohn_analyse.totalaufwand_ag' | translate }}</td>
<td class="px-4 py-3.5 text-right text-amber-700 dark:text-amber-400"></td>
<td class="px-4 py-3.5 text-right font-semibold text-amber-900 dark:text-amber-200">{{ result().totalCostMonthly | number:'1.2-2' }}</td>
<td class="px-5 py-3.5 text-right font-semibold text-amber-900 dark:text-amber-200">{{ result().totalCostAnnual | number:'1.2-2' }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Aufschlüsselung Arbeitnehmer -->
<div class="rounded-lg bg-white border border-gray-200 dark:bg-gray-800 dark:border-gray-700 overflow-hidden">
<div class="px-5 py-3.5 border-b border-gray-100 dark:border-gray-700">
<h2 class="text-base font-semibold text-gray-900 dark:text-white">
{{ 'lohn_analyse.section_an' | translate }}
</h2>
</div>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
<th class="px-5 py-3 text-left">{{ 'lohn_analyse.col_position' | translate }}</th>
<th class="px-4 py-3 text-right">{{ 'lohn_analyse.col_satz' | translate }}</th>
<th class="px-4 py-3 text-right">{{ 'lohn_analyse.col_monat' | translate }}</th>
<th class="px-5 py-3 text-right">{{ 'lohn_analyse.col_jahr' | translate }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 dark:divide-gray-700">
<!-- Bruttolohn -->
<tr class="bg-gray-50 dark:bg-gray-700/30">
<td class="px-5 py-3 font-medium text-gray-900 dark:text-white">{{ 'lohn_analyse.bruttolohn' | translate }}</td>
<td class="px-4 py-3 text-right text-gray-400 dark:text-gray-500"></td>
<td class="px-4 py-3 text-right font-medium text-gray-900 dark:text-white">{{ result().grossMonthly | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right font-medium text-gray-900 dark:text-white">{{ result().grossAnnual | number:'1.2-2' }}</td>
</tr>
<!-- AHV/IV/EO -->
<tr>
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.ahv_iv_eo' | translate }}</td>
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(0.053) }}</td>
<td class="px-4 py-3 text-right text-red-600 dark:text-red-400">{{ result().anAhv | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right text-red-600 dark:text-red-400">{{ result().anAhv * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
<!-- ALV -->
<tr>
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.alv' | translate }}</td>
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(0.011) }}</td>
<td class="px-4 py-3 text-right text-red-600 dark:text-red-400">{{ result().anAlv | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right text-red-600 dark:text-red-400">{{ result().anAlv * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
<!-- KTV AN (optional) -->
@if (anKtvPct() > 0) {
<tr>
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.ktv' | translate }}</td>
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(anKtvPct() / 100) }}</td>
<td class="px-4 py-3 text-right text-red-600 dark:text-red-400">{{ result().anKtvAmt | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right text-red-600 dark:text-red-400">{{ result().anKtvAmt * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
}
<!-- NBU AN (optional) -->
@if (anNbuPct() > 0) {
<tr>
<td class="px-5 py-3 text-gray-700 dark:text-gray-300">{{ 'lohn_analyse.nbu' | translate }}</td>
<td class="px-4 py-3 text-right text-gray-500 dark:text-gray-400">{{ pct(anNbuPct() / 100) }}</td>
<td class="px-4 py-3 text-right text-red-600 dark:text-red-400">{{ result().anNbuAmt | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right text-red-600 dark:text-red-400">{{ result().anNbuAmt * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
}
<!-- Total Abzüge -->
<tr class="bg-red-50 dark:bg-red-900/10">
<td class="px-5 py-3 font-medium text-red-800 dark:text-red-300">{{ 'lohn_analyse.total_abzuege_an' | translate }}</td>
<td class="px-4 py-3 text-right text-red-700 dark:text-red-400">{{ pct(result().anTotalRate) }}</td>
<td class="px-4 py-3 text-right font-medium text-red-800 dark:text-red-300">{{ result().anTotal | number:'1.2-2' }}</td>
<td class="px-5 py-3 text-right font-medium text-red-800 dark:text-red-300">{{ result().anTotal * anzahlMonate() | number:'1.2-2' }}</td>
</tr>
<!-- Nettolohn -->
<tr class="bg-emerald-50 dark:bg-emerald-900/20 border-t-2 border-emerald-200 dark:border-emerald-700">
<td class="px-5 py-3.5 font-semibold text-emerald-900 dark:text-emerald-200">{{ 'lohn_analyse.nettolohn' | translate }}</td>
<td class="px-4 py-3.5 text-right text-emerald-700 dark:text-emerald-400"></td>
<td class="px-4 py-3.5 text-right font-semibold text-emerald-900 dark:text-emerald-200">{{ result().netMonthly | number:'1.2-2' }}</td>
<td class="px-5 py-3.5 text-right font-semibold text-emerald-900 dark:text-emerald-200">{{ result().netAnnual | number:'1.2-2' }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Quellenhinweis -->
<p class="text-xs text-gray-400 dark:text-gray-500">
{{ 'lohn_analyse.source_hint' | translate }}
</p>
</div><!-- end results -->
</div>
</div>
+102
View File
@@ -0,0 +1,102 @@
import { Component, computed, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
export const FAK_RATES: Record<string, number> = {
AG: 0.0145, AI: 0.016, AR: 0.016, BE: 0.015, BL: 0.013,
BS: 0.0165, FR: 0.0227, GE: 0.0222, GL: 0.014, GR: 0.015,
JU: 0.0275, LU: 0.0135, NE: 0.018, NW: 0.015, OW: 0.014,
SG: 0.018, SH: 0.013, SO: 0.0125, SZ: 0.013, TG: 0.014,
TI: 0.016, UR: 0.017, VD: 0.0237, VS: 0.025, ZG: 0.0135,
ZH: 0.01025,
};
export const CANTONS = Object.keys(FAK_RATES).sort();
const AHV_RATE = 0.053;
const ALV_RATE = 0.011;
const VK_RATE = 0.005275;
@Component({
selector: 'app-salary-analyse',
standalone: true,
imports: [CommonModule, FormsModule, TranslateModule],
templateUrl: './analyse.html',
})
export class SalaryAnalyse {
readonly cantons = CANTONS;
readonly fakRates = FAK_RATES;
// ── Inputs ────────────────────────────────────────────────────────────────
monatslohn = signal(5000);
anzahlMonate = signal(12);
kanton = signal('ZH');
verfahren = signal('ordentlich');
agKtvPct = signal(0);
agBuPct = signal(0);
anKtvPct = signal(0);
anNbuPct = signal(0);
showOptional = signal(false);
// ── Computed ───────────────────────────────────────────────────────────────
result = computed(() => {
const gross = Math.max(0, this.monatslohn());
const monate = Math.min(13, Math.max(1, this.anzahlMonate()));
const fak = FAK_RATES[this.kanton()] ?? 0;
const agKtv = (this.agKtvPct() ?? 0) / 100;
const agBu = (this.agBuPct() ?? 0) / 100;
const anKtv = (this.anKtvPct() ?? 0) / 100;
const anNbu = (this.anNbuPct() ?? 0) / 100;
// Arbeitgeber
const agAhv = gross * AHV_RATE;
const agAlv = gross * ALV_RATE;
const agFak = gross * fak;
const agVk = gross * VK_RATE;
const agKtvAmt = gross * agKtv;
const agBuAmt = gross * agBu;
const agTotal = agAhv + agAlv + agFak + agVk + agKtvAmt + agBuAmt;
const agTotalRate = AHV_RATE + ALV_RATE + fak + VK_RATE + agKtv + agBu;
// Arbeitnehmer
const anAhv = gross * AHV_RATE;
const anAlv = gross * ALV_RATE;
const anKtvAmt = gross * anKtv;
const anNbuAmt = gross * anNbu;
const anTotal = anAhv + anAlv + anKtvAmt + anNbuAmt;
const anTotalRate = AHV_RATE + ALV_RATE + anKtv + anNbu;
return {
grossMonthly: gross,
grossAnnual: gross * monate,
fakRate: fak,
agAhv, agAlv, agFak, agVk, agKtvAmt, agBuAmt,
agTotal, agTotalRate,
totalCostMonthly: gross + agTotal,
totalCostAnnual: (gross + agTotal) * monate,
anAhv, anAlv, anKtvAmt, anNbuAmt,
anTotal, anTotalRate,
netMonthly: gross - anTotal,
netAnnual: (gross - anTotal) * monate,
};
});
// ── Helpers ────────────────────────────────────────────────────────────────
pct(rate: number): string {
return (rate * 100).toFixed(rate % 0.001 === 0 ? 1 : 3).replace(/\.?0+$/, '') + ' %';
}
setMonatslohn(v: string) { this.monatslohn.set(+v || 0); }
setAnzahlMonate(v: string) { this.anzahlMonate.set(Math.min(13, Math.max(1, +v || 12))); }
setKanton(v: string) { this.kanton.set(v); }
setVerfahren(v: string) { this.verfahren.set(v); }
setAgKtvPct(v: string) { this.agKtvPct.set(+v || 0); }
setAgBuPct(v: string) { this.agBuPct.set(+v || 0); }
setAnKtvPct(v: string) { this.anKtvPct.set(+v || 0); }
setAnNbuPct(v: string) { this.anNbuPct.set(+v || 0); }
}
@@ -0,0 +1,17 @@
<div class="p-4 sm:p-6 lg:p-8">
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">{{ 'salary_entwicklung.title' | translate }}</h1>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ 'salary_entwicklung.subtitle' | translate }}</p>
</div>
<!-- Placeholder -->
<div class="flex flex-col items-center justify-center py-20 text-center">
<div class="w-16 h-16 rounded-full bg-violet-100 dark:bg-violet-900/30 flex items-center justify-center mb-4">
<svg class="w-8 h-8 text-violet-600 dark:text-violet-400" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12 7a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0V8.414l-4.293 4.293a1 1 0 01-1.414 0L8 10.414l-4.293 4.293a1 1 0 01-1.414-1.414l5-5a1 1 0 011.414 0L11 10.586 14.586 7H12z" clip-rule="evenodd"/>
</svg>
</div>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">{{ 'salary_entwicklung.coming_soon_title' | translate }}</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 max-w-sm">{{ 'salary_entwicklung.coming_soon_text' | translate }}</p>
</div>
</div>
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
@Component({
selector: 'app-salary-entwicklung',
standalone: true,
imports: [TranslateModule],
templateUrl: './entwicklung.html',
})
export class SalaryEntwicklung {}
@@ -0,0 +1,29 @@
<div class="flex flex-col h-[calc(100vh-57px)]">
<!-- Header -->
<div class="flex items-center justify-between px-4 sm:px-6 py-3 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 flex-shrink-0">
<div>
<h1 class="text-lg font-semibold text-gray-900 dark:text-white">{{ 'salarium.title' | translate }}</h1>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ 'salarium.subtitle' | translate }}</p>
</div>
<a [href]="externalUrl" target="_blank" rel="noopener noreferrer"
class="inline-flex items-center gap-1.5 rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-xs font-medium text-gray-700
hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600 transition-colors">
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M18 14v4.833A1.166 1.166 0 0 1 16.833 20H5.167A1.167 1.167 0 0 1 4 18.833V7.167A1.166 1.166 0 0 1 5.167 6h4.618m4.447-2H20v5.768m-7.889 2.121 7.778-7.778"/>
</svg>
{{ 'salarium.open_external' | translate }}
</a>
</div>
<!-- iFrame -->
<iframe
[src]="safeUrl"
class="flex-1 w-full border-0"
title="Salarium BFS Lohnrechner"
loading="lazy"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox">
</iframe>
</div>
@@ -0,0 +1,16 @@
import { Component, inject } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
@Component({
selector: 'app-salarium',
standalone: true,
imports: [TranslateModule],
templateUrl: './salarium.html',
})
export class Salarium {
private sanitizer = inject(DomSanitizer);
readonly externalUrl = 'https://www.salarium.bfs.admin.ch/';
readonly safeUrl: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.externalUrl);
}
+5
View File
@@ -9,6 +9,7 @@ export class SidebarService {
mobileOpen = signal(false); mobileOpen = signal(false);
budgetsOpen = signal(false); budgetsOpen = signal(false);
accountsOpen = signal(false); accountsOpen = signal(false);
salaryOpen = signal(false);
toggle() { toggle() {
this.collapsed.update(v => !v); this.collapsed.update(v => !v);
@@ -31,6 +32,10 @@ export class SidebarService {
this.accountsOpen.update(v => !v); this.accountsOpen.update(v => !v);
} }
toggleSalary() {
this.salaryOpen.update(v => !v);
}
toggleFlyout(name: string) { toggleFlyout(name: string) {
this.openFlyout.update(current => current === name ? null : name); this.openFlyout.update(current => current === name ? null : name);
} }
+61 -1
View File
@@ -144,7 +144,67 @@
"calendar": "Kalender", "calendar": "Kalender",
"accounts": "Konten", "accounts": "Konten",
"revenue_accounts": "Einnahmekonten", "revenue_accounts": "Einnahmekonten",
"transactions": "Transaktionen" "transactions": "Transaktionen",
"salary": "Lohn",
"salarium": "Salarium",
"salary_analyse": "Analyse",
"salary_entwicklung": "Entwicklung"
},
"salarium": {
"title": "Salarium",
"subtitle": "Statistischer Lohnrechner des Bundesamts für Statistik (BFS).",
"open_external": "Im neuen Tab öffnen"
},
"salary_analyse": {
"title": "Lohnanalyse",
"subtitle": "Vergleiche deinen aktuellen Lohn mit dem Marktdurchschnitt.",
"coming_soon_title": "Lohnanalyse in Entwicklung",
"coming_soon_text": "Hier kannst du deinen Lohn mit Branchen- und Regionaldaten vergleichen."
},
"salary_entwicklung": {
"title": "Lohnentwicklung",
"subtitle": "Verfolge deine Lohnentwicklung über die Jahre.",
"coming_soon_title": "Lohnentwicklung in Entwicklung",
"coming_soon_text": "Visualisiere deine Lohnhistorie und erkenne Trends über die Zeit."
},
"lohn_analyse": {
"title": "Lohnanalyse",
"subtitle": "Berechne Brutto- und Nettolohn sowie den Totalaufwand des Arbeitgebers nach Schweizer Recht.",
"section_basics": "Grundlagen",
"label_monatslohn": "Monatslohn",
"label_monate": "Anzahl Monate",
"monate": "Monate",
"label_kanton": "Kanton",
"fak_rate_hint": "FAK-Beitragssatz:",
"label_verfahren": "Abrechnungsverfahren",
"verfahren_ordentlich": "Ordentliches Verfahren",
"verfahren_vereinfacht": "Vereinfachtes Verfahren",
"verfahren_quellensteuer": "Ordentliches mit Quellensteuer",
"verfahren_hint": "Dieses Verfahren wird noch nicht vollständig berechnet — es werden die Standardsätze verwendet.",
"section_optional": "Weitere Sätze (KTV / BU / NBU)",
"optional_hint": "KTV- und Berufsunfall-Sätze sind betriebsabhängig. Leer lassen wenn unbekannt.",
"beitraege_ag": "Arbeitgeber",
"abzuege_an": "Arbeitnehmer",
"ktv": "KTV",
"bu": "BU",
"nbu": "NBU",
"section_ag": "Kosten Arbeitgeber",
"section_an": "Abzüge Arbeitnehmer",
"bruttolohn": "Bruttolohn",
"ahv_iv_eo": "AHV / IV / EO",
"alv": "ALV",
"fak": "FAK",
"vk": "Verwaltungskosten (VK)",
"total_beitraege_ag": "Total Beiträge Arbeitgeber",
"totalaufwand_ag": "Totalaufwand Arbeitgeber",
"total_abzuege_an": "Total Abzüge Arbeitnehmer",
"nettolohn": "Nettolohn",
"col_position": "Position",
"col_satz": "Satz",
"col_monat": "/ Monat",
"col_jahr": "/ Jahr",
"jahr": "Jahr",
"source_hint": "Quelle: Lohnbudget Monatslohn, Bundesamt für Sozialversicherungen (BSV). Gilt für das ordentliche Abrechnungsverfahren."
}, },
"dashboard": { "dashboard": {
"title": "Dashboard", "title": "Dashboard",
+61 -1
View File
@@ -144,7 +144,67 @@
"calendar": "Calendar", "calendar": "Calendar",
"accounts": "Accounts", "accounts": "Accounts",
"revenue_accounts": "Revenue Accounts", "revenue_accounts": "Revenue Accounts",
"transactions": "Transactions" "transactions": "Transactions",
"salary": "Salary",
"salarium": "Salarium",
"salary_analyse": "Analysis",
"salary_entwicklung": "Development"
},
"salarium": {
"title": "Salarium",
"subtitle": "Statistical salary calculator by the Federal Statistical Office (FSO).",
"open_external": "Open in new tab"
},
"salary_analyse": {
"title": "Salary Analysis",
"subtitle": "Compare your current salary with the market average.",
"coming_soon_title": "Salary Analysis in Development",
"coming_soon_text": "Here you will be able to compare your salary with industry and regional benchmarks."
},
"salary_entwicklung": {
"title": "Salary Development",
"subtitle": "Track your salary development over the years.",
"coming_soon_title": "Salary Development in Development",
"coming_soon_text": "Visualize your salary history and identify trends over time."
},
"lohn_analyse": {
"title": "Salary Analysis",
"subtitle": "Calculate gross and net salary as well as the total employer cost under Swiss law.",
"section_basics": "Basics",
"label_monatslohn": "Monthly Salary",
"label_monate": "Number of Months",
"monate": "Months",
"label_kanton": "Canton",
"fak_rate_hint": "FAK contribution rate:",
"label_verfahren": "Billing Method",
"verfahren_ordentlich": "Standard Method",
"verfahren_vereinfacht": "Simplified Method",
"verfahren_quellensteuer": "Standard with Withholding Tax",
"verfahren_hint": "This method is not yet fully calculated — standard rates are used.",
"section_optional": "Additional Rates (KTV / BU / NBU)",
"optional_hint": "KTV and accident insurance rates depend on the employer. Leave empty if unknown.",
"beitraege_ag": "Employer",
"abzuege_an": "Employee",
"ktv": "KTV",
"bu": "BU",
"nbu": "NBU",
"section_ag": "Employer Costs",
"section_an": "Employee Deductions",
"bruttolohn": "Gross Salary",
"ahv_iv_eo": "AHV / IV / EO",
"alv": "ALV",
"fak": "FAK",
"vk": "Admin Costs (VK)",
"total_beitraege_ag": "Total Employer Contributions",
"totalaufwand_ag": "Total Employer Cost",
"total_abzuege_an": "Total Employee Deductions",
"nettolohn": "Net Salary",
"col_position": "Position",
"col_satz": "Rate",
"col_monat": "/ Month",
"col_jahr": "/ Year",
"jahr": "Year",
"source_hint": "Source: Monthly Salary Budget, Federal Social Insurance Office (FSIO). Applies to the standard billing method."
}, },
"dashboard": { "dashboard": {
"title": "Dashboard", "title": "Dashboard",
+61 -1
View File
@@ -144,7 +144,67 @@
"calendar": "Calendrier", "calendar": "Calendrier",
"accounts": "Comptes", "accounts": "Comptes",
"revenue_accounts": "Comptes de revenus", "revenue_accounts": "Comptes de revenus",
"transactions": "Transactions" "transactions": "Transactions",
"salary": "Salaire",
"salarium": "Salarium",
"salary_analyse": "Analyse",
"salary_entwicklung": "Évolution"
},
"salarium": {
"title": "Salarium",
"subtitle": "Calculateur de salaire statistique de l'Office fédéral de la statistique (OFS).",
"open_external": "Ouvrir dans un nouvel onglet"
},
"salary_analyse": {
"title": "Analyse salariale",
"subtitle": "Comparez votre salaire actuel avec la moyenne du marché.",
"coming_soon_title": "Analyse salariale en développement",
"coming_soon_text": "Vous pourrez ici comparer votre salaire avec les données sectorielles et régionales."
},
"salary_entwicklung": {
"title": "Évolution salariale",
"subtitle": "Suivez l'évolution de votre salaire au fil des années.",
"coming_soon_title": "Évolution salariale en développement",
"coming_soon_text": "Visualisez votre historique salarial et identifiez les tendances au fil du temps."
},
"lohn_analyse": {
"title": "Analyse salariale",
"subtitle": "Calculez le salaire brut, net et le coût total de l'employeur selon le droit suisse.",
"section_basics": "Données de base",
"label_monatslohn": "Salaire mensuel",
"label_monate": "Nombre de mois",
"monate": "mois",
"label_kanton": "Canton",
"fak_rate_hint": "Taux de cotisation CAF :",
"label_verfahren": "Procédure de décompte",
"verfahren_ordentlich": "Procédure ordinaire",
"verfahren_vereinfacht": "Procédure simplifiée",
"verfahren_quellensteuer": "Procédure ordinaire avec impôt à la source",
"verfahren_hint": "Cette procédure n'est pas encore entièrement calculée — les taux standard sont utilisés.",
"section_optional": "Taux supplémentaires (IJM / AA)",
"optional_hint": "Les taux IJM et accidents dépendent de l'employeur. Laisser vide si inconnu.",
"beitraege_ag": "Employeur",
"abzuege_an": "Employé",
"ktv": "IJM",
"bu": "AA pro.",
"nbu": "AA non-pro.",
"section_ag": "Coûts employeur",
"section_an": "Déductions employé",
"bruttolohn": "Salaire brut",
"ahv_iv_eo": "AVS / AI / APG",
"alv": "AC",
"fak": "CAF",
"vk": "Frais admin. (VK)",
"total_beitraege_ag": "Total cotisations employeur",
"totalaufwand_ag": "Coût total employeur",
"total_abzuege_an": "Total déductions employé",
"nettolohn": "Salaire net",
"col_position": "Position",
"col_satz": "Taux",
"col_monat": "/ Mois",
"col_jahr": "/ An",
"jahr": "An",
"source_hint": "Source : Budget salaire mensuel, Office fédéral des assurances sociales (OFAS). Valable pour la procédure de décompte ordinaire."
}, },
"dashboard": { "dashboard": {
"title": "Tableau de bord", "title": "Tableau de bord",
+61 -1
View File
@@ -144,7 +144,67 @@
"calendar": "Calendario", "calendar": "Calendario",
"accounts": "Conti", "accounts": "Conti",
"revenue_accounts": "Conti entrate", "revenue_accounts": "Conti entrate",
"transactions": "Transazioni" "transactions": "Transazioni",
"salary": "Stipendio",
"salarium": "Salarium",
"salary_analyse": "Analisi",
"salary_entwicklung": "Sviluppo"
},
"salarium": {
"title": "Salarium",
"subtitle": "Calcolatore statistico degli stipendi dell'Ufficio federale di statistica (UST).",
"open_external": "Apri in una nuova scheda"
},
"salary_analyse": {
"title": "Analisi stipendio",
"subtitle": "Confronta il tuo stipendio attuale con la media di mercato.",
"coming_soon_title": "Analisi stipendio in sviluppo",
"coming_soon_text": "Qui potrai confrontare il tuo stipendio con i dati del settore e della regione."
},
"salary_entwicklung": {
"title": "Sviluppo stipendio",
"subtitle": "Monitora lo sviluppo del tuo stipendio nel tempo.",
"coming_soon_title": "Sviluppo stipendio in sviluppo",
"coming_soon_text": "Visualizza la tua storia salariale e identifica le tendenze nel tempo."
},
"lohn_analyse": {
"title": "Analisi stipendio",
"subtitle": "Calcola lo stipendio lordo, netto e il costo totale del datore di lavoro secondo il diritto svizzero.",
"section_basics": "Dati di base",
"label_monatslohn": "Stipendio mensile",
"label_monate": "Numero di mesi",
"monate": "mesi",
"label_kanton": "Cantone",
"fak_rate_hint": "Aliquota CAF:",
"label_verfahren": "Procedura di conteggio",
"verfahren_ordentlich": "Procedura ordinaria",
"verfahren_vereinfacht": "Procedura semplificata",
"verfahren_quellensteuer": "Procedura ordinaria con imposta alla fonte",
"verfahren_hint": "Questa procedura non è ancora completamente calcolata — vengono utilizzate le aliquote standard.",
"section_optional": "Aliquote aggiuntive (IJM / LAINF)",
"optional_hint": "Le aliquote IJM e infortuni dipendono dal datore di lavoro. Lasciare vuoto se sconosciuto.",
"beitraege_ag": "Datore di lavoro",
"abzuege_an": "Lavoratore",
"ktv": "IJM",
"bu": "LAINF pro.",
"nbu": "LAINF non-pro.",
"section_ag": "Costi datore di lavoro",
"section_an": "Deduzioni lavoratore",
"bruttolohn": "Stipendio lordo",
"ahv_iv_eo": "AVS / AI / IPG",
"alv": "AD",
"fak": "CAF",
"vk": "Spese amm. (VK)",
"total_beitraege_ag": "Totale contributi datore di lavoro",
"totalaufwand_ag": "Costo totale datore di lavoro",
"total_abzuege_an": "Totale deduzioni lavoratore",
"nettolohn": "Stipendio netto",
"col_position": "Posizione",
"col_satz": "Aliquota",
"col_monat": "/ Mese",
"col_jahr": "/ Anno",
"jahr": "Anno",
"source_hint": "Fonte: Budget stipendio mensile, Ufficio federale delle assicurazioni sociali (UFAS). Valido per la procedura di conteggio ordinaria."
}, },
"dashboard": { "dashboard": {
"title": "Dashboard", "title": "Dashboard",