Files
picpeak/backend/src/services/contractService.js
T
Paul Nothaft fdf62dafef refactor(backend): decompose invoiceService, contractService, adminEvents
Move-code split, public entry points unchanged:
- services/invoiceService.js (3,623 -> 99) + services/invoice/* (10 modules)
- services/contractService.js (2,363 -> 83) + services/contract/* (6 modules)
- routes/adminEvents.js -> routes/adminEvents/* (crud, slideshow, resets,
  archive/bulk, logo); route registration order verified identical
Lazy cross-service requires preserved to keep the module graph acyclic.
2026-07-03 08:10:25 +02:00

84 lines
3.2 KiB
JavaScript

/**
* contractService — orchestrates the lifecycle of `contracts`, their
* `contract_block_inclusions` (which blocks from the library make it
* onto a given contract), and the public `contract_action_tokens` used
* by the customer's signing link.
*
* Contracts are an INDEPENDENT document type alongside quotes and
* invoices. Composition model:
* - Admin picks blocks from the `contract_blocks` library and toggles
* them on/off per section (basics → scope → privacy → commercial →
* nda → closing). Order within a section is admin-controlled.
* - On send, every included block's body is FROZEN into
* `body_text_snapshot` on the inclusion row, so future edits to
* the source block don't mutate already-sent contracts.
*
* Signing:
* 1. Customer opens /contract/:token and either:
* a) Types name, optionally draws a signature on canvas, ticks
* "I have read and agree", submits → recordCustomerSignature
* stamps the signature into a re-rendered PDF and the system
* emails the admin.
* b) Uploads a wet-signed PDF → attachSignedPdfUpload sets the
* signed_pdf_path as the authoritative copy.
* 2. Admin counter-signs (in-browser or by re-uploading the
* double-signed PDF) → status flips to `fully_signed`.
*
* Bodies support {{placeholders}} resolved at PDF/preview render time
* using the same Handlebars-lite regex that emailProcessor.safeTemplateReplace
* uses. We rebuild it inline here (not exported from emailProcessor) to
* keep the dependency tree shallow and so contracts can render
* client-side previews in the future without pulling the email
* processor.
*/
//
// Decomposed into ./contract/* modules (move-code refactor). This file is the
// stable public entry point: same require path, same exported names.
const helpers = require('./contract/helpers');
const renderContext = require('./contract/renderContext');
const crud = require('./contract/crud');
const sending = require('./contract/sending');
const signatures = require('./contract/signatures');
const conversions = require('./contract/conversions');
const { SECTIONS_ORDER, nextContractNumber } = helpers;
const { renderTemplatedBody, buildPlaceholderContext, buildRenderContext } = renderContext;
const {
listContracts, getContractById, createContract, updateContract, cancelContract,
} = crud;
const { renderContractPdfBuffer, sendContract } = sending;
const {
recordCustomerSignature, recordAdminCountersignature, attachSignedPdfUpload,
rerenderAndResend, restampSignatures, getAuditTrail, verifyIntegrity,
} = signatures;
const { createFromQuote, convertToEvent, convertToInvoiceOnly } = conversions;
module.exports = {
listContracts,
getContractById,
createContract,
updateContract,
sendContract,
renderContractPdfBuffer,
recordCustomerSignature,
recordAdminCountersignature,
attachSignedPdfUpload,
cancelContract,
createFromQuote,
convertToEvent,
convertToInvoiceOnly,
rerenderAndResend,
restampSignatures,
getAuditTrail,
verifyIntegrity,
// Exported for tests + the public-route preview endpoint.
_internal: {
nextContractNumber,
renderTemplatedBody,
buildPlaceholderContext,
buildRenderContext,
SECTIONS_ORDER,
},
};