@baruk/core 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.baruk-provenance.json +4 -4
- package/dist/cjs/clients/ibge.js +14 -0
- package/dist/cjs/clients/ibge.js.map +1 -1
- package/dist/cjs/clients/registry/http.js +157 -0
- package/dist/cjs/clients/registry/http.js.map +1 -0
- package/dist/cjs/clients/registry/ibge-municipios.js +28 -0
- package/dist/cjs/clients/registry/ibge-municipios.js.map +1 -0
- package/dist/cjs/clients/registry/ibge.js +63 -0
- package/dist/cjs/clients/registry/ibge.js.map +1 -0
- package/dist/cjs/clients/registry/lookup.js +191 -0
- package/dist/cjs/clients/registry/lookup.js.map +1 -0
- package/dist/cjs/clients/registry/provider.js +34 -0
- package/dist/cjs/clients/registry/provider.js.map +1 -0
- package/dist/cjs/clients/registry/resolve.js +47 -0
- package/dist/cjs/clients/registry/resolve.js.map +1 -0
- package/dist/cjs/clients/registry/serpro.js +149 -0
- package/dist/cjs/clients/registry/serpro.js.map +1 -0
- package/dist/cjs/clients/registry/service.js +230 -0
- package/dist/cjs/clients/registry/service.js.map +1 -0
- package/dist/cjs/clients/registry/synthetic.js +165 -0
- package/dist/cjs/clients/registry/synthetic.js.map +1 -0
- package/dist/cjs/clients/repository.js +6 -2
- package/dist/cjs/clients/repository.js.map +1 -1
- package/dist/cjs/db/provision-e2e-identity.js +8 -3
- package/dist/cjs/db/provision-e2e-identity.js.map +1 -1
- package/dist/cjs/fiscal/collection/authorization.js +186 -0
- package/dist/cjs/fiscal/collection/authorization.js.map +1 -0
- package/dist/cjs/fiscal/collection/competence.js +97 -0
- package/dist/cjs/fiscal/collection/competence.js.map +1 -0
- package/dist/cjs/fiscal/collection/first-contact.js +204 -0
- package/dist/cjs/fiscal/collection/first-contact.js.map +1 -0
- package/dist/cjs/fiscal/collection/plan.js +122 -0
- package/dist/cjs/fiscal/collection/plan.js.map +1 -0
- package/dist/cjs/fiscal/coverage/model.js +11 -1
- package/dist/cjs/fiscal/coverage/model.js.map +1 -1
- package/dist/cjs/fiscal/credentials/inspect.js +182 -0
- package/dist/cjs/fiscal/credentials/inspect.js.map +1 -0
- package/dist/cjs/fiscal/credentials/material.js +1 -1
- package/dist/cjs/fiscal/credentials/material.js.map +1 -1
- package/dist/cjs/fiscal/credentials/onboarding.js +266 -0
- package/dist/cjs/fiscal/credentials/onboarding.js.map +1 -0
- package/dist/cjs/fiscal/credentials/readiness.js +29 -2
- package/dist/cjs/fiscal/credentials/readiness.js.map +1 -1
- package/dist/cjs/fiscal/enrollment.js +134 -0
- package/dist/cjs/fiscal/enrollment.js.map +1 -0
- package/dist/cjs/fiscal/transport/synthetic-tls.js +52 -0
- package/dist/cjs/fiscal/transport/synthetic-tls.js.map +1 -1
- package/dist/cjs/index.js +196 -113
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/legal/documents.js +64 -4
- package/dist/cjs/legal/documents.js.map +1 -1
- package/dist/cjs/legal/types.js.map +1 -1
- package/dist/cjs/platform/capabilities.js +29 -0
- package/dist/cjs/platform/capabilities.js.map +1 -1
- package/dist/cjs/rbac/catalog.js +78 -2
- package/dist/cjs/rbac/catalog.js.map +1 -1
- package/dist/cjs/subscription-billing/index.js +8 -1
- package/dist/cjs/subscription-billing/index.js.map +1 -1
- package/dist/cjs/subscription-billing/platform-exemption.js +121 -0
- package/dist/cjs/subscription-billing/platform-exemption.js.map +1 -0
- package/dist/cjs/subscription-billing/service.js +93 -1
- package/dist/cjs/subscription-billing/service.js.map +1 -1
- package/dist/cjs/vault/repository.js +92 -5
- package/dist/cjs/vault/repository.js.map +1 -1
- package/dist/clients/ibge.d.ts.map +1 -1
- package/dist/clients/ibge.js +14 -0
- package/dist/clients/ibge.js.map +1 -1
- package/dist/clients/registry/http.d.ts +61 -0
- package/dist/clients/registry/http.d.ts.map +1 -0
- package/dist/clients/registry/http.js +151 -0
- package/dist/clients/registry/http.js.map +1 -0
- package/dist/clients/registry/ibge-municipios.d.ts +25 -0
- package/dist/clients/registry/ibge-municipios.d.ts.map +1 -0
- package/dist/clients/registry/ibge-municipios.js +25 -0
- package/dist/clients/registry/ibge-municipios.js.map +1 -0
- package/dist/clients/registry/ibge.d.ts +62 -0
- package/dist/clients/registry/ibge.d.ts.map +1 -0
- package/dist/clients/registry/ibge.js +57 -0
- package/dist/clients/registry/ibge.js.map +1 -0
- package/dist/clients/registry/lookup.d.ts +94 -0
- package/dist/clients/registry/lookup.d.ts.map +1 -0
- package/dist/clients/registry/lookup.js +184 -0
- package/dist/clients/registry/lookup.js.map +1 -0
- package/dist/clients/registry/provider.d.ts +124 -0
- package/dist/clients/registry/provider.d.ts.map +1 -0
- package/dist/clients/registry/provider.js +39 -0
- package/dist/clients/registry/provider.js.map +1 -0
- package/dist/clients/registry/resolve.d.ts +36 -0
- package/dist/clients/registry/resolve.d.ts.map +1 -0
- package/dist/clients/registry/resolve.js +44 -0
- package/dist/clients/registry/resolve.js.map +1 -0
- package/dist/clients/registry/serpro.d.ts +57 -0
- package/dist/clients/registry/serpro.d.ts.map +1 -0
- package/dist/clients/registry/serpro.js +143 -0
- package/dist/clients/registry/serpro.js.map +1 -0
- package/dist/clients/registry/service.d.ts +59 -0
- package/dist/clients/registry/service.d.ts.map +1 -0
- package/dist/clients/registry/service.js +225 -0
- package/dist/clients/registry/service.js.map +1 -0
- package/dist/clients/registry/synthetic.d.ts +30 -0
- package/dist/clients/registry/synthetic.d.ts.map +1 -0
- package/dist/clients/registry/synthetic.js +161 -0
- package/dist/clients/registry/synthetic.js.map +1 -0
- package/dist/clients/repository.d.ts +4 -0
- package/dist/clients/repository.d.ts.map +1 -1
- package/dist/clients/repository.js +6 -2
- package/dist/clients/repository.js.map +1 -1
- package/dist/db/provision-e2e-identity.js +8 -3
- package/dist/db/provision-e2e-identity.js.map +1 -1
- package/dist/fiscal/collection/authorization.d.ts +152 -0
- package/dist/fiscal/collection/authorization.d.ts.map +1 -0
- package/dist/fiscal/collection/authorization.js +178 -0
- package/dist/fiscal/collection/authorization.js.map +1 -0
- package/dist/fiscal/collection/competence.d.ts +66 -0
- package/dist/fiscal/collection/competence.d.ts.map +1 -0
- package/dist/fiscal/collection/competence.js +90 -0
- package/dist/fiscal/collection/competence.js.map +1 -0
- package/dist/fiscal/collection/first-contact.d.ts +103 -0
- package/dist/fiscal/collection/first-contact.d.ts.map +1 -0
- package/dist/fiscal/collection/first-contact.js +201 -0
- package/dist/fiscal/collection/first-contact.js.map +1 -0
- package/dist/fiscal/collection/plan.d.ts +74 -0
- package/dist/fiscal/collection/plan.d.ts.map +1 -0
- package/dist/fiscal/collection/plan.js +119 -0
- package/dist/fiscal/collection/plan.js.map +1 -0
- package/dist/fiscal/coverage/model.d.ts +28 -0
- package/dist/fiscal/coverage/model.d.ts.map +1 -1
- package/dist/fiscal/coverage/model.js +11 -1
- package/dist/fiscal/coverage/model.js.map +1 -1
- package/dist/fiscal/credentials/inspect.d.ts +114 -0
- package/dist/fiscal/credentials/inspect.d.ts.map +1 -0
- package/dist/fiscal/credentials/inspect.js +178 -0
- package/dist/fiscal/credentials/inspect.js.map +1 -0
- package/dist/fiscal/credentials/material.js +2 -2
- package/dist/fiscal/credentials/material.js.map +1 -1
- package/dist/fiscal/credentials/onboarding.d.ts +119 -0
- package/dist/fiscal/credentials/onboarding.d.ts.map +1 -0
- package/dist/fiscal/credentials/onboarding.js +259 -0
- package/dist/fiscal/credentials/onboarding.js.map +1 -0
- package/dist/fiscal/credentials/readiness.d.ts +30 -3
- package/dist/fiscal/credentials/readiness.d.ts.map +1 -1
- package/dist/fiscal/credentials/readiness.js +29 -2
- package/dist/fiscal/credentials/readiness.js.map +1 -1
- package/dist/fiscal/enrollment.d.ts +91 -0
- package/dist/fiscal/enrollment.d.ts.map +1 -0
- package/dist/fiscal/enrollment.js +126 -0
- package/dist/fiscal/enrollment.js.map +1 -0
- package/dist/fiscal/transport/synthetic-tls.d.ts +19 -0
- package/dist/fiscal/transport/synthetic-tls.d.ts.map +1 -1
- package/dist/fiscal/transport/synthetic-tls.js +51 -0
- package/dist/fiscal/transport/synthetic-tls.js.map +1 -1
- package/dist/index.d.ts +32 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/legal/documents.d.ts.map +1 -1
- package/dist/legal/documents.js +64 -4
- package/dist/legal/documents.js.map +1 -1
- package/dist/legal/index.d.ts +1 -1
- package/dist/legal/index.d.ts.map +1 -1
- package/dist/legal/types.d.ts +77 -0
- package/dist/legal/types.d.ts.map +1 -1
- package/dist/legal/types.js.map +1 -1
- package/dist/platform/capabilities.d.ts.map +1 -1
- package/dist/platform/capabilities.js +29 -0
- package/dist/platform/capabilities.js.map +1 -1
- package/dist/rbac/catalog.d.ts.map +1 -1
- package/dist/rbac/catalog.js +78 -2
- package/dist/rbac/catalog.js.map +1 -1
- package/dist/subscription-billing/index.d.ts +3 -1
- package/dist/subscription-billing/index.d.ts.map +1 -1
- package/dist/subscription-billing/index.js +2 -1
- package/dist/subscription-billing/index.js.map +1 -1
- package/dist/subscription-billing/platform-exemption.d.ts +89 -0
- package/dist/subscription-billing/platform-exemption.d.ts.map +1 -0
- package/dist/subscription-billing/platform-exemption.js +113 -0
- package/dist/subscription-billing/platform-exemption.js.map +1 -0
- package/dist/subscription-billing/service.d.ts +53 -0
- package/dist/subscription-billing/service.d.ts.map +1 -1
- package/dist/subscription-billing/service.js +92 -1
- package/dist/subscription-billing/service.js.map +1 -1
- package/dist/vault/repository.d.ts +69 -1
- package/dist/vault/repository.d.ts.map +1 -1
- package/dist/vault/repository.js +88 -5
- package/dist/vault/repository.js.map +1 -1
- package/package.json +3 -2
- package/src/db/migrations/0060_fiscal_credential_vault_and_run_authorization.sql +321 -0
- package/src/db/migrations/0061_client_official_registry_provenance.sql +193 -0
- package/src/db/migrations/0062_first_contact_and_capability_truth.sql +119 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { uuidv7 } from '@baruk/shared';
|
|
3
|
+
import { encryptSecret } from '../../vault/crypto.js';
|
|
4
|
+
import { inspectPkcs12, matchIdentity } from './inspect.js';
|
|
5
|
+
/** Impressão digital truncada. 16 hex identificam sem virar um segundo identificador. */
|
|
6
|
+
function shortFingerprint(full) {
|
|
7
|
+
return full.slice(0, 16);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Nome determinístico da credencial no cofre.
|
|
11
|
+
*
|
|
12
|
+
* Determinístico porque a substituição precisa ENCONTRAR o que já existe: o
|
|
13
|
+
* índice único do cofre é `(tenant, cliente, lower(nome))`, e um nome novo a
|
|
14
|
+
* cada upload deixaria uma trilha de chaves privadas órfãs, todas ainda
|
|
15
|
+
* decifráveis, nenhuma referenciada.
|
|
16
|
+
*/
|
|
17
|
+
export function fiscalCertificateCredentialName(clientId, certType) {
|
|
18
|
+
return `fiscal:certificate:${certType}:${clientId}`;
|
|
19
|
+
}
|
|
20
|
+
export async function onboardFiscalCertificate(tx, keyring, tenantId, input) {
|
|
21
|
+
const certType = input.certType ?? 'A1';
|
|
22
|
+
const clients = await tx `
|
|
23
|
+
SELECT id, document_number FROM client
|
|
24
|
+
WHERE id = ${input.clientId} AND deleted_at IS NULL
|
|
25
|
+
`;
|
|
26
|
+
const client = clients[0];
|
|
27
|
+
if (!client)
|
|
28
|
+
return { status: 'rejected', reason: 'client_not_found' };
|
|
29
|
+
const inspection = await inspectPkcs12(input.material, input.passphrase);
|
|
30
|
+
if (inspection.status === 'failed') {
|
|
31
|
+
return { status: 'rejected', reason: inspection.reason };
|
|
32
|
+
}
|
|
33
|
+
const facts = inspection.facts;
|
|
34
|
+
if (facts.expired)
|
|
35
|
+
return { status: 'rejected', reason: 'expired' };
|
|
36
|
+
const identity = matchIdentity(facts.document, client.document_number);
|
|
37
|
+
if (identity === 'mismatch' && !input.identityOverrideReason) {
|
|
38
|
+
return { status: 'rejected', reason: 'identity_mismatch', identityMatch: identity };
|
|
39
|
+
}
|
|
40
|
+
// O envelope é o MESMO que `material.ts` sabe abrir. Um segundo formato aqui
|
|
41
|
+
// significaria que o certificado que entra não é o que o transporte usa.
|
|
42
|
+
const envelope = JSON.stringify({
|
|
43
|
+
format: 'pkcs12',
|
|
44
|
+
material: input.material.toString('base64'),
|
|
45
|
+
...(input.passphrase ? { passphrase: input.passphrase } : {}),
|
|
46
|
+
});
|
|
47
|
+
const credentialName = fiscalCertificateCredentialName(input.clientId, certType);
|
|
48
|
+
const credentialId = await upsertWriteOnlyCredential(tx, keyring, tenantId, {
|
|
49
|
+
name: credentialName,
|
|
50
|
+
clientId: input.clientId,
|
|
51
|
+
secret: envelope,
|
|
52
|
+
expiresAt: facts.validTo,
|
|
53
|
+
actorId: input.actorId,
|
|
54
|
+
metadata: {
|
|
55
|
+
// Metadata do cofre é lida por telas e por trilha: só o que é público no
|
|
56
|
+
// certificado entra aqui.
|
|
57
|
+
fingerprint: shortFingerprint(facts.fingerprintSha256),
|
|
58
|
+
certType,
|
|
59
|
+
subject: facts.subjectCommonName,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const expiresOn = facts.validTo.slice(0, 10);
|
|
63
|
+
const issuedAt = facts.validFrom.slice(0, 10);
|
|
64
|
+
const holderName = facts.subjectCommonName ?? 'Titular não identificado no certificado';
|
|
65
|
+
const existing = await tx `
|
|
66
|
+
SELECT id FROM digital_certificate
|
|
67
|
+
WHERE client_id = ${input.clientId} AND cert_type = ${certType} AND deleted_at IS NULL
|
|
68
|
+
ORDER BY created_at DESC LIMIT 1
|
|
69
|
+
`;
|
|
70
|
+
const override = identity === 'mismatch' ? (input.identityOverrideReason ?? null) : null;
|
|
71
|
+
const overrideBy = override ? input.actorId : null;
|
|
72
|
+
let rows;
|
|
73
|
+
if (existing[0]) {
|
|
74
|
+
rows = await tx `
|
|
75
|
+
UPDATE digital_certificate SET
|
|
76
|
+
holder_name = ${holderName},
|
|
77
|
+
holder_document = ${facts.document ?? client.document_number},
|
|
78
|
+
issued_at = ${issuedAt},
|
|
79
|
+
expires_at = ${expiresOn},
|
|
80
|
+
vault_credential_id = ${credentialId},
|
|
81
|
+
status = 'active',
|
|
82
|
+
fingerprint_sha256 = ${facts.fingerprintSha256},
|
|
83
|
+
subject_common_name = ${facts.subjectCommonName},
|
|
84
|
+
issuer_common_name = ${facts.issuerCommonName},
|
|
85
|
+
serial_tail = ${facts.serialTail},
|
|
86
|
+
certificate_document = ${facts.document},
|
|
87
|
+
identity_match = ${identity},
|
|
88
|
+
identity_override_by = ${overrideBy},
|
|
89
|
+
identity_override_reason = ${override},
|
|
90
|
+
uploaded_by = ${input.actorId},
|
|
91
|
+
uploaded_at = now(),
|
|
92
|
+
-- O material acabou de ser aberto pela inspeção — o mesmo OpenSSL do
|
|
93
|
+
-- handshake real. Isso é a validação LOCAL, e ela é fato deste instante:
|
|
94
|
+
-- senha correta, chave casando com o certificado, titular e datas
|
|
95
|
+
-- conferidos. Ver 1006.
|
|
96
|
+
local_validated_at = now(),
|
|
97
|
+
-- Material novo é uma afirmação nova: o que o ambiente oficial aceitou
|
|
98
|
+
-- foi o certificado ANTERIOR, e herdar a verificação diria que o novo
|
|
99
|
+
-- já funcionou. Ver 1004.
|
|
100
|
+
verified_at = NULL,
|
|
101
|
+
verified_by = NULL,
|
|
102
|
+
remote_verification_provider = NULL,
|
|
103
|
+
remote_verification_last_attempt_at = NULL,
|
|
104
|
+
last_verification_error = NULL,
|
|
105
|
+
updated_at = now()
|
|
106
|
+
WHERE id = ${existing[0].id}
|
|
107
|
+
RETURNING ${tx.unsafe(CERTIFICATE_COLUMNS)}
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
rows = await tx `
|
|
112
|
+
INSERT INTO digital_certificate (
|
|
113
|
+
id, tenant_id, client_id, cert_type, holder_name, holder_document,
|
|
114
|
+
issued_at, expires_at, vault_credential_id, status,
|
|
115
|
+
fingerprint_sha256, subject_common_name, issuer_common_name, serial_tail,
|
|
116
|
+
certificate_document, identity_match, identity_override_by, identity_override_reason,
|
|
117
|
+
uploaded_by, uploaded_at, local_validated_at
|
|
118
|
+
) VALUES (
|
|
119
|
+
${uuidv7()}, current_setting('app.current_tenant')::uuid, ${input.clientId},
|
|
120
|
+
${certType}, ${holderName}, ${facts.document ?? client.document_number},
|
|
121
|
+
${issuedAt}, ${expiresOn}, ${credentialId}, 'active',
|
|
122
|
+
${facts.fingerprintSha256}, ${facts.subjectCommonName}, ${facts.issuerCommonName},
|
|
123
|
+
${facts.serialTail}, ${facts.document}, ${identity}, ${overrideBy}, ${override},
|
|
124
|
+
-- As duas datas coincidem aqui, e é correto: o material só chega a
|
|
125
|
+
-- esta linha porque a inspeção passou.
|
|
126
|
+
${input.actorId}, now(), now()
|
|
127
|
+
)
|
|
128
|
+
RETURNING ${tx.unsafe(CERTIFICATE_COLUMNS)}
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
status: 'stored',
|
|
133
|
+
certificate: mapCertificate(rows[0], { replaced: Boolean(existing[0]) }),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Grava (ou rotaciona) o segredo no cofre com `exposure = 'write_only'`.
|
|
138
|
+
*
|
|
139
|
+
* Não reutiliza `createCredential`/`rotateSecret` de `vault/repository.ts` por
|
|
140
|
+
* um motivo específico: aqueles não conhecem `exposure`, e um certificado
|
|
141
|
+
* gravado por eles nasceria `revealable` — a 0060 recusaria o INSERT, o que
|
|
142
|
+
* está correto, mas a mensagem de erro seria uma violação de CHECK em vez de
|
|
143
|
+
* uma decisão explícita. Aqui a classe do segredo é dita na escrita.
|
|
144
|
+
*/
|
|
145
|
+
async function upsertWriteOnlyCredential(tx, keyring, tenantId, input) {
|
|
146
|
+
const enc = encryptSecret(keyring, tenantId, input.secret);
|
|
147
|
+
const existing = await tx `
|
|
148
|
+
SELECT id FROM vault_credential
|
|
149
|
+
WHERE client_id = ${input.clientId}
|
|
150
|
+
AND lower(name) = lower(${input.name})
|
|
151
|
+
AND deleted_at IS NULL
|
|
152
|
+
LIMIT 1
|
|
153
|
+
`;
|
|
154
|
+
if (existing[0]) {
|
|
155
|
+
await tx `
|
|
156
|
+
UPDATE vault_credential SET
|
|
157
|
+
ciphertext = ${enc.ciphertext}, iv = ${enc.iv}, auth_tag = ${enc.authTag},
|
|
158
|
+
key_version = ${enc.keyVersion}, rotated_at = now(), updated_at = now(),
|
|
159
|
+
updated_by = ${input.actorId}, version = version + 1,
|
|
160
|
+
expires_at = ${input.expiresAt},
|
|
161
|
+
metadata = ${tx.json(input.metadata)},
|
|
162
|
+
exposure = 'write_only'
|
|
163
|
+
WHERE id = ${existing[0].id}
|
|
164
|
+
`;
|
|
165
|
+
return existing[0].id;
|
|
166
|
+
}
|
|
167
|
+
const rows = await tx `
|
|
168
|
+
INSERT INTO vault_credential (
|
|
169
|
+
id, tenant_id, client_id, name, category, provider,
|
|
170
|
+
ciphertext, iv, auth_tag, key_version, metadata, expires_at, created_by, exposure
|
|
171
|
+
) VALUES (
|
|
172
|
+
${uuidv7()}, current_setting('app.current_tenant')::uuid, ${input.clientId},
|
|
173
|
+
${input.name}, 'certificate', 'icp_brasil',
|
|
174
|
+
${enc.ciphertext}, ${enc.iv}, ${enc.authTag}, ${enc.keyVersion},
|
|
175
|
+
${tx.json(input.metadata)}, ${input.expiresAt}, ${input.actorId}, 'write_only'
|
|
176
|
+
)
|
|
177
|
+
RETURNING id
|
|
178
|
+
`;
|
|
179
|
+
return rows[0].id;
|
|
180
|
+
}
|
|
181
|
+
const CERTIFICATE_COLUMNS = `
|
|
182
|
+
id, client_id, cert_type, status, subject_common_name, issuer_common_name,
|
|
183
|
+
serial_tail, fingerprint_sha256, certificate_document, identity_match,
|
|
184
|
+
identity_override_reason, issued_at, expires_at, uploaded_at, verified_at,
|
|
185
|
+
local_validated_at, remote_verification_provider
|
|
186
|
+
`;
|
|
187
|
+
function mapCertificate(r, extra) {
|
|
188
|
+
return {
|
|
189
|
+
id: r.id,
|
|
190
|
+
clientId: r.client_id,
|
|
191
|
+
certType: r.cert_type === 'A3' ? 'A3' : 'A1',
|
|
192
|
+
status: r.status,
|
|
193
|
+
subjectCommonName: r.subject_common_name,
|
|
194
|
+
issuerCommonName: r.issuer_common_name,
|
|
195
|
+
serialTail: r.serial_tail,
|
|
196
|
+
fingerprint: shortFingerprint(r.fingerprint_sha256 ?? ''),
|
|
197
|
+
certificateDocument: r.certificate_document,
|
|
198
|
+
identityMatch: r.identity_match ?? 'indeterminate',
|
|
199
|
+
identityOverrideReason: r.identity_override_reason,
|
|
200
|
+
issuedAt: r.issued_at ? isoDate(r.issued_at) : null,
|
|
201
|
+
expiresOn: isoDate(r.expires_at),
|
|
202
|
+
uploadedAt: r.uploaded_at?.toISOString() ?? null,
|
|
203
|
+
verifiedAt: r.verified_at?.toISOString() ?? null,
|
|
204
|
+
replaced: extra.replaced,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function isoDate(d) {
|
|
208
|
+
return d.toISOString().slice(0, 10);
|
|
209
|
+
}
|
|
210
|
+
/** Os certificados de um cliente, só metadata. É o que a tela lê. */
|
|
211
|
+
export async function listFiscalCertificates(tx, clientId) {
|
|
212
|
+
const rows = await tx `
|
|
213
|
+
SELECT ${tx.unsafe(CERTIFICATE_COLUMNS)}
|
|
214
|
+
FROM digital_certificate
|
|
215
|
+
WHERE client_id = ${clientId} AND deleted_at IS NULL
|
|
216
|
+
ORDER BY expires_at DESC
|
|
217
|
+
`;
|
|
218
|
+
return rows.map((r) => mapCertificate(r, { replaced: false }));
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Revoga o uso do certificado DENTRO deste produto.
|
|
222
|
+
*
|
|
223
|
+
* Não toca a ICP-Brasil, e é por isso que a função se chama assim. Revogar de
|
|
224
|
+
* verdade um certificado é um ato junto à Autoridade Certificadora que o emitiu;
|
|
225
|
+
* apagar a linha daqui apenas garante que este produto pare de apresentá-lo. A
|
|
226
|
+
* tela repete a distinção, porque um operador que acredite ter revogado o
|
|
227
|
+
* certificado do cliente não vai à AC — e o certificado segue válido no mundo.
|
|
228
|
+
*
|
|
229
|
+
* O material sai do cofre no mesmo movimento (`deleted_at`), de modo que a
|
|
230
|
+
* revogação não deixa chave privada decifrável para trás.
|
|
231
|
+
*/
|
|
232
|
+
export async function revokeFiscalCertificate(tx, clientId, certificateId) {
|
|
233
|
+
const rows = await tx `
|
|
234
|
+
UPDATE digital_certificate SET status = 'revoked', deleted_at = now(), updated_at = now()
|
|
235
|
+
WHERE id = ${certificateId} AND client_id = ${clientId} AND deleted_at IS NULL
|
|
236
|
+
RETURNING vault_credential_id
|
|
237
|
+
`;
|
|
238
|
+
if (rows.length === 0)
|
|
239
|
+
return { revoked: false };
|
|
240
|
+
const pointer = rows[0]?.vault_credential_id;
|
|
241
|
+
if (pointer) {
|
|
242
|
+
await tx `
|
|
243
|
+
UPDATE vault_credential SET deleted_at = now()
|
|
244
|
+
WHERE id = ${pointer} AND deleted_at IS NULL
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
return { revoked: true };
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Impressão digital do material recebido, antes de qualquer decisão.
|
|
251
|
+
*
|
|
252
|
+
* Serve à trilha do upload recusado: registrar "tentaram subir algo" sem
|
|
253
|
+
* registrar o quê. É o mesmo cálculo de `material.ts`, e é seguro por
|
|
254
|
+
* construção — um hash não volta a ser um certificado.
|
|
255
|
+
*/
|
|
256
|
+
export function materialFingerprint(material) {
|
|
257
|
+
return shortFingerprint(createHash('sha256').update(material).digest('hex'));
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=onboarding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboarding.js","sourceRoot":"","sources":["../../../src/fiscal/credentials/onboarding.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,15 +36,39 @@ import type { FiscalCredentialKind, FiscalEnvironment } from '../eligibility.js'
|
|
|
36
36
|
* `EXPIRED` venceu. Só a renovação resolve.
|
|
37
37
|
* `INVALID` revogada, ou o ponteiro para o cofre não resolve.
|
|
38
38
|
* `ACCESS_DENIED` o material serve; o ambiente recusou o TITULAR.
|
|
39
|
-
* `
|
|
40
|
-
*
|
|
39
|
+
* `LOCAL_VALID` o material abre, a chave casa, o titular confere e as
|
|
40
|
+
* datas estão na janela — e NENHUM ambiente oficial o
|
|
41
|
+
* aceitou ainda. É o estado em que todo certificado novo
|
|
42
|
+
* nasce, e o único que o primeiro contato admite.
|
|
43
|
+
* `REQUIRES_REVIEW` cadastrada e sem validação local registrada — ou de um
|
|
44
|
+
* tipo que um cron não consegue usar (A3 vive em token com
|
|
45
|
+
* PIN presencial).
|
|
41
46
|
*/
|
|
42
|
-
export type FiscalCredentialState = 'NOT_CONFIGURED' | 'READY' | 'EXPIRED' | 'INVALID' | 'ACCESS_DENIED' | 'REQUIRES_REVIEW';
|
|
47
|
+
export type FiscalCredentialState = 'NOT_CONFIGURED' | 'READY' | 'LOCAL_VALID' | 'EXPIRED' | 'INVALID' | 'ACCESS_DENIED' | 'REQUIRES_REVIEW';
|
|
43
48
|
export interface FiscalCredentialReadiness {
|
|
44
49
|
state: FiscalCredentialState;
|
|
45
50
|
kind: FiscalCredentialKind | null;
|
|
46
51
|
/** Só `READY` autoriza o worker a tentar. Nenhum outro estado, por motivo nenhum. */
|
|
47
52
|
usableByWorker: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* O material passou na verificação LOCAL: abriu com a senha, a chave casa com
|
|
55
|
+
* o certificado, o titular confere e as datas estão na janela.
|
|
56
|
+
*
|
|
57
|
+
* Independente de `usableByWorker` de propósito. Foi a fusão dos dois que
|
|
58
|
+
* produziu o impasse: exigir aceite remoto para executar, quando só executar
|
|
59
|
+
* produz o aceite.
|
|
60
|
+
*/
|
|
61
|
+
localValid: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Esta credencial pode ser usada num PRIMEIRO CONTATO — a execução única,
|
|
64
|
+
* manual e autorizada que existe justamente para obter o aceite remoto.
|
|
65
|
+
*
|
|
66
|
+
* `true` exatamente quando o material é válido localmente e nenhum ambiente o
|
|
67
|
+
* aceitou ainda. Um cron jamais consulta este campo: ele existe para o
|
|
68
|
+
* executor de primeiro contato, e o banco recusa autorização de primeiro
|
|
69
|
+
* contato disparada por agendador.
|
|
70
|
+
*/
|
|
71
|
+
eligibleForFirstContact: boolean;
|
|
48
72
|
/**
|
|
49
73
|
* `true` quando um retry futuro pode mudar o desfecho sem intervenção humana.
|
|
50
74
|
* `EXPIRED`, `INVALID` e `ACCESS_DENIED` são `false` — e é isso que decide se
|
|
@@ -52,7 +76,10 @@ export interface FiscalCredentialReadiness {
|
|
|
52
76
|
*/
|
|
53
77
|
selfHealing: boolean;
|
|
54
78
|
expiresOn: string | null;
|
|
79
|
+
/** Quando o ambiente OFICIAL aceitou. Nunca sai do cadastro. */
|
|
55
80
|
verifiedAt: string | null;
|
|
81
|
+
/** Quando o material passou na verificação local. */
|
|
82
|
+
localValidatedAt: string | null;
|
|
56
83
|
/** sha256 truncado do material, quando registrado. Seguro para tela e trilha. */
|
|
57
84
|
fingerprint: string | null;
|
|
58
85
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../../../src/fiscal/credentials/readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH
|
|
1
|
+
{"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../../../src/fiscal/credentials/readiness.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,qBAAqB,GAC7B,gBAAgB,GAChB,OAAO,GACP,aAAa,GACb,SAAS,GACT,SAAS,GACT,eAAe,GACf,iBAAiB,CAAC;AAEtB,MAAM,WAAW,yBAAyB;IACxC,KAAK,EAAE,qBAAqB,CAAC;IAC7B,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAClC,qFAAqF;IACrF,cAAc,EAAE,OAAO,CAAC;IACxB;;;;;;;OAOG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB;;;;;;;;OAQG;IACH,uBAAuB,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gEAAgE;IAChE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qDAAqD;IACrD,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,iFAAiF;IACjF,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;OAGG;IACH,qBAAqB,EAAE,SAAS,GAAG,aAAa,CAAC;IACjD,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAsBD;;;;;;;;;;GAUG;AACH,wBAAsB,6BAA6B,CACjD,EAAE,EAAE,EAAE,EACN,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,oBAAoB,CAAC;CAC7B,GACA,OAAO,CAAC,yBAAyB,CAAC,CAkJpC;AAWD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,qBAAqB,GAC3B,qBAAqB,GAAG,qBAAqB,GAAG,eAAe,GAAG,IAAI,CAWxE"}
|
|
@@ -21,9 +21,12 @@ export async function readFiscalCredentialReadiness(tx, input) {
|
|
|
21
21
|
const base = {
|
|
22
22
|
kind,
|
|
23
23
|
usableByWorker: false,
|
|
24
|
+
localValid: false,
|
|
25
|
+
eligibleForFirstContact: false,
|
|
24
26
|
selfHealing: false,
|
|
25
27
|
expiresOn: null,
|
|
26
28
|
verifiedAt: null,
|
|
29
|
+
localValidatedAt: null,
|
|
27
30
|
fingerprint: null,
|
|
28
31
|
providerAuthorization: 'unknown',
|
|
29
32
|
};
|
|
@@ -40,6 +43,7 @@ export async function readFiscalCredentialReadiness(tx, input) {
|
|
|
40
43
|
try {
|
|
41
44
|
rows = await tx `
|
|
42
45
|
SELECT id, cert_type, status, expires_at, verified_at,
|
|
46
|
+
local_validated_at, fingerprint_sha256,
|
|
43
47
|
last_verification_error, vault_credential_id
|
|
44
48
|
FROM digital_certificate
|
|
45
49
|
WHERE client_id = ${input.clientId}
|
|
@@ -62,7 +66,14 @@ export async function readFiscalCredentialReadiness(tx, input) {
|
|
|
62
66
|
}
|
|
63
67
|
const expiresOn = cert.expires_at ? isoDate(cert.expires_at) : null;
|
|
64
68
|
const verifiedAt = cert.verified_at?.toISOString() ?? null;
|
|
65
|
-
const
|
|
69
|
+
const localValidatedAt = cert.local_validated_at?.toISOString() ?? null;
|
|
70
|
+
const withDates = {
|
|
71
|
+
...base,
|
|
72
|
+
expiresOn,
|
|
73
|
+
verifiedAt,
|
|
74
|
+
localValidatedAt,
|
|
75
|
+
fingerprint: cert.fingerprint_sha256 ?? null,
|
|
76
|
+
};
|
|
66
77
|
if (cert.status === 'revoked') {
|
|
67
78
|
return { ...withDates, state: 'INVALID', detail: 'certificado revogado' };
|
|
68
79
|
}
|
|
@@ -101,10 +112,23 @@ export async function readFiscalCredentialReadiness(tx, input) {
|
|
|
101
112
|
};
|
|
102
113
|
}
|
|
103
114
|
if (!cert.verified_at) {
|
|
115
|
+
// Aqui estava o impasse. Antes, tudo que não fora aceito remotamente caía
|
|
116
|
+
// num balde só, e o balde reprovava a execução — inclusive a execução que
|
|
117
|
+
// produziria o aceite. Agora o material conferido localmente tem estado
|
|
118
|
+
// próprio, e é ele que o primeiro contato aceita.
|
|
119
|
+
if (cert.local_validated_at) {
|
|
120
|
+
return {
|
|
121
|
+
...withDates,
|
|
122
|
+
state: 'LOCAL_VALID',
|
|
123
|
+
localValid: true,
|
|
124
|
+
eligibleForFirstContact: true,
|
|
125
|
+
detail: 'válida localmente; nenhum ambiente oficial a aceitou ainda',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
104
128
|
return {
|
|
105
129
|
...withDates,
|
|
106
130
|
state: 'REQUIRES_REVIEW',
|
|
107
|
-
detail: '
|
|
131
|
+
detail: 'cadastrada sem validação local registrada',
|
|
108
132
|
};
|
|
109
133
|
}
|
|
110
134
|
// A autorização do provedor sai de execução OBSERVADA. Um `SELECT` sobre a
|
|
@@ -122,6 +146,9 @@ export async function readFiscalCredentialReadiness(tx, input) {
|
|
|
122
146
|
...withDates,
|
|
123
147
|
state: 'READY',
|
|
124
148
|
usableByWorker: true,
|
|
149
|
+
localValid: true,
|
|
150
|
+
// Já aceita remotamente: não há primeiro contato a fazer.
|
|
151
|
+
eligibleForFirstContact: false,
|
|
125
152
|
selfHealing: true,
|
|
126
153
|
providerAuthorization: established ? 'established' : 'unknown',
|
|
127
154
|
detail: established
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readiness.js","sourceRoot":"","sources":["../../../src/fiscal/credentials/readiness.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"readiness.js","sourceRoot":"","sources":["../../../src/fiscal/credentials/readiness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Tx } from '../db/client.js';
|
|
2
|
+
import { type FiscalDocumentType, type FiscalEnvironment, type TenantFiscalAutomation } from './eligibility.js';
|
|
3
|
+
/**
|
|
4
|
+
* Quem entra na automação fiscal — o escritório, e depois cada cliente.
|
|
5
|
+
*
|
|
6
|
+
* ## Duas chaves, e por que não bastava uma
|
|
7
|
+
*
|
|
8
|
+
* `tenant_fiscal_automation` diz em QUE AMBIENTE o escritório declarou operar.
|
|
9
|
+
* É a chave-geral: enquanto ela estiver desligada, nenhum cliente é consultado,
|
|
10
|
+
* por mais matriculado que esteja.
|
|
11
|
+
*
|
|
12
|
+
* `client_fiscal_enrollment` diz QUAIS clientes, e para QUAIS documentos. É a
|
|
13
|
+
* chave por porta: um cliente pode ter NFS-e habilitada e NF-e não, porque os
|
|
14
|
+
* dois conectores têm maturidades diferentes e o escritório pode querer
|
|
15
|
+
* começar por um.
|
|
16
|
+
*
|
|
17
|
+
* As duas tabelas existem desde a 0058 e nunca tiveram escritor. Este módulo é
|
|
18
|
+
* o escritor — e o motivo de ele existir agora é que, sem ele, habilitar
|
|
19
|
+
* automação fiscal exigia um `INSERT` manual em produção.
|
|
20
|
+
*
|
|
21
|
+
* ## Nada aqui liga nada sozinho
|
|
22
|
+
*
|
|
23
|
+
* Um certificado cadastrado NÃO habilita ninguém. São atos distintos, com
|
|
24
|
+
* autores distintos e trilhas distintas: enviar material é uma coisa, declarar
|
|
25
|
+
* que o escritório opera automação fiscal em produção é outra, e dizer que
|
|
26
|
+
* ESTE cliente entra é uma terceira.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* O modo declarado pelo escritório.
|
|
30
|
+
*
|
|
31
|
+
* Deriva de `TenantFiscalAutomation` (eligibility.ts) em vez de repetir a lista:
|
|
32
|
+
* duas cópias do mesmo vocabulário divergem, e a divergência decide quem entra
|
|
33
|
+
* na automação.
|
|
34
|
+
*/
|
|
35
|
+
export type FiscalAutomationMode = TenantFiscalAutomation['mode'];
|
|
36
|
+
export interface ClientFiscalEnrollment {
|
|
37
|
+
documentType: FiscalDocumentType;
|
|
38
|
+
enrolledAt: string;
|
|
39
|
+
enrolledBy: string | null;
|
|
40
|
+
revokedAt: string | null;
|
|
41
|
+
note: string | null;
|
|
42
|
+
/** Vale agora? Uma matrícula revogada continua no registro, e deve. */
|
|
43
|
+
active: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** O estado das duas chaves, para a tela e para o portão. */
|
|
46
|
+
export interface FiscalEnrollmentView {
|
|
47
|
+
automation: TenantFiscalAutomation;
|
|
48
|
+
enrollments: readonly ClientFiscalEnrollment[];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Declara em que ambiente o escritório opera automação fiscal.
|
|
52
|
+
*
|
|
53
|
+
* `production` é a fronteira entre ensaio e operação real: a partir dela, o
|
|
54
|
+
* material de um contribuinte pode ser apresentado a um servidor da Receita. Por
|
|
55
|
+
* isso o ato é auditado com o modo anterior e o novo — "passou a ser produção"
|
|
56
|
+
* não responde a pergunta que se faz meses depois.
|
|
57
|
+
*/
|
|
58
|
+
export declare function setTenantFiscalAutomation(tx: Tx, input: {
|
|
59
|
+
mode: FiscalAutomationMode;
|
|
60
|
+
actorId: string;
|
|
61
|
+
note?: string | null;
|
|
62
|
+
}): Promise<TenantFiscalAutomation>;
|
|
63
|
+
export declare function listClientFiscalEnrollments(tx: Tx, clientId: string): Promise<ClientFiscalEnrollment[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Matricula um cliente para UM tipo de documento.
|
|
66
|
+
*
|
|
67
|
+
* A granularidade é o ponto: NFS-e e NF-e são conectores diferentes, com
|
|
68
|
+
* maturidades diferentes. Uma matrícula que valesse "para tudo" habilitaria o
|
|
69
|
+
* cliente num conector que talvez nem exista.
|
|
70
|
+
*
|
|
71
|
+
* Rematricular um cliente revogado o reativa — e o registro guarda as duas
|
|
72
|
+
* datas, porque quem revogou tinha um motivo e ele não deve sumir.
|
|
73
|
+
*/
|
|
74
|
+
export declare function enrollClientFiscal(tx: Tx, input: {
|
|
75
|
+
clientId: string;
|
|
76
|
+
documentType: FiscalDocumentType;
|
|
77
|
+
actorId: string;
|
|
78
|
+
note?: string | null;
|
|
79
|
+
}): Promise<ClientFiscalEnrollment[]>;
|
|
80
|
+
/** Tira o cliente da automação. Não apaga o histórico. */
|
|
81
|
+
export declare function revokeClientFiscalEnrollment(tx: Tx, input: {
|
|
82
|
+
clientId: string;
|
|
83
|
+
documentType: FiscalDocumentType;
|
|
84
|
+
actorId: string;
|
|
85
|
+
reason?: string | null;
|
|
86
|
+
}): Promise<ClientFiscalEnrollment[]>;
|
|
87
|
+
/** As duas chaves numa leitura só, para a tela não fazer duas viagens. */
|
|
88
|
+
export declare function readFiscalEnrollmentView(tx: Tx, clientId: string): Promise<FiscalEnrollmentView>;
|
|
89
|
+
/** O ambiente declarado casa com o pedido? Chave-geral do portão. */
|
|
90
|
+
export declare function automationCovers(automation: TenantFiscalAutomation, environment: FiscalEnvironment): boolean;
|
|
91
|
+
//# sourceMappingURL=enrollment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrollment.d.ts","sourceRoot":"","sources":["../../src/fiscal/enrollment.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAElE,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,kBAAkB,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,uEAAuE;IACvE,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,6DAA6D;AAC7D,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,sBAAsB,CAAC;IACnC,WAAW,EAAE,SAAS,sBAAsB,EAAE,CAAC;CAChD;AAED;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,EAAE,EACN,KAAK,EAAE;IAAE,IAAI,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC3E,OAAO,CAAC,sBAAsB,CAAC,CA4BjC;AAED,wBAAsB,2BAA2B,CAC/C,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAuBnC;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,EAAE,EACN,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,GACA,OAAO,CAAC,sBAAsB,EAAE,CAAC,CA6BnC;AAED,0DAA0D;AAC1D,wBAAsB,4BAA4B,CAChD,EAAE,EAAE,EAAE,EACN,KAAK,EAAE;IACL,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,kBAAkB,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,GACA,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAuBnC;AAED,0EAA0E;AAC1E,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,CAAC,CAK/B;AAED,qEAAqE;AACrE,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,sBAAsB,EAClC,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAET"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { uuidv7 } from '@baruk/shared';
|
|
2
|
+
import { appendAudit } from '../audit/audit.js';
|
|
3
|
+
import { getTenantFiscalAutomation, } from './eligibility.js';
|
|
4
|
+
/**
|
|
5
|
+
* Declara em que ambiente o escritório opera automação fiscal.
|
|
6
|
+
*
|
|
7
|
+
* `production` é a fronteira entre ensaio e operação real: a partir dela, o
|
|
8
|
+
* material de um contribuinte pode ser apresentado a um servidor da Receita. Por
|
|
9
|
+
* isso o ato é auditado com o modo anterior e o novo — "passou a ser produção"
|
|
10
|
+
* não responde a pergunta que se faz meses depois.
|
|
11
|
+
*/
|
|
12
|
+
export async function setTenantFiscalAutomation(tx, input) {
|
|
13
|
+
const anterior = await getTenantFiscalAutomation(tx);
|
|
14
|
+
const ligando = input.mode !== 'disabled';
|
|
15
|
+
await tx `
|
|
16
|
+
INSERT INTO tenant_fiscal_automation (tenant_id, mode, enabled_at, enabled_by, note, updated_at)
|
|
17
|
+
VALUES (
|
|
18
|
+
current_setting('app.current_tenant')::uuid, ${input.mode},
|
|
19
|
+
${ligando ? new Date() : null}, ${ligando ? input.actorId : null},
|
|
20
|
+
${input.note ?? null}, now()
|
|
21
|
+
)
|
|
22
|
+
ON CONFLICT (tenant_id) DO UPDATE SET
|
|
23
|
+
mode = EXCLUDED.mode,
|
|
24
|
+
enabled_at = EXCLUDED.enabled_at,
|
|
25
|
+
enabled_by = EXCLUDED.enabled_by,
|
|
26
|
+
note = EXCLUDED.note,
|
|
27
|
+
updated_at = now()
|
|
28
|
+
`;
|
|
29
|
+
await appendAudit(tx, {
|
|
30
|
+
actorId: input.actorId,
|
|
31
|
+
action: 'fiscal_automation_mode_changed',
|
|
32
|
+
resourceType: 'tenant_fiscal_automation',
|
|
33
|
+
resourceId: null,
|
|
34
|
+
metadata: { from: anterior.mode, to: input.mode, note: input.note ?? null },
|
|
35
|
+
});
|
|
36
|
+
return getTenantFiscalAutomation(tx);
|
|
37
|
+
}
|
|
38
|
+
export async function listClientFiscalEnrollments(tx, clientId) {
|
|
39
|
+
const rows = await tx `
|
|
40
|
+
SELECT document_type, enrolled_at, enrolled_by, revoked_at, note
|
|
41
|
+
FROM client_fiscal_enrollment
|
|
42
|
+
WHERE client_id = ${clientId}
|
|
43
|
+
ORDER BY document_type, enrolled_at DESC
|
|
44
|
+
`;
|
|
45
|
+
return rows.map((r) => ({
|
|
46
|
+
documentType: r.document_type,
|
|
47
|
+
enrolledAt: r.enrolled_at.toISOString(),
|
|
48
|
+
enrolledBy: r.enrolled_by,
|
|
49
|
+
revokedAt: r.revoked_at?.toISOString() ?? null,
|
|
50
|
+
note: r.note,
|
|
51
|
+
active: r.revoked_at === null,
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Matricula um cliente para UM tipo de documento.
|
|
56
|
+
*
|
|
57
|
+
* A granularidade é o ponto: NFS-e e NF-e são conectores diferentes, com
|
|
58
|
+
* maturidades diferentes. Uma matrícula que valesse "para tudo" habilitaria o
|
|
59
|
+
* cliente num conector que talvez nem exista.
|
|
60
|
+
*
|
|
61
|
+
* Rematricular um cliente revogado o reativa — e o registro guarda as duas
|
|
62
|
+
* datas, porque quem revogou tinha um motivo e ele não deve sumir.
|
|
63
|
+
*/
|
|
64
|
+
export async function enrollClientFiscal(tx, input) {
|
|
65
|
+
await tx `
|
|
66
|
+
INSERT INTO client_fiscal_enrollment (
|
|
67
|
+
id, tenant_id, client_id, document_type, enrolled_at, enrolled_by, note
|
|
68
|
+
) VALUES (
|
|
69
|
+
${uuidv7()}, current_setting('app.current_tenant')::uuid, ${input.clientId},
|
|
70
|
+
${input.documentType}, now(), ${input.actorId}, ${input.note ?? null}
|
|
71
|
+
)
|
|
72
|
+
-- O índice único é PARCIAL (WHERE revoked_at IS NULL), e o ON CONFLICT
|
|
73
|
+
-- precisa repetir o predicado: sem ele o Postgres não sabe qual índice usar
|
|
74
|
+
-- e a instrução falha.
|
|
75
|
+
--
|
|
76
|
+
-- E a consequência do índice ser parcial é desejável: rematricular um
|
|
77
|
+
-- cliente que foi revogado cria uma linha NOVA em vez de reescrever a
|
|
78
|
+
-- antiga. Quem revogou tinha um motivo, e ele fica no registro.
|
|
79
|
+
ON CONFLICT (tenant_id, client_id, document_type) WHERE revoked_at IS NULL
|
|
80
|
+
DO UPDATE SET
|
|
81
|
+
enrolled_at = now(), enrolled_by = ${input.actorId}, note = ${input.note ?? null}
|
|
82
|
+
`;
|
|
83
|
+
await appendAudit(tx, {
|
|
84
|
+
actorId: input.actorId,
|
|
85
|
+
action: 'fiscal_enrollment_created',
|
|
86
|
+
resourceType: 'client_fiscal_enrollment',
|
|
87
|
+
resourceId: null,
|
|
88
|
+
metadata: { clientId: input.clientId, documentType: input.documentType },
|
|
89
|
+
});
|
|
90
|
+
return listClientFiscalEnrollments(tx, input.clientId);
|
|
91
|
+
}
|
|
92
|
+
/** Tira o cliente da automação. Não apaga o histórico. */
|
|
93
|
+
export async function revokeClientFiscalEnrollment(tx, input) {
|
|
94
|
+
await tx `
|
|
95
|
+
UPDATE client_fiscal_enrollment
|
|
96
|
+
SET revoked_at = now(), revoked_by = ${input.actorId},
|
|
97
|
+
note = COALESCE(${input.reason ?? null}, note)
|
|
98
|
+
WHERE client_id = ${input.clientId}
|
|
99
|
+
AND document_type = ${input.documentType}
|
|
100
|
+
AND revoked_at IS NULL
|
|
101
|
+
`;
|
|
102
|
+
await appendAudit(tx, {
|
|
103
|
+
actorId: input.actorId,
|
|
104
|
+
action: 'fiscal_enrollment_revoked',
|
|
105
|
+
resourceType: 'client_fiscal_enrollment',
|
|
106
|
+
resourceId: null,
|
|
107
|
+
metadata: {
|
|
108
|
+
clientId: input.clientId,
|
|
109
|
+
documentType: input.documentType,
|
|
110
|
+
reason: input.reason ?? null,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
return listClientFiscalEnrollments(tx, input.clientId);
|
|
114
|
+
}
|
|
115
|
+
/** As duas chaves numa leitura só, para a tela não fazer duas viagens. */
|
|
116
|
+
export async function readFiscalEnrollmentView(tx, clientId) {
|
|
117
|
+
return {
|
|
118
|
+
automation: await getTenantFiscalAutomation(tx),
|
|
119
|
+
enrollments: await listClientFiscalEnrollments(tx, clientId),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/** O ambiente declarado casa com o pedido? Chave-geral do portão. */
|
|
123
|
+
export function automationCovers(automation, environment) {
|
|
124
|
+
return automation.mode !== 'disabled' && automation.mode === environment;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=enrollment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrollment.js","sourceRoot":"","sources":["../../src/fiscal/enrollment.ts"],"names":[],"mappings":"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"}
|