Employment types¶
Italian labor law recognises three categories of subordinate employment that differ
in their legal duration, INPS contribution rules, and salary computation. Each maps
to a concrete type in ccnl_engine.inputs passed as Employment.contract_type.
The field has no default: the contract type selects the NASpI surcharge and the
art. 13 TUIR deduction minimum, so it is always stated.
See CCNL Components for the legal background on fixed-term and Apprenticeship for how the apprendistato professionalizzante contract works under Italian law.
Permanent (tempo indeterminato)¶
The standard open-ended employment contract under Art. 2094 c.c. No duration limit; full INPS rates apply. This is the baseline from which all other types deviate.
"""Quickstart: compute one payroll period with PayrollEngine."""
from datetime import date
from ccnl_engine import (
EmployerProfile,
Employment,
Headcount,
PayrollEngine,
PayrollRun,
PeriodInput,
)
from ccnl_engine.inputs import Permanent
engine = PayrollEngine.bundled()
result = engine.calculate_period(
PeriodInput(
run=PayrollRun.regular(year=2026, month=1),
payment_date=date(2026, 1, 28),
employment=Employment(
ccnl_slug="commercio-confcommercio.json",
level_code="4",
contract_type=Permanent(),
),
employer=EmployerProfile(headcount=Headcount(50)),
)
)
print(f"Gross: {result.period_gross} EUR")
print(f"Net: {result.period_net} EUR")
print(f"Cost: {result.period_employer_cost} EUR")
print(f"Payable: {result.is_payable} ({len(result.blockers)} blockers)")
# Readiness of the CCNL ruleset: the same before a run and in the result.
ruleset = engine.inspect_ruleset("commercio-confcommercio")
print(f"CCNL: {ruleset.id} is {ruleset.readiness}")
Fixed-term (tempo determinato)¶
Governed by D.Lgs. 81/2015. Duration is capped (36 months, with acausale extensions). Gross and net pay are unchanged relative to a permanent worker at the same level; the employer owes the NASpI surcharge on the INPS side, as a disincentive to systematic precarious use.
NASpI surcharge¶
L. 92/2012 art. 2 (Normattiva, text in force on 7 October 2026):
- c. 28: a contribution of 1.4% of the INPS base, charged to the employer on every non-permanent employment, "aumentato di 0,5 punti percentuali in occasione di ciascun rinnovo del contratto a tempo determinato, anche in regime di somministrazione" (period added by D.L. 87/2018 art. 3 c. 2). Each renewal adds 0.5 points to the rate of the previous contract: 1.4%, 1.9%, 2.4%, 2.9% (INPS circ. 121/2019 par. 2.3). A prorogation is not a renewal, and renewals signed before 14 July 2018 are not counted (same circular, par. 2.2 and 2.3). The increase does not apply to domestic work.
- c. 29: not charged for workers hired to replace absent workers (lett. a), for the seasonal activities of DPR 1525/1963 and of the Bolzano CCNLs of lett. b-bis (lett. b), for apprentices (lett. c), for the public administrations of art. 1 c. 2 D.Lgs. 165/2001 (lett. d) and for the services of at most three days in tourism and public establishments of D.Lgs. 81/2015 art. 29 c. 2 lett. b (lett. d-bis).
- c. 3: the whole article does not apply to the operai agricoli a tempo determinato o indeterminato.
- D.L. 87/2018 art. 1 c. 3: research and teaching contracts of private universities and research bodies keep the 1.4% but not the renewal increase.
FixedTerm carries the two facts of the contract:
| Field | Meaning | None |
|---|---|---|
renewals |
Renewals with the same employer counted from 14 July 2018, this contract being the last | Unknown: a run that owes the increase has a missing_fact renewals blocker |
naspi_exclusion |
NaspiExclusion.NONE, REPLACEMENT, SEASONAL, PUBLIC_ADMINISTRATION, SHORT_SERVICE or RESEARCH (1.4% without the increase) |
Unknown: a run whose sector charges the surcharge has a missing_fact naspi_exclusion blocker |
Apprentices are not an exclusion of FixedTerm: an apprenticeship is an
Apprentice contract, which never pays the surcharge. The operai agricoli
are not either: the agricoltura ruleset exempts the category operaio, so a
level that leaves the category open has a missing_fact category blocker.
While a fact is missing, the amounts charge the surcharge as for a contract
with no exclusion and no renewal, and the inps_employer decision has no
amount. The decision records naspi_surcharge (charged, excluded,
exempt_category), naspi_surcharge_rate and naspi_renewals.
Not modelled: the refund of the surcharge on a conversion to a permanent contract or a permanent hire within six months (c. 30), and the reading of the seasonal exclusion for activities that only a CCNL calls seasonal (INPS messages 269/2025 and 483/2025): the caller states the exclusion.
"""Fixed-term employment renewed once: the NASpI surcharge is 1.4% + 0.5%."""
from datetime import date
from ccnl_engine import (
EmployerProfile,
Employment,
Headcount,
PayrollEngine,
PayrollRun,
PeriodInput,
)
from ccnl_engine.inputs import FixedTerm, NaspiExclusion
engine = PayrollEngine.bundled()
result = engine.calculate_period(
PeriodInput(
run=PayrollRun.regular(year=2026, month=3),
payment_date=date(2026, 3, 27),
employment=Employment(
ccnl_slug="commercio-confcommercio.json",
level_code="4",
# Second fixed-term contract with the employer, no exclusion of
# L. 92/2012 art. 2 c. 29: surcharge 1.4% + 0.5% (c. 28).
contract_type=FixedTerm(renewals=1, naspi_exclusion=NaspiExclusion.NONE),
),
employer=EmployerProfile(headcount=Headcount(50)),
)
)
print(f"Period gross: {result.period_gross}")
print(f"Period net: {result.period_net}")
print(f"Employer cost (includes the NASpI surcharge): {result.period_employer_cost}")
(employer,) = (d for d in result.decisions if d.capability == "inps_employer")
print(f"NASpI surcharge rate: {employer.inputs['naspi_surcharge_rate']}")
Apprenticeship (apprendistato)¶
The apprendistato professionalizzante (Art. 44 D.Lgs. 81/2015) is a dual-purpose contract: the employer trains the worker toward a target qualification (qualifica) while benefiting from reduced INPS rates and a below-minimum starting salary. Duration and pay progression are set by the CCNL, not by statute.
The Apprentice type covers both salary tracks used by Italian CCNLs:
- Percentage track: pay = % of destination level minimum, stepped by elapsed months. Used by most industrial CCNLs (Metalmeccanico, Chimica, Edilizia).
- Under-classification track: the apprentice is placed at a lower level for each phase. Used by most tertiary CCNLs (Commercio, Turismo).
The engine automatically detects which track the loaded CCNL uses.
"""Apprenticeship: reduced salary percentage from CCNL percentage track."""
from datetime import date
from ccnl_engine import (
EmployerProfile,
Employment,
Headcount,
PayrollEngine,
PayrollRun,
PeriodInput,
)
from ccnl_engine.inputs import Apprentice
engine = PayrollEngine.bundled()
def apprentice_period(months_elapsed: int) -> PeriodInput:
"""Build a Commercio level 4 apprentice run for January 2026.
Returns:
The period input at ``months_elapsed`` months of apprenticeship.
"""
return PeriodInput(
run=PayrollRun.regular(year=2026, month=1),
payment_date=date(2026, 1, 28),
employment=Employment(
ccnl_slug="commercio-confcommercio.json",
level_code="4",
contract_type=Apprentice(months_elapsed=months_elapsed),
),
employer=EmployerProfile(headcount=Headcount(50)),
)
# Month 0: start of apprenticeship (lowest percentage)
result_start = engine.calculate_period(apprentice_period(0))
# Month 24: further into the track (higher percentage)
result_24 = engine.calculate_period(apprentice_period(24))
print(f"Gross at month 0: {result_start.period_gross}")
print(f"Gross at month 24: {result_24.period_gross}")
API reference: Permanent, FixedTerm,
Apprentice