@feedmepos/mf-order-setting 0.0.76 → 0.0.77

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 (81) hide show
  1. package/dist/{KioskDevicesView-H7stMDFu.js → KioskDevicesView-Dgs5_UoK.js} +1 -1
  2. package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-DgpV4oKE.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-B4DXPgAV.js} +3 -3
  3. package/dist/KioskSettingView-BUcPNVTc.js +681 -0
  4. package/dist/{KioskView-Cxcc1UL1.js → KioskView-DelVMMj4.js} +137 -127
  5. package/dist/{OrderSettingsView-Ds2L_rbu.js → OrderSettingsView-DE2U7vqW.js} +9232 -8262
  6. package/dist/{app-BDlPjU4y.js → app-BIvt4NBq.js} +1476 -1302
  7. package/dist/app.js +1 -1
  8. package/dist/{dayjs.min-OJf1trOq.js → dayjs.min-B58_jBg3.js} +1 -1
  9. package/dist/frontend/mf-order/src/api/index.d.ts +3 -1
  10. package/dist/frontend/mf-order/src/api/remoteOrder/index.d.ts +65 -2
  11. package/dist/frontend/mf-order/src/app.d.ts +174 -0
  12. package/dist/frontend/mf-order/src/composables/useInternalUser.d.ts +3 -0
  13. package/dist/frontend/mf-order/src/main.d.ts +174 -0
  14. package/dist/frontend/mf-order/src/modules/order-setting/kiosk/interface.d.ts +4 -0
  15. package/dist/frontend/mf-order/src/stores/order-setting/index.d.ts +15 -3
  16. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/AdditionalStores.vue.d.ts +16 -0
  17. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/ExternalSetting.vue.d.ts +2 -2
  18. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/FoodpandaAdditionalStores.vue.d.ts +10 -0
  19. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/IntegratedDelivery.vue.d.ts +15 -4
  20. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/IntegrationIdentityDetails.vue.d.ts +10 -0
  21. package/dist/frontend/mf-order/src/views/order-settings/delivery/integrated-delivery/ShopeefoodAdditionalStores.vue.d.ts +10 -0
  22. package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +1 -1
  23. package/dist/index-CLS-FSH-.js +762 -0
  24. package/dist/{index-gGOpbnY3.js → index-CahsmFL1.js} +1 -1
  25. package/dist/{lineman-settings.dto-Cny_fF4n.js → lineman-settings.dto-DBDThIb0.js} +8999 -8874
  26. package/dist/package/entity/delivery/delivery.enum.d.ts +1 -1
  27. package/dist/package/entity/incoming-order/incoming-order.do.d.ts +4 -4
  28. package/dist/package/entity/kiosk/marketing/marketing.dto.d.ts +11 -0
  29. package/dist/package/entity/order-platform/base-integration.dto.d.ts +179 -0
  30. package/dist/package/entity/order-platform/deliveroo/deliveroo-dto.d.ts +2 -2
  31. package/dist/package/entity/order-platform/deliveroo/deliveroo-setting.do.d.ts +2 -2
  32. package/dist/package/entity/order-platform/external/order/external-order.dto.d.ts +2 -2
  33. package/dist/package/entity/order-platform/external/setting/external-setting.do.d.ts +4 -4
  34. package/dist/package/entity/order-platform/external/setting/external-setting.dto.d.ts +4 -4
  35. package/dist/package/entity/order-platform/foodpanda/foodpanda-menu.dto.d.ts +1 -1
  36. package/dist/package/entity/order-platform/foodpanda/foodpanda-settings.do.d.ts +105 -4
  37. package/dist/package/entity/order-platform/foodpanda/foodpanda-settings.dto.d.ts +30 -11
  38. package/dist/package/entity/order-platform/gofood/gofood-settings.do.d.ts +2 -2
  39. package/dist/package/entity/order-platform/gofood/gofood-settings.dto.d.ts +2 -2
  40. package/dist/package/entity/order-platform/grabfood/grabfood-order.do.d.ts +10 -10
  41. package/dist/package/entity/order-platform/grabfood/grabfood-settings.do.d.ts +134 -6
  42. package/dist/package/entity/order-platform/grabfood/grabfood.dto.d.ts +42 -20
  43. package/dist/package/entity/order-platform/lineman/lineman-settings.do.d.ts +2 -2
  44. package/dist/package/entity/order-platform/lineman/lineman-settings.dto.d.ts +2 -2
  45. package/dist/package/entity/order-platform/shopeefood/shopeefood-settings.do.d.ts +102 -4
  46. package/dist/package/entity/order-platform/shopeefood/shopeefood-settings.dto.d.ts +66 -5
  47. package/dist/package/entity/order-platform/tng/tng-settings.do.d.ts +2 -2
  48. package/dist/package/entity/order-platform/tng/tng-settings.dto.d.ts +4 -4
  49. package/dist/package/entity/order-setting/dine-in/dine-in.do.d.ts +2 -2
  50. package/dist/package/entity/order-setting/dine-in/dine-in.dto.d.ts +2 -2
  51. package/dist/package/entity/order-setting/kiosk/kiosk.do.d.ts +6 -0
  52. package/dist/package/entity/order-setting/kiosk/kiosk.dto.d.ts +6 -0
  53. package/dist/package/entity/order-setting/order-setting.do.d.ts +14 -4
  54. package/dist/package/entity/order-setting/order-setting.dto.d.ts +28 -8
  55. package/dist/package/entity/restaurant/restaurant.dto.d.ts +10 -0
  56. package/dist/package/entity/websocket/websocket.dto.d.ts +24 -0
  57. package/dist/style.css +1 -1
  58. package/package.json +1 -1
  59. package/src/api/index.ts +8 -2
  60. package/src/api/remoteOrder/index.ts +200 -4
  61. package/src/composables/useInternalUser.ts +14 -0
  62. package/src/locales/en-US.json +43 -0
  63. package/src/locales/ja-JP.json +43 -0
  64. package/src/locales/th-TH.json +90 -45
  65. package/src/locales/zh-CN.json +43 -0
  66. package/src/modules/order-setting/kiosk/interface.ts +29 -19
  67. package/src/stores/order-setting/mapper.ts +65 -55
  68. package/src/views/kiosk/settings/KioskSettingView.vue +33 -0
  69. package/src/views/order-settings/delivery/DeliverySetting.vue +0 -1
  70. package/src/views/order-settings/delivery/delivery.data.ts +3 -0
  71. package/src/views/order-settings/delivery/integrated-delivery/AdditionalStores.vue +472 -0
  72. package/src/views/order-settings/delivery/integrated-delivery/FoodpandaAdditionalStores.vue +314 -0
  73. package/src/views/order-settings/delivery/integrated-delivery/FoodpandaSetting.vue +27 -8
  74. package/src/views/order-settings/delivery/integrated-delivery/GrabfoodSetting.vue +31 -12
  75. package/src/views/order-settings/delivery/integrated-delivery/IntegratedDelivery.vue +23 -11
  76. package/src/views/order-settings/delivery/integrated-delivery/IntegrationIdentityDetails.vue +144 -0
  77. package/src/views/order-settings/delivery/integrated-delivery/ShopeefoodAdditionalStores.vue +335 -0
  78. package/src/views/order-settings/delivery/integrated-delivery/ShopeefoodSetting.vue +29 -8
  79. package/src/views/order-settings/queue/QueueSetting.vue +1 -1
  80. package/dist/KioskSettingView-fF6k9s_q.js +0 -658
  81. package/dist/index-Bid9lO2x.js +0 -665
@@ -0,0 +1,314 @@
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, reactive, ref, watch } from 'vue'
3
+ import Day from 'dayjs'
4
+ import { useI18n } from '@feedmepos/mf-common'
5
+ import { useDialog } from '@feedmepos/ui-library'
6
+ import { remoteOrderApi, type FoodpandaStoreView } from '@/api/remoteOrder'
7
+ import { useSnackbarFunctions } from '@/components/snackbar'
8
+ import { useLoading } from '@/composables/loading'
9
+ import { useMenuStore } from '@/stores/menu/menu'
10
+ import IntegrationIdentityDetails from './IntegrationIdentityDetails.vue'
11
+ import { useInternalUser } from '@/composables/useInternalUser'
12
+
13
+ const props = defineProps<{ restaurantId: string; primaryEnabled: boolean }>()
14
+ const emits = defineEmits<{ (event: 'syncMenu', isPrimary: false): void }>()
15
+ const { t } = useI18n()
16
+ const Dialog = useDialog()
17
+ const menuStore = useMenuStore()
18
+ const { startAsyncCallWithErr } = useLoading()
19
+ const { showError, showSuccess } = useSnackbarFunctions()
20
+ const { isInternal } = useInternalUser()
21
+
22
+ const stores = ref<FoodpandaStoreView[]>([])
23
+ const editing = ref<null | {
24
+ mode: 'new' | 'edit'
25
+ integrationId: string | null
26
+ routeId: string
27
+ remoteId: string
28
+ }>(null)
29
+ const isSaving = ref(false)
30
+ const form = reactive<{ catalogId: string | null; enable: boolean }>({
31
+ catalogId: null,
32
+ enable: true
33
+ })
34
+ const originalForm = reactive<{ catalogId: string | null; enable: boolean }>({
35
+ catalogId: null,
36
+ enable: true
37
+ })
38
+ const catalogOptions = computed(() => menuStore.catalogOptions ?? [])
39
+ const additionalStores = computed(() => stores.value.filter((store) => !store.isPrimary))
40
+ const showSyncAction = computed(
41
+ () => !props.primaryEnabled && additionalStores.value.some((store) => store.enable === true)
42
+ )
43
+
44
+ function catalogLabel(catalogId?: string | null) {
45
+ if (!catalogId) return '—'
46
+ return catalogOptions.value.find((catalog) => catalog.value === catalogId)?.label ?? catalogId
47
+ }
48
+ function formatDate(value?: string | null) {
49
+ return value ? Day(value).format('DD/MM/YYYY hh:mm a') : '—'
50
+ }
51
+ async function load() {
52
+ if (!props.restaurantId) return
53
+ await startAsyncCallWithErr(async () => {
54
+ stores.value = await remoteOrderApi.integratedDelivery.listFoodpandaStores(props.restaurantId)
55
+ })
56
+ }
57
+ onMounted(load)
58
+ watch(() => props.restaurantId, load)
59
+
60
+ function openAdd() {
61
+ editing.value = { mode: 'new', integrationId: null, routeId: '', remoteId: '' }
62
+ form.catalogId = null
63
+ form.enable = true
64
+ }
65
+ function openEdit(store: FoodpandaStoreView) {
66
+ editing.value = {
67
+ mode: 'edit',
68
+ integrationId: store.integrationId,
69
+ routeId: store.routeId,
70
+ remoteId: store.remoteId
71
+ }
72
+ form.catalogId = store.catalogId ?? null
73
+ form.enable = store.enable ?? false
74
+ originalForm.catalogId = form.catalogId
75
+ originalForm.enable = form.enable
76
+ }
77
+ function cancelEdit() {
78
+ editing.value = null
79
+ }
80
+ function onSheetToggle(open: boolean) {
81
+ if (!open) cancelEdit()
82
+ }
83
+
84
+ async function save() {
85
+ if (isSaving.value) return
86
+
87
+ isSaving.value = true
88
+ try {
89
+ await startAsyncCallWithErr(async () => {
90
+ const payload = { catalogId: form.catalogId, enable: form.enable }
91
+ if (editing.value?.mode === 'new') {
92
+ await remoteOrderApi.integratedDelivery.addFoodpandaStore(props.restaurantId, payload)
93
+ showSuccess(t('order.storeAdded'))
94
+ } else if (editing.value) {
95
+ const updatePayload =
96
+ form.enable === false && form.catalogId === originalForm.catalogId
97
+ ? { enable: false }
98
+ : payload
99
+ await remoteOrderApi.integratedDelivery.updateFoodpandaStore(
100
+ props.restaurantId,
101
+ editing.value.routeId,
102
+ updatePayload
103
+ )
104
+ showSuccess(t('order.storeUpdated'))
105
+ }
106
+ editing.value = null
107
+ await load()
108
+ })
109
+ } finally {
110
+ isSaving.value = false
111
+ }
112
+ }
113
+
114
+ function promptRemove(store: FoodpandaStoreView) {
115
+ if (store.enable) {
116
+ showError(t('order.disableBeforeRemove'))
117
+ return
118
+ }
119
+ Dialog.open({
120
+ title: t('order.removeStoreConfirm', { merchantID: store.remoteId }),
121
+ primaryActions: { text: t('order.removeStore'), variant: 'destructive', close: true },
122
+ secondaryActions: { text: t('order.cancel'), variant: 'tertiary', close: true }
123
+ }).onPrimary(async () => {
124
+ await startAsyncCallWithErr(async () => {
125
+ await remoteOrderApi.integratedDelivery.removeFoodpandaStore(
126
+ props.restaurantId,
127
+ store.routeId
128
+ )
129
+ showSuccess(t('order.storeRemoved'))
130
+ await load()
131
+ })
132
+ })
133
+ }
134
+ </script>
135
+
136
+ <template>
137
+ <div class="mt-10 store-section">
138
+ <div class="flex flex-row items-center mb-1">
139
+ <div class="flex-grow fm-typo-en-title-sm-600">{{ t('order.additionalStores') }}</div>
140
+ <div class="store-card__actions">
141
+ <FmButton
142
+ v-if="showSyncAction"
143
+ variant="primary"
144
+ :label="t('order.syncMenu')"
145
+ @click="emits('syncMenu', false)"
146
+ />
147
+ <FmButton variant="secondary" :label="t('order.addStore')" @click="openAdd" />
148
+ </div>
149
+ </div>
150
+ <div class="text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400 mb-3">
151
+ {{ t('order.foodpandaStoresHint') }}
152
+ </div>
153
+ <div
154
+ v-if="additionalStores.length"
155
+ class="text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400 mb-3"
156
+ >
157
+ {{ t('order.syncAllStoresHint') }}
158
+ </div>
159
+
160
+ <FmCard
161
+ v-for="store in additionalStores"
162
+ :key="store.routeId"
163
+ variant="outlined"
164
+ class="store-card"
165
+ >
166
+ <div class="store-card__top">
167
+ <div class="store-card__title fm-typo-en-title-sm-600">{{ store.remoteId }}</div>
168
+ <div class="store-card__actions">
169
+ <FmButton variant="secondary" :label="t('order.edit')" @click="openEdit(store)" />
170
+ <FmButton
171
+ v-if="isInternal"
172
+ variant="secondary"
173
+ :label="t('order.removeStore')"
174
+ @click="promptRemove(store)"
175
+ />
176
+ </div>
177
+ </div>
178
+ <div
179
+ v-if="isInternal && store.enable"
180
+ class="text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400 mb-3"
181
+ >
182
+ {{ t('order.disableBeforeRemove') }}
183
+ </div>
184
+ <div class="store-card__fields fm-typo-cn-body-md-400">
185
+ <div class="store-field">
186
+ <span>{{ t('order.enabled') }}</span
187
+ ><span>{{ store.enable ? '✓' : '✕' }}</span>
188
+ </div>
189
+ <div class="store-field">
190
+ <span>{{ t('order.catalog') }}</span
191
+ ><span>{{ catalogLabel(store.catalogId) }}</span>
192
+ </div>
193
+ <div class="store-field">
194
+ <span>{{ t('order.status') }}</span
195
+ ><span>{{ store.status ?? '—' }}</span>
196
+ </div>
197
+ <div class="store-field">
198
+ <span>{{ t('order.menuSyncError') }}</span
199
+ ><span>{{ store.menuSyncError ?? '—' }}</span>
200
+ </div>
201
+ <div class="store-field">
202
+ <span>{{ t('order.lastSync') }}</span
203
+ ><span>{{ formatDate(store.lastSynchronize) }}</span>
204
+ </div>
205
+ <div class="store-field">
206
+ <span>{{ t('order.updatedAt') }}</span
207
+ ><span>{{ formatDate(store.updatedAt) }}</span>
208
+ </div>
209
+ </div>
210
+ <IntegrationIdentityDetails
211
+ :integration-id="store.integrationId"
212
+ :partner-id="store.partnerId ?? store.remoteId"
213
+ :partner-label="t('order.foodpandaStoreId')"
214
+ />
215
+ </FmCard>
216
+
217
+ <FmSideSheet
218
+ :header="editing?.mode === 'new' ? t('order.addStore') : t('order.editStore')"
219
+ dismiss-away
220
+ class="w-full fm-typo-en-title-md-600"
221
+ :max-width="600"
222
+ :model-value="!!editing"
223
+ @update:model-value="onSheetToggle"
224
+ >
225
+ <div class="store-sheet-body">
226
+ <IntegrationIdentityDetails
227
+ v-if="editing?.mode === 'edit'"
228
+ :integration-id="editing.integrationId"
229
+ :partner-id="editing.remoteId"
230
+ :partner-label="t('order.foodpandaStoreId')"
231
+ />
232
+ <div v-else class="text-fm-color-neutral-gray-400 fm-typo-cn-body-md-400">
233
+ {{ t('order.foodpandaStoreIdGeneratedHint') }}
234
+ </div>
235
+ <FmSelect
236
+ v-model="form.catalogId"
237
+ :label="t('order.catalog')"
238
+ :placeholder="t('order.selectCatalog')"
239
+ :items="catalogOptions"
240
+ />
241
+ <FmSwitch v-model="form.enable" :label="t('order.enabled')" label-placement="right" />
242
+ </div>
243
+ <template #side-sheet-footer>
244
+ <div class="flex gap-8 items-center justify-start">
245
+ <FmButton
246
+ variant="primary"
247
+ size="lg"
248
+ :label="editing?.mode === 'new' ? t('order.addStore') : t('order.save')"
249
+ :loading="isSaving"
250
+ :disabled="isSaving"
251
+ @click="save"
252
+ />
253
+ <FmButton variant="secondary" size="lg" :label="t('order.cancel')" @click="cancelEdit" />
254
+ </div>
255
+ </template>
256
+ </FmSideSheet>
257
+ </div>
258
+ </template>
259
+
260
+ <style scoped>
261
+ .store-section {
262
+ border-top: 1px solid #e5e7eb;
263
+ padding-top: 1.5rem;
264
+ }
265
+ .store-card {
266
+ padding: 1.25rem;
267
+ margin-bottom: 1rem;
268
+ }
269
+ .store-card__top {
270
+ display: flex;
271
+ align-items: flex-start;
272
+ justify-content: space-between;
273
+ gap: 1rem;
274
+ margin-bottom: 1rem;
275
+ }
276
+ .store-card__title {
277
+ min-width: 0;
278
+ word-break: break-all;
279
+ }
280
+ .store-card__actions {
281
+ display: flex;
282
+ flex-wrap: wrap;
283
+ justify-content: flex-end;
284
+ gap: 0.5rem;
285
+ flex-shrink: 0;
286
+ }
287
+ .store-card__fields {
288
+ display: flex;
289
+ flex-direction: column;
290
+ gap: 0.625rem;
291
+ }
292
+ .store-field {
293
+ display: grid;
294
+ grid-template-columns: 9rem minmax(0, 1fr);
295
+ gap: 1rem;
296
+ }
297
+ .store-field > span:first-child {
298
+ color: var(--fm-color-neutral-gray-400);
299
+ }
300
+ .store-field > span:last-child {
301
+ word-break: break-word;
302
+ }
303
+ .store-sheet-body {
304
+ display: flex;
305
+ flex-direction: column;
306
+ gap: 1rem;
307
+ padding-top: 0.5rem;
308
+ }
309
+ @media (max-width: 460px) {
310
+ .store-field {
311
+ grid-template-columns: 1fr;
312
+ }
313
+ }
314
+ </style>
@@ -3,6 +3,8 @@ import { FdoLinkedDelivery } from '@feedmepos/core/entity'
3
3
  import { FdoFoodpandaReportCalculationConfig, FdoFoodpandaSettings } from '@entity'
4
4
  import { type PropType, reactive, computed } from 'vue'
5
5
  import IntegratedDelivery from './IntegratedDelivery.vue'
6
+ import FoodpandaAdditionalStores from './FoodpandaAdditionalStores.vue'
7
+ import IntegrationIdentityDetails from './IntegrationIdentityDetails.vue'
6
8
  import { useCoreStore } from '@feedmepos/mf-common'
7
9
  import { clone } from '@/helpers/object'
8
10
  import type { DeliveryCompany } from '../delivery'
@@ -68,10 +70,12 @@ function initReportConfig(): FdoFoodpandaReportCalculationConfig {
68
70
  }
69
71
 
70
72
  function updateData({ manual, auto }: IntegratedDeliverySettingData) {
73
+ const { integrationId: remoteId, ...autoSettings } = auto
71
74
  data.manual = manual
72
75
  data.integrated = {
73
- ...auto,
74
- remoteId: auto.integrationId,
76
+ ...data.integrated,
77
+ ...autoSettings,
78
+ remoteId,
75
79
  reportConfig: {
76
80
  ...(data.integrated?.reportConfig || initReportConfig()),
77
81
  commissionRate: auto.commissionRate
@@ -191,20 +195,20 @@ function formatCampaignDate(date: { start: string; end: string }) {
191
195
  }
192
196
 
193
197
  const { startAsyncCallWithErr } = useLoading()
194
- async function syncMenu() {
198
+ async function syncMenu(isPrimary = true) {
195
199
  const restaurantId = currentRestaurant.value?._id ?? ''
196
200
  await startAsyncCallWithErr(async () => {
197
- const res = await remoteOrderApi.integratedDelivery.syncFoodpandaMenu(restaurantId)
198
- const temp = {
199
- ...modelValue.value.auto,
200
- lastSynchronize: res?.lastSynchronize || modelValue.value.auto.lastSynchronize
201
+ await remoteOrderApi.integratedDelivery.syncFoodpandaMenu(restaurantId)
202
+ if (isPrimary) {
203
+ data.integrated.lastSynchronize = new Date().toISOString()
201
204
  }
202
- updateData({ auto: temp, manual: data.manual })
203
205
  showSuccess(t('order.syncMenuSuccess'))
204
206
  })
205
207
  }
206
208
 
207
209
  const { currentRestaurant } = useCoreStore()
210
+ const restaurantId = computed(() => currentRestaurant.value?._id ?? '')
211
+ const primaryIntegrationId = computed(() => data.integrated.integrationId)
208
212
  import { useSnackbarFunctions } from '@/components/snackbar'
209
213
  const { showSuccess } = useSnackbarFunctions()
210
214
 
@@ -229,10 +233,25 @@ async function cancelTestOrder() {
229
233
  :model-value="modelValue"
230
234
  :company="company"
231
235
  :can-use-auto-delivery-integration="canUseAutoDeliveryIntegration"
236
+ integration-id-read-only
232
237
  @update:model-value="updateDataPass"
233
238
  @sync-menu="syncMenu"
234
239
  @flow-test-order="flowTestOrder"
235
240
  @cancel-test-order="cancelTestOrder"
236
241
  >
242
+ <template #integration-details>
243
+ <IntegrationIdentityDetails
244
+ :integration-id="primaryIntegrationId"
245
+ :partner-id="data.integrated.remoteId"
246
+ :partner-label="t('order.foodpandaStoreId')"
247
+ :store-label="t('order.primaryStore')"
248
+ />
249
+ </template>
237
250
  </IntegratedDelivery>
251
+ <FoodpandaAdditionalStores
252
+ v-if="canUseAutoDeliveryIntegration === true"
253
+ :restaurant-id="restaurantId"
254
+ :primary-enabled="modelValue.auto.enable"
255
+ @sync-menu="syncMenu"
256
+ />
238
257
  </template>
@@ -8,6 +8,8 @@ import type { IntegratedDeliverySettingData } from './IntegratedDelivery.vue'
8
8
  import { useLoading } from '@/composables/loading'
9
9
  import { remoteOrderApi } from '@/api/remoteOrder'
10
10
  import IntegratedDelivery from './IntegratedDelivery.vue'
11
+ import AdditionalStores from './AdditionalStores.vue'
12
+ import IntegrationIdentityDetails from './IntegrationIdentityDetails.vue'
11
13
  import { clone } from '@/helpers/object'
12
14
  import { useI18n, useCoreStore } from '@feedmepos/mf-common'
13
15
 
@@ -61,16 +63,20 @@ const modelValue = computed<IntegratedDeliverySettingData>(() => {
61
63
  })
62
64
 
63
65
  const { currentRestaurant, currentCountry } = useCoreStore()
66
+ const restaurantId = computed(() => currentRestaurant.value?._id ?? '')
67
+ const primaryIntegrationId = computed(() => data.integrated.integrationId)
64
68
  const learnMoreUrl = computed(() => {
65
69
  const country = (currentCountry.value || F_COUNTRY.enum.MY) as F_COUNTRY
66
70
  return GRABFOOD_LEARN_MORE_URL_BY_COUNTRY[country] || GRABFOOD_LEARN_MORE_URL_DEFAULT
67
71
  })
68
72
 
69
73
  function updateData({ manual, auto }: IntegratedDeliverySettingData) {
74
+ const { integrationId: merchantID, ...autoSettings } = auto
70
75
  data.manual = manual
71
76
  data.integrated = {
72
- ...auto,
73
- merchantID: auto.integrationId,
77
+ ...data.integrated,
78
+ ...autoSettings,
79
+ merchantID,
74
80
  commissionRate: auto.commissionRate
75
81
  }
76
82
  return { manual: data.manual, integrated: data.integrated }
@@ -86,16 +92,14 @@ function updateDataPass({ manual, auto }: IntegratedDeliverySettingData) {
86
92
  }
87
93
 
88
94
  const { startAsyncCallWithErr } = useLoading()
89
- async function syncMenu() {
95
+ async function syncMenu(merchantID?: string) {
90
96
  await startAsyncCallWithErr(async () => {
91
- const res = await remoteOrderApi.integratedDelivery.syncGrabfoodMenu(
92
- data.integrated.merchantID ?? ''
93
- )
94
- const temp = {
95
- ...modelValue.value.auto,
96
- lastSynchronize: res?.lastSynchronize || modelValue.value.auto.lastSynchronize
97
+ const isPrimary = merchantID === undefined
98
+ const targetMerchantID = merchantID ?? data.integrated.merchantID ?? ''
99
+ await remoteOrderApi.integratedDelivery.syncGrabfoodMenu(targetMerchantID)
100
+ if (isPrimary) {
101
+ data.integrated.lastSynchronize = new Date().toISOString()
97
102
  }
98
- updateData({ auto: temp, manual: data.manual })
99
103
  showSuccess(t('order.syncMenuSuccess'))
100
104
  })
101
105
  }
@@ -147,7 +151,16 @@ async function startSelfServeFlow() {
147
151
  @sync-menu="syncMenu"
148
152
  @flow-test-order="flowTestOrder"
149
153
  @cancel-test-order="cancelTestOrder"
150
- />
154
+ >
155
+ <template #integration-details>
156
+ <IntegrationIdentityDetails
157
+ :integration-id="primaryIntegrationId"
158
+ :provider-id="data.integrated.merchantID"
159
+ :provider-label="t('order.merchantId')"
160
+ :store-label="t('order.primaryStore')"
161
+ />
162
+ </template>
163
+ </IntegratedDelivery>
151
164
  <div v-if="canUseAutoDeliveryIntegration === true">
152
165
  <div class="flex flex-row mt-5 items-center">
153
166
  <div class="flex-grow fm-typo-en-title-sm-600 mb-3 mt-3">
@@ -155,7 +168,7 @@ async function startSelfServeFlow() {
155
168
  </div>
156
169
  <FmButton
157
170
  variant="primary"
158
- :label="t('order.start')"
171
+ :label="t('order.activate')"
159
172
  @click="startSelfServeFlow"
160
173
  class="m-1"
161
174
  />
@@ -167,4 +180,10 @@ async function startSelfServeFlow() {
167
180
  >.
168
181
  </div>
169
182
  </div>
183
+ <AdditionalStores
184
+ v-if="canUseAutoDeliveryIntegration === true"
185
+ :restaurant-id="restaurantId"
186
+ :primary-enabled="modelValue.auto.enable"
187
+ @sync-menu="syncMenu"
188
+ />
170
189
  </template>
@@ -6,8 +6,9 @@ import type { DeliveryCompany } from '../delivery'
6
6
  import { useMenuStore } from '@/stores/menu/menu'
7
7
  import ManualIntegratedDeliverySetting from '../components/ManualIntegratedDeliverySetting.vue'
8
8
  import { ItemTax } from '@/helpers/menu'
9
- import { useCoreStore } from '@feedmepos/mf-common'
10
9
  import { useI18n } from '@feedmepos/mf-common'
10
+ import { overrideMenu } from '@feedmepos/menu'
11
+ import { useInternalUser } from '@/composables/useInternalUser'
11
12
 
12
13
  const { t } = useI18n()
13
14
 
@@ -54,6 +55,10 @@ const props = defineProps({
54
55
  showTestOrderActions: {
55
56
  type: Boolean,
56
57
  default: true
58
+ },
59
+ integrationIdReadOnly: {
60
+ type: Boolean,
61
+ default: false
57
62
  }
58
63
  })
59
64
 
@@ -65,18 +70,12 @@ const emits = defineEmits<{
65
70
  }>()
66
71
 
67
72
  const menuStore = useMenuStore()
68
- const CoreStore = useCoreStore()
69
73
  const taxSystemCode = computed(() => ItemTax.taxSetting().systemCode)
74
+ const { isInternal } = useInternalUser()
70
75
 
71
76
  // For now, allow all users to edit, but added detail logging in backend
72
77
  const canEdit = computed(() => true)
73
78
 
74
- // FeedMe-internal (staff/admin) only. Merchants/business owners must not edit
75
- // the platform integration id or place test orders; both gated on this below.
76
- const isInternal = computed(
77
- () => (CoreStore.sessionUser.value?.role?.isStaff || CoreStore.sessionUser.value?.role?.isAdmin) ?? false
78
- )
79
-
80
79
  function updateManual(manual: FdoLinkedDelivery | null) {
81
80
  emits('update:model-value', {
82
81
  ...props.modelValue,
@@ -249,8 +248,17 @@ const formattedMenuSyncError = computed(() => {
249
248
  const matches = error.match(itemIdPattern)
250
249
 
251
250
  if (matches) {
251
+ let items = menuStore.menu.modules.item
252
+ try {
253
+ items = overrideMenu({
254
+ menu: menuStore.menu,
255
+ catalogId: props.modelValue.auto.catalogId
256
+ }).modules.item
257
+ } catch {
258
+ // A malformed override must not prevent the integration setting from rendering.
259
+ }
252
260
  for (const itemId of matches) {
253
- const item = menuStore.items.find((i) => i._id === itemId)
261
+ const item = items.find((i) => i._id === itemId)
254
262
  if (item) {
255
263
  const itemName = item.code ? `${item.code} - ${item.name}` : item.name
256
264
  formatted = formatted.replace(itemId, `"${itemName}" (${itemId})`)
@@ -281,7 +289,7 @@ const formattedMenuSyncError = computed(() => {
281
289
  <div v-if="modelValue.auto.enable">
282
290
  <FmCard variant="outlined" class="mt-10 p-5">
283
291
  <FmTextField
284
- :disabled="!isInternal"
292
+ :disabled="!isInternal || integrationIdReadOnly"
285
293
  :model-value="modelValue.auto.integrationId"
286
294
  :label="t('order.integrationId')"
287
295
  @update:model-value="updateIntegrateId"
@@ -304,6 +312,8 @@ const formattedMenuSyncError = computed(() => {
304
312
  <slot name="order-setting" :editable="!canEdit" />
305
313
  </div>
306
314
 
315
+ <slot name="integration-details" />
316
+
307
317
  <!-- Commision rate not used yet -->
308
318
  <!-- <div class="mb-5">
309
319
  <FmTextField
@@ -380,7 +390,9 @@ const formattedMenuSyncError = computed(() => {
380
390
  <div v-else class="mt-10">
381
391
  <FmSnackbar
382
392
  :title="`${t('order.autoIntegration')}`"
383
- :description="`${t('order.autoIntegrationWarning', { reason: canUseAutoDeliveryIntegration })}`"
393
+ :description="`${t('order.autoIntegrationWarning', {
394
+ reason: canUseAutoDeliveryIntegration
395
+ })}`"
384
396
  variant="warning"
385
397
  />
386
398
  </div>