Files
dashfront/src/automats/companyAutomat.js
2026-10-04 22:47:26 +02:00

114 lines
4.1 KiB
JavaScript

/**
* @file companyAutomat.js
* @description Manages configuration details of the currently selected company
* (tax rates, currency, language, legal details, and root category ID).
*
* Upstream Triggers:
* - `authAutomat`: Subscribes to auth state changes — triggers `actions.load()`
* when status is `'authed'` with a `selectedCompanyId`, and `actions.reset()` on `'anon'`.
* - UI / Settings: Calls `update(payload)` or manual `load()`.
*
* Downstream Triggers:
* - `categoryAutomat`: Subscribes to `companyAutomat` — triggers `actions.loadTree()`
* whenever `company.current.id` or `rootCategoryId` changes/becomes available;
* resets when company is unset.
* - `posterminalAutomat`: Reads `companyAutomat.state.current?.id` as a fallback company ID.
*/
import { Automat } from 'react-automat';
import { company as companyApi } from '../api';
import authAutomat from './authAutomat';
import { normalizeCompany } from '../utils/companyForm';
/**
* @typedef {Object} CompanyState
* @property {object|null} current Normalized company object for the selected company.
* @property {'idle'|'loading'|'succeeded'|'failed'} status
* @property {string|null} error
*/
/** @type {CompanyState} */
const initialState = {
current: null,
status: 'idle',
error: null,
};
let companyAutomat;
const actions = {
/**
* Load the company data for the currently selected company.
* @returns {Promise<object|null>}
*/
async load() {
const companyId = authAutomat.state.user?.selectedCompanyId;
if (!companyId) return null;
companyAutomat.setState({ status: 'loading', error: null });
try {
const data = await companyApi.getById(companyId);
const normalized = normalizeCompany(data.company);
companyAutomat.setState({ current: normalized, status: 'succeeded' });
return normalized;
} catch (err) {
companyAutomat.setState({ status: 'failed', error: err?.message ?? String(err) });
throw err;
}
},
/**
* Update company details and refresh cached data.
* @param {object} payload
*/
async update(payload) {
const companyId = authAutomat.state.user?.selectedCompanyId;
if (!companyId) throw new Error('Kein Unternehmen ausgewählt.');
companyAutomat.setState({ status: 'loading', error: null });
try {
await companyApi.update(companyId, payload);
const data = await companyApi.getById(companyId);
const normalized = normalizeCompany(data.company, payload);
companyAutomat.setState({ current: normalized, status: 'succeeded' });
return normalized;
} catch (err) {
companyAutomat.setState({ status: 'failed', error: err?.message ?? String(err) });
throw err;
}
},
/** Reset to idle (called on logout / company switch). */
reset() {
companyAutomat.setState(initialState);
},
};
/**
* Global company state container.
* Loads automatically when authAutomat reaches 'authed' with a selectedCompanyId.
*
* @type {Automat<CompanyState>}
*/
companyAutomat = new Automat(initialState, actions, { name: 'company' });
// Subscribe to auth changes — load when authed, reset when logged out / anon
authAutomat.subscribe((auth) => {
const companyId = auth.user?.selectedCompanyId;
if (auth.status === 'authed' && companyId) {
void companyAutomat.actions.load();
} else if (auth.status === 'anon') {
companyAutomat.actions.reset();
}
});
// Selectors (plain functions for use outside React)
export const selectCurrentCompany = () => companyAutomat.state.current;
export const selectCompanyId = () => companyAutomat.state.current?.id ?? null;
export const selectCompanyStatus = () => companyAutomat.state.status;
export const selectCompanyName = () => companyAutomat.state.current?.name;
export const selectCompanyIso639 = () => companyAutomat.state.current?.iso639;
export const selectCompanyTaxrate = () => companyAutomat.state.current?.taxrate ?? null;
export const selectCompanyReducedTaxrate = () => companyAutomat.state.current?.reducedtaxrate ?? null;
export const selectCompanyRootCategoryId = () => companyAutomat.state.current?.rootCategoryId ?? null;
export default companyAutomat;