@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.
- package/assets/less/chatbox.less +8 -0
- package/assets/less/message.less +8 -3
- package/assets/less/order.less +10 -0
- package/compiled_src/chatbox/OrderAttachments.js +97 -16
- package/compiled_src/store/store.js +4 -1
- package/compiled_src/util/index.js +2 -0
- package/compiled_src/util/order.js +118 -0
- package/package.json +4 -5
- package/src/chatbox/OrderAttachments.js +111 -18
- package/src/store/store.js +16 -12
- package/src/util/index.js +3 -0
- package/src/util/order.js +120 -0
package/assets/less/chatbox.less
CHANGED
|
@@ -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';
|
package/assets/less/message.less
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/assets/less/order.less
CHANGED
|
@@ -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: "
|
|
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, '
|
|
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
|
-
|
|
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
|
|
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",
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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 (
|
|
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
|
-
},
|
|
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.
|
|
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.
|
|
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
|
-
"
|
|
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": "^
|
|
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='
|
|
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, '
|
|
44
|
+
src={sb.get(product, 'image', require('../../assets/img/default.jpg'))}
|
|
42
45
|
/>
|
|
43
46
|
<div style='overflow: hidden !important;'>
|
|
44
|
-
<div
|
|
45
|
-
|
|
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} · {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
|
|
60
|
-
|
|
61
|
-
<
|
|
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 (
|
|
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
|
|
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'
|
|
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
|
)
|
package/src/store/store.js
CHANGED
|
@@ -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(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
this.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
@@ -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
|
+
}
|