@alizadefaig/pos-server 1.2.26 → 1.2.28
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/app/core/Context.js +170 -0
- package/app/core/auth/index.js +133 -0
- package/app/core/auth/license.js +45 -0
- package/app/core/auth/posMode.js +260 -0
- package/app/core/auth/sanctum.js +209 -0
- package/app/core/auth/scope.js +231 -0
- package/app/core/emitter.js +67 -0
- package/app/core/errors.js +18 -0
- package/app/core/guard.js +54 -0
- package/app/core/idempotency.js +196 -0
- package/app/core/index.js +374 -0
- package/app/core/models/Order.js +176 -0
- package/app/core/models/OrderProduct.js +249 -0
- package/app/core/repositories/EventRepository.js +51 -0
- package/app/core/repositories/OrderProductRepository.js +225 -0
- package/app/core/repositories/OrderRepository.js +274 -0
- package/app/core/repositories/OutboxRepository.js +52 -0
- package/app/core/repositories/PaymentRepository.js +43 -0
- package/app/core/repositories/PrinterRepository.js +179 -0
- package/app/core/repositories/ProductRelations.js +148 -0
- package/app/core/repositories/SnapshotRepository.js +197 -0
- package/app/core/resources/HallResource.js +54 -0
- package/app/core/resources/OrderResource.js +177 -0
- package/app/core/resources/ProductResource.js +186 -0
- package/app/core/resources/ShiftResource.js +42 -0
- package/app/core/resources/TeamResource.js +117 -0
- package/app/core/resources/casts.js +55 -0
- package/app/core/services/AuthService.js +468 -0
- package/app/core/services/InitService.js +279 -0
- package/app/core/services/KdsService.js +639 -0
- package/app/core/services/OrderProductService.js +309 -0
- package/app/core/services/OrderService.js +911 -0
- package/app/core/services/PrintService.js +715 -0
- package/app/core/services/PrinterAdminService.js +324 -0
- package/app/core/services/ReportService.js +581 -0
- package/app/core/services/ShiftService.js +199 -0
- package/app/core/services/StorageService.js +425 -0
- package/app/core/services/TelegramService.js +162 -0
- package/app/core/support/db.js +48 -0
- package/app/core/support/ids.js +9 -0
- package/app/core/support/imageData.js +135 -0
- package/app/core/support/images.js +92 -0
- package/app/core/support/lanHandoff.js +105 -0
- package/app/core/support/lang.js +98 -0
- package/app/core/support/lock.js +63 -0
- package/app/core/support/num.js +51 -0
- package/app/core/support/php.js +125 -0
- package/app/core/support/pin.js +34 -0
- package/app/core/support/receipts.js +278 -0
- package/app/core/support/rows.js +38 -0
- package/app/core/support/str.js +31 -0
- package/app/core/support/teamSettings.js +77 -0
- package/app/core/support/telegram.js +253 -0
- package/app/core/support/telegramReport.js +143 -0
- package/app/core/support/time.js +58 -0
- package/app/core/support/tz.js +80 -0
- package/app/core/support/unlock.js +113 -0
- package/app/core/timers.js +151 -0
- package/app/db/Database.js +197 -1
- package/app/db/Migrator.js +45 -0
- package/app/db/driver.js +248 -0
- package/app/db/migrations/001_snapshot.sql +214 -0
- package/app/db/migrations/002_orders.sql +190 -0
- package/app/db/migrations/003_idempotency.sql +18 -0
- package/app/db/migrations/004_shifts.sql +21 -0
- package/app/db/migrations/005_printers.sql +43 -0
- package/app/db/migrations/006_sync.sql +136 -0
- package/app/db/migrations/007_printer_mac.sql +4 -0
- package/app/db/migrations/008_kds.sql +94 -0
- package/app/db/migrations/009_product_categories.sql +20 -0
- package/app/db/migrations/010_printers_sync.sql +43 -0
- package/app/db/migrations/011_team_logos.sql +13 -0
- package/app/db/migrations/012_category_workshop.sql +4 -0
- package/app/monitor.js +185 -0
- package/app/printers/address.js +348 -0
- package/app/proxy.js +11 -3
- package/app/relay/masters.js +173 -0
- package/app/security.js +218 -0
- package/app/socket/auth.js +160 -0
- package/app/socket/request.js +87 -0
- package/app/socket/server.js +121 -19
- package/app/sync/bridge.js +162 -0
- package/app/sync/cloud.js +120 -0
- package/app/sync/config.js +185 -0
- package/app/sync/discovery.js +546 -0
- package/app/sync/index.js +1644 -0
- package/app/sync/kv.js +26 -0
- package/app/sync/lan.js +139 -0
- package/app/sync/lanDevices.js +177 -0
- package/app/sync/lanFront.js +431 -0
- package/app/sync/mirror.js +236 -0
- package/app/sync/push.js +324 -0
- package/app/sync/reconcile.js +161 -0
- package/app/sync/signal.js +202 -0
- package/app/sync/snapshot.js +189 -0
- package/app/sync/store.js +234 -0
- package/app/tcp.js +11 -2
- package/package.json +6 -3
- package/routes/common/api/auth.js +38 -0
- package/routes/common/api/kds.js +37 -0
- package/routes/common/api/orderProducts.js +81 -0
- package/routes/common/api/orders.js +176 -0
- package/routes/common/api/printersCore.js +35 -0
- package/routes/common/api/reports.js +29 -0
- package/routes/common/socket.js +15 -35
- package/routes/common/tax.js +29 -7
- package/routes/desktop/api/offline.js +146 -0
- package/routes/desktop/api/printers.js +288 -5
- package/routes/desktop/local.js +130 -0
- package/routes/server/api/login.js +37 -0
- package/routes/server/api/platform.js +31 -0
- package/routes/server/api/shifts.js +25 -0
- package/routes/server/master.js +24 -0
- package/server.js +72 -3
- package/utils.js +6 -0
- package/tests/usbDiagnose.js +0 -59
- package/tests/usbPrinterTest.js +0 -20
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
const {teamResource, terminalResource, teamUserResource} = require('../resources/TeamResource');
|
|
2
|
+
const {hallResource} = require('../resources/HallResource');
|
|
3
|
+
const {productResource, categoryResource, extraProductResource} = require('../resources/ProductResource');
|
|
4
|
+
const {nullableBool, modelArray, takeText, jsonText} = require('../resources/casts');
|
|
5
|
+
const {transArray, FALLBACK_LOCALE} = require('../support/lang');
|
|
6
|
+
const {imageUrls} = require('../support/images');
|
|
7
|
+
const {unique, groupBy, indexBy, alive, whereIn} = require('../support/rows');
|
|
8
|
+
const ProductRelations = require('../repositories/ProductRelations');
|
|
9
|
+
const posMode = require('../auth/posMode');
|
|
10
|
+
const lanHandoff = require('../support/lanHandoff');
|
|
11
|
+
const pin = require('../support/pin');
|
|
12
|
+
const {HttpError} = require('../errors');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Облачные справочники POS: порт AuthController::init и соседних чтений (ProductService,
|
|
16
|
+
* HallService, UserService, CategoryService, getExtraProducts). Смены — ShiftService.
|
|
17
|
+
*
|
|
18
|
+
* Eloquent грузит связи отдельными запросами `WHERE … IN (…)` — здесь то же самое, без N+1:
|
|
19
|
+
* мутатор цеха товара в PHP читает категорию по одной на товар, здесь — одним запросом.
|
|
20
|
+
*
|
|
21
|
+
* Схема — Laravel (Postgres). Мягко удалённые строки отсекаются только под Postgres: в локальном
|
|
22
|
+
* снапшоте колонки deleted_at у части таблиц нет, как и удалённых строк.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const TOP_PRODUCT_TYPES = ['dish', 'product', 'timer'];
|
|
26
|
+
|
|
27
|
+
class InitService {
|
|
28
|
+
/**
|
|
29
|
+
* @param {{db: object, context: import('../Context'), orders: object}} services
|
|
30
|
+
*/
|
|
31
|
+
constructor({db, context, orders}) {
|
|
32
|
+
this.db = db;
|
|
33
|
+
this.context = context;
|
|
34
|
+
this.orders = orders;
|
|
35
|
+
this.pgsql = db.dialect === 'pgsql';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
alive(alias = null) {
|
|
39
|
+
return alive(this.db, alias);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Локаль ответа — как у PlatformApiMiddleware: язык команды, иначе app.locale. */
|
|
43
|
+
get locale() {
|
|
44
|
+
return this.context.localeCode || process.env.APP_LOCALE || FALLBACK_LOCALE;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
whereIn(sql, ids, params = []) {
|
|
48
|
+
return whereIn(this.db, sql, ids, params);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ------------------------------------------------------------ app.init
|
|
52
|
+
|
|
53
|
+
async init() {
|
|
54
|
+
await this.context.load();
|
|
55
|
+
const team = this.context.getTeam();
|
|
56
|
+
const terminal = this.context.getTerminal();
|
|
57
|
+
if (!team || !terminal) {
|
|
58
|
+
// В PHP getTerminal()->load(…) на null — Error и 500.
|
|
59
|
+
throw new HttpError(500, {message: 'Server Error'});
|
|
60
|
+
}
|
|
61
|
+
const teamId = this.context.teamId;
|
|
62
|
+
const companyId = this.context.companyId;
|
|
63
|
+
|
|
64
|
+
const [header, halls, products, users, orders, categories, units, workshops, paymentMethods, orderSources] = await Promise.all([
|
|
65
|
+
this.header(team, terminal),
|
|
66
|
+
this.halls(companyId),
|
|
67
|
+
this.products(teamId),
|
|
68
|
+
this.users(teamId, team),
|
|
69
|
+
this.orders.getOpenOrders(companyId),
|
|
70
|
+
this.categories(teamId),
|
|
71
|
+
this.units(),
|
|
72
|
+
this.workshops(teamId),
|
|
73
|
+
this.paymentMethods(teamId),
|
|
74
|
+
this.orderSources(teamId),
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
const result = {
|
|
78
|
+
team: header.team,
|
|
79
|
+
terminal: header.terminal,
|
|
80
|
+
halls,
|
|
81
|
+
products,
|
|
82
|
+
users,
|
|
83
|
+
orders,
|
|
84
|
+
categories,
|
|
85
|
+
units,
|
|
86
|
+
workshops,
|
|
87
|
+
payment_methods: paymentMethods,
|
|
88
|
+
order_sources: orderSources,
|
|
89
|
+
};
|
|
90
|
+
if (pin.hashed()) {
|
|
91
|
+
result.pin_salt = pin.salt(teamId);
|
|
92
|
+
}
|
|
93
|
+
if (lanHandoff.enabled()) {
|
|
94
|
+
result.lan_master = await lanHandoff.masterFor(this.db, companyId);
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** TeamResource + TerminalResource (с company и workshops). */
|
|
100
|
+
async header(team, terminal) {
|
|
101
|
+
const [currency, language, company, terminalWorkshops, urls, mode] = await Promise.all([
|
|
102
|
+
team.currency_id
|
|
103
|
+
? this.db.get(`SELECT symbol FROM currencies WHERE id = ?${this.alive()}`, [team.currency_id])
|
|
104
|
+
: null,
|
|
105
|
+
team.language_id
|
|
106
|
+
? this.db.get(`SELECT code FROM languages WHERE id = ?${this.alive()}`, [team.language_id])
|
|
107
|
+
: null,
|
|
108
|
+
terminal.company_id
|
|
109
|
+
? this.db.get(`SELECT * FROM companies WHERE id = ?${this.alive()}`, [terminal.company_id])
|
|
110
|
+
: null,
|
|
111
|
+
this.db.all(
|
|
112
|
+
`SELECT pw.id, pw.name FROM product_workshops pw
|
|
113
|
+
INNER JOIN terminal_workshop tw ON pw.id = tw.product_workshop_id
|
|
114
|
+
WHERE tw.terminal_id = ?${this.alive('pw')}`,
|
|
115
|
+
[terminal.id]
|
|
116
|
+
),
|
|
117
|
+
imageUrls(this.db, [team.logo]),
|
|
118
|
+
posMode.state(this.db, terminal.company_id),
|
|
119
|
+
]);
|
|
120
|
+
const currencySymbol = currency ? currency.symbol : null;
|
|
121
|
+
return {
|
|
122
|
+
team: teamResource(team, {
|
|
123
|
+
currencySymbol,
|
|
124
|
+
languageCode: language ? language.code : null,
|
|
125
|
+
urls,
|
|
126
|
+
locale: this.locale,
|
|
127
|
+
}),
|
|
128
|
+
terminal: terminalResource(terminal, {
|
|
129
|
+
companyName: company ? company.name : null,
|
|
130
|
+
workshops: terminalWorkshops,
|
|
131
|
+
posMode: mode,
|
|
132
|
+
// SELECT * выше: офлайн-колонок (offline_*) в локальной схеме кассы нет.
|
|
133
|
+
company,
|
|
134
|
+
}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ------------------------------------------------------------ залы
|
|
139
|
+
|
|
140
|
+
/** HallService::getHalls — залы по order, столы по order. */
|
|
141
|
+
async halls(companyId) {
|
|
142
|
+
const halls = await this.db.all(
|
|
143
|
+
`SELECT * FROM halls WHERE company_id = ?${this.alive()} ORDER BY "order"`,
|
|
144
|
+
[companyId]
|
|
145
|
+
);
|
|
146
|
+
if (!halls.length) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const tables = await this.whereIn(
|
|
150
|
+
`SELECT * FROM tables WHERE hall_id IN (:ids)${this.alive()} ORDER BY "order"`,
|
|
151
|
+
unique(halls.map((hall) => hall.id))
|
|
152
|
+
);
|
|
153
|
+
const byHall = groupBy(tables, 'hall_id');
|
|
154
|
+
return halls.map((hall) => hallResource(hall, byHall.get(String(hall.id)) || []));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ------------------------------------------------------------ товары
|
|
158
|
+
|
|
159
|
+
/** ProductService::allProducts — верхние товары со связями. */
|
|
160
|
+
async products(teamId) {
|
|
161
|
+
const products = await this.db.all(
|
|
162
|
+
`SELECT * FROM products
|
|
163
|
+
WHERE parent_id IS NULL AND team_id = ? AND type IN (${TOP_PRODUCT_TYPES.map(() => '?').join(', ')})${this.alive()}
|
|
164
|
+
ORDER BY "order"`,
|
|
165
|
+
[teamId, ...TOP_PRODUCT_TYPES]
|
|
166
|
+
);
|
|
167
|
+
if (!products.length) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
const rel = await new ProductRelations(this.db, this.locale).load(products);
|
|
171
|
+
return products.map((product) => productResource(product, rel));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** CategoryService::all. */
|
|
175
|
+
async categories(teamId) {
|
|
176
|
+
const rows = await this.db.all(
|
|
177
|
+
`SELECT * FROM product_categories WHERE team_id = ?${this.alive()} ORDER BY "order"`,
|
|
178
|
+
[teamId]
|
|
179
|
+
);
|
|
180
|
+
const urls = await imageUrls(this.db, rows.map((row) => row.image));
|
|
181
|
+
return rows.map((row) => categoryResource(row, {urls, locale: this.locale}));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** `products.getExtraProducts` / `orderProducts.getExtraProducts`. */
|
|
185
|
+
async extraProducts(teamId) {
|
|
186
|
+
const rows = await this.db.all(
|
|
187
|
+
`SELECT id, full_name, product_workshop_id, price FROM products
|
|
188
|
+
WHERE type = ? AND team_id = ?${this.alive()}`,
|
|
189
|
+
['extra', teamId]
|
|
190
|
+
);
|
|
191
|
+
return rows.map((row) => extraProductResource(row, this.locale));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ------------------------------------------------------------ сотрудники
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* UserService::all — `latest()`. Смены в коллекции всегда null: TeamUserResource берёт их из
|
|
198
|
+
* своих свойств конструктора, а коллекция создаёт ресурс без них (eager-load workShift
|
|
199
|
+
* в ответ не попадает).
|
|
200
|
+
*/
|
|
201
|
+
async users(teamId, team) {
|
|
202
|
+
const members = await this.db.all(
|
|
203
|
+
`SELECT * FROM team_users WHERE team_id = ?${this.alive()} ORDER BY created_at DESC`,
|
|
204
|
+
[teamId]
|
|
205
|
+
);
|
|
206
|
+
if (!members.length) {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
const userIds = unique(members.map((member) => member.user_id));
|
|
210
|
+
const roleIds = unique(members.map((member) => member.role_id));
|
|
211
|
+
const [users, roles, events] = await Promise.all([
|
|
212
|
+
userIds.length ? this.whereIn(`SELECT id, name FROM users WHERE id IN (:ids)${this.alive()}`, userIds) : [],
|
|
213
|
+
roleIds.length ? this.whereIn(`SELECT * FROM roles WHERE id IN (:ids)${this.alive()}`, roleIds) : [],
|
|
214
|
+
roleIds.length ? this.whereIn(
|
|
215
|
+
`SELECT pe."group", pe.slug, per.can, per.role_id
|
|
216
|
+
FROM platform_events pe
|
|
217
|
+
INNER JOIN platform_event_roles per ON pe.id = per.platform_event_id
|
|
218
|
+
WHERE per.role_id IN (:ids)${this.alive('pe')}`,
|
|
219
|
+
roleIds
|
|
220
|
+
) : [],
|
|
221
|
+
]);
|
|
222
|
+
const usersById = indexBy(users);
|
|
223
|
+
const rolesById = indexBy(roles);
|
|
224
|
+
const scopesByRole = new Map();
|
|
225
|
+
for (const event of events) {
|
|
226
|
+
const key = String(event.role_id);
|
|
227
|
+
if (!scopesByRole.has(key)) {
|
|
228
|
+
scopesByRole.set(key, {});
|
|
229
|
+
}
|
|
230
|
+
scopesByRole.get(key)[`${event.group}:${event.slug}`] = event.can;
|
|
231
|
+
}
|
|
232
|
+
return members.map((member) => teamUserResource(member, {
|
|
233
|
+
user: usersById.get(String(member.user_id)) || null,
|
|
234
|
+
role: rolesById.get(String(member.role_id)) || null,
|
|
235
|
+
scopes: scopesByRole.get(String(member.role_id)) || {},
|
|
236
|
+
teamOwnerId: team.user_id,
|
|
237
|
+
teamId,
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ------------------------------------------------------------ справочники
|
|
242
|
+
|
|
243
|
+
/** `Unit::query()->get(['id', 'code', 'name', 'short_name'])` — без фильтра команды. */
|
|
244
|
+
async units() {
|
|
245
|
+
const rows = await this.db.all(`SELECT id, code, name, short_name FROM units WHERE 1 = 1${this.alive()}`);
|
|
246
|
+
return rows.map((row) => ({
|
|
247
|
+
id: row.id,
|
|
248
|
+
code: row.code,
|
|
249
|
+
name: transArray(row.name, this.locale),
|
|
250
|
+
short_name: transArray(row.short_name, this.locale),
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async workshops(teamId) {
|
|
255
|
+
const rows = await this.db.all(
|
|
256
|
+
`SELECT *${jsonText(this.db, ['config'])} FROM product_workshops WHERE team_id = ?${this.alive()}`,
|
|
257
|
+
[teamId]
|
|
258
|
+
);
|
|
259
|
+
return rows.map((row) => modelArray(takeText(row, ['config']), {print_ticket: nullableBool}));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async paymentMethods(teamId) {
|
|
263
|
+
const rows = await this.db.all(
|
|
264
|
+
`SELECT *${jsonText(this.db, ['data'])} FROM payment_methods WHERE team_id = ?${this.alive()}`,
|
|
265
|
+
[teamId]
|
|
266
|
+
);
|
|
267
|
+
return rows.map((row) => modelArray(takeText(row, ['data'])));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async orderSources(teamId) {
|
|
271
|
+
const rows = await this.db.all(
|
|
272
|
+
`SELECT * FROM order_sources WHERE team_id = ? AND show_in_terminal = ?${this.alive()}`,
|
|
273
|
+
[teamId, true]
|
|
274
|
+
);
|
|
275
|
+
return rows.map((row) => modelArray(row, {show_in_terminal: nullableBool}));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
module.exports = InitService;
|