ey-commerce-lib 1.0.0__py3-none-any.whl

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.

Potentially problematic release.


This version of ey-commerce-lib might be problematic. Click here for more details.

@@ -0,0 +1,337 @@
1
+ import re
2
+
3
+ from ey_commerce_lib.dxm.schemas.order import DxmOrderRule, DxmOrderRuleCond, DxmOrderReviewRule, DxmOrderLogisticsRule
4
+ from ey_commerce_lib.utils.dxm import get_remaining_ship_time, get_data_custom_mark_to_str
5
+ from ey_commerce_lib.utils.list_util import get_str_list_first_not_blank_or_none
6
+ from lxml import html
7
+
8
+
9
+ def get_single_order_item_list(order_element: html.HtmlElement):
10
+ """
11
+ 获取单个订单项的订单列表
12
+ :return:
13
+ """
14
+ sku_list = list()
15
+ sku_element_list = order_element.xpath('.//tr[contains(@class, "pairProInfo")]')
16
+ for sku_element in sku_element_list:
17
+ sku = sku_element.xpath('.//a[contains(@class, "pairProInfoSku")]/text()')[0]
18
+ img = get_str_list_first_not_blank_or_none(sku_element.xpath('.//img/@data-original'))
19
+ quantity = sku_element.xpath('.//p[@class="limingcentUrlpicson"]/span/text()')[0]
20
+ # 价格
21
+ currency, price = \
22
+ sku_element.xpath('.//p[@class="limingcentUrlpicson"]/following-sibling::p[1]/text()')[
23
+ 0].split(' ')
24
+ # 变种
25
+ variants = sku_element.xpath(
26
+ './/p[@class="pairProInfoName orderPairNameIsOverlength"]/span[@class="isOverLengThHide"]/text()')
27
+ variant_list = list()
28
+ for variant in variants:
29
+ variant_list.append({
30
+ 'name': variant.split(':')[0].strip(),
31
+ 'value': variant.split(':')[1].strip()
32
+ })
33
+ sku_list.append({
34
+ 'sku': sku,
35
+ 'quantity': int(quantity),
36
+ 'price': float(price.replace(',', '')),
37
+ 'currency': currency,
38
+ 'img': img,
39
+ 'variants': variant_list
40
+ })
41
+ # 订单金额
42
+ currency, price = order_element.xpath('./td[2]/text()')[0].strip().split(' ')
43
+ recipient = None
44
+ country = None
45
+ if order_element.xpath('.//td[3]/span/text()')[0] != '「」':
46
+ # 收件人
47
+ recipient = order_element.xpath('.//td[3]/span/text()')[0]
48
+ country = order_element.xpath('.//td[3]/span/text()')[1].replace('「', '').replace('」', '')
49
+ # 订单号
50
+ order_id = order_element.xpath('.//td[4]//a[contains(@class, "limingcentUrlpic")]/text()')[0]
51
+ # 备注
52
+ remark = get_str_list_first_not_blank_or_none(
53
+ order_element.xpath('.//td[4]//span[contains(@class, "buyerNotes")]/@data-content'))
54
+ # 下单时间等信息
55
+ time_element_list = order_element.xpath('.//td[5]/div')
56
+ time_dict = dict()
57
+ for time_element in time_element_list:
58
+ # 如果存在id,说明是剩余发货时间需要动态计算
59
+ if len(time_element.xpath('@id')) > 0:
60
+ script_str = time_element.xpath('./script/text()')[0].strip()
61
+ m = re.search(r'addTimer\(".*?",\s*(\d+),', script_str)
62
+ remaining_ship_time = None
63
+ if m:
64
+ # 剩余发货时间
65
+ remaining_ship_time = get_remaining_ship_time(int(m.group(1)))
66
+ # 如果是None就代表已到期
67
+ time_dict['remaining_ship_time'] = remaining_ship_time
68
+
69
+ else:
70
+ time_name, time_value = time_element.xpath('text()')[0].split(":")
71
+ if time_name == '下单':
72
+ time_dict['order_time'] = time_value
73
+ elif time_name == '付款':
74
+ time_dict['pay_time'] = time_value
75
+ elif time_name == '发货':
76
+ time_dict['ship_time'] = time_value
77
+ elif time_name == '提交':
78
+ time_dict['submit_time'] = time_value
79
+ else:
80
+ time_dict[time_name] = time_value
81
+ return [{
82
+ 'sku_list': sku_list,
83
+ 'currency': currency,
84
+ 'price': float(price.replace(',', '')),
85
+ 'recipient': recipient,
86
+ 'country': country,
87
+ 'order_id': order_id,
88
+ 'remark': remark,
89
+ 'time': time_dict
90
+ }]
91
+
92
+
93
+ def get_merge_order_item_list(order_element_list: list[html.HtmlElement]):
94
+ """
95
+ 解析获取合并单的订单项列表
96
+ :return:
97
+ """
98
+ order_item_list = list()
99
+ for order_element in order_element_list:
100
+ order_item_list.extend(get_single_order_item_list(order_element))
101
+ return order_item_list
102
+
103
+
104
+ def get_order_buyer_select_provider(good_element: html.HtmlElement):
105
+ """
106
+ 获取买家指定
107
+ :return:
108
+ """
109
+ # 第一种情况
110
+ buyer_select_provider_list = good_element.xpath('.//span[contains(@class, "buyerSelectProvider")]/text()')
111
+ # 第二种情况
112
+ buyer_select_provider_2_list = good_element.xpath('./td[3]/text()')[0].strip().split(":")
113
+ if len(buyer_select_provider_list) > 0:
114
+ buyer_select_provider = buyer_select_provider_list[0]
115
+ elif len(buyer_select_provider_2_list) == 2:
116
+ buyer_select_provider = buyer_select_provider_2_list[1].strip()
117
+ else:
118
+ buyer_select_provider = None
119
+ return buyer_select_provider
120
+
121
+
122
+ def list_order_base_by_html(html_str: str) -> list[dict]:
123
+ """
124
+ 获取基本的订单分页信息
125
+ 有三种情况:
126
+ 1. 合并单
127
+ 2. 一单多件
128
+ 3. 普通一单一件
129
+ :param html_str:
130
+ :return:
131
+ """
132
+ tree = html.fromstring(html_str)
133
+ good_element_list = tree.xpath('//tbody[@class="xianshishujudate"]/tr[@class="goodsId"]')
134
+ order_list = list()
135
+ table_columns = tree.xpath('//table[@id="orderListTable"]/thead/tr/th/text()')
136
+ # 是否又物流方式
137
+ has_logistics = True if '物流方式' in table_columns else False
138
+ for good_element in good_element_list:
139
+ # 包裹号
140
+ package_number = good_element.xpath('.//a[@class="limingcentUrlpic"]/text()')[0]
141
+ # 包裹id
142
+ package_id = good_element.xpath('.//input[@class="input1"]/@value')[0]
143
+ # authid 物流方式的id
144
+ auth_id = good_element.xpath(f'.//input[@id="dxmAuthId{package_id}"]/@value')[0]
145
+ buyer_select_provider = get_order_buyer_select_provider(good_element)
146
+ order_form_source = good_element.xpath('.//span[contains(@class, "order-form-source")]/text()')[0]
147
+ platform, shop = order_form_source.replace('「', '').replace('」', '').split(':')
148
+ order_class = f"orderId_{package_id}"
149
+ order_element_list = tree.xpath(f'//tr[@class="{order_class}"]')
150
+ order_first_element = order_element_list[0]
151
+ # 订单备注
152
+ hover_prompt_content_list = good_element.xpath('.//span[contains(@class, "hoverPrompt")]/@data-content')
153
+ # 物流方式, 要先检查是否包含物流方式这一列
154
+ logistics_info = None
155
+ if has_logistics:
156
+ # 物流方式
157
+ logistics_list = order_first_element.xpath('.//td[6]/span/a/text()')
158
+ # 可能又不存在的物流方式情况发生
159
+ if len(logistics_list) > 0:
160
+ logistics_info = dict()
161
+ logistics = logistics_list[0].strip()
162
+ # 物流单号
163
+ logistics_info['track_number'] = get_str_list_first_not_blank_or_none(
164
+ order_first_element.xpath('.//td[6]//span[contains(@class, "limingcentUrlpicson")]/a/text()'))
165
+ logistics_info['logistics'] = logistics
166
+ # 状态
167
+ if has_logistics:
168
+ status = order_first_element.xpath('.//td[7]/text()')[0].strip()
169
+ else:
170
+ status = order_first_element.xpath('.//td[6]/text()')[0].strip()
171
+ # 整理订单
172
+ if len(order_element_list) > 1:
173
+ # 合并单
174
+ order_item_list = get_merge_order_item_list(order_element_list)
175
+ else:
176
+ # 普通单
177
+ order_item_list = get_single_order_item_list(order_element_list[0])
178
+ order_list.append({
179
+ 'package_id': package_id,
180
+ 'auth_id': auth_id,
181
+ 'package_number': package_number,
182
+ 'buyer_select_provider': buyer_select_provider,
183
+ 'platform': platform,
184
+ 'shop': shop,
185
+ 'hover_prompt_content_list': hover_prompt_content_list,
186
+ 'logistics_info': logistics_info,
187
+ 'status': status,
188
+ 'order_item_list': order_item_list
189
+ })
190
+ return order_list
191
+
192
+
193
+ def list_order_rule(html_str: str):
194
+ """
195
+ 获取订单规则列表
196
+ :return:
197
+ """
198
+ tree = html.fromstring(html_str)
199
+ rule_element_list = tree.xpath('//tbody[@id="ruleTbody"]/tr[@class="content rulesTr"]')
200
+ rule_list = list()
201
+ for rule_element in rule_element_list:
202
+ rule_id = rule_element.xpath('./td[1]/input/@data-id')[0]
203
+ rule_name = rule_element.xpath('./td[2]/text()')[0]
204
+ rule_list.append({
205
+ 'rule_id': rule_id,
206
+ 'rule_name': rule_name
207
+ })
208
+ return rule_list
209
+
210
+
211
+ def get_rule_detail(html_str: str) -> DxmOrderRule:
212
+ """
213
+ 根据订单规则的html获取订单规则详情
214
+ :param html_str:
215
+ :return:
216
+ """
217
+ tree = html.fromstring(html_str)
218
+ cond_element_list = tree.xpath('//ul[@id="chooseSelectUl"]/li')
219
+ cond_list = list()
220
+ # 获取规则的条件
221
+ for cond_element in cond_element_list:
222
+ cond_val = cond_element.xpath('./input[@name="condVal"]/@value')[0]
223
+ cond_id = cond_element.xpath('./input[@name="condId"]/@value')[0]
224
+ cond_name = cond_element.xpath('./input[@name="condName"]/@value')[0]
225
+ cond_unit = cond_element.xpath('./input[@name="condUnit"]/@value')[0]
226
+ cond_type = cond_element.xpath('./input[@name="condType"]/@value')[0]
227
+ cond_list.append(DxmOrderRuleCond(cond_val=cond_val,
228
+ cond_id=cond_id,
229
+ cond_name=cond_name,
230
+ cond_unit=cond_unit,
231
+ cond_type=cond_type))
232
+ # 判断是物流规则还是审核规则
233
+ modal_title = tree.xpath('//h4[@class="modal-title"]/text()')[0]
234
+ rule_id = tree.xpath('//input[@name="id"]/@value')[0]
235
+ rule_type = tree.xpath('//input[@name="type"]/@value')[0]
236
+ rule_name = tree.xpath('//input[@id="ruleName"]/@value')[0]
237
+
238
+ kfbz = get_str_list_first_not_blank_or_none(tree.xpath('//textarea[@name="kfbz"]/text()'))
239
+ jhbz = get_str_list_first_not_blank_or_none(tree.xpath('//textarea[@name="jhbz"]/@value'))
240
+ jh_color = tree.xpath('//a[contains(@class, "bgCommentFocus")]/@data-val')[0]
241
+ other_action_checked_list = tree.xpath('//input[@name="otherAction"]/@checked')
242
+ other_action = 'on' if len(other_action_checked_list) > 0 else ''
243
+ custom_mark = tree.xpath('//div[contains(@class, "customMarkRuleEl")]/@data-custom-mark')[0]
244
+
245
+ if modal_title.count('物流') > 0:
246
+ warehouse_element_list = tree.xpath('//select[@id="ruleWareIdSelect"]//option')
247
+ warehouse_id = ''
248
+ distribute_type = ''
249
+ for warehouse_element in warehouse_element_list:
250
+ if warehouse_element.get('selected') == 'selected':
251
+ warehouse_id = warehouse_element.get('value')
252
+ distribute_type_element_list = tree.xpath('//input[@name="distributeType"]')
253
+ for distribute_type_element in distribute_type_element_list:
254
+ if distribute_type_element.get('checked') == 'checked':
255
+ distribute_type = distribute_type_element.get('value')
256
+ auth_ids = tree.xpath('//input[@name="authIds"]/@value')[0]
257
+ pattern = re.compile(r"if\s*\(\s*'(\d+)'\s*===\s*item\.idStr")
258
+ auth_id = pattern.findall(html_str)[0]
259
+
260
+ # 物流规则
261
+ dxm_rule_order = DxmOrderLogisticsRule(dxm_cond_list=cond_list, id=rule_id, type=rule_type,
262
+ rule_name=rule_name, kfbz=kfbz, jhbz=jhbz,
263
+ jh_color=jh_color, other_action=other_action,
264
+ custom_mark=get_data_custom_mark_to_str(custom_mark),
265
+ ware_id=warehouse_id, auth_id=auth_id, distribute_type=distribute_type,
266
+ auth_ids=auth_ids
267
+ )
268
+ else:
269
+ action = tree.xpath('//input[@name="action"]/@value')[0]
270
+ dxm_rule_order = DxmOrderReviewRule(dxm_cond_list=cond_list, id=rule_id, type=rule_type,
271
+ rule_name=rule_name, kfbz=kfbz, jhbz=jhbz,
272
+ jh_color=jh_color, other_action=other_action,
273
+ custom_mark=get_data_custom_mark_to_str(custom_mark),
274
+ action=action
275
+ )
276
+
277
+ return dxm_rule_order
278
+
279
+
280
+ def get_order_detail_by_html(html_str: str):
281
+ tree = html.fromstring(html_str)
282
+ recipient = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="detailContact1"]/@data-info'))
283
+ phone = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="detailPhone1"]/@data-info'))
284
+ mobile = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="detailMobile1"]/@data-info'))
285
+ country = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="detailCountry1"]/@data-country'))
286
+ province = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="detailProvince1"]/text()'))
287
+ city = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="detailCity1"]/text()'))
288
+ addr1 = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="detailAddr11"]/@data-info'))
289
+ addr2 = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="detailAddress21"]/@data-info'))
290
+ company = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="companyName1"]/text()'))
291
+ apartment = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="apartmentNumber1"]/text()'))
292
+ zip_code = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="detailZip1"]/text()'))
293
+ tax_number = get_str_list_first_not_blank_or_none(tree.xpath('//div[@id="taxNumber1"]/text()'))
294
+
295
+ pair_info_element_list = tree.xpath('//tr[@class="orderInfoCon pairProInfoBox"]')
296
+ ids = re.findall(r"var\s+storageId\s*=\s*['\"](\d+)['\"]", html_str)
297
+ # 有就取第一个,否则 None
298
+ storage_id = ids[0] if ids else None
299
+ pair_info_list = []
300
+ for pair_info_element in pair_info_element_list:
301
+ pair_info_sku = pair_info_element.xpath('.//span[@class="pairProInfoSku"]/text()')[0].split(' x')[0].strip()
302
+ pair_info_sku_quantity = int(pair_info_element.xpath('.//span[@class="pairProInfoSku"]/span/text()')[0].strip())
303
+ # warehouse_sku, warehouse_sku_quantity = pair_info_element.xpath(
304
+ # './/div[contains(@class, "normalDiv")]/p[1]/text()')[0].split(' x ')
305
+ warehouse_sku_info_list = pair_info_element.xpath('.//div[contains(@class, "normalDiv")]/p[1]/text()')
306
+ warehouse_sku, warehouse_sku_quantity = None, None
307
+ if len(warehouse_sku_info_list) > 0:
308
+ warehouse_sku_info = warehouse_sku_info_list[0]
309
+ warehouse_sku, warehouse_sku_quantity = warehouse_sku_info.split(' x ')
310
+
311
+ warehouse_available_quantity_element_list = pair_info_element.xpath(
312
+ './/div[contains(@class, "normalDiv")]/p[2]/span[2]/text()')
313
+ warehouse_available_quantity = int(warehouse_available_quantity_element_list[0].strip()) if len(
314
+ warehouse_available_quantity_element_list) > 0 else None
315
+ pair_info_list.append({
316
+ 'pair_info_sku': pair_info_sku,
317
+ 'pair_info_sku_quantity': pair_info_sku_quantity,
318
+ 'warehouse_sku': warehouse_sku,
319
+ 'warehouse_sku_quantity': warehouse_sku_quantity,
320
+ 'warehouse_available_quantity': warehouse_available_quantity
321
+ })
322
+ return {
323
+ 'recipient': recipient,
324
+ 'phone': phone,
325
+ 'mobile': mobile,
326
+ 'country': country,
327
+ 'province': province,
328
+ 'city': city,
329
+ 'addr1': addr1,
330
+ 'addr2': addr2,
331
+ 'company': company,
332
+ 'apartment': apartment,
333
+ 'zip_code': zip_code,
334
+ 'tax_number': tax_number,
335
+ 'pair_info_list': pair_info_list,
336
+ 'storage_id': storage_id
337
+ }
@@ -0,0 +1,40 @@
1
+ from lxml import html
2
+
3
+
4
+ def list_purchasing_all(html_str: str):
5
+ tree = html.fromstring(html_str)
6
+ good_element_list: list[html.HtmlElement] = tree.xpath('//tr[contains(@class, "goodsId")]')
7
+ for good_element in good_element_list:
8
+ purchase_id = good_element.xpath('@id')[0]
9
+ purchase_order_number = good_element.xpath('.//a[@class="limingcentUrlpic"]/text()')[0]
10
+ ghs_id = good_element.xpath('.//span[contains(@class, "ghsHoverPrompt")]/@data-ghsid')[0]
11
+ ghs_name = good_element.xpath('.//span[contains(@class, "ghsHoverPrompt")]/span/text()')[0].strip()
12
+ warehouse_name, purchase_info = good_element.xpath('.//td[4]/span/text()')[0].strip().split('|')
13
+ # 采购员
14
+ agent_name = purchase_info.split(':')[1]
15
+ #print(purchase_order_number, ghs_id, ghs_name, warehouse_name, agent_name)
16
+ # 获取兄弟元素
17
+ content_element = good_element.getnext()
18
+ # 获取商品信息
19
+ img = content_element.xpath('.//img/@data-original')[0]
20
+ content_number_info = content_element.xpath('./td[2]/text()')
21
+ product_zl_number = None
22
+ purchase_number = None
23
+ for content_number in content_number_info:
24
+ content_number = content_number.strip()
25
+ if content_number.split(':')[0] == '商品种类':
26
+ product_zl_number = content_number.split(':')[1].strip()
27
+ elif content_number.split(':')[0] == '采购数量':
28
+ purchase_number = content_number.split(':')[1].strip()
29
+ # 解析出货款
30
+ total_amount = content_element.xpath('./td[3]//input/@data-totalamount')[0]
31
+ # 运费
32
+ shipping_amount = content_element.xpath('./td[3]//input/@value')[0]
33
+ platform_list = content_element.xpath('./td[4]/div/span')
34
+ if len(platform_list) > 0:
35
+ source = platform_list[0].text.strip()
36
+ else:
37
+ source = content_element.xpath('./td[4]/span/span/text()')[0].strip()
38
+ source2 = content_element.xpath('./td[4]/span/span[@class="alibabaPurchaseOrder"]/text()')[0].strip()
39
+ source = source + source2
40
+ print(source)
@@ -0,0 +1,65 @@
1
+ from ey_commerce_lib.dxm.schemas.warehouse import WarehouseProduct
2
+ from lxml import html
3
+
4
+
5
+ def __get_warehouse_product_by_xpath_element(warehouse_product_element: html.HtmlElement) -> WarehouseProduct:
6
+ sku = warehouse_product_element.xpath('.//span[contains(@class, "productSku")]/text()')[0].strip()
7
+ name_list = warehouse_product_element.xpath('.//p[contains(@class, "name")]/text()')
8
+ name = name_list[0].strip() if len(name_list) > 0 else ''
9
+ sku_code = warehouse_product_element.xpath('.//div[contains(@class, "skuCode")]/span/text()')[0].strip()
10
+ img = warehouse_product_element.xpath('.//img/@src')[0].strip()
11
+ shelf_position = warehouse_product_element.xpath('.//td[3]/text()')[0].strip()
12
+ # 库存详情元素
13
+ warehouse_product_stock_element = warehouse_product_element.xpath('.//td[4]//tbody/tr')[0]
14
+ # 安全库存
15
+ try:
16
+ safe_stock = int(warehouse_product_stock_element.xpath('./td[1]/span/text()')[0])
17
+ except:
18
+ safe_stock = None
19
+ try:
20
+ # # 在途库存
21
+ on_the_way_stock = int(warehouse_product_stock_element.xpath('./td[2]/span/text()')[0])
22
+ except:
23
+ on_the_way_stock = None
24
+ # 未发库存
25
+ try:
26
+ not_shipped_stock = int(warehouse_product_stock_element.xpath('./td[3]/span/text()')[0])
27
+ except:
28
+ not_shipped_stock = None
29
+ try:
30
+ # # 占用库存
31
+ occupy_stock = int(warehouse_product_stock_element.xpath('./td[4]/span/text()')[0])
32
+ except:
33
+ occupy_stock = None
34
+ # # 可用库存
35
+ try:
36
+ available_stock = int(warehouse_product_stock_element.xpath('./td[5]/span/text()')[0])
37
+ except:
38
+ available_stock = None
39
+ # 总量库存
40
+ try:
41
+ total_stock = int(warehouse_product_stock_element.xpath('./td[6]/span/text()')[0])
42
+ except:
43
+ total_stock = None
44
+ # 单价
45
+ price = float(warehouse_product_element.xpath('./td[5]/text()')[0])
46
+ # 总价
47
+ total_price = float(warehouse_product_element.xpath('./td[6]/text()')[0])
48
+ # 更新时间
49
+ update_time = warehouse_product_element.xpath('./td[7]/div[1]/text()')[0]
50
+ # 创建时间
51
+ create_time = warehouse_product_element.xpath('./td[7]/div[2]/text()')[0]
52
+ return WarehouseProduct(sku=sku, name=name, sku_code=sku_code, img=img, shelf_position=shelf_position,
53
+ safe_stock=safe_stock, on_the_way_stock=on_the_way_stock,
54
+ not_shipped_stock=not_shipped_stock, occupy_stock=occupy_stock,
55
+ available_stock=available_stock, total_stock=total_stock, price=price,
56
+ total_price=total_price, update_time=update_time, create_time=create_time)
57
+
58
+
59
+ def list_warehouse_product(html_content: str) -> list[WarehouseProduct]:
60
+ tree = html.fromstring(html_content)
61
+ warehouse_product_element_list = tree.xpath('//tbody/tr[@class="content"]')
62
+ warehouse_product_list = list()
63
+ for warehouse_product_element in warehouse_product_element_list:
64
+ warehouse_product_list.append(__get_warehouse_product_by_xpath_element(warehouse_product_element))
65
+ return warehouse_product_list
File without changes
@@ -0,0 +1,13 @@
1
+ from typing import Generic, TypeVar
2
+
3
+ from pydantic import BaseModel
4
+
5
+ T = TypeVar("T")
6
+
7
+
8
+ class Page(BaseModel, Generic[T]):
9
+ data: list[T]
10
+ page_size: int
11
+ total_page: int
12
+ total_size: int
13
+ page_number: int