Skip to content

Second-level bargaining

Company or territorial agreements may add allowances on top of the national CCNL minimum. Second-level amounts are not an input of the engine: there is no model for a caller-supplied second-level allowance, and a CCNL data file carries only the allowances of the national agreement. An amount agreed at company level that must be paid can be declared today only through the work events of the run, for example a BonusEvent, with the treatment of that event.

See Domain: Second-level bargaining for the legal background.

Company agreements and the calendar

A company agreement can change when an extra month is paid, or grant more than the CCNL. The year calculation accepts such a calendar only as a CalendarOverride with its reason, validated against the CCNL calendar:

"""Full-year run: standard calendar derived from the CCNL, then an override.

Without ``calendar_override`` the engine runs the calendar the CCNL grants.  A custom
calendar is accepted only as a ``CalendarOverride`` with a domain reason; an
override that drops or lowers a CCNL extra month raises ``InvalidInputError``.
"""

from ccnl_engine import (
    CompetenceYearPlan,
    EmployerProfile,
    Employment,
    Headcount,
    InvalidInputError,
    PayrollEngine,
)
from ccnl_engine.inputs import CalendarOverride, CalendarOverrideReason, WorkCalendar
from ccnl_engine.inputs import Permanent

engine = PayrollEngine.bundled()


def year_request(calendar: CalendarOverride | None = None) -> CompetenceYearPlan:
    """Build a Commercio level 4 input for 2026.

    Returns:
        The year input, with ``calendar`` as the optional override.
    """
    return CompetenceYearPlan(
        year=2026,
        employment=Employment(
            ccnl_slug="commercio-confcommercio.json",
            level_code="4",
            contract_type=Permanent(),
        ),
        employer=EmployerProfile(headcount=Headcount(50)),
        calendar_override=calendar,
    )


# Standard calendar: tredicesima in December, quattordicesima in June.
result = engine.calculate_competence_year(year_request())

print(f"Annual gross:        {result.annual_gross}")
print(f"Annual net:          {result.annual_net}")
print(f"Annual employer cost:{result.annual_employer_cost}")
print(f"Runs computed:       {len(result.period_results)}")

for r in result.period_results:
    run_label = r.run.run_id if r.run else str(r.period_id)
    print(f"  {run_label:30s}  gross={r.period_gross}")

# Same entitlement, quattordicesima paid with the July salary.
july = CalendarOverride(
    calendar=WorkCalendar.from_additional_months(2026, 14, fourteenth_payment_month=7),
    reason=CalendarOverrideReason.PAYMENT_MONTH,
    note="quattordicesima paid with the July salary",
)
moved = engine.calculate_competence_year(year_request(july))
print(f"Runs with July quattordicesima: {len(moved.period_results)}")

# Dropping the quattordicesima is rejected whatever the reason.
only_thirteenth = CalendarOverride(
    calendar=WorkCalendar.from_additional_months(2026, 13),
    reason=CalendarOverrideReason.PAYMENT_MONTH,
    note="attempt to skip the quattordicesima",
)
try:
    engine.calculate_competence_year(year_request(only_thirteenth))
except InvalidInputError as exc:
    print(f"Rejected: {exc}")

Note

A premio di risultato paid under a second-level agreement is a BonusEvent with kind="productivity_bonus". Its 1% substitute tax needs the prior-year employment income in PriorYearTaxFacts; without it the bonus is taxed at ordinary rates.

API reference: CompetenceYearPlan, CalendarOverride