@getexeer/saudi-eosb 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/package.json +66 -0
- package/src/index.d.ts +57 -0
- package/src/index.js +152 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Exeer (إكسير) — https://getexeer.com
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# saudi-eosb
|
|
2
|
+
|
|
3
|
+
End-of-service benefits for Saudi Arabia — **مكافأة نهاية الخدمة** — under Articles 84 and 85 of the Saudi Labour Law.
|
|
4
|
+
|
|
5
|
+
Zero dependencies. No build step. Runs in Node 18+ and in the browser.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { calculateEosb, REASON } from "@getexeer/saudi-eosb";
|
|
9
|
+
|
|
10
|
+
calculateEosb({ wage: 5000, years: 7, reason: REASON.RESIGNATION }).amount;
|
|
11
|
+
// 15000
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## What it implements
|
|
15
|
+
|
|
16
|
+
**Article 84 — the base award.** Half a month's wage for each of the first five years of service, and one full month's wage for each year after that, calculated on the **last** wage.
|
|
17
|
+
|
|
18
|
+
**Article 85 — what resignation actually pays.** The base award is reduced on resignation:
|
|
19
|
+
|
|
20
|
+
| Length of service | Entitlement |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| Under 2 years | None |
|
|
23
|
+
| 2 to under 5 years | One third of the base award |
|
|
24
|
+
| 5 to under 10 years | Two thirds of the base award |
|
|
25
|
+
| 10 years or more | The full base award |
|
|
26
|
+
|
|
27
|
+
Termination by the employer pays the full base award.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install @getexeer/saudi-eosb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Usage
|
|
36
|
+
|
|
37
|
+
### From a length of service you already have
|
|
38
|
+
|
|
39
|
+
```js
|
|
40
|
+
import { calculateEosb, REASON } from "@getexeer/saudi-eosb";
|
|
41
|
+
|
|
42
|
+
const result = calculateEosb({
|
|
43
|
+
wage: 9000, // last monthly wage, in SAR
|
|
44
|
+
years: 6.5, // decimal years of service
|
|
45
|
+
reason: REASON.TERMINATION, // or REASON.RESIGNATION
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
result.amount; // 58500 — the settlement
|
|
49
|
+
result.baseAmount; // 58500 — Article 84 award before the Article 85 fraction
|
|
50
|
+
result.baseMonths; // 6.5 — months of wage
|
|
51
|
+
result.factor; // 1
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### From two dates
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
const result = calculateEosb({
|
|
58
|
+
wage: 7500,
|
|
59
|
+
startDate: "2018-03-01",
|
|
60
|
+
endDate: "2026-09-01",
|
|
61
|
+
reason: REASON.RESIGNATION,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
result.years; // 8.50… decimal years used in the calculation
|
|
65
|
+
result.service.wholeYears; // 8
|
|
66
|
+
result.service.months; // 6
|
|
67
|
+
result.service.days; // 0
|
|
68
|
+
result.amount; // two thirds of the base award
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### The pieces on their own
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
import { baseAward, resignationFactor, serviceLength } from "@getexeer/saudi-eosb";
|
|
75
|
+
|
|
76
|
+
baseAward({ wage: 5000, years: 10 });
|
|
77
|
+
// { firstYears: 5, laterYears: 5, baseMonths: 7.5, amount: 37500 }
|
|
78
|
+
|
|
79
|
+
resignationFactor(7);
|
|
80
|
+
// { factor: 0.666…, label: "Two thirds (5 to under 10 years)", labelAr: "ثلثا المكافأة (5 إلى أقل من 10)" }
|
|
81
|
+
|
|
82
|
+
serviceLength("2020-01-01", "2023-07-01");
|
|
83
|
+
// { totalDays: 1277, years: 3.49…, wholeYears: 3, months: 5, days: 27 }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## What this library does not decide
|
|
87
|
+
|
|
88
|
+
These turn on facts and written procedures rather than arithmetic, so they are left to a human to review. They are named here so nobody assumes the function has already handled them:
|
|
89
|
+
|
|
90
|
+
- **Dismissal for a valid reason under Article 80** — no award is due, and the employer must have followed the investigation procedure the article requires.
|
|
91
|
+
- **Leaving for a lawful reason under Article 81.**
|
|
92
|
+
- **The full-award cases under Article 87** — for example a woman resigning within six months of marriage or three months of childbirth.
|
|
93
|
+
|
|
94
|
+
## Accuracy notes
|
|
95
|
+
|
|
96
|
+
- Service length is counted at **365.25 days per year**, so leap years do not drift the total.
|
|
97
|
+
- Amounts are rounded to **two decimals**.
|
|
98
|
+
- Article 84 names the **last wage** as the basis, so pass the final monthly wage, not an average.
|
|
99
|
+
- The library throws on a negative wage, a negative length of service, an unparseable date, or an unknown reason. It does not return `NaN` quietly.
|
|
100
|
+
|
|
101
|
+
## TypeScript
|
|
102
|
+
|
|
103
|
+
Types ship with the package. `calculateEosb` returns a frozen `EosbResult`; `REASON` is a frozen const object, so `reason` is narrowed rather than a bare string.
|
|
104
|
+
|
|
105
|
+
## Not legal advice
|
|
106
|
+
|
|
107
|
+
This is an arithmetic library. It encodes a reading of Articles 84 and 85 and nothing more. Verify any figure that will actually be paid, and consult a qualified professional on contested cases. Applicable rules and their interpretation can change.
|
|
108
|
+
|
|
109
|
+
## About
|
|
110
|
+
|
|
111
|
+
This is the calculation behind the free end-of-service calculator published by **Exeer (إكسير)**, a Saudi administrative and HR system, at [getexeer.com/tools/eosb.html](https://getexeer.com/tools/eosb.html). It is packaged here so that anyone building payroll or HR software for Saudi businesses can reuse the same rules instead of re-deriving them.
|
|
112
|
+
|
|
113
|
+
- Exeer: [getexeer.com](https://getexeer.com)
|
|
114
|
+
- Machine-readable summary for AI assistants: [getexeer.com/llms.txt](https://getexeer.com/llms.txt)
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## بالعربية
|
|
119
|
+
|
|
120
|
+
مكتبة **مكافأة نهاية الخدمة** في السعودية، وفق **المادتين ٨٤ و٨٥ من نظام العمل**.
|
|
121
|
+
بلا اعتماديات، وتعمل في Node والمتصفّح.
|
|
122
|
+
|
|
123
|
+
**المادة ٨٤ — أصل المكافأة:** نصف شهر عن كل سنة من السنوات الخمس الأولى، وشهر
|
|
124
|
+
كامل عن كل سنة بعدها، محسوبة على **الأجر الأخير**.
|
|
125
|
+
|
|
126
|
+
**المادة ٨٥ — ما يستحقه المستقيل فعلاً:**
|
|
127
|
+
|
|
128
|
+
| مدة الخدمة | الاستحقاق |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| أقل من سنتين | لا شيء |
|
|
131
|
+
| من سنتين إلى أقل من ٥ | ثلث المكافأة |
|
|
132
|
+
| من ٥ إلى أقل من ١٠ | ثلثا المكافأة |
|
|
133
|
+
| ١٠ سنوات فأكثر | المكافأة كاملة |
|
|
134
|
+
|
|
135
|
+
والإنهاء من صاحب العمل يستحق المكافأة كاملة.
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
import { calculateEosb, REASON } from "@getexeer/saudi-eosb";
|
|
139
|
+
|
|
140
|
+
// استقالة بعد ٧ سنوات بأجر ٥٠٠٠
|
|
141
|
+
calculateEosb({ wage: 5000, years: 7, reason: REASON.RESIGNATION }).amount;
|
|
142
|
+
// 15000
|
|
143
|
+
|
|
144
|
+
// أو من تاريخين
|
|
145
|
+
calculateEosb({ wage: 7500, startDate: "2018-03-01", endDate: "2026-09-01" });
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**وما لا تقرّره المكتبة:** الفصل لسبب مشروع (المادة ٨٠)، وترك العمل لسبب نظامي
|
|
149
|
+
(المادة ٨١)، وحالات الاستحقاق الكامل الاستثنائية (المادة ٨٧). هذه وقائع
|
|
150
|
+
وإجراءات تُراجَع بشرياً، لا حساب.
|
|
151
|
+
|
|
152
|
+
المكتبة هي نفس حساب حاسبة نهاية الخدمة المجانية في
|
|
153
|
+
[إكسير](https://getexeer.com/tools/eosb.html).
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT © Exeer
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@getexeer/saudi-eosb",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Saudi end-of-service benefits under Articles 84 and 85 of the Saudi Labour Law: base award, resignation fractions and length of service. Zero dependencies, works in Node and the browser.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"import": "./src/index.js",
|
|
12
|
+
"default": "./src/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"saudi-arabia",
|
|
22
|
+
"saudi-labor-law",
|
|
23
|
+
"end-of-service",
|
|
24
|
+
"eosb",
|
|
25
|
+
"eosb-calculator",
|
|
26
|
+
"gratuity",
|
|
27
|
+
"indemnity",
|
|
28
|
+
"settlement",
|
|
29
|
+
"resignation",
|
|
30
|
+
"payroll",
|
|
31
|
+
"hr",
|
|
32
|
+
"hrms",
|
|
33
|
+
"gosi",
|
|
34
|
+
"qiwa",
|
|
35
|
+
"mudad",
|
|
36
|
+
"ksa",
|
|
37
|
+
"arabic",
|
|
38
|
+
"rtl",
|
|
39
|
+
"نظام-العمل",
|
|
40
|
+
"مكافأة-نهاية-الخدمة"
|
|
41
|
+
],
|
|
42
|
+
"author": {
|
|
43
|
+
"name": "Exeer",
|
|
44
|
+
"email": "hello@getexeer.com"
|
|
45
|
+
},
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"url": "git+https://github.com/Exeerkit/saudi-eosb.git",
|
|
49
|
+
"type": "git"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://getexeer.com/tools/eosb.html",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/Exeerkit/saudi-eosb/issues"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18"
|
|
57
|
+
},
|
|
58
|
+
"sideEffects": false,
|
|
59
|
+
"scripts": {
|
|
60
|
+
"test": "node --test",
|
|
61
|
+
"demo": "node examples/node.mjs"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Why the employment ended. Only the distinction that changes the amount. */
|
|
2
|
+
export declare const REASON: Readonly<{
|
|
3
|
+
RESIGNATION: "resignation";
|
|
4
|
+
TERMINATION: "termination";
|
|
5
|
+
}>;
|
|
6
|
+
|
|
7
|
+
export type EosbReason = (typeof REASON)[keyof typeof REASON];
|
|
8
|
+
|
|
9
|
+
export type ServiceLength = Readonly<{
|
|
10
|
+
totalDays: number;
|
|
11
|
+
years: number;
|
|
12
|
+
wholeYears: number;
|
|
13
|
+
months: number;
|
|
14
|
+
days: number;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
export type BaseAward = Readonly<{
|
|
18
|
+
firstYears: number;
|
|
19
|
+
laterYears: number;
|
|
20
|
+
baseMonths: number;
|
|
21
|
+
amount: number;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
export type Fraction = Readonly<{ factor: number; label: string; labelAr: string }>;
|
|
25
|
+
|
|
26
|
+
export type EosbResult = Readonly<{
|
|
27
|
+
reason: EosbReason;
|
|
28
|
+
years: number;
|
|
29
|
+
wage: number;
|
|
30
|
+
baseMonths: number;
|
|
31
|
+
baseAmount: number;
|
|
32
|
+
factor: number;
|
|
33
|
+
factorLabel: string;
|
|
34
|
+
factorLabelAr: string;
|
|
35
|
+
amount: number;
|
|
36
|
+
service: ServiceLength | null;
|
|
37
|
+
}>;
|
|
38
|
+
|
|
39
|
+
/** Length of service in years, plus a year/month/day breakdown. */
|
|
40
|
+
export declare function serviceLength(startDate: Date | string | number, endDate?: Date | string | number): ServiceLength;
|
|
41
|
+
|
|
42
|
+
/** The Article 84 base award, before any resignation fraction. */
|
|
43
|
+
export declare function baseAward(input: { wage: number; years: number }): BaseAward;
|
|
44
|
+
|
|
45
|
+
/** The Article 85 fraction applied to the base award on resignation. */
|
|
46
|
+
export declare function resignationFactor(years: number): Fraction;
|
|
47
|
+
|
|
48
|
+
/** The full end-of-service settlement. */
|
|
49
|
+
export declare function calculateEosb(input: {
|
|
50
|
+
wage: number;
|
|
51
|
+
years?: number;
|
|
52
|
+
startDate?: Date | string | number;
|
|
53
|
+
endDate?: Date | string | number;
|
|
54
|
+
reason?: EosbReason;
|
|
55
|
+
}): EosbResult;
|
|
56
|
+
|
|
57
|
+
export default calculateEosb;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saudi end-of-service benefits (مكافأة نهاية الخدمة).
|
|
3
|
+
*
|
|
4
|
+
* Articles 84 and 85 of the Saudi Labour Law:
|
|
5
|
+
*
|
|
6
|
+
* Article 84 sets the base award: half a month's wage for each of the first
|
|
7
|
+
* five years of service, and one full month's wage for each year after that,
|
|
8
|
+
* calculated on the last wage.
|
|
9
|
+
*
|
|
10
|
+
* Article 85 sets what the worker actually receives on **resignation**: none
|
|
11
|
+
* under two years of service, one third from two to under five years, two
|
|
12
|
+
* thirds from five to under ten, and the full award at ten years or more.
|
|
13
|
+
*
|
|
14
|
+
* What this library deliberately does NOT decide: dismissal for a valid reason
|
|
15
|
+
* under Article 80 (no award), and the special full-award cases under Article
|
|
16
|
+
* 87 (a woman resigning within six months of marriage or three months of
|
|
17
|
+
* childbirth) and leaving for a lawful reason under Article 81. Those turn on
|
|
18
|
+
* facts and procedures, not arithmetic, and are left to a human to review.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Why the employment ended. Only the distinction that changes the amount. */
|
|
22
|
+
export const REASON = Object.freeze({
|
|
23
|
+
/** The worker resigned. Article 85 fractions apply. */
|
|
24
|
+
RESIGNATION: "resignation",
|
|
25
|
+
/** The employer terminated. The full Article 84 award is due. */
|
|
26
|
+
TERMINATION: "termination",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const DAYS_PER_YEAR = 365.25;
|
|
30
|
+
const DAYS_PER_MONTH = 30.44;
|
|
31
|
+
|
|
32
|
+
/** Rounds to two decimals without the float drift of `toFixed` arithmetic. */
|
|
33
|
+
function money(value) {
|
|
34
|
+
return Math.round((value + Number.EPSILON) * 100) / 100;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function assertFiniteNumber(value, name) {
|
|
38
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
39
|
+
throw new TypeError(`${name} must be a finite number`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Length of service in years as a decimal, plus a year/month/day breakdown.
|
|
45
|
+
*
|
|
46
|
+
* Uses 365.25 days per year so leap years do not drift the total, matching how
|
|
47
|
+
* the end-of-service calculator on getexeer.com counts service.
|
|
48
|
+
*/
|
|
49
|
+
export function serviceLength(startDate, endDate = new Date()) {
|
|
50
|
+
const start = startDate instanceof Date ? startDate : new Date(startDate);
|
|
51
|
+
const end = endDate instanceof Date ? endDate : new Date(endDate);
|
|
52
|
+
if (Number.isNaN(start.getTime())) throw new TypeError("startDate is not a valid date");
|
|
53
|
+
if (Number.isNaN(end.getTime())) throw new TypeError("endDate is not a valid date");
|
|
54
|
+
if (end < start) throw new RangeError("endDate must not be before startDate");
|
|
55
|
+
|
|
56
|
+
const totalDays = (end - start) / 86_400_000;
|
|
57
|
+
const years = totalDays / DAYS_PER_YEAR;
|
|
58
|
+
|
|
59
|
+
const wholeYears = Math.floor(years);
|
|
60
|
+
const remainderDays = totalDays - Math.round(wholeYears * DAYS_PER_YEAR);
|
|
61
|
+
const months = Math.floor(remainderDays / DAYS_PER_MONTH);
|
|
62
|
+
const days = Math.max(0, remainderDays - Math.round(months * DAYS_PER_MONTH));
|
|
63
|
+
|
|
64
|
+
return Object.freeze({ totalDays, years, wholeYears, months, days });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The Article 84 base award, before any resignation fraction is applied.
|
|
69
|
+
*
|
|
70
|
+
* @returns {{ baseMonths: number, firstYears: number, laterYears: number, amount: number }}
|
|
71
|
+
*/
|
|
72
|
+
export function baseAward({ wage, years }) {
|
|
73
|
+
assertFiniteNumber(wage, "wage");
|
|
74
|
+
assertFiniteNumber(years, "years");
|
|
75
|
+
if (wage < 0) throw new RangeError("wage must not be negative");
|
|
76
|
+
if (years < 0) throw new RangeError("years must not be negative");
|
|
77
|
+
|
|
78
|
+
const firstYears = Math.min(years, 5);
|
|
79
|
+
const laterYears = Math.max(years - 5, 0);
|
|
80
|
+
const baseMonths = firstYears * 0.5 + laterYears;
|
|
81
|
+
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
firstYears,
|
|
84
|
+
laterYears,
|
|
85
|
+
baseMonths,
|
|
86
|
+
amount: money(baseMonths * wage),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The Article 85 fraction applied to the base award on resignation.
|
|
92
|
+
*
|
|
93
|
+
* @returns {{ factor: number, label: string, labelAr: string }}
|
|
94
|
+
*/
|
|
95
|
+
export function resignationFactor(years) {
|
|
96
|
+
assertFiniteNumber(years, "years");
|
|
97
|
+
if (years < 2) return Object.freeze({ factor: 0, label: "Not due (under 2 years)", labelAr: "لا تُستحق (الخدمة أقل من سنتين)" });
|
|
98
|
+
if (years < 5) return Object.freeze({ factor: 1 / 3, label: "One third (2 to under 5 years)", labelAr: "ثلث المكافأة (من سنتين إلى أقل من 5)" });
|
|
99
|
+
if (years < 10) return Object.freeze({ factor: 2 / 3, label: "Two thirds (5 to under 10 years)", labelAr: "ثلثا المكافأة (من 5 إلى أقل من 10)" });
|
|
100
|
+
return Object.freeze({ factor: 1, label: "Full award (10 years or more)", labelAr: "المكافأة كاملة (10 سنوات فأكثر)" });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The end-of-service settlement.
|
|
105
|
+
*
|
|
106
|
+
* Pass either `years` (a decimal) or `startDate`, not both. `wage` is the last
|
|
107
|
+
* monthly wage in SAR, which is the basis Article 84 names.
|
|
108
|
+
*
|
|
109
|
+
* @returns {Readonly<{
|
|
110
|
+
* reason: string, years: number, wage: number,
|
|
111
|
+
* baseMonths: number, baseAmount: number,
|
|
112
|
+
* factor: number, factorLabel: string, factorLabelAr: string,
|
|
113
|
+
* amount: number, service: object | null
|
|
114
|
+
* }>}
|
|
115
|
+
*/
|
|
116
|
+
export function calculateEosb({ wage, years, startDate, endDate, reason = REASON.TERMINATION } = {}) {
|
|
117
|
+
assertFiniteNumber(wage, "wage");
|
|
118
|
+
if (wage < 0) throw new RangeError("wage must not be negative");
|
|
119
|
+
|
|
120
|
+
if (reason !== REASON.RESIGNATION && reason !== REASON.TERMINATION) {
|
|
121
|
+
throw new RangeError(`reason must be "${REASON.RESIGNATION}" or "${REASON.TERMINATION}"`);
|
|
122
|
+
}
|
|
123
|
+
if (years === undefined && startDate === undefined) {
|
|
124
|
+
throw new TypeError("pass either years or startDate");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const service = startDate === undefined ? null : serviceLength(startDate, endDate);
|
|
128
|
+
const servedYears = years === undefined ? service.years : years;
|
|
129
|
+
assertFiniteNumber(servedYears, "years");
|
|
130
|
+
if (servedYears < 0) throw new RangeError("years must not be negative");
|
|
131
|
+
|
|
132
|
+
const base = baseAward({ wage, years: servedYears });
|
|
133
|
+
const fraction =
|
|
134
|
+
reason === REASON.RESIGNATION
|
|
135
|
+
? resignationFactor(servedYears)
|
|
136
|
+
: Object.freeze({ factor: 1, label: "Full award (termination)", labelAr: "المكافأة كاملة (إنهاء من صاحب العمل)" });
|
|
137
|
+
|
|
138
|
+
return Object.freeze({
|
|
139
|
+
reason,
|
|
140
|
+
years: servedYears,
|
|
141
|
+
wage,
|
|
142
|
+
baseMonths: base.baseMonths,
|
|
143
|
+
baseAmount: base.amount,
|
|
144
|
+
factor: fraction.factor,
|
|
145
|
+
factorLabel: fraction.label,
|
|
146
|
+
factorLabelAr: fraction.labelAr,
|
|
147
|
+
amount: money(base.amount * fraction.factor),
|
|
148
|
+
service,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default calculateEosb;
|