Metadata-Version: 2.4
Name: nexterp-declarations
Version: 0.1.1
Summary: Romanian ANAF declarations: pure-Python schemas + generators (private)
Author-email: NextERP Romania <contact@nexterp.ro>
License: Proprietary
Project-URL: Homepage, https://www.nexterp.ro
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial :: Accounting
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: pydantic>=2.5
Requires-Dist: lxml>=5.0
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: ruff; extra == "dev"

# nexterp-anaf-declarations

Romanian ANAF declarations — **one repo, two halves of the same product**:

| Layer | Lives in | Purpose |
|---|---|---|
| **Library** (`nexterp_declarations`) | [`src/`](src/), [`tests/`](tests/), [`tools/`](tools/) | Pure-Python pip wheel: schemas + generators that turn Odoo / CLI inputs into ANAF XML. Cython-compiled. |
| **Validator service** | [`validator/`](validator/) | FastAPI app that XSD-validates generated XML, forwards to DUKIntegrator.jar, and runs the watcher that detects new ANAF schema versions. |

Both halves share the same declaration code: the validator imports the library via the in-repo `-e ..` install, so working-copy edits to the library are reflected immediately in the running service.

```
                    ┌──────────────────────────┐
   Odoo / CLI ─────►│  nexterp_declarations    │──► XML bytes
                    │  (library, this repo)    │
                    └──────────┬───────────────┘
                               │
                               ▼
                    ┌──────────────────────────┐
                    │  validator/  (FastAPI)   │──► XSD validate
                    │  - watcher (Phase 2)     │──► DUK forward
                    │  - diff publisher        │──► Diff JSON feed
                    └──────────────────────────┘
                               │
                               ▼
              tools/gen_version_skeleton.py (Phase 3)
            opens PR when watcher reports a new version
```

## Library layout

```
src/nexterp_declarations/
├── base/                ← Company, Period, Signer, MovesData, CoA, XML helpers
├── d100/  d300/  d394/  ← schema.py + version subdirs (v2 / v6..v12 / v3..v5)
└── …                    ← d101, d120, d390, d406, bilant, intrastat, jurnal — pending
```

Each declaration has:

- a **public Pydantic v2 input schema** (`<decl>/schema.py`) — readable, that's the contract callers map their data to;
- one subdir per ANAF schema version (`<decl>/vN/generator.py`) — Cython-compiled, takes the schema instance and returns the official XML/PDF bytes;
- a `_dispatcher.py` that picks the right version from `inp.period.date_to`.

Newer versions inherit from older ones and override only the bits that changed in that ANAF release.

## Install (development)

```bash
# Library (and its test suite)
pip install -e ".[dev]"
pytest

# Validator service (depends on the library above)
cd validator
pip install -r requirements.txt
uvicorn app.main:app --reload
```

## Build the protected wheel

```bash
BUILD_CYTHON=1 python -m build --wheel
# dist/nexterp_declarations-*.whl ships compiled generators (.so)
```

## Release flow (versioning + publish)

Versioning runs via `setuptools_scm`: the wheel version is read from the
latest `v*` git tag. There are three workflows in `.github/workflows/`
that drive the release pipeline:

| Workflow | Trigger | What it does |
|---|---|---|
| `auto-tag.yml` | push to `main` touching `src/nexterp_declarations/d*/v*/*.py` (or `_dispatcher.py` / `__init__.py` / `schema.py`) | Computes the next patch tag (`vX.Y.Z+1`) from the latest `v*` tag and pushes it. Skip with `[skip release]` in the commit message. |
| `publish-wheel.yml` | every `v*` tag push (manual or auto) | `BUILD_CYTHON=1 python -m build` + smoke-imports the wheel + attaches it as a GitHub Release asset under that tag. |
| `auto-version-skeleton.yml` | daily cron (06:30 UTC) | Pulls the duk watcher's diff JSON feed, runs `tools/gen_version_skeleton.py` and opens a PR for every new ANAF schema version. (Phase 3.) |

### Bumping major / minor manually

Auto-tag only ever bumps the patch component. For a minor or major bump
(e.g. when adopting a new ANAF format that breaks compat), do it by hand:

```bash
git tag v0.2.0
git push --tags
```

`publish-wheel.yml` picks the tag up immediately and builds the wheel.

### What the customer installs

Once a tag is published, the wheel ships through two channels in this order:

1. **Primary:** the nexterp.ro private PyPI (Phase 4 — `publish-wheel.yml` uploads to it automatically).
2. **Backup:** the GitHub Release asset (kept as a fallback if nexterp.ro is unreachable).

For the Odoo manifest constraint `external_dependencies.python = ["nexterp_declarations>=0.1.0"]`, customers install with:

```bash
# Primary -- private PyPI on nexterp.ro, gated by the per-customer license api_key.
# ``__token__`` is the conventional username for PyPI API-token auth; the api_key
# goes in the password slot. URL-quote the key if it contains ``/`` or ``+``.
pip install \
    --index-url "https://__token__:<YOUR_API_KEY>@nexterp.ro/packages/simple/" \
    nexterp-declarations

# Backup -- direct from a GitHub Release (works for public repos, or with a
# gh-pat for private):
pip install \
    "https://github.com/NextERP-Romania/anaf-declarations/releases/download/v0.2.0/nexterp_declarations-0.2.0-cp312-cp312-linux_x86_64.whl"
```

The auto-update cron on the customer's Odoo (`nexterp_license._cron_auto_update_packages`) hits the first URL once the license is refreshed (the api_key is cached on the `nexterp.license` row).

### CI secrets for the nexterp.ro upload step

`publish-wheel.yml` ends with an "Upload wheel to nexterp.ro private PyPI" step that POSTs the freshly-built wheel to `/packages/upload`. It's gated on these four repo secrets (set at `https://github.com/<org>/<repo>/settings/secrets/actions`):

| Secret | Value |
|---|---|
| `NEXTERP_SERVER_URL` | `https://nexterp.ro` (base URL, no trailing slash) |
| `NEXTERP_ADMIN_LOGIN` | admin email on the nexterp.ro Odoo instance |
| `NEXTERP_ADMIN_PASSWORD` | admin password |
| `NEXTERP_DB` | database name on nexterp.ro |

If any of them is unset the upload step is skipped (the workflow does not fail; the GitHub Release stays as the backup channel).

For one-off manual uploads, `tools/upload_release.py` does the same thing from a developer machine.

### End-to-end flow when ANAF publishes a new XSD version

```
ANAF releases D300 v13 XSD
        ↓
duk watcher detects (validator/app/watcher/tick.py)
        ↓
diff JSON published to nexterp.ro/diffs/D300__v13.json
        ↓
auto-version-skeleton.yml (daily) reads it
        ↓
PR opened with src/nexterp_declarations/d300/v13/generator.py skeleton
        ↓
Developer reviews + implements diff + merges
        ↓
auto-tag.yml detects v*/*.py touched → tags v0.X.Y+1
        ↓
publish-wheel.yml builds + attaches to Release
        ↓
Customer Odoo cron → pip install --upgrade → next get_report() uses v13
```

## Operations

- The validator's watcher (`validator/app/watcher/tick.py`) crawls ANAF nightly. When it detects a new XSD version it
  - records the change in `anaf_changes_log`,
  - writes `<diff_publish_dir>/<Decl>__<vN>.json` (Phase 2 — see `validator/app/watcher/diff_publisher.py`),
  - the diff JSON gets served from nexterp.ro.
- `tools/gen_version_skeleton.py` reads a diff JSON and materialises the skeleton class + dispatcher entry inside this repo. GitHub Actions (`.github/workflows/auto-version-skeleton.yml`) runs it daily and opens a PR if anything is new.

## License

Proprietary. Distributed only to NextERP customers with an active subscription.
