@open-mercato/core 0.6.5-develop.5382.1.f542de69af → 0.6.5

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 (237) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/bootstrap.js +46 -6
  3. package/dist/bootstrap.js.map +2 -2
  4. package/dist/generated/entities/organization/index.js +2 -0
  5. package/dist/generated/entities/organization/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +1 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/helpers/integration/crmFixtures.js +4 -0
  9. package/dist/helpers/integration/crmFixtures.js.map +2 -2
  10. package/dist/modules/attachments/api/route.js +2 -0
  11. package/dist/modules/attachments/api/route.js.map +2 -2
  12. package/dist/modules/attachments/lib/access.js +18 -0
  13. package/dist/modules/attachments/lib/access.js.map +2 -2
  14. package/dist/modules/audit_logs/data/entities.js +2 -1
  15. package/dist/modules/audit_logs/data/entities.js.map +2 -2
  16. package/dist/modules/audit_logs/migrations/Migration20260611104500.js +13 -0
  17. package/dist/modules/audit_logs/migrations/Migration20260611104500.js.map +7 -0
  18. package/dist/modules/audit_logs/services/accessLogService.js +10 -0
  19. package/dist/modules/audit_logs/services/accessLogService.js.map +2 -2
  20. package/dist/modules/auth/api/admin/nav.js +9 -0
  21. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  22. package/dist/modules/auth/api/login.js +4 -13
  23. package/dist/modules/auth/api/login.js.map +2 -2
  24. package/dist/modules/auth/data/entities.js +3 -1
  25. package/dist/modules/auth/data/entities.js.map +2 -2
  26. package/dist/modules/auth/lib/backendChrome.js +35 -2
  27. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  28. package/dist/modules/auth/lib/consentIntegrity.js +3 -3
  29. package/dist/modules/auth/lib/consentIntegrity.js.map +2 -2
  30. package/dist/modules/auth/migrations/Migration20260611103000.js +15 -0
  31. package/dist/modules/auth/migrations/Migration20260611103000.js.map +7 -0
  32. package/dist/modules/auth/services/authService.js +5 -3
  33. package/dist/modules/auth/services/authService.js.map +2 -2
  34. package/dist/modules/auth/services/rbacService.js +3 -2
  35. package/dist/modules/auth/services/rbacService.js.map +2 -2
  36. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +0 -3
  37. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +2 -2
  38. package/dist/modules/customers/api/deals/route.js +43 -2
  39. package/dist/modules/customers/api/deals/route.js.map +2 -2
  40. package/dist/modules/customers/api/deals/summary/route.js +402 -0
  41. package/dist/modules/customers/api/deals/summary/route.js.map +7 -0
  42. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js +16 -5
  43. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js.map +2 -2
  44. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js +22 -5
  45. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js.map +2 -2
  46. package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -2
  47. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  48. package/dist/modules/customers/backend/customers/deals/page.js +221 -56
  49. package/dist/modules/customers/backend/customers/deals/page.js.map +3 -3
  50. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
  51. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  52. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +18 -0
  53. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  54. package/dist/modules/customers/cli.js +15 -9
  55. package/dist/modules/customers/cli.js.map +2 -2
  56. package/dist/modules/customers/components/DealsKpiStrip.js +282 -0
  57. package/dist/modules/customers/components/DealsKpiStrip.js.map +7 -0
  58. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +0 -1
  59. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
  60. package/dist/modules/customers/components/detail/DealForm.js +100 -17
  61. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  62. package/dist/modules/customers/components/detail/PersonDetailTabs.js +11 -3
  63. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  64. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +1 -2
  65. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  66. package/dist/modules/customers/components/kpi/PipelineStageBar.js +63 -0
  67. package/dist/modules/customers/components/kpi/PipelineStageBar.js.map +7 -0
  68. package/dist/modules/customers/lib/dealsMetrics.js +82 -0
  69. package/dist/modules/customers/lib/dealsMetrics.js.map +7 -0
  70. package/dist/modules/directory/api/organization-branding/route.js +214 -0
  71. package/dist/modules/directory/api/organization-branding/route.js.map +7 -0
  72. package/dist/modules/directory/api/organizations/route.js +7 -0
  73. package/dist/modules/directory/api/organizations/route.js.map +3 -3
  74. package/dist/modules/directory/backend/directory/branding/page.js +214 -0
  75. package/dist/modules/directory/backend/directory/branding/page.js.map +7 -0
  76. package/dist/modules/directory/backend/directory/branding/page.meta.js +26 -0
  77. package/dist/modules/directory/backend/directory/branding/page.meta.js.map +7 -0
  78. package/dist/modules/directory/commands/organizations.js +8 -1
  79. package/dist/modules/directory/commands/organizations.js.map +2 -2
  80. package/dist/modules/directory/data/entities.js +3 -0
  81. package/dist/modules/directory/data/entities.js.map +2 -2
  82. package/dist/modules/directory/data/validators.js +9 -0
  83. package/dist/modules/directory/data/validators.js.map +2 -2
  84. package/dist/modules/directory/migrations/Migration20260607222259_directory.js +13 -0
  85. package/dist/modules/directory/migrations/Migration20260607222259_directory.js.map +7 -0
  86. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +2 -1
  87. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +2 -2
  88. package/dist/modules/directory/utils/organizationScope.js +59 -27
  89. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  90. package/dist/modules/entities/api/definitions.batch.js +2 -1
  91. package/dist/modules/entities/api/definitions.batch.js.map +2 -2
  92. package/dist/modules/entities/api/entities.js +7 -0
  93. package/dist/modules/entities/api/entities.js.map +2 -2
  94. package/dist/modules/entities/api/records.js +26 -15
  95. package/dist/modules/entities/api/records.js.map +2 -2
  96. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +14 -0
  97. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
  98. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js +14 -0
  99. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js.map +2 -2
  100. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +12 -0
  101. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  102. package/dist/modules/entities/components/useRecordsEntityGuard.js +30 -0
  103. package/dist/modules/entities/components/useRecordsEntityGuard.js.map +7 -0
  104. package/dist/modules/query_index/data/entities.js +2 -1
  105. package/dist/modules/query_index/data/entities.js.map +2 -2
  106. package/dist/modules/query_index/lib/engine.js +4 -2
  107. package/dist/modules/query_index/lib/engine.js.map +2 -2
  108. package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js +16 -0
  109. package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js.map +7 -0
  110. package/dist/modules/sales/commands/documents.js +7 -5
  111. package/dist/modules/sales/commands/documents.js.map +2 -2
  112. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -1
  113. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  114. package/dist/modules/sales/components/documents/salesDocumentsColumns.js +10 -0
  115. package/dist/modules/sales/components/documents/salesDocumentsColumns.js.map +7 -0
  116. package/dist/modules/staff/api/team-members.js +9 -2
  117. package/dist/modules/staff/api/team-members.js.map +2 -2
  118. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +24 -1
  119. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
  120. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +11 -6
  121. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  122. package/dist/modules/staff/commands/team-members.js +1 -1
  123. package/dist/modules/staff/commands/team-members.js.map +2 -2
  124. package/dist/modules/staff/components/TeamMemberForm.js +1 -1
  125. package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
  126. package/dist/modules/staff/lib/scheduleSwitch.js +23 -0
  127. package/dist/modules/staff/lib/scheduleSwitch.js.map +7 -0
  128. package/dist/modules/workflows/backend/definitions/create/page.js +1 -2
  129. package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
  130. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +1 -2
  131. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  132. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -2
  133. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
  134. package/dist/modules/workflows/components/NodeEditDialog.js +4 -13
  135. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  136. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js +4 -13
  137. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +2 -2
  138. package/dist/modules/workflows/components/WorkflowGraphImpl.js +1 -4
  139. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  140. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +2 -5
  141. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +2 -2
  142. package/generated/entities/organization/index.ts +1 -0
  143. package/generated/entity-fields-registry.ts +1 -0
  144. package/package.json +11 -12
  145. package/src/bootstrap.ts +65 -7
  146. package/src/helpers/integration/crmFixtures.ts +21 -1
  147. package/src/modules/attachments/AGENTS.md +79 -0
  148. package/src/modules/attachments/api/route.ts +2 -0
  149. package/src/modules/attachments/lib/access.ts +36 -0
  150. package/src/modules/audit_logs/data/entities.ts +1 -0
  151. package/src/modules/audit_logs/migrations/.snapshot-open-mercato.json +10 -0
  152. package/src/modules/audit_logs/migrations/Migration20260611104500.ts +13 -0
  153. package/src/modules/audit_logs/services/accessLogService.ts +15 -0
  154. package/src/modules/auth/api/admin/nav.ts +9 -0
  155. package/src/modules/auth/api/login.ts +13 -13
  156. package/src/modules/auth/data/entities.ts +2 -0
  157. package/src/modules/auth/i18n/de.json +0 -1
  158. package/src/modules/auth/i18n/en.json +0 -1
  159. package/src/modules/auth/i18n/es.json +0 -1
  160. package/src/modules/auth/i18n/pl.json +0 -1
  161. package/src/modules/auth/lib/backendChrome.tsx +37 -1
  162. package/src/modules/auth/lib/consentIntegrity.ts +6 -3
  163. package/src/modules/auth/migrations/.snapshot-open-mercato.json +20 -0
  164. package/src/modules/auth/migrations/Migration20260611103000.ts +21 -0
  165. package/src/modules/auth/services/authService.ts +24 -4
  166. package/src/modules/auth/services/rbacService.ts +11 -2
  167. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +0 -3
  168. package/src/modules/customers/api/deals/route.ts +51 -2
  169. package/src/modules/customers/api/deals/summary/route.ts +496 -0
  170. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.ts +28 -6
  171. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealData.ts +33 -6
  172. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +17 -2
  173. package/src/modules/customers/backend/customers/deals/page.tsx +254 -66
  174. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +1 -2
  175. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -0
  176. package/src/modules/customers/cli.ts +15 -15
  177. package/src/modules/customers/components/DealsKpiStrip.tsx +389 -0
  178. package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +0 -1
  179. package/src/modules/customers/components/detail/DealForm.tsx +121 -19
  180. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +12 -2
  181. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +1 -2
  182. package/src/modules/customers/components/kpi/PipelineStageBar.tsx +77 -0
  183. package/src/modules/customers/i18n/de.json +43 -0
  184. package/src/modules/customers/i18n/en.json +43 -0
  185. package/src/modules/customers/i18n/es.json +43 -0
  186. package/src/modules/customers/i18n/pl.json +43 -0
  187. package/src/modules/customers/lib/dealsMetrics.ts +159 -0
  188. package/src/modules/directory/api/organization-branding/route.ts +238 -0
  189. package/src/modules/directory/api/organizations/route.ts +7 -0
  190. package/src/modules/directory/backend/directory/branding/page.meta.ts +24 -0
  191. package/src/modules/directory/backend/directory/branding/page.tsx +248 -0
  192. package/src/modules/directory/commands/organizations.ts +9 -1
  193. package/src/modules/directory/data/entities.ts +3 -0
  194. package/src/modules/directory/data/validators.ts +12 -0
  195. package/src/modules/directory/i18n/de.json +21 -0
  196. package/src/modules/directory/i18n/en.json +21 -0
  197. package/src/modules/directory/i18n/es.json +21 -0
  198. package/src/modules/directory/i18n/pl.json +21 -0
  199. package/src/modules/directory/migrations/.snapshot-open-mercato.json +40 -0
  200. package/src/modules/directory/migrations/Migration20260607222259_directory.ts +13 -0
  201. package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +3 -1
  202. package/src/modules/directory/utils/organizationScope.ts +85 -30
  203. package/src/modules/entities/api/definitions.batch.ts +11 -7
  204. package/src/modules/entities/api/entities.ts +11 -0
  205. package/src/modules/entities/api/records.ts +46 -25
  206. package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +15 -0
  207. package/src/modules/entities/backend/entities/user/[entityId]/records/create/page.tsx +15 -0
  208. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +23 -0
  209. package/src/modules/entities/components/useRecordsEntityGuard.ts +41 -0
  210. package/src/modules/entities/i18n/de.json +1 -0
  211. package/src/modules/entities/i18n/en.json +1 -0
  212. package/src/modules/entities/i18n/es.json +1 -0
  213. package/src/modules/entities/i18n/pl.json +1 -0
  214. package/src/modules/query_index/data/entities.ts +1 -0
  215. package/src/modules/query_index/lib/engine.ts +11 -5
  216. package/src/modules/query_index/migrations/.snapshot-open-mercato.json +11 -0
  217. package/src/modules/query_index/migrations/Migration20260611103000_query_index.ts +29 -0
  218. package/src/modules/sales/commands/documents.ts +7 -5
  219. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -1
  220. package/src/modules/sales/components/documents/salesDocumentsColumns.ts +6 -0
  221. package/src/modules/staff/api/team-members.ts +9 -2
  222. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +31 -1
  223. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +18 -8
  224. package/src/modules/staff/commands/team-members.ts +5 -2
  225. package/src/modules/staff/components/TeamMemberForm.tsx +4 -1
  226. package/src/modules/staff/i18n/de.json +1 -0
  227. package/src/modules/staff/i18n/en.json +1 -0
  228. package/src/modules/staff/i18n/es.json +1 -0
  229. package/src/modules/staff/i18n/pl.json +1 -0
  230. package/src/modules/staff/lib/scheduleSwitch.ts +46 -0
  231. package/src/modules/workflows/backend/definitions/create/page.tsx +1 -2
  232. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +1 -2
  233. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -2
  234. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -4
  235. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +4 -7
  236. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +1 -2
  237. package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +2 -3
@@ -0,0 +1,248 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { useQuery, useQueryClient } from '@tanstack/react-query'
5
+ import { ImagePlus, Loader2, RotateCcw, Save } from 'lucide-react'
6
+ import { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'
7
+ import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
8
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
9
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
10
+ import { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
11
+ import { Button } from '@open-mercato/ui/primitives/button'
12
+ import { Input } from '@open-mercato/ui/primitives/input'
13
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
14
+
15
+ type BrandingPayload = {
16
+ organizationId: string
17
+ organizationName: string
18
+ tenantId: string
19
+ logoUrl: string | null
20
+ }
21
+
22
+ type UploadPayload = {
23
+ ok: true
24
+ item: {
25
+ id: string
26
+ url: string
27
+ thumbnailUrl?: string
28
+ }
29
+ }
30
+
31
+ const BRANDING_API = '/api/directory/organization-branding'
32
+ const BRANDING_ENTITY_ID = 'directory.organization'
33
+
34
+ export default function OrganizationBrandingPage() {
35
+ const t = useT()
36
+ const queryClient = useQueryClient()
37
+ const [logoUrl, setLogoUrl] = React.useState('')
38
+ const [selectedFile, setSelectedFile] = React.useState<File | null>(null)
39
+ const [filePreviewUrl, setFilePreviewUrl] = React.useState<string | null>(null)
40
+ const [saving, setSaving] = React.useState(false)
41
+ const fileInputRef = React.useRef<HTMLInputElement | null>(null)
42
+ const { runMutation } = useGuardedMutation({
43
+ contextId: 'directory.organization-branding',
44
+ blockedMessage: t('directory.branding.errors.blocked', 'Branding save was blocked.'),
45
+ })
46
+
47
+ const { data, isLoading, error } = useQuery<BrandingPayload>({
48
+ queryKey: ['directory-organization-branding'],
49
+ queryFn: () => readApiResultOrThrow<BrandingPayload>(
50
+ BRANDING_API,
51
+ undefined,
52
+ { errorMessage: t('directory.branding.errors.load', 'Failed to load organization branding') },
53
+ ),
54
+ })
55
+
56
+ React.useEffect(() => {
57
+ setLogoUrl(data?.logoUrl ?? '')
58
+ setSelectedFile(null)
59
+ }, [data?.logoUrl])
60
+
61
+ React.useEffect(() => {
62
+ if (!selectedFile || typeof URL === 'undefined') {
63
+ setFilePreviewUrl(null)
64
+ return
65
+ }
66
+ const nextPreviewUrl = URL.createObjectURL(selectedFile)
67
+ setFilePreviewUrl(nextPreviewUrl)
68
+ return () => URL.revokeObjectURL(nextPreviewUrl)
69
+ }, [selectedFile])
70
+
71
+ const currentPreviewUrl = filePreviewUrl ?? logoUrl
72
+
73
+ const uploadLogo = React.useCallback(async (organizationId: string): Promise<string | null> => {
74
+ if (!selectedFile) return null
75
+ const form = new FormData()
76
+ form.set('entityId', BRANDING_ENTITY_ID)
77
+ form.set('recordId', organizationId)
78
+ form.set('file', selectedFile)
79
+ form.set('tags', JSON.stringify(['organization-logo']))
80
+
81
+ const upload = await readApiResultOrThrow<UploadPayload>(
82
+ '/api/attachments',
83
+ {
84
+ method: 'POST',
85
+ body: form,
86
+ },
87
+ { errorMessage: t('directory.branding.errors.upload', 'Failed to upload logo') },
88
+ )
89
+ return upload?.item.thumbnailUrl ?? upload?.item.url ?? null
90
+ }, [selectedFile, t])
91
+
92
+ const saveBranding = React.useCallback(async (nextLogoUrl?: string, options?: { skipUpload?: boolean }) => {
93
+ if (!data) return
94
+ const shouldUpload = Boolean(selectedFile && !options?.skipUpload)
95
+ setSaving(true)
96
+ try {
97
+ await runMutation({
98
+ operation: async () => {
99
+ const uploadedLogoUrl = shouldUpload ? await uploadLogo(data.organizationId) : null
100
+ const resolvedLogoUrl = uploadedLogoUrl ?? nextLogoUrl ?? logoUrl.trim()
101
+ // optimistic-lock-exempt: selected organization branding uses a scoped command endpoint without an exposed updatedAt token.
102
+ const response = await apiCallOrThrow<BrandingPayload>(
103
+ BRANDING_API,
104
+ {
105
+ method: 'PUT',
106
+ headers: { 'content-type': 'application/json' },
107
+ body: JSON.stringify({ logoUrl: resolvedLogoUrl || null }),
108
+ },
109
+ { errorMessage: t('directory.branding.errors.save', 'Failed to update organization branding') },
110
+ )
111
+ return response.result
112
+ },
113
+ context: {
114
+ entityId: BRANDING_ENTITY_ID,
115
+ recordId: data.organizationId,
116
+ operation: 'update-branding',
117
+ },
118
+ mutationPayload: {
119
+ organizationId: data.organizationId,
120
+ logoUrl: (nextLogoUrl ?? logoUrl.trim()) || null,
121
+ hasUpload: shouldUpload,
122
+ },
123
+ })
124
+ await queryClient.invalidateQueries({ queryKey: ['directory-organization-branding'] })
125
+ window.dispatchEvent(new Event('om:refresh-sidebar'))
126
+ setSelectedFile(null)
127
+ if (fileInputRef.current) fileInputRef.current.value = ''
128
+ flash(t('directory.branding.flash.saved', 'Organization branding updated'), 'success')
129
+ } catch (err: unknown) {
130
+ const fallback = t('directory.branding.errors.save', 'Failed to update organization branding')
131
+ const message = err instanceof Error ? err.message : fallback
132
+ flash(message, 'error')
133
+ } finally {
134
+ setSaving(false)
135
+ }
136
+ }, [data, logoUrl, queryClient, runMutation, selectedFile, t, uploadLogo])
137
+
138
+ const handleSubmit = React.useCallback((event: React.FormEvent<HTMLFormElement>) => {
139
+ event.preventDefault()
140
+ void saveBranding()
141
+ }, [saveBranding])
142
+
143
+ if (isLoading) {
144
+ return <LoadingMessage label={t('directory.branding.loading', 'Loading organization branding...')} />
145
+ }
146
+
147
+ if (error || !data) {
148
+ return (
149
+ <ErrorMessage
150
+ label={t('directory.branding.errors.load', 'Failed to load organization branding')}
151
+ description={error instanceof Error ? error.message : undefined}
152
+ />
153
+ )
154
+ }
155
+
156
+ return (
157
+ <Page>
158
+ <PageHeader
159
+ title={t('directory.branding.title', 'Organization branding')}
160
+ description={t(
161
+ 'directory.branding.description',
162
+ 'Set the logo used in the backend sidebar for the currently selected organization.',
163
+ )}
164
+ />
165
+ <PageBody>
166
+ <form className="space-y-5" onSubmit={handleSubmit}>
167
+ <div className="grid gap-5 lg:grid-cols-[260px_1fr]">
168
+ <div className="space-y-3">
169
+ <div className="flex aspect-square w-full max-w-[220px] items-center justify-center overflow-hidden rounded-lg border bg-muted/30">
170
+ {currentPreviewUrl ? (
171
+ <img
172
+ src={currentPreviewUrl}
173
+ alt={t('directory.branding.previewAlt', '{{name}} logo preview', { name: data.organizationName })}
174
+ className="h-full w-full object-contain"
175
+ />
176
+ ) : (
177
+ <ImagePlus className="size-10 text-muted-foreground" aria-hidden />
178
+ )}
179
+ </div>
180
+ <p className="text-sm font-medium text-foreground">{data.organizationName}</p>
181
+ <p className="text-xs text-muted-foreground">
182
+ {t('directory.branding.currentScope', 'Current organization')}
183
+ </p>
184
+ </div>
185
+
186
+ <div className="space-y-4">
187
+ <div className="space-y-2">
188
+ <label htmlFor="organization-logo-file" className="text-sm font-medium">
189
+ {t('directory.branding.file.label', 'Upload logo')}
190
+ </label>
191
+ <Input
192
+ ref={fileInputRef}
193
+ id="organization-logo-file"
194
+ type="file"
195
+ accept="image/png,image/jpeg,image/webp,image/svg+xml"
196
+ onChange={(event) => {
197
+ const file = event.currentTarget.files?.[0]
198
+ if (!file) return
199
+ setSelectedFile(file)
200
+ }}
201
+ />
202
+ <p className="text-xs text-muted-foreground">
203
+ {t('directory.branding.file.hint', 'PNG, JPG, WebP, or SVG works best. Uploaded files are stored as organization attachments.')}
204
+ </p>
205
+ </div>
206
+
207
+ <div className="space-y-2">
208
+ <label htmlFor="organization-logo-url" className="text-sm font-medium">
209
+ {t('directory.branding.url.label', 'Logo URL')}
210
+ </label>
211
+ <Input
212
+ id="organization-logo-url"
213
+ value={logoUrl}
214
+ onChange={(event) => setLogoUrl(event.currentTarget.value)}
215
+ placeholder={t('directory.branding.url.placeholder', 'https://example.com/logo.svg')}
216
+ />
217
+ <p className="text-xs text-muted-foreground">
218
+ {t('directory.branding.url.hint', 'Use an external image URL or leave empty to fall back to the default Open Mercato logo.')}
219
+ </p>
220
+ </div>
221
+
222
+ <div className="flex flex-wrap items-center gap-2">
223
+ <Button type="submit" disabled={saving}>
224
+ {saving ? <Loader2 className="mr-2 size-4 animate-spin" aria-hidden /> : <Save className="mr-2 size-4" aria-hidden />}
225
+ {t('directory.branding.actions.save', 'Save branding')}
226
+ </Button>
227
+ <Button
228
+ type="button"
229
+ variant="outline"
230
+ disabled={saving}
231
+ onClick={() => {
232
+ setSelectedFile(null)
233
+ if (fileInputRef.current) fileInputRef.current.value = ''
234
+ setLogoUrl('')
235
+ void saveBranding('', { skipUpload: true })
236
+ }}
237
+ >
238
+ <RotateCcw className="mr-2 size-4" aria-hidden />
239
+ {t('directory.branding.actions.reset', 'Use default logo')}
240
+ </Button>
241
+ </div>
242
+ </div>
243
+ </div>
244
+ </form>
245
+ </PageBody>
246
+ </Page>
247
+ )
248
+ }
@@ -84,6 +84,7 @@ type OrganizationUndoSnapshot = {
84
84
  tenantId: string | null
85
85
  name: string
86
86
  slug?: string | null
87
+ logoUrl?: string | null
87
88
  isActive: boolean
88
89
  parentId: string | null
89
90
  childParents: ChildParentSnapshot[]
@@ -120,6 +121,7 @@ function serializeOrganization(entity: Organization, custom?: Record<string, unk
120
121
  tenantId: resolveTenantIdFromEntity(entity),
121
122
  name: entity.name,
122
123
  slug: entity.slug ?? null,
124
+ logoUrl: entity.logoUrl ?? null,
123
125
  isActive: !!entity.isActive,
124
126
  parentId: entity.parentId ?? null,
125
127
  ancestorIds: Array.isArray(entity.ancestorIds) ? [...entity.ancestorIds] : [],
@@ -144,6 +146,7 @@ function captureOrganizationSnapshots(
144
146
  tenantId,
145
147
  name: entity.name,
146
148
  slug: entity.slug ?? null,
149
+ logoUrl: entity.logoUrl ?? null,
147
150
  isActive: !!entity.isActive,
148
151
  parentId: entity.parentId ?? null,
149
152
  childParents: (childParents ?? []).map((entry) => ({
@@ -303,6 +306,7 @@ const createOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
303
306
  tenant: tenantRef,
304
307
  name: parsed.name,
305
308
  slug,
309
+ logoUrl: parsed.logoUrl ?? null,
306
310
  isActive: parsed.isActive ?? true,
307
311
  parentId,
308
312
  },
@@ -429,6 +433,7 @@ const createOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
429
433
  existing.deletedAt = null
430
434
  existing.name = after.name
431
435
  if (after.slug !== undefined) existing.slug = after.slug ?? null
436
+ if (after.logoUrl !== undefined) existing.logoUrl = after.logoUrl ?? null
432
437
  existing.isActive = after.isActive
433
438
  existing.parentId = after.parentId
434
439
  await em.flush()
@@ -440,6 +445,7 @@ const createOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
440
445
  id: after.id,
441
446
  name: after.name,
442
447
  slug: after.slug ?? null,
448
+ logoUrl: after.logoUrl ?? null,
443
449
  tenant: em.getReference(Tenant, tenantId),
444
450
  isActive: after.isActive,
445
451
  parentId: after.parentId,
@@ -564,6 +570,7 @@ const updateOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
564
570
  apply: (entity) => {
565
571
  if (parsed.name !== undefined) entity.name = parsed.name
566
572
  if (resolvedSlug !== undefined) entity.slug = resolvedSlug
573
+ if (parsed.logoUrl !== undefined) entity.logoUrl = parsed.logoUrl ?? null
567
574
  if (parsed.isActive !== undefined) entity.isActive = parsed.isActive
568
575
  entity.parentId = parentId
569
576
  },
@@ -630,7 +637,7 @@ const updateOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
630
637
  organizationId: String(result.id),
631
638
  })
632
639
  const after = serializeOrganization(result, custom)
633
- const changes = buildChanges(beforeRecord, after as Record<string, unknown>, ['name', 'slug', 'isActive', 'parentId'])
640
+ const changes = buildChanges(beforeRecord, after as Record<string, unknown>, ['name', 'slug', 'logoUrl', 'isActive', 'parentId'])
634
641
  const customDiff = diffCustomFieldChanges(beforeRecord?.custom, custom)
635
642
  for (const [key, diff] of Object.entries(customDiff)) {
636
643
  changes[`cf_${key}`] = diff
@@ -667,6 +674,7 @@ const updateOrganizationCommand: CommandHandler<Record<string, unknown>, Organiz
667
674
  apply: (entity) => {
668
675
  entity.name = before.name
669
676
  if (before.slug !== undefined) entity.slug = before.slug
677
+ if (before.logoUrl !== undefined) entity.logoUrl = before.logoUrl ?? null
670
678
  entity.isActive = before.isActive
671
679
  entity.parentId = before.parentId
672
680
  },
@@ -40,6 +40,9 @@ export class Organization {
40
40
  @Property({ type: 'text', nullable: true })
41
41
  slug?: string | null
42
42
 
43
+ @Property({ name: 'logo_url', type: 'text', nullable: true })
44
+ logoUrl?: string | null
45
+
43
46
  @Property({ name: 'is_active', type: 'boolean', default: true })
44
47
  isActive: boolean = true
45
48
 
@@ -12,11 +12,22 @@ export const tenantUpdateSchema = z.object({
12
12
  })
13
13
 
14
14
  const slugField = z.string().trim().toLowerCase().regex(/^[a-z0-9\-_]+$/).max(150).optional().nullable()
15
+ const logoUrlField = z
16
+ .union([
17
+ z.string().trim().url().max(2048).refine(
18
+ (value) => value.startsWith('https://') || value.startsWith('http://'),
19
+ { message: 'Logo URL must use http or https.' },
20
+ ),
21
+ z.string().trim().regex(/^\/api\/attachments\/(?:image|file)\/[A-Za-z0-9%_.~/?=&-]+$/).max(2048),
22
+ ])
23
+ .optional()
24
+ .nullable()
15
25
 
16
26
  export const organizationCreateSchema = z.object({
17
27
  tenantId: z.string().uuid().optional(),
18
28
  name: z.string().min(1).max(200),
19
29
  slug: slugField,
30
+ logoUrl: logoUrlField,
20
31
  isActive: z.boolean().optional(),
21
32
  parentId: z.string().uuid().nullable().optional(),
22
33
  childIds: z.array(z.string().uuid()).optional(),
@@ -27,6 +38,7 @@ export const organizationUpdateSchema = z.object({
27
38
  tenantId: z.string().uuid().optional(),
28
39
  name: z.string().min(1).max(200).optional(),
29
40
  slug: slugField,
41
+ logoUrl: logoUrlField,
30
42
  isActive: z.boolean().optional(),
31
43
  parentId: z.string().uuid().nullable().optional(),
32
44
  childIds: z.array(z.string().uuid()).optional(),
@@ -5,6 +5,27 @@
5
5
  "directory.audit.tenants.create": "Mandanten anlegen",
6
6
  "directory.audit.tenants.delete": "Mandanten löschen",
7
7
  "directory.audit.tenants.update": "Mandanten aktualisieren",
8
+ "directory.branding.actions.reset": "Standardlogo verwenden",
9
+ "directory.branding.actions.save": "Branding speichern",
10
+ "directory.branding.currentScope": "Aktuelle Organisation",
11
+ "directory.branding.description": "Legen Sie das Logo fest, das in der Backend-Seitenleiste für die aktuell ausgewählte Organisation verwendet wird.",
12
+ "directory.branding.errors.blocked": "Das Speichern des Brandings wurde blockiert.",
13
+ "directory.branding.errors.invalidLogoUrl": "Geben Sie eine gültige Bild-URL ein.",
14
+ "directory.branding.errors.load": "Organisationsbranding konnte nicht geladen werden",
15
+ "directory.branding.errors.notFound": "Organisation nicht gefunden",
16
+ "directory.branding.errors.organizationRequired": "Wählen Sie eine einzelne Organisation aus, bevor Sie das Seitenleisten-Branding ändern.",
17
+ "directory.branding.errors.save": "Organisationsbranding konnte nicht aktualisiert werden",
18
+ "directory.branding.errors.upload": "Logo konnte nicht hochgeladen werden",
19
+ "directory.branding.file.hint": "PNG, JPG, WebP oder SVG funktioniert am besten. Hochgeladene Dateien werden als Organisationsanhänge gespeichert.",
20
+ "directory.branding.file.label": "Logo hochladen",
21
+ "directory.branding.flash.saved": "Organisationsbranding aktualisiert",
22
+ "directory.branding.loading": "Organisationsbranding wird geladen...",
23
+ "directory.branding.nav": "Organisationsbranding",
24
+ "directory.branding.previewAlt": "Logovorschau für {{name}}",
25
+ "directory.branding.title": "Organisationsbranding",
26
+ "directory.branding.url.hint": "Verwenden Sie eine externe Bild-URL oder lassen Sie das Feld leer, um auf das Standardlogo von Open Mercato zurückzufallen.",
27
+ "directory.branding.url.label": "Logo-URL",
28
+ "directory.branding.url.placeholder": "https://example.com/logo.svg",
8
29
  "directory.nav.group": "Verzeichnis",
9
30
  "directory.nav.organizations": "Organisationen",
10
31
  "directory.nav.organizations.create": "Organisation erstellen",
@@ -5,6 +5,27 @@
5
5
  "directory.audit.tenants.create": "Create tenant",
6
6
  "directory.audit.tenants.delete": "Delete tenant",
7
7
  "directory.audit.tenants.update": "Update tenant",
8
+ "directory.branding.actions.reset": "Use default logo",
9
+ "directory.branding.actions.save": "Save branding",
10
+ "directory.branding.currentScope": "Current organization",
11
+ "directory.branding.description": "Set the logo used in the backend sidebar for the currently selected organization.",
12
+ "directory.branding.errors.blocked": "Branding save was blocked.",
13
+ "directory.branding.errors.invalidLogoUrl": "Enter a valid image URL.",
14
+ "directory.branding.errors.load": "Failed to load organization branding",
15
+ "directory.branding.errors.notFound": "Organization not found",
16
+ "directory.branding.errors.organizationRequired": "Select a single organization before changing sidebar branding.",
17
+ "directory.branding.errors.save": "Failed to update organization branding",
18
+ "directory.branding.errors.upload": "Failed to upload logo",
19
+ "directory.branding.file.hint": "PNG, JPG, WebP, or SVG works best. Uploaded files are stored as organization attachments.",
20
+ "directory.branding.file.label": "Upload logo",
21
+ "directory.branding.flash.saved": "Organization branding updated",
22
+ "directory.branding.loading": "Loading organization branding...",
23
+ "directory.branding.nav": "Organization branding",
24
+ "directory.branding.previewAlt": "{{name}} logo preview",
25
+ "directory.branding.title": "Organization branding",
26
+ "directory.branding.url.hint": "Use an external image URL or leave empty to fall back to the default Open Mercato logo.",
27
+ "directory.branding.url.label": "Logo URL",
28
+ "directory.branding.url.placeholder": "https://example.com/logo.svg",
8
29
  "directory.nav.group": "Directory",
9
30
  "directory.nav.organizations": "Organizations",
10
31
  "directory.nav.organizations.create": "Create Organization",
@@ -5,6 +5,27 @@
5
5
  "directory.audit.tenants.create": "Crear inquilino",
6
6
  "directory.audit.tenants.delete": "Eliminar inquilino",
7
7
  "directory.audit.tenants.update": "Actualizar inquilino",
8
+ "directory.branding.actions.reset": "Usar logo predeterminado",
9
+ "directory.branding.actions.save": "Guardar marca",
10
+ "directory.branding.currentScope": "Organización actual",
11
+ "directory.branding.description": "Define el logo usado en la barra lateral del backend para la organización seleccionada.",
12
+ "directory.branding.errors.blocked": "Se bloqueó el guardado de la marca.",
13
+ "directory.branding.errors.invalidLogoUrl": "Introduce una URL de imagen válida.",
14
+ "directory.branding.errors.load": "No se pudo cargar la marca de la organización",
15
+ "directory.branding.errors.notFound": "Organización no encontrada",
16
+ "directory.branding.errors.organizationRequired": "Selecciona una sola organización antes de cambiar la marca de la barra lateral.",
17
+ "directory.branding.errors.save": "No se pudo actualizar la marca de la organización",
18
+ "directory.branding.errors.upload": "No se pudo subir el logo",
19
+ "directory.branding.file.hint": "PNG, JPG, WebP o SVG funcionan mejor. Los archivos subidos se guardan como adjuntos de la organización.",
20
+ "directory.branding.file.label": "Subir logo",
21
+ "directory.branding.flash.saved": "Marca de la organización actualizada",
22
+ "directory.branding.loading": "Cargando marca de la organización...",
23
+ "directory.branding.nav": "Marca de la organización",
24
+ "directory.branding.previewAlt": "Vista previa del logo de {{name}}",
25
+ "directory.branding.title": "Marca de la organización",
26
+ "directory.branding.url.hint": "Usa una URL de imagen externa o deja el campo vacío para volver al logo predeterminado de Open Mercato.",
27
+ "directory.branding.url.label": "URL del logo",
28
+ "directory.branding.url.placeholder": "https://example.com/logo.svg",
8
29
  "directory.nav.group": "Directorio",
9
30
  "directory.nav.organizations": "Organizaciones",
10
31
  "directory.nav.organizations.create": "Crear organización",
@@ -5,6 +5,27 @@
5
5
  "directory.audit.tenants.create": "Utwórz najemcę",
6
6
  "directory.audit.tenants.delete": "Usuń najemcę",
7
7
  "directory.audit.tenants.update": "Aktualizuj najemcę",
8
+ "directory.branding.actions.reset": "Użyj domyślnego logo",
9
+ "directory.branding.actions.save": "Zapisz branding",
10
+ "directory.branding.currentScope": "Aktualna organizacja",
11
+ "directory.branding.description": "Ustaw logo używane w sidebarze backendu dla aktualnie wybranej organizacji.",
12
+ "directory.branding.errors.blocked": "Zapis brandingu został zablokowany.",
13
+ "directory.branding.errors.invalidLogoUrl": "Podaj poprawny adres URL obrazka.",
14
+ "directory.branding.errors.load": "Nie udało się wczytać brandingu organizacji",
15
+ "directory.branding.errors.notFound": "Nie znaleziono organizacji",
16
+ "directory.branding.errors.organizationRequired": "Wybierz pojedynczą organizację przed zmianą brandingu sidebara.",
17
+ "directory.branding.errors.save": "Nie udało się zaktualizować brandingu organizacji",
18
+ "directory.branding.errors.upload": "Nie udało się przesłać logo",
19
+ "directory.branding.file.hint": "Najlepiej sprawdzi się PNG, JPG, WebP albo SVG. Przesłane pliki są zapisywane jako załączniki organizacji.",
20
+ "directory.branding.file.label": "Prześlij logo",
21
+ "directory.branding.flash.saved": "Branding organizacji zaktualizowany",
22
+ "directory.branding.loading": "Ładowanie brandingu organizacji...",
23
+ "directory.branding.nav": "Branding organizacji",
24
+ "directory.branding.previewAlt": "Podgląd logo {{name}}",
25
+ "directory.branding.title": "Branding organizacji",
26
+ "directory.branding.url.hint": "Użyj zewnętrznego adresu obrazka albo zostaw puste, aby wrócić do domyślnego logo Open Mercato.",
27
+ "directory.branding.url.label": "Adres URL logo",
28
+ "directory.branding.url.placeholder": "https://example.com/logo.svg",
8
29
  "directory.nav.group": "Katalog",
9
30
  "directory.nav.organizations": "Organizacje",
10
31
  "directory.nav.organizations.create": "Utwórz organizację",
@@ -21,6 +21,7 @@
21
21
  "scale": null,
22
22
  "default": "'[]'",
23
23
  "comment": null,
24
+ "collation": null,
24
25
  "enumItems": [],
25
26
  "mappedType": "json"
26
27
  },
@@ -37,6 +38,7 @@
37
38
  "scale": null,
38
39
  "default": "'[]'",
39
40
  "comment": null,
41
+ "collation": null,
40
42
  "enumItems": [],
41
43
  "mappedType": "json"
42
44
  },
@@ -53,6 +55,7 @@
53
55
  "scale": null,
54
56
  "default": null,
55
57
  "comment": null,
58
+ "collation": null,
56
59
  "enumItems": [],
57
60
  "mappedType": "datetime"
58
61
  },
@@ -69,6 +72,7 @@
69
72
  "scale": null,
70
73
  "default": null,
71
74
  "comment": null,
75
+ "collation": null,
72
76
  "enumItems": [],
73
77
  "mappedType": "datetime"
74
78
  },
@@ -85,6 +89,7 @@
85
89
  "scale": null,
86
90
  "default": "0",
87
91
  "comment": null,
92
+ "collation": null,
88
93
  "enumItems": [],
89
94
  "mappedType": "integer"
90
95
  },
@@ -101,6 +106,7 @@
101
106
  "scale": null,
102
107
  "default": "'[]'",
103
108
  "comment": null,
109
+ "collation": null,
104
110
  "enumItems": [],
105
111
  "mappedType": "json"
106
112
  },
@@ -117,6 +123,7 @@
117
123
  "scale": null,
118
124
  "default": "gen_random_uuid()",
119
125
  "comment": null,
126
+ "collation": null,
120
127
  "enumItems": [],
121
128
  "mappedType": "uuid"
122
129
  },
@@ -133,9 +140,27 @@
133
140
  "scale": null,
134
141
  "default": "true",
135
142
  "comment": null,
143
+ "collation": null,
136
144
  "enumItems": [],
137
145
  "mappedType": "boolean"
138
146
  },
147
+ "logo_url": {
148
+ "name": "logo_url",
149
+ "type": "text",
150
+ "unsigned": false,
151
+ "autoincrement": false,
152
+ "primary": false,
153
+ "nullable": true,
154
+ "unique": false,
155
+ "length": null,
156
+ "precision": null,
157
+ "scale": null,
158
+ "default": null,
159
+ "comment": null,
160
+ "collation": null,
161
+ "enumItems": [],
162
+ "mappedType": "text"
163
+ },
139
164
  "name": {
140
165
  "name": "name",
141
166
  "type": "text",
@@ -149,6 +174,7 @@
149
174
  "scale": null,
150
175
  "default": null,
151
176
  "comment": null,
177
+ "collation": null,
152
178
  "enumItems": [],
153
179
  "mappedType": "text"
154
180
  },
@@ -165,6 +191,7 @@
165
191
  "scale": null,
166
192
  "default": null,
167
193
  "comment": null,
194
+ "collation": null,
168
195
  "enumItems": [],
169
196
  "mappedType": "uuid"
170
197
  },
@@ -181,6 +208,7 @@
181
208
  "scale": null,
182
209
  "default": null,
183
210
  "comment": null,
211
+ "collation": null,
184
212
  "enumItems": [],
185
213
  "mappedType": "uuid"
186
214
  },
@@ -197,6 +225,7 @@
197
225
  "scale": null,
198
226
  "default": null,
199
227
  "comment": null,
228
+ "collation": null,
200
229
  "enumItems": [],
201
230
  "mappedType": "text"
202
231
  },
@@ -213,6 +242,7 @@
213
242
  "scale": null,
214
243
  "default": null,
215
244
  "comment": null,
245
+ "collation": null,
216
246
  "enumItems": [],
217
247
  "mappedType": "uuid"
218
248
  },
@@ -229,6 +259,7 @@
229
259
  "scale": null,
230
260
  "default": null,
231
261
  "comment": null,
262
+ "collation": null,
232
263
  "enumItems": [],
233
264
  "mappedType": "text"
234
265
  },
@@ -245,6 +276,7 @@
245
276
  "scale": null,
246
277
  "default": null,
247
278
  "comment": null,
279
+ "collation": null,
248
280
  "enumItems": [],
249
281
  "mappedType": "datetime"
250
282
  }
@@ -273,6 +305,7 @@
273
305
  }
274
306
  ],
275
307
  "checks": [],
308
+ "triggers": [],
276
309
  "foreignKeys": {
277
310
  "organizations_tenant_id_foreign": {
278
311
  "columnNames": [
@@ -305,6 +338,7 @@
305
338
  "scale": null,
306
339
  "default": null,
307
340
  "comment": null,
341
+ "collation": null,
308
342
  "enumItems": [],
309
343
  "mappedType": "datetime"
310
344
  },
@@ -321,6 +355,7 @@
321
355
  "scale": null,
322
356
  "default": null,
323
357
  "comment": null,
358
+ "collation": null,
324
359
  "enumItems": [],
325
360
  "mappedType": "datetime"
326
361
  },
@@ -337,6 +372,7 @@
337
372
  "scale": null,
338
373
  "default": "gen_random_uuid()",
339
374
  "comment": null,
375
+ "collation": null,
340
376
  "enumItems": [],
341
377
  "mappedType": "uuid"
342
378
  },
@@ -353,6 +389,7 @@
353
389
  "scale": null,
354
390
  "default": "true",
355
391
  "comment": null,
392
+ "collation": null,
356
393
  "enumItems": [],
357
394
  "mappedType": "boolean"
358
395
  },
@@ -369,6 +406,7 @@
369
406
  "scale": null,
370
407
  "default": null,
371
408
  "comment": null,
409
+ "collation": null,
372
410
  "enumItems": [],
373
411
  "mappedType": "text"
374
412
  },
@@ -385,6 +423,7 @@
385
423
  "scale": null,
386
424
  "default": null,
387
425
  "comment": null,
426
+ "collation": null,
388
427
  "enumItems": [],
389
428
  "mappedType": "datetime"
390
429
  }
@@ -402,6 +441,7 @@
402
441
  }
403
442
  ],
404
443
  "checks": [],
444
+ "triggers": [],
405
445
  "foreignKeys": {},
406
446
  "comment": null
407
447
  }