@grapadigital/shared-schemas 1.0.227 → 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.
@@ -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: true, default: null }),
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: true, default: null }),
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: true, default: null }),
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: true, default: null }),
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([
@@ -62,15 +62,60 @@ export declare const NoteSchema: MongooseSchema<Note, import("mongoose").Model<N
62
62
  } & {
63
63
  __v: number;
64
64
  }>;
65
+ /** Status do Contrato do Influenciador. Ver GD-715. */
66
+ export declare const CONTRACT_STATUS_VALUES: readonly ["draft", "proposed", "signed", "rejected"];
67
+ export type ContractStatus = (typeof CONTRACT_STATUS_VALUES)[number];
68
+ /**
69
+ * Aditivo do Contrato: soma no valor total quando ativo e pode estender a
70
+ * vigência (o fim do Contrato vai até o fim do último Aditivo ativo). Não tem
71
+ * ligação com Ações. Ver GD-715.
72
+ */
73
+ export declare class ContractAmendment {
74
+ _id?: Types.ObjectId;
75
+ value: number;
76
+ isActive?: boolean;
77
+ startDate: Date;
78
+ endDate: Date;
79
+ description?: string | null;
80
+ }
81
+ export declare const ContractAmendmentSchema: MongooseSchema<ContractAmendment, import("mongoose").Model<ContractAmendment, any, any, any, import("mongoose").Document<unknown, any, ContractAmendment, any, {}> & ContractAmendment & Required<{
82
+ _id: Types.ObjectId;
83
+ }> & {
84
+ __v: number;
85
+ }, any>, {}, {}, {}, {}, import("mongoose").DefaultSchemaOptions, ContractAmendment, import("mongoose").Document<unknown, {}, import("mongoose").FlatRecord<ContractAmendment>, {}, import("mongoose").ResolveSchemaOptions<import("mongoose").DefaultSchemaOptions>> & import("mongoose").FlatRecord<ContractAmendment> & Required<{
86
+ _id: Types.ObjectId;
87
+ }> & {
88
+ __v: number;
89
+ }>;
65
90
  export declare class InfluencerContract {
66
91
  _id?: Types.ObjectId;
67
92
  client: Types.ObjectId | Client;
93
+ /** Cachê total do Contrato (o `valor_cache` do genc). Ver GD-715. */
68
94
  value: number;
95
+ /** Valor em produtos; entra no total. Ver GD-715. */
96
+ productsValue?: number;
97
+ status?: ContractStatus;
98
+ /** Desligado = Contrato encerrado. */
99
+ isActive?: boolean;
100
+ /**
101
+ * Rateio do custo: monthly = valor mensal (total ÷ meses) por mês-calendário;
102
+ * annual = valor total no período.
103
+ */
69
104
  periodType: 'monthly' | 'annual';
70
105
  startDate: Date;
71
106
  endDate?: Date;
72
107
  deliveryScope?: Delivery;
73
108
  documentUrl?: string;
109
+ /** CPI (Custo por Impacto) contratual, digitado no planejamento. Ver GD-715. */
110
+ contractedCostPerImpact?: number | null;
111
+ /** E/R contratual, em fração (0,02 = 2%). */
112
+ contractedEngagementRate?: number | null;
113
+ /** Ex.: "Team Galaxy", "Content Creator", "Tech". */
114
+ squad?: string | null;
115
+ /** Ex.: "Technology", "Camera", "Fashion". */
116
+ passionPoint?: string | null;
117
+ description?: string | null;
118
+ amendments?: ContractAmendment[];
74
119
  }
75
120
  export declare const InfluencerContractSchema: MongooseSchema<InfluencerContract, import("mongoose").Model<InfluencerContract, any, any, any, import("mongoose").Document<unknown, any, InfluencerContract, any, {}> & InfluencerContract & Required<{
76
121
  _id: Types.ObjectId;
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.InfluencerModel = exports.InfluencerSchema = exports.Influencer = exports.InfluencerContractSchema = exports.InfluencerContract = exports.NoteSchema = exports.Note = exports.AdvisorSchema = exports.Advisor = void 0;
12
+ exports.InfluencerModel = exports.InfluencerSchema = exports.Influencer = exports.InfluencerContractSchema = exports.InfluencerContract = exports.ContractAmendmentSchema = exports.ContractAmendment = exports.CONTRACT_STATUS_VALUES = exports.NoteSchema = exports.Note = exports.AdvisorSchema = exports.Advisor = void 0;
13
13
  const mongoose_1 = require("@nestjs/mongoose");
14
14
  const mongoose_2 = require("mongoose");
15
15
  const party_schema_1 = require("./party.schema");
@@ -72,6 +72,44 @@ Note = __decorate([
72
72
  ], Note);
73
73
  exports.Note = Note;
74
74
  exports.NoteSchema = mongoose_1.SchemaFactory.createForClass(Note);
75
+ /** Status do Contrato do Influenciador. Ver GD-715. */
76
+ exports.CONTRACT_STATUS_VALUES = ['draft', 'proposed', 'signed', 'rejected'];
77
+ /**
78
+ * Aditivo do Contrato: soma no valor total quando ativo e pode estender a
79
+ * vigência (o fim do Contrato vai até o fim do último Aditivo ativo). Não tem
80
+ * ligação com Ações. Ver GD-715.
81
+ */
82
+ let ContractAmendment = class ContractAmendment {
83
+ };
84
+ __decorate([
85
+ (0, mongoose_1.Prop)({ type: mongoose_2.Schema.Types.ObjectId, auto: true, required: false }),
86
+ __metadata("design:type", mongoose_2.Types.ObjectId)
87
+ ], ContractAmendment.prototype, "_id", void 0);
88
+ __decorate([
89
+ (0, mongoose_1.Prop)({ type: Number, required: true }),
90
+ __metadata("design:type", Number)
91
+ ], ContractAmendment.prototype, "value", void 0);
92
+ __decorate([
93
+ (0, mongoose_1.Prop)({ type: Boolean, required: false, default: true }),
94
+ __metadata("design:type", Boolean)
95
+ ], ContractAmendment.prototype, "isActive", void 0);
96
+ __decorate([
97
+ (0, mongoose_1.Prop)({ type: Date, required: true }),
98
+ __metadata("design:type", Date)
99
+ ], ContractAmendment.prototype, "startDate", void 0);
100
+ __decorate([
101
+ (0, mongoose_1.Prop)({ type: Date, required: true }),
102
+ __metadata("design:type", Date)
103
+ ], ContractAmendment.prototype, "endDate", void 0);
104
+ __decorate([
105
+ (0, mongoose_1.Prop)({ type: String, required: false, default: null }),
106
+ __metadata("design:type", Object)
107
+ ], ContractAmendment.prototype, "description", void 0);
108
+ ContractAmendment = __decorate([
109
+ (0, mongoose_1.Schema)({ timestamps: true })
110
+ ], ContractAmendment);
111
+ exports.ContractAmendment = ContractAmendment;
112
+ exports.ContractAmendmentSchema = mongoose_1.SchemaFactory.createForClass(ContractAmendment);
75
113
  // Contrato de valor fixo recorrente (influenciador embaixador de marca) —
76
114
  // alternativa ao pagamento avulso por Recruitment (scope.payment/advisorPayment).
77
115
  // Escopo de entregas reaproveita a mesma classe Delivery do termo de aceite
@@ -90,6 +128,18 @@ __decorate([
90
128
  (0, mongoose_1.Prop)({ type: Number, required: true }),
91
129
  __metadata("design:type", Number)
92
130
  ], InfluencerContract.prototype, "value", void 0);
131
+ __decorate([
132
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: 0 }),
133
+ __metadata("design:type", Number)
134
+ ], InfluencerContract.prototype, "productsValue", void 0);
135
+ __decorate([
136
+ (0, mongoose_1.Prop)({ type: String, enum: exports.CONTRACT_STATUS_VALUES, required: false, default: 'signed' }),
137
+ __metadata("design:type", String)
138
+ ], InfluencerContract.prototype, "status", void 0);
139
+ __decorate([
140
+ (0, mongoose_1.Prop)({ type: Boolean, required: false, default: true }),
141
+ __metadata("design:type", Boolean)
142
+ ], InfluencerContract.prototype, "isActive", void 0);
93
143
  __decorate([
94
144
  (0, mongoose_1.Prop)({ type: String, enum: ['monthly', 'annual'], required: true }),
95
145
  __metadata("design:type", String)
@@ -110,6 +160,30 @@ __decorate([
110
160
  (0, mongoose_1.Prop)({ type: String, required: false }),
111
161
  __metadata("design:type", String)
112
162
  ], InfluencerContract.prototype, "documentUrl", void 0);
163
+ __decorate([
164
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
165
+ __metadata("design:type", Object)
166
+ ], InfluencerContract.prototype, "contractedCostPerImpact", void 0);
167
+ __decorate([
168
+ (0, mongoose_1.Prop)({ type: Number, required: false, default: null }),
169
+ __metadata("design:type", Object)
170
+ ], InfluencerContract.prototype, "contractedEngagementRate", void 0);
171
+ __decorate([
172
+ (0, mongoose_1.Prop)({ type: String, required: false, default: null }),
173
+ __metadata("design:type", Object)
174
+ ], InfluencerContract.prototype, "squad", void 0);
175
+ __decorate([
176
+ (0, mongoose_1.Prop)({ type: String, required: false, default: null }),
177
+ __metadata("design:type", Object)
178
+ ], InfluencerContract.prototype, "passionPoint", void 0);
179
+ __decorate([
180
+ (0, mongoose_1.Prop)({ type: String, required: false, default: null }),
181
+ __metadata("design:type", Object)
182
+ ], InfluencerContract.prototype, "description", void 0);
183
+ __decorate([
184
+ (0, mongoose_1.Prop)({ type: [exports.ContractAmendmentSchema], required: false, default: [] }),
185
+ __metadata("design:type", Array)
186
+ ], InfluencerContract.prototype, "amendments", void 0);
113
187
  InfluencerContract = __decorate([
114
188
  (0, mongoose_1.Schema)({ timestamps: true })
115
189
  ], InfluencerContract);
@@ -0,0 +1,37 @@
1
+ import { InfluencerContract } from '../schemas/influencer.schema';
2
+ export declare const getEngagementRatePercent: (totals: {
3
+ engagement: number;
4
+ potentialReach: number;
5
+ }) => number | null;
6
+ export declare const getCostPerImpact: (input: {
7
+ value: number;
8
+ engagementRatePercent: number | null;
9
+ impressions: number;
10
+ posts: number;
11
+ }) => number | null;
12
+ export interface ContractMonthTotals {
13
+ year: number;
14
+ month: number;
15
+ posts: number;
16
+ impressions: number;
17
+ engagement: number;
18
+ potentialReach: number;
19
+ }
20
+ export interface MonthlyCostPerImpact extends ContractMonthTotals {
21
+ monthlyValue: number;
22
+ monthlyCostPerImpact: number | null;
23
+ engagementRatePercent: number | null;
24
+ elapsedMonths: number;
25
+ accumulatedSpend: number;
26
+ }
27
+ /**
28
+ * Série mensal acumulada desde o mês de início até `untilMonth` (inclusive):
29
+ * gasto acumulado = valor mensal × meses decorridos; métricas somadas desde o
30
+ * início. `monthlyTotals` traz os totais de cada mês (não acumulados).
31
+ */
32
+ export declare const getMonthlyCostPerImpactSeries: (contract: Pick<InfluencerContract, 'value' | 'productsValue' | 'startDate' | 'endDate' | 'amendments' | 'status' | 'isActive'>, monthlyTotals: ContractMonthTotals[], until: {
33
+ year: number;
34
+ month: number;
35
+ }) => MonthlyCostPerImpact[];
36
+ /** Média dos CPIs mensais não nulos. */
37
+ export declare const getAverageMonthlyCostPerImpact: (series: MonthlyCostPerImpact[]) => number | null;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAverageMonthlyCostPerImpact = exports.getMonthlyCostPerImpactSeries = exports.getCostPerImpact = exports.getEngagementRatePercent = void 0;
4
+ const influencer_contract_util_1 = require("./influencer-contract.util");
5
+ /**
6
+ * CPI (Custo por Impacto), como no genc (`TB_Contratos_Influencer_Cliente` e
7
+ * `TB_CPI_Mensal_Contrato`). Ver GD-715.
8
+ * CPI = valor ÷ ((E/R% ÷ 100) × impressões × posts), E/R% = engajamento ÷ alcance potencial × 100.
9
+ */
10
+ const round = (value, places) => Math.round(value * 10 ** places) / 10 ** places;
11
+ const getEngagementRatePercent = (totals) => totals.potentialReach > 0 ? (totals.engagement / totals.potentialReach) * 100 : null;
12
+ exports.getEngagementRatePercent = getEngagementRatePercent;
13
+ const getCostPerImpact = (input) => {
14
+ const { value, engagementRatePercent, impressions, posts } = input;
15
+ if (!engagementRatePercent || engagementRatePercent <= 0 || value <= 0)
16
+ return null;
17
+ const divisor = (engagementRatePercent / 100) * impressions * posts;
18
+ return divisor > 0 ? round(value / divisor, 6) : null;
19
+ };
20
+ exports.getCostPerImpact = getCostPerImpact;
21
+ /**
22
+ * Série mensal acumulada desde o mês de início até `untilMonth` (inclusive):
23
+ * gasto acumulado = valor mensal × meses decorridos; métricas somadas desde o
24
+ * início. `monthlyTotals` traz os totais de cada mês (não acumulados).
25
+ */
26
+ const getMonthlyCostPerImpactSeries = (contract, monthlyTotals, until) => {
27
+ const brt = new Date(new Date(contract.startDate).getTime() - 3 * 60 * 60 * 1000);
28
+ let year = brt.getUTCFullYear();
29
+ let month = brt.getUTCMonth() + 1;
30
+ const monthlyValue = (0, influencer_contract_util_1.getContractMonthlyValue)(contract);
31
+ const byMonth = new Map(monthlyTotals.map((t) => [`${t.year}-${t.month}`, t]));
32
+ const acc = { posts: 0, impressions: 0, engagement: 0, potentialReach: 0 };
33
+ const series = [];
34
+ let elapsedMonths = 0;
35
+ while (year < until.year || (year === until.year && month <= until.month)) {
36
+ elapsedMonths += 1;
37
+ const totals = byMonth.get(`${year}-${month}`);
38
+ if (totals) {
39
+ acc.posts += totals.posts;
40
+ acc.impressions += totals.impressions;
41
+ acc.engagement += totals.engagement;
42
+ acc.potentialReach += totals.potentialReach;
43
+ }
44
+ const accumulatedSpend = round(monthlyValue * elapsedMonths, 2);
45
+ const engagementRatePercent = acc.posts > 0 ? (0, exports.getEngagementRatePercent)(acc) : null;
46
+ series.push({
47
+ year,
48
+ month,
49
+ ...acc,
50
+ monthlyValue,
51
+ elapsedMonths,
52
+ accumulatedSpend,
53
+ engagementRatePercent: engagementRatePercent == null ? null : round(engagementRatePercent, 4),
54
+ monthlyCostPerImpact: acc.posts > 0 && acc.engagement > 0
55
+ ? (0, exports.getCostPerImpact)({ value: accumulatedSpend, engagementRatePercent, impressions: acc.impressions, posts: acc.posts })
56
+ : null,
57
+ });
58
+ month += 1;
59
+ if (month > 12) {
60
+ month = 1;
61
+ year += 1;
62
+ }
63
+ }
64
+ return series;
65
+ };
66
+ exports.getMonthlyCostPerImpactSeries = getMonthlyCostPerImpactSeries;
67
+ /** Média dos CPIs mensais não nulos. */
68
+ const getAverageMonthlyCostPerImpact = (series) => {
69
+ const values = series.map((row) => row.monthlyCostPerImpact).filter((v) => v != null);
70
+ return values.length > 0 ? round(values.reduce((a, b) => a + b, 0) / values.length, 6) : null;
71
+ };
72
+ exports.getAverageMonthlyCostPerImpact = getAverageMonthlyCostPerImpact;
@@ -0,0 +1,32 @@
1
+ import { InfluencerContract } from '../schemas/influencer.schema';
2
+ type ContractLike = Pick<InfluencerContract, 'value' | 'productsValue' | 'startDate' | 'endDate' | 'amendments' | 'status' | 'isActive'> & {
3
+ client?: unknown;
4
+ };
5
+ /**
6
+ * Fim efetivo da vigência: o maior entre o fim do Contrato e o fim dos Aditivos
7
+ * ativos (o Aditivo estende a vigência). null = Contrato em aberto, mesmo com
8
+ * Aditivo. Ver GD-715.
9
+ */
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;
16
+ /**
17
+ * Meses de vigência como no genc (`calcular_meses_vigencia`): anos × 12 + meses
18
+ * e mais 1 se sobrar dia; mínimo 1. Sem fim, conta até `now`.
19
+ * Ex.: 10/06/2025 a 11/02/2027 = 21; 15/03 a 14/09 = 6.
20
+ */
21
+ export declare const getContractMonths: (contract: ContractLike, now?: Date) => number;
22
+ /** Cachê + produtos + Aditivos ativos. */
23
+ export declare const getContractTotalValue: (contract: ContractLike) => number;
24
+ /** Total ÷ meses de vigência, 2 casas (428.392,93 ÷ 21 = 20.399,66). */
25
+ export declare const getContractMonthlyValue: (contract: ContractLike, now?: Date) => number;
26
+ /** Ativo e assinado (status ausente conta como assinado). */
27
+ export declare const isContractUsable: (contract: ContractLike) => boolean;
28
+ /** Contrato usável do cliente vigente em `at`, com o último dia inteiro (sem fim = em aberto). */
29
+ export declare const getActiveContract: <T extends ContractLike>(influencer: {
30
+ contracts?: T[] | null | undefined;
31
+ } | null | undefined, clientId: unknown, at: Date) => T | null;
32
+ export {};
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getActiveContract = exports.isContractUsable = exports.getContractMonthlyValue = exports.getContractTotalValue = exports.getContractMonths = exports.getContractEndExclusive = exports.getContractEndDate = void 0;
4
+ const BRT_OFFSET_MS = 3 * 60 * 60 * 1000;
5
+ /** Data no calendário de São Paulo (as datas do Flow são gravadas à meia-noite BRT = 03:00Z). */
6
+ const brtParts = (date) => {
7
+ const d = new Date(new Date(date).getTime() - BRT_OFFSET_MS);
8
+ return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1, day: d.getUTCDate() };
9
+ };
10
+ const round2 = (value) => Math.round(value * 100) / 100;
11
+ const activeAmendments = (contract) => (contract.amendments ?? []).filter((amendment) => amendment.isActive !== false);
12
+ /**
13
+ * Fim efetivo da vigência: o maior entre o fim do Contrato e o fim dos Aditivos
14
+ * ativos (o Aditivo estende a vigência). null = Contrato em aberto, mesmo com
15
+ * Aditivo. Ver GD-715.
16
+ */
17
+ const getContractEndDate = (contract) => {
18
+ if (!contract.endDate)
19
+ return null;
20
+ const ends = [contract.endDate, ...activeAmendments(contract).map((a) => a.endDate)]
21
+ .filter((date) => !!date)
22
+ .map((date) => new Date(date).getTime());
23
+ return new Date(Math.max(...ends));
24
+ };
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;
38
+ /**
39
+ * Meses de vigência como no genc (`calcular_meses_vigencia`): anos × 12 + meses
40
+ * e mais 1 se sobrar dia; mínimo 1. Sem fim, conta até `now`.
41
+ * Ex.: 10/06/2025 a 11/02/2027 = 21; 15/03 a 14/09 = 6.
42
+ */
43
+ const getContractMonths = (contract, now = new Date()) => {
44
+ const start = brtParts(contract.startDate);
45
+ const end = brtParts((0, exports.getContractEndDate)(contract) ?? now);
46
+ let months = (end.year - start.year) * 12 + (end.month - start.month);
47
+ // relativedelta: dia do fim menor tira um mês e sobra dia, que devolve o mês.
48
+ if (end.day > start.day)
49
+ months += 1;
50
+ return Math.max(1, months);
51
+ };
52
+ exports.getContractMonths = getContractMonths;
53
+ /** Cachê + produtos + Aditivos ativos. */
54
+ const getContractTotalValue = (contract) => round2((contract.value ?? 0) +
55
+ (contract.productsValue ?? 0) +
56
+ activeAmendments(contract).reduce((sum, a) => sum + (a.value ?? 0), 0));
57
+ exports.getContractTotalValue = getContractTotalValue;
58
+ /** Total ÷ meses de vigência, 2 casas (428.392,93 ÷ 21 = 20.399,66). */
59
+ const getContractMonthlyValue = (contract, now) => round2((0, exports.getContractTotalValue)(contract) / (0, exports.getContractMonths)(contract, now));
60
+ exports.getContractMonthlyValue = getContractMonthlyValue;
61
+ /** Ativo e assinado (status ausente conta como assinado). */
62
+ const isContractUsable = (contract) => contract.isActive !== false && (contract.status ?? 'signed') === 'signed';
63
+ exports.isContractUsable = isContractUsable;
64
+ /** Contrato usável do cliente vigente em `at`, com o último dia inteiro (sem fim = em aberto). */
65
+ const getActiveContract = (influencer, clientId, at) => {
66
+ const time = new Date(at).getTime();
67
+ return ((influencer?.contracts ?? []).find((contract) => {
68
+ if (!(0, exports.isContractUsable)(contract))
69
+ return false;
70
+ const client = contract.client?._id ?? contract.client;
71
+ if (String(client) !== String(clientId))
72
+ return false;
73
+ const end = (0, exports.getContractEndExclusive)(contract);
74
+ return new Date(contract.startDate).getTime() <= time && (!end || time < end.getTime());
75
+ }) ?? null);
76
+ };
77
+ exports.getActiveContract = getActiveContract;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grapadigital/shared-schemas",
3
- "version": "1.0.227",
3
+ "version": "1.0.230",
4
4
  "description": "Shared Mongoose Schemas",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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: true, default: null })
331
+ @Prop({ type: Number, required: false, default: null })
331
332
  impressionCount?: number | null;
332
333
 
333
- @Prop({ type: Number, required: true, default: null })
334
+ @Prop({ type: Number, required: false, default: null })
334
335
  reachCount?: number | null;
335
336
 
336
- @Prop({ type: Number, required: true, default: null })
337
+ @Prop({ type: Number, required: false, default: null })
337
338
  engagementCount?: number | null;
338
339
 
339
- @Prop({ type: Number, required: true, default: null })
340
+ @Prop({ type: Number, required: false, default: null })
340
341
  engagementRate?: number | null;
341
342
  }
342
343
 
@@ -47,6 +47,38 @@ export class Note {
47
47
 
48
48
  export const NoteSchema = SchemaFactory.createForClass(Note);
49
49
 
50
+ /** Status do Contrato do Influenciador. Ver GD-715. */
51
+ export const CONTRACT_STATUS_VALUES = ['draft', 'proposed', 'signed', 'rejected'] as const;
52
+ export type ContractStatus = (typeof CONTRACT_STATUS_VALUES)[number];
53
+
54
+ /**
55
+ * Aditivo do Contrato: soma no valor total quando ativo e pode estender a
56
+ * vigência (o fim do Contrato vai até o fim do último Aditivo ativo). Não tem
57
+ * ligação com Ações. Ver GD-715.
58
+ */
59
+ @Schema({ timestamps: true })
60
+ export class ContractAmendment {
61
+ @Prop({ type: MongooseSchema.Types.ObjectId, auto: true, required: false })
62
+ _id?: Types.ObjectId;
63
+
64
+ @Prop({ type: Number, required: true })
65
+ value: number;
66
+
67
+ @Prop({ type: Boolean, required: false, default: true })
68
+ isActive?: boolean;
69
+
70
+ @Prop({ type: Date, required: true })
71
+ startDate: Date;
72
+
73
+ @Prop({ type: Date, required: true })
74
+ endDate: Date;
75
+
76
+ @Prop({ type: String, required: false, default: null })
77
+ description?: string | null;
78
+ }
79
+
80
+ export const ContractAmendmentSchema = SchemaFactory.createForClass(ContractAmendment);
81
+
50
82
  // Contrato de valor fixo recorrente (influenciador embaixador de marca) —
51
83
  // alternativa ao pagamento avulso por Recruitment (scope.payment/advisorPayment).
52
84
  // Escopo de entregas reaproveita a mesma classe Delivery do termo de aceite
@@ -59,9 +91,25 @@ export class InfluencerContract {
59
91
  @Prop({ type: MongooseSchema.Types.ObjectId, ref: 'Client', required: true })
60
92
  client: Types.ObjectId | Client;
61
93
 
94
+ /** Cachê total do Contrato (o `valor_cache` do genc). Ver GD-715. */
62
95
  @Prop({ type: Number, required: true })
63
96
  value: number;
64
97
 
98
+ /** Valor em produtos; entra no total. Ver GD-715. */
99
+ @Prop({ type: Number, required: false, default: 0 })
100
+ productsValue?: number;
101
+
102
+ @Prop({ type: String, enum: CONTRACT_STATUS_VALUES, required: false, default: 'signed' })
103
+ status?: ContractStatus;
104
+
105
+ /** Desligado = Contrato encerrado. */
106
+ @Prop({ type: Boolean, required: false, default: true })
107
+ isActive?: boolean;
108
+
109
+ /**
110
+ * Rateio do custo: monthly = valor mensal (total ÷ meses) por mês-calendário;
111
+ * annual = valor total no período.
112
+ */
65
113
  @Prop({ type: String, enum: ['monthly', 'annual'], required: true })
66
114
  periodType: 'monthly' | 'annual';
67
115
 
@@ -83,6 +131,28 @@ export class InfluencerContract {
83
131
  // fica salva no Mongo.
84
132
  @Prop({ type: String, required: false })
85
133
  documentUrl?: string;
134
+
135
+ /** CPI (Custo por Impacto) contratual, digitado no planejamento. Ver GD-715. */
136
+ @Prop({ type: Number, required: false, default: null })
137
+ contractedCostPerImpact?: number | null;
138
+
139
+ /** E/R contratual, em fração (0,02 = 2%). */
140
+ @Prop({ type: Number, required: false, default: null })
141
+ contractedEngagementRate?: number | null;
142
+
143
+ /** Ex.: "Team Galaxy", "Content Creator", "Tech". */
144
+ @Prop({ type: String, required: false, default: null })
145
+ squad?: string | null;
146
+
147
+ /** Ex.: "Technology", "Camera", "Fashion". */
148
+ @Prop({ type: String, required: false, default: null })
149
+ passionPoint?: string | null;
150
+
151
+ @Prop({ type: String, required: false, default: null })
152
+ description?: string | null;
153
+
154
+ @Prop({ type: [ContractAmendmentSchema], required: false, default: [] })
155
+ amendments?: ContractAmendment[];
86
156
  }
87
157
 
88
158
  export const InfluencerContractSchema = SchemaFactory.createForClass(InfluencerContract);