This commit is contained in:
seb
2026-10-04 11:25:17 +02:00
parent e5ff4ef307
commit 43fa8ea105
24 changed files with 1016 additions and 214 deletions

View File

@@ -32,10 +32,21 @@ function parseCompanyId(id) {
return { companyId };
}
function requireSelectedCompany(req) {
const id = req.user?.selectedCompanyId;
if (id == null) {
const err = new Error('No company selected');
err.statusCode = 400;
throw err;
}
return id;
}
function companyErrorStatus(error) {
if (error.message === 'Company not found') return 404;
if (error.message === 'Company name not available') return 409;
if (error.message === 'Invalid tax rate') return 400;
if (error.message === 'Invalid tax rate' || error.message === 'Text required' || error.message === 'No company selected') return 400;
if (error.message === 'Not authorized') return 403;
return 500;
}
@@ -167,6 +178,170 @@ export function register(app, basePath = '') {
}
});
/**
* @openapi
* /company/history:
* get:
* tags: [Company]
* summary: Get company history
* description: |
* Returns `companyhistory` rows for the selected company, newest first (`date` DESC, `id` DESC).
* security:
* - cookieAuth: []
* x-markdown: company/history.md
* parameters:
* - in: query
* name: limit
* required: false
* schema:
* type: integer
* minimum: 1
* maximum: 1000
* description: Maximum number of entries to return (newest first). Omit for all entries.
* responses:
* 200:
* description: Company history (newest first)
* content:
* application/json:
* schema:
* type: object
* required: [history, truncated]
* properties:
* history:
* type: array
* items:
* $ref: '#/components/schemas/CompanyHistoryEntry'
* truncated:
* type: boolean
* description: True when a limit was applied and the result set was exactly that size (more entries may exist).
* 400:
* description: Invalid limit or no company selected
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 401:
* description: Unauthorized
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 403:
* description: Forbidden
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 500:
* description: Server error
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* post:
* tags: [Company]
* summary: Add company history entry
* description: |
* Appends an entry to `companyhistory` for the selected company.
* security:
* - cookieAuth: []
* x-markdown: company/addHistory.md
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [text]
* properties:
* text:
* type: string
* description: History entry text or note
* responses:
* 201:
* description: History entry created
* content:
* application/json:
* schema:
* type: object
* required: [history]
* properties:
* history:
* $ref: '#/components/schemas/CompanyHistoryEntry'
* 400:
* description: Text required or no company selected
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 401:
* description: Unauthorized
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 403:
* description: Forbidden
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* 500:
* description: Server error
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
*/
app.get(`${basePath}/company/history`, async (req, res) => {
try {
requireSelectedCompany(req);
} catch (error) {
return res.status(error.statusCode || 400).json({ error: error.message });
}
const rawLimit = req.query.limit;
let limit = null;
if (rawLimit !== undefined) {
limit = parseInt(rawLimit, 10);
if (!Number.isInteger(limit) || limit < 1 || limit > 1000) {
return res.status(400).json({ error: 'limit must be an integer between 1 and 1000' });
}
}
try {
const history = await db.getCompanyHistory(req.user.id, limit != null ? { limit } : undefined);
const truncated = limit != null && history.length === limit;
res.status(200).json({ history, truncated });
} catch (error) {
const status = companyErrorStatus(error);
if (status === 500) console.error('Get company history failed:', error);
res.status(status).json({ error: error.message });
}
});
app.post(`${basePath}/company/history`, async (req, res) => {
try {
requireSelectedCompany(req);
} catch (error) {
return res.status(error.statusCode || 400).json({ error: error.message });
}
const text = typeof req.body?.text === 'string' ? req.body.text.trim() : '';
if (!text) {
return res.status(400).json({ error: 'text is required' });
}
try {
const entry = await db.addCompanyHistory(req.user.id, { text });
res.status(201).json({ history: entry });
} catch (error) {
const status = companyErrorStatus(error);
if (status === 500) console.error('Add company history failed:', error);
res.status(status).json({ error: error.message });
}
});
/**
* @openapi
* /company/{id}: