@open-mercato/core 0.7.1-develop.7175.1.d49ab48ee2 → 0.7.1-develop.7176.1.d229567a50

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.
Files changed (79) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/phone_call/index.js +53 -0
  3. package/dist/generated/entities/phone_call/index.js.map +7 -0
  4. package/dist/generated/entities/phone_call_participant/index.js +27 -0
  5. package/dist/generated/entities/phone_call_participant/index.js.map +7 -0
  6. package/dist/generated/entities.ids.generated.js +5 -0
  7. package/dist/generated/entities.ids.generated.js.map +2 -2
  8. package/dist/generated/entity-fields-registry.js +41 -0
  9. package/dist/generated/entity-fields-registry.js.map +2 -2
  10. package/dist/modules/configs/lib/upgrade-actions.js +20 -0
  11. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  12. package/dist/modules/entities/migrations/Migration20260822120000.js +44 -0
  13. package/dist/modules/entities/migrations/Migration20260822120000.js.map +7 -0
  14. package/dist/modules/phone_calls/acl.js +15 -0
  15. package/dist/modules/phone_calls/acl.js.map +7 -0
  16. package/dist/modules/phone_calls/api/calls/route.js +132 -0
  17. package/dist/modules/phone_calls/api/calls/route.js.map +7 -0
  18. package/dist/modules/phone_calls/api/openapi.js +23 -0
  19. package/dist/modules/phone_calls/api/openapi.js.map +7 -0
  20. package/dist/modules/phone_calls/backend/page.js +261 -0
  21. package/dist/modules/phone_calls/backend/page.js.map +7 -0
  22. package/dist/modules/phone_calls/backend/page.meta.js +16 -0
  23. package/dist/modules/phone_calls/backend/page.meta.js.map +7 -0
  24. package/dist/modules/phone_calls/commands/calls.js +125 -0
  25. package/dist/modules/phone_calls/commands/calls.js.map +7 -0
  26. package/dist/modules/phone_calls/commands/index.js +2 -0
  27. package/dist/modules/phone_calls/commands/index.js.map +7 -0
  28. package/dist/modules/phone_calls/data/entities.js +162 -0
  29. package/dist/modules/phone_calls/data/entities.js.map +7 -0
  30. package/dist/modules/phone_calls/data/validators.js +68 -0
  31. package/dist/modules/phone_calls/data/validators.js.map +7 -0
  32. package/dist/modules/phone_calls/di.js +12 -0
  33. package/dist/modules/phone_calls/di.js.map +7 -0
  34. package/dist/modules/phone_calls/encryption.js +28 -0
  35. package/dist/modules/phone_calls/encryption.js.map +7 -0
  36. package/dist/modules/phone_calls/events.js +18 -0
  37. package/dist/modules/phone_calls/events.js.map +7 -0
  38. package/dist/modules/phone_calls/index.js +15 -0
  39. package/dist/modules/phone_calls/index.js.map +7 -0
  40. package/dist/modules/phone_calls/migrations/Migration20260706124120_phone_calls.js +22 -0
  41. package/dist/modules/phone_calls/migrations/Migration20260706124120_phone_calls.js.map +7 -0
  42. package/dist/modules/phone_calls/migrations/Migration20260814124333_phone_calls.js +13 -0
  43. package/dist/modules/phone_calls/migrations/Migration20260814124333_phone_calls.js.map +7 -0
  44. package/dist/modules/phone_calls/setup.js +16 -0
  45. package/dist/modules/phone_calls/setup.js.map +7 -0
  46. package/generated/entities/phone_call/index.ts +25 -0
  47. package/generated/entities/phone_call_participant/index.ts +12 -0
  48. package/generated/entities.ids.generated.ts +5 -0
  49. package/generated/entity-fields-registry.ts +41 -0
  50. package/package.json +7 -7
  51. package/src/modules/auth/i18n/de.json +3 -0
  52. package/src/modules/auth/i18n/en.json +3 -0
  53. package/src/modules/auth/i18n/es.json +3 -0
  54. package/src/modules/auth/i18n/ko.json +3 -0
  55. package/src/modules/auth/i18n/pl.json +3 -0
  56. package/src/modules/configs/lib/upgrade-actions.ts +20 -0
  57. package/src/modules/entities/migrations/Migration20260822120000.ts +56 -0
  58. package/src/modules/phone_calls/acl.ts +11 -0
  59. package/src/modules/phone_calls/api/calls/route.ts +150 -0
  60. package/src/modules/phone_calls/api/openapi.ts +25 -0
  61. package/src/modules/phone_calls/backend/page.meta.ts +12 -0
  62. package/src/modules/phone_calls/backend/page.tsx +303 -0
  63. package/src/modules/phone_calls/commands/calls.ts +156 -0
  64. package/src/modules/phone_calls/commands/index.ts +1 -0
  65. package/src/modules/phone_calls/data/entities.ts +144 -0
  66. package/src/modules/phone_calls/data/validators.ts +69 -0
  67. package/src/modules/phone_calls/di.ts +10 -0
  68. package/src/modules/phone_calls/encryption.ts +26 -0
  69. package/src/modules/phone_calls/events.ts +25 -0
  70. package/src/modules/phone_calls/i18n/de.json +42 -0
  71. package/src/modules/phone_calls/i18n/en.json +42 -0
  72. package/src/modules/phone_calls/i18n/es.json +42 -0
  73. package/src/modules/phone_calls/i18n/ko.json +42 -0
  74. package/src/modules/phone_calls/i18n/pl.json +42 -0
  75. package/src/modules/phone_calls/index.ts +13 -0
  76. package/src/modules/phone_calls/migrations/.snapshot-open-mercato.json +772 -0
  77. package/src/modules/phone_calls/migrations/Migration20260706124120_phone_calls.ts +23 -0
  78. package/src/modules/phone_calls/migrations/Migration20260814124333_phone_calls.ts +13 -0
  79. package/src/modules/phone_calls/setup.ts +14 -0
@@ -0,0 +1,22 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20260706124120_phone_calls extends Migration {
3
+ up() {
4
+ this.addSql(`create table "phone_calls" ("id" uuid not null default gen_random_uuid(), "organization_id" uuid not null, "tenant_id" uuid not null, "provider_key" text not null, "integration_id" text null, "external_call_id" text not null, "external_conversation_id" text null, "direction" text not null, "status" text not null, "started_at" timestamptz null, "answered_at" timestamptz null, "ended_at" timestamptz null, "duration_seconds" int null, "recording_url" text null, "recording_attachment_id" uuid null, "active_transcript_version_id" uuid null, "active_summary_version_id" uuid null, "communication_projection_id" uuid null, "provider_facts" jsonb null, "raw_snapshot" jsonb null, "ingest_status" text not null default 'pending', "last_ingested_at" timestamptz null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, primary key ("id"));`);
5
+ this.addSql(`create index "phone_calls_communication_projection_id_idx" on "phone_calls" ("communication_projection_id") where "communication_projection_id" is not null;`);
6
+ this.addSql(`create index "phone_calls_org_status_idx" on "phone_calls" ("organization_id", "status");`);
7
+ this.addSql(`create index "phone_calls_org_started_at_idx" on "phone_calls" ("organization_id", "started_at");`);
8
+ this.addSql(`create index "phone_calls_org_tenant_idx" on "phone_calls" ("organization_id", "tenant_id");`);
9
+ this.addSql(`alter table "phone_calls" add constraint "phone_calls_provider_external_scope_uq" unique ("provider_key", "external_call_id", "tenant_id", "organization_id");`);
10
+ this.addSql(`create table "phone_call_participants" ("id" uuid not null default gen_random_uuid(), "organization_id" uuid not null, "tenant_id" uuid not null, "phone_call_id" uuid not null, "provider_participant_id" text null, "role" text not null, "phone_number" text null, "display_name" text null, "email" text null, "metadata" jsonb null, "created_at" timestamptz not null, "updated_at" timestamptz not null, primary key ("id"));`);
11
+ this.addSql(`create index "phone_call_participants_org_tenant_idx" on "phone_call_participants" ("organization_id", "tenant_id");`);
12
+ this.addSql(`create index "phone_call_participants_call_idx" on "phone_call_participants" ("phone_call_id");`);
13
+ }
14
+ down() {
15
+ this.addSql(`drop table if exists "phone_call_participants";`);
16
+ this.addSql(`drop table if exists "phone_calls";`);
17
+ }
18
+ }
19
+ export {
20
+ Migration20260706124120_phone_calls
21
+ };
22
+ //# sourceMappingURL=Migration20260706124120_phone_calls.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/phone_calls/migrations/Migration20260706124120_phone_calls.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260706124120_phone_calls extends Migration {\n\n override up(): void | Promise<void> {\n this.addSql(`create table \"phone_calls\" (\"id\" uuid not null default gen_random_uuid(), \"organization_id\" uuid not null, \"tenant_id\" uuid not null, \"provider_key\" text not null, \"integration_id\" text null, \"external_call_id\" text not null, \"external_conversation_id\" text null, \"direction\" text not null, \"status\" text not null, \"started_at\" timestamptz null, \"answered_at\" timestamptz null, \"ended_at\" timestamptz null, \"duration_seconds\" int null, \"recording_url\" text null, \"recording_attachment_id\" uuid null, \"active_transcript_version_id\" uuid null, \"active_summary_version_id\" uuid null, \"communication_projection_id\" uuid null, \"provider_facts\" jsonb null, \"raw_snapshot\" jsonb null, \"ingest_status\" text not null default 'pending', \"last_ingested_at\" timestamptz null, \"created_at\" timestamptz not null, \"updated_at\" timestamptz not null, \"deleted_at\" timestamptz null, primary key (\"id\"));`);\n this.addSql(`create index \"phone_calls_communication_projection_id_idx\" on \"phone_calls\" (\"communication_projection_id\") where \"communication_projection_id\" is not null;`);\n this.addSql(`create index \"phone_calls_org_status_idx\" on \"phone_calls\" (\"organization_id\", \"status\");`);\n this.addSql(`create index \"phone_calls_org_started_at_idx\" on \"phone_calls\" (\"organization_id\", \"started_at\");`);\n this.addSql(`create index \"phone_calls_org_tenant_idx\" on \"phone_calls\" (\"organization_id\", \"tenant_id\");`);\n this.addSql(`alter table \"phone_calls\" add constraint \"phone_calls_provider_external_scope_uq\" unique (\"provider_key\", \"external_call_id\", \"tenant_id\", \"organization_id\");`);\n\n this.addSql(`create table \"phone_call_participants\" (\"id\" uuid not null default gen_random_uuid(), \"organization_id\" uuid not null, \"tenant_id\" uuid not null, \"phone_call_id\" uuid not null, \"provider_participant_id\" text null, \"role\" text not null, \"phone_number\" text null, \"display_name\" text null, \"email\" text null, \"metadata\" jsonb null, \"created_at\" timestamptz not null, \"updated_at\" timestamptz not null, primary key (\"id\"));`);\n this.addSql(`create index \"phone_call_participants_org_tenant_idx\" on \"phone_call_participants\" (\"organization_id\", \"tenant_id\");`);\n this.addSql(`create index \"phone_call_participants_call_idx\" on \"phone_call_participants\" (\"phone_call_id\");`);\n }\n\n override down(): void | Promise<void> {\n this.addSql(`drop table if exists \"phone_call_participants\";`);\n this.addSql(`drop table if exists \"phone_calls\";`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,4CAA4C,UAAU;AAAA,EAExD,KAA2B;AAClC,SAAK,OAAO,u3BAAu3B;AACn4B,SAAK,OAAO,8JAA8J;AAC1K,SAAK,OAAO,2FAA2F;AACvG,SAAK,OAAO,mGAAmG;AAC/G,SAAK,OAAO,8FAA8F;AAC1G,SAAK,OAAO,gKAAgK;AAE5K,SAAK,OAAO,saAAsa;AAClb,SAAK,OAAO,sHAAsH;AAClI,SAAK,OAAO,iGAAiG;AAAA,EAC/G;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,iDAAiD;AAC7D,SAAK,OAAO,qCAAqC;AAAA,EACnD;AAEF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,13 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20260814124333_phone_calls extends Migration {
3
+ up() {
4
+ this.addSql(`alter table "phone_call_participants" add constraint "phone_call_participants_phone_call_id_foreign" foreign key ("phone_call_id") references "phone_calls" ("id") on delete cascade;`);
5
+ }
6
+ down() {
7
+ this.addSql(`alter table "phone_call_participants" drop constraint if exists "phone_call_participants_phone_call_id_foreign";`);
8
+ }
9
+ }
10
+ export {
11
+ Migration20260814124333_phone_calls
12
+ };
13
+ //# sourceMappingURL=Migration20260814124333_phone_calls.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/phone_calls/migrations/Migration20260814124333_phone_calls.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260814124333_phone_calls extends Migration {\n\n override up(): void | Promise<void> {\n this.addSql(`alter table \"phone_call_participants\" add constraint \"phone_call_participants_phone_call_id_foreign\" foreign key (\"phone_call_id\") references \"phone_calls\" (\"id\") on delete cascade;`);\n }\n\n override down(): void | Promise<void> {\n this.addSql(`alter table \"phone_call_participants\" drop constraint if exists \"phone_call_participants_phone_call_id_foreign\";`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,4CAA4C,UAAU;AAAA,EAExD,KAA2B;AAClC,SAAK,OAAO,uLAAuL;AAAA,EACrM;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,kHAAkH;AAAA,EAChI;AAEF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ const setup = {
2
+ defaultRoleFeatures: {
3
+ admin: [
4
+ "phone_calls.*"
5
+ ],
6
+ employee: [
7
+ "phone_calls.view"
8
+ ]
9
+ }
10
+ };
11
+ var setup_default = setup;
12
+ export {
13
+ setup_default as default,
14
+ setup
15
+ };
16
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/phone_calls/setup.ts"],
4
+ "sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n admin: [\n 'phone_calls.*',\n ],\n employee: [\n 'phone_calls.view',\n ],\n },\n}\n\nexport default setup\n"],
5
+ "mappings": "AAEO,MAAM,QAA2B;AAAA,EACtC,qBAAqB;AAAA,IACnB,OAAO;AAAA,MACL;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,25 @@
1
+ export const id = "id";
2
+ export const organization_id = "organization_id";
3
+ export const tenant_id = "tenant_id";
4
+ export const provider_key = "provider_key";
5
+ export const integration_id = "integration_id";
6
+ export const external_call_id = "external_call_id";
7
+ export const external_conversation_id = "external_conversation_id";
8
+ export const direction = "direction";
9
+ export const status = "status";
10
+ export const started_at = "started_at";
11
+ export const answered_at = "answered_at";
12
+ export const ended_at = "ended_at";
13
+ export const duration_seconds = "duration_seconds";
14
+ export const recording_url = "recording_url";
15
+ export const recording_attachment_id = "recording_attachment_id";
16
+ export const active_transcript_version_id = "active_transcript_version_id";
17
+ export const active_summary_version_id = "active_summary_version_id";
18
+ export const communication_projection_id = "communication_projection_id";
19
+ export const provider_facts = "provider_facts";
20
+ export const raw_snapshot = "raw_snapshot";
21
+ export const ingest_status = "ingest_status";
22
+ export const last_ingested_at = "last_ingested_at";
23
+ export const created_at = "created_at";
24
+ export const updated_at = "updated_at";
25
+ export const deleted_at = "deleted_at";
@@ -0,0 +1,12 @@
1
+ export const id = "id";
2
+ export const organization_id = "organization_id";
3
+ export const tenant_id = "tenant_id";
4
+ export const phone_call_id = "phone_call_id";
5
+ export const provider_participant_id = "provider_participant_id";
6
+ export const role = "role";
7
+ export const phone_number = "phone_number";
8
+ export const display_name = "display_name";
9
+ export const email = "email";
10
+ export const metadata = "metadata";
11
+ export const created_at = "created_at";
12
+ export const updated_at = "updated_at";
@@ -34,6 +34,7 @@ export const M = {
34
34
  "messages": "messages",
35
35
  "communication_channels": "communication_channels",
36
36
  "push_notifications": "push_notifications",
37
+ "phone_calls": "phone_calls",
37
38
  "translations": "translations",
38
39
  "inbox_ops": "inbox_ops",
39
40
  "payment_gateways": "payment_gateways",
@@ -293,6 +294,10 @@ export const M = {
293
294
  "push_notifications": {
294
295
  "push_notification_delivery": "push_notifications:push_notification_delivery"
295
296
  },
297
+ "phone_calls": {
298
+ "phone_call": "phone_calls:phone_call",
299
+ "phone_call_participant": "phone_calls:phone_call_participant"
300
+ },
296
301
  "translations": {
297
302
  "entity_translation": "translations:entity_translation"
298
303
  },
@@ -1872,6 +1872,47 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
1872
1872
  "updated_at": "updated_at",
1873
1873
  "deleted_at": "deleted_at"
1874
1874
  },
1875
+ "phone_call": {
1876
+ "id": "id",
1877
+ "organization_id": "organization_id",
1878
+ "tenant_id": "tenant_id",
1879
+ "provider_key": "provider_key",
1880
+ "integration_id": "integration_id",
1881
+ "external_call_id": "external_call_id",
1882
+ "external_conversation_id": "external_conversation_id",
1883
+ "direction": "direction",
1884
+ "status": "status",
1885
+ "started_at": "started_at",
1886
+ "answered_at": "answered_at",
1887
+ "ended_at": "ended_at",
1888
+ "duration_seconds": "duration_seconds",
1889
+ "recording_url": "recording_url",
1890
+ "recording_attachment_id": "recording_attachment_id",
1891
+ "active_transcript_version_id": "active_transcript_version_id",
1892
+ "active_summary_version_id": "active_summary_version_id",
1893
+ "communication_projection_id": "communication_projection_id",
1894
+ "provider_facts": "provider_facts",
1895
+ "raw_snapshot": "raw_snapshot",
1896
+ "ingest_status": "ingest_status",
1897
+ "last_ingested_at": "last_ingested_at",
1898
+ "created_at": "created_at",
1899
+ "updated_at": "updated_at",
1900
+ "deleted_at": "deleted_at"
1901
+ },
1902
+ "phone_call_participant": {
1903
+ "id": "id",
1904
+ "organization_id": "organization_id",
1905
+ "tenant_id": "tenant_id",
1906
+ "phone_call_id": "phone_call_id",
1907
+ "provider_participant_id": "provider_participant_id",
1908
+ "role": "role",
1909
+ "phone_number": "phone_number",
1910
+ "display_name": "display_name",
1911
+ "email": "email",
1912
+ "metadata": "metadata",
1913
+ "created_at": "created_at",
1914
+ "updated_at": "updated_at"
1915
+ },
1875
1916
  "planner_availability_rule": {
1876
1917
  "id": "id",
1877
1918
  "tenant_id": "tenant_id",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.7.1-develop.7175.1.d49ab48ee2",
3
+ "version": "0.7.1-develop.7176.1.d229567a50",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -252,16 +252,16 @@
252
252
  "zod": "^4.4.3"
253
253
  },
254
254
  "peerDependencies": {
255
- "@open-mercato/ai-assistant": "0.7.1-develop.7175.1.d49ab48ee2",
256
- "@open-mercato/shared": "0.7.1-develop.7175.1.d49ab48ee2",
257
- "@open-mercato/ui": "0.7.1-develop.7175.1.d49ab48ee2",
255
+ "@open-mercato/ai-assistant": "0.7.1-develop.7176.1.d229567a50",
256
+ "@open-mercato/shared": "0.7.1-develop.7176.1.d229567a50",
257
+ "@open-mercato/ui": "0.7.1-develop.7176.1.d229567a50",
258
258
  "react": "^19.0.0",
259
259
  "react-dom": "^19.0.0"
260
260
  },
261
261
  "devDependencies": {
262
- "@open-mercato/ai-assistant": "0.7.1-develop.7175.1.d49ab48ee2",
263
- "@open-mercato/shared": "0.7.1-develop.7175.1.d49ab48ee2",
264
- "@open-mercato/ui": "0.7.1-develop.7175.1.d49ab48ee2",
262
+ "@open-mercato/ai-assistant": "0.7.1-develop.7176.1.d229567a50",
263
+ "@open-mercato/shared": "0.7.1-develop.7176.1.d229567a50",
264
+ "@open-mercato/ui": "0.7.1-develop.7176.1.d229567a50",
265
265
  "@testing-library/dom": "^10.4.1",
266
266
  "@testing-library/jest-dom": "^7.0.1",
267
267
  "@testing-library/react": "^16.3.3",
@@ -203,6 +203,8 @@
203
203
  "auth.acl.features.payment_gateways.view": "View payment gateway transactions",
204
204
  "auth.acl.features.perspectives.role_defaults": "Manage role default perspectives",
205
205
  "auth.acl.features.perspectives.use": "Use table perspectives",
206
+ "auth.acl.features.phone_calls.manage": "Telefonanrufe verwalten (Abruf, Rohdaten)",
207
+ "auth.acl.features.phone_calls.view": "Telefonanrufe anzeigen",
206
208
  "auth.acl.features.planner.manage_availability": "Manage availability",
207
209
  "auth.acl.features.planner.view": "View schedules",
208
210
  "auth.acl.features.progress.cancel": "Cancel progress jobs",
@@ -280,6 +282,7 @@
280
282
  "auth.acl.features.storage_providers.manage": "Manage storage providers",
281
283
  "auth.acl.features.sync_excel.run": "Upload CSV files and prepare imports",
282
284
  "auth.acl.features.sync_excel.view": "View sync_excel uploads and previews",
285
+ "auth.acl.features.tillio.manage": "Tillio-Integration verwalten",
283
286
  "auth.acl.features.translations.manage": "Manage entity translations",
284
287
  "auth.acl.features.translations.manage_locales": "Configure supported locales",
285
288
  "auth.acl.features.translations.view": "View translations",
@@ -203,6 +203,8 @@
203
203
  "auth.acl.features.payment_gateways.view": "View payment gateway transactions",
204
204
  "auth.acl.features.perspectives.role_defaults": "Manage role default perspectives",
205
205
  "auth.acl.features.perspectives.use": "Use table perspectives",
206
+ "auth.acl.features.phone_calls.manage": "Manage phone calls (pull, raw payload)",
207
+ "auth.acl.features.phone_calls.view": "View phone calls",
206
208
  "auth.acl.features.planner.manage_availability": "Manage availability",
207
209
  "auth.acl.features.planner.view": "View schedules",
208
210
  "auth.acl.features.progress.cancel": "Cancel progress jobs",
@@ -280,6 +282,7 @@
280
282
  "auth.acl.features.storage_providers.manage": "Manage storage providers",
281
283
  "auth.acl.features.sync_excel.run": "Upload CSV files and prepare imports",
282
284
  "auth.acl.features.sync_excel.view": "View sync_excel uploads and previews",
285
+ "auth.acl.features.tillio.manage": "Manage Tillio integration",
283
286
  "auth.acl.features.translations.manage": "Manage entity translations",
284
287
  "auth.acl.features.translations.manage_locales": "Configure supported locales",
285
288
  "auth.acl.features.translations.view": "View translations",
@@ -203,6 +203,8 @@
203
203
  "auth.acl.features.payment_gateways.view": "View payment gateway transactions",
204
204
  "auth.acl.features.perspectives.role_defaults": "Manage role default perspectives",
205
205
  "auth.acl.features.perspectives.use": "Use table perspectives",
206
+ "auth.acl.features.phone_calls.manage": "Gestionar llamadas telefónicas (importación, datos sin procesar)",
207
+ "auth.acl.features.phone_calls.view": "Ver llamadas telefónicas",
206
208
  "auth.acl.features.planner.manage_availability": "Manage availability",
207
209
  "auth.acl.features.planner.view": "View schedules",
208
210
  "auth.acl.features.progress.cancel": "Cancel progress jobs",
@@ -280,6 +282,7 @@
280
282
  "auth.acl.features.storage_providers.manage": "Manage storage providers",
281
283
  "auth.acl.features.sync_excel.run": "Upload CSV files and prepare imports",
282
284
  "auth.acl.features.sync_excel.view": "View sync_excel uploads and previews",
285
+ "auth.acl.features.tillio.manage": "Gestionar la integración con Tillio",
283
286
  "auth.acl.features.translations.manage": "Manage entity translations",
284
287
  "auth.acl.features.translations.manage_locales": "Configure supported locales",
285
288
  "auth.acl.features.translations.view": "View translations",
@@ -203,6 +203,8 @@
203
203
  "auth.acl.features.payment_gateways.view": "View payment gateway transactions",
204
204
  "auth.acl.features.perspectives.role_defaults": "Manage role default perspectives",
205
205
  "auth.acl.features.perspectives.use": "Use table perspectives",
206
+ "auth.acl.features.phone_calls.manage": "전화 통화 관리(가져오기, 원시 페이로드)",
207
+ "auth.acl.features.phone_calls.view": "전화 통화 보기",
206
208
  "auth.acl.features.planner.manage_availability": "Manage availability",
207
209
  "auth.acl.features.planner.view": "View schedules",
208
210
  "auth.acl.features.progress.cancel": "Cancel progress jobs",
@@ -280,6 +282,7 @@
280
282
  "auth.acl.features.storage_providers.manage": "Manage storage providers",
281
283
  "auth.acl.features.sync_excel.run": "Upload CSV files and prepare imports",
282
284
  "auth.acl.features.sync_excel.view": "View sync_excel uploads and previews",
285
+ "auth.acl.features.tillio.manage": "Tillio 통합 관리",
283
286
  "auth.acl.features.translations.manage": "Manage entity translations",
284
287
  "auth.acl.features.translations.manage_locales": "Configure supported locales",
285
288
  "auth.acl.features.translations.view": "View translations",
@@ -203,6 +203,8 @@
203
203
  "auth.acl.features.payment_gateways.view": "Wyświetl transakcje bramek płatniczych",
204
204
  "auth.acl.features.perspectives.role_defaults": "Zarządzaj domyślnymi perspektywami ról",
205
205
  "auth.acl.features.perspectives.use": "Używaj perspektyw tabel",
206
+ "auth.acl.features.phone_calls.manage": "Zarządzaj połączeniami telefonicznymi (pobieranie, surowe dane)",
207
+ "auth.acl.features.phone_calls.view": "Wyświetl połączenia telefoniczne",
206
208
  "auth.acl.features.planner.manage_availability": "Zarządzaj dostępnością",
207
209
  "auth.acl.features.planner.view": "Wyświetl harmonogramy",
208
210
  "auth.acl.features.progress.cancel": "Anuluj zadania postępu",
@@ -280,6 +282,7 @@
280
282
  "auth.acl.features.storage_providers.manage": "Zarządzaj dostawcami pamięci masowej",
281
283
  "auth.acl.features.sync_excel.run": "Przesyłaj pliki CSV i przygotowuj importy",
282
284
  "auth.acl.features.sync_excel.view": "Wyświetl przesłane pliki i podglądy importu Excel / CSV",
285
+ "auth.acl.features.tillio.manage": "Zarządzaj integracją Tillio",
283
286
  "auth.acl.features.translations.manage": "Zarządzaj tłumaczeniami encji",
284
287
  "auth.acl.features.translations.manage_locales": "Konfiguruj obsługiwane ustawienia regionalne",
285
288
  "auth.acl.features.translations.view": "Wyświetl tłumaczenia",
@@ -126,6 +126,26 @@ export const upgradeActions: UpgradeActionDefinition[] = [
126
126
  await registerSessionInitializationPruneSchedule(container, { tenantId, organizationId })
127
127
  },
128
128
  },
129
+ {
130
+ id: 'phone_calls.seed-call-encryption-maps',
131
+ version: '0.7.1',
132
+ messageKey: 'phone_calls.config.upgradeActions.encryptionMaps.message',
133
+ ctaKey: 'phone_calls.config.upgradeActions.encryptionMaps.cta',
134
+ successKey: 'phone_calls.config.upgradeActions.encryptionMaps.success',
135
+ loadingKey: 'phone_calls.config.upgradeActions.encryptionMaps.loading',
136
+ // `Migration20260822120000` backfills these maps, but only for scopes that had active maps when
137
+ // it ran. A tenant that upgraded with encryption disabled and turned it on afterwards is left
138
+ // without a map, so ingest writes call PII as plaintext with nothing to signal it. This is the
139
+ // heal for that case, and the same re-assert path devices exposes for its own map.
140
+ // Lazy-imported so configs stays decoupled from phone_calls/entities (mirrors the devices action).
141
+ run: async ({ em, tenantId, organizationId }) => {
142
+ const [{ default: phoneCallsEncryptionMaps }, { upsertEncryptionMapSpecs }] = await Promise.all([
143
+ import('@open-mercato/core/modules/phone_calls/encryption'),
144
+ import('@open-mercato/core/modules/entities/cli'),
145
+ ])
146
+ await upsertEncryptionMapSpecs(em, tenantId, organizationId ?? null, phoneCallsEncryptionMaps)
147
+ },
148
+ },
129
149
  ]
130
150
 
131
151
  export function actionsUpToVersion(version: string): UpgradeActionDefinition[] {
@@ -0,0 +1,56 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ // Backfill the phone_calls encryption maps for every pre-existing (tenant, org) scope that already
4
+ // has active encryption maps. Encryption maps are seeded only at tenant creation (`entities
5
+ // seed-encryption`), so a tenant that predates the phone_calls module has no map for either of its
6
+ // entities, `encryptEntityPayload` no-ops, and caller/destination numbers plus the untouched Tillio
7
+ // payload are written as PLAINTEXT, both in the base tables and in the query index doc. Restricting
8
+ // the insert to scopes that ALREADY have maps mirrors what seed-encryption does and correctly skips
9
+ // tenants with encryption disabled (which have no maps at all). Idempotent via the NOT EXISTS guard,
10
+ // so re-runs are a no-op and it stays consistent with the runtime `upsertEncryptionMapSpecs` helper.
11
+ // Same shape as the `devices:user_device` backfill in Migration20260722120000.
12
+ export class Migration20260822120000 extends Migration {
13
+
14
+ override async up(): Promise<void> {
15
+ this.addSql(`
16
+ insert into "encryption_maps" ("id", "entity_id", "tenant_id", "organization_id", "fields_json", "is_active", "created_at", "updated_at")
17
+ select gen_random_uuid(), 'phone_calls:phone_call', src."tenant_id", src."organization_id", '[{"field":"raw_snapshot"},{"field":"provider_facts"},{"field":"recording_url"}]'::jsonb, true, now(), now()
18
+ from (
19
+ select distinct "tenant_id", "organization_id"
20
+ from "encryption_maps"
21
+ where "is_active" = true and "deleted_at" is null
22
+ ) src
23
+ where not exists (
24
+ select 1 from "encryption_maps" existing
25
+ where existing."entity_id" = 'phone_calls:phone_call'
26
+ and existing."tenant_id" is not distinct from src."tenant_id"
27
+ and existing."organization_id" is not distinct from src."organization_id"
28
+ and existing."deleted_at" is null
29
+ );
30
+ `);
31
+
32
+ this.addSql(`
33
+ insert into "encryption_maps" ("id", "entity_id", "tenant_id", "organization_id", "fields_json", "is_active", "created_at", "updated_at")
34
+ select gen_random_uuid(), 'phone_calls:phone_call_participant', src."tenant_id", src."organization_id", '[{"field":"phone_number"},{"field":"display_name"},{"field":"email"}]'::jsonb, true, now(), now()
35
+ from (
36
+ select distinct "tenant_id", "organization_id"
37
+ from "encryption_maps"
38
+ where "is_active" = true and "deleted_at" is null
39
+ ) src
40
+ where not exists (
41
+ select 1 from "encryption_maps" existing
42
+ where existing."entity_id" = 'phone_calls:phone_call_participant'
43
+ and existing."tenant_id" is not distinct from src."tenant_id"
44
+ and existing."organization_id" is not distinct from src."organization_id"
45
+ and existing."deleted_at" is null
46
+ );
47
+ `);
48
+ }
49
+
50
+ override async down(): Promise<void> {
51
+ // Reverts the feature's encryption maps. phone_calls maps have no source other than this module,
52
+ // so deleting them all returns the schema to its pre-feature state.
53
+ this.addSql(`delete from "encryption_maps" where "entity_id" in ('phone_calls:phone_call', 'phone_calls:phone_call_participant');`);
54
+ }
55
+
56
+ }
@@ -0,0 +1,11 @@
1
+ export const features = [
2
+ { id: 'phone_calls.view', title: 'View phone calls', module: 'phone_calls' },
3
+ {
4
+ id: 'phone_calls.manage',
5
+ title: 'Manage phone calls (pull, raw payload)',
6
+ module: 'phone_calls',
7
+ dependsOn: ['phone_calls.view'],
8
+ },
9
+ ]
10
+
11
+ export default features
@@ -0,0 +1,150 @@
1
+ import { z } from 'zod'
2
+ import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
3
+ import type { Where } from '@open-mercato/shared/lib/query/types'
4
+ import { buildIlikeTerm } from '@open-mercato/shared/lib/db/buildIlikeTerm'
5
+ import { E } from '#generated/entities.ids.generated'
6
+ import { PhoneCall } from '../../data/entities'
7
+ import { createPhoneCallsCrudOpenApi, createPagedListResponseSchema } from '../openapi'
8
+
9
+ const filterDateSchema = z
10
+ .string()
11
+ .trim()
12
+ .min(1)
13
+ .refine((value) => !Number.isNaN(new Date(value).getTime()), { message: 'Expected a parsable date' })
14
+
15
+ const listSchema = z
16
+ .object({
17
+ page: z.coerce.number().min(1).default(1),
18
+ pageSize: z.coerce.number().min(1).max(100).default(50),
19
+ q: z.string().optional(),
20
+ providerKey: z.string().optional(),
21
+ status: z.string().optional(),
22
+ direction: z.string().optional(),
23
+ // Validated rather than parsed later: an unparsable value used to be dropped, so a typo in a
24
+ // filter silently returned the unfiltered page.
25
+ startedFrom: filterDateSchema.optional(),
26
+ startedTo: filterDateSchema.optional(),
27
+ sortField: z.string().optional(),
28
+ sortDir: z.enum(['asc', 'desc']).optional(),
29
+ id: z.string().uuid().optional(),
30
+ })
31
+ .passthrough()
32
+
33
+ const dayOnlySchema = z.iso.date()
34
+
35
+ // A day is a range, not an instant. `new Date('2026-08-20')` is midnight UTC, so using it as
36
+ // the upper bound drops every call on the day the user picked. The filter bar sends bare
37
+ // `yyyy-MM-dd` for both ends, so each is widened to the day it names; a caller that sends a
38
+ // full timestamp keeps the instant it asked for.
39
+ function rangeBoundary(value: string, edge: 'start' | 'end'): Date {
40
+ if (!dayOnlySchema.safeParse(value).success) return new Date(value)
41
+ return new Date(`${value}T${edge === 'start' ? '00:00:00.000' : '23:59:59.999'}Z`)
42
+ }
43
+
44
+ const routeMetadata = {
45
+ GET: { requireAuth: true, requireFeatures: ['phone_calls.view'] },
46
+ }
47
+
48
+ export const metadata = routeMetadata
49
+
50
+ // No `recording_url`: provider recording links carry their own access token, and this route
51
+ // is gated on `phone_calls.view`. The key still ships as null so the response shape holds.
52
+ const listFields = [
53
+ 'id',
54
+ 'organization_id',
55
+ 'tenant_id',
56
+ 'provider_key',
57
+ 'integration_id',
58
+ 'external_call_id',
59
+ 'external_conversation_id',
60
+ 'direction',
61
+ 'status',
62
+ 'started_at',
63
+ 'answered_at',
64
+ 'ended_at',
65
+ 'duration_seconds',
66
+ 'ingest_status',
67
+ 'last_ingested_at',
68
+ 'created_at',
69
+ 'updated_at',
70
+ ]
71
+
72
+ const crud = makeCrudRoute({
73
+ metadata: routeMetadata,
74
+ orm: {
75
+ entity: PhoneCall,
76
+ idField: 'id',
77
+ orgField: 'organizationId',
78
+ tenantField: 'tenantId',
79
+ softDeleteField: 'deletedAt',
80
+ },
81
+ indexer: { entityType: E.phone_calls.phone_call },
82
+ list: {
83
+ schema: listSchema,
84
+ entityId: E.phone_calls.phone_call,
85
+ fields: listFields,
86
+ transformItem: (item: Record<string, unknown>) => ({ ...item, recording_url: null }),
87
+ sortFieldMap: {
88
+ externalCallId: 'external_call_id',
89
+ startedAt: 'started_at',
90
+ endedAt: 'ended_at',
91
+ durationSeconds: 'duration_seconds',
92
+ status: 'status',
93
+ direction: 'direction',
94
+ providerKey: 'provider_key',
95
+ ingestStatus: 'ingest_status',
96
+ createdAt: 'created_at',
97
+ updatedAt: 'updated_at',
98
+ },
99
+ buildFilters: (query): Where<Record<string, unknown>> => {
100
+ const filters: Record<string, unknown> = {}
101
+ if (query.id) filters.id = { $eq: query.id }
102
+ if (query.providerKey) filters.provider_key = { $eq: query.providerKey }
103
+ if (query.status) filters.status = { $eq: query.status }
104
+ if (query.direction) filters.direction = { $eq: query.direction }
105
+ if (query.q) {
106
+ const term = buildIlikeTerm(query.q)
107
+ filters.$or = [
108
+ { external_call_id: { $ilike: term } },
109
+ { external_conversation_id: { $ilike: term } },
110
+ { provider_key: { $ilike: term } },
111
+ ]
112
+ }
113
+ const startedRange: Record<string, Date> = {}
114
+ if (query.startedFrom) startedRange.$gte = rangeBoundary(query.startedFrom, 'start')
115
+ if (query.startedTo) startedRange.$lte = rangeBoundary(query.startedTo, 'end')
116
+ if (Object.keys(startedRange).length) filters.started_at = startedRange
117
+ return filters
118
+ },
119
+ },
120
+ })
121
+
122
+ export const GET = crud.GET
123
+
124
+ const phoneCallListItemSchema = z.object({
125
+ id: z.string().uuid(),
126
+ organization_id: z.string().uuid().nullable().optional(),
127
+ tenant_id: z.string().uuid().nullable().optional(),
128
+ provider_key: z.string().nullable().optional(),
129
+ integration_id: z.string().nullable().optional(),
130
+ external_call_id: z.string().nullable().optional(),
131
+ external_conversation_id: z.string().nullable().optional(),
132
+ direction: z.string().nullable().optional(),
133
+ status: z.string().nullable().optional(),
134
+ started_at: z.string().nullable().optional(),
135
+ answered_at: z.string().nullable().optional(),
136
+ ended_at: z.string().nullable().optional(),
137
+ duration_seconds: z.number().nullable().optional(),
138
+ recording_url: z.string().nullable().optional(),
139
+ ingest_status: z.string().nullable().optional(),
140
+ last_ingested_at: z.string().nullable().optional(),
141
+ created_at: z.string().nullable().optional(),
142
+ updated_at: z.string().nullable().optional(),
143
+ })
144
+
145
+ export const openApi = createPhoneCallsCrudOpenApi({
146
+ resourceName: 'PhoneCall',
147
+ pluralName: 'PhoneCalls',
148
+ querySchema: listSchema,
149
+ listResponseSchema: createPagedListResponseSchema(phoneCallListItemSchema),
150
+ })
@@ -0,0 +1,25 @@
1
+ import { type ZodTypeAny } from 'zod'
2
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
3
+ import {
4
+ createCrudOpenApiFactory,
5
+ createPagedListResponseSchema as createSharedPagedListResponseSchema,
6
+ defaultOkResponseSchema as sharedDefaultOkResponseSchema,
7
+ type CrudOpenApiOptions,
8
+ } from '@open-mercato/shared/lib/openapi/crud'
9
+
10
+ export const defaultOkResponseSchema = sharedDefaultOkResponseSchema
11
+
12
+ export function createPagedListResponseSchema(itemSchema: ZodTypeAny) {
13
+ return createSharedPagedListResponseSchema(itemSchema, { paginationMetaOptional: true })
14
+ }
15
+
16
+ const buildPhoneCallsCrudOpenApi = createCrudOpenApiFactory({
17
+ defaultTag: 'PhoneCalls',
18
+ defaultOkResponseSchema,
19
+ makeListDescription: ({ pluralLower }) =>
20
+ `Returns a paginated collection of ${pluralLower} scoped to the authenticated organization.`,
21
+ })
22
+
23
+ export function createPhoneCallsCrudOpenApi(options: CrudOpenApiOptions): OpenApiRouteDoc {
24
+ return buildPhoneCallsCrudOpenApi(options)
25
+ }
@@ -0,0 +1,12 @@
1
+ export const metadata = {
2
+ requireAuth: true,
3
+ requireFeatures: ['phone_calls.view'],
4
+ pageTitle: 'Phone Calls',
5
+ pageTitleKey: 'phone_calls.nav.calls',
6
+ pageGroup: 'Communication',
7
+ pageGroupKey: 'phone_calls.nav.group',
8
+ pagePriority: 10,
9
+ pageOrder: 100,
10
+ icon: 'phone-call',
11
+ breadcrumb: [{ label: 'Phone Calls', labelKey: 'phone_calls.nav.calls' }],
12
+ }