@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.
- package/dist/{DisplaySystemView-DjFBEWoq.js → DisplaySystemView-Dx4As33V.js} +1 -1
- package/dist/{KioskDevicesView-RKMdLuB3.js → KioskDevicesView-Ziu9ekHK.js} +1 -1
- 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
- package/dist/{KioskSettingView-CzelEig6.js → KioskSettingView-BlLW-tDD.js} +3 -3
- package/dist/{KioskView-Bi5Kc4cS.js → KioskView-CzlaEjMR.js} +5 -5
- package/dist/{OrderSettingsView-BfxrjGAi.js → OrderSettingsView-B69It-yu.js} +16654 -16507
- package/dist/{app-Bg-n-Eug.js → app-C8jy3wlN.js} +324 -248
- package/dist/app.js +1 -1
- package/dist/common/sms/index.d.ts +14 -3
- package/dist/frontend/mf-order/src/app.d.ts +76 -0
- package/dist/frontend/mf-order/src/main.d.ts +76 -0
- package/dist/frontend/mf-order/src/stores/order-setting/index.d.ts +6 -0
- package/dist/frontend/mf-order/src/views/order-settings/components/NotifyChannelEditor.vue.d.ts +11 -0
- package/dist/frontend/mf-order/src/views/order-settings/components/notify-channel-country.d.ts +1 -0
- package/dist/frontend/mf-order/tsconfig.app.tsbuildinfo +1 -1
- package/dist/{index-C7Ug4fCi.js → index-DNvUwB59.js} +1 -1
- package/dist/{index-g_rocKrp.js → index-DiRlxqKH.js} +2 -2
- package/dist/{index-0eYg6XEb.js → index-VtBPFvME.js} +1 -1
- package/dist/{order.dto-MKeJAj6d.js → order.dto-CFw4X11T.js} +3708 -3631
- package/dist/package/entity/incoming-order/incoming-order-to-bill.dto.d.ts +10 -1
- package/dist/package/entity/index.d.ts +2 -0
- package/dist/package/entity/index.frontend.d.ts +2 -0
- package/dist/package/entity/order-platform/external/order/external-order.do.d.ts +7 -0
- package/dist/package/entity/order-setting/notification/notification.enum.d.ts +50 -0
- package/dist/package/entity/order-setting/notification/notification.route.d.ts +91 -0
- package/dist/package/entity/order-setting/order-setting.do.d.ts +10 -0
- package/dist/package/entity/order-setting/order-setting.dto.d.ts +20 -0
- package/dist/package/entity/order-setting/queue/queue.do.d.ts +17 -0
- package/dist/package/entity/order-setting/queue/queue.dto.d.ts +4 -0
- package/dist/package/entity/order-setting/sms/sms.do.d.ts +14 -0
- package/dist/package/entity/order-setting/sms/sms.dto.d.ts +4 -0
- package/dist/package/entity/restaurant/restaurant.dto.d.ts +10 -0
- package/package.json +1 -1
- package/src/locales/en-US.json +20 -1
- package/src/locales/ja-JP.json +20 -1
- package/src/locales/th-TH.json +20 -1
- package/src/locales/zh-CN.json +20 -1
- package/src/views/order-settings/OrderSettingsView.vue +1 -1
- package/src/views/order-settings/components/NotifyChannelEditor.vue +130 -0
- package/src/views/order-settings/components/notify-channel-country.spec.ts +20 -0
- package/src/views/order-settings/components/notify-channel-country.ts +21 -0
- package/src/views/order-settings/queue/QueueSetting.vue +7 -0
- package/src/views/order-settings/sms/SmsSetting.vue +41 -1
- package/src/views/order-settings/sms/notify-channels-dto.spec.ts +42 -0
package/src/locales/en-US.json
CHANGED
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
"dineInType": "Dine In Type",
|
|
217
217
|
"disabled": "Disabled",
|
|
218
218
|
"disableRemoteQueue": "Disable Remote Queue",
|
|
219
|
-
"disableSMS": "Disable
|
|
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",
|
package/src/locales/ja-JP.json
CHANGED
|
@@ -326,7 +326,7 @@
|
|
|
326
326
|
"duration": "時間",
|
|
327
327
|
"disableRemoteQueue": "リモート順番待ちを無効にする",
|
|
328
328
|
"allowOverrideQueueCode": "整理番号の上書きを許可",
|
|
329
|
-
"disableSMS": "
|
|
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": "開始",
|
package/src/locales/th-TH.json
CHANGED
|
@@ -429,7 +429,7 @@
|
|
|
429
429
|
"duration": "ระยะเวลา",
|
|
430
430
|
"disableRemoteQueue": "ปิดการรับคิวออนไลน์",
|
|
431
431
|
"allowOverrideQueueCode": "อนุญาตให้แก้ไขรหัสคิว",
|
|
432
|
-
"disableSMS": "
|
|
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": "เริ่มต้น",
|
package/src/locales/zh-CN.json
CHANGED
|
@@ -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.
|
|
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(
|
|
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
|
+
})
|