@etsoo/appscript 1.3.90 → 1.3.92

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.
@@ -1237,23 +1237,35 @@ export abstract class CoreApp<
1237
1237
  getEnumList<E extends DataTypes.EnumBase = DataTypes.EnumBase>(
1238
1238
  em: E,
1239
1239
  prefix: string,
1240
- filter?: (
1241
- id: E[keyof E],
1242
- key: keyof E & string
1243
- ) => E[keyof E] | undefined
1240
+ filter?:
1241
+ | ((
1242
+ id: E[keyof E],
1243
+ key: keyof E & string
1244
+ ) => E[keyof E] | undefined)
1245
+ | E[keyof E][]
1244
1246
  ): ListType[] {
1245
1247
  const list: ListType[] = [];
1246
- const keys = DataTypes.getEnumKeys(em);
1247
- for (const key of keys) {
1248
- let id = em[key as keyof E];
1249
- if (filter) {
1250
- const fid = filter(id, key);
1251
- if (fid == null) continue;
1252
- id = fid;
1248
+
1249
+ if (Array.isArray(filter)) {
1250
+ filter.forEach((id) => {
1251
+ if (typeof id !== 'number') return;
1252
+ const key = DataTypes.getEnumKey(em, id);
1253
+ var label = this.get<string>(prefix + key) ?? key;
1254
+ list.push({ id, label });
1255
+ });
1256
+ } else {
1257
+ const keys = DataTypes.getEnumKeys(em);
1258
+ for (const key of keys) {
1259
+ let id = em[key as keyof E];
1260
+ if (filter) {
1261
+ const fid = filter(id, key);
1262
+ if (fid == null) continue;
1263
+ id = fid;
1264
+ }
1265
+ if (typeof id !== 'number') continue;
1266
+ var label = this.get<string>(prefix + key) ?? key;
1267
+ list.push({ id, label });
1253
1268
  }
1254
- if (typeof id !== 'number') continue;
1255
- var label = this.get<string>(prefix + key) ?? key;
1256
- list.push({ id, label });
1257
1269
  }
1258
1270
  return list;
1259
1271
  }
@@ -1322,14 +1334,8 @@ export abstract class CoreApp<
1322
1334
  * @param ids Limited ids
1323
1335
  * @returns list
1324
1336
  */
1325
- getStatusList(ids: EntityStatus[] = []) {
1326
- return this.getEnumList(
1327
- EntityStatus,
1328
- 'status',
1329
- ids.length > 0
1330
- ? (id, _key) => (ids.includes(id) ? id : undefined)
1331
- : undefined
1332
- );
1337
+ getStatusList(ids?: EntityStatus[]) {
1338
+ return this.getEnumList(EntityStatus, 'status', ids);
1333
1339
  }
1334
1340
 
1335
1341
  /**
package/src/app/IApp.ts CHANGED
@@ -434,10 +434,12 @@ export interface IApp {
434
434
  getEnumStrList<E extends DataTypes.EnumBase = DataTypes.EnumBase>(
435
435
  em: E,
436
436
  prefix: string,
437
- filter?: (
438
- id: E[keyof E],
439
- key: keyof E & string
440
- ) => E[keyof E] | undefined
437
+ filter?:
438
+ | ((
439
+ id: E[keyof E],
440
+ key: keyof E & string
441
+ ) => E[keyof E] | undefined)
442
+ | E[keyof E][]
441
443
  ): ListType1[];
442
444
 
443
445
  /**
@@ -0,0 +1,524 @@
1
+ import { DataTypes, IStorage, NumberUtils, WindowStorage } from '@etsoo/shared';
2
+
3
+ /**
4
+ * Shopping cart owner
5
+ * 购物篮所有人
6
+ */
7
+ export type ShoppingCartOwner = DataTypes.IdNameItem & {
8
+ isSupplier?: boolean;
9
+ };
10
+
11
+ /**
12
+ * Shopping cart data
13
+ * 购物篮数据
14
+ */
15
+ export type ShoppingCartData<T extends ShoppingCartItem> = {
16
+ currency: string;
17
+ owner: ShoppingCartOwner;
18
+ items: T[];
19
+ promotions: ShoppingPromotion[];
20
+ formData?: any;
21
+ };
22
+
23
+ /**
24
+ * Shopping promotion
25
+ * 购物促销
26
+ */
27
+ export type ShoppingPromotion = {
28
+ /**
29
+ * Promotion id
30
+ * 促销编号
31
+ */
32
+ id: number;
33
+
34
+ /**
35
+ * Promotion title
36
+ * 促销标题
37
+ */
38
+ title: string;
39
+
40
+ /**
41
+ * Discount amount
42
+ * 折扣金额
43
+ */
44
+ amount: number;
45
+ };
46
+
47
+ /**
48
+ * Shopping cart base item
49
+ * 购物篮基础项目
50
+ */
51
+ export type ShoppingCartItemBase = {
52
+ /**
53
+ * Product id
54
+ * 产品编号
55
+ */
56
+ id: DataTypes.IdType;
57
+
58
+ /**
59
+ * Product title, default is name
60
+ * 产品标题,默认为name
61
+ */
62
+ title?: string;
63
+
64
+ /**
65
+ * Sale price
66
+ * 销售价格
67
+ */
68
+ price: number;
69
+
70
+ /**
71
+ * Qty
72
+ * 数量
73
+ */
74
+ qty: number;
75
+
76
+ /**
77
+ * Product level promotions
78
+ * 产品层次促销
79
+ */
80
+ promotions: ShoppingPromotion[];
81
+ };
82
+
83
+ /**
84
+ * Shopping cart item
85
+ * 购物篮项目
86
+ */
87
+ export type ShoppingCartItem = ShoppingCartItemBase & {
88
+ /**
89
+ * Product name
90
+ * 产品名称
91
+ */
92
+ name: string;
93
+
94
+ /**
95
+ * Current price for cache
96
+ * 当前缓存价格
97
+ */
98
+ currentPrice?: number;
99
+
100
+ /**
101
+ * Subtotal
102
+ * 小计
103
+ */
104
+ subtotal: number;
105
+
106
+ /**
107
+ * Total discount amount
108
+ * 总折扣金额
109
+ */
110
+ discount: number;
111
+ };
112
+
113
+ /**
114
+ * Shopping cart change reason
115
+ * 购物篮改变原因
116
+ */
117
+ export type ShoppingCartChangeReason =
118
+ | 'add'
119
+ | 'clear'
120
+ | 'remove'
121
+ | 'title'
122
+ | 'update';
123
+
124
+ /**
125
+ * Shopping cart
126
+ * 购物篮
127
+ */
128
+ export class ShoppingCart<T extends ShoppingCartItem> {
129
+ /**
130
+ * Create identifier key
131
+ * 创建识别键
132
+ * @param currency Currency
133
+ * @returns Result
134
+ */
135
+ static createKey(currency: string) {
136
+ return `ETSOO-CART-${currency}`;
137
+ }
138
+
139
+ /**
140
+ * Clear shopping cart
141
+ * 清除购物篮
142
+ * @param identifier Identifier
143
+ * @param storage Storage
144
+ */
145
+ static clear(identifier: string, storage: IStorage) {
146
+ try {
147
+ storage.setData(identifier, null);
148
+ storage.setPersistedData(identifier, null);
149
+ } catch (error) {
150
+ console.log('ShoppingCart clear', error);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Clear shopping cart
156
+ * 清除购物篮
157
+ * @param currency Currency
158
+ * @param storage Storage
159
+ */
160
+ static clearWith(
161
+ currency: string,
162
+ storage: IStorage = new WindowStorage()
163
+ ) {
164
+ const identifier = this.createKey(currency);
165
+ this.clear(identifier, storage);
166
+ }
167
+
168
+ /**
169
+ * Owner data
170
+ * 所有者信息
171
+ */
172
+ owner?: ShoppingCartOwner;
173
+
174
+ /**
175
+ * ISO currency id
176
+ * 标准货币编号
177
+ */
178
+ readonly currency: string;
179
+
180
+ /**
181
+ * Items
182
+ * 项目
183
+ */
184
+ readonly items: T[];
185
+
186
+ /**
187
+ * Order level promotions
188
+ * 订单层面促销
189
+ */
190
+ readonly promotions: ShoppingPromotion[];
191
+
192
+ /**
193
+ * Related form data
194
+ * 关联的表单数据
195
+ */
196
+ formData: any;
197
+
198
+ /**
199
+ * Currency symbol
200
+ * 币种符号
201
+ */
202
+ readonly symbol: string | undefined;
203
+
204
+ /**
205
+ * Cart identifier
206
+ * 购物篮标识
207
+ */
208
+ private readonly identifier: string;
209
+
210
+ /**
211
+ * Lines count
212
+ * 项目数量
213
+ */
214
+ get lines() {
215
+ return this.items.length;
216
+ }
217
+
218
+ /**
219
+ * Total qty
220
+ * 总数量
221
+ */
222
+ get totalQty() {
223
+ return this.items.map((item) => item.qty).sum();
224
+ }
225
+
226
+ /**
227
+ * Total amount
228
+ * 总金额
229
+ */
230
+ get totalAmount() {
231
+ const subtotal = this.items
232
+ .map((item) => item.subtotal - item.discount)
233
+ .sum();
234
+
235
+ const discount = this.promotions.sum('amount');
236
+
237
+ return subtotal - discount;
238
+ }
239
+
240
+ /**
241
+ * Total amount string
242
+ * 总金额字符串
243
+ */
244
+ get totalAmountStr() {
245
+ return this.formatAmount(this.totalAmount);
246
+ }
247
+
248
+ /**
249
+ * Cached prices
250
+ * 缓存的价格
251
+ */
252
+ private readonly prices: Record<T['id'], number> = {} as any;
253
+
254
+ /**
255
+ * Onchange callback
256
+ * 改变时回调
257
+ */
258
+ onChange?: (reason: ShoppingCartChangeReason, changedItems: T[]) => void;
259
+
260
+ /**
261
+ * Constructor
262
+ * 构造函数
263
+ * @param currency Currency ISO code
264
+ * @param storage Data storage
265
+ */
266
+ constructor(currency: string, storage?: IStorage);
267
+
268
+ /**
269
+ * Constructor
270
+ * 构造函数
271
+ * @param state Initialization state
272
+ * @param storage Data storage
273
+ */
274
+ constructor(state: ShoppingCartData<T>, storage?: IStorage);
275
+
276
+ /**
277
+ * Constructor
278
+ * 构造函数
279
+ * @param currency Currency ISO code
280
+ * @param storage Data storage
281
+ * @param state Initialization state
282
+ */
283
+ constructor(
284
+ currencyOrState: string | ShoppingCartData<T>,
285
+ private readonly storage: IStorage = new WindowStorage()
286
+ ) {
287
+ const isCurrency = typeof currencyOrState === 'string';
288
+ this.currency = isCurrency ? currencyOrState : currencyOrState.currency;
289
+
290
+ const key = ShoppingCart.createKey(this.currency);
291
+ this.identifier = key;
292
+ this.symbol = NumberUtils.getCurrencySymbol(this.currency);
293
+
294
+ let state: ShoppingCartData<T> | undefined;
295
+ if (isCurrency) {
296
+ try {
297
+ state =
298
+ storage.getPersistedObject<ShoppingCartData<T>>(key) ??
299
+ storage.getObject<ShoppingCartData<T>>(key);
300
+ } catch (error) {
301
+ console.log('ShoppingCart constructor', error);
302
+ }
303
+ } else {
304
+ state = currencyOrState;
305
+ }
306
+
307
+ const { owner, items = [], promotions = [], formData } = state ?? {};
308
+
309
+ this.owner = owner;
310
+ this.items = items;
311
+ this.promotions = promotions;
312
+ this.formData = formData;
313
+ }
314
+
315
+ private doChange(reason: ShoppingCartChangeReason, changedItems: T[]) {
316
+ if (this.onChange) this.onChange(reason, changedItems);
317
+ }
318
+
319
+ /**
320
+ * Add item
321
+ * 添加项目
322
+ * @param item New item
323
+ */
324
+ addItem(item: T) {
325
+ this.addItems([item]);
326
+ }
327
+
328
+ /**
329
+ * Add items
330
+ * @param items New items
331
+ */
332
+ addItems(items: T[]) {
333
+ this.items.push(...items);
334
+ this.doChange('add', items);
335
+ }
336
+
337
+ /**
338
+ * Cache price
339
+ * @param id Item id
340
+ * @param price Price
341
+ */
342
+ cachePrice(id: T['id'], price: number) {
343
+ this.prices[id] = price;
344
+ }
345
+
346
+ /**
347
+ * Clear storage
348
+ * @param keepOwner Keep owner data
349
+ */
350
+ clear(keepOwner?: boolean) {
351
+ this.items.length = 0;
352
+ this.promotions.length = 0;
353
+
354
+ if (keepOwner) {
355
+ this.save();
356
+ } else {
357
+ ShoppingCart.clear(this.identifier, this.storage);
358
+ }
359
+
360
+ this.doChange('clear', []);
361
+ }
362
+
363
+ /**
364
+ * Format amount
365
+ * @param amount Amount
366
+ * @returns Result
367
+ */
368
+ formatAmount(amount: number) {
369
+ return NumberUtils.formatMoney(amount, this.currency);
370
+ }
371
+
372
+ /**
373
+ * Get item
374
+ * @param id Item id
375
+ * @returns Result
376
+ */
377
+ getItem(id: T['id']) {
378
+ return this.items.find((item) => item.id === id);
379
+ }
380
+
381
+ /**
382
+ * Reset
383
+ * @param item Shopping cart item
384
+ */
385
+ reset(item: ShoppingCartItem) {
386
+ item.discount = 0;
387
+ item.currentPrice = undefined;
388
+ item.promotions = [];
389
+ }
390
+
391
+ /**
392
+ * Save cart data
393
+ * @param persisted For persisted storage
394
+ */
395
+ save(persisted: boolean = true) {
396
+ if (this.owner == null) return;
397
+
398
+ const { currency, owner, items, promotions, formData } = this;
399
+ const data: ShoppingCartData<T> = {
400
+ currency,
401
+ owner,
402
+ items,
403
+ promotions,
404
+ formData
405
+ };
406
+
407
+ try {
408
+ if (persisted) {
409
+ this.storage.setPersistedData(this.identifier, data);
410
+ } else {
411
+ this.storage.setData(this.identifier, data);
412
+ }
413
+ } catch (error) {
414
+ console.log('ShoppingCart save', error);
415
+ }
416
+
417
+ return data;
418
+ }
419
+
420
+ /**
421
+ * Trigger update
422
+ * 触发更新
423
+ */
424
+ update() {
425
+ this.doChange('update', []);
426
+ }
427
+
428
+ /**
429
+ * Update discount
430
+ * @param item Shopping cart item
431
+ */
432
+ updateDiscount(item: ShoppingCartItem) {
433
+ item.discount = item.promotions.sum('amount');
434
+ }
435
+
436
+ /**
437
+ * Update item
438
+ * 更新项目
439
+ * @param id Product id
440
+ * @param qty Qty
441
+ * @param itemCreator New item creator
442
+ * @returns Updated or not
443
+ */
444
+ updateItem(
445
+ id: T['id'],
446
+ qty: number | undefined,
447
+ itemCreator?: () => Omit<
448
+ T,
449
+ 'id' | 'price' | 'qty' | 'subtotal' | 'discount' | 'promotions'
450
+ >
451
+ ) {
452
+ const index = this.items.findIndex((item) => item.id === id);
453
+ if (qty == null) {
454
+ // Remove the item
455
+ if (index !== -1) {
456
+ const removedItems = this.items.splice(index, 1);
457
+ this.doChange('remove', removedItems);
458
+ }
459
+ } else if (index === -1) {
460
+ // New
461
+ if (itemCreator) {
462
+ const price = this.prices[id];
463
+ const newItem = {
464
+ ...itemCreator(),
465
+ id,
466
+ price,
467
+ qty,
468
+ subtotal: price * qty,
469
+ discount: 0
470
+ } as T;
471
+ this.addItem(newItem);
472
+ }
473
+ return false;
474
+ } else {
475
+ // Update
476
+ const item = this.items[index];
477
+ const price = item.price;
478
+ const newItem = {
479
+ ...item,
480
+ qty,
481
+ subtotal: price * qty,
482
+ discount: 0
483
+ };
484
+ this.items.splice(index, 1, newItem);
485
+ this.doChange('update', [item, newItem]);
486
+ }
487
+
488
+ return true;
489
+ }
490
+
491
+ /**
492
+ * Update price
493
+ * @param id Item id
494
+ * @param price New price
495
+ */
496
+ updatePrice(id: T['id'], price: number) {
497
+ this.cachePrice(id, price);
498
+
499
+ const index = this.items.findIndex((item) => item.id === id);
500
+ if (index !== -1) {
501
+ const item = this.items[index];
502
+ const qty = item.qty;
503
+ const newItem = { ...item, price, subtotal: price * qty };
504
+ this.items.splice(index, 1, newItem);
505
+ this.doChange('update', [item, newItem]);
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Update title
511
+ * @param id Item id
512
+ * @param title New title
513
+ */
514
+ updateTitle(id: T['id'], title: string) {
515
+ const index = this.items.findIndex((item) => item.id === id);
516
+ if (index !== -1) {
517
+ const item = this.items[index];
518
+ const newItem: T = { ...item, title };
519
+ if (newItem.name === newItem.title) newItem.title = undefined;
520
+ this.items.splice(index, 1, newItem);
521
+ this.doChange('title', [item, newItem]);
522
+ }
523
+ }
524
+ }
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ export * from './business/DataPrivacy';
29
29
  export * from './business/EntityStatus';
30
30
  export * from './business/ProductUnit';
31
31
  export * from './business/RepeatOption';
32
+ export * from './business/ShoppingCart';
32
33
 
33
34
  // def
34
35
  export * from './def/ListItem';