@etsoo/appscript 1.3.89 → 1.3.91

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.
@@ -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/i18n/en.json CHANGED
@@ -177,6 +177,7 @@
177
177
  "statusAudited": "Audited",
178
178
  "statusCompleted": "Completed",
179
179
  "statusDeleted": "Deleted",
180
+ "statusDoing": "Doing",
180
181
  "statusFlaged": "Flaged",
181
182
  "statusNormal": "Normla",
182
183
  "statusInactivated": "Inactivated",
@@ -177,6 +177,7 @@
177
177
  "statusAudited": "已审核",
178
178
  "statusCompleted": "已完成",
179
179
  "statusDeleted": "已删除",
180
+ "statusDoing": "处理中",
180
181
  "statusFlaged": "已标记",
181
182
  "statusNormal": "正常",
182
183
  "statusInactivated": "已停用",
@@ -177,6 +177,7 @@
177
177
  "statusAudited": "已審核",
178
178
  "statusCompleted": "已完成",
179
179
  "statusDeleted": "已刪除",
180
+ "statusDoing": "處理中",
180
181
  "statusFlaged": "已標記",
181
182
  "statusNormal": "正常",
182
183
  "statusInactivated": "已停用",
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';