@grapadigital/shared-schemas 1.0.228 → 1.0.230
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/index.d.ts +0 -2
- package/dist/index.js +0 -2
- package/dist/schemas/content.schema.js +5 -4
- package/dist/utils/influencer-contract.util.d.ts +9 -4
- package/dist/utils/influencer-contract.util.js +24 -14
- package/package.json +1 -1
- package/src/index.ts +0 -2
- package/src/schemas/content.schema.ts +5 -4
- package/src/utils/cost-per-impact.util.ts +0 -99
- package/src/utils/influencer-contract.util.ts +0 -83
package/dist/index.d.ts
CHANGED
|
@@ -38,5 +38,3 @@ export * from './schemas/snapshot.schema';
|
|
|
38
38
|
export * from './schemas/lead.schema';
|
|
39
39
|
export * from './utils/normalize-content-link.util';
|
|
40
40
|
export * from './utils/merge-metrics-breakdown.util';
|
|
41
|
-
export * from './utils/influencer-contract.util';
|
|
42
|
-
export * from './utils/cost-per-impact.util';
|
package/dist/index.js
CHANGED
|
@@ -59,5 +59,3 @@ __exportStar(require("./schemas/snapshot.schema"), exports);
|
|
|
59
59
|
__exportStar(require("./schemas/lead.schema"), exports);
|
|
60
60
|
__exportStar(require("./utils/normalize-content-link.util"), exports);
|
|
61
61
|
__exportStar(require("./utils/merge-metrics-breakdown.util"), exports);
|
|
62
|
-
__exportStar(require("./utils/influencer-contract.util"), exports);
|
|
63
|
-
__exportStar(require("./utils/cost-per-impact.util"), exports);
|
|
@@ -393,22 +393,23 @@ ContentMetricsHistoryEntry = __decorate([
|
|
|
393
393
|
exports.ContentMetricsHistoryEntry = ContentMetricsHistoryEntry;
|
|
394
394
|
exports.ContentMetricsHistoryEntrySchema = mongoose_1.SchemaFactory.createForClass(ContentMetricsHistoryEntry);
|
|
395
395
|
/** Métricas estimadas pelo porte do perfil, para quando a rede não informa. Ver GD-751. */
|
|
396
|
+
// null é valor válido (sem estimativa): required recusaria null no create.
|
|
396
397
|
let EstimatedMetrics = class EstimatedMetrics {
|
|
397
398
|
};
|
|
398
399
|
__decorate([
|
|
399
|
-
(0, mongoose_1.Prop)({ type: Number, required:
|
|
400
|
+
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
400
401
|
__metadata("design:type", Object)
|
|
401
402
|
], EstimatedMetrics.prototype, "impressionCount", void 0);
|
|
402
403
|
__decorate([
|
|
403
|
-
(0, mongoose_1.Prop)({ type: Number, required:
|
|
404
|
+
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
404
405
|
__metadata("design:type", Object)
|
|
405
406
|
], EstimatedMetrics.prototype, "reachCount", void 0);
|
|
406
407
|
__decorate([
|
|
407
|
-
(0, mongoose_1.Prop)({ type: Number, required:
|
|
408
|
+
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
408
409
|
__metadata("design:type", Object)
|
|
409
410
|
], EstimatedMetrics.prototype, "engagementCount", void 0);
|
|
410
411
|
__decorate([
|
|
411
|
-
(0, mongoose_1.Prop)({ type: Number, required:
|
|
412
|
+
(0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
|
|
412
413
|
__metadata("design:type", Object)
|
|
413
414
|
], EstimatedMetrics.prototype, "engagementRate", void 0);
|
|
414
415
|
EstimatedMetrics = __decorate([
|
|
@@ -4,14 +4,19 @@ type ContractLike = Pick<InfluencerContract, 'value' | 'productsValue' | 'startD
|
|
|
4
4
|
};
|
|
5
5
|
/**
|
|
6
6
|
* Fim efetivo da vigência: o maior entre o fim do Contrato e o fim dos Aditivos
|
|
7
|
-
* ativos (
|
|
8
|
-
* Ver GD-715.
|
|
7
|
+
* ativos (o Aditivo estende a vigência). null = Contrato em aberto, mesmo com
|
|
8
|
+
* Aditivo. Ver GD-715.
|
|
9
9
|
*/
|
|
10
10
|
export declare const getContractEndDate: (contract: ContractLike) => Date | null;
|
|
11
|
+
/**
|
|
12
|
+
* Início do dia seguinte ao fim da vigência em São Paulo (limite exclusivo):
|
|
13
|
+
* o último dia conta inteiro. null = Contrato em aberto.
|
|
14
|
+
*/
|
|
15
|
+
export declare const getContractEndExclusive: (contract: ContractLike) => Date | null;
|
|
11
16
|
/**
|
|
12
17
|
* Meses de vigência como no genc (`calcular_meses_vigencia`): anos × 12 + meses
|
|
13
18
|
* e mais 1 se sobrar dia; mínimo 1. Sem fim, conta até `now`.
|
|
14
|
-
* Ex.: 10/06/2025 a 11/02/2027 = 21.
|
|
19
|
+
* Ex.: 10/06/2025 a 11/02/2027 = 21; 15/03 a 14/09 = 6.
|
|
15
20
|
*/
|
|
16
21
|
export declare const getContractMonths: (contract: ContractLike, now?: Date) => number;
|
|
17
22
|
/** Cachê + produtos + Aditivos ativos. */
|
|
@@ -20,7 +25,7 @@ export declare const getContractTotalValue: (contract: ContractLike) => number;
|
|
|
20
25
|
export declare const getContractMonthlyValue: (contract: ContractLike, now?: Date) => number;
|
|
21
26
|
/** Ativo e assinado (status ausente conta como assinado). */
|
|
22
27
|
export declare const isContractUsable: (contract: ContractLike) => boolean;
|
|
23
|
-
/** Contrato usável do cliente vigente em `at
|
|
28
|
+
/** Contrato usável do cliente vigente em `at`, com o último dia inteiro (sem fim = em aberto). */
|
|
24
29
|
export declare const getActiveContract: <T extends ContractLike>(influencer: {
|
|
25
30
|
contracts?: T[] | null | undefined;
|
|
26
31
|
} | null | undefined, clientId: unknown, at: Date) => T | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getActiveContract = exports.isContractUsable = exports.getContractMonthlyValue = exports.getContractTotalValue = exports.getContractMonths = exports.getContractEndDate = void 0;
|
|
3
|
+
exports.getActiveContract = exports.isContractUsable = exports.getContractMonthlyValue = exports.getContractTotalValue = exports.getContractMonths = exports.getContractEndExclusive = exports.getContractEndDate = void 0;
|
|
4
4
|
const BRT_OFFSET_MS = 3 * 60 * 60 * 1000;
|
|
5
5
|
/** Data no calendário de São Paulo (as datas do Flow são gravadas à meia-noite BRT = 03:00Z). */
|
|
6
6
|
const brtParts = (date) => {
|
|
@@ -11,31 +11,41 @@ const round2 = (value) => Math.round(value * 100) / 100;
|
|
|
11
11
|
const activeAmendments = (contract) => (contract.amendments ?? []).filter((amendment) => amendment.isActive !== false);
|
|
12
12
|
/**
|
|
13
13
|
* Fim efetivo da vigência: o maior entre o fim do Contrato e o fim dos Aditivos
|
|
14
|
-
* ativos (
|
|
15
|
-
* Ver GD-715.
|
|
14
|
+
* ativos (o Aditivo estende a vigência). null = Contrato em aberto, mesmo com
|
|
15
|
+
* Aditivo. Ver GD-715.
|
|
16
16
|
*/
|
|
17
17
|
const getContractEndDate = (contract) => {
|
|
18
|
+
if (!contract.endDate)
|
|
19
|
+
return null;
|
|
18
20
|
const ends = [contract.endDate, ...activeAmendments(contract).map((a) => a.endDate)]
|
|
19
21
|
.filter((date) => !!date)
|
|
20
22
|
.map((date) => new Date(date).getTime());
|
|
21
|
-
return
|
|
23
|
+
return new Date(Math.max(...ends));
|
|
22
24
|
};
|
|
23
25
|
exports.getContractEndDate = getContractEndDate;
|
|
26
|
+
/**
|
|
27
|
+
* Início do dia seguinte ao fim da vigência em São Paulo (limite exclusivo):
|
|
28
|
+
* o último dia conta inteiro. null = Contrato em aberto.
|
|
29
|
+
*/
|
|
30
|
+
const getContractEndExclusive = (contract) => {
|
|
31
|
+
const end = (0, exports.getContractEndDate)(contract);
|
|
32
|
+
if (!end)
|
|
33
|
+
return null;
|
|
34
|
+
const { year, month, day } = brtParts(end);
|
|
35
|
+
return new Date(Date.UTC(year, month - 1, day + 1) + BRT_OFFSET_MS);
|
|
36
|
+
};
|
|
37
|
+
exports.getContractEndExclusive = getContractEndExclusive;
|
|
24
38
|
/**
|
|
25
39
|
* Meses de vigência como no genc (`calcular_meses_vigencia`): anos × 12 + meses
|
|
26
40
|
* e mais 1 se sobrar dia; mínimo 1. Sem fim, conta até `now`.
|
|
27
|
-
* Ex.: 10/06/2025 a 11/02/2027 = 21.
|
|
41
|
+
* Ex.: 10/06/2025 a 11/02/2027 = 21; 15/03 a 14/09 = 6.
|
|
28
42
|
*/
|
|
29
43
|
const getContractMonths = (contract, now = new Date()) => {
|
|
30
44
|
const start = brtParts(contract.startDate);
|
|
31
45
|
const end = brtParts((0, exports.getContractEndDate)(contract) ?? now);
|
|
32
46
|
let months = (end.year - start.year) * 12 + (end.month - start.month);
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
months -= 1;
|
|
36
|
-
days += 1; // relativedelta: só importa se sobrou dia
|
|
37
|
-
}
|
|
38
|
-
if (days > 0)
|
|
47
|
+
// relativedelta: dia do fim menor tira um mês e sobra dia, que devolve o mês.
|
|
48
|
+
if (end.day > start.day)
|
|
39
49
|
months += 1;
|
|
40
50
|
return Math.max(1, months);
|
|
41
51
|
};
|
|
@@ -51,7 +61,7 @@ exports.getContractMonthlyValue = getContractMonthlyValue;
|
|
|
51
61
|
/** Ativo e assinado (status ausente conta como assinado). */
|
|
52
62
|
const isContractUsable = (contract) => contract.isActive !== false && (contract.status ?? 'signed') === 'signed';
|
|
53
63
|
exports.isContractUsable = isContractUsable;
|
|
54
|
-
/** Contrato usável do cliente vigente em `at
|
|
64
|
+
/** Contrato usável do cliente vigente em `at`, com o último dia inteiro (sem fim = em aberto). */
|
|
55
65
|
const getActiveContract = (influencer, clientId, at) => {
|
|
56
66
|
const time = new Date(at).getTime();
|
|
57
67
|
return ((influencer?.contracts ?? []).find((contract) => {
|
|
@@ -60,8 +70,8 @@ const getActiveContract = (influencer, clientId, at) => {
|
|
|
60
70
|
const client = contract.client?._id ?? contract.client;
|
|
61
71
|
if (String(client) !== String(clientId))
|
|
62
72
|
return false;
|
|
63
|
-
const end = (0, exports.
|
|
64
|
-
return new Date(contract.startDate).getTime() <= time && (!end || time
|
|
73
|
+
const end = (0, exports.getContractEndExclusive)(contract);
|
|
74
|
+
return new Date(contract.startDate).getTime() <= time && (!end || time < end.getTime());
|
|
65
75
|
}) ?? null);
|
|
66
76
|
};
|
|
67
77
|
exports.getActiveContract = getActiveContract;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -38,5 +38,3 @@ export * from './schemas/snapshot.schema';
|
|
|
38
38
|
export * from './schemas/lead.schema';
|
|
39
39
|
export * from './utils/normalize-content-link.util';
|
|
40
40
|
export * from './utils/merge-metrics-breakdown.util';
|
|
41
|
-
export * from './utils/influencer-contract.util';
|
|
42
|
-
export * from './utils/cost-per-impact.util';
|
|
@@ -325,18 +325,19 @@ export class ContentMetricsHistoryEntry {
|
|
|
325
325
|
export const ContentMetricsHistoryEntrySchema = SchemaFactory.createForClass(ContentMetricsHistoryEntry);
|
|
326
326
|
|
|
327
327
|
/** Métricas estimadas pelo porte do perfil, para quando a rede não informa. Ver GD-751. */
|
|
328
|
+
// null é valor válido (sem estimativa): required recusaria null no create.
|
|
328
329
|
@Schema({ _id: false })
|
|
329
330
|
export class EstimatedMetrics {
|
|
330
|
-
@Prop({ type: Number, required:
|
|
331
|
+
@Prop({ type: Number, required: false, default: null })
|
|
331
332
|
impressionCount?: number | null;
|
|
332
333
|
|
|
333
|
-
@Prop({ type: Number, required:
|
|
334
|
+
@Prop({ type: Number, required: false, default: null })
|
|
334
335
|
reachCount?: number | null;
|
|
335
336
|
|
|
336
|
-
@Prop({ type: Number, required:
|
|
337
|
+
@Prop({ type: Number, required: false, default: null })
|
|
337
338
|
engagementCount?: number | null;
|
|
338
339
|
|
|
339
|
-
@Prop({ type: Number, required:
|
|
340
|
+
@Prop({ type: Number, required: false, default: null })
|
|
340
341
|
engagementRate?: number | null;
|
|
341
342
|
}
|
|
342
343
|
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { getContractMonthlyValue } from './influencer-contract.util';
|
|
2
|
-
import { InfluencerContract } from '../schemas/influencer.schema';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* CPI (Custo por Impacto), como no genc (`TB_Contratos_Influencer_Cliente` e
|
|
6
|
-
* `TB_CPI_Mensal_Contrato`). Ver GD-715.
|
|
7
|
-
* CPI = valor ÷ ((E/R% ÷ 100) × impressões × posts), E/R% = engajamento ÷ alcance potencial × 100.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const round = (value: number, places: number) => Math.round(value * 10 ** places) / 10 ** places;
|
|
11
|
-
|
|
12
|
-
export const getEngagementRatePercent = (totals: { engagement: number; potentialReach: number }): number | null =>
|
|
13
|
-
totals.potentialReach > 0 ? (totals.engagement / totals.potentialReach) * 100 : null;
|
|
14
|
-
|
|
15
|
-
export const getCostPerImpact = (input: {
|
|
16
|
-
value: number;
|
|
17
|
-
engagementRatePercent: number | null;
|
|
18
|
-
impressions: number;
|
|
19
|
-
posts: number;
|
|
20
|
-
}): number | null => {
|
|
21
|
-
const { value, engagementRatePercent, impressions, posts } = input;
|
|
22
|
-
if (!engagementRatePercent || engagementRatePercent <= 0 || value <= 0) return null;
|
|
23
|
-
const divisor = (engagementRatePercent / 100) * impressions * posts;
|
|
24
|
-
return divisor > 0 ? round(value / divisor, 6) : null;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export interface ContractMonthTotals {
|
|
28
|
-
year: number;
|
|
29
|
-
month: number;
|
|
30
|
-
posts: number;
|
|
31
|
-
impressions: number;
|
|
32
|
-
engagement: number;
|
|
33
|
-
potentialReach: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface MonthlyCostPerImpact extends ContractMonthTotals {
|
|
37
|
-
monthlyValue: number;
|
|
38
|
-
monthlyCostPerImpact: number | null;
|
|
39
|
-
engagementRatePercent: number | null;
|
|
40
|
-
elapsedMonths: number;
|
|
41
|
-
accumulatedSpend: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Série mensal acumulada desde o mês de início até `untilMonth` (inclusive):
|
|
46
|
-
* gasto acumulado = valor mensal × meses decorridos; métricas somadas desde o
|
|
47
|
-
* início. `monthlyTotals` traz os totais de cada mês (não acumulados).
|
|
48
|
-
*/
|
|
49
|
-
export const getMonthlyCostPerImpactSeries = (
|
|
50
|
-
contract: Pick<InfluencerContract, 'value' | 'productsValue' | 'startDate' | 'endDate' | 'amendments' | 'status' | 'isActive'>,
|
|
51
|
-
monthlyTotals: ContractMonthTotals[],
|
|
52
|
-
until: { year: number; month: number },
|
|
53
|
-
): MonthlyCostPerImpact[] => {
|
|
54
|
-
const brt = new Date(new Date(contract.startDate).getTime() - 3 * 60 * 60 * 1000);
|
|
55
|
-
let year = brt.getUTCFullYear();
|
|
56
|
-
let month = brt.getUTCMonth() + 1;
|
|
57
|
-
const monthlyValue = getContractMonthlyValue(contract);
|
|
58
|
-
const byMonth = new Map(monthlyTotals.map((t) => [`${t.year}-${t.month}`, t]));
|
|
59
|
-
const acc = { posts: 0, impressions: 0, engagement: 0, potentialReach: 0 };
|
|
60
|
-
const series: MonthlyCostPerImpact[] = [];
|
|
61
|
-
let elapsedMonths = 0;
|
|
62
|
-
while (year < until.year || (year === until.year && month <= until.month)) {
|
|
63
|
-
elapsedMonths += 1;
|
|
64
|
-
const totals = byMonth.get(`${year}-${month}`);
|
|
65
|
-
if (totals) {
|
|
66
|
-
acc.posts += totals.posts;
|
|
67
|
-
acc.impressions += totals.impressions;
|
|
68
|
-
acc.engagement += totals.engagement;
|
|
69
|
-
acc.potentialReach += totals.potentialReach;
|
|
70
|
-
}
|
|
71
|
-
const accumulatedSpend = round(monthlyValue * elapsedMonths, 2);
|
|
72
|
-
const engagementRatePercent = acc.posts > 0 ? getEngagementRatePercent(acc) : null;
|
|
73
|
-
series.push({
|
|
74
|
-
year,
|
|
75
|
-
month,
|
|
76
|
-
...acc,
|
|
77
|
-
monthlyValue,
|
|
78
|
-
elapsedMonths,
|
|
79
|
-
accumulatedSpend,
|
|
80
|
-
engagementRatePercent: engagementRatePercent == null ? null : round(engagementRatePercent, 4),
|
|
81
|
-
monthlyCostPerImpact:
|
|
82
|
-
acc.posts > 0 && acc.engagement > 0
|
|
83
|
-
? getCostPerImpact({ value: accumulatedSpend, engagementRatePercent, impressions: acc.impressions, posts: acc.posts })
|
|
84
|
-
: null,
|
|
85
|
-
});
|
|
86
|
-
month += 1;
|
|
87
|
-
if (month > 12) {
|
|
88
|
-
month = 1;
|
|
89
|
-
year += 1;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return series;
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/** Média dos CPIs mensais não nulos. */
|
|
96
|
-
export const getAverageMonthlyCostPerImpact = (series: MonthlyCostPerImpact[]): number | null => {
|
|
97
|
-
const values = series.map((row) => row.monthlyCostPerImpact).filter((v): v is number => v != null);
|
|
98
|
-
return values.length > 0 ? round(values.reduce((a, b) => a + b, 0) / values.length, 6) : null;
|
|
99
|
-
};
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { InfluencerContract } from '../schemas/influencer.schema';
|
|
2
|
-
|
|
3
|
-
type ContractLike = Pick<
|
|
4
|
-
InfluencerContract,
|
|
5
|
-
'value' | 'productsValue' | 'startDate' | 'endDate' | 'amendments' | 'status' | 'isActive'
|
|
6
|
-
> & { client?: unknown };
|
|
7
|
-
|
|
8
|
-
const BRT_OFFSET_MS = 3 * 60 * 60 * 1000;
|
|
9
|
-
|
|
10
|
-
/** Data no calendário de São Paulo (as datas do Flow são gravadas à meia-noite BRT = 03:00Z). */
|
|
11
|
-
const brtParts = (date: Date) => {
|
|
12
|
-
const d = new Date(new Date(date).getTime() - BRT_OFFSET_MS);
|
|
13
|
-
return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1, day: d.getUTCDate() };
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const round2 = (value: number) => Math.round(value * 100) / 100;
|
|
17
|
-
|
|
18
|
-
const activeAmendments = (contract: ContractLike) =>
|
|
19
|
-
(contract.amendments ?? []).filter((amendment) => amendment.isActive !== false);
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Fim efetivo da vigência: o maior entre o fim do Contrato e o fim dos Aditivos
|
|
23
|
-
* ativos (no genc o Aditivo estende a vigência). null = Contrato em aberto.
|
|
24
|
-
* Ver GD-715.
|
|
25
|
-
*/
|
|
26
|
-
export const getContractEndDate = (contract: ContractLike): Date | null => {
|
|
27
|
-
const ends = [contract.endDate, ...activeAmendments(contract).map((a) => a.endDate)]
|
|
28
|
-
.filter((date): date is Date => !!date)
|
|
29
|
-
.map((date) => new Date(date).getTime());
|
|
30
|
-
return ends.length > 0 ? new Date(Math.max(...ends)) : null;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Meses de vigência como no genc (`calcular_meses_vigencia`): anos × 12 + meses
|
|
35
|
-
* e mais 1 se sobrar dia; mínimo 1. Sem fim, conta até `now`.
|
|
36
|
-
* Ex.: 10/06/2025 a 11/02/2027 = 21.
|
|
37
|
-
*/
|
|
38
|
-
export const getContractMonths = (contract: ContractLike, now: Date = new Date()): number => {
|
|
39
|
-
const start = brtParts(contract.startDate);
|
|
40
|
-
const end = brtParts(getContractEndDate(contract) ?? now);
|
|
41
|
-
let months = (end.year - start.year) * 12 + (end.month - start.month);
|
|
42
|
-
let days = end.day - start.day;
|
|
43
|
-
if (days < 0) {
|
|
44
|
-
months -= 1;
|
|
45
|
-
days += 1; // relativedelta: só importa se sobrou dia
|
|
46
|
-
}
|
|
47
|
-
if (days > 0) months += 1;
|
|
48
|
-
return Math.max(1, months);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/** Cachê + produtos + Aditivos ativos. */
|
|
52
|
-
export const getContractTotalValue = (contract: ContractLike): number =>
|
|
53
|
-
round2(
|
|
54
|
-
(contract.value ?? 0) +
|
|
55
|
-
(contract.productsValue ?? 0) +
|
|
56
|
-
activeAmendments(contract).reduce((sum, a) => sum + (a.value ?? 0), 0),
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
/** Total ÷ meses de vigência, 2 casas (428.392,93 ÷ 21 = 20.399,66). */
|
|
60
|
-
export const getContractMonthlyValue = (contract: ContractLike, now?: Date): number =>
|
|
61
|
-
round2(getContractTotalValue(contract) / getContractMonths(contract, now));
|
|
62
|
-
|
|
63
|
-
/** Ativo e assinado (status ausente conta como assinado). */
|
|
64
|
-
export const isContractUsable = (contract: ContractLike): boolean =>
|
|
65
|
-
contract.isActive !== false && (contract.status ?? 'signed') === 'signed';
|
|
66
|
-
|
|
67
|
-
/** Contrato usável do cliente vigente em `at` (sem fim = em aberto). */
|
|
68
|
-
export const getActiveContract = <T extends ContractLike>(
|
|
69
|
-
influencer: { contracts?: T[] | null } | null | undefined,
|
|
70
|
-
clientId: unknown,
|
|
71
|
-
at: Date,
|
|
72
|
-
): T | null => {
|
|
73
|
-
const time = new Date(at).getTime();
|
|
74
|
-
return (
|
|
75
|
-
(influencer?.contracts ?? []).find((contract) => {
|
|
76
|
-
if (!isContractUsable(contract)) return false;
|
|
77
|
-
const client = (contract.client as { _id?: unknown })?._id ?? contract.client;
|
|
78
|
-
if (String(client) !== String(clientId)) return false;
|
|
79
|
-
const end = getContractEndDate(contract);
|
|
80
|
-
return new Date(contract.startDate).getTime() <= time && (!end || time <= end.getTime());
|
|
81
|
-
}) ?? null
|
|
82
|
-
);
|
|
83
|
-
};
|