---
title: "Change Fiscal Year Shape"
method: POST
path: "/companies/{company_id}/accounting-settings/fiscal-year-shape"
tags: ["accounting-settings"]
---

# Change Fiscal Year Shape

`POST /companies/{company_id}/accounting-settings/fiscal-year-shape`

## Request

- Base URL: `https://app.zeno-works.ch` (the document declares no server; this is the origin it was published from)
- URL: `https://app.zeno-works.ch/companies/{company_id}/accounting-settings/fiscal-year-shape`
- Auth: none declared

Plan — or perform — a deliberate re-cut of the fiscal-year shape (accountant, ZET-306).

``PUT /accounting-settings`` **refuses** a change to ``fiscal_year_start_month`` or
``base_currency`` once the company has any ``gl_fiscal_year`` row, because nothing there
re-cuts the existing years: one request would otherwise silently reinterpret how a whole
tenant's books are divided into years. This route is the remedy that refusal names, and a
refusal with no remedy beside it is a dead end rather than protection — onboarding
auto-creates a Jan–Dec year and the accountant may only afterwards discover the client's
books run Jul–Jun.

**``apply`` defaults to ``false``, and that is the design.** The default answer is the
*plan*: every existing year with the window it has and the window it would get, or —
when the re-cut is refused — the blockers **with their counts**, so the caller is told
what is in the way rather than only that something is. Confirming with ``apply=true``
deletes the (unreferenced) posting periods, rewrites the year windows, writes the
settings and records who did it and why (``gl_fiscal_year_shape_change``).

Refused with **409** whenever the books are not empty — any journal entry, a sealed year,
a closed period, a VAT period, a period-derived artefact, or an irregular (stub/short)
year whose window is not the twelve-month formula window. Re-cutting a year that holds
postings moves entries between fiscal years, and with them the opening balances and any
VAT already filed; that is an accounting migration and is deliberately not attempted
here. **422** when the request changes nothing.

``scoped_tx`` rather than ``db.tx()``, deliberately: this route writes ``gl_fiscal_year``
rows guarded by a GIST exclusion constraint, and ``api/deps.py``'s ``IntegrityError``
mapping only runs under ``scoped_tx``. Under ``db.tx()`` a raced constraint violation
would answer 500 — the same defect ZET-296 recorded for
``GET …/accounting-settings?fiscal_year=``.

## Path parameters

- `company_id` string, required

## Headers

- `authorization` string, nullable

## Cookies

- `access_token` string, nullable

## Request body

- FiscalYearShapeChangeIn: ``POST /companies/{id}/accounting-settings/fiscal-year-shape`` body (ZET-306). The deliberate re-cut that ``PUT /accounting-settings`` refuses a shape change in favour of. **``apply`` defaults to ``False`` on purpose**: the default answer is the plan — what the change would do to every existing fiscal year, or what stands in its way — and the caller has to opt in to performing it. That is the mechanical form of "states up front what it will do"; a route that re-cut on the first call would have no such moment. ``reason`` is required for the same reason ``admin_action_audit.reason`` is: the trail says what changed, the reason says why someone decided it should.
  - `fiscal_year_start_month` integer, nullable
  - `base_currency` string, nullable
  - `reason` string, required
  - `apply` boolean

## Response `200`

Successful Response

- FiscalYearShapePlanOut: What the re-cut would do (``applied=False``) or did (``applied=True``).
  - `company_id` string, required
  - `old_fiscal_year_start_month` integer, required
  - `new_fiscal_year_start_month` integer, required
  - `old_base_currency` string, nullable
  - `new_base_currency` string, nullable
  - `years` FiscalYearShapePlannedYearOut[]
    - `year_label` integer, required
    - `old_date_from` string, date, required
    - `old_date_to` string, date, required
    - `new_date_from` string, date, required
    - `new_date_to` string, date, required
  - `blockers` FiscalYearShapeBlockerOut[]
    - `kind` string, required
    - `count` integer, required
    - `detail` string, required
  - `periods_deleted` integer
  - `allowed` boolean, required
  - `applied` boolean, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/zeno-works/apis/zeno-api.md) · [All operations](https://skmtc.dev/zeno-works/apis/zeno-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zeno-works/apis/zeno-api/revisions/1eb93dde146c?raw)
