@feedmepos/mf-order-setting 0.0.86 → 0.0.87

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 (44) hide show
  1. package/dist/{DisplaySystemView-DjFBEWoq.js → DisplaySystemView-Dx4As33V.js} +1 -1
  2. package/dist/{KioskDevicesView-RKMdLuB3.js → KioskDevicesView-Ziu9ekHK.js} +1 -1
  3. package/dist/{KioskDevicesView.vue_vue_type_script_setup_true_lang-BrG6Au5z.js → KioskDevicesView.vue_vue_type_script_setup_true_lang-Bki-OF23.js} +3 -3
  4. package/dist/{KioskSettingView-CzelEig6.js → KioskSettingView-BlLW-tDD.js} +3 -3
  5. package/dist/{KioskView-Bi5Kc4cS.js → KioskView-CzlaEjMR.js} +5 -5
  6. package/dist/{OrderSettingsView-BfxrjGAi.js → OrderSettingsView-B69It-yu.js} +16654 -16507
  7. package/dist/{app-Bg-n-Eug.js → app-C8jy3wlN.js} +324 -248
  8. package/dist/app.js +1 -1
  9. package/dist/common/sms/index.d.ts +14 -3
  10. package/dist/frontend/mf-order/src/app.d.ts +76 -0
  11. package/dist/frontend/mf-order/src/main.d.ts +76 -0
  12. package/dist/frontend/mf-order/src/stores/order-setting/index.d.ts +6 -0
  13. package/dist/frontend/mf-order/src/views/order-settings/components/NotifyChannelEditor.vue.d.ts +11 -0
  14. package/dist/frontend/mf-order/src/views/order-settings/components/notify-channel-country.d.ts +1 -0
  15. package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +1 -1
  16. package/dist/{index-C7Ug4fCi.js → index-DNvUwB59.js} +1 -1
  17. package/dist/{index-g_rocKrp.js → index-DiRlxqKH.js} +2 -2
  18. package/dist/{index-0eYg6XEb.js → index-VtBPFvME.js} +1 -1
  19. package/dist/{order.dto-MKeJAj6d.js → order.dto-CFw4X11T.js} +3708 -3631
  20. package/dist/package/entity/incoming-order/incoming-order-to-bill.dto.d.ts +10 -1
  21. package/dist/package/entity/index.d.ts +2 -0
  22. package/dist/package/entity/index.frontend.d.ts +2 -0
  23. package/dist/package/entity/order-platform/external/order/external-order.do.d.ts +7 -0
  24. package/dist/package/entity/order-setting/notification/notification.enum.d.ts +50 -0
  25. package/dist/package/entity/order-setting/notification/notification.route.d.ts +91 -0
  26. package/dist/package/entity/order-setting/order-setting.do.d.ts +10 -0
  27. package/dist/package/entity/order-setting/order-setting.dto.d.ts +20 -0
  28. package/dist/package/entity/order-setting/queue/queue.do.d.ts +17 -0
  29. package/dist/package/entity/order-setting/queue/queue.dto.d.ts +4 -0
  30. package/dist/package/entity/order-setting/sms/sms.do.d.ts +14 -0
  31. package/dist/package/entity/order-setting/sms/sms.dto.d.ts +4 -0
  32. package/dist/package/entity/restaurant/restaurant.dto.d.ts +10 -0
  33. package/package.json +1 -1
  34. package/src/locales/en-US.json +20 -1
  35. package/src/locales/ja-JP.json +20 -1
  36. package/src/locales/th-TH.json +20 -1
  37. package/src/locales/zh-CN.json +20 -1
  38. package/src/views/order-settings/OrderSettingsView.vue +1 -1
  39. package/src/views/order-settings/components/NotifyChannelEditor.vue +130 -0
  40. package/src/views/order-settings/components/notify-channel-country.spec.ts +20 -0
  41. package/src/views/order-settings/components/notify-channel-country.ts +21 -0
  42. package/src/views/order-settings/queue/QueueSetting.vue +7 -0
  43. package/src/views/order-settings/sms/SmsSetting.vue +41 -1
  44. package/src/views/order-settings/sms/notify-channels-dto.spec.ts +42 -0
@@ -216,7 +216,7 @@
216
216
  "dineInType": "Dine In Type",
217
217
  "disabled": "Disabled",
218
218
  "disableRemoteQueue": "Disable Remote Queue",
219
- "disableSMS": "Disable SMS",
219
+ "disableSMS": "Disable notifications",
220
220
  "discountCampaign": "Discount Campaign",
221
221
  "discountRule": {
222
222
  "add": "Add discount rule",
@@ -468,6 +468,25 @@
468
468
  "showNotAvailable": "Show not available",
469
469
  "showNotAvailableMenuItem": "Show not available menu item",
470
470
  "sms": "SMS",
471
+ "notifications": "Notifications",
472
+ "notifyChannel": {
473
+ "title": "Channels",
474
+ "hint": "Tried in order. The next channel is used only if the one before it cannot reach the customer.",
475
+ "add": "Add channel",
476
+ "pushNote": "Push only reaches customers who have the app installed with notifications allowed. Otherwise the next channel is tried.",
477
+ "alongside": "{channel} is always sent, plus {route}",
478
+ "everyone": "All customers: {route}",
479
+ "member": "Signed-in customers: {route}",
480
+ "guest": "Guests: {route}",
481
+ "messageRequired": "Write a message before turning this on.",
482
+ "channel": {
483
+ "SMS": "SMS",
484
+ "APP_INBOX": "App Inbox",
485
+ "PUSH_NOTIFICATION": "Push notification",
486
+ "WHATSAPP": "WhatsApp",
487
+ "EMAIL": "Email"
488
+ }
489
+ },
471
490
  "sstInclusive": "{taxName} Inclusive",
472
491
  "start": "Start",
473
492
  "staticQRcode": "Static QR code",
@@ -326,7 +326,7 @@
326
326
  "duration": "時間",
327
327
  "disableRemoteQueue": "リモート順番待ちを無効にする",
328
328
  "allowOverrideQueueCode": "整理番号の上書きを許可",
329
- "disableSMS": "SMSを無効にする",
329
+ "disableSMS": "通知を無効にする",
330
330
  "printChecklist": "チェックリストを印刷",
331
331
  "printChecklistSublabel": "オンライン注文のみ",
332
332
  "onlinePaxDialog": "人数ダイアログ(オンライン注文)",
@@ -374,6 +374,25 @@
374
374
  "settings": "設定",
375
375
  "showNotAvailable": "利用不可を表示",
376
376
  "sms": "SMS",
377
+ "notifications": "通知",
378
+ "notifyChannel": {
379
+ "title": "送信チャネル",
380
+ "hint": "上から順に試します。前のチャネルでお客様に届かない場合のみ、次のチャネルを使います。",
381
+ "add": "チャネルを追加",
382
+ "pushNote": "プッシュ通知は、アプリをインストールし通知を許可しているお客様にのみ届きます。届かない場合は次のチャネルを試します。",
383
+ "alongside": "{channel}は常に送信、加えて {route}",
384
+ "everyone": "すべてのお客様:{route}",
385
+ "member": "ログイン済みのお客様:{route}",
386
+ "guest": "ゲスト:{route}",
387
+ "messageRequired": "有効にする前にメッセージを入力してください。",
388
+ "channel": {
389
+ "SMS": "SMS",
390
+ "APP_INBOX": "アプリ受信箱",
391
+ "PUSH_NOTIFICATION": "プッシュ通知",
392
+ "WHATSAPP": "WhatsApp",
393
+ "EMAIL": "メール"
394
+ }
395
+ },
377
396
  "sstInclusive": "{taxName}込み",
378
397
  "vatExclude": "VAT除外",
379
398
  "start": "開始",
@@ -429,7 +429,7 @@
429
429
  "duration": "ระยะเวลา",
430
430
  "disableRemoteQueue": "ปิดการรับคิวออนไลน์",
431
431
  "allowOverrideQueueCode": "อนุญาตให้แก้ไขรหัสคิว",
432
- "disableSMS": "ปิดใช้งาน SMS",
432
+ "disableSMS": "ปิดใช้งานการแจ้งเตือน",
433
433
  "codeSettings": "ตั้งค่ารหัสคิว",
434
434
  "addCodeSetting": "เพิ่มการตั้งค่ารหัสคิว",
435
435
  "current": "ปัจจุบัน",
@@ -470,6 +470,25 @@
470
470
  "settings": "การตั้งค่า",
471
471
  "showNotAvailable": "ไม่แสดงข้อมูล",
472
472
  "sms": "SMS",
473
+ "notifications": "การแจ้งเตือน",
474
+ "notifyChannel": {
475
+ "title": "ช่องทาง",
476
+ "hint": "ลองตามลำดับ ช่องทางถัดไปจะใช้เมื่อช่องทางก่อนหน้าส่งถึงลูกค้าไม่ได้เท่านั้น",
477
+ "add": "เพิ่มช่องทาง",
478
+ "pushNote": "การแจ้งเตือนแบบพุชจะส่งถึงเฉพาะลูกค้าที่ติดตั้งแอปและอนุญาตการแจ้งเตือน มิฉะนั้นจะลองช่องทางถัดไป",
479
+ "alongside": "ส่ง{channel}เสมอ และ {route}",
480
+ "everyone": "ลูกค้าทุกคน: {route}",
481
+ "member": "ลูกค้าที่เข้าสู่ระบบ: {route}",
482
+ "guest": "ผู้ใช้ทั่วไป: {route}",
483
+ "messageRequired": "กรุณาเขียนข้อความก่อนเปิดใช้งาน",
484
+ "channel": {
485
+ "SMS": "SMS",
486
+ "APP_INBOX": "กล่องข้อความในแอป",
487
+ "PUSH_NOTIFICATION": "การแจ้งเตือนแบบพุช",
488
+ "WHATSAPP": "WhatsApp",
489
+ "EMAIL": "อีเมล"
490
+ }
491
+ },
473
492
  "sstInclusive": "{taxName} รวม",
474
493
  "vatExclude": "ราคาไม่รวมภาษีมูลค่าเพิ่ม",
475
494
  "start": "เริ่มต้น",
@@ -431,7 +431,7 @@
431
431
  "duration": "持续时间",
432
432
  "disableRemoteQueue": "禁用远程排队",
433
433
  "allowOverrideQueueCode": "允许覆盖排队号码",
434
- "disableSMS": "禁用短信",
434
+ "disableSMS": "禁用通知",
435
435
  "codeSettings": "代码设置",
436
436
  "addCodeSetting": "添加代码设置",
437
437
  "current": "当前",
@@ -474,6 +474,25 @@
474
474
  "settings": "设置",
475
475
  "showNotAvailable": "显示无可用",
476
476
  "sms": "短信",
477
+ "notifications": "通知",
478
+ "notifyChannel": {
479
+ "title": "发送渠道",
480
+ "hint": "按顺序尝试。只有前一个渠道无法送达顾客时,才会使用下一个。",
481
+ "add": "添加渠道",
482
+ "pushNote": "推送通知只能送达已安装应用并允许通知的顾客,否则会尝试下一个渠道。",
483
+ "alongside": "始终发送{channel},另外:{route}",
484
+ "everyone": "所有顾客:{route}",
485
+ "member": "已登录顾客:{route}",
486
+ "guest": "访客:{route}",
487
+ "messageRequired": "请先填写消息内容再开启。",
488
+ "channel": {
489
+ "SMS": "短信",
490
+ "APP_INBOX": "应用收件箱",
491
+ "PUSH_NOTIFICATION": "推送通知",
492
+ "WHATSAPP": "WhatsApp",
493
+ "EMAIL": "电子邮件"
494
+ }
495
+ },
477
496
  "sstInclusive": "包含{taxName}",
478
497
  "vatExclude": "不含增值税",
479
498
  "start": "开始",
@@ -61,7 +61,7 @@ const settingItem = computed<FmTabProps[]>(() => {
61
61
  { label: t('order.driveThru'), value: 'driveThru' },
62
62
  // { label: t('order.serviceCharge'), value: 'servicecharge' },
63
63
  // { label: t('order.discountRule.title'), value: 'discountRule' },
64
- { label: t('order.sms'), value: 'sms' },
64
+ { label: t('order.notifications'), value: 'sms' },
65
65
  { label: t('order.general'), value: 'general' },
66
66
  { label: t('order.queue'), value: 'queue' }
67
67
  ]
@@ -0,0 +1,130 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { F_ORDER_NOTIFY_CHANNEL, planSends, resolveChannels } from '@entity'
4
+ import { useCoreStore, useI18n } from '@feedmepos/mf-common'
5
+
6
+ import { canChooseNotifyChannels } from './notify-channel-country'
7
+
8
+ const props = defineProps<{
9
+ modelValue?: F_ORDER_NOTIFY_CHANNEL[] | null
10
+ readonly?: boolean
11
+ }>()
12
+ const emit = defineEmits<{ 'update:modelValue': [value: F_ORDER_NOTIFY_CHANNEL[]] }>()
13
+
14
+ const { t } = useI18n()
15
+
16
+ // MY only: outside it a chosen channel cannot be delivered. See notify-channel-country.ts.
17
+ // Gated here rather than at each use so the SMS row, its read-only summary and the
18
+ // queue setting all follow one rule.
19
+ const { currentCountry } = useCoreStore()
20
+ const available = computed(() => canChooseNotifyChannels(currentCountry.value))
21
+
22
+ const { SMS, APP_INBOX, PUSH_NOTIFICATION } = F_ORDER_NOTIFY_CHANNEL.Enum
23
+ // EMAIL is dropped by routing and WHATSAPP is not offered yet. A stored value
24
+ // set outside the portal is still listed so it can be seen and removed.
25
+ const SELECTABLE: F_ORDER_NOTIFY_CHANNEL[] = [SMS, APP_INBOX, PUSH_NOTIFICATION]
26
+
27
+ const channels = computed(() => props.modelValue ?? [])
28
+
29
+ const addable = computed(() =>
30
+ SELECTABLE.filter((c) => !channels.value.includes(c)).map((c) => ({
31
+ label: channelLabel(c),
32
+ value: c
33
+ }))
34
+ )
35
+
36
+ function channelLabel(channel: F_ORDER_NOTIFY_CHANNEL) {
37
+ return t(`order.notifyChannel.channel.${channel}`)
38
+ }
39
+
40
+ function add(channel: F_ORDER_NOTIFY_CHANNEL) {
41
+ emit('update:modelValue', [...channels.value, channel])
42
+ }
43
+
44
+ function remove(index: number) {
45
+ emit(
46
+ 'update:modelValue',
47
+ channels.value.filter((_, i) => i !== index)
48
+ )
49
+ }
50
+
51
+ function move(index: number, by: -1 | 1) {
52
+ const next = [...channels.value]
53
+ ;[next[index], next[index + by]] = [next[index + by], next[index]]
54
+ emit('update:modelValue', next)
55
+ }
56
+
57
+ function describe(hasAccount: boolean) {
58
+ const { alongside, chain } = planSends(
59
+ resolveChannels({ configured: channels.value, hasAppInboxUid: hasAccount })
60
+ )
61
+ const route = chain.map(channelLabel).join(' → ')
62
+ return alongside.length
63
+ ? t('order.notifyChannel.alongside', { channel: channelLabel(alongside[0]), route })
64
+ : route
65
+ }
66
+
67
+ const summary = computed(() => {
68
+ const member = describe(true)
69
+ const guest = describe(false)
70
+ if (member === guest) return [t('order.notifyChannel.everyone', { route: member })]
71
+ return [
72
+ t('order.notifyChannel.member', { route: member }),
73
+ t('order.notifyChannel.guest', { route: guest })
74
+ ]
75
+ })
76
+ </script>
77
+
78
+ <template>
79
+ <div v-if="available" class="flex flex-col gap-2">
80
+ <span class="fm-typo-en-body-md-600">{{ t('order.notifyChannel.title') }}</span>
81
+
82
+ <template v-if="!readonly">
83
+ <span class="fm-typo-en-body-sm-400 text-fm-color-neutral-gray-400">
84
+ {{ t('order.notifyChannel.hint') }}
85
+ </span>
86
+ <div
87
+ v-for="(channel, i) in channels"
88
+ :key="channel"
89
+ class="flex items-center gap-2 border border-fm-color-neutral-gray-200 rounded px-3 py-1"
90
+ >
91
+ <span class="flex-grow">{{ i + 1 }}. {{ channelLabel(channel) }}</span>
92
+ <FmButton
93
+ icon="arrow_upward"
94
+ variant="tertiary"
95
+ :disabled="i === 0"
96
+ @click="move(i, -1)"
97
+ />
98
+ <FmButton
99
+ icon="arrow_downward"
100
+ variant="tertiary"
101
+ :disabled="i === channels.length - 1"
102
+ @click="move(i, 1)"
103
+ />
104
+ <FmButton icon="close" variant="tertiary" @click="remove(i)" />
105
+ </div>
106
+ <FmSelect
107
+ v-if="addable.length"
108
+ class="w-1/2"
109
+ :model-value="null"
110
+ :items="addable"
111
+ :placeholder="t('order.notifyChannel.add')"
112
+ @update:model-value="add"
113
+ />
114
+ <span
115
+ v-if="channels.includes(PUSH_NOTIFICATION)"
116
+ class="fm-typo-en-body-sm-400 text-fm-color-neutral-gray-400"
117
+ >
118
+ {{ t('order.notifyChannel.pushNote') }}
119
+ </span>
120
+ </template>
121
+
122
+ <span
123
+ v-for="line in summary"
124
+ :key="line"
125
+ class="fm-typo-en-body-sm-400 text-fm-color-neutral-gray-400"
126
+ >
127
+ {{ line }}
128
+ </span>
129
+ </div>
130
+ </template>
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { canChooseNotifyChannels } from './notify-channel-country'
4
+
5
+ describe('canChooseNotifyChannels', () => {
6
+ it('allows MY, whatever the case or spacing', () => {
7
+ expect(canChooseNotifyChannels('MY')).toBe(true)
8
+ expect(canChooseNotifyChannels(' my ')).toBe(true)
9
+ })
10
+
11
+ it.each(['TH', 'SG', 'ID', 'VN', 'AE'])('hides the editor in %s', (country) => {
12
+ expect(canChooseNotifyChannels(country)).toBe(false)
13
+ })
14
+
15
+ it('hides the editor while the country is unknown', () => {
16
+ expect(canChooseNotifyChannels(undefined)).toBe(false)
17
+ expect(canChooseNotifyChannels(null)).toBe(false)
18
+ expect(canChooseNotifyChannels('')).toBe(false)
19
+ })
20
+ })
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Where a merchant may choose order notification channels (KDI-5013).
3
+ *
4
+ * Only MY, for now. Every channel except plain SMS goes to sms-backend's
5
+ * /multi-channel/send, which order-backend reaches over the internal ingress —
6
+ * and that URL (SMS_BACKEND_INTERNAL_URL) is configured for MY only. App Inbox
7
+ * itself is MY-only on sms-backend too. Anywhere else a chosen channel would be
8
+ * sent to the public host, which refuses it, and the customer would be told
9
+ * nothing. So the editor is hidden outside MY and those merchants keep plain SMS.
10
+ *
11
+ * An unknown country (still loading, or missing) counts as "not MY": the editor
12
+ * appearing a moment late is harmless, offering it in the wrong market is not.
13
+ *
14
+ * To open another country, set SMS_BACKEND_INTERNAL_URL for its order-backend in
15
+ * gitops-apps first, then add the code here.
16
+ */
17
+ const CHANNEL_COUNTRIES = new Set(['MY'])
18
+
19
+ export function canChooseNotifyChannels(country: string | null | undefined): boolean {
20
+ return CHANNEL_COUNTRIES.has((country ?? '').trim().toUpperCase())
21
+ }
@@ -10,6 +10,7 @@ import {
10
10
  OrderSettingUpdateDto
11
11
  } from '@entity'
12
12
  import RestaurantSelector from '../components/RestaurantSelector.vue'
13
+ import NotifyChannelEditor from '../components/NotifyChannelEditor.vue'
13
14
  import { useCoreStore, useI18n } from '@feedmepos/mf-common'
14
15
  import { useSnackbarFunctions } from '@/components/snackbar'
15
16
  import { useDialog } from '@feedmepos/ui-library'
@@ -424,6 +425,12 @@ async function saveAllowOverrideQueueCode(value: boolean) {
424
425
  label-placement="right"
425
426
  />
426
427
 
428
+ <NotifyChannelEditor
429
+ v-if="!queueSettings.disableSMS"
430
+ :model-value="queueSettings.channels"
431
+ @update:model-value="(v) => updateQueueSetting('channels', v)"
432
+ />
433
+
427
434
  <FmSwitch
428
435
  :model-value="queueSettings.printChecklist ?? false"
429
436
  @update:model-value="(v: boolean) => updateQueueSetting('printChecklist', v)"
@@ -7,10 +7,12 @@ import {
7
7
  F_ORDER_TYPE,
8
8
  F_SMS_ACTION,
9
9
  F_SMS_KEY,
10
+ type F_ORDER_NOTIFY_CHANNEL,
10
11
  type OrderSettingsDto,
11
12
  type OrderSmsSettingDto
12
13
  } from '@entity'
13
14
  import RestaurantSelector from '../components/RestaurantSelector.vue'
15
+ import NotifyChannelEditor from '../components/NotifyChannelEditor.vue'
14
16
 
15
17
  import { useCoreStore } from '@feedmepos/mf-common'
16
18
  import { Dinero } from '@feedmepos/core'
@@ -74,7 +76,9 @@ async function updateOrderSetting() {
74
76
  if (orderSettingDefault.value) {
75
77
  await OrderSettingApi.updateOrderSetting({
76
78
  ...orderSettingDefault.value,
77
- smsSettings: orderSmsSettings.value.filter((e: OrderSmsSettingDto) => e.enable)
79
+ smsSettings: orderSmsSettings.value.filter(
80
+ (e: OrderSmsSettingDto) => e.enable || !!e.message.raw || !!e.channels?.length
81
+ )
78
82
  })
79
83
  }
80
84
  processEditing(undefined)
@@ -83,7 +87,17 @@ async function updateOrderSetting() {
83
87
  showSuccess('SMS setting updated. ')
84
88
  }
85
89
 
90
+ // Only pickup has a code-owned body for a blank template (`SMS.processSms`).
91
+ // Anything else would send an empty message.
92
+ function needsMessage(sms: OrderSmsSettingDto) {
93
+ return sms.type !== F_ORDER_TYPE.Enum.PICKUP && !sms.message.raw?.trim()
94
+ }
95
+
86
96
  async function toggleEnable(enable: boolean, sms: OrderSmsSettingDto) {
97
+ if (enable && needsMessage(sms)) {
98
+ showError(t('order.notifyChannel.messageRequired'))
99
+ return
100
+ }
87
101
  const smsSetting = orderSmsSettings.value.map((_sms: OrderSmsSettingDto) => {
88
102
  if (_sms.action === sms.action && _sms.type === sms.type) {
89
103
  _sms.enable = enable
@@ -115,6 +129,11 @@ function processEditing(index?: number) {
115
129
 
116
130
  async function confirmTextField(index: number) {
117
131
  if (index !== undefined) {
132
+ const setting = orderSmsSettings.value[index]
133
+ if (setting.enable && needsMessage(setting)) {
134
+ showError(t('order.notifyChannel.messageRequired'))
135
+ return
136
+ }
118
137
  await updateOrderSetting()
119
138
  }
120
139
  }
@@ -126,6 +145,13 @@ function updateRawMessage(rawMessage: string, index: number) {
126
145
  orderSmsSettings.value = settings
127
146
  }
128
147
 
148
+ function updateChannels(channels: F_ORDER_NOTIFY_CHANNEL[], index: number) {
149
+ const settings = [...orderSmsSettings.value]
150
+ settings[index].channels = channels
151
+
152
+ orderSmsSettings.value = settings
153
+ }
154
+
129
155
  function updateMessageKey(messageKey: string, option: string, index: number) {
130
156
  const settings = [...orderSmsSettings.value]
131
157
  settings[index].message.keys[messageKey] = F_SMS_KEY.parse(option)
@@ -150,6 +176,12 @@ function initOrderSmsSettings() {
150
176
  type: F_ORDER_TYPE.Enum.PICKUP,
151
177
  action: F_SMS_ACTION.Enum.SERVE,
152
178
  message: { raw: '', keys: {} }
179
+ },
180
+ {
181
+ enable: false,
182
+ type: F_ORDER_TYPE.Enum.DELIVERY,
183
+ action: F_SMS_ACTION.Enum.ACCEPT,
184
+ message: { raw: '', keys: {} }
153
185
  }
154
186
  ]
155
187
  }
@@ -158,6 +190,8 @@ function convertTypeI18n(type: F_ORDER_TYPE | F_ORDER_DINE_IN_TYPE) {
158
190
  switch (type) {
159
191
  case F_ORDER_TYPE.enum.PICKUP:
160
192
  return t('order.pickup')
193
+ case F_ORDER_TYPE.enum.DELIVERY:
194
+ return t('order.delivery')
161
195
  default:
162
196
  return ''
163
197
  }
@@ -225,7 +259,13 @@ function convertActionI18n(action: F_SMS_ACTION) {
225
259
  />
226
260
  </div>
227
261
  </div>
262
+ <NotifyChannelEditor
263
+ class="mb-5"
264
+ :model-value="orderSmsSettings[i].channels"
265
+ @update:model-value="(channels) => updateChannels(channels, i)"
266
+ />
228
267
  </div>
268
+ <NotifyChannelEditor v-else class="mb-5" readonly :model-value="sms.channels" />
229
269
  <FmButton
230
270
  variant="secondary"
231
271
  text-color="primary"
@@ -0,0 +1,42 @@
1
+ import { OrderSettingUpdateDto } from '@entity'
2
+ import { describe, expect, it } from 'vitest'
3
+
4
+ // The server re-attaches a stored `channels` when a save omits it, so a DTO that
5
+ // strips the field does not fail loudly: the merchant's change is just reverted.
6
+ describe('OrderSettingUpdateDto keeps notification channels', () => {
7
+ it('on queueSettings', () => {
8
+ const parsed: any = OrderSettingUpdateDto.parse({
9
+ queueSettings: {
10
+ enableOrder: true,
11
+ autoCalledLimit: 0,
12
+ channels: ['PUSH_NOTIFICATION', 'APP_INBOX', 'SMS']
13
+ }
14
+ })
15
+
16
+ expect(parsed.queueSettings.channels).toEqual(['PUSH_NOTIFICATION', 'APP_INBOX', 'SMS'])
17
+ })
18
+
19
+ it('on smsSettings rows, including the delivery row', () => {
20
+ const parsed: any = OrderSettingUpdateDto.parse({
21
+ smsSettings: [
22
+ {
23
+ enable: true,
24
+ action: 'ACCEPT',
25
+ type: 'DELIVERY',
26
+ message: { raw: 'Accepted', keys: {} },
27
+ channels: ['APP_INBOX', 'SMS']
28
+ }
29
+ ]
30
+ })
31
+
32
+ expect(parsed.smsSettings[0].channels).toEqual(['APP_INBOX', 'SMS'])
33
+ })
34
+
35
+ it('keeps an explicit empty list, which is how a merchant goes back to SMS only', () => {
36
+ const parsed: any = OrderSettingUpdateDto.parse({
37
+ queueSettings: { enableOrder: true, autoCalledLimit: 0, channels: [] }
38
+ })
39
+
40
+ expect(parsed.queueSettings.channels).toEqual([])
41
+ })
42
+ })