@thecodecodex/tools 0.0.0-stage → 0.1.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 +44 -2
- package/package.json +46 -4
- package/src/base64.ts +36 -0
- package/src/br/cnpj.ts +55 -0
- package/src/br/cpf.ts +61 -0
- package/src/cli/main.ts +93 -0
- package/src/cli/tools.ts +643 -0
- package/src/compensation-comparator.ts +124 -0
- package/src/css-playground.ts +699 -0
- package/src/dev-ref.ts +295 -0
- package/src/diff.ts +301 -0
- package/src/finance/clt.ts +189 -0
- package/src/finance/compound.ts +78 -0
- package/src/finance/pj.ts +146 -0
- package/src/fov.ts +145 -0
- package/src/hash.ts +13 -0
- package/src/image-to-pdf.ts +299 -0
- package/src/invoice-pdf.ts +134 -0
- package/src/invoice.ts +94 -0
- package/src/jwt.ts +44 -0
- package/src/market.ts +49 -0
- package/src/password.ts +51 -0
- package/src/pdf-studio.ts +453 -0
- package/src/random/index.ts +19 -0
- package/src/regex.ts +426 -0
- package/src/skills.json +60 -0
- package/src/skills.ts +52 -0
- package/src/state-holidays.ts +94 -0
- package/src/table-convert/code.ts +99 -0
- package/src/table-convert/index.ts +42 -0
- package/src/table-convert/model.ts +72 -0
- package/src/table-convert/parse.ts +242 -0
- package/src/table-convert/serialize.ts +435 -0
- package/src/table-convert/transform.ts +133 -0
- package/src/table-convert/xlsx.ts +262 -0
- package/src/table-convert/zip.ts +127 -0
- package/src/timestamp.ts +50 -0
- package/src/url-codec.ts +25 -0
- package/src/uuid.ts +53 -0
- package/src/vacation.ts +171 -0
package/src/vacation.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Planejador de férias CLT: feriados nacionais, regra de início (CLT art. 134, §3º) e fracionamento (art. 134, §1º).
|
|
2
|
+
// Datas são tratadas como dias do calendário em UTC ("YYYY-MM-DD") para não sofrer com fuso/horário de verão.
|
|
3
|
+
|
|
4
|
+
import { stateHolidaysFor, type Uf } from "./state-holidays"
|
|
5
|
+
|
|
6
|
+
export type Iso = string
|
|
7
|
+
|
|
8
|
+
const DAY = 86_400_000
|
|
9
|
+
export const toIso = (date: Date): Iso => date.toISOString().slice(0, 10)
|
|
10
|
+
export const fromIso = (iso: Iso): Date => new Date(`${iso}T00:00:00Z`)
|
|
11
|
+
export const addDays = (iso: Iso, days: number): Iso => toIso(new Date(fromIso(iso).getTime() + days * DAY))
|
|
12
|
+
const weekday = (iso: Iso) => fromIso(iso).getUTCDay() // 0 = domingo
|
|
13
|
+
|
|
14
|
+
// Domingo de Páscoa (algoritmo de Meeus/Jones/Butcher, calendário gregoriano)
|
|
15
|
+
export function easter(year: number): Iso {
|
|
16
|
+
const a = year % 19
|
|
17
|
+
const b = Math.floor(year / 100)
|
|
18
|
+
const c = year % 100
|
|
19
|
+
const d = Math.floor(b / 4)
|
|
20
|
+
const e = b % 4
|
|
21
|
+
const f = Math.floor((b + 8) / 25)
|
|
22
|
+
const g = Math.floor((b - f + 1) / 3)
|
|
23
|
+
const h = (19 * a + b - d - g + 15) % 30
|
|
24
|
+
const i = Math.floor(c / 4)
|
|
25
|
+
const k = c % 4
|
|
26
|
+
const l = (32 + 2 * e + 2 * i - h - k) % 7
|
|
27
|
+
const m = Math.floor((a + 11 * h + 22 * l) / 451)
|
|
28
|
+
const month = Math.floor((h + l - 7 * m + 114) / 31)
|
|
29
|
+
const day = ((h + l - 7 * m + 114) % 31) + 1
|
|
30
|
+
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type HolidayKind = "national" | "optional" | "state" | "custom"
|
|
34
|
+
|
|
35
|
+
export interface Holiday {
|
|
36
|
+
date: Iso
|
|
37
|
+
name: string
|
|
38
|
+
kind: HolidayKind
|
|
39
|
+
// feriados estaduais: lei e fonte
|
|
40
|
+
law?: string
|
|
41
|
+
source?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Feriados nacionais (Lei 662/1949, Lei 6.802/1980, Lei 14.759/2023) e pontos facultativos usuais
|
|
45
|
+
export function nationalHolidays(year: number): Holiday[] {
|
|
46
|
+
const e = easter(year)
|
|
47
|
+
const fixed: [string, string][] = [
|
|
48
|
+
["01-01", "Confraternização Universal"],
|
|
49
|
+
["04-21", "Tiradentes"],
|
|
50
|
+
["05-01", "Dia do Trabalho"],
|
|
51
|
+
["09-07", "Independência"],
|
|
52
|
+
["10-12", "Nossa Senhora Aparecida"],
|
|
53
|
+
["11-02", "Finados"],
|
|
54
|
+
["11-15", "Proclamação da República"],
|
|
55
|
+
["11-20", "Consciência Negra"],
|
|
56
|
+
["12-25", "Natal"],
|
|
57
|
+
]
|
|
58
|
+
return [
|
|
59
|
+
...fixed.map(([md, name]) => ({ date: `${year}-${md}`, name, kind: "national" as const })),
|
|
60
|
+
{ date: addDays(e, -2), name: "Sexta-feira Santa", kind: "national" as const },
|
|
61
|
+
{ date: addDays(e, -48), name: "Carnaval (segunda)", kind: "optional" as const },
|
|
62
|
+
{ date: addDays(e, -47), name: "Carnaval (terça)", kind: "optional" as const },
|
|
63
|
+
{ date: addDays(e, 60), name: "Corpus Christi", kind: "optional" as const },
|
|
64
|
+
].sort((a, b) => a.date.localeCompare(b.date))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 5x2: folga sábado e domingo (repouso semanal no domingo). 6x1: folga só no domingo.
|
|
68
|
+
export type Schedule = "5x2" | "6x1"
|
|
69
|
+
|
|
70
|
+
export interface PlannerInput {
|
|
71
|
+
year: number
|
|
72
|
+
days: number
|
|
73
|
+
schedule: Schedule
|
|
74
|
+
// pontos facultativos (Carnaval, Corpus Christi) contam como folga?
|
|
75
|
+
includeOptional: boolean
|
|
76
|
+
// feriados locais ou folgas já combinadas
|
|
77
|
+
extraDaysOff: Iso[]
|
|
78
|
+
// estado para incluir os feriados estaduais
|
|
79
|
+
uf?: Uf | undefined
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface Suggestion {
|
|
83
|
+
start: Iso
|
|
84
|
+
end: Iso
|
|
85
|
+
// descanso contínuo, incluindo fins de semana e feriados colados
|
|
86
|
+
restStart: Iso
|
|
87
|
+
restEnd: Iso
|
|
88
|
+
restDays: number
|
|
89
|
+
holidays: Holiday[]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function holidaysFor(input: Pick<PlannerInput, "year" | "includeOptional" | "extraDaysOff" | "uf">): Holiday[] {
|
|
93
|
+
const base = [...nationalHolidays(input.year), ...nationalHolidays(input.year + 1).filter((h) => h.date.endsWith("-01-01"))]
|
|
94
|
+
const state = input.uf ? stateHolidaysFor(input.uf, input.year).map((h) => ({ ...h, kind: "state" as const })) : []
|
|
95
|
+
return [
|
|
96
|
+
...base.filter((h) => input.includeOptional || h.kind !== "optional"),
|
|
97
|
+
...state,
|
|
98
|
+
...input.extraDaysOff.map((date) => ({ date, name: "Folga local", kind: "custom" as const })),
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function makeDayOff(input: Pick<PlannerInput, "schedule">, holidays: Holiday[]) {
|
|
103
|
+
const set = new Set(holidays.map((h) => h.date))
|
|
104
|
+
const isWeeklyRest = (iso: Iso) => (input.schedule === "5x2" ? [0, 6].includes(weekday(iso)) : weekday(iso) === 0)
|
|
105
|
+
// repouso semanal REMUNERADO é o domingo nas duas escalas (no 5x2 o sábado é folga compensada, não DSR)
|
|
106
|
+
const isPaidRestOrHoliday = (iso: Iso) => weekday(iso) === 0 || set.has(iso)
|
|
107
|
+
return { isOff: (iso: Iso) => isWeeklyRest(iso) || set.has(iso), isWeeklyRest, isPaidRestOrHoliday, isHoliday: (iso: Iso) => set.has(iso) }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// CLT art. 134, §3º: as férias não podem começar nos 2 dias que antecedem feriado ou repouso semanal remunerado;
|
|
111
|
+
// também não faz sentido começar num dia que já é folga
|
|
112
|
+
export function canStart(iso: Iso, isOff: (iso: Iso) => boolean, isPaidRestOrHoliday: (iso: Iso) => boolean): boolean {
|
|
113
|
+
return !isOff(iso) && !isPaidRestOrHoliday(addDays(iso, 1)) && !isPaidRestOrHoliday(addDays(iso, 2))
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function suggest(input: PlannerInput, limit = 5, exclude: { start: Iso, end: Iso }[] = []): Suggestion[] {
|
|
117
|
+
const holidays = holidaysFor(input)
|
|
118
|
+
const { isOff, isHoliday, isPaidRestOrHoliday } = makeDayOff(input, holidays)
|
|
119
|
+
const days = Math.min(30, Math.max(1, Math.round(input.days)))
|
|
120
|
+
const results: Suggestion[] = []
|
|
121
|
+
for (let start = `${input.year}-01-01`; start <= `${input.year}-12-31`; start = addDays(start, 1)) {
|
|
122
|
+
if (!canStart(start, isOff, isPaidRestOrHoliday)) continue
|
|
123
|
+
const end = addDays(start, days - 1)
|
|
124
|
+
// não pode sobrepor períodos já escolhidos (com 1 dia de folga entre eles)
|
|
125
|
+
if (exclude.some((p) => !(end < addDays(p.start, -1) || start > addDays(p.end, 1)))) continue
|
|
126
|
+
let restStart = start
|
|
127
|
+
while (isOff(addDays(restStart, -1))) restStart = addDays(restStart, -1)
|
|
128
|
+
let restEnd = end
|
|
129
|
+
while (isOff(addDays(restEnd, 1))) restEnd = addDays(restEnd, 1)
|
|
130
|
+
const restDays = Math.round((fromIso(restEnd).getTime() - fromIso(restStart).getTime()) / DAY) + 1
|
|
131
|
+
const inside = holidays.filter((h) => h.date >= restStart && h.date <= restEnd && isHoliday(h.date))
|
|
132
|
+
results.push({ start, end, restStart, restEnd, restDays, holidays: inside })
|
|
133
|
+
}
|
|
134
|
+
// mais descanso primeiro; empate: mais feriados aproveitados, depois a data mais cedo
|
|
135
|
+
results.sort((a, b) => b.restDays - a.restDays || b.holidays.length - a.holidays.length || a.start.localeCompare(b.start))
|
|
136
|
+
// evita sugestões quase iguais (mesmo bloco de descanso)
|
|
137
|
+
const picked: Suggestion[] = []
|
|
138
|
+
for (const s of results) {
|
|
139
|
+
if (picked.some((p) => !(s.restEnd < p.restStart || s.restStart > p.restEnd))) continue
|
|
140
|
+
picked.push(s)
|
|
141
|
+
if (picked.length === limit) break
|
|
142
|
+
}
|
|
143
|
+
return picked
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Fracionamento (CLT art. 134, §1º): até 3 períodos, um com 14+ dias e os demais com 5+ dias
|
|
147
|
+
export function isValidSplit(parts: number[]): boolean {
|
|
148
|
+
if (parts.length < 1 || parts.length > 3) return false
|
|
149
|
+
if (parts.some((p) => p < 5)) return false
|
|
150
|
+
return parts.length === 1 || parts.some((p) => p >= 14)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const SPLITS: number[][] = [[30], [15, 15], [20, 10], [14, 8, 8], [14, 11, 5], [20, 5, 5], [14, 6, 10]].filter(isValidSplit)
|
|
154
|
+
|
|
155
|
+
export interface SplitPlan {
|
|
156
|
+
parts: Suggestion[]
|
|
157
|
+
restDays: number
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Escolhe o melhor período para cada parte, da maior para a menor, sem sobreposição
|
|
161
|
+
export function planSplit(input: Omit<PlannerInput, "days">, parts: number[]): SplitPlan | undefined {
|
|
162
|
+
if (!isValidSplit(parts)) return undefined
|
|
163
|
+
const chosen: Suggestion[] = []
|
|
164
|
+
for (const days of [...parts].sort((a, b) => b - a)) {
|
|
165
|
+
const best = suggest({ ...input, days }, 1, chosen)[0]
|
|
166
|
+
if (!best) return undefined
|
|
167
|
+
chosen.push(best)
|
|
168
|
+
}
|
|
169
|
+
chosen.sort((a, b) => a.start.localeCompare(b.start))
|
|
170
|
+
return { parts: chosen, restDays: chosen.reduce((sum, s) => sum + s.restDays, 0) }
|
|
171
|
+
}
|