@tillhub/schemas 6.455.0 → 6.457.0
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/lib/v1/configurations/items/features.js +43 -0
- package/lib/v1/configurations/items/interfaces.js +225 -230
- package/lib/v1/configurations/items/receipts.js +5 -0
- package/lib/v1/configurations/items/reservations.js +8 -2
- package/package.json +5 -2
- package/test/configurations/configurations.v1.spec.js +197 -0
|
@@ -374,6 +374,49 @@ module.exports = oneOf({
|
|
|
374
374
|
}
|
|
375
375
|
})
|
|
376
376
|
}
|
|
377
|
+
}),
|
|
378
|
+
press_item_barcode: oneOf({
|
|
379
|
+
description: 'German press products carry their retail price and VAT class inside the EAN-13 barcode, so they can be sold without being maintained in the catalog.',
|
|
380
|
+
type: 'object',
|
|
381
|
+
additionalProperties: false,
|
|
382
|
+
properties: {
|
|
383
|
+
enabled: {
|
|
384
|
+
description: 'Specifies if the feature is available. When false, behaviour is identical to a client without the feature.',
|
|
385
|
+
type: 'boolean',
|
|
386
|
+
default: false
|
|
387
|
+
},
|
|
388
|
+
lookup_priority: {
|
|
389
|
+
description: 'Which side is asked first for a scanned code, i.e. which article carries the line. \'catalog_first\' searches the catalog and decodes only what it does not find, \'barcode_first\' decodes before searching. An unrecognised value falls back to the default.',
|
|
390
|
+
type: 'string',
|
|
391
|
+
enum: [
|
|
392
|
+
'catalog_first',
|
|
393
|
+
'barcode_first'
|
|
394
|
+
],
|
|
395
|
+
default: 'catalog_first'
|
|
396
|
+
},
|
|
397
|
+
price_source_on_catalog_hit: {
|
|
398
|
+
description: 'Which price is booked when a decodable press code also matches a catalog article. \'barcode\' charges the encoded amount and uses the article\'s own price only as the value it is compared against, \'catalog\' charges the article\'s own price and falls back to the encoded amount when it has none. A disagreement between the two is logged under either setting. The field is inert wherever no catalog hit occurs, which includes every scan under \'barcode_first\' - that combination is contradictory but not an error and must not be rejected. An unrecognised value falls back to the default.',
|
|
399
|
+
type: 'string',
|
|
400
|
+
enum: [
|
|
401
|
+
'barcode',
|
|
402
|
+
'catalog'
|
|
403
|
+
],
|
|
404
|
+
default: 'barcode'
|
|
405
|
+
},
|
|
406
|
+
products: {
|
|
407
|
+
description: 'Maps a press prefix to the uuid of the fallback article that supplies VAT and accounting information for that prefix. The supported prefixes are the keys, not a fixed set, so adding one needs no client release. Enabled with an empty map is equivalent to disabled.',
|
|
408
|
+
type: 'object',
|
|
409
|
+
additionalProperties: false,
|
|
410
|
+
patternProperties: {
|
|
411
|
+
'^[0-9]{3}$': {
|
|
412
|
+
description: 'Uuid of the fallback article for this press prefix.',
|
|
413
|
+
type: 'string',
|
|
414
|
+
format: 'uuid'
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
default: {}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
377
420
|
})
|
|
378
421
|
}
|
|
379
422
|
})
|
|
@@ -1,262 +1,257 @@
|
|
|
1
1
|
const { oneOf } = require('../../../helpers/payload-or-null')
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
{
|
|
4
|
+
type: ['object', 'null'],
|
|
5
|
+
additionalProperties: false,
|
|
6
|
+
properties: {
|
|
7
|
+
scanner_apis: {
|
|
9
8
|
type: 'object',
|
|
10
|
-
|
|
9
|
+
default: {},
|
|
11
10
|
properties: {
|
|
12
|
-
|
|
11
|
+
zebra: {
|
|
13
12
|
type: 'object',
|
|
13
|
+
default: {},
|
|
14
14
|
properties: {
|
|
15
|
-
|
|
16
|
-
type: '
|
|
17
|
-
|
|
18
|
-
enabled: {
|
|
19
|
-
type: 'boolean',
|
|
20
|
-
default: false
|
|
21
|
-
},
|
|
22
|
-
background_release_seconds: {
|
|
23
|
-
description: 'The time in seconds that the scanner will stay connected when the app is send to the background (for other apps to grab afterwards)',
|
|
24
|
-
type: 'integer',
|
|
25
|
-
example: 15,
|
|
26
|
-
minimum: 0,
|
|
27
|
-
maximum: 30, // to be on the safe side of Apple
|
|
28
|
-
default: 25
|
|
29
|
-
}
|
|
30
|
-
}
|
|
15
|
+
enabled: {
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
default: true
|
|
31
18
|
},
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
background_release_seconds: {
|
|
40
|
-
description: 'The time in seconds that the scanner will stay connected when the app is send to the background (for other apps to grab afterwards)',
|
|
41
|
-
type: 'integer',
|
|
42
|
-
example: 15,
|
|
43
|
-
minimum: 0,
|
|
44
|
-
maximum: 30, // to be on the safe side of Apple
|
|
45
|
-
default: 25
|
|
46
|
-
}
|
|
47
|
-
}
|
|
19
|
+
background_release_seconds: {
|
|
20
|
+
description: 'The time in seconds that the scanner will stay connected when the app is send to the background (for other apps to grab afterwards)',
|
|
21
|
+
type: 'integer',
|
|
22
|
+
example: 15,
|
|
23
|
+
minimum: 0,
|
|
24
|
+
maximum: 30, // to be on the safe side of Apple
|
|
25
|
+
default: 25
|
|
48
26
|
}
|
|
49
27
|
}
|
|
50
28
|
},
|
|
51
|
-
|
|
29
|
+
epson: {
|
|
52
30
|
type: 'object',
|
|
53
31
|
properties: {
|
|
54
|
-
|
|
55
|
-
type: '
|
|
56
|
-
|
|
57
|
-
enabled: {
|
|
58
|
-
type: 'boolean',
|
|
59
|
-
default: true
|
|
60
|
-
},
|
|
61
|
-
pre_connect: oneOf({
|
|
62
|
-
description: 'Try connecting to the printer without an actual printing task',
|
|
63
|
-
type: 'array',
|
|
64
|
-
items: {
|
|
65
|
-
description: 'Views or conditions where a printer connection is tried just in case',
|
|
66
|
-
type: 'string',
|
|
67
|
-
enum: [
|
|
68
|
-
'payment_view'
|
|
69
|
-
]
|
|
70
|
-
}
|
|
71
|
-
}),
|
|
72
|
-
allow_force_narrow: oneOf({
|
|
73
|
-
description: 'Potentially allow forcing 60mm-print on 80mm-printers via UI toggle ',
|
|
74
|
-
type: 'boolean',
|
|
75
|
-
default: false
|
|
76
|
-
})
|
|
77
|
-
}
|
|
32
|
+
enabled: {
|
|
33
|
+
type: 'boolean',
|
|
34
|
+
default: true
|
|
78
35
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
36
|
+
background_release_seconds: {
|
|
37
|
+
description: 'The time in seconds that the scanner will stay connected when the app is send to the background (for other apps to grab afterwards)',
|
|
38
|
+
type: 'integer',
|
|
39
|
+
example: 15,
|
|
40
|
+
minimum: 0,
|
|
41
|
+
maximum: 30, // to be on the safe side of Apple
|
|
42
|
+
default: 25
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
printer_apis: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
epson: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
enabled: {
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
default: true
|
|
57
|
+
},
|
|
58
|
+
pre_connect: oneOf({
|
|
59
|
+
description: 'Try connecting to the printer without an actual printing task',
|
|
60
|
+
type: 'array',
|
|
61
|
+
items: {
|
|
62
|
+
description: 'Views or conditions where a printer connection is tried just in case',
|
|
63
|
+
type: 'string',
|
|
64
|
+
enum: [
|
|
65
|
+
'payment_view'
|
|
66
|
+
]
|
|
86
67
|
}
|
|
68
|
+
}),
|
|
69
|
+
allow_force_narrow: oneOf({
|
|
70
|
+
description: 'Potentially allow forcing 60mm-print on 80mm-printers via UI toggle ',
|
|
71
|
+
type: 'boolean',
|
|
72
|
+
default: false
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
epson_fp_it: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
enabled: {
|
|
80
|
+
type: 'boolean',
|
|
81
|
+
default: false
|
|
87
82
|
}
|
|
88
83
|
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
terminal_apis: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {
|
|
90
|
+
adyen: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
enabled: {
|
|
94
|
+
type: 'boolean',
|
|
95
|
+
default: true
|
|
96
|
+
},
|
|
97
|
+
webserver: oneOf({
|
|
98
|
+
description: 'Defines which webserver implementation to use for secondary cashier messages (terminal -> POS)',
|
|
99
|
+
type: 'string',
|
|
100
|
+
enum: [
|
|
101
|
+
'none', // messages will not be visible on the POS but overall functionality is not affected
|
|
102
|
+
'criollo', // there have been issues (BSH)
|
|
103
|
+
'telegraph' // more stable than criollo
|
|
104
|
+
],
|
|
105
|
+
default: 'telegraph'
|
|
106
|
+
}),
|
|
107
|
+
inter_call_delay_seconds: oneOf({
|
|
108
|
+
type: 'number',
|
|
109
|
+
description: 'Artificial delay between a previous terminal response and the next terminal request when performed automatically (some devices require additional cooldowns to avoid \'busy\' states)',
|
|
110
|
+
minimum: 0.0,
|
|
111
|
+
maximum: 10.0,
|
|
112
|
+
multipleOf: 0.001,
|
|
113
|
+
default: 0.0
|
|
114
|
+
})
|
|
115
|
+
}
|
|
89
116
|
},
|
|
90
|
-
|
|
117
|
+
concardis: {
|
|
91
118
|
type: 'object',
|
|
92
119
|
properties: {
|
|
93
|
-
|
|
94
|
-
type: '
|
|
95
|
-
|
|
96
|
-
enabled: {
|
|
97
|
-
type: 'boolean',
|
|
98
|
-
default: true
|
|
99
|
-
},
|
|
100
|
-
webserver: oneOf({
|
|
101
|
-
description: 'Defines which webserver implementation to use for secondary cashier messages (terminal -> POS)',
|
|
102
|
-
type: 'string',
|
|
103
|
-
enum: [
|
|
104
|
-
'none', // messages will not be visible on the POS but overall functionality is not affected
|
|
105
|
-
'criollo', // there have been issues (BSH)
|
|
106
|
-
'telegraph' // more stable than criollo
|
|
107
|
-
],
|
|
108
|
-
default: 'telegraph'
|
|
109
|
-
}),
|
|
110
|
-
inter_call_delay_seconds: oneOf({
|
|
111
|
-
type: 'number',
|
|
112
|
-
description: 'Artificial delay between a previous terminal response and the next terminal request when performed automatically (some devices require additional cooldowns to avoid \'busy\' states)',
|
|
113
|
-
minimum: 0.0,
|
|
114
|
-
maximum: 10.0,
|
|
115
|
-
multipleOf: 0.001,
|
|
116
|
-
default: 0.0
|
|
117
|
-
})
|
|
118
|
-
}
|
|
120
|
+
enabled: {
|
|
121
|
+
type: 'boolean',
|
|
122
|
+
default: true
|
|
119
123
|
},
|
|
120
|
-
|
|
121
|
-
type: '
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
124
|
+
inter_call_delay_seconds: oneOf({
|
|
125
|
+
type: 'number',
|
|
126
|
+
description: 'Artificial delay between a previous terminal response and the next terminal request when performed automatically (some devices require additional cooldowns to avoid \'busy\' states)',
|
|
127
|
+
minimum: 0.0,
|
|
128
|
+
maximum: 10.0,
|
|
129
|
+
multipleOf: 0.001,
|
|
130
|
+
default: 0.0
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
opi: {
|
|
135
|
+
type: 'object',
|
|
136
|
+
properties: {
|
|
137
|
+
enabled: {
|
|
138
|
+
type: 'boolean',
|
|
139
|
+
default: true
|
|
136
140
|
},
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
141
|
+
swiss: oneOf({
|
|
142
|
+
description: 'Defines if swiss-specific handling of the OPI protocol will be used',
|
|
143
|
+
type: 'boolean',
|
|
144
|
+
default: false
|
|
145
|
+
}),
|
|
146
|
+
omit_amount_on_reversals: oneOf({
|
|
147
|
+
description: 'Defines if amount can be omitted in reversals',
|
|
148
|
+
type: 'boolean',
|
|
149
|
+
default: false
|
|
150
|
+
}),
|
|
151
|
+
inter_call_delay_seconds: oneOf({
|
|
152
|
+
type: 'number',
|
|
153
|
+
description: 'Artificial delay between a previous terminal response and the next terminal request when performed automatically (some devices require additional cooldowns to avoid \'busy\' states)',
|
|
154
|
+
minimum: 0.0,
|
|
155
|
+
maximum: 10.0,
|
|
156
|
+
multipleOf: 0.001,
|
|
157
|
+
default: 1.0 // defaults to 3.0 if Swiss
|
|
158
|
+
}),
|
|
159
|
+
language_code: oneOf({
|
|
160
|
+
type: 'string',
|
|
161
|
+
description: 'Language code send in card service requests. ISO 639-1:2002.',
|
|
162
|
+
example: 'de',
|
|
163
|
+
minLength: 2,
|
|
164
|
+
maxLength: 2,
|
|
165
|
+
default: null // fallback is defined by the current POS language
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
sumup: {
|
|
170
|
+
type: 'object',
|
|
171
|
+
properties: {
|
|
172
|
+
enabled: {
|
|
173
|
+
type: 'boolean',
|
|
174
|
+
default: true
|
|
171
175
|
},
|
|
172
|
-
|
|
173
|
-
type: '
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
176
|
+
inter_call_delay_seconds: oneOf({
|
|
177
|
+
type: 'number',
|
|
178
|
+
description: 'Artificial delay between a previous terminal response and the next terminal request when performed automatically (some devices require additional cooldowns to avoid \'busy\' states)',
|
|
179
|
+
minimum: 0.0,
|
|
180
|
+
maximum: 10.0,
|
|
181
|
+
multipleOf: 0.001,
|
|
182
|
+
default: 0.0
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
tim: {
|
|
187
|
+
type: 'object',
|
|
188
|
+
properties: {
|
|
189
|
+
enabled: {
|
|
190
|
+
type: 'boolean',
|
|
191
|
+
default: true
|
|
188
192
|
},
|
|
189
|
-
|
|
190
|
-
type: '
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
193
|
+
inter_call_delay_seconds: oneOf({
|
|
194
|
+
type: 'number',
|
|
195
|
+
description: 'Artificial delay between a previous terminal response and the next terminal request when performed automatically (some devices require additional cooldowns to avoid \'busy\' states)',
|
|
196
|
+
minimum: 0.0,
|
|
197
|
+
maximum: 10.0,
|
|
198
|
+
multipleOf: 0.001,
|
|
199
|
+
default: 0.0
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
spos: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
enabled: {
|
|
207
|
+
type: 'boolean',
|
|
208
|
+
default: true
|
|
205
209
|
},
|
|
206
|
-
|
|
207
|
-
type: '
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
210
|
+
inter_call_delay_seconds: oneOf({
|
|
211
|
+
type: 'number',
|
|
212
|
+
description: 'Artificial delay between a previous terminal response and the next terminal request when performed automatically (some devices require additional cooldowns to avoid \'busy\' states)',
|
|
213
|
+
minimum: 0.0,
|
|
214
|
+
maximum: 10.0,
|
|
215
|
+
multipleOf: 0.001,
|
|
216
|
+
default: 0.0
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
softpay: {
|
|
221
|
+
type: 'object',
|
|
222
|
+
properties: {
|
|
223
|
+
enabled: {
|
|
224
|
+
type: 'boolean',
|
|
225
|
+
default: true
|
|
222
226
|
},
|
|
223
|
-
|
|
224
|
-
type: '
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
227
|
+
inter_call_delay_seconds: oneOf({
|
|
228
|
+
type: 'number',
|
|
229
|
+
description: 'Artificial delay between a previous terminal response and the next terminal request when performed automatically (some devices require additional cooldowns to avoid \'busy\' states)',
|
|
230
|
+
minimum: 0.0,
|
|
231
|
+
maximum: 10.0,
|
|
232
|
+
multipleOf: 0.001,
|
|
233
|
+
default: 0.0
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
ZVT: { // capitalized for historical reasons
|
|
238
|
+
type: 'object',
|
|
239
|
+
properties: {
|
|
240
|
+
enabled: {
|
|
241
|
+
type: 'boolean',
|
|
242
|
+
default: true
|
|
239
243
|
},
|
|
240
|
-
|
|
241
|
-
type: '
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
type: 'number',
|
|
249
|
-
description: 'Artificial delay between a previous terminal response and the next terminal request when performed automatically (some devices require additional cooldowns to avoid \'busy\' states)',
|
|
250
|
-
minimum: 0.0,
|
|
251
|
-
maximum: 10.0,
|
|
252
|
-
multipleOf: 0.001,
|
|
253
|
-
default: 0.0
|
|
254
|
-
})
|
|
255
|
-
}
|
|
256
|
-
}
|
|
244
|
+
inter_call_delay_seconds: oneOf({
|
|
245
|
+
type: 'number',
|
|
246
|
+
description: 'Artificial delay between a previous terminal response and the next terminal request when performed automatically (some devices require additional cooldowns to avoid \'busy\' states)',
|
|
247
|
+
minimum: 0.0,
|
|
248
|
+
maximum: 10.0,
|
|
249
|
+
multipleOf: 0.001,
|
|
250
|
+
default: 0.0
|
|
251
|
+
})
|
|
257
252
|
}
|
|
258
253
|
}
|
|
259
254
|
}
|
|
260
255
|
}
|
|
261
|
-
|
|
256
|
+
}
|
|
262
257
|
}
|
|
@@ -148,6 +148,11 @@ const cartReceipt = {
|
|
|
148
148
|
type: 'boolean',
|
|
149
149
|
default: true
|
|
150
150
|
}),
|
|
151
|
+
print_barcode: oneOf({
|
|
152
|
+
description: 'Defines if the scanned barcode of a press item is printed on the specific receipt, as its own line below the product name. It governs press lines booked against a titled article, whose own name already identifies them. A line booked against one of the generic press fallback articles configured in features.press_item_barcode.products prints its barcode regardless of this flag, because the barcode is the only thing that tells two such lines apart. Defaults to true like print_serial_number, and is inert until that prefix map is populated. Per-receipt-type defaults are applied by the client, since this object is shared by every cart receipt type.',
|
|
153
|
+
type: 'boolean',
|
|
154
|
+
default: true
|
|
155
|
+
}),
|
|
151
156
|
print_service_question_answers: oneOf({
|
|
152
157
|
description: 'States if the product customization answers and questions will be printed',
|
|
153
158
|
type: 'boolean',
|
|
@@ -162,12 +162,12 @@ module.exports = oneOf({
|
|
|
162
162
|
}
|
|
163
163
|
},
|
|
164
164
|
opening_hours_exceptions: {
|
|
165
|
-
description: 'Additive Open or Closed exceptions over a date range, optionally limited to selected weekdays. Closed is full day only. 0 = Monday … 6 = Sunday.',
|
|
165
|
+
description: 'Additive Open or Closed exceptions over a date range, optionally limited to selected weekdays. Closed is full day only. 0 = Monday … 6 = Sunday. `from` and `to` are required keys on every item: omit neither. Open all-day is `from: null` and `to: null` (both present). Open specific hours requires both as HH:mm. Closed requires `from: null` and `to: null`.',
|
|
166
166
|
type: 'array',
|
|
167
167
|
items: {
|
|
168
168
|
type: 'object',
|
|
169
169
|
additionalProperties: false,
|
|
170
|
-
required: ['type', 'start_date', 'end_date'],
|
|
170
|
+
required: ['type', 'start_date', 'end_date', 'from', 'to'],
|
|
171
171
|
properties: {
|
|
172
172
|
type: {
|
|
173
173
|
type: 'string',
|
|
@@ -190,12 +190,14 @@ module.exports = oneOf({
|
|
|
190
190
|
}
|
|
191
191
|
},
|
|
192
192
|
from: {
|
|
193
|
+
description: 'Required. Open all-day: must be null together with `to` (the key must still be present). Open specific hours: HH:mm start, with `to` also set. Closed: always null.',
|
|
193
194
|
oneOf: [
|
|
194
195
|
{ type: 'null' },
|
|
195
196
|
{ type: 'string', pattern: '^([01]\\d|2[0-3]):[0-5]\\d$' }
|
|
196
197
|
]
|
|
197
198
|
},
|
|
198
199
|
to: {
|
|
200
|
+
description: 'Required. Open all-day: must be null together with `from` (the key must still be present). Open specific hours: HH:mm end, with `from` also set and earlier than `to`. Closed: always null. Omitting this key is invalid and is not treated as all-day.',
|
|
199
201
|
oneOf: [
|
|
200
202
|
{ type: 'null' },
|
|
201
203
|
{ type: 'string', pattern: '^([01]\\d|2[0-3]):[0-5]\\d$' }
|
|
@@ -226,6 +228,7 @@ module.exports = oneOf({
|
|
|
226
228
|
required: ['type']
|
|
227
229
|
},
|
|
228
230
|
then: {
|
|
231
|
+
required: ['from', 'to'],
|
|
229
232
|
properties: {
|
|
230
233
|
from: { type: 'null' },
|
|
231
234
|
to: { type: 'null' },
|
|
@@ -244,12 +247,15 @@ module.exports = oneOf({
|
|
|
244
247
|
then: {
|
|
245
248
|
oneOf: [
|
|
246
249
|
{
|
|
250
|
+
description: 'Open all day: from and to must both be present and null (00:00–23:59). A break may sit strictly inside that window. Omitting either key is not all-day.',
|
|
251
|
+
required: ['from', 'to'],
|
|
247
252
|
properties: {
|
|
248
253
|
from: { type: 'null' },
|
|
249
254
|
to: { type: 'null' }
|
|
250
255
|
}
|
|
251
256
|
},
|
|
252
257
|
{
|
|
258
|
+
description: 'Open specific hours: from and to must both be present as HH:mm.',
|
|
253
259
|
properties: {
|
|
254
260
|
from: { type: 'string' },
|
|
255
261
|
to: { type: 'string' }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tillhub/schemas",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.457.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "All Tillhub API OpenAPI 3 compatible JSON Schemas.",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"clean": "rm -rf coverage coverage.lcov .nyc_output",
|
|
18
18
|
"deploy:static": "firebase deploy --only=hosting",
|
|
19
19
|
"compile": "node compile",
|
|
20
|
-
"new:schema": "hygen schema with-prompt"
|
|
20
|
+
"new:schema": "hygen schema with-prompt",
|
|
21
|
+
"commitlint": "commitlint --from origin/master --to HEAD --verbose"
|
|
21
22
|
},
|
|
22
23
|
"repository": {
|
|
23
24
|
"type": "git",
|
|
@@ -51,6 +52,8 @@
|
|
|
51
52
|
],
|
|
52
53
|
"homepage": "https://github.com/tillhub/schemas#readme",
|
|
53
54
|
"devDependencies": {
|
|
55
|
+
"@commitlint/cli": "^19.8.1",
|
|
56
|
+
"@commitlint/config-conventional": "^19.8.1",
|
|
54
57
|
"@semantic-release/changelog": "^6.0.3",
|
|
55
58
|
"@semantic-release/exec": "^7.1.0",
|
|
56
59
|
"@semantic-release/git": "^10.0.1",
|
|
@@ -47,6 +47,16 @@ test('Configurations: opening_hours_exceptions accepts Open all-day, Open hours,
|
|
|
47
47
|
to: null,
|
|
48
48
|
reason: 'Sunday brunch all day'
|
|
49
49
|
},
|
|
50
|
+
{
|
|
51
|
+
type: 'open',
|
|
52
|
+
start_date: '2026-12-21',
|
|
53
|
+
end_date: '2026-12-21',
|
|
54
|
+
from: null,
|
|
55
|
+
to: null,
|
|
56
|
+
break_from: '15:00',
|
|
57
|
+
break_to: '16:00',
|
|
58
|
+
reason: 'All day with afternoon break'
|
|
59
|
+
},
|
|
50
60
|
{
|
|
51
61
|
type: 'open',
|
|
52
62
|
start_date: '2026-07-02',
|
|
@@ -93,6 +103,40 @@ test('Configurations: opening_hours_exceptions rejects Closed with specific hour
|
|
|
93
103
|
t.end()
|
|
94
104
|
})
|
|
95
105
|
|
|
106
|
+
test('Configurations: opening_hours_exceptions rejects omitted Open from/to keys', function (t) {
|
|
107
|
+
t.plan(1)
|
|
108
|
+
const valid = validateUpdatePayload({
|
|
109
|
+
reservations: {
|
|
110
|
+
opening_hours_exceptions: [
|
|
111
|
+
{
|
|
112
|
+
type: 'open',
|
|
113
|
+
start_date: '2026-12-20',
|
|
114
|
+
end_date: '2026-12-20'
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
t.notOk(valid, 'Open from/to keys are required; omitted keys are not all-day')
|
|
120
|
+
t.end()
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('Configurations: opening_hours_exceptions rejects omitted Closed from/to keys', function (t) {
|
|
124
|
+
t.plan(1)
|
|
125
|
+
const valid = validateUpdatePayload({
|
|
126
|
+
reservations: {
|
|
127
|
+
opening_hours_exceptions: [
|
|
128
|
+
{
|
|
129
|
+
type: 'closed',
|
|
130
|
+
start_date: '2026-01-01',
|
|
131
|
+
end_date: '2026-01-01'
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
t.notOk(valid, 'Closed from/to keys are required and must be null')
|
|
137
|
+
t.end()
|
|
138
|
+
})
|
|
139
|
+
|
|
96
140
|
test('Configurations: opening_hours_exceptions rejects mixed Open from/to', function (t) {
|
|
97
141
|
t.plan(1)
|
|
98
142
|
const valid = validateUpdatePayload({
|
|
@@ -141,3 +185,156 @@ test('Configurations: payloads without opening_hours_exceptions remain valid', f
|
|
|
141
185
|
t.ok(valid, ajv.errorsText(validateUpdatePayload.errors))
|
|
142
186
|
t.end()
|
|
143
187
|
})
|
|
188
|
+
|
|
189
|
+
test('Configurations: interfaces seeds scanner_apis.zebra.enabled when scanner_apis is absent', function (t) {
|
|
190
|
+
t.plan(2)
|
|
191
|
+
const ajvWithDefaults = new Ajv({ useDefaults: true })
|
|
192
|
+
const validateCreatePayload = ajvWithDefaults.compile(require('../../lib/v1/configurations').create.request)
|
|
193
|
+
const payload = {
|
|
194
|
+
name: 'Default Configurations',
|
|
195
|
+
owner: 'self',
|
|
196
|
+
interfaces: {
|
|
197
|
+
terminal_apis: {
|
|
198
|
+
opi: { enabled: true },
|
|
199
|
+
tim: { enabled: true },
|
|
200
|
+
adyen: { enabled: true },
|
|
201
|
+
sumup: { enabled: true },
|
|
202
|
+
concardis: { enabled: true }
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const valid = validateCreatePayload(payload)
|
|
207
|
+
|
|
208
|
+
t.ok(valid, ajvWithDefaults.errorsText(validateCreatePayload.errors))
|
|
209
|
+
t.deepEquals(payload.interfaces.scanner_apis, {
|
|
210
|
+
zebra: {
|
|
211
|
+
enabled: true,
|
|
212
|
+
background_release_seconds: 25
|
|
213
|
+
}
|
|
214
|
+
}, 'zebra is enabled by default')
|
|
215
|
+
t.end()
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test('Configurations: interfaces keeps an explicitly disabled zebra scanner', function (t) {
|
|
219
|
+
t.plan(2)
|
|
220
|
+
const ajvWithDefaults = new Ajv({ useDefaults: true })
|
|
221
|
+
const validateCreatePayload = ajvWithDefaults.compile(require('../../lib/v1/configurations').create.request)
|
|
222
|
+
const payload = {
|
|
223
|
+
name: 'Default Configurations',
|
|
224
|
+
owner: 'self',
|
|
225
|
+
interfaces: {
|
|
226
|
+
scanner_apis: {
|
|
227
|
+
zebra: { enabled: false }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const valid = validateCreatePayload(payload)
|
|
232
|
+
|
|
233
|
+
t.ok(valid, ajvWithDefaults.errorsText(validateCreatePayload.errors))
|
|
234
|
+
t.notOk(payload.interfaces.scanner_apis.zebra.enabled, 'explicit value wins over the default')
|
|
235
|
+
t.end()
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test('Configurations: interfaces still accepts null', function (t) {
|
|
239
|
+
t.plan(2)
|
|
240
|
+
const validCreate = ajv.compile(require('../../lib/v1/configurations').create.request)({
|
|
241
|
+
name: 'Default Configurations',
|
|
242
|
+
owner: 'self',
|
|
243
|
+
interfaces: null
|
|
244
|
+
})
|
|
245
|
+
t.ok(validCreate, 'null interfaces is valid on create')
|
|
246
|
+
|
|
247
|
+
const validUpdate = validateUpdatePayload({ interfaces: null })
|
|
248
|
+
t.ok(validUpdate, ajv.errorsText(validateUpdatePayload.errors))
|
|
249
|
+
t.end()
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
const pressUuid = '5b1b1b1b-1b1b-4b1b-8b1b-1b1b1b1b1b1b'
|
|
253
|
+
|
|
254
|
+
test('Configurations: press_item_barcode accepts the documented contract', function (t) {
|
|
255
|
+
t.plan(1)
|
|
256
|
+
const valid = validateUpdatePayload({
|
|
257
|
+
features: {
|
|
258
|
+
press_item_barcode: {
|
|
259
|
+
enabled: false,
|
|
260
|
+
lookup_priority: 'catalog_first',
|
|
261
|
+
price_source_on_catalog_hit: 'barcode',
|
|
262
|
+
products: {
|
|
263
|
+
414: pressUuid,
|
|
264
|
+
419: pressUuid,
|
|
265
|
+
434: pressUuid,
|
|
266
|
+
439: pressUuid
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
})
|
|
271
|
+
t.ok(valid, ajv.errorsText(validateUpdatePayload.errors))
|
|
272
|
+
t.end()
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test('Configurations: press_item_barcode accepts a prefix the client does not know', function (t) {
|
|
276
|
+
t.plan(1)
|
|
277
|
+
const valid = validateUpdatePayload({
|
|
278
|
+
features: {
|
|
279
|
+
press_item_barcode: { enabled: true, products: { 444: pressUuid } }
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
t.ok(valid, 'the supported prefixes are the map keys, so a new one needs no schema change')
|
|
283
|
+
t.end()
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
test('Configurations: press_item_barcode accepts barcode_first with a catalog-hit price source', function (t) {
|
|
287
|
+
t.plan(1)
|
|
288
|
+
const valid = validateUpdatePayload({
|
|
289
|
+
features: {
|
|
290
|
+
press_item_barcode: {
|
|
291
|
+
lookup_priority: 'barcode_first',
|
|
292
|
+
price_source_on_catalog_hit: 'catalog'
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
t.ok(valid, 'the combination is inert rather than invalid and must not be rejected')
|
|
297
|
+
t.end()
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
test('Configurations: press_item_barcode accepts an empty products map', function (t) {
|
|
301
|
+
t.plan(1)
|
|
302
|
+
const valid = validateUpdatePayload({
|
|
303
|
+
features: {
|
|
304
|
+
press_item_barcode: { enabled: true, products: {} }
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
t.ok(valid, 'equivalent to disabled, which the client resolves rather than the schema')
|
|
308
|
+
t.end()
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
test('Configurations: press_item_barcode rejects a prefix that is not exactly three digits', function (t) {
|
|
312
|
+
t.plan(2)
|
|
313
|
+
t.notOk(validateUpdatePayload({
|
|
314
|
+
features: { press_item_barcode: { products: { 41: pressUuid } } }
|
|
315
|
+
}), 'two digits')
|
|
316
|
+
t.notOk(validateUpdatePayload({
|
|
317
|
+
features: { press_item_barcode: { products: { 4141: pressUuid } } }
|
|
318
|
+
}), 'four digits')
|
|
319
|
+
t.end()
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
test('Configurations: press_item_barcode rejects a product value that is not a uuid', function (t) {
|
|
323
|
+
t.plan(1)
|
|
324
|
+
const valid = validateUpdatePayload({
|
|
325
|
+
features: { press_item_barcode: { products: { 414: 'presse-19' } } }
|
|
326
|
+
})
|
|
327
|
+
t.notOk(valid, 'the map holds article uuids, not names or numbers')
|
|
328
|
+
t.end()
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
test('Configurations: press_item_barcode rejects unknown enum values', function (t) {
|
|
332
|
+
t.plan(2)
|
|
333
|
+
t.notOk(validateUpdatePayload({
|
|
334
|
+
features: { press_item_barcode: { lookup_priority: 'catalogue_first' } }
|
|
335
|
+
}), 'lookup_priority')
|
|
336
|
+
t.notOk(validateUpdatePayload({
|
|
337
|
+
features: { press_item_barcode: { price_source_on_catalog_hit: 'ean' } }
|
|
338
|
+
}), 'price_source_on_catalog_hit')
|
|
339
|
+
t.end()
|
|
340
|
+
})
|