@subiz/widget-v4 4.0.93 → 4.0.95

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.
@@ -229,6 +229,14 @@ body div#sbz92 {
229
229
  flex-shrink: 0;
230
230
  }
231
231
 
232
+ .justify-content-between {
233
+ justify-content: space-between;
234
+ }
235
+
236
+ .no-shrink {
237
+ flex-shrink: 0;
238
+ }
239
+
232
240
  @import 'avatar.less';
233
241
  @import 'widget_header.less';
234
242
  @import 'message_input.less';
@@ -613,10 +613,11 @@ a.message_error {
613
613
  .order_attachment__footer {
614
614
  margin-top: 10px;
615
615
  padding-top: 10px;
616
- display: flex;
617
- justify-content: space-between;
618
616
  border-top: 1px solid #ccc;
619
- line-height: 1;
617
+ }
618
+
619
+ .order_attachment__total {
620
+ font-size: 16px;
620
621
  }
621
622
 
622
623
  .order_attachment__label {
@@ -625,6 +626,10 @@ a.message_error {
625
626
  margin-top: 10px;
626
627
  }
627
628
 
629
+ .order_attachment__label.no_margin {
630
+ margin-top: 0;
631
+ }
632
+
628
633
  .order_attachment.order_attachment__user {
629
634
  margin-right: 50px;
630
635
 
@@ -482,3 +482,13 @@ div.order__card.order__card__active {
482
482
  height: 100%;
483
483
  width: 100%;
484
484
  }
485
+
486
+ .order_attachment__note {
487
+ font-size: 14px;
488
+ white-space: pre-wrap;
489
+ overflow: hidden;
490
+ text-overflow: ellipsis;
491
+ display: -webkit-box;
492
+ -webkit-line-clamp: 3;
493
+ -webkit-box-orient: vertical;
494
+ }
@@ -18,12 +18,14 @@ class ProductAttachments extends _common.Com {
18
18
  let orders = _index.default.filter(attachments, att => att.type === 'order' && att.order).map(att => att.order);
19
19
  if (orders.length === 0) return null;
20
20
  let $orders = _index.default.map(orders, (order, index) => {
21
+ _index.default.calcOrderTotal(order);
21
22
  let $note = null;
22
23
  if (order.note) {
23
24
  $note = (0, _preact.h)("div", null, (0, _preact.h)("div", {
24
25
  class: "order_attachment__label"
25
26
  }, this.props.t('note')), (0, _preact.h)("div", {
26
- class: "order_attachment__text_truncate"
27
+ class: "order_attachment__note",
28
+ title: order.note
27
29
  }, order.note));
28
30
  }
29
31
  let $products = null;
@@ -37,24 +39,26 @@ class ProductAttachments extends _common.Com {
37
39
  }, (0, _preact.h)("img", {
38
40
  class: "order_attachment__product_img",
39
41
  alt: '' + _index.default.get(product, 'id'),
40
- src: _index.default.get(product, 'images.0', require('../../assets/img/default.jpg'))
42
+ src: _index.default.get(product, 'image', require('../../assets/img/default.jpg'))
41
43
  }), (0, _preact.h)("div", {
42
44
  style: "overflow: hidden !important;"
43
45
  }, (0, _preact.h)("div", {
44
- class: "order_attachment__product_name"
45
- }, product.name), (0, _preact.h)("div", {
46
+ class: "order_attachment__product_name",
47
+ title: this.props.t.i18n(product.i18n_name) || product.name
48
+ }, this.props.t.i18n(product.i18n_name) || product.name), (0, _preact.h)("div", {
46
49
  class: "order_attachment__product_desc"
47
- }, product.description || product.long_description), (0, _preact.h)("div", {
50
+ }, _index.default.getProductPropName(product, ' / ')), (0, _preact.h)("div", {
48
51
  class: "order_attachment__product_desc"
49
52
  }, this.props.t('qty'), " ", item.quantity, "\xA0\xB7\xA0", _index.default.formatCurrency(product.price))));
50
53
  }));
51
54
  }
52
55
  let $total = (0, _preact.h)("div", {
53
56
  class: "order_attachment__footer"
54
- }, (0, _preact.h)("div", null, this.props.t('total')), ' ', (0, _preact.h)("div", null, (0, _preact.h)("b", null, _index.default.formatCurrency(order.total))));
55
- let addr = _index.default.get(order, 'shipping.address', {});
56
- let fulladdr = [addr.street, addr.ward, addr.district, addr.region, addr.country].filter(a => a).join(', ');
57
- let address = fulladdr;
57
+ }, (0, _preact.h)("div", {
58
+ class: "d-flex align-items-center justify-content-between"
59
+ }, (0, _preact.h)("div", null, this.props.t('total')), ' ', (0, _preact.h)("div", null, (0, _preact.h)("b", {
60
+ class: "order_attachment__total"
61
+ }, _index.default.formatCurrency(order.total)))));
58
62
  let phoneattr = _index.default.find(_index.default.get(order, 'user.attributes', []), attr => attr.key === 'phones') || {
59
63
  key: 'phones',
60
64
  text: ''
@@ -63,24 +67,101 @@ class ProductAttachments extends _common.Com {
63
67
  phone = phone || _index.default.trim(phoneattr.text);
64
68
  if (phone) phone = this.props.t('phn') + ': ' + phone;
65
69
  let $shipto = null;
66
- if (address || phone) {
70
+ if (_index.default.displayOrderShippingAddress(order) || phone) {
67
71
  $shipto = (0, _preact.h)("div", null, (0, _preact.h)("div", {
68
72
  class: "order_attachment__label"
69
- }, this.props.t('ship_to')), (0, _preact.h)("div", {
70
- class: "order_attachment__text_truncate"
71
- }, address), (0, _preact.h)("div", {
73
+ }, this.props.t('ship_to')), (0, _preact.h)("div", null, _index.default.displayOrderShippingAddress(order)), (0, _preact.h)("div", {
72
74
  class: "order_attachment__text_truncate"
73
75
  }, phone));
74
76
  }
77
+ let $beforeTax = null;
78
+ if (order._discount_before_tax) {
79
+ $beforeTax = (0, _preact.h)("div", {
80
+ class: "order_attachment__footer"
81
+ }, (0, _preact.h)("div", {
82
+ class: "d-flex align-items-center justify-content-between"
83
+ }, (0, _preact.h)("div", {
84
+ class: "order_attachment__label no_margin"
85
+ }, this.props.t('subtotal_before_tax')), ' ', (0, _preact.h)("div", null, _index.default.formatCurrency(order._order_value))), (0, _preact.h)("div", {
86
+ class: "d-flex align-items-center justify-content-between"
87
+ }, (0, _preact.h)("div", {
88
+ class: "order_attachment__label no_margin"
89
+ }, this.props.t('discount_before_tax')), ' ', (0, _preact.h)("div", null, "-", _index.default.formatCurrency(order._discount_before_tax))));
90
+ }
91
+ let $subtotal = (0, _preact.h)("div", {
92
+ class: "d-flex align-items-center justify-content-between"
93
+ }, (0, _preact.h)("div", {
94
+ class: "order_attachment__label no_margin"
95
+ }, this.props.t('subtotal')), ' ', (0, _preact.h)("div", {
96
+ style: ""
97
+ }, _index.default.formatCurrency(order.subtotal)));
98
+ let $tax = null;
99
+ if (order.total_tax) {
100
+ let taxname = '';
101
+ if (_index.default.size(order._taxM) === 1) {
102
+ let tax = _index.default.get(order._taxM, '0.tax', {});
103
+ if (tax.name && tax.percentage) taxname = `(${tax.name} ${Math.round(tax.percentage || 0) / 100}%)`;
104
+ }
105
+ $tax = (0, _preact.h)("div", {
106
+ class: "d-flex align-items-center justify-content-between"
107
+ }, (0, _preact.h)("div", {
108
+ class: "order_attachment__label no_margin"
109
+ }, this.props.t('tax'), " ", taxname), (0, _preact.h)("div", {
110
+ style: ""
111
+ }, _index.default.formatCurrency(order.total_tax)));
112
+ }
113
+ let $discount = null;
114
+ if (order.discount_type === 'percentage' && order.discount_percentage) {
115
+ $discount = (0, _preact.h)("div", {
116
+ class: "d-flex align-items-center justify-content-between"
117
+ }, (0, _preact.h)("div", {
118
+ class: "order_attachment__label no_margin"
119
+ }, this.props.t('discount_after_tax'), " (", Math.round(order.discount_percentage / 100), "%)"), ' ', (0, _preact.h)("div", {
120
+ style: ""
121
+ }, "-", _index.default.formatCurrency(order._computed_discount)));
122
+ }
123
+ if (order.discount_type === 'amount' && order.discount_amount) {
124
+ $discount = (0, _preact.h)("div", {
125
+ class: "d-flex align-items-center justify-content-between"
126
+ }, (0, _preact.h)("div", {
127
+ class: "order_attachment__label no_margin"
128
+ }, this.props.t('discount_after_tax')), ' ', (0, _preact.h)("div", {
129
+ class: "no-shrink"
130
+ }, "-", _index.default.formatCurrency(order._computed_discount)));
131
+ }
132
+ let $shipping = null;
133
+ if (_index.default.get(order, 'shipping.nominal_fee')) {
134
+ $shipping = (0, _preact.h)("div", {
135
+ class: "d-flex align-items-center justify-content-between"
136
+ }, (0, _preact.h)("div", {
137
+ class: "order_attachment__label no_margin"
138
+ }, this.props.t('shipping_fee')), ' ', (0, _preact.h)("div", {
139
+ style: ""
140
+ }, _index.default.formatCurrency(_index.default.get(order, 'shipping.nominal_fee', 0))));
141
+ }
142
+ let $adjustment = null;
143
+ if (order.adjustment) {
144
+ $adjustment = (0, _preact.h)("div", {
145
+ class: "d-flex align-items-center justify-content-between"
146
+ }, (0, _preact.h)("div", {
147
+ class: "order_attachment__label no_margin"
148
+ }, order.adjustment_description), ' ', (0, _preact.h)("div", {
149
+ class: "no-shrink"
150
+ }, _index.default.formatCurrency(order.adjustment)));
151
+ }
152
+ $subtotal = (0, _preact.h)("div", {
153
+ class: "order_attachment__footer"
154
+ }, $subtotal, $tax, $shipping, $discount, $adjustment);
155
+ if (!$shipping && !$adjustment && !$discount && !$tax) {
156
+ $subtotal = null;
157
+ }
75
158
  return (0, _preact.h)("div", {
76
159
  class: `order_attachment order_attachment__${byType}`
77
160
  }, (0, _preact.h)("div", {
78
161
  class: "order_attachment__header"
79
162
  }, this.props.t('order'), (0, _preact.h)("span", {
80
163
  class: "order_attachment__text_muted order_attachment__text_sm"
81
- }, "#", order.id)), $products, $shipto, (0, _preact.h)("div", null, (0, _preact.h)("div", {
82
- class: "order_attachment__label"
83
- }, this.props.t('payment')), (0, _preact.h)("div", null, "COD")), $note, $total);
164
+ }, _index.default.getOrderDisplayName(order))), $products, $shipto, $note, $beforeTax, $subtotal, $total);
84
165
  });
85
166
  return $orders;
86
167
  }
@@ -118,7 +118,10 @@ class Store {
118
118
  if (!_index.default.get(this.kv.match('user'), 'id')) return;
119
119
  if (_index.default.isUserBanned(this.kv.match('user'))) return;
120
120
  return this.api.sendPresence(this.device, this.browserVisibility === 'visible' && document.hasFocus(), this._currentViewingProduct.id, this._currentViewingProduct.source_id, this._currentViewingProduct.sku);
121
- }, 1000));
121
+ }, 1000,
122
+ {
123
+ leading: false
124
+ }));
122
125
  _defineProperty(this, "now", () => _index.default.Date.now() + this.timeDiff);
123
126
  _defineProperty(this, "seenQueue", []);
124
127
  _defineProperty(this, "matchTrafficSource", () => this.kv.match('traffic_source'));
@@ -7,6 +7,7 @@ exports.default = void 0;
7
7
  var common = _interopRequireWildcard(require("./common.js"));
8
8
  var extractor = _interopRequireWildcard(require("./extractor.js"));
9
9
  var campaign = _interopRequireWildcard(require("./campaign.js"));
10
+ var order = _interopRequireWildcard(require("./order.js"));
10
11
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
11
12
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
12
13
 
@@ -20,5 +21,6 @@ sb.set = op.set;
20
21
  sb.Pubsub = require('./pubsub.js').default;
21
22
  sb = Object.assign(sb, extractor);
22
23
  sb = Object.assign(sb, campaign);
24
+ sb = Object.assign(sb, order);
23
25
  var _default = sb;
24
26
  exports.default = _default;
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.calcOrderTotal = calcOrderTotal;
7
+ exports.displayOrderShippingAddress = displayOrderShippingAddress;
8
+ exports.getOrderDisplayName = getOrderDisplayName;
9
+ exports.getProductPropName = getProductPropName;
10
+ const op = require('./op.js');
11
+ const common = require('./common');
12
+ op.filter = common.filter;
13
+ op.map = common.map;
14
+ op.each = common.each;
15
+ op.size = common.size;
16
+ op.find = common.find;
17
+ function getProductPropName(product) {
18
+ let seperator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ' - ';
19
+ let options = op.get(product, 'options', []);
20
+ let props = op.get(product, 'props', []);
21
+ let multiPropsOptions = op.filter(options, option => op.size(option.values) > 1);
22
+ let names = [];
23
+ op.each(multiPropsOptions, option => {
24
+ let prop = op.find(props, prop => prop.key === option.key) || {};
25
+ if (prop.value) names.push(prop.value);
26
+ });
27
+ return names.join(seperator);
28
+ }
29
+ function getOrderDisplayName() {
30
+ let order = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
31
+ return order.number || order.id || '';
32
+ }
33
+ function displayOrderShippingAddress(order, isShorten) {
34
+ let addr = op.get(order, 'shipping.address', {});
35
+ let fulladdr = [addr.address, addr.ward, addr.district, addr.province].filter(a => a).join(', ').trim();
36
+ if (isShorten) fulladdr = shortenAddr(fulladdr);
37
+ return fulladdr;
38
+ }
39
+ function shortenAddr(str) {
40
+ str = str || '';
41
+ str = str.replace(/Hà Nội/gi, 'HN');
42
+ str = str.replace(/Hồ Chí Minh/gi, 'HCM');
43
+ str = str.replace(/Thành Phố/gi, 'TP');
44
+ return str;
45
+ }
46
+ function calcOrderTotal(order) {
47
+ let subtotal = 0;
48
+ let beforeTaxDiscount = 0;
49
+ let ordervalue = 0;
50
+ op.map(order.items, item => {
51
+ if (!item.product) return 0;
52
+ let price = op.get(item, 'product.price', 0) * (item.quantity || 0);
53
+ ordervalue += price;
54
+
55
+ if (item.discount_type == 'percentage') {
56
+ if (item.discount_percentage > 0) {
57
+ beforeTaxDiscount += price * item.discount_percentage / 10000;
58
+ price = price * (1 - (item.discount_percentage || 0) / 10000);
59
+ }
60
+ } else if (item.discount_type == 'amount') {
61
+ if (item.discount_amount > 0) {
62
+ beforeTaxDiscount += item.discount_amount;
63
+ price = price - (item.discount_amount || 0);
64
+ if (price <= 0) price = 0;
65
+ }
66
+ }
67
+ item.total = price;
68
+ subtotal += price;
69
+ });
70
+
71
+ let taxM = {};
72
+ op.map(order.items, (item, i) => {
73
+ if (!item.product) return;
74
+ let tax = item.product.tax;
75
+ if (!tax) return;
76
+ if (!tax.id) return;
77
+ let taxprice = item.total * tax.percentage / 10000;
78
+ item.total_tax = taxprice;
79
+ taxM[i] = {
80
+ tax: tax,
81
+ taxprice: taxprice
82
+ };
83
+ });
84
+ let totaltax = 0;
85
+ op.map(taxM, t => totaltax += t.taxprice);
86
+ let computed_discount = 0;
87
+ if (order.discount_type == 'percentage' && order.discount_percentage > 0) {
88
+ computed_discount = (subtotal + totaltax) * order.discount_percentage / 10000;
89
+ } else if (order.discount_type == 'amount') {
90
+ computed_discount = order.discount_amount || 0;
91
+ }
92
+
93
+ let shippingfee = op.get(order, 'shipping.nominal_fee', 0);
94
+ if (shippingfee > 0) {
95
+ if (op.get(order, 'shipping.tax.id')) {
96
+ let tax = order.shipping.tax;
97
+ let taxprice = shippingfee * (tax.percentage / 10000);
98
+ taxM['ship'] = {
99
+ tax: tax,
100
+ taxprice: taxprice
101
+ };
102
+ order.shipping.total_tax = taxprice;
103
+ }
104
+ }
105
+ totaltax = 0;
106
+ op.map(taxM, t => totaltax += t.taxprice);
107
+ let total = subtotal + totaltax - computed_discount + shippingfee;
108
+
109
+ total += order.adjustment || 0;
110
+ if (total < 0) total = 0;
111
+ order.subtotal = subtotal;
112
+ order._computed_discount = computed_discount;
113
+ order._taxM = taxM;
114
+ order.total = total;
115
+ order.total_tax = totaltax;
116
+ order._discount_before_tax = beforeTaxDiscount;
117
+ order._order_value = ordervalue;
118
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subiz/widget-v4",
3
- "version": "4.0.93",
3
+ "version": "4.0.95",
4
4
  "main": "./compiled_src/lib.js",
5
5
  "scripts": {
6
6
  "dev": "npx webpack serve --config webpack.dev.js",
@@ -19,7 +19,7 @@
19
19
  "file-loader": "^6.2.0",
20
20
  "less": "^4.1.3",
21
21
  "less-loader": "^11.1.0",
22
- "marked": "^4.2.2",
22
+ "marked": "^4.2.3",
23
23
  "preact": "^10.11.3"
24
24
  },
25
25
  "devDependencies": {
@@ -31,11 +31,10 @@
31
31
  "babel-loader": "^9.1.0",
32
32
  "babel-plugin-syntax-jsx": "^6.18.0",
33
33
  "html-webpack-plugin": "^5.5.0",
34
- "prettier": "^2.7.1",
35
- "puppeteer": "^19.2.2",
34
+ "puppeteer": "^19.3.0",
36
35
  "webpack": "^5.75.0",
37
36
  "webpack-bundle-analyzer": "^4.7.0",
38
- "webpack-cli": "^4.10.0",
37
+ "webpack-cli": "^5.0.0",
39
38
  "webpack-dev-server": "^4.11.1"
40
39
  }
41
40
  }
@@ -15,12 +15,15 @@ export default class ProductAttachments extends Com {
15
15
  if (orders.length === 0) return null
16
16
 
17
17
  let $orders = sb.map(orders, (order, index) => {
18
+ sb.calcOrderTotal(order)
18
19
  let $note = null
19
20
  if (order.note) {
20
21
  $note = (
21
22
  <div>
22
23
  <div class='order_attachment__label'>{this.props.t('note')}</div>
23
- <div class='order_attachment__text_truncate'>{order.note}</div>
24
+ <div class='order_attachment__note' title={order.note}>
25
+ {order.note}
26
+ </div>
24
27
  </div>
25
28
  )
26
29
  }
@@ -38,11 +41,16 @@ export default class ProductAttachments extends Com {
38
41
  <img
39
42
  class='order_attachment__product_img'
40
43
  alt={'' + sb.get(product, 'id')}
41
- src={sb.get(product, 'images.0', require('../../assets/img/default.jpg'))}
44
+ src={sb.get(product, 'image', require('../../assets/img/default.jpg'))}
42
45
  />
43
46
  <div style='overflow: hidden !important;'>
44
- <div class='order_attachment__product_name'>{product.name}</div>
45
- <div class='order_attachment__product_desc'>{product.description || product.long_description}</div>
47
+ <div
48
+ class='order_attachment__product_name'
49
+ title={this.props.t.i18n(product.i18n_name) || product.name}
50
+ >
51
+ {this.props.t.i18n(product.i18n_name) || product.name}
52
+ </div>
53
+ <div class='order_attachment__product_desc'>{sb.getProductPropName(product, ' / ')}</div>
46
54
  <div class='order_attachment__product_desc'>
47
55
  {this.props.t('qty')} {item.quantity}&nbsp;·&nbsp;{sb.formatCurrency(product.price)}
48
56
  </div>
@@ -56,17 +64,15 @@ export default class ProductAttachments extends Com {
56
64
 
57
65
  let $total = (
58
66
  <div class='order_attachment__footer'>
59
- <div>{this.props.t('total')}</div>{' '}
60
- <div>
61
- <b>{sb.formatCurrency(order.total)}</b>
67
+ <div class='d-flex align-items-center justify-content-between'>
68
+ <div>{this.props.t('total')}</div>{' '}
69
+ <div>
70
+ <b class='order_attachment__total'>{sb.formatCurrency(order.total)}</b>
71
+ </div>
62
72
  </div>
63
73
  </div>
64
74
  )
65
75
 
66
- let addr = sb.get(order, 'shipping.address', {})
67
- let fulladdr = [addr.street, addr.ward, addr.district, addr.region, addr.country].filter((a) => a).join(', ')
68
- let address = fulladdr
69
-
70
76
  let phoneattr = sb.find(sb.get(order, 'user.attributes', []), (attr) => attr.key === 'phones') || {
71
77
  key: 'phones',
72
78
  text: '',
@@ -76,29 +82,116 @@ export default class ProductAttachments extends Com {
76
82
  if (phone) phone = this.props.t('phn') + ': ' + phone
77
83
 
78
84
  let $shipto = null
79
- if (address || phone) {
85
+ if (sb.displayOrderShippingAddress(order) || phone) {
80
86
  $shipto = (
81
87
  <div>
82
88
  <div class='order_attachment__label'>{this.props.t('ship_to')}</div>
83
- <div class='order_attachment__text_truncate'>{address}</div>
89
+ <div>{sb.displayOrderShippingAddress(order)}</div>
84
90
  <div class='order_attachment__text_truncate'>{phone}</div>
85
91
  </div>
86
92
  )
87
93
  }
88
94
 
95
+ let $beforeTax = null
96
+ if (order._discount_before_tax) {
97
+ $beforeTax = (
98
+ <div class='order_attachment__footer'>
99
+ <div class='d-flex align-items-center justify-content-between'>
100
+ <div class='order_attachment__label no_margin'>{this.props.t('subtotal_before_tax')}</div>{' '}
101
+ <div>{sb.formatCurrency(order._order_value)}</div>
102
+ </div>
103
+ <div class='d-flex align-items-center justify-content-between'>
104
+ <div class='order_attachment__label no_margin'>{this.props.t('discount_before_tax')}</div>{' '}
105
+ <div>-{sb.formatCurrency(order._discount_before_tax)}</div>
106
+ </div>
107
+ </div>
108
+ )
109
+ }
110
+
111
+ let $subtotal = (
112
+ <div class='d-flex align-items-center justify-content-between'>
113
+ <div class='order_attachment__label no_margin'>{this.props.t('subtotal')}</div>{' '}
114
+ <div style=''>{sb.formatCurrency(order.subtotal)}</div>
115
+ </div>
116
+ )
117
+ let $tax = null
118
+ if (order.total_tax) {
119
+ let taxname = ''
120
+ if (sb.size(order._taxM) === 1) {
121
+ let tax = sb.get(order._taxM, '0.tax', {})
122
+ if (tax.name && tax.percentage) taxname = `(${tax.name} ${Math.round(tax.percentage || 0) / 100}%)`
123
+ }
124
+ $tax = (
125
+ <div class='d-flex align-items-center justify-content-between'>
126
+ <div class='order_attachment__label no_margin'>
127
+ {this.props.t('tax')} {taxname}
128
+ </div>
129
+ <div style=''>{sb.formatCurrency(order.total_tax)}</div>
130
+ </div>
131
+ )
132
+ }
133
+ let $discount = null
134
+ if (order.discount_type === 'percentage' && order.discount_percentage) {
135
+ $discount = (
136
+ <div class='d-flex align-items-center justify-content-between'>
137
+ <div class='order_attachment__label no_margin'>
138
+ {this.props.t('discount_after_tax')} ({Math.round(order.discount_percentage / 100)}%)
139
+ </div>{' '}
140
+ <div style=''>-{sb.formatCurrency(order._computed_discount)}</div>
141
+ </div>
142
+ )
143
+ }
144
+ if (order.discount_type === 'amount' && order.discount_amount) {
145
+ $discount = (
146
+ <div class='d-flex align-items-center justify-content-between'>
147
+ <div class='order_attachment__label no_margin'>{this.props.t('discount_after_tax')}</div>{' '}
148
+ <div class='no-shrink'>-{sb.formatCurrency(order._computed_discount)}</div>
149
+ </div>
150
+ )
151
+ }
152
+ let $shipping = null
153
+ if (sb.get(order, 'shipping.nominal_fee')) {
154
+ $shipping = (
155
+ <div class='d-flex align-items-center justify-content-between'>
156
+ <div class='order_attachment__label no_margin'>{this.props.t('shipping_fee')}</div>{' '}
157
+ <div style=''>{sb.formatCurrency(sb.get(order, 'shipping.nominal_fee', 0))}</div>
158
+ </div>
159
+ )
160
+ }
161
+ let $adjustment = null
162
+ if (order.adjustment) {
163
+ $adjustment = (
164
+ <div class='d-flex align-items-center justify-content-between'>
165
+ <div class='order_attachment__label no_margin'>{order.adjustment_description}</div>{' '}
166
+ <div class='no-shrink'>{sb.formatCurrency(order.adjustment)}</div>
167
+ </div>
168
+ )
169
+ }
170
+
171
+ $subtotal = (
172
+ <div class='order_attachment__footer'>
173
+ {$subtotal}
174
+ {$tax}
175
+ {$shipping}
176
+ {$discount}
177
+ {$adjustment}
178
+ </div>
179
+ )
180
+ if (!$shipping && !$adjustment && !$discount && !$tax) {
181
+ $subtotal = null
182
+ }
183
+
89
184
  return (
90
185
  <div class={`order_attachment order_attachment__${byType}`}>
91
186
  <div class='order_attachment__header'>
92
187
  {this.props.t('order')}
93
- <span class='order_attachment__text_muted order_attachment__text_sm'>#{order.id}</span>
188
+ <span class='order_attachment__text_muted order_attachment__text_sm'>{sb.getOrderDisplayName(order)}</span>
94
189
  </div>
95
190
  {$products}
96
191
  {$shipto}
97
- <div>
98
- <div class='order_attachment__label'>{this.props.t('payment')}</div>
99
- <div>COD</div>
100
- </div>
101
192
  {$note}
193
+ {$beforeTax}
194
+ {$subtotal}
102
195
  {$total}
103
196
  </div>
104
197
  )
@@ -317,18 +317,22 @@ export default class Store {
317
317
  matchHasManuallyClosed = () => !!this.kv.match('hasManuallyClosed') // true if user has ever closed the widget
318
318
  matchClosePreviewAt = () => this.kv.match('close_preview_at') || 0
319
319
 
320
- sendPresence = sb.throttle(async () => {
321
- await this.readyGate.entry()
322
- if (!sb.get(this.kv.match('user'), 'id')) return // store not init
323
- if (sb.isUserBanned(this.kv.match('user'))) return
324
- return this.api.sendPresence(
325
- this.device,
326
- this.browserVisibility === 'visible' && document.hasFocus(),
327
- this._currentViewingProduct.id,
328
- this._currentViewingProduct.source_id,
329
- this._currentViewingProduct.sku,
330
- )
331
- }, 1000)
320
+ sendPresence = sb.throttle(
321
+ async () => {
322
+ await this.readyGate.entry()
323
+ if (!sb.get(this.kv.match('user'), 'id')) return // store not init
324
+ if (sb.isUserBanned(this.kv.match('user'))) return
325
+ return this.api.sendPresence(
326
+ this.device,
327
+ this.browserVisibility === 'visible' && document.hasFocus(),
328
+ this._currentViewingProduct.id,
329
+ this._currentViewingProduct.source_id,
330
+ this._currentViewingProduct.sku,
331
+ )
332
+ },
333
+ 1000, // dont send presence update too tight
334
+ {leading: false}, // dont send right after load. should send after content_view
335
+ )
332
336
 
333
337
  now = () => sb.Date.now() + this.timeDiff // millisecond on server
334
338
 
package/src/util/index.js CHANGED
@@ -32,4 +32,7 @@ sb = Object.assign(sb, extractor)
32
32
  import * as campaign from './campaign.js'
33
33
  sb = Object.assign(sb, campaign)
34
34
 
35
+ import * as order from './order.js'
36
+ sb = Object.assign(sb, order)
37
+
35
38
  export default sb
@@ -0,0 +1,120 @@
1
+ const op = require('./op.js')
2
+ const common = require('./common')
3
+ op.filter = common.filter
4
+ op.map = common.map
5
+ op.each = common.each
6
+ op.size = common.size
7
+ op.find = common.find
8
+
9
+ export function getProductPropName(product, seperator = ' - ') {
10
+ let options = op.get(product, 'options', [])
11
+ let props = op.get(product, 'props', [])
12
+ let multiPropsOptions = op.filter(options, (option) => op.size(option.values) > 1)
13
+ let names = []
14
+ op.each(multiPropsOptions, (option) => {
15
+ let prop = op.find(props, (prop) => prop.key === option.key) || {}
16
+ if (prop.value) names.push(prop.value)
17
+ })
18
+ return names.join(seperator)
19
+ }
20
+
21
+ export function getOrderDisplayName(order = {}) {
22
+ return order.number || order.id || ''
23
+ }
24
+
25
+ export function displayOrderShippingAddress(order, isShorten) {
26
+ let addr = op.get(order, 'shipping.address', {})
27
+ let fulladdr = [addr.address, addr.ward, addr.district, addr.province]
28
+ .filter((a) => a)
29
+ .join(', ')
30
+ .trim()
31
+ if (isShorten) fulladdr = shortenAddr(fulladdr)
32
+ return fulladdr
33
+ }
34
+
35
+ function shortenAddr(str) {
36
+ str = str || ''
37
+ str = str.replace(/Hà Nội/gi, 'HN')
38
+ str = str.replace(/Hồ Chí Minh/gi, 'HCM')
39
+ str = str.replace(/Thành Phố/gi, 'TP')
40
+
41
+ return str
42
+ }
43
+
44
+ export function calcOrderTotal(order) {
45
+ // compute subtotal first, since it doesn't depend on tax or gloal discount
46
+ let subtotal = 0
47
+ let beforeTaxDiscount = 0
48
+ let ordervalue = 0
49
+ op.map(order.items, (item) => {
50
+ if (!item.product) return 0
51
+ let price = op.get(item, 'product.price', 0) * (item.quantity || 0)
52
+ ordervalue += price
53
+
54
+ // some products that are discounted before tax
55
+ // the discount amount is calculated in product price for
56
+ // simpler calucation in future
57
+ if (item.discount_type == 'percentage') {
58
+ if (item.discount_percentage > 0) {
59
+ beforeTaxDiscount += (price * item.discount_percentage) / 10000
60
+ price = price * (1 - (item.discount_percentage || 0) / 10000)
61
+ }
62
+ } else if (item.discount_type == 'amount') {
63
+ if (item.discount_amount > 0) {
64
+ beforeTaxDiscount += item.discount_amount
65
+ price = price - (item.discount_amount || 0)
66
+ if (price <= 0) price = 0
67
+ }
68
+ }
69
+ item.total = price
70
+ subtotal += price
71
+ })
72
+
73
+ // TAX
74
+ let taxM = {}
75
+ op.map(order.items, (item, i) => {
76
+ if (!item.product) return
77
+ let tax = item.product.tax
78
+ if (!tax) return
79
+ if (!tax.id) return
80
+ let taxprice = (item.total * tax.percentage) / 10000
81
+ item.total_tax = taxprice
82
+ taxM[i] = {tax: tax, taxprice: taxprice}
83
+ })
84
+
85
+ let totaltax = 0
86
+ op.map(taxM, (t) => (totaltax += t.taxprice))
87
+
88
+ let computed_discount = 0
89
+ if (order.discount_type == 'percentage' && order.discount_percentage > 0) {
90
+ computed_discount = ((subtotal + totaltax) * order.discount_percentage) / 10000
91
+ } else if (order.discount_type == 'amount') {
92
+ computed_discount = order.discount_amount || 0
93
+ }
94
+
95
+ // SHIP
96
+ let shippingfee = op.get(order, 'shipping.nominal_fee', 0)
97
+ if (shippingfee > 0) {
98
+ if (op.get(order, 'shipping.tax.id')) {
99
+ let tax = order.shipping.tax
100
+ let taxprice = shippingfee * (tax.percentage / 10000)
101
+ taxM['ship'] = {tax: tax, taxprice: taxprice}
102
+ order.shipping.total_tax = taxprice
103
+ }
104
+ }
105
+
106
+ totaltax = 0
107
+ op.map(taxM, (t) => (totaltax += t.taxprice))
108
+ let total = subtotal + totaltax - computed_discount + shippingfee
109
+
110
+ // add adjustment
111
+ total += order.adjustment || 0
112
+ if (total < 0) total = 0
113
+ order.subtotal = subtotal
114
+ order._computed_discount = computed_discount
115
+ order._taxM = taxM
116
+ order.total = total
117
+ order.total_tax = totaltax
118
+ order._discount_before_tax = beforeTaxDiscount
119
+ order._order_value = ordervalue
120
+ }