@mamindom/contracts 1.0.129 → 1.0.131

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.
@@ -16,6 +16,42 @@ service NotificationTemplateService {
16
16
  rpc RestoreVersion (RestoreVersionRequest) returns (TemplateResponse);
17
17
  rpc RenderPreview (RenderPreviewRequest) returns (RenderPreviewResponse);
18
18
  rpc SendTest (SendTestRequest) returns (SendTestResponse);
19
+ // Реальна відправка клієнту з адмінки — або по шаблону, або ad-hoc body.
20
+ rpc SendToCustomer (SendToCustomerRequest) returns (SendToCustomerResponse);
21
+ }
22
+
23
+ // SendToCustomer — викликається з gateway після того, як клієнтський контакт
24
+ // (email/phone) вже резолвнений і опційно згенерований персональний купон.
25
+ message SendToCustomerRequest {
26
+ // userId з auth-service — пишемо в NotificationLog.user_id для зведень.
27
+ string user_id = 1;
28
+ NotificationChannel channel = 2;
29
+
30
+ // Recipient — email або phone, заздалегідь визначені gateway з акаунта.
31
+ string recipient = 3;
32
+
33
+ // Шлях 1: templateId — рендеримо існуючий шаблон з variables.
34
+ optional string template_id = 4;
35
+
36
+ // Шлях 2: ad-hoc контент. Якщо template_id порожній — використовуємо ці поля.
37
+ // body_html — для EMAIL (буде обгорнутий брендингом).
38
+ // body_text — для SMS (plain text) або текстова версія email.
39
+ optional string custom_subject = 5;
40
+ optional string custom_body_html = 6;
41
+ optional string custom_body_text = 7;
42
+
43
+ // JSON з placeholders для Handlebars: { "coupon_code": "...", ... }
44
+ string variables_json = 8;
45
+
46
+ // Locale для пошуку версії шаблону + надсилання branding-обгортки.
47
+ optional NotificationLocale locale = 9;
48
+ }
49
+
50
+ message SendToCustomerResponse {
51
+ bool ok = 1;
52
+ string error_message = 2;
53
+ // id запису в NotificationLog (для трекінгу в /notifications/logs).
54
+ string log_id = 3;
19
55
  }
20
56
 
21
57
  service NotificationLogsService {
@@ -29,6 +65,202 @@ service TelegramChatsService {
29
65
  rpc TestSendTelegram (TestSendTelegramRequest) returns (TestSendTelegramResponse);
30
66
  }
31
67
 
68
+ // Маркетингові кампанії: створення, запуск, статистика.
69
+ service CampaignsService {
70
+ rpc ListCampaigns (ListCampaignsRequest) returns (ListCampaignsResponse);
71
+ rpc GetCampaign (GetCampaignRequest) returns (CampaignResponse);
72
+ rpc CreateCampaign (CreateCampaignRequest) returns (CampaignResponse);
73
+ rpc UpdateCampaign (UpdateCampaignRequest) returns (CampaignResponse);
74
+ rpc DeleteCampaign (DeleteCampaignRequest) returns (DeleteCampaignResponse);
75
+ rpc StartCampaign (StartCampaignRequest) returns (CampaignResponse);
76
+ rpc CancelCampaign (CancelCampaignRequest) returns (CampaignResponse);
77
+ // Превью сегменту — повертає кількість + перші N клієнтів, БЕЗ створення кампанії.
78
+ rpc PreviewSegment (PreviewSegmentRequest) returns (PreviewSegmentResponse);
79
+ // Список отримувачів конкретної кампанії з їхніми статусами.
80
+ rpc ListRecipients (ListRecipientsRequest) returns (ListRecipientsResponse);
81
+ // Викликається з gateway після того, як сегмент резолвнено клієнтами —
82
+ // передається список {userId, recipient}, нотифікація розкладає у чергу.
83
+ rpc EnqueueRecipients(EnqueueRecipientsRequest) returns (EnqueueRecipientsResponse);
84
+ }
85
+
86
+ message EnqueueRecipientsRequest {
87
+ string campaign_id = 1;
88
+ repeated CampaignResolvedRecipient recipients = 2;
89
+ }
90
+
91
+ message CampaignResolvedRecipient {
92
+ string user_id = 1;
93
+ string recipient = 2; // email або phone
94
+ // true → у клієнта promotions=false. dispatcher позначить SKIPPED_OPT_OUT
95
+ // якщо respect_opt_out=true для кампанії.
96
+ bool opt_out = 3;
97
+ // Унікальний код, згенерований gateway через CouponsService.CreateCoupon.
98
+ // Підставляється у шаблон як {{coupon_code}}.
99
+ optional string coupon_code = 4;
100
+ }
101
+
102
+ message EnqueueRecipientsResponse {
103
+ int32 enqueued = 1;
104
+ }
105
+
106
+ message ListCampaignsRequest {
107
+ optional CampaignStatusEnum status = 1;
108
+ optional int32 page = 2;
109
+ optional int32 limit = 3;
110
+ }
111
+
112
+ message ListCampaignsResponse {
113
+ repeated Campaign items = 1;
114
+ int32 total = 2;
115
+ }
116
+
117
+ message GetCampaignRequest {
118
+ string id = 1;
119
+ }
120
+
121
+ message DeleteCampaignRequest {
122
+ string id = 1;
123
+ }
124
+
125
+ message DeleteCampaignResponse {
126
+ bool ok = 1;
127
+ }
128
+
129
+ message StartCampaignRequest {
130
+ string id = 1;
131
+ // ISO date; якщо порожньо → запустити одразу.
132
+ optional string scheduled_at = 2;
133
+ }
134
+
135
+ message CancelCampaignRequest {
136
+ string id = 1;
137
+ }
138
+
139
+ message CreateCampaignRequest {
140
+ string name = 1;
141
+ NotificationChannel channel = 2;
142
+ optional string template_id = 3;
143
+ optional string custom_subject = 4;
144
+ optional string custom_body_html = 5;
145
+ optional string custom_body_text = 6;
146
+ // JSON segment spec.
147
+ string segment_json = 7;
148
+ // JSON coupon spec — null/empty для кампаній без купонів.
149
+ optional string coupon_spec_json = 8;
150
+ bool respect_opt_out = 9;
151
+ optional NotificationLocale locale = 10;
152
+ string created_by_id = 11;
153
+ }
154
+
155
+ message UpdateCampaignRequest {
156
+ string id = 1;
157
+ // Можна оновлювати тільки DRAFT — гілки нижче опційні.
158
+ optional string name = 2;
159
+ optional NotificationChannel channel = 3;
160
+ optional string template_id = 4;
161
+ optional string custom_subject = 5;
162
+ optional string custom_body_html = 6;
163
+ optional string custom_body_text = 7;
164
+ optional string segment_json = 8;
165
+ optional string coupon_spec_json = 9;
166
+ optional bool respect_opt_out = 10;
167
+ optional NotificationLocale locale = 11;
168
+ }
169
+
170
+ message CampaignResponse {
171
+ bool ok = 1;
172
+ string error_message = 2;
173
+ Campaign campaign = 3;
174
+ }
175
+
176
+ message Campaign {
177
+ string id = 1;
178
+ string name = 2;
179
+ NotificationChannel channel = 3;
180
+ optional string template_id = 4;
181
+ optional string custom_subject = 5;
182
+ optional string custom_body_html = 6;
183
+ optional string custom_body_text = 7;
184
+ NotificationLocale locale = 8;
185
+ string segment_json = 9;
186
+ optional string coupon_spec_json = 10;
187
+ bool respect_opt_out = 11;
188
+ CampaignStatusEnum status = 12;
189
+ optional string scheduled_at = 13;
190
+ optional string started_at = 14;
191
+ optional string completed_at = 15;
192
+ int32 total_recipients = 16;
193
+ int32 sent_count = 17;
194
+ int32 failed_count = 18;
195
+ int32 skipped_opt_out_count = 19;
196
+ string created_by_id = 20;
197
+ string created_at = 21;
198
+ string updated_at = 22;
199
+ }
200
+
201
+ enum CampaignStatusEnum {
202
+ CAMPAIGN_STATUS_UNSPECIFIED = 0;
203
+ DRAFT = 1;
204
+ SCHEDULED = 2;
205
+ RUNNING = 3;
206
+ COMPLETED = 4;
207
+ CANCELLED = 5;
208
+ CAMPAIGN_FAILED = 6;
209
+ }
210
+
211
+ message PreviewSegmentRequest {
212
+ string segment_json = 1;
213
+ NotificationChannel channel = 2; // потрібно для перевірки наявності email/phone
214
+ }
215
+
216
+ message PreviewSegmentResponse {
217
+ int32 total_count = 1;
218
+ // Перші 10 для UI; решта — за необхідності окремим запитом.
219
+ repeated PreviewSegmentSample sample = 2;
220
+ }
221
+
222
+ message PreviewSegmentSample {
223
+ string user_id = 1;
224
+ string first_name = 2;
225
+ string last_name = 3;
226
+ string email = 4;
227
+ string phone = 5;
228
+ }
229
+
230
+ message ListRecipientsRequest {
231
+ string campaign_id = 1;
232
+ optional CampaignRecipientStatusEnum status = 2;
233
+ optional int32 page = 3;
234
+ optional int32 limit = 4;
235
+ }
236
+
237
+ message ListRecipientsResponse {
238
+ repeated CampaignRecipient items = 1;
239
+ int32 total = 2;
240
+ }
241
+
242
+ message CampaignRecipient {
243
+ string id = 1;
244
+ string campaign_id = 2;
245
+ string user_id = 3;
246
+ string recipient = 4;
247
+ CampaignRecipientStatusEnum status = 5;
248
+ optional string coupon_code = 6;
249
+ optional string notification_log_id = 7;
250
+ optional string error_message = 8;
251
+ optional string sent_at = 9;
252
+ string created_at = 10;
253
+ }
254
+
255
+ enum CampaignRecipientStatusEnum {
256
+ CAMPAIGN_RECIPIENT_UNSPECIFIED = 0;
257
+ RECIPIENT_PENDING = 1;
258
+ RECIPIENT_SENT = 2;
259
+ RECIPIENT_FAILED = 3;
260
+ RECIPIENT_SKIPPED_OPT_OUT = 4;
261
+ RECIPIENT_SKIPPED_NO_CONTACT = 5;
262
+ }
263
+
32
264
  // Брендинг email-шаблонів — singleton редагується адміном.
33
265
  service BrandingService {
34
266
  rpc GetBranding (BrandingEmpty) returns (BrandingProfile);
@@ -53,6 +285,8 @@ message BrandingProfile {
53
285
  string resolved_brand_name = 9;
54
286
  string resolved_brand_url = 10;
55
287
  string resolved_support_email = 11;
288
+ // Кастомний HTML-хедер (замість дефолтного логотип-блоку). Порожньо → default.
289
+ string header_html = 12;
56
290
  }
57
291
 
58
292
  message UpdateBrandingRequest {
@@ -63,6 +297,7 @@ message UpdateBrandingRequest {
63
297
  optional string support_email = 4;
64
298
  optional string footer_html = 5;
65
299
  string actor_id = 6;
300
+ optional string header_html = 7;
66
301
  }
67
302
 
68
303
 
@@ -238,6 +473,9 @@ message RenderPreviewRequest {
238
473
  // JSON-stringified variables { "order.number": "MD-2026-0001", ... }
239
474
  string variables_json = 2;
240
475
  optional string subject = 3;
476
+ // JSON-stringified branding override (header/footer/logo/brand…) для live-
477
+ // превʼю незбережених змін на сторінці брендингу. Порожньо → беремо з БД.
478
+ optional string branding_json = 4;
241
479
  }
242
480
 
243
481
  message RenderPreviewResponse {
@@ -25,6 +25,26 @@ service OrderService {
25
25
  rpc ReorderOrder(ReorderOrderRequest) returns (ReorderResponse);
26
26
 
27
27
  rpc ListHistory(ListHistoryRequest) returns (ListHistoryResponse);
28
+
29
+ // CRM-агрегація для експорту клієнтів — повертає count/sum/last для кожного userId.
30
+ rpc AggregateByCustomers(AggregateByCustomersRequest) returns (AggregateByCustomersResponse);
31
+ }
32
+
33
+ message AggregateByCustomersRequest {
34
+ repeated string user_ids = 1;
35
+ }
36
+
37
+ message CustomerOrderAggregate {
38
+ string user_id = 1;
39
+ int32 orders_count = 2;
40
+ // Decimal у вигляді рядка ('1234.56') — щоб не втратити точність на gRPC border.
41
+ string total_sum = 3;
42
+ // ISO-рядок, відсутній якщо клієнт ще не робив замовлень.
43
+ optional string last_order_at = 4;
44
+ }
45
+
46
+ message AggregateByCustomersResponse {
47
+ repeated CustomerOrderAggregate items = 1;
28
48
  }
29
49
 
30
50
  // ─── Enums (string у proto3, для зворотної сумісності) ─────────
@@ -47,6 +47,9 @@ service UsersService {
47
47
  rpc RemoveCartItem(RemoveCartItemRequest) returns (GetCartResponse);
48
48
  rpc ClearCart(ClearCartRequest) returns (ClearCartResponse);
49
49
  rpc SyncCart(SyncCartRequest) returns (GetCartResponse);
50
+
51
+ // Admin CRM — редагування клієнта менеджером (customers.edit на gateway).
52
+ rpc AdminUpdateCustomer(AdminUpdateCustomerRequest) returns (AdminUpdateCustomerResponse);
50
53
  }
51
54
 
52
55
  message GetMeRequest {
@@ -87,6 +90,19 @@ message User {
87
90
  int32 bonuses = 4;
88
91
  optional string email = 5;
89
92
  optional string phone = 6;
93
+ optional string internal_note = 7;
94
+ }
95
+
96
+ message AdminUpdateCustomerRequest {
97
+ string id = 1;
98
+ optional string first_name = 2;
99
+ optional string last_name = 3;
100
+ // internal_note редагується тут окремо, бо це CRM-поле — не видиме клієнту.
101
+ optional string internal_note = 4;
102
+ }
103
+
104
+ message AdminUpdateCustomerResponse {
105
+ User user = 1;
90
106
  }
91
107
 
92
108
  message NotificationSettings {
@@ -8,7 +8,6 @@ export interface OrderCancelledEvent {
8
8
  releaseStock: boolean
9
9
  cancelledAt: number
10
10
 
11
- // Опціональний customer-snapshot для нотифікацій.
12
11
  userId?: string
13
12
  customer?: {
14
13
  firstName: string
@@ -8,8 +8,6 @@ export interface OrderStatusChangedEvent {
8
8
  actorName?: string
9
9
  changedAt: number
10
10
 
11
- // Опціональний customer-snapshot для нотифікацій (щоб
12
- // notification-service не робив додатковий lookup в order-service).
13
11
  userId?: string
14
12
  customer?: {
15
13
  firstName: string
@@ -18,7 +16,6 @@ export interface OrderStatusChangedEvent {
18
16
  phone: string
19
17
  }
20
18
 
21
- // На переходах у shipped — ТТН + tracking URL.
22
19
  trackingNumber?: string
23
20
  trackingUrl?: string
24
21
  }
@@ -1,5 +1,3 @@
1
- // Подія від order-service: замовлення переходить у packing → catalog
2
- // списує резерв у реальний OUTGOING рух (підтверджує продаж).
3
1
  export interface StockConfirmRequestedEvent {
4
2
  orderId: string
5
3
  orderNumber: string
@@ -1,5 +1,3 @@
1
- // Подія від order-service: cancel/відмова → catalog скасовує всі резерви
2
- // замовлення (по reference_type='order', reference_id=orderId).
3
1
  export interface StockReleaseRequestedEvent {
4
2
  orderId: string
5
3
  orderNumber: string
@@ -1,5 +1,3 @@
1
- // Подія від catalog-service: catalog НЕ зміг зарезервувати (insufficient
2
- // stock, product disappeared). Order-service кенселить замовлення з reason.
3
1
  export interface StockReserveFailedEvent {
4
2
  orderId: string
5
3
  orderNumber: string
@@ -1,9 +1,3 @@
1
- // Подія від order-service: catalog має зарезервувати stock для замовлення.
2
- // Catalog сам обирає warehouse(и) (auto-pick first-available, greedy
3
- // fill через product_warehouses ORDER BY created_at).
4
- //
5
- // TTL у мс — після його закінчення UnpaidOrdersCron у order-service
6
- // автоматично скасує замовлення → catalog отримає stock.release_requested.
7
1
  export interface StockReserveRequestedEvent {
8
2
  orderId: string
9
3
  orderNumber: string
@@ -1,5 +1,3 @@
1
- // Подія від catalog-service: успішний резерв → order-service зберігає
2
- // reservationId/reservedUntil у OrderItem (для admin UI і traceability).
3
1
  export interface StockReservedEvent {
4
2
  orderId: string
5
3
  orderNumber: string