evo360-types 1.3.732 → 1.3.736
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/dist/apps/evo-finops/nfse-reissue.d.ts +54 -0
- package/dist/apps/evo-finops/nfse-reissue.js +120 -0
- package/dist/apps/evo-finops/nfse-reissue.ts +156 -0
- package/dist/apps/evo-finops/zod-schemas.d.ts +1260 -60
- package/dist/apps/evo-finops/zod-schemas.js +80 -1
- package/dist/apps/evo-finops/zod-schemas.ts +88 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.ts +1 -0
- package/dist/types/evo-finops/invoices/index.d.ts +122 -0
- package/dist/types/evo-finops/invoices/index.ts +142 -0
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { NfseReissueField, ServiceInvoiceState } from "../../types/evo-finops/invoices";
|
|
2
|
+
/** Campos que a correção no lugar aceita (tomador e serviço municipal ⇒ caminho B). */
|
|
3
|
+
export declare const NFSE_REISSUE_FIELDS: ReadonlyArray<NfseReissueField>;
|
|
4
|
+
/**
|
|
5
|
+
* Por quanto tempo uma substituição `pending` trava novas reemissões da mesma origem.
|
|
6
|
+
* Passado esse prazo sem `done`, considera-se que a emissão anterior morreu e libera-se.
|
|
7
|
+
*/
|
|
8
|
+
export declare const NFSE_REPLACEMENT_PENDING_LOCK_MS: number;
|
|
9
|
+
/** Recorte mínimo da nota que as regras precisam (aceita `IServiceInvoice` ou linha da lista). */
|
|
10
|
+
export interface INfseReissueCheckInput {
|
|
11
|
+
state?: ServiceInvoiceState | string | null;
|
|
12
|
+
provider_invoice_id?: string | null;
|
|
13
|
+
deleted_at?: unknown;
|
|
14
|
+
replaced_by?: {
|
|
15
|
+
status?: "pending" | "done" | string | null;
|
|
16
|
+
requested_at?: unknown;
|
|
17
|
+
} | null;
|
|
18
|
+
}
|
|
19
|
+
/** Por que a nota não pode ser corrigida no lugar (`null` = pode). */
|
|
20
|
+
export type NfseFixInPlaceBlockReason = "deleted" | "no_provider_id" | "replaced" | "not_error";
|
|
21
|
+
/** Por que a nota não pode ser reemitida como nova (`null` = pode). */
|
|
22
|
+
export type NfseReissueAsNewBlockReason = "deleted" | "already_replaced" | "replacement_pending" | "not_reissuable";
|
|
23
|
+
/**
|
|
24
|
+
* Diz por que a NFS-e não pode ser corrigida no lugar ("Corrigir e reemitir"), ou `null` se pode.
|
|
25
|
+
*
|
|
26
|
+
* Precedência: `deleted` > `replaced` > `no_provider_id` > estado.
|
|
27
|
+
* - `replaced`: já existe `replaced_by` (qualquer status) — a origem foi (ou está sendo) substituída.
|
|
28
|
+
* - Só nota em `ERROR` é corrigível; qualquer outro estado (ou ausente) ⇒ `not_error`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function nfseFixInPlaceBlockReason(inv: INfseReissueCheckInput | null | undefined): NfseFixInPlaceBlockReason | null;
|
|
31
|
+
/** `true` quando a NFS-e em erro pode ser corrigida e reemitida no lugar. */
|
|
32
|
+
export declare function isNfseFixableInPlace(inv: INfseReissueCheckInput | null | undefined): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Converte uma data vinda do Firestore/JSON em milissegundos.
|
|
35
|
+
* Aceita `Date`, string ISO, número (ms), objeto com `toDate()` e Timestamp
|
|
36
|
+
* serializado (`{seconds}` ou `{_seconds}`, com `nanoseconds`/`_nanoseconds` opcionais).
|
|
37
|
+
* Retorna `null` quando não reconhece ou a data é inválida.
|
|
38
|
+
*/
|
|
39
|
+
export declare function nfseReissueDateToMillis(v: unknown): number | null;
|
|
40
|
+
/**
|
|
41
|
+
* Diz por que a NFS-e não pode ser reemitida como nova ("Reemitir como nova"), ou `null` se pode.
|
|
42
|
+
*
|
|
43
|
+
* Precedência: `deleted` > `already_replaced` > `replacement_pending` > estado.
|
|
44
|
+
* - `already_replaced`: `replaced_by.status = done` (uma substituição por origem;
|
|
45
|
+
* se a nova for cancelada, reemite-se a partir da nova).
|
|
46
|
+
* - `replacement_pending`: `replaced_by.status = pending` pedido há menos de
|
|
47
|
+
* `NFSE_REPLACEMENT_PENDING_LOCK_MS`. Data ilegível conta como trava ativa (na dúvida, bloqueia).
|
|
48
|
+
* - Só `CANCELED` ou `ERROR` são reemitíveis; o resto ⇒ `not_reissuable`.
|
|
49
|
+
*
|
|
50
|
+
* @param now relógio injetável (ms ou `Date`); padrão `Date.now()`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function nfseReissueAsNewBlockReason(inv: INfseReissueCheckInput | null | undefined, now?: number | Date): NfseReissueAsNewBlockReason | null;
|
|
53
|
+
/** `true` quando a NFS-e pode ser reemitida como uma nota nova. */
|
|
54
|
+
export declare function isNfseReissuableAsNew(inv: INfseReissueCheckInput | null | undefined, now?: number | Date): boolean;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Regra única de reemissão de NFS-e (feat-292), usada pelo backend e pelo front-end.
|
|
3
|
+
// Dois caminhos:
|
|
4
|
+
// - A "Corrigir e reemitir": corrige no lugar uma nota em ERROR (PUT + authorize no Asaas).
|
|
5
|
+
// - B "Reemitir como nova": emite uma nota nova a partir de uma CANCELED ou ERROR.
|
|
6
|
+
// Runtime lib: es6 — sem dependências de Node e sem APIs posteriores ao target.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.NFSE_REPLACEMENT_PENDING_LOCK_MS = exports.NFSE_REISSUE_FIELDS = void 0;
|
|
9
|
+
exports.nfseFixInPlaceBlockReason = nfseFixInPlaceBlockReason;
|
|
10
|
+
exports.isNfseFixableInPlace = isNfseFixableInPlace;
|
|
11
|
+
exports.nfseReissueDateToMillis = nfseReissueDateToMillis;
|
|
12
|
+
exports.nfseReissueAsNewBlockReason = nfseReissueAsNewBlockReason;
|
|
13
|
+
exports.isNfseReissuableAsNew = isNfseReissuableAsNew;
|
|
14
|
+
/** Campos que a correção no lugar aceita (tomador e serviço municipal ⇒ caminho B). */
|
|
15
|
+
exports.NFSE_REISSUE_FIELDS = [
|
|
16
|
+
"value",
|
|
17
|
+
"service_description",
|
|
18
|
+
"observations",
|
|
19
|
+
"deductions",
|
|
20
|
+
"taxes",
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Por quanto tempo uma substituição `pending` trava novas reemissões da mesma origem.
|
|
24
|
+
* Passado esse prazo sem `done`, considera-se que a emissão anterior morreu e libera-se.
|
|
25
|
+
*/
|
|
26
|
+
exports.NFSE_REPLACEMENT_PENDING_LOCK_MS = 21 * 60 * 1000;
|
|
27
|
+
/**
|
|
28
|
+
* Diz por que a NFS-e não pode ser corrigida no lugar ("Corrigir e reemitir"), ou `null` se pode.
|
|
29
|
+
*
|
|
30
|
+
* Precedência: `deleted` > `replaced` > `no_provider_id` > estado.
|
|
31
|
+
* - `replaced`: já existe `replaced_by` (qualquer status) — a origem foi (ou está sendo) substituída.
|
|
32
|
+
* - Só nota em `ERROR` é corrigível; qualquer outro estado (ou ausente) ⇒ `not_error`.
|
|
33
|
+
*/
|
|
34
|
+
function nfseFixInPlaceBlockReason(inv) {
|
|
35
|
+
if (!inv)
|
|
36
|
+
return "not_error";
|
|
37
|
+
if (inv.deleted_at)
|
|
38
|
+
return "deleted";
|
|
39
|
+
if (inv.replaced_by)
|
|
40
|
+
return "replaced";
|
|
41
|
+
if (!inv.provider_invoice_id)
|
|
42
|
+
return "no_provider_id";
|
|
43
|
+
return inv.state === "ERROR" ? null : "not_error";
|
|
44
|
+
}
|
|
45
|
+
/** `true` quando a NFS-e em erro pode ser corrigida e reemitida no lugar. */
|
|
46
|
+
function isNfseFixableInPlace(inv) {
|
|
47
|
+
return nfseFixInPlaceBlockReason(inv) === null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Converte uma data vinda do Firestore/JSON em milissegundos.
|
|
51
|
+
* Aceita `Date`, string ISO, número (ms), objeto com `toDate()` e Timestamp
|
|
52
|
+
* serializado (`{seconds}` ou `{_seconds}`, com `nanoseconds`/`_nanoseconds` opcionais).
|
|
53
|
+
* Retorna `null` quando não reconhece ou a data é inválida.
|
|
54
|
+
*/
|
|
55
|
+
function nfseReissueDateToMillis(v) {
|
|
56
|
+
if (v === null || v === undefined)
|
|
57
|
+
return null;
|
|
58
|
+
let ms = null;
|
|
59
|
+
if (v instanceof Date) {
|
|
60
|
+
ms = v.getTime();
|
|
61
|
+
}
|
|
62
|
+
else if (typeof v === "number") {
|
|
63
|
+
ms = v;
|
|
64
|
+
}
|
|
65
|
+
else if (typeof v === "string") {
|
|
66
|
+
ms = v.trim() ? Date.parse(v) : null;
|
|
67
|
+
}
|
|
68
|
+
else if (typeof v === "object") {
|
|
69
|
+
const o = v;
|
|
70
|
+
if (typeof o.toDate === "function") {
|
|
71
|
+
const d = o.toDate.call(v);
|
|
72
|
+
ms = d instanceof Date ? d.getTime() : null;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const s = typeof o.seconds === "number" ? o.seconds : o._seconds;
|
|
76
|
+
const n = typeof o.nanoseconds === "number" ? o.nanoseconds : o._nanoseconds;
|
|
77
|
+
if (typeof s === "number") {
|
|
78
|
+
ms = s * 1000 + (typeof n === "number" ? Math.floor(n / 1e6) : 0);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return ms !== null && isFinite(ms) ? ms : null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Diz por que a NFS-e não pode ser reemitida como nova ("Reemitir como nova"), ou `null` se pode.
|
|
86
|
+
*
|
|
87
|
+
* Precedência: `deleted` > `already_replaced` > `replacement_pending` > estado.
|
|
88
|
+
* - `already_replaced`: `replaced_by.status = done` (uma substituição por origem;
|
|
89
|
+
* se a nova for cancelada, reemite-se a partir da nova).
|
|
90
|
+
* - `replacement_pending`: `replaced_by.status = pending` pedido há menos de
|
|
91
|
+
* `NFSE_REPLACEMENT_PENDING_LOCK_MS`. Data ilegível conta como trava ativa (na dúvida, bloqueia).
|
|
92
|
+
* - Só `CANCELED` ou `ERROR` são reemitíveis; o resto ⇒ `not_reissuable`.
|
|
93
|
+
*
|
|
94
|
+
* @param now relógio injetável (ms ou `Date`); padrão `Date.now()`.
|
|
95
|
+
*/
|
|
96
|
+
function nfseReissueAsNewBlockReason(inv, now) {
|
|
97
|
+
if (!inv)
|
|
98
|
+
return "not_reissuable";
|
|
99
|
+
if (inv.deleted_at)
|
|
100
|
+
return "deleted";
|
|
101
|
+
const rb = inv.replaced_by;
|
|
102
|
+
if (rb) {
|
|
103
|
+
if (rb.status === "done")
|
|
104
|
+
return "already_replaced";
|
|
105
|
+
if (rb.status === "pending") {
|
|
106
|
+
const at = nfseReissueDateToMillis(rb.requested_at);
|
|
107
|
+
const nowMs = now === undefined ? Date.now() : now instanceof Date ? now.getTime() : now;
|
|
108
|
+
if (at === null || nowMs - at < exports.NFSE_REPLACEMENT_PENDING_LOCK_MS) {
|
|
109
|
+
return "replacement_pending";
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return inv.state === "CANCELED" || inv.state === "ERROR"
|
|
114
|
+
? null
|
|
115
|
+
: "not_reissuable";
|
|
116
|
+
}
|
|
117
|
+
/** `true` quando a NFS-e pode ser reemitida como uma nota nova. */
|
|
118
|
+
function isNfseReissuableAsNew(inv, now) {
|
|
119
|
+
return nfseReissueAsNewBlockReason(inv, now) === null;
|
|
120
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Regra única de reemissão de NFS-e (feat-292), usada pelo backend e pelo front-end.
|
|
2
|
+
// Dois caminhos:
|
|
3
|
+
// - A "Corrigir e reemitir": corrige no lugar uma nota em ERROR (PUT + authorize no Asaas).
|
|
4
|
+
// - B "Reemitir como nova": emite uma nota nova a partir de uma CANCELED ou ERROR.
|
|
5
|
+
// Runtime lib: es6 — sem dependências de Node e sem APIs posteriores ao target.
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
NfseReissueField,
|
|
9
|
+
ServiceInvoiceState,
|
|
10
|
+
} from "../../types/evo-finops/invoices";
|
|
11
|
+
|
|
12
|
+
/** Campos que a correção no lugar aceita (tomador e serviço municipal ⇒ caminho B). */
|
|
13
|
+
export const NFSE_REISSUE_FIELDS: ReadonlyArray<NfseReissueField> = [
|
|
14
|
+
"value",
|
|
15
|
+
"service_description",
|
|
16
|
+
"observations",
|
|
17
|
+
"deductions",
|
|
18
|
+
"taxes",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Por quanto tempo uma substituição `pending` trava novas reemissões da mesma origem.
|
|
23
|
+
* Passado esse prazo sem `done`, considera-se que a emissão anterior morreu e libera-se.
|
|
24
|
+
*/
|
|
25
|
+
export const NFSE_REPLACEMENT_PENDING_LOCK_MS = 21 * 60 * 1000;
|
|
26
|
+
|
|
27
|
+
/** Recorte mínimo da nota que as regras precisam (aceita `IServiceInvoice` ou linha da lista). */
|
|
28
|
+
export interface INfseReissueCheckInput {
|
|
29
|
+
state?: ServiceInvoiceState | string | null;
|
|
30
|
+
provider_invoice_id?: string | null;
|
|
31
|
+
deleted_at?: unknown;
|
|
32
|
+
replaced_by?: {
|
|
33
|
+
status?: "pending" | "done" | string | null;
|
|
34
|
+
requested_at?: unknown;
|
|
35
|
+
} | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Por que a nota não pode ser corrigida no lugar (`null` = pode). */
|
|
39
|
+
export type NfseFixInPlaceBlockReason =
|
|
40
|
+
| "deleted"
|
|
41
|
+
| "no_provider_id"
|
|
42
|
+
| "replaced"
|
|
43
|
+
| "not_error";
|
|
44
|
+
|
|
45
|
+
/** Por que a nota não pode ser reemitida como nova (`null` = pode). */
|
|
46
|
+
export type NfseReissueAsNewBlockReason =
|
|
47
|
+
| "deleted"
|
|
48
|
+
| "already_replaced"
|
|
49
|
+
| "replacement_pending"
|
|
50
|
+
| "not_reissuable";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Diz por que a NFS-e não pode ser corrigida no lugar ("Corrigir e reemitir"), ou `null` se pode.
|
|
54
|
+
*
|
|
55
|
+
* Precedência: `deleted` > `replaced` > `no_provider_id` > estado.
|
|
56
|
+
* - `replaced`: já existe `replaced_by` (qualquer status) — a origem foi (ou está sendo) substituída.
|
|
57
|
+
* - Só nota em `ERROR` é corrigível; qualquer outro estado (ou ausente) ⇒ `not_error`.
|
|
58
|
+
*/
|
|
59
|
+
export function nfseFixInPlaceBlockReason(
|
|
60
|
+
inv: INfseReissueCheckInput | null | undefined,
|
|
61
|
+
): NfseFixInPlaceBlockReason | null {
|
|
62
|
+
if (!inv) return "not_error";
|
|
63
|
+
if (inv.deleted_at) return "deleted";
|
|
64
|
+
if (inv.replaced_by) return "replaced";
|
|
65
|
+
if (!inv.provider_invoice_id) return "no_provider_id";
|
|
66
|
+
return inv.state === "ERROR" ? null : "not_error";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** `true` quando a NFS-e em erro pode ser corrigida e reemitida no lugar. */
|
|
70
|
+
export function isNfseFixableInPlace(
|
|
71
|
+
inv: INfseReissueCheckInput | null | undefined,
|
|
72
|
+
): boolean {
|
|
73
|
+
return nfseFixInPlaceBlockReason(inv) === null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Converte uma data vinda do Firestore/JSON em milissegundos.
|
|
78
|
+
* Aceita `Date`, string ISO, número (ms), objeto com `toDate()` e Timestamp
|
|
79
|
+
* serializado (`{seconds}` ou `{_seconds}`, com `nanoseconds`/`_nanoseconds` opcionais).
|
|
80
|
+
* Retorna `null` quando não reconhece ou a data é inválida.
|
|
81
|
+
*/
|
|
82
|
+
export function nfseReissueDateToMillis(v: unknown): number | null {
|
|
83
|
+
if (v === null || v === undefined) return null;
|
|
84
|
+
let ms: number | null = null;
|
|
85
|
+
if (v instanceof Date) {
|
|
86
|
+
ms = v.getTime();
|
|
87
|
+
} else if (typeof v === "number") {
|
|
88
|
+
ms = v;
|
|
89
|
+
} else if (typeof v === "string") {
|
|
90
|
+
ms = v.trim() ? Date.parse(v) : null;
|
|
91
|
+
} else if (typeof v === "object") {
|
|
92
|
+
const o = v as {
|
|
93
|
+
toDate?: unknown;
|
|
94
|
+
seconds?: unknown;
|
|
95
|
+
_seconds?: unknown;
|
|
96
|
+
nanoseconds?: unknown;
|
|
97
|
+
_nanoseconds?: unknown;
|
|
98
|
+
};
|
|
99
|
+
if (typeof o.toDate === "function") {
|
|
100
|
+
const d = (o.toDate as () => unknown).call(v);
|
|
101
|
+
ms = d instanceof Date ? d.getTime() : null;
|
|
102
|
+
} else {
|
|
103
|
+
const s = typeof o.seconds === "number" ? o.seconds : o._seconds;
|
|
104
|
+
const n = typeof o.nanoseconds === "number" ? o.nanoseconds : o._nanoseconds;
|
|
105
|
+
if (typeof s === "number") {
|
|
106
|
+
ms = s * 1000 + (typeof n === "number" ? Math.floor(n / 1e6) : 0);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return ms !== null && isFinite(ms) ? ms : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Diz por que a NFS-e não pode ser reemitida como nova ("Reemitir como nova"), ou `null` se pode.
|
|
115
|
+
*
|
|
116
|
+
* Precedência: `deleted` > `already_replaced` > `replacement_pending` > estado.
|
|
117
|
+
* - `already_replaced`: `replaced_by.status = done` (uma substituição por origem;
|
|
118
|
+
* se a nova for cancelada, reemite-se a partir da nova).
|
|
119
|
+
* - `replacement_pending`: `replaced_by.status = pending` pedido há menos de
|
|
120
|
+
* `NFSE_REPLACEMENT_PENDING_LOCK_MS`. Data ilegível conta como trava ativa (na dúvida, bloqueia).
|
|
121
|
+
* - Só `CANCELED` ou `ERROR` são reemitíveis; o resto ⇒ `not_reissuable`.
|
|
122
|
+
*
|
|
123
|
+
* @param now relógio injetável (ms ou `Date`); padrão `Date.now()`.
|
|
124
|
+
*/
|
|
125
|
+
export function nfseReissueAsNewBlockReason(
|
|
126
|
+
inv: INfseReissueCheckInput | null | undefined,
|
|
127
|
+
now?: number | Date,
|
|
128
|
+
): NfseReissueAsNewBlockReason | null {
|
|
129
|
+
if (!inv) return "not_reissuable";
|
|
130
|
+
if (inv.deleted_at) return "deleted";
|
|
131
|
+
|
|
132
|
+
const rb = inv.replaced_by;
|
|
133
|
+
if (rb) {
|
|
134
|
+
if (rb.status === "done") return "already_replaced";
|
|
135
|
+
if (rb.status === "pending") {
|
|
136
|
+
const at = nfseReissueDateToMillis(rb.requested_at);
|
|
137
|
+
const nowMs =
|
|
138
|
+
now === undefined ? Date.now() : now instanceof Date ? now.getTime() : now;
|
|
139
|
+
if (at === null || nowMs - at < NFSE_REPLACEMENT_PENDING_LOCK_MS) {
|
|
140
|
+
return "replacement_pending";
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return inv.state === "CANCELED" || inv.state === "ERROR"
|
|
146
|
+
? null
|
|
147
|
+
: "not_reissuable";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** `true` quando a NFS-e pode ser reemitida como uma nota nova. */
|
|
151
|
+
export function isNfseReissuableAsNew(
|
|
152
|
+
inv: INfseReissueCheckInput | null | undefined,
|
|
153
|
+
now?: number | Date,
|
|
154
|
+
): boolean {
|
|
155
|
+
return nfseReissueAsNewBlockReason(inv, now) === null;
|
|
156
|
+
}
|