wxq
This commit is contained in:
commit
e279a7fa6a
|
|
@ -0,0 +1,3 @@
|
|||
*.xlsx filter=lfs diff=lfs merge=lfs -text
|
||||
*.csv filter=lfs diff=lfs merge=lfs -text
|
||||
*.txt filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
*.pyc
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Python: Current File",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": false,
|
||||
"env": {
|
||||
"PYTHONPATH": "C:\\Users\\Admin\\Desktop\\test\\logistics"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,197 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from utils.gtools import MySQLconnect\n",
|
||||
"import pandas as pd\n",
|
||||
"ads = MySQLconnect('ads')\n",
|
||||
"engine = ads.engine()\n",
|
||||
"conn = ads.connect()\n",
|
||||
"# sql = \"\"\" SELECT\n",
|
||||
"# sku.SKU,\n",
|
||||
"# 包裹数据,\n",
|
||||
"# 成本价\n",
|
||||
"# FROM\n",
|
||||
"# ods.`stg_bayshop_litfad_sku` sku\n",
|
||||
"# LEFT JOIN ads.new_erp_sku_size size ON sku.SKU=size.SKU\n",
|
||||
"# \t\t\t\t\t\t\t WHERE sku.状态 = \"启用\"\n",
|
||||
"# \t\t\t\t\t\t\t AND EXISTS (SELECT 1 FROM ods.stg_bayshop_litfad_spu s1 where s1.`产品PID` = sku.`产品PID` AND s1.状态 = \"正常销售\" and s1.`产品分类` regexp \"light\") AND sku.添加时间 >=\"2024-01-01\"\n",
|
||||
"# \"\"\"\n",
|
||||
"# df = pd.read_sql(sql, conn)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"C:\\Users\\Admin\\AppData\\Local\\Temp\\ipykernel_9392\\3242304332.py:43: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
|
||||
" df = pd.read_sql(sql, conn)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"日期:2024-01-01, 售价计算完成\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"C:\\Users\\Admin\\AppData\\Local\\Temp\\ipykernel_9392\\3242304332.py:43: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
|
||||
" df = pd.read_sql(sql, conn)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"日期:2024-02-01, 售价计算完成\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"C:\\Users\\Admin\\AppData\\Local\\Temp\\ipykernel_9392\\3242304332.py:43: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
|
||||
" df = pd.read_sql(sql, conn)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"日期:2024-03-01, 售价计算完成\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"C:\\Users\\Admin\\AppData\\Local\\Temp\\ipykernel_9392\\3242304332.py:43: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
|
||||
" df = pd.read_sql(sql, conn)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"日期:2024-04-01, 售价计算完成\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"C:\\Users\\Admin\\AppData\\Local\\Temp\\ipykernel_9392\\3242304332.py:43: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
|
||||
" df = pd.read_sql(sql, conn)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ename": "KeyboardInterrupt",
|
||||
"evalue": "",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
|
||||
"\u001b[1;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
|
||||
"Cell \u001b[1;32mIn[3], line 44\u001b[0m\n\u001b[0;32m 26\u001b[0m sql \u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\"\"\u001b[39m\u001b[38;5;124m SELECT\u001b[39m\n\u001b[0;32m 27\u001b[0m \u001b[38;5;124m\tsku.SKU,\u001b[39m\n\u001b[0;32m 28\u001b[0m \u001b[38;5;124m\t包裹数据,\u001b[39m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 41\u001b[0m \u001b[38;5;124m\tAND DATE_FORMAT( sku.添加时间, \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m%Y-%m-01\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m )= \u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mdate\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 42\u001b[0m \u001b[38;5;124m \u001b[39m\u001b[38;5;124m\"\"\"\u001b[39m\n\u001b[0;32m 43\u001b[0m df \u001b[38;5;241m=\u001b[39m pd\u001b[38;5;241m.\u001b[39mread_sql(sql, conn)\n\u001b[1;32m---> 44\u001b[0m df1 \u001b[38;5;241m=\u001b[39m cal_sell_price(df)\n\u001b[0;32m 45\u001b[0m df1\u001b[38;5;241m.\u001b[39mto_excel(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m售价_\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mdate\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m.xlsx\u001b[39m\u001b[38;5;124m'\u001b[39m, index\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[0;32m 46\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m日期:\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mdate\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m, 售价计算完成\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n",
|
||||
"Cell \u001b[1;32mIn[3], line 16\u001b[0m, in \u001b[0;36mcal_sell_price\u001b[1;34m(df)\u001b[0m\n\u001b[0;32m 14\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m 15\u001b[0m package_dict \u001b[38;5;241m=\u001b[39m json\u001b[38;5;241m.\u001b[39mloads(row[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m包裹数据\u001b[39m\u001b[38;5;124m'\u001b[39m])\n\u001b[1;32m---> 16\u001b[0m sell_price,order_price,order_type \u001b[38;5;241m=\u001b[39m call_sell_and_order_price(row[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m成本价\u001b[39m\u001b[38;5;124m'\u001b[39m], package_dict)\n\u001b[0;32m 17\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[0;32m 18\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mrow[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mSKU\u001b[39m\u001b[38;5;124m'\u001b[39m]\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m 报错: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00me\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n",
|
||||
"File \u001b[1;32md:\\test\\logistics\\sell\\sell_price.py:120\u001b[0m, in \u001b[0;36mcall_sell_and_order_price\u001b[1;34m(price, package_dict)\u001b[0m\n\u001b[0;32m 118\u001b[0m \u001b[38;5;66;03m# 修改版本\u001b[39;00m\n\u001b[0;32m 119\u001b[0m sell_price \u001b[38;5;241m=\u001b[39m litfad\u001b[38;5;241m.\u001b[39mcal_sell_price_2025()\n\u001b[1;32m--> 120\u001b[0m order_price, order_type \u001b[38;5;241m=\u001b[39m us_ocean_order_price(packages)\n\u001b[0;32m 121\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mround\u001b[39m(sell_price,\u001b[38;5;241m2\u001b[39m),order_price,order_type\n",
|
||||
"File \u001b[1;32md:\\test\\logistics\\sell\\sell_price.py:23\u001b[0m, in \u001b[0;36mus_ocean_order_price\u001b[1;34m(packages)\u001b[0m\n\u001b[0;32m 21\u001b[0m base_fee \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m999999\u001b[39m\n\u001b[0;32m 22\u001b[0m \u001b[38;5;28;01mbreak\u001b[39;00m\n\u001b[1;32m---> 23\u001b[0m base_fee \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m df1[df1[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mg\u001b[39m\u001b[38;5;124m'\u001b[39m] \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m lbs_weight][\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m费用\u001b[39m\u001b[38;5;124m'\u001b[39m]\u001b[38;5;241m.\u001b[39miloc[\u001b[38;5;241m0\u001b[39m]\n\u001b[0;32m 24\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m package\u001b[38;5;241m.\u001b[39mfst_size \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m116\u001b[39m \u001b[38;5;129;01mor\u001b[39;00m package\u001b[38;5;241m.\u001b[39msed_size \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m71\u001b[39m \u001b[38;5;129;01mor\u001b[39;00m package\u001b[38;5;241m.\u001b[39mgirth \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m251\u001b[39m:\n\u001b[0;32m 25\u001b[0m other_fee \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m16.3\u001b[39m\n",
|
||||
"File \u001b[1;32mc:\\ProgramData\\anaconda3\\Lib\\site-packages\\pandas\\core\\frame.py:4093\u001b[0m, in \u001b[0;36mDataFrame.__getitem__\u001b[1;34m(self, key)\u001b[0m\n\u001b[0;32m 4091\u001b[0m \u001b[38;5;66;03m# Do we have a (boolean) 1d indexer?\u001b[39;00m\n\u001b[0;32m 4092\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m com\u001b[38;5;241m.\u001b[39mis_bool_indexer(key):\n\u001b[1;32m-> 4093\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_getitem_bool_array(key)\n\u001b[0;32m 4095\u001b[0m \u001b[38;5;66;03m# We are left with two options: a single key, and a collection of keys,\u001b[39;00m\n\u001b[0;32m 4096\u001b[0m \u001b[38;5;66;03m# We interpret tuples as collections only for non-MultiIndex\u001b[39;00m\n\u001b[0;32m 4097\u001b[0m is_single_key \u001b[38;5;241m=\u001b[39m \u001b[38;5;28misinstance\u001b[39m(key, \u001b[38;5;28mtuple\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m is_list_like(key)\n",
|
||||
"File \u001b[1;32mc:\\ProgramData\\anaconda3\\Lib\\site-packages\\pandas\\core\\frame.py:4155\u001b[0m, in \u001b[0;36mDataFrame._getitem_bool_array\u001b[1;34m(self, key)\u001b[0m\n\u001b[0;32m 4152\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mcopy(deep\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[0;32m 4154\u001b[0m indexer \u001b[38;5;241m=\u001b[39m key\u001b[38;5;241m.\u001b[39mnonzero()[\u001b[38;5;241m0\u001b[39m]\n\u001b[1;32m-> 4155\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_take_with_is_copy(indexer, axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m0\u001b[39m)\n",
|
||||
"File \u001b[1;32mc:\\ProgramData\\anaconda3\\Lib\\site-packages\\pandas\\core\\generic.py:4153\u001b[0m, in \u001b[0;36mNDFrame._take_with_is_copy\u001b[1;34m(self, indices, axis)\u001b[0m\n\u001b[0;32m 4142\u001b[0m \u001b[38;5;129m@final\u001b[39m\n\u001b[0;32m 4143\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21m_take_with_is_copy\u001b[39m(\u001b[38;5;28mself\u001b[39m, indices, axis: Axis \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m Self:\n\u001b[0;32m 4144\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m 4145\u001b[0m \u001b[38;5;124;03m Internal version of the `take` method that sets the `_is_copy`\u001b[39;00m\n\u001b[0;32m 4146\u001b[0m \u001b[38;5;124;03m attribute to keep track of the parent dataframe (using in indexing\u001b[39;00m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 4151\u001b[0m \u001b[38;5;124;03m See the docstring of `take` for full explanation of the parameters.\u001b[39;00m\n\u001b[0;32m 4152\u001b[0m \u001b[38;5;124;03m \"\"\"\u001b[39;00m\n\u001b[1;32m-> 4153\u001b[0m result \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtake(indices\u001b[38;5;241m=\u001b[39mindices, axis\u001b[38;5;241m=\u001b[39maxis)\n\u001b[0;32m 4154\u001b[0m \u001b[38;5;66;03m# Maybe set copy if we didn't actually change the index.\u001b[39;00m\n\u001b[0;32m 4155\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mndim \u001b[38;5;241m==\u001b[39m \u001b[38;5;241m2\u001b[39m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m result\u001b[38;5;241m.\u001b[39m_get_axis(axis)\u001b[38;5;241m.\u001b[39mequals(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_get_axis(axis)):\n",
|
||||
"File \u001b[1;32mc:\\ProgramData\\anaconda3\\Lib\\site-packages\\pandas\\core\\generic.py:4133\u001b[0m, in \u001b[0;36mNDFrame.take\u001b[1;34m(self, indices, axis, **kwargs)\u001b[0m\n\u001b[0;32m 4128\u001b[0m \u001b[38;5;66;03m# We can get here with a slice via DataFrame.__getitem__\u001b[39;00m\n\u001b[0;32m 4129\u001b[0m indices \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39marange(\n\u001b[0;32m 4130\u001b[0m indices\u001b[38;5;241m.\u001b[39mstart, indices\u001b[38;5;241m.\u001b[39mstop, indices\u001b[38;5;241m.\u001b[39mstep, dtype\u001b[38;5;241m=\u001b[39mnp\u001b[38;5;241m.\u001b[39mintp\n\u001b[0;32m 4131\u001b[0m )\n\u001b[1;32m-> 4133\u001b[0m new_data \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_mgr\u001b[38;5;241m.\u001b[39mtake(\n\u001b[0;32m 4134\u001b[0m indices,\n\u001b[0;32m 4135\u001b[0m axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_get_block_manager_axis(axis),\n\u001b[0;32m 4136\u001b[0m verify\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m,\n\u001b[0;32m 4137\u001b[0m )\n\u001b[0;32m 4138\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_constructor_from_mgr(new_data, axes\u001b[38;5;241m=\u001b[39mnew_data\u001b[38;5;241m.\u001b[39maxes)\u001b[38;5;241m.\u001b[39m__finalize__(\n\u001b[0;32m 4139\u001b[0m \u001b[38;5;28mself\u001b[39m, method\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtake\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 4140\u001b[0m )\n",
|
||||
"File \u001b[1;32mc:\\ProgramData\\anaconda3\\Lib\\site-packages\\pandas\\core\\internals\\managers.py:894\u001b[0m, in \u001b[0;36mBaseBlockManager.take\u001b[1;34m(self, indexer, axis, verify)\u001b[0m\n\u001b[0;32m 891\u001b[0m indexer \u001b[38;5;241m=\u001b[39m maybe_convert_indices(indexer, n, verify\u001b[38;5;241m=\u001b[39mverify)\n\u001b[0;32m 893\u001b[0m new_labels \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39maxes[axis]\u001b[38;5;241m.\u001b[39mtake(indexer)\n\u001b[1;32m--> 894\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mreindex_indexer(\n\u001b[0;32m 895\u001b[0m new_axis\u001b[38;5;241m=\u001b[39mnew_labels,\n\u001b[0;32m 896\u001b[0m indexer\u001b[38;5;241m=\u001b[39mindexer,\n\u001b[0;32m 897\u001b[0m axis\u001b[38;5;241m=\u001b[39maxis,\n\u001b[0;32m 898\u001b[0m allow_dups\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m,\n\u001b[0;32m 899\u001b[0m copy\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m,\n\u001b[0;32m 900\u001b[0m )\n",
|
||||
"File \u001b[1;32mc:\\ProgramData\\anaconda3\\Lib\\site-packages\\pandas\\core\\internals\\managers.py:687\u001b[0m, in \u001b[0;36mBaseBlockManager.reindex_indexer\u001b[1;34m(self, new_axis, indexer, axis, fill_value, allow_dups, copy, only_slice, use_na_proxy)\u001b[0m\n\u001b[0;32m 680\u001b[0m new_blocks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_slice_take_blocks_ax0(\n\u001b[0;32m 681\u001b[0m indexer,\n\u001b[0;32m 682\u001b[0m fill_value\u001b[38;5;241m=\u001b[39mfill_value,\n\u001b[0;32m 683\u001b[0m only_slice\u001b[38;5;241m=\u001b[39monly_slice,\n\u001b[0;32m 684\u001b[0m use_na_proxy\u001b[38;5;241m=\u001b[39muse_na_proxy,\n\u001b[0;32m 685\u001b[0m )\n\u001b[0;32m 686\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m--> 687\u001b[0m new_blocks \u001b[38;5;241m=\u001b[39m [\n\u001b[0;32m 688\u001b[0m blk\u001b[38;5;241m.\u001b[39mtake_nd(\n\u001b[0;32m 689\u001b[0m indexer,\n\u001b[0;32m 690\u001b[0m axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m,\n\u001b[0;32m 691\u001b[0m fill_value\u001b[38;5;241m=\u001b[39m(\n\u001b[0;32m 692\u001b[0m fill_value \u001b[38;5;28;01mif\u001b[39;00m fill_value \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m blk\u001b[38;5;241m.\u001b[39mfill_value\n\u001b[0;32m 693\u001b[0m ),\n\u001b[0;32m 694\u001b[0m )\n\u001b[0;32m 695\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m blk \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mblocks\n\u001b[0;32m 696\u001b[0m ]\n\u001b[0;32m 698\u001b[0m new_axes \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlist\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39maxes)\n\u001b[0;32m 699\u001b[0m new_axes[axis] \u001b[38;5;241m=\u001b[39m new_axis\n",
|
||||
"File \u001b[1;32mc:\\ProgramData\\anaconda3\\Lib\\site-packages\\pandas\\core\\internals\\managers.py:688\u001b[0m, in \u001b[0;36m<listcomp>\u001b[1;34m(.0)\u001b[0m\n\u001b[0;32m 680\u001b[0m new_blocks \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_slice_take_blocks_ax0(\n\u001b[0;32m 681\u001b[0m indexer,\n\u001b[0;32m 682\u001b[0m fill_value\u001b[38;5;241m=\u001b[39mfill_value,\n\u001b[0;32m 683\u001b[0m only_slice\u001b[38;5;241m=\u001b[39monly_slice,\n\u001b[0;32m 684\u001b[0m use_na_proxy\u001b[38;5;241m=\u001b[39muse_na_proxy,\n\u001b[0;32m 685\u001b[0m )\n\u001b[0;32m 686\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m 687\u001b[0m new_blocks \u001b[38;5;241m=\u001b[39m [\n\u001b[1;32m--> 688\u001b[0m blk\u001b[38;5;241m.\u001b[39mtake_nd(\n\u001b[0;32m 689\u001b[0m indexer,\n\u001b[0;32m 690\u001b[0m axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m,\n\u001b[0;32m 691\u001b[0m fill_value\u001b[38;5;241m=\u001b[39m(\n\u001b[0;32m 692\u001b[0m fill_value \u001b[38;5;28;01mif\u001b[39;00m fill_value \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m blk\u001b[38;5;241m.\u001b[39mfill_value\n\u001b[0;32m 693\u001b[0m ),\n\u001b[0;32m 694\u001b[0m )\n\u001b[0;32m 695\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m blk \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mblocks\n\u001b[0;32m 696\u001b[0m ]\n\u001b[0;32m 698\u001b[0m new_axes \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlist\u001b[39m(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39maxes)\n\u001b[0;32m 699\u001b[0m new_axes[axis] \u001b[38;5;241m=\u001b[39m new_axis\n",
|
||||
"File \u001b[1;32mc:\\ProgramData\\anaconda3\\Lib\\site-packages\\pandas\\core\\internals\\blocks.py:1307\u001b[0m, in \u001b[0;36mBlock.take_nd\u001b[1;34m(self, indexer, axis, new_mgr_locs, fill_value)\u001b[0m\n\u001b[0;32m 1304\u001b[0m allow_fill \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[0;32m 1306\u001b[0m \u001b[38;5;66;03m# Note: algos.take_nd has upcast logic similar to coerce_to_target_dtype\u001b[39;00m\n\u001b[1;32m-> 1307\u001b[0m new_values \u001b[38;5;241m=\u001b[39m algos\u001b[38;5;241m.\u001b[39mtake_nd(\n\u001b[0;32m 1308\u001b[0m values, indexer, axis\u001b[38;5;241m=\u001b[39maxis, allow_fill\u001b[38;5;241m=\u001b[39mallow_fill, fill_value\u001b[38;5;241m=\u001b[39mfill_value\n\u001b[0;32m 1309\u001b[0m )\n\u001b[0;32m 1311\u001b[0m \u001b[38;5;66;03m# Called from three places in managers, all of which satisfy\u001b[39;00m\n\u001b[0;32m 1312\u001b[0m \u001b[38;5;66;03m# these assertions\u001b[39;00m\n\u001b[0;32m 1313\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(\u001b[38;5;28mself\u001b[39m, ExtensionBlock):\n\u001b[0;32m 1314\u001b[0m \u001b[38;5;66;03m# NB: in this case, the 'axis' kwarg will be ignored in the\u001b[39;00m\n\u001b[0;32m 1315\u001b[0m \u001b[38;5;66;03m# algos.take_nd call above.\u001b[39;00m\n",
|
||||
"File \u001b[1;32mc:\\ProgramData\\anaconda3\\Lib\\site-packages\\pandas\\core\\array_algos\\take.py:117\u001b[0m, in \u001b[0;36mtake_nd\u001b[1;34m(arr, indexer, axis, fill_value, allow_fill)\u001b[0m\n\u001b[0;32m 114\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m arr\u001b[38;5;241m.\u001b[39mtake(indexer, fill_value\u001b[38;5;241m=\u001b[39mfill_value, allow_fill\u001b[38;5;241m=\u001b[39mallow_fill)\n\u001b[0;32m 116\u001b[0m arr \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39masarray(arr)\n\u001b[1;32m--> 117\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m _take_nd_ndarray(arr, indexer, axis, fill_value, allow_fill)\n",
|
||||
"File \u001b[1;32mc:\\ProgramData\\anaconda3\\Lib\\site-packages\\pandas\\core\\array_algos\\take.py:162\u001b[0m, in \u001b[0;36m_take_nd_ndarray\u001b[1;34m(arr, indexer, axis, fill_value, allow_fill)\u001b[0m\n\u001b[0;32m 157\u001b[0m out \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mempty(out_shape, dtype\u001b[38;5;241m=\u001b[39mdtype)\n\u001b[0;32m 159\u001b[0m func \u001b[38;5;241m=\u001b[39m _get_take_nd_function(\n\u001b[0;32m 160\u001b[0m arr\u001b[38;5;241m.\u001b[39mndim, arr\u001b[38;5;241m.\u001b[39mdtype, out\u001b[38;5;241m.\u001b[39mdtype, axis\u001b[38;5;241m=\u001b[39maxis, mask_info\u001b[38;5;241m=\u001b[39mmask_info\n\u001b[0;32m 161\u001b[0m )\n\u001b[1;32m--> 162\u001b[0m func(arr, indexer, out, fill_value)\n\u001b[0;32m 164\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m flip_order:\n\u001b[0;32m 165\u001b[0m out \u001b[38;5;241m=\u001b[39m out\u001b[38;5;241m.\u001b[39mT\n",
|
||||
"\u001b[1;31mKeyboardInterrupt\u001b[0m: "
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# 计算售价\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"from sell.sell_price import call_sell_and_order_price\n",
|
||||
"import json\n",
|
||||
"import pandas as pd\n",
|
||||
"DATE_LIST = [\"2024-01-01\",\"2024-02-01\",\"2024-03-01\",\"2024-04-01\",\"2024-05-01\",\"2024-06-01\",\"2024-07-01\",\"2024-08-01\",\"2024-09-01\",\"2024-10-01\",\"2024-11-01\",\"2024-12-01\",\"2025-01-01\",\"2025-02-01\",\"2025-03-01\"]\n",
|
||||
"\n",
|
||||
"def cal_sell_price(df):\n",
|
||||
" \"\"\"\n",
|
||||
" 计算所有SKU的售价,物流分摊费\n",
|
||||
" \"\"\"\n",
|
||||
" for index, row in df.iterrows():\n",
|
||||
" try:\n",
|
||||
" package_dict = json.loads(row['包裹数据'])\n",
|
||||
" sell_price,order_price,order_type = call_sell_and_order_price(row['成本价'], package_dict)\n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\" {row['SKU']} 报错: {e}\")\n",
|
||||
" continue\n",
|
||||
" df.loc[index, '售价'] = sell_price\n",
|
||||
" df.loc[index, '物流分摊费'] = order_price\n",
|
||||
" df.loc[index, '物流类型'] = order_type\n",
|
||||
" # print(f\"sku:{row['SKU']},售价:{sell_price},物流分摊费:{order_price},物流类型:{order_type}\")\n",
|
||||
" return df\n",
|
||||
"for date in DATE_LIST:\n",
|
||||
" sql = f\"\"\" SELECT\n",
|
||||
"\tsku.SKU,\n",
|
||||
"\t包裹数据,\n",
|
||||
"\t成本价,\n",
|
||||
"\t产品售价,\n",
|
||||
"\t`产品品类`,\n",
|
||||
"\t`产品分类` \n",
|
||||
"FROM\n",
|
||||
"\tods.`stg_bayshop_litfad_sku` sku\n",
|
||||
"\tLEFT JOIN ads.new_erp_sku_size size ON sku.SKU = size.SKU \n",
|
||||
"\tLEFT JOIN ods.stg_bayshop_litfad_spu spu ON sku.`产品PID` =spu.`产品PID`\n",
|
||||
"WHERE\n",
|
||||
"\tsku.状态 = \"启用\" \n",
|
||||
"\tAND spu.`产品品类` REGEXP \"66\"\n",
|
||||
"\tAND spu.`状态` =\"正常销售\"\n",
|
||||
"\tAND DATE_FORMAT( sku.添加时间, \"%Y-%m-01\" )= \"{date}\"\n",
|
||||
" \"\"\"\n",
|
||||
" df = pd.read_sql(sql, conn)\n",
|
||||
" df1 = cal_sell_price(df)\n",
|
||||
" df1.to_excel(f'售价_{date}.xlsx', index=False)\n",
|
||||
" print(f\"日期:{date}, 售价计算完成\")\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "base",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"C:\\Users\\Admin\\AppData\\Local\\Temp\\ipykernel_2152\\2535791683.py:19: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
|
||||
" order_df = pd.read_sql(query, db.con)\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" DATE_FORMAT(order_date,'%Y-%m-%d') order_id \\\n",
|
||||
"0 2024-08-01 240801000602903 \n",
|
||||
"1 2024-08-01 240801000802198 \n",
|
||||
"2 2024-08-01 240801001202641 \n",
|
||||
"3 2024-08-01 240801001402908 \n",
|
||||
"4 2024-08-01 240801001414785 \n",
|
||||
"... ... ... \n",
|
||||
"143421 2025-01-20 250120235520923 \n",
|
||||
"143422 2025-01-20 250120235602837 \n",
|
||||
"143423 2025-01-20 250120235616266 \n",
|
||||
"143424 2025-01-20 250120235627823 \n",
|
||||
"143425 2025-01-20 250120235802366 \n",
|
||||
"\n",
|
||||
" order_price_dollar order_freight_price_dollar order_cate \n",
|
||||
"0 201.66 64.30 家具 \n",
|
||||
"1 420.65 53.60 家具 \n",
|
||||
"2 51.93 13.66 家具 \n",
|
||||
"3 2099.55 112.20 家具 \n",
|
||||
"4 154.47 39.78 灯具 \n",
|
||||
"... ... ... ... \n",
|
||||
"143421 496.54 50.00 家具 \n",
|
||||
"143422 121.50 17.02 灯具 \n",
|
||||
"143423 281.34 9.66 家具 \n",
|
||||
"143424 312.36 73.47 家具 \n",
|
||||
"143425 117.39 17.21 灯具 \n",
|
||||
"\n",
|
||||
"[143426 rows x 5 columns]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from utils.gtools import MySQLconnect\n",
|
||||
"import pandas as pd\n",
|
||||
"# 先找出去年八月之后的订单号,订单时间,订单费用,订单所属条目,订单物流成本\n",
|
||||
"with MySQLconnect('ods') as db:\n",
|
||||
" query = \"\"\"SELECT\n",
|
||||
" DATE_FORMAT( order_date, '%Y-%m-%d' ),\n",
|
||||
" order_id,\n",
|
||||
" order_price_dollar,\n",
|
||||
" order_cate,\n",
|
||||
" SUM(pfi.package_fund + pfi.head_way_express_fee + pfi.other_expend + pfi.indemnity)/7 AS `物流成本` \n",
|
||||
" FROM\n",
|
||||
" ods.order_list ol\n",
|
||||
" LEFT JOIN order_express oe ON ol.order_id = oe.`单号`\n",
|
||||
" LEFT JOIN package_fee_info pfi ON oe.包裹号 = pfi.package \n",
|
||||
" WHERE\n",
|
||||
" order_date >= '2024-08-01' \n",
|
||||
" AND order_date <= '2025-01-21' \n",
|
||||
" AND fund_status NOT REGEXP '等待|退款|失败|冻结' \n",
|
||||
" AND site_name = 'Litfad' \n",
|
||||
" GROUP BY\n",
|
||||
" order_id\n",
|
||||
" \"\"\"\n",
|
||||
" order_df = pd.read_sql(query, db.con)\n",
|
||||
" print(order_df)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 取采购价\n",
|
||||
"order_id = order_df['order_id'].tolist()\n",
|
||||
"order_ids = ','.join(f\"'{i}'\" for i in order_id)\n",
|
||||
"with MySQLconnect('ods') as db:\n",
|
||||
" query = f\"\"\"\n",
|
||||
"with t1 AS (SELECT LEFT\n",
|
||||
" ( ol.out_detials_outlink_id, 15 ) AS order_id,\n",
|
||||
" SUM( out_detials_qty * price )/ 7 AS instock_cost,\n",
|
||||
"\t\t\t\tNULL AS buy_cost\n",
|
||||
" FROM\n",
|
||||
" ods.outstock_list ol\n",
|
||||
" JOIN ods.instock_list il ON ol.store_in_id = il.id \n",
|
||||
" WHERE\n",
|
||||
" LEFT ( ol.out_detials_outlink_id, 15 ) IN ({order_ids}) \n",
|
||||
" GROUP BY\n",
|
||||
" LEFT ( ol.out_detials_outlink_id, 15 )\n",
|
||||
"UNION ALL\n",
|
||||
" SELECT\n",
|
||||
" LEFT ( order_product_id, 15 ) as order_id, \n",
|
||||
"\t\t\t\tNULL as instock_cost,\n",
|
||||
"\t\t\t\tSUM(buy_num * actual_price)/ 7 AS buy_cost\n",
|
||||
" FROM\n",
|
||||
" `warehouse_purchasing`\n",
|
||||
" WHERE\n",
|
||||
" LEFT ( order_product_id, 15 ) IN ({order_ids}) \n",
|
||||
" AND buy_audit = \"采购完成\"\n",
|
||||
" group by LEFT ( order_product_id, 15 )\n",
|
||||
"\t\t)\n",
|
||||
"\t\t\n",
|
||||
"\tSELECT\n",
|
||||
"\torder_id,\n",
|
||||
"\tSUM(CASE \n",
|
||||
"\tWHEN instock_cost is null THEN\n",
|
||||
"\t\tbuy_cost\n",
|
||||
"\tELSE\n",
|
||||
"\t\tinstock_cost END) AS pur_cost\n",
|
||||
"\tFROM\n",
|
||||
"\tt1 \n",
|
||||
"\tGROUP BY order_id\n",
|
||||
"\t\n",
|
||||
"\"\"\"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "base",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,20 @@
|
|||
zone_west = {
|
||||
2:["90000-93599"],
|
||||
3:["85400-85499", "86400-86499", "88900-89299", "93600-93999", "94300-94399", "95000-95399"],
|
||||
4:["84000-85399", "85500-86399", "86500-86599", "87300-87499", "89300-89899", "94000-94299", "94400-94999", "95400-96699", "97500-97699", "98700-98799"],
|
||||
5:["59000-59199", "59400-59499", "59600-59999", "67700-67799", "67900-67999", "69300-69399", "73900-73999", "79000-79199", "79300-79499", "79700-83899", "87000-87299", "87500-88599", "97000-97499", "97700-98699", "98800-99499"],
|
||||
6:["50500-50599", "50800-50899", "51000-51699", "56100-56199", "57000-58199", "58300-58899", "59200-59399", "59500-59599", "64000-64999", "65600-67699", "67800-67899", "68000-69299", "71000-71199", "71800-71899", "72600-72799", "72900-73899", "74000-77599", "77800-78999", "79200-79299", "79500-79699"],
|
||||
7:["35000-35899", "36500-36799", "36900-37299", "37500-37599", "38000-38499", "38600-39799", "42000-42499", "46000-46699", "46900-46999", "47400-47999", "49800-50499", "50600-50799", "50900-50999", "52000-56099", "56200-56799", "58200-58299", "60000-63999", "65000-65599", "70000-70999", "71200-71799", "71900-72599", "72800-72899", "77600-77799"],
|
||||
8:["00400-00599", "01000-34999", "35900-36499", "36800-36899", "37300-37499", "37600-37999", "38500-38599", "39800-41899", "42500-45999", "46700-46899", "47000-47399", "48000-49799"],
|
||||
9:["96700", "96701", "96702-96705", "96706-96707", "96708", "96709", "96710", "96711-96712", "96713-96716", "96717", "96718-96729", "96730-96731", "96732-96733", "96734", "96735-96743", "96744", "96745-96757", "96758-96759", "96760-96761", "96762", "96763-96774", "96775", "96776-96781", "96782", "96783-96785", "96786", "96787-96788", "96789", "96790", "96791-96792", "96793", "96794-96795", "96796", "96797", "96798-96800", "96801-96863", "96864-96899"],
|
||||
17:["99500", "99501-99524", "99525-99539", "99540", "99541-99566", "99567", "99568-99576", "99577", "99578-99999"]
|
||||
}
|
||||
zone_east = {
|
||||
2:["00400-00599", "01000-01299", "06000-11999", "12400-12799", "13700-13999", "17000-17699", "17800-19999", "20600-20899", "21000-21499", "21600-21699", "21800-21999"],
|
||||
3:["01300-04199", "05000-05999", "12000-12399", "12800-13699", "14000-14299", "14400-14999", "15400-16099", "16200-16399", "16600-16999", "17700-17799", "20000-20599", "20900-20999", "21500-21599", "21700-21799", "22000-23999", "24400-24499", "25400-25499", "26500-26599", "26700-26899", "27900-27999"],
|
||||
4:["04200-04999", "14300-14399", "15000-15399", "16100-16199", "16400-16599", "24000-24399", "24500-25399", "25500-26499", "26600-26699", "27000-27899", "28000-28899", "29000-29799", "37600-37999", "40300-41899", "42500-42699", "43000-45999", "46700-46899", "47000-47099", "47200-47399", "48000-49299"],
|
||||
5:["28900-28999", "29800-32999", "33400-33899", "34000-34099", "34200-36499", "36700-37599", "38000-38699", "38800-38999", "39300-39399", "39700-40299", "42000-42499", "42700-42799", "46000-46699", "46900-46999", "47100-47199", "47400-47999", "49300-49999", "50400-50499", "50600-50799", "52000-55999", "60000-63999", "65000-65299", "65400-65599", "72300-72599"],
|
||||
6:["33000-33399", "33900-33999", "34100-34199", "36500-36699", "38700-38799", "39000-39299", "39400-39699", "50000-50399", "50500-50599", "50800-51699", "56000-57699", "58000-58599", "64000-64999", "65300-65399", "65600-67699", "67800-67899", "68000-69299", "70000-72299", "72600-73299", "73400-73899", "74000-76499", "76600-76799", "77000-77899"],
|
||||
7:["57700-57799", "58600-59399", "67700-67799", "67900-67999", "69300-69399", "73300-73399", "73900-73999", "76500-76599", "76800-76999", "77900-79799", "80000-81499", "81600-82099", "82200-82899", "87000-87299", "87500-87799", "88100-88499"],
|
||||
8:["59400-59999", "79800-79999", "81500-81599", "82100-82199", "82900-86599", "87300-87499", "87800-88099", "88500-96699", "97000-99499"]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,284 @@
|
|||
#coding:utf-8
|
||||
|
||||
import requests
|
||||
import re
|
||||
import pytesseract
|
||||
from PIL import Image
|
||||
from tempfile import NamedTemporaryFile
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.remote.file_detector import UselessFileDetector #跳过文件检测 1
|
||||
from selenium import webdriver
|
||||
import redis
|
||||
|
||||
def vercode(cookie=None):
|
||||
urls = 'https://cp.maso.hk:4433/index.php?main=login&act=vercode'#图片链接(每秒更新,距离当前时间最近的时间戳最近为可使用图片)
|
||||
if cookie is not None:
|
||||
haders = {
|
||||
'Cookie': cookie #使用之前的图片链接Cooike
|
||||
}
|
||||
res = requests.post(url=urls,headers=haders)
|
||||
ress = re.findall('Cookie (.*?) for',str(res.cookies))#获取图片链接Cooike,图片Cooike必须和登录请求的Cooike保持一致
|
||||
f1 = NamedTemporaryFile(mode='wb+',suffix='.png')
|
||||
f1.write(res.content)#导入文件路径(待处理图片)
|
||||
f1.seek(0)
|
||||
img = Image.open(f1)#未处理图片
|
||||
lim = img.convert('L')
|
||||
threshold = 165 # 灰度阈值设为165(可调整),低于这个值的点全部填白色
|
||||
table = []
|
||||
for j in range(256):
|
||||
if j < threshold:
|
||||
table.append(1)
|
||||
else:
|
||||
table.append(0)
|
||||
bim = lim.point(table, '1')#连接各个且分片儿
|
||||
f2 = NamedTemporaryFile(mode='wb+',suffix='.png')
|
||||
bim.save(f2)#处理完成导入
|
||||
f2.seek(0)
|
||||
text = pytesseract.image_to_string(Image.open(f2))#开始识别
|
||||
return text.replace('\n',''),
|
||||
|
||||
def Vc(user='honghuayuan',pswd= 'a12345'):
|
||||
resetcookie = 0
|
||||
try :
|
||||
r = redis.StrictRedis(host='192.168.100.44', port=7379, db=11,password="123456")
|
||||
viewcookie = r.get('cpmasosessid'+user)
|
||||
url="https://cp.maso.hk/index.php?main=panel"#主页
|
||||
header={'Cookie':viewcookie.decode('utf8')}
|
||||
resp = requests.get(url=url,headers=header)
|
||||
exists = re.findall(r'欢迎进入本公司后台管理系统',resp.text)
|
||||
if exists != []:
|
||||
return r.get('cpmasosessid'+user).decode('utf8')
|
||||
|
||||
else :
|
||||
resetcookie = 1
|
||||
except:
|
||||
resetcookie = 1
|
||||
|
||||
|
||||
while resetcookie ==1:
|
||||
urls = 'https://cp.maso.hk:4433/index.php?main=login&act=vercode'#图片链接(每秒更新,距离当前时间最近的时间戳最近为可使用图片)
|
||||
res = requests.post(url=urls)
|
||||
ress = re.findall('Cookie (.*?) for',str(res.cookies))#获取图片链接Cooike,图片Cooike必须和登录请求的Cooike保持一致
|
||||
f1 = NamedTemporaryFile(mode='wb+',suffix='.png')
|
||||
f1.write(res.content)#导入文件路径(待处理图片)
|
||||
f1.seek(0)
|
||||
img = Image.open(f1)#未处理图片
|
||||
lim = img.convert('L')
|
||||
threshold = 165 # 灰度阈值设为165(可调整),低于这个值的点全部填白色
|
||||
table = []
|
||||
for j in range(256):
|
||||
if j < threshold:
|
||||
table.append(1)
|
||||
else:
|
||||
table.append(0)
|
||||
bim = lim.point(table, '1')#连接各个且分片儿
|
||||
f2 = NamedTemporaryFile(mode='wb+',suffix='.png')
|
||||
bim.save(f2)#处理完成导入
|
||||
f2.seek(0)
|
||||
text = pytesseract.image_to_string(Image.open(f2))#开始识别
|
||||
url = 'https://cp.maso.hk:4433/index.php?main=login&act=check'
|
||||
haders = {
|
||||
'Cookie': ress[0] #使用之前的图片链接Cooike
|
||||
}
|
||||
data = {
|
||||
'user': user,
|
||||
'pswd': pswd,
|
||||
'vercode': text.replace('\n',''), #识别后的验证码结果
|
||||
'issub': '0',
|
||||
'subuser': ''
|
||||
}
|
||||
trylogin = requests.post(url=url,headers=haders,data=data).text# 尝试使用验证码登录
|
||||
if trylogin != 'success':
|
||||
print('验证码错误正在尝试重新识别验证码')
|
||||
continue
|
||||
else:
|
||||
try:
|
||||
r.set("cpmasosessid"+user,ress[0])
|
||||
except:
|
||||
pass
|
||||
resetcookie = 0
|
||||
return ress[0]
|
||||
break
|
||||
|
||||
def erp_cookie(type ='erp'):
|
||||
r = redis.StrictRedis(host='192.168.100.44', port=7379, db=11,password="123456")
|
||||
if type == 'erp':
|
||||
cookie=r.get('erpcookie').decode('utf-8')
|
||||
url = "https://erp.baycheer.com/"
|
||||
header = {
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36'
|
||||
}
|
||||
header['cookie']=cookie
|
||||
f = requests.get(url=url,headers=header)
|
||||
ned = re.findall('企业微信扫码登录',f.text)
|
||||
if ned == ['企业微信扫码登录']:
|
||||
mainUrl = "https://erp.baycheer.com/login/"#地址
|
||||
service=Service( r'E:\chromedriver.exe')#谷歌驱动路劲
|
||||
service.command_line_args()
|
||||
service.start()
|
||||
options = webdriver.ChromeOptions()
|
||||
options.add_argument('--headless')
|
||||
options.add_argument('ignore-certificate-errors')
|
||||
options.add_argument('--start-maximized')
|
||||
options.add_argument('--no-sandbox')
|
||||
options.add_argument('--disable-gpu')
|
||||
options.add_argument(' -port=9222')
|
||||
driver=webdriver.Remote(service.service_url,options=options)
|
||||
driver.file_detector = UselessFileDetector()##################
|
||||
driver.get(mainUrl)
|
||||
input_box=driver.find_element(By.ID,'uid')
|
||||
input_pswd=driver.find_element(By.ID,'password')
|
||||
login_box=driver.find_element(By.CLASS_NAME,'btn')
|
||||
input_box.send_keys('10818')
|
||||
input_pswd.send_keys('taaw0NCl')
|
||||
login_box.click()
|
||||
cookie ="sess=" + driver.get_cookie('sess')['value']
|
||||
r.set('erpcookie',cookie)
|
||||
driver.close()
|
||||
elif type == 'bayshop':
|
||||
cookie=r.get('bayshopcookie').decode('utf-8')
|
||||
url = "https://shop.baycheer.com/"
|
||||
header = {
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36'
|
||||
}
|
||||
header['cookie']=cookie
|
||||
f = requests.get(url=url,headers=header)
|
||||
ned = re.findall('请通过公司系统漫游登录',f.text)
|
||||
if ned == ['请通过公司系统漫游登录']:
|
||||
mainUrl = "https://erp.baycheer.com/login/"#地址
|
||||
ssoUrl ='https://erp.baycheer.com/sso'
|
||||
sceUrl = 'https://erp.baycheer.com/sso/remoteLogin?sso_site_id=3642'
|
||||
thrUrl = 'https://shop.baycheer.com/'
|
||||
service=Service('/usr/bin/chromedriver')#谷歌驱动路劲
|
||||
service.command_line_args()
|
||||
service.start()
|
||||
options = webdriver.ChromeOptions()
|
||||
options.add_argument('--headless')
|
||||
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36')
|
||||
options.add_argument('ignore-certificate-errors')
|
||||
options.add_argument('--start-maximized')
|
||||
options.add_argument('--no-sandbox')
|
||||
options.add_argument('--disable-gpu')
|
||||
options.add_argument(' -port=9222')
|
||||
driver=webdriver.Remote(service.service_url,options=options)
|
||||
driver.file_detector = UselessFileDetector()##################
|
||||
driver.get(mainUrl)
|
||||
input_box=driver.find_element(By.ID,'uid')
|
||||
input_pswd=driver.find_element(By.ID,'password')
|
||||
login_box=driver.find_element(By.CLASS_NAME,'btn')
|
||||
input_box.send_keys('10818')
|
||||
input_pswd.send_keys('taaw0NCl')
|
||||
login_box.click()
|
||||
cookie ="sess=" + driver.get_cookie('sess')['value']
|
||||
r.set('erpcookie',cookie)
|
||||
driver.get(ssoUrl)
|
||||
driver.get(sceUrl)
|
||||
import time
|
||||
time.sleep(5)
|
||||
driver.get(thrUrl)
|
||||
cookie ="sess=" + driver.get_cookie('sess')['value']
|
||||
r.set('bayshopcookie',cookie)
|
||||
driver.quit()
|
||||
r.close()
|
||||
return cookie
|
||||
def zenid():
|
||||
r = redis.StrictRedis(host='192.168.100.44', port=7379, db=11,password="123456")
|
||||
zenAdminID=r.get('zenAdminID').decode('utf-8')
|
||||
url = 'https://www.beautifulhalo.com/masodress/index.php'
|
||||
header = {'Cookie': "zenAdminID={};".format(zenAdminID)}
|
||||
res = requests.get(url=url, headers=header).text
|
||||
retry =0
|
||||
if res == 'please use OA remote login this system. if you have any question, please contact with KING.' :
|
||||
retry =1
|
||||
if 'Checking if the site connection is secure' in res:
|
||||
retry =1
|
||||
if retry ==1:
|
||||
t=Vc()[7:]
|
||||
service=Service('/usr/bin/chromedriver')
|
||||
service.command_line_args()
|
||||
service.start()
|
||||
options = webdriver.ChromeOptions()
|
||||
options.add_argument('--headless')
|
||||
options.add_argument('--no-sandbox')
|
||||
options.add_argument('--disable-gpu')
|
||||
options.add_argument(' -port=9222')
|
||||
driver=webdriver.Remote(service.service_url,options=options)
|
||||
mainurl='http://cp.maso.hk/index.php?main=main'
|
||||
panelUrl = "http://cp.maso.hk/index.php?main=panel"#地址
|
||||
remoteurl='http://cp.maso.hk/index.php?main=sys_remote_login&act=remotelogin&id=303'
|
||||
driver.get(mainurl)
|
||||
driver.delete_all_cookies()
|
||||
driver.add_cookie({'name':'sessid','value':f'{t}'})
|
||||
driver.get(panelUrl)
|
||||
driver.get(remoteurl)
|
||||
zenAdminID=re.findall(r'zenAdminID=(.*)$',driver.current_url)[0]
|
||||
driver.quit()
|
||||
r.set('zenAdminID',zenAdminID)
|
||||
return zenAdminID
|
||||
|
||||
|
||||
def twtoken():
|
||||
"""
|
||||
先获取ACCESS token
|
||||
如果ACCESS TOKEN存在且过期时间没到,则不管他,如果到了则重新获取一次,考虑刷新机制
|
||||
"""
|
||||
import sys
|
||||
import pendulum
|
||||
sys.path.append('E:/python/workspace/taotian')
|
||||
import iop
|
||||
from urllib.parse import quote
|
||||
import webbrowser
|
||||
appkey = '501176'
|
||||
appSecret = '9evPt00JW5Z63WXUsjOYlKjPqhkGkBjX'
|
||||
def escape_special_characters(url):
|
||||
return quote(url, safe=':/?&=') # 允许保留 :/?&= 这些符号
|
||||
|
||||
r = redis.StrictRedis(host='192.168.100.44', port=7379, db=11,password="123456")
|
||||
tokenbody = r.hgetall('twtoken')
|
||||
data = {k.decode('utf-8'): v.decode('utf-8') for k, v in tokenbody.items()}
|
||||
data['expire_date'] = "2024-05-25"
|
||||
if pendulum.parse(data['expire_date']) >= pendulum.now():
|
||||
return data["access_token"]
|
||||
|
||||
elif pendulum.parse(data['refresh_expire_date']) >= pendulum.now():
|
||||
url = 'https://api.taobao.global/rest'
|
||||
client = iop.IopClient(url, appkey ,appSecret)
|
||||
request = iop.IopRequest('/auth/token/refresh')
|
||||
request.add_api_param('refresh_token', data['refresh_token'])
|
||||
response = client.execute(request)
|
||||
for key in response.body:
|
||||
if key in data:
|
||||
data[key] = response.body[key]
|
||||
return data['access_token']
|
||||
|
||||
else:
|
||||
"""
|
||||
重新授权
|
||||
"""
|
||||
redirect_uri = 'http://region-42.seetacloud.com:40555/post'
|
||||
original_url = f'https://api.taobao.global/oauth/authorize?response_type=code&redirect_uri={redirect_uri}&force_auth=true&client_id={appkey}'
|
||||
formatted_url = escape_special_characters(original_url)
|
||||
webbrowser.open_new(formatted_url)
|
||||
webbrowser.open_new_tab(formatted_url)
|
||||
print("账号: 贝勤国际有限公司:chanpin01\r\n密码: cp123456")
|
||||
code = input("输入获得的code:")
|
||||
|
||||
request = iop.IopRequest('/auth/token/create')
|
||||
request.add_api_param('refresh_token', code)
|
||||
response = client.execute(request)
|
||||
data = response.body
|
||||
data['expire_date']=pendulum.now().add(seconds=int(data['expires_in'])).strftime("%Y-%m-%d")
|
||||
data['refresh_expire_date']=pendulum.now().add(seconds=int(data['refresh_expires_in'])).strftime("%Y-%m-%d")
|
||||
r.hmset('twtoken',data)
|
||||
return data['access_token']
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
s = vercode()
|
||||
print(s)
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
from . import logisticsTail_US
|
||||
from . import logisticsBaseClass
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
"""物流模块基类,不做具体实现"""
|
||||
from enum import Enum
|
||||
|
||||
class LogisticsType(Enum):
|
||||
EXPRESS = '快递'
|
||||
COURIER = '卡派'
|
||||
OCEAN = '海运'
|
||||
AIR = '空运'
|
||||
class PortType(Enum):
|
||||
DEFAULT = '默认'
|
||||
WEST = 'west'
|
||||
CENTRAL = 'central'
|
||||
EAST = 'east'
|
||||
SOUTH = 'south'
|
||||
NORTH = 'north'
|
||||
|
||||
# 基础物流费用类
|
||||
class BaseLogistics():
|
||||
"""基础物流类,强制要求子类定义特定属性"""
|
||||
company: str
|
||||
currency:str = 'USD' # 货币单位,默认美元
|
||||
port: PortType = PortType.DEFAULT
|
||||
logistics_type:LogisticsType
|
||||
active: bool = True # 是否启用该物流,默认未启用
|
||||
def __init__(self):
|
||||
# 检查子类是否定义了必要的类变量
|
||||
for attr in ['company', 'country_code', 'country']:
|
||||
if not getattr(self.__class__, attr, None):
|
||||
raise AttributeError(f"Subclass {self.__class__.__name__} is missing required attribute '{attr}'.")
|
||||
|
||||
def __repr__(self):
|
||||
return f"类名:{self.__class__.__name__}, 物流类型:{self.logistics_type.value}"
|
||||
|
||||
@classmethod
|
||||
def get_open_subclasses(cls):
|
||||
"""递归获取所有启用状态为 True 的子类"""
|
||||
subclasses = set(cls.__subclasses__())
|
||||
for subclass in cls.__subclasses__():
|
||||
subclasses.update(subclass.get_open_subclasses())
|
||||
subclasses = {subclass for subclass in subclasses if subclass.active ==True}
|
||||
return subclasses
|
||||
@classmethod
|
||||
def get_close_subclasses(cls):
|
||||
"""获取关闭状态的子类,状态为 False 的子类"""
|
||||
subclasses = set(cls.__subclasses__())
|
||||
for subclass in cls.__subclasses__():
|
||||
subclasses.update(subclass.get_close_subclasses())
|
||||
return {subclass for subclass in subclasses if subclass.active == False}
|
||||
@classmethod
|
||||
def open_logistics(cls):
|
||||
"""打开该类"""
|
||||
cls.active = True
|
||||
@classmethod
|
||||
def close_logistics(cls):
|
||||
"""关闭该类"""
|
||||
cls.active = False
|
||||
|
||||
|
||||
|
||||
class TailLogistics(BaseLogistics):
|
||||
"""尾端物流类,包含快递和卡派"""
|
||||
# 费用结果保存在实例对象中,不需要cal函数返回
|
||||
country_code: str
|
||||
country: str
|
||||
company: str
|
||||
currency:str = 'USD' # 货币单位,默认美元
|
||||
port: PortType = PortType.DEFAULT
|
||||
logistics_type = LogisticsType.EXPRESS # 默认快递
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def calculate_fee(self):
|
||||
"""计算费用"""
|
||||
raise NotImplementedError("Subclasses must implement express fee calculation.")
|
||||
|
||||
|
||||
class HeadLogistics(BaseLogistics):
|
||||
"""头程物流类,包含海运和空运"""
|
||||
country_code: list[str]
|
||||
country: list[str]
|
||||
logistics_type = LogisticsType.OCEAN # 默认海运
|
||||
currency = 'CNY'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.head_ratio:float
|
||||
|
||||
def calculate_fee(self, packages):
|
||||
"""计算费用"""
|
||||
detail_amount = {
|
||||
"volume_weight":0.00,
|
||||
"head_per":0.00,
|
||||
"head_amount":0.00
|
||||
}
|
||||
total_weight = sum([package.get_volume_weight(6000) for package in packages])
|
||||
detail_amount['volume_weight'] = round(total_weight,2)
|
||||
detail_amount['head_per'] = self.head_ratio
|
||||
detail_amount["head_amount"] = total_weight * self.head_ratio
|
||||
return detail_amount
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
from logisticsClass.logisticsBaseClass import HeadLogistics, LogisticsType
|
||||
"""
|
||||
port:SYD(default)
|
||||
currency:str = 'CNY'
|
||||
logistics_type:LogisticsType (海运,空运)
|
||||
"""
|
||||
|
||||
class OceanAMSLogistics_EUR(HeadLogistics):
|
||||
"""欧洲海运"""
|
||||
logistics_type = LogisticsType.OCEAN # 默认海运
|
||||
company = "海SY"
|
||||
country_code = 'AU'
|
||||
country = 'Australia'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.head_ratio = 6
|
||||
|
||||
|
||||
class AirAMSLogistics_EUR(HeadLogistics):
|
||||
"""欧洲空运"""
|
||||
logistics_type = LogisticsType.AIR
|
||||
company = "空SYD"
|
||||
country_code = 'AU'
|
||||
country = 'Australia'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.head_ratio = 30
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
from logisticsClass.logisticsBaseClass import HeadLogistics, LogisticsType
|
||||
"""
|
||||
port:AMS(default)
|
||||
currency:str = 'CNY'
|
||||
logistics_type:LogisticsType (海运,空运)
|
||||
"""
|
||||
|
||||
class OceanAMSLogistics_EUR(HeadLogistics):
|
||||
"""欧洲海运"""
|
||||
logistics_type = LogisticsType.OCEAN
|
||||
company = "海NL"
|
||||
country_code = ['FR','ES','DE','IT','NL','BE','PT']
|
||||
country = ['France','Spain','Germany','Italy','Portugal','Belgium','Netherlands']
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.head_ratio = 8
|
||||
|
||||
|
||||
class AirAMSLogistics_EUR(HeadLogistics):
|
||||
"""欧洲空运"""
|
||||
logistics_type = LogisticsType.AIR
|
||||
company = "空AMS"
|
||||
country_code = ['FR','ES','DE','IT','NL','BE','PT']
|
||||
country = ['France','Spain','Germany','Italy','Portugal','Belgium','Netherlands']
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.head_ratio = 40
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
from logisticsClass.logisticsBaseClass import HeadLogistics, LogisticsType
|
||||
"""
|
||||
port:LHR(default)
|
||||
currency:str = 'CNY'
|
||||
logistics_type:LogisticsType (海运,空运)
|
||||
"""
|
||||
|
||||
class OceanAMSLogistics_EUR(HeadLogistics):
|
||||
"""欧洲海运"""
|
||||
logistics_type = LogisticsType.OCEAN # 默认海运
|
||||
company = "海GB"
|
||||
country_code = 'UK'
|
||||
country = 'United Kingdom'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.head_ratio = 5
|
||||
|
||||
|
||||
class AirAMSLogistics_EUR(HeadLogistics):
|
||||
"""欧洲空运"""
|
||||
logistics_type = LogisticsType.AIR
|
||||
company = "空LHR"
|
||||
country_code = 'UK'
|
||||
country = 'United Kingdom'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.head_ratio = 40
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from logisticsClass.logisticsBaseClass import HeadLogistics, LogisticsType
|
||||
"""
|
||||
port:LAX(default) 美西
|
||||
currency:str = 'CNY'(default)
|
||||
logistics_type:LogisticsType (海运(default),空运)
|
||||
"""
|
||||
|
||||
class OceanMSLogistics_US(HeadLogistics):
|
||||
"""美国海运美森"""
|
||||
logistics_type = LogisticsType.OCEAN # 默认海运
|
||||
company = "海MS"
|
||||
country_code = "US"
|
||||
country = "United States"
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.head_ratio = 9
|
||||
|
||||
class AirLAXLogistics_US(HeadLogistics):
|
||||
"""美国空运LAX"""
|
||||
logistics_type = LogisticsType.AIR
|
||||
company = "空LAX"
|
||||
country_code = "US"
|
||||
country = "United States"
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.head_ratio = 35
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,345 @@
|
|||
""" 澳大利亚尾端物流模块实现类"""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import math
|
||||
import pandas as pd
|
||||
from logisticsClass.logisticsBaseClass import TailLogistics
|
||||
from utils.Package import Package, Package_group
|
||||
"""
|
||||
counrty:Australia
|
||||
company:POST,ALL,TOLL
|
||||
port:海SY,空SYD(default)
|
||||
currency:str = 'AUD'(default)
|
||||
logistics_type:LogisticsType (快递(default),卡派)
|
||||
"""
|
||||
# POST
|
||||
class PostLogistics_AU(TailLogistics):
|
||||
country_code = 'AU'
|
||||
country = 'Australia'
|
||||
company = 'POST'
|
||||
currency = 'AUD'
|
||||
|
||||
_is_loaded = False
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
remote_path = parent_current_directory.joinpath("data")
|
||||
_postcode_files = remote_path.joinpath("澳洲三大渠道.xlsx")
|
||||
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if not cls._is_loaded:
|
||||
cls._load_postcodes() # 第一次实例化时加载文件
|
||||
cls._is_loaded = True # 标记文件已加载
|
||||
return super().__new__(cls)
|
||||
# Path_current_directory = Path(__file__).parent
|
||||
# current_directory = os.path.dirname(__file__)
|
||||
@classmethod
|
||||
def _load_postcodes(cls):
|
||||
"""加载文件"""
|
||||
# 获取eparcel邮编所属的分区
|
||||
cls.eparcel_zone = pd.read_excel(str(cls._postcode_files),sheet_name="eparcel_postcode",usecols="A:B")
|
||||
# 获取eparcel的价格
|
||||
cls.eparcel_price = pd.read_excel(str(cls._postcode_files),sheet_name="eparcel",usecols="A:M")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.eparcel_zone = self.__class__.eparcel_zone
|
||||
self.eparcel_price = self.__class__.eparcel_price
|
||||
self.fuel_rate =0.051# 0.074 0.064
|
||||
|
||||
def calculate_fee(self, packages, postcode):
|
||||
"""
|
||||
计费重: 常规计费, 4000
|
||||
限制条件: 32kg,最长边100cm
|
||||
是否有燃油费:是
|
||||
"""
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"fuel":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
if isinstance(postcode, str):
|
||||
postcode = ''.join(filter(str.isdigit, postcode)) # 只保留数字部分
|
||||
if postcode: # 确保postcode不是空字符串
|
||||
postcode = str(int(postcode)).zfill(4) # 转换为整数再补齐4位
|
||||
else:
|
||||
detail_amount['tail_amount'] = 88888
|
||||
return detail_amount
|
||||
|
||||
# 先看邮编呐
|
||||
self.eparcel_zone['邮编'] = self.eparcel_zone['邮编'].astype(str).str.zfill(4)
|
||||
|
||||
filtered_df = self.eparcel_zone[self.eparcel_zone['邮编'] == postcode]
|
||||
if filtered_df.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
|
||||
post = filtered_df['地区代码'].iloc[0] # 获取第一行的 '地区代码'
|
||||
# 根据post筛选出对应行
|
||||
row = self.eparcel_price[self.eparcel_price['post'] == post]
|
||||
# 检查 row 是否为空
|
||||
if row.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
|
||||
base_data = list(zip(self.eparcel_price.columns[1:], row.values[0][1:]))
|
||||
for package in packages:
|
||||
billing_weight = max(package.weight, package.volume/4)
|
||||
|
||||
if package.fst_size > 100 or billing_weight >= 32000:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
if billing_weight > 22000:
|
||||
detail_amount['base'] += base_data[-2][1] + base_data[-1][1] * math.ceil(billing_weight/1000)
|
||||
else:
|
||||
for weight,price in base_data[:-2]:
|
||||
if billing_weight <=weight*1000:
|
||||
detail_amount['base'] += price
|
||||
break
|
||||
detail_amount['fuel'] = detail_amount['base'] * self.fuel_rate
|
||||
detail_amount['tail_amount'] = detail_amount['base'] + detail_amount['fuel']
|
||||
return detail_amount
|
||||
|
||||
# TOLL
|
||||
class TollLogistics_AU(TailLogistics):
|
||||
country_code = 'AU'
|
||||
country = 'Australia'
|
||||
company = 'TOLL'
|
||||
currency = 'AUD'
|
||||
|
||||
_is_loaded = False
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
remote_path = parent_current_directory.joinpath("data")
|
||||
_postcode_files = remote_path.joinpath("澳洲三大渠道.xlsx")
|
||||
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if not cls._is_loaded:
|
||||
cls._load_postcodes() # 第一次实例化时加载文件
|
||||
cls._is_loaded = True # 标记文件已加载
|
||||
return super().__new__(cls)
|
||||
@classmethod
|
||||
def _load_postcodes(cls):
|
||||
"""加载文件"""
|
||||
cls.toll_zone = pd.read_excel(str(cls._postcode_files),sheet_name="toll_postcode",usecols="A:B")
|
||||
cls.toll_price = pd.read_excel(str(cls._postcode_files),sheet_name="toll",usecols="A:D")
|
||||
cls.toll_remote = pd.read_excel(str(cls._postcode_files),sheet_name="toll_remote",usecols="A:D")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.toll_zone = self.__class__.toll_zone
|
||||
self.toll_price = self.__class__.toll_price
|
||||
self.toll_remote = self.__class__.toll_remote
|
||||
self.fuel_rate = 0.0725
|
||||
self.oversize_fee = [15.5,62]
|
||||
|
||||
def calculate_fee(self, packages, postcode):
|
||||
# 抛重4000
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"oversize":0.00,
|
||||
"remote":0.00,
|
||||
"fuel":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
if isinstance(postcode, str):
|
||||
postcode = ''.join(filter(str.isdigit, postcode)) # 只保留数字部分
|
||||
if postcode: # 确保postcode不是空字符串
|
||||
postcode = str(int(postcode)).zfill(4) # 转换为整数再补齐4位
|
||||
else:
|
||||
detail_amount['tail_amount'] = 88888
|
||||
return detail_amount
|
||||
# 先看邮编呐
|
||||
self.toll_zone['postcode'] = self.toll_zone['postcode'].astype(str).str.zfill(4)
|
||||
filtered_df = self.toll_zone[self.toll_zone['postcode'] == postcode]
|
||||
if filtered_df.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
|
||||
post = filtered_df['post'].iloc[0] # 获取第一行的 '地区代码'
|
||||
# 根据post筛选出对应行
|
||||
row = self.toll_price[self.toll_price['post'] == post]
|
||||
# 检查 row 是否为空
|
||||
if row.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
|
||||
base = row['Base'].iloc[0]
|
||||
per = self.toll_price[self.toll_price['post']==post]['Per'].iloc[0]
|
||||
minimun = self.toll_price[self.toll_price['post']==post]['Minimun'].iloc[0]
|
||||
for package in packages:
|
||||
volume_weight = package.get_volume_weight(4)
|
||||
billing_weight = max(package.weight,volume_weight)
|
||||
detail_amount['base'] += max(base + per * math.ceil(billing_weight/1000), minimun)
|
||||
# if package.weight >= 35000 or package.fst_size >= 180 or package.volume >=700000:
|
||||
# detail_amount['oversize'] += self.oversize_fee[1]
|
||||
if package.weight >30000 or package.fst_size > 120 or package.sed_size > 80:
|
||||
detail_amount['oversize'] += self.oversize_fee[0]
|
||||
|
||||
# 计算偏远附加费,只跟地区有关,不需要放入循环内部计算
|
||||
postcode_counts = self.toll_zone[self.toll_zone['postcode'] == postcode].shape[0] # 该邮编总共多少个地区
|
||||
remote_count = self.toll_remote[self.toll_remote['postcode'] == postcode].shape[0] # 该邮编有多少个偏远地区
|
||||
postcode_counts = int(postcode_counts)
|
||||
remote_count=int(remote_count)
|
||||
# 将 price 列中的 NaN 替换为 0
|
||||
self.toll_remote['price'] = self.toll_remote['price'].fillna(0)
|
||||
# 该邮编偏远地区的偏远费
|
||||
remote_fee = self.toll_remote[self.toll_remote['postcode'] == postcode]['price'].mean()
|
||||
# 检查 remote_fee 是否为 NaN,若是则设为 0
|
||||
if pd.isna(remote_fee):
|
||||
remote_fee = 0
|
||||
|
||||
# 计算最终的偏远附加费
|
||||
if postcode_counts > 0 and remote_count > 0:
|
||||
remote_fee = remote_fee * remote_count / postcode_counts
|
||||
else:
|
||||
remote_fee = 0 # 处理为其他合适的值
|
||||
|
||||
detail_amount['remote'] = remote_fee * len(packages)
|
||||
for key in detail_amount:
|
||||
if key!= 'tail_amount' and key!= 'fuel':
|
||||
detail_amount['tail_amount'] += detail_amount[key]
|
||||
detail_amount['fuel'] += detail_amount[key] * self.fuel_rate
|
||||
detail_amount['tail_amount'] += detail_amount['fuel']
|
||||
return detail_amount
|
||||
|
||||
# ALL
|
||||
class AllLogistics_AU(TailLogistics):
|
||||
country_code = 'AU'
|
||||
country = 'Australia'
|
||||
company = 'ALL'
|
||||
currency = 'AUD'
|
||||
|
||||
_is_loaded = False
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
remote_path = parent_current_directory.joinpath("data")
|
||||
_postcode_files = remote_path.joinpath("澳洲三大渠道.xlsx")
|
||||
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if not cls._is_loaded:
|
||||
cls._load_postcodes() # 第一次实例化时加载文件
|
||||
cls._is_loaded = True # 标记文件已加载
|
||||
return super().__new__(cls)
|
||||
@classmethod
|
||||
def _load_postcodes(cls):
|
||||
"""加载文件"""
|
||||
cls.all_zone = pd.read_excel(str(cls._postcode_files),sheet_name="allied_postcode",usecols="A:C")
|
||||
cls.all_price = pd.read_excel(str(cls._postcode_files),sheet_name="allied",usecols="A:D")
|
||||
cls.all_remote = pd.read_excel(str(cls._postcode_files),sheet_name="allied_remote",usecols="A:E")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.all_zone = self.__class__.all_zone
|
||||
self.all_price = self.__class__.all_price
|
||||
self.all_remote = self.__class__.all_remote
|
||||
self.fuel_rate = 0.269
|
||||
self.homedelivery_fee = [5.51,11.02,38.56,82.61]
|
||||
self.oversize_fee = [7.8,10.92,12.41,26.42,92.15,123.96,160.07]
|
||||
self.handing_fee = 15.34 # 不收燃油费
|
||||
self.twomancrew_fee = [49.92,78,124.8] # 多包裹的情况下,只收一次最大的
|
||||
def calculate_fee(self, packages, postcode):
|
||||
# 抛重4000
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"Homedelivery":0.00,
|
||||
"twomancrew":0.00, # 多包裹的情况下,只收一次最大的
|
||||
"lengthover":0.00,
|
||||
"fuel":0.00,
|
||||
"widthover":0.00, # 240cm内不收燃油费
|
||||
"handling":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
if isinstance(postcode, str):
|
||||
postcode = ''.join(filter(str.isdigit, postcode)) # 只保留数字部分
|
||||
if postcode: # 确保postcode不是空字符串
|
||||
postcode = str(int(postcode)).zfill(4) # 转换为整数再补齐4位
|
||||
else:
|
||||
detail_amount['tail_amount'] = 88888
|
||||
return detail_amount
|
||||
|
||||
# 先看邮编呐
|
||||
self.all_zone['postcode'] = self.all_zone['postcode'].astype(str).str.zfill(4)
|
||||
filtered_df = self.all_zone[self.all_zone['postcode'] == postcode]
|
||||
if filtered_df.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
|
||||
post = filtered_df['post'].iloc[0] # 获取第一行的 '地区代码'
|
||||
try:
|
||||
base = self.all_price[self.all_price['post']==post]['Base'].iloc[0]
|
||||
per = self.all_price[self.all_price['post']==post]['Per'].iloc[0]
|
||||
minimun = self.all_price[self.all_price['post']==post]['Minimun'].iloc[0]
|
||||
except:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
total_weight = 0 # 总计费重,用于计算偏远附加费
|
||||
for package in packages:
|
||||
volume_weight = package.get_volume_weight(4)
|
||||
billing_weight = max(package.weight,volume_weight)
|
||||
total_weight += billing_weight # 总计费重,用于计算偏远附加费
|
||||
detail_amount['base'] += max(base + per * math.ceil(billing_weight/1000), minimun)
|
||||
if billing_weight <= 22000:
|
||||
detail_amount['Homedelivery'] += self.homedelivery_fee[0]
|
||||
elif 22000<billing_weight <=55000:
|
||||
detail_amount['Homedelivery'] += self.homedelivery_fee[1]
|
||||
elif 55000<package.weight<=90000 or 55000<volume_weight<=135000:
|
||||
detail_amount['Homedelivery'] += self.homedelivery_fee[2]
|
||||
else:
|
||||
detail_amount['Homedelivery'] += self.homedelivery_fee[3]
|
||||
[7.8,10.92,12.41,26.42,92.15,123.96,160.07]
|
||||
if 110<package.fst_size <=160:
|
||||
detail_amount['widthover'] += self.oversize_fee[0]
|
||||
elif 160<package.fst_size <=240:
|
||||
detail_amount['widthover'] += self.oversize_fee[1]
|
||||
elif 240<package.fst_size <=359:
|
||||
detail_amount['lengthover'] += self.oversize_fee[2]
|
||||
elif 359<package.fst_size <=419:
|
||||
detail_amount['lengthover'] += self.oversize_fee[3]
|
||||
elif 419<package.fst_size <=479:
|
||||
detail_amount['lengthover'] += self.oversize_fee[4]
|
||||
elif 479<package.fst_size <=599:
|
||||
detail_amount['lengthover'] += self.oversize_fee[5]
|
||||
elif package.fst_size > 599:
|
||||
detail_amount['lengthover'] += self.oversize_fee[6]
|
||||
if 130<=package.fst_size <=190 and package.sed_size > 90 and (package.weight >46000 or billing_weight>91000):
|
||||
detail_amount['twomancrew'] = self.twomancrew_fee[0]
|
||||
if 190<=package.fst_size <=240 and package.sed_size > 130 and (package.weight >55000 or billing_weight>110000):
|
||||
detail_amount['twomancrew'] = self.twomancrew_fee[1]
|
||||
if 240<package.fst_size and package.sed_size > 130 and (package.weight >75000 or billing_weight>150000):
|
||||
detail_amount['twomancrew'] = self.twomancrew_fee[2]
|
||||
if billing_weight >30000 or package.fst_size >110:
|
||||
detail_amount['handling'] += self.handing_fee
|
||||
|
||||
# 计算偏远附加费,只跟地区有关,不需要放入循环内部计算
|
||||
postcode_counts = self.all_zone[self.all_zone['postcode'] == postcode].shape[0] # 该邮编总共多少个地区
|
||||
remote_count = self.all_remote[self.all_remote['Postcode'] == postcode].shape[0] # 该邮编有多少个偏远地区
|
||||
postcode_counts = int(postcode_counts)
|
||||
remote_count=int(remote_count)
|
||||
# 该邮编偏远地区的偏远费
|
||||
remote_base_fee = self.all_remote[self.all_remote['Postcode']==postcode]['Base'].mean() # 该邮编偏远地区的偏远基础费
|
||||
remote_per_fee = self.all_remote[self.all_remote['Postcode']==postcode]['Per'].mean()
|
||||
# 检查 remote_base_fee 是否为 NaN,若是则设为 0
|
||||
if pd.isna(remote_base_fee) or pd.isna(remote_per_fee):
|
||||
remote_base_fee = 0
|
||||
remote_per_fee = 0
|
||||
|
||||
# 计算最终的偏远附加费
|
||||
if postcode_counts > 0 and remote_count > 0:
|
||||
detail_amount['remote'] = remote_base_fee+remote_per_fee*math.ceil(total_weight/1000)* remote_count/postcode_counts
|
||||
|
||||
for key in detail_amount:
|
||||
if key!= 'tail_amount' and key!= 'fuel':
|
||||
detail_amount['tail_amount'] += detail_amount[key]
|
||||
if key!='widthover' and key!='handling':
|
||||
detail_amount['fuel'] += detail_amount[key] * self.fuel_rate
|
||||
detail_amount['tail_amount'] += detail_amount['fuel']
|
||||
return detail_amount
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 测试
|
||||
aau = PostLogistics_AU()
|
||||
package = Package("wxx",40,25,25,1780)
|
||||
packages = Package_group([package])
|
||||
aau.calculate_fee(packages,'3101')
|
||||
TollLogistics_AU.active = True
|
||||
PostLogistics_AU.active = True
|
||||
|
|
@ -0,0 +1,434 @@
|
|||
""" 欧洲国家尾端物流模块实现类"""
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import pandas
|
||||
from logisticsClass.logisticsBaseClass import LogisticsType, TailLogistics
|
||||
"""
|
||||
port:NL(default)
|
||||
currency:str = 'EUR'
|
||||
logistics_type:LogisticsType (快递,卡派)
|
||||
"""
|
||||
|
||||
# DPD-ASL 欧洲国家逻辑基类
|
||||
class DPDASLLogistics(TailLogistics):
|
||||
"""DPD-ASL"""
|
||||
company = "DPD-ASL" # DPD_ASL,荷兰发件,欧洲地区默认港口
|
||||
currency = "EUR"
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee = None
|
||||
self.overweight = 2.89
|
||||
self.bigpackage = 44.5
|
||||
self.remote_fee = None
|
||||
self.fuel_rate = 0.13
|
||||
def is_remote(self,postcode):
|
||||
"""判断是否偏远,1偏远0非偏远"""
|
||||
raise NotImplementedError("Subclasses must implement remote calculation.")
|
||||
def calculate_fee(self,packages,postcode):
|
||||
"""计算快递费用"""
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"overweight":0.00,
|
||||
"bigpackage":0.00,
|
||||
"remote":0.00,
|
||||
"fuel":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
isremote = self.is_remote(postcode)
|
||||
if isremote == "邮编格式不合法":
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
for package in packages: # 逐个处理列表中的每个包裹
|
||||
if package.weight > 31500 or package.girth > 350 or package.fst_size > 200:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
detail_amount['base'] += self.base_fee
|
||||
detail_amount['remote'] +=self.remote_fee* isremote
|
||||
detail_amount['overweight'] += self.overweight if package.weight >= 20000 else 0
|
||||
if package.fst_size >=175 or package.weight >= 30000 or package.girth >= 300:
|
||||
detail_amount['bigpackage'] += self.bigpackage
|
||||
for key in detail_amount:
|
||||
if key != 'tail_amount' and key != 'fuel':
|
||||
detail_amount['fuel'] += detail_amount[key] * self.fuel_rate
|
||||
detail_amount['tail_amount'] += detail_amount[key]
|
||||
detail_amount['tail_amount'] +=detail_amount['fuel']
|
||||
return detail_amount
|
||||
|
||||
# DPDASL 德国实现
|
||||
class DPDASLLogistics_DE(DPDASLLogistics):
|
||||
country_code = 'DE'
|
||||
country = 'Germany'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee = 7.1
|
||||
self.remote_fee = 12
|
||||
def is_remote(self,postcode):
|
||||
"""判断是否偏远,1偏远0非偏远"""
|
||||
# 先判断邮编是否合法
|
||||
if not re.match(r'^\d{5}$', postcode):
|
||||
return "邮编格式不合法"
|
||||
remote_postcodes = ["18565","25845-25847","25849","25859","25863","25869","25929-25933","25938-25942",
|
||||
"25946-25949","25952-25955","25980","25961-25970","25985-25986","25988-25990","25992-25994",
|
||||
"25996-25999","26465","26474","26486","26548","26571","26579","26757","27498","83256"
|
||||
]
|
||||
postcodes = []
|
||||
for code in remote_postcodes:
|
||||
if '-' in code:
|
||||
start,end = code.split('-')
|
||||
postcodes.extend(list(range(int(start),int(end)+1)))
|
||||
else:
|
||||
postcodes.append(int(code))
|
||||
return 1 if postcode in postcodes else 0
|
||||
|
||||
# DPDASL 法国实现
|
||||
class DPDASLLogistics_FR(DPDASLLogistics):
|
||||
country_code = 'FR'
|
||||
country = 'France'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee = 10.2
|
||||
self.remote_fee = 22.5
|
||||
def is_remote(self,postcode):
|
||||
"""判断是否偏远,1偏远0非偏远"""
|
||||
# 先判断邮编是否合法
|
||||
if not re.match(r'^\d{5}$', postcode):
|
||||
return "邮编格式不合法"
|
||||
if postcode.startswith("20"):
|
||||
return 1
|
||||
remove_postcodes = ["17111","17123","17190","17310","17370","17410","17480","17550","17580","17590","17630","17650",
|
||||
"17670","17740","17840","17880","17940","56360","56590","56780","56840","85330","85350"]
|
||||
return 1 if postcode in remove_postcodes else 0
|
||||
# DPDASL 西班牙实现
|
||||
class DPDASLLogistics_ES(DPDASLLogistics):
|
||||
country_code = 'ES'
|
||||
country = 'Spain'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee = 11.9
|
||||
self.remote_fee = 34
|
||||
def is_remote(self,postcode):
|
||||
"""判断是否偏远,1偏远0非偏远"""
|
||||
# 先判断邮编是否合法
|
||||
if not re.match(r'^\d{5}$', postcode):
|
||||
return "邮编格式不合法"
|
||||
if postcode.startswith("07") or postcode.startswith("35") or postcode.startswith("38"):
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
# DPD-ZG 欧洲国家逻辑基类
|
||||
class DPDZGLogistics(TailLogistics):
|
||||
"""DPD-智谷"""
|
||||
company = "DPD-ZG" # DPD-ZG,可以海运可以空运,海运港口NL,空运港口AMS
|
||||
currency = "EUR"
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee:list #[5,10,30]
|
||||
self.operate_rate = 0.18
|
||||
self.fuel_rate = 0.125
|
||||
self.remote_fee:float
|
||||
def is_remote(self,postcode):
|
||||
"""判断是否偏远,1偏远0非偏远"""
|
||||
raise NotImplementedError("Subclasses must implement remote calculation.")
|
||||
def calculate_fee(self,packages,postcode):
|
||||
"""计算快递费用"""
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"operate":0.00,
|
||||
"remote":0.00,
|
||||
"fuel":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
isremote = self.is_remote(postcode)
|
||||
if isremote == "邮编格式不合法":
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
for package in packages: # 逐个处理列表中的每个包裹
|
||||
if package.weight > 31500 or package.girth > 300 or package.fst_size >175:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
if package.weight <5000:
|
||||
detail_amount['base'] += self.base_fee[0]
|
||||
elif package.weight < 10000:
|
||||
detail_amount['base'] += self.base_fee[1]
|
||||
else:
|
||||
detail_amount['base'] += self.base_fee[2]
|
||||
detail_amount['remote'] += float(self.remote_fee) * float(isremote)
|
||||
detail_amount['operate'] += package.weight * self.operate_rate
|
||||
for key in detail_amount:
|
||||
if key != 'tail_amount' and key != 'fuel':
|
||||
detail_amount['fuel'] += detail_amount[key] * self.fuel_rate
|
||||
detail_amount['tail_amount'] += detail_amount[key]
|
||||
detail_amount['tail_amount'] +=detail_amount['fuel']
|
||||
return detail_amount
|
||||
|
||||
# DPD-ZG 德国实现
|
||||
class DPDZGLogistics_DE(DPDZGLogistics):
|
||||
country_code = 'DE'
|
||||
country = 'Germany'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee = [7.72,8.83,11.67]
|
||||
self.operate_rate = 0.18
|
||||
self.fuel_rate = 0.125
|
||||
self.remote_fee=11.5
|
||||
def is_remote(self,postcode):
|
||||
"""判断是否偏远,1偏远0非偏远"""
|
||||
# 先判断邮编是否合法
|
||||
if not re.match(r'^\d{5}$', postcode):
|
||||
return "邮编格式不合法"
|
||||
remote_postcodes = ["18565","25845-25847","25849","25859","25863","25869","25929-25933","25938-25942",
|
||||
"25946-25949","25952-25955","25980","25961-25970","25985-25986","25988-25990","25992-25994",
|
||||
"25996-25999","26465","26474","26486","26548","26571","26579","26757","27498","83256"
|
||||
]
|
||||
postcodes = []
|
||||
for code in remote_postcodes:
|
||||
if '-' in code:
|
||||
start,end = code.split('-')
|
||||
postcodes.extend(list(range(int(start),int(end)+1)))
|
||||
else:
|
||||
postcodes.append(int(code))
|
||||
return 1 if postcode in postcodes else 0
|
||||
|
||||
# DPD-ZG 法国实现
|
||||
class DPDZGLogistics_FR(DPDZGLogistics):
|
||||
country_code = 'FR'
|
||||
country = 'France'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee = [10.77,11.92,14.03]
|
||||
self.operate_rate = 0.18
|
||||
self.fuel_rate = 0.125
|
||||
self.remote_fee= 18.5
|
||||
def is_remote(self,postcode):
|
||||
"""判断是否偏远,1偏远0非偏远"""
|
||||
# 先判断邮编是否合法
|
||||
if not re.match(r'^\d{5}$', postcode):
|
||||
return "邮编格式不合法"
|
||||
if postcode.startswith("20"):
|
||||
return 1
|
||||
remove_postcodes = ["17111","17123","17190","17310","17370","17410","17480","17550","17580","17590","17630","17650",
|
||||
"17670","17740","17840","17880","17940","56360","56590","56780","56840","85330","85350"]
|
||||
return 1 if postcode in remove_postcodes else 0
|
||||
|
||||
# DPD-ZG 西班牙实现
|
||||
class DPDZGLogistics_SP(DPDZGLogistics):
|
||||
country_code = 'ES'
|
||||
country = 'Spain'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee = [12.1,13.17,15.46]
|
||||
self.operate_rate = 0.18
|
||||
self.fuel_rate = 0.125
|
||||
self.remote_fee= 32
|
||||
def is_remote(self,postcode):
|
||||
"""判断是否偏远,1偏远0非偏远"""
|
||||
# 先判断邮编是否合法
|
||||
if not re.match(r'^\d{5}$', postcode):
|
||||
return "邮编格式不合法"
|
||||
if postcode.startswith("07") or postcode.startswith("35") or postcode.startswith("38"):
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
# 卡派-ASL 欧洲各国基类
|
||||
class KPASLLogistics(TailLogistics):
|
||||
"""卡派—ASL"""
|
||||
company = "卡派-ASL" # 欧洲国家的卡派
|
||||
currency = "EUR"
|
||||
logistics_type = LogisticsType.COURIER
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
price_path = parent_current_directory.joinpath("data")
|
||||
_price_files = price_path.joinpath("欧洲卡派.xlsx")
|
||||
ltl_cost = None
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if cls.ltl_cost is None:
|
||||
cls.ltl_cost = pandas.read_excel(cls._price_files,sheet_name="DHL卡派IP报价")
|
||||
return super().__new__(cls)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.fuel_rate = 0.08
|
||||
def calculate_fee(self,packages,postcode):
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"fuel":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
postcode = str(postcode)
|
||||
postcode_str = postcode[:2]
|
||||
bill_weight = 0
|
||||
for package in packages:
|
||||
bill_weight +=package.weight/1000
|
||||
base_df = self.ltl_cost[(self.ltl_cost['Country']==self.country_code)&(self.ltl_cost['Postalcode']==postcode_str)]
|
||||
if base_df.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
detail_amount['base'] = base_df['1 IP'].iloc[0]
|
||||
detail_amount['fuel'] = detail_amount['base'] * self.fuel_rate
|
||||
detail_amount['tail_amount'] = detail_amount['base'] + detail_amount['fuel']
|
||||
return detail_amount
|
||||
# ASL卡派德国
|
||||
class KPASLLogistics_DE(KPASLLogistics):
|
||||
country_code = 'DE'
|
||||
country = 'Germany'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def calculate_fee(self,packages,postcode):
|
||||
return super().calculate_fee(packages,postcode)
|
||||
# ASL卡派法国
|
||||
class KPASLLogistics_FR(KPASLLogistics):
|
||||
country_code = 'FR'
|
||||
country = 'France'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def calculate_fee(self,packages,postcode):
|
||||
return super().calculate_fee(packages,postcode)
|
||||
# ASL卡派西班牙
|
||||
class KPASLLogistics_SP(KPASLLogistics):
|
||||
country_code = 'ES'
|
||||
country = 'Spain'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def calculate_fee(self,packages,postcode):
|
||||
return super().calculate_fee(packages,postcode)
|
||||
|
||||
# 卡派-GEL 欧洲各国基类
|
||||
class KPGELLogistics(TailLogistics):
|
||||
"""卡派—GEL
|
||||
6000抛重,计费重
|
||||
"""
|
||||
company = "卡派-GEL" # 欧洲国家的卡派
|
||||
currency = "EUR"
|
||||
logistics_type = LogisticsType.COURIER
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
price_path = parent_current_directory.joinpath("data")
|
||||
_price_files = price_path.joinpath("欧洲卡派-GEL.xlsx")
|
||||
base_cost = None
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if cls.base_cost is None:
|
||||
cls.base_cost = pandas.read_excel(cls._price_files,sheet_name="基础费用")
|
||||
return super().__new__(cls)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.notify = 5 # 预约通知费,必收
|
||||
self.remote_fee = 9 # 德国偏远费,17/18/19
|
||||
self.multiple_package = 2.5 # 多包裹附加费,超过三个包裹的,每个包裹2.5EUR
|
||||
self.responsibility = 1.85 # 责任保险,必收
|
||||
self.management = 2 # 订单管理费,必收
|
||||
self.toll = 0.024 # 过路过桥费,0.024*分段最大kg
|
||||
|
||||
def is_remote(self,postcode):
|
||||
"""判断分区,德国只有1,法国有123,"""
|
||||
raise NotImplementedError("Subclasses must implement remote calculation.")
|
||||
|
||||
def calculate_fee(self,packages,postcode):
|
||||
# 6000计费重
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"notify":0.00,
|
||||
"multiple_package":0.00,
|
||||
"responsibility":0.00,
|
||||
"management":0.00,
|
||||
"toll" :0.00,
|
||||
"remote":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
postcode = str(postcode)
|
||||
zone = self.is_remote(postcode)
|
||||
# 德国偏远费
|
||||
if postcode[:2] in ['17','18','19'] and self.country_code =='DE':
|
||||
detail_amount['remote'] = self.remote_fee
|
||||
|
||||
base_df = self.base_cost[(self.base_cost['国家'] == self.country_code) & (self.base_cost['分区'] == zone)]
|
||||
if base_df.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
|
||||
total_weight = 0
|
||||
for package in packages:
|
||||
if (package.fst_size > 320 or package.sed_size>170 or package.trd_size>120) and (self.country_code != "DE"):
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
if package.fst_size > 320 or package.sed_size>200 or package.trd_size>120:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
billing_weight = max(package.weight/1000,package.get_volume_weight(6000))
|
||||
total_weight += billing_weight
|
||||
# 基础费用
|
||||
weight_limit =[col for col in base_df.columns if isinstance(col, (int, float))]
|
||||
for weight in weight_limit:
|
||||
if total_weight <= weight:
|
||||
detail_amount['base'] = base_df[weight].iloc[0]
|
||||
detail_amount['toll'] = self.toll * int(weight)
|
||||
break
|
||||
if total_weight > 300:
|
||||
detail_amount['base'] = detail_amount['base'] * total_weight
|
||||
|
||||
detail_amount['notify'] = self.notify
|
||||
detail_amount['multiple_package'] = self.multiple_package * len(packages) if len(packages) > 3 else 0
|
||||
detail_amount['responsibility'] = self.responsibility
|
||||
detail_amount['management'] = self.management
|
||||
for key in detail_amount:
|
||||
if key != 'tail_amount':
|
||||
detail_amount['tail_amount'] += detail_amount[key]
|
||||
return detail_amount
|
||||
|
||||
# GEL卡派德国
|
||||
class KPGELLogistics_DE(KPGELLogistics):
|
||||
country_code = 'DE'
|
||||
country = 'Germany'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def is_remote(self,postcode):
|
||||
return 1
|
||||
def calculate_fee(self,packages,postcode):
|
||||
return super().calculate_fee(packages,postcode)
|
||||
# GEL卡派法国
|
||||
class KPGELLogistics_FR(KPGELLogistics):
|
||||
country_code = 'FR'
|
||||
country = 'France'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def is_remote(self, postcode):
|
||||
postcode = str(postcode)
|
||||
postcode_str = postcode[:2]
|
||||
if postcode_str in ["75","78","91","92","93","94","95",]:
|
||||
return 1
|
||||
elif postcode_str in [
|
||||
"01", "02", "03", "08", "10", "14", "16", "17", "18","21", "22", "23", "25", "27", "28", "29",
|
||||
"35", "36", "37", "39", "41", "42", "44", "45","49", "50", "51", "52", "53", "54", "55", "56",
|
||||
"57","58", "59", "60", "61", "62", "63","67", "68", "69", "70", "71", "72", "73", "74",
|
||||
"76", "77", "79", "80","85", "86", "87", "88", "89", "90"
|
||||
]:
|
||||
return 2
|
||||
elif postcode_str in [
|
||||
"04", "05", "06", "07", "09", "11", "12", "13", "15", "19", "24", "26",
|
||||
"30", "31", "32", "33", "34", "38", "40", "43", "46", "47", "48",
|
||||
"64", "65", "66", "81", "82", "83", "84"
|
||||
]:
|
||||
return 3
|
||||
|
||||
def calculate_fee(self,packages,postcode):
|
||||
return super().calculate_fee(packages,postcode)
|
||||
# GEL卡派西班牙
|
||||
class KPGELLogistics_SP(KPGELLogistics):
|
||||
country_code = 'ES'
|
||||
country = 'Spain'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def is_remote(self, postcode):
|
||||
postcode = str(postcode)
|
||||
if postcode.startswith("07"):
|
||||
return 2
|
||||
else:
|
||||
return 1
|
||||
def calculate_fee(self,packages,postcode):
|
||||
return super().calculate_fee(packages,postcode)
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
""" 英国尾端物流模块实现类"""
|
||||
import math
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
import pandas
|
||||
from logisticsClass.logisticsBaseClass import LogisticsType, TailLogistics
|
||||
|
||||
|
||||
class DPDLogistics_UK(TailLogistics):
|
||||
# 实重计费
|
||||
country_code = 'UK'
|
||||
country = 'United Kingdom'
|
||||
company = '智谷-DPD'
|
||||
currency = 'GBP'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee = 3.7
|
||||
self.oversize = 26
|
||||
self.fuel_rate = 0.2 # BT开头的有20%燃油费
|
||||
self.remote_fee = 35
|
||||
|
||||
def calculate_fee(self, packages, postcode):
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"oversize":0.00,
|
||||
"remote":0.00,
|
||||
"fuel":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
# zone = self.is_remote(postcode)
|
||||
# if zone == 2:
|
||||
# return detail_amount
|
||||
if self.is_remote(postcode):
|
||||
detail_amount['remote'] = self.remote_fee
|
||||
for package in packages:
|
||||
if package.weight >= 40000 or package.fst_size >= 175 or package.girth>=339:
|
||||
detail_amount['tail_amount'] =99999
|
||||
return detail_amount
|
||||
if package.weight >= 30000 or package.fst_size >= 100 or package.sed_size >=60:
|
||||
detail_amount['oversize'] += self.oversize
|
||||
detail_amount['base'] += self.base_fee
|
||||
|
||||
detail_amount['tail_amount'] = detail_amount['base'] + detail_amount['oversize'] + detail_amount['remote']
|
||||
# if postcode.startswith('BT'):
|
||||
# detail_amount['fuel'] = detail_amount['tail_amount'] * self.fuel_rate
|
||||
# detail_amount['tail_amount'] += detail_amount['fuel']
|
||||
return detail_amount
|
||||
|
||||
def is_remote(self,postcode):
|
||||
"""判断是否偏远,1偏远0非偏远"""
|
||||
# 先判断邮编是否合法
|
||||
# if not re.match(r'^[A-Z]{1,2}[0-9]{1,2}[A-Z]?\s?[0-9][ABD-HJLNP-UW-Z]{2}$', postcode):
|
||||
# print("邮编不合法")
|
||||
# return 2
|
||||
postcode = postcode.split()[0].upper()
|
||||
if postcode[0:2] not in ['BT','IM','JE','ZE','GY','HS','PO','IV','KA','KW','PH','PA']:
|
||||
return 0
|
||||
remote_postcodes = ["GY1-9","HS1-9","PO30-41","IM1-9","JE1-4","ZE1-3","BT1-71",
|
||||
"BT86-88","BT74-82","BT92-97","IV1-28","IV36","IV40-56","PA20-38",
|
||||
"PA41-48","IV63","KA27-28","KW1-17","PH19-26","PH31-44"]
|
||||
postcodelist = []
|
||||
# 解析并直接展开
|
||||
for postcodes in remote_postcodes:
|
||||
if "-" in postcodes: # 如果包含范围
|
||||
prefix = ''.join(filter(str.isalpha, postcodes)) # 提取字母前缀
|
||||
start, end = map(int, (''.join(filter(str.isdigit, part)) for part in postcodes.split("-")))
|
||||
postcodelist.extend([f"{prefix}{i}" for i in range(start, end + 1)])
|
||||
else:
|
||||
postcodelist.append(postcodes) # 没有范围,直接添加
|
||||
return 1 if postcode in postcodelist else 0
|
||||
|
||||
class bigLogistics_UK(TailLogistics):
|
||||
# 计费重5000取大
|
||||
country_code = 'UK'
|
||||
country = 'United Kingdom'
|
||||
company = '智谷-大件'
|
||||
currency = 'GBP'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee:float = 8
|
||||
self.oversize:float = 0
|
||||
self.congestion = 3
|
||||
def calculate_fee(self, packages, postcode):
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"oversize":0.00,
|
||||
"congestion":0.00, # 伦敦中心地区有拥堵费
|
||||
"tail_amount":0.00}
|
||||
# 0正常,1收拥堵费,2不派送
|
||||
is_remote = self.is_remote(postcode)
|
||||
if is_remote==2:
|
||||
detail_amount['tail_amount'] =99999
|
||||
return detail_amount
|
||||
elif is_remote==1:
|
||||
detail_amount['congestion'] = self.congestion
|
||||
|
||||
for package in packages:
|
||||
# 计费重
|
||||
volume_weight = package.get_volume_weight(5000)
|
||||
bill_weight = math.ceil(max(package.weight/1000,volume_weight))
|
||||
if package.weight/1000 > 150 or volume_weight > 600 or package.fst_size > 300:
|
||||
detail_amount['tail_amount'] =99999
|
||||
return detail_amount
|
||||
# 大包 计费重 20以内-->8,(20,50)-->0.5*(计费重-20)+8 , 50以上-->50+0.5*(计费重-50)
|
||||
if bill_weight < 50 and package.fst_size < 300:
|
||||
detail_amount['base'] += 8
|
||||
if bill_weight > 20:
|
||||
detail_amount['oversize'] += 0.5 * (bill_weight -20)
|
||||
else:
|
||||
detail_amount['base'] += 50
|
||||
if bill_weight > 50:
|
||||
detail_amount['oversize'] += 0.5 * (bill_weight -50)
|
||||
|
||||
detail_amount['tail_amount'] = detail_amount['base'] + detail_amount['oversize'] + detail_amount['congestion']
|
||||
return detail_amount
|
||||
|
||||
def is_remote(self,postcode):
|
||||
"""判断邮编情况,0正常1收拥堵费,2不派送"""
|
||||
# 先判断邮编是否合法
|
||||
# if not re.match(r'^[A-Z]{1,2}[0-9]{1,2}[A-Z]?\s?[0-9][ABD-HJLNP-UW-Z]{2}$', postcode):
|
||||
# print("邮编不合法")
|
||||
# return 2
|
||||
postcode = postcode.strip().upper()
|
||||
# 先判断london中心区
|
||||
congestion = ['E','EC','N','NW','SE','SW','W','WC']
|
||||
for c in congestion:
|
||||
pattern = rf"^{c}\d.*"
|
||||
if re.match(pattern, postcode):
|
||||
return 1
|
||||
|
||||
# prefixes = ["BS","AL","B","BA","BB","BD","BH","BL","BN","BR","CB","CH","CM","CR","CT","CV","CW","DA","DE","DN","DT","DY","EN","FY","GL","GU","HA","HD","HG","HP","HU","HX","IG","L","LA","LE","LN","LS","LU","M","ME","MK","NG","NN","OL","OX","PE","PO1-24","PR","RG","RH","RM","S","SG","SK","SL","SM","SO","SP","SS","ST","TF","TN","TW","UB","WA","WD","WF","WN","WR","WS","WV","YO","KT","OX","PR","RG","RM","S","SG","SK","SL","SM","SN","ST","TF","TW","UB","WA","WD","WF","WN","WR","WS","WV",]
|
||||
# for prefix in prefixes:
|
||||
# if "-" in prefix:
|
||||
# # 处理范围格式前缀 (如 "PO1-24")
|
||||
# po_post = []
|
||||
# prefix_code = ''.join(filter(str.isalpha, prefix)) # 提取字母前缀
|
||||
# start, end = map(int, (''.join(filter(str.isdigit, part)) for part in prefix.split("-")))
|
||||
# po_post.extend([f"{prefix_code}{i}" for i in range(start, end + 1)])
|
||||
# if postcode.split()[0] in po_post:
|
||||
# return 0
|
||||
# else:
|
||||
# # 精确匹配单一前缀(后面必须是数字或空格)
|
||||
# pattern = rf"^{prefix}\d.*"
|
||||
# if re.match(pattern, postcode):
|
||||
# return 0
|
||||
return 0
|
||||
|
||||
class KPZGLogistics_UK(TailLogistics):
|
||||
# 实重
|
||||
country_code = 'UK'
|
||||
country = 'United Kingdom'
|
||||
company = '海GB-卡派'
|
||||
currency = 'GBP'
|
||||
logistics_type = LogisticsType.COURIER
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_dice = {
|
||||
55: ['RG7-8', 'RG14', 'RG17-29'],
|
||||
65: ['B','CV','DY','NN','WS','WV'],
|
||||
75: ['AL', 'BA', 'BB', 'BD', 'BL', 'BS', 'CB', 'CF', 'CH', 'CM', 'CW', 'DE', 'DN', 'FY',
|
||||
'GL', 'HD', 'HG', 'HP', 'H', 'HU', 'L', 'LA1-9', 'LE', 'NG', 'NN', 'NP', 'OL',
|
||||
'OX', 'PE1-19', 'PE26-29', 'PE34', 'PE38-99', 'PR', 'S', 'SG', 'SK', 'SN', 'ST',
|
||||
'TF', 'WA', 'WD', 'WF', 'WN', 'WR', 'M', 'BR', 'HA','CO', 'DH', 'DL', 'IP', 'NE', 'NR', 'PE20-25', 'PE30-32', 'PE33', 'PE35-37',
|
||||
'PO1-29', 'RH', 'SO', 'SP', 'SR', 'SS', 'TA', 'TS', 'YO', 'UB', 'KT', 'HR','DA','EN',
|
||||
'IG','RG1-7','RG9-13','RG15-16','RG30-99','RM','SL'],
|
||||
78: ['BH','DT','GU1-24','GU26-99','ME','TW','BN','EX','LD','TN','TQ'],
|
||||
80: ['CR','SM'],
|
||||
95: ['E','EC','N','NW','SE','SW','W','WC','CA','CT','DG','EH','FK','G','KA1-26','KA29-99','KY','LA10-99','LL','ML','PA1-19','PL','SA','SY','TD','TR'],
|
||||
125:['DD','PH1-7','PH14'],
|
||||
140:['IV1-3','IV30','IV36','AB10-16','AB21-25'],
|
||||
150:['HS1-2','IV20-29','IV37-99','PH15-26','KW1-14','PH8-13','PO30-41'],
|
||||
160:['HS3-99','KA27-28','PA20-33','PA35-59','PA62-75','PA79-99','PH27-41','PH45-50'],
|
||||
200:['KW15-17','PA34','PA60-61','PA76-78','PH42-44', 'ZE' ],
|
||||
}
|
||||
def calculate_fee(self, packages, postcode):
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"oversize":0.00,
|
||||
"tail_amount":0.00}
|
||||
postcode_prefix = postcode.split()[0].upper()
|
||||
letters = ''.join([char for char in postcode_prefix if char.isalpha()])
|
||||
numbers = ''.join([char for char in postcode_prefix if char.isdigit()])
|
||||
if numbers=='':
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
for price,codes in self.base_dice.items():
|
||||
for code in codes:
|
||||
if letters == code:
|
||||
detail_amount['base'] += price
|
||||
break
|
||||
elif code.startswith(letters) and '-' in code:
|
||||
start, end = map(int, (''.join(filter(str.isdigit, part)) for part in code.split("-")))
|
||||
if int(numbers) in range(start, end + 1):
|
||||
detail_amount['base'] += price
|
||||
break
|
||||
else:
|
||||
code_numbers = ''.join([char for char in code if char.isdigit()])
|
||||
if code_numbers=='':
|
||||
continue
|
||||
if int(numbers) == int(code_numbers):
|
||||
detail_amount['base'] += price
|
||||
break
|
||||
if detail_amount['base']>0:
|
||||
break
|
||||
# 处理超尺寸问题
|
||||
if detail_amount['base']==0:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
for package in packages:
|
||||
if package.fst_size > 180 or package.sed_size >120 or package.trd_size > 100:
|
||||
detail_amount['oversize'] = detail_amount['base']
|
||||
detail_amount['tail_amount'] = detail_amount['base'] + detail_amount['oversize']
|
||||
return detail_amount
|
||||
|
||||
class KPNVlogistics_UK(TailLogistics):
|
||||
country_code = 'UK'
|
||||
country = 'United Kingdom'
|
||||
company = '卡派-NV'
|
||||
currency = 'GBP'
|
||||
logistics_type = LogisticsType.COURIER
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
price_path = parent_current_directory.joinpath("data")
|
||||
_price_files = price_path.joinpath("英国卡派.xlsx")
|
||||
ltl_cost = None
|
||||
ltl_zone = None
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if cls.ltl_cost is None or cls.ltl_zone is None:
|
||||
cls.ltl_cost = pandas.read_excel(cls._price_files,sheet_name="运费")
|
||||
cls.ltl_zone = pandas.read_excel(cls._price_files,sheet_name="分区")
|
||||
return super().__new__(cls)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee = 0
|
||||
self.fuel_rate = 0.1
|
||||
def is_remote(self,postcode):
|
||||
"""根据邮编分区,返回分区"""
|
||||
postcode_prefix = postcode.split()[0].upper()
|
||||
postcode_prefix = str(postcode_prefix)
|
||||
zone_df = self.ltl_zone[self.ltl_zone['邮编']== postcode_prefix]
|
||||
if not zone_df.empty:
|
||||
return zone_df['区域'].values[0]
|
||||
return "不在配送范围内"
|
||||
def calculate_fee(self, packages, postcode):
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"fuel":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
zone = self.is_remote(postcode)
|
||||
if zone == "不在配送范围内":
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
for package in packages:
|
||||
tuopan = math.ceil(package.fst_size/120)
|
||||
tuopan = min(tuopan, 7)
|
||||
base_df = self.ltl_cost[(self.ltl_cost['分区']==zone)&(self.ltl_cost['托盘']==tuopan)]
|
||||
if base_df.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
self.base_fee = base_df['运费'].values[0]
|
||||
price = self.base_fee * tuopan/len(packages)
|
||||
detail_amount['base'] += price
|
||||
detail_amount['fuel'] = detail_amount['base'] * self.fuel_rate
|
||||
detail_amount['tail_amount'] = detail_amount['base']+detail_amount['fuel']
|
||||
return detail_amount
|
||||
|
||||
# class KPDXLogistics_UK(TailLogistics):
|
||||
# country_code = 'UK'
|
||||
# country = 'United Kingdom'
|
||||
# company = 'DX-EL'
|
||||
# currency = 'GBP'
|
||||
# logistics_type = LogisticsType.COURIER
|
||||
# def __init__(self):
|
||||
# super().__init__()
|
||||
# self.base_fee = 0
|
||||
# def calculate_fee(self, packages, postcode):
|
||||
# detail_amount = {
|
||||
# "base":0.00,
|
||||
# "oversize":0.00,
|
||||
# "tail_amount":0.00
|
||||
# }
|
||||
|
||||
if __name__ == '__main__':
|
||||
# # 关闭渠道
|
||||
bigLogistics_UK.active = True
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,854 @@
|
|||
""" 美国尾端物流模块实现类"""
|
||||
import math
|
||||
import re
|
||||
import pandas
|
||||
from logisticsClass.logisticsBaseClass import LogisticsType, TailLogistics
|
||||
from data.us_zone import zone_west, zone_east
|
||||
from pathlib import Path
|
||||
|
||||
"""
|
||||
port:west(default),east
|
||||
currency:str = 'USD'
|
||||
logistics_type:LogisticsType (快递,卡派)
|
||||
"""
|
||||
class WestLogistics_US(TailLogistics):
|
||||
_is_loaded = False
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
remote_path = parent_current_directory.joinpath("data")
|
||||
_postcode_files = [
|
||||
remote_path.joinpath("contiguous_us.txt"),
|
||||
remote_path.joinpath("contiguous_us_extended.txt"),
|
||||
remote_path.joinpath("contiguous_us_remote.txt")
|
||||
]
|
||||
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if not cls._is_loaded:
|
||||
cls._load_postcodes() # 第一次实例化时加载文件
|
||||
cls._is_loaded = True # 标记文件已加载
|
||||
return super().__new__(cls)
|
||||
# Path_current_directory = Path(__file__).parent
|
||||
# current_directory = os.path.dirname(__file__)
|
||||
@classmethod
|
||||
def _load_postcodes(cls):
|
||||
"""加载邮编文件"""
|
||||
cls.postcodes_by_file = {
|
||||
1: set(), # 用于存储contiguous_us.txt的邮政编码
|
||||
2: set(), # 用于存储contiguous_us_extended.txt的邮政编码
|
||||
3: set() # 用于存储contiguous_us_remote.txt的邮政编码
|
||||
}
|
||||
# 加载每个文件,并将邮政编码存入对应的集合中
|
||||
for index, file in enumerate(cls._postcode_files, start=1):
|
||||
with open(file, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
cls.postcodes_by_file[index].add(line.strip())
|
||||
|
||||
def is_remote(self, postcode):
|
||||
"""判断是否偏远 0非偏远,1偏远,2超偏远Extend,3超偏远Remote"""
|
||||
if not re.match(r'\d{5}-\d{4}|\d{5}', postcode):
|
||||
raise ValueError("邮编格式不合法")
|
||||
if not hasattr(self, 'postcodes_by_file'):
|
||||
raise ValueError("Postcodes are not loaded yet.")
|
||||
postcode = postcode[:5]
|
||||
postcode = str(postcode)
|
||||
for index, postcodes in self.postcodes_by_file.items():
|
||||
for line in postcodes:
|
||||
postcodes_range = line.strip().split("\t")
|
||||
if postcode in postcodes_range:
|
||||
return index
|
||||
return 0
|
||||
|
||||
def get_west_zone(self, postcode):
|
||||
"""获取美西邮编分区"""
|
||||
if not re.match(r'\d{5}-\d{4}|\d{5}', postcode):
|
||||
return "邮编格式不合法"
|
||||
postcode = postcode[:5]
|
||||
postcode = int(postcode)
|
||||
for zone, postcodes in zone_west.items():
|
||||
start =int(postcodes[0].split("-")[0])
|
||||
end = int(postcodes[-1].split("-")[-1])
|
||||
if start > postcode or postcode > end:
|
||||
continue
|
||||
for postcode_range in postcodes:
|
||||
if "-" in postcode_range:
|
||||
start, end = map(int, postcode_range.split("-"))
|
||||
if start <= postcode <= end:
|
||||
return zone
|
||||
else:
|
||||
if int(zone) == postcode:
|
||||
return zone
|
||||
return "未查询到邮编分区"
|
||||
|
||||
|
||||
class FedexLogistics(WestLogistics_US):
|
||||
"""Fedex"""
|
||||
country = "United States"
|
||||
country_code = "US"
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.volume_weight_ratio:int # lbs抛重系数
|
||||
self.base_fee:dict
|
||||
self.residential:float
|
||||
self.residential_peak:float # 报价表没写,账单有
|
||||
self.oversize_2:float
|
||||
self.oversize_3:float
|
||||
self.oversize_5:float
|
||||
self.oversize_7:float
|
||||
self.overweight_2:float
|
||||
self.overweight_3:float
|
||||
self.overweight_5:float
|
||||
self.overweight_7:float
|
||||
self.overhanding_2:float # 额外操作费 这个可能用不上。
|
||||
self.overhanding_3:float
|
||||
self.overhanding_5:float
|
||||
self.overhanding_7:float
|
||||
self.ahs_peak:float # 额外处理费旺季附加费
|
||||
self.residential_das:float # 偏远费
|
||||
self.residential_extended:float # 超偏远费
|
||||
self.residential_remote:float # 超超偏远费
|
||||
self.bigpackage_2:float # 大包裹费
|
||||
self.bigpackage_3:float
|
||||
self.bigpackage_5:float
|
||||
self.bigpackage_7:float
|
||||
self.bigpackage_peak:float # 大包裹旺季附加费
|
||||
self.return_package:float # 超大包裹(不可发)
|
||||
self.fuel_rate = 0.16 # 燃油费率
|
||||
|
||||
def calculate_fee(self,packages,postcode):
|
||||
"""计算运费,抛重计费,系数275"""
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"oversize":0.00,
|
||||
"remote":0.00,
|
||||
"big_package":0.00,
|
||||
"big_package_peak":0.00,
|
||||
"residential_delivery":0.00,
|
||||
"residential_peak":0.00,
|
||||
"fuel":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
# 先看分区
|
||||
zone = self.get_west_zone(postcode)
|
||||
if zone == 2:
|
||||
ahs_oversize = self.oversize_2
|
||||
ahs_overweight = self.overweight_2
|
||||
ahs_overhanding = self.overhanding_2
|
||||
big_oversize = self.bigpackage_2
|
||||
elif zone == 3 or zone == 4:
|
||||
ahs_oversize = self.oversize_3
|
||||
ahs_overweight = self.overweight_3
|
||||
ahs_overhanding = self.overhanding_3
|
||||
big_oversize = self.bigpackage_3
|
||||
elif zone == 5 or zone == 6:
|
||||
ahs_oversize = self.oversize_5
|
||||
ahs_overweight = self.overweight_5
|
||||
ahs_overhanding = self.overhanding_5
|
||||
big_oversize = self.bigpackage_5
|
||||
elif zone == 7 or zone == 8:
|
||||
ahs_oversize = self.oversize_7
|
||||
ahs_overweight = self.overweight_7
|
||||
ahs_overhanding = self.overhanding_7
|
||||
big_oversize = self.bigpackage_7
|
||||
else:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
# 再看是否偏远 0非偏远,1偏远,2超偏远Extend,3超偏远Remote
|
||||
is_remote = self.is_remote(postcode)
|
||||
if is_remote == 1:
|
||||
remote_fee = self.residential_das
|
||||
elif is_remote ==2:
|
||||
remote_fee = self.residential_extended
|
||||
elif is_remote ==3:
|
||||
remote_fee = self.residential_remote
|
||||
else:
|
||||
remote_fee = 0
|
||||
|
||||
for package in packages:
|
||||
if package.lbs_weight>150 or package.fst_inch>108 or package.girth_inch >165:
|
||||
detail_amount['tail_amount'] = self.return_package
|
||||
return detail_amount
|
||||
cal_weight = max(package.lbs_weight,package.volume_inch / self.volume_weight_ratio)
|
||||
ahs_fee = 0 # AHS费系列,多种存在只取最高
|
||||
if package.fst_inch > 48 or package.sed_inch> 30 or package.girth_inch>105:
|
||||
ahs_fee = ahs_oversize +self.ahs_peak
|
||||
cal_weight = max(cal_weight,40)
|
||||
if package.lbs_weight > 50:
|
||||
ahs_fee = max(ahs_fee,ahs_overweight +self.ahs_peak)
|
||||
if package.girth_inch >130 or package.fst_inch >96:
|
||||
cal_weight = max(cal_weight,90)
|
||||
if cal_weight > 150:
|
||||
detail_amount['tail_amount'] = self.return_package
|
||||
return detail_amount
|
||||
detail_amount['oversize'] += 0 if package.girth_inch >130 or package.fst_inch >96 else ahs_fee
|
||||
detail_amount['remote'] += remote_fee
|
||||
detail_amount['big_package'] += big_oversize if package.girth_inch >130 or package.fst_inch >96 else 0
|
||||
detail_amount['big_package_peak'] += self.bigpackage_peak if package.girth_inch >130 or package.fst_inch >96 else 0
|
||||
detail_amount['residential_delivery'] += self.residential
|
||||
detail_amount['residential_peak'] += self.residential_peak
|
||||
|
||||
detail_amount['base'] +=self.base_price[self.base_price['lbs.']==math.ceil(cal_weight)][zone].values[0]
|
||||
|
||||
for key in detail_amount:
|
||||
if key!= 'tail_amount' and key!= 'fuel':
|
||||
detail_amount['tail_amount'] += detail_amount[key]
|
||||
detail_amount['fuel'] += detail_amount[key] * self.fuel_rate
|
||||
detail_amount['tail_amount'] += detail_amount['fuel']
|
||||
return detail_amount
|
||||
|
||||
class FedexPPLogistics_US(FedexLogistics):
|
||||
company="Fedex-彩虹小马"
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
price_path = parent_current_directory.joinpath("data")
|
||||
_price_files = price_path.joinpath("美国快递.xlsx")
|
||||
base_price = None
|
||||
def __new__(cls):
|
||||
if cls.base_price is None:
|
||||
cls.base_price = pandas.read_excel(cls._price_files,sheet_name='邮差小马')
|
||||
return super().__new__(cls)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.volume_weight_ratio=250 # lbs抛重系数
|
||||
self.residential = 6.38
|
||||
self.residential_peak = 0 # 0.33 0.6 # 报价表没写,账单有
|
||||
self.oversize_2 = 4.50
|
||||
self.oversize_3 = 4.99
|
||||
self.oversize_5 = 5.47
|
||||
self.oversize_7 = 6.11
|
||||
self.overweight_2 =6.99
|
||||
self.overweight_3 = 7.64
|
||||
self.overweight_5 =8.12
|
||||
self.overweight_7 = 8.84
|
||||
self.overhanding_2 = 4.02 # 额外操作费
|
||||
self.overhanding_3 = 4.66
|
||||
self.overhanding_5 = 4.91
|
||||
self.overhanding_7 = 5.07
|
||||
self.ahs_peak = 0 # 4.16 5.36 # 操作费旺季附加费
|
||||
self.residential_das = 1.58# 偏远费
|
||||
self.residential_extended = 1.97 # 超偏远费
|
||||
self.residential_remote = 5.82 # 超超偏远费
|
||||
self.bigpackage_2 = 32.94 # 大包裹费
|
||||
self.bigpackage_3 = 36.16
|
||||
self.bigpackage_5 = 38.57
|
||||
self.bigpackage_7 = 41.78
|
||||
self.bigpackage_peak = 0 # 45.26 53.56# 大包裹旺季附加费
|
||||
self.fuel_rate = 0.16 # 燃油费率
|
||||
self.return_package = 1419.34 # 超大包裹(不可发)
|
||||
|
||||
class FedexKHLogistics_US(FedexLogistics):
|
||||
"""金宏亚"""
|
||||
company = "Fedex-金宏亚"
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
price_path = parent_current_directory.joinpath("data")
|
||||
_price_files = price_path.joinpath("美国快递.xlsx")
|
||||
base_price = None
|
||||
def __new__(cls):
|
||||
if cls.base_price is None:
|
||||
cls.base_price = pandas.read_excel(cls._price_files,sheet_name='金宏亚')
|
||||
return super().__new__(cls)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.volume_weight_ratio = 250 # lbs抛重系数
|
||||
self.residential = 2.1
|
||||
self.residential_peak =0 # 0.57 0.31 # 报价表没写,账单有
|
||||
self.oversize_2 = 2.3
|
||||
self.oversize_3 = 2.6
|
||||
self.oversize_5 = 2.9
|
||||
self.oversize_7 = 3.2
|
||||
self.overweight_2 =3.6
|
||||
self.overweight_3 = 3.9
|
||||
self.overweight_5 =4.2
|
||||
self.overweight_7 =4.6
|
||||
self.overhanding_2 = 3.1 # 额外操作费
|
||||
self.overhanding_3 = 3.6
|
||||
self.overhanding_5 = 3.8
|
||||
self.overhanding_7 = 3.9
|
||||
self.ahs_peak = 0 #1.3 # 操作费旺季附加费
|
||||
self.residential_das = 2.26 # 偏远费
|
||||
self.residential_extended = 3.02 # 超偏远费
|
||||
self.residential_remote = 5.64# 超超偏远费
|
||||
self.bigpackage_2 = 120 # 大包裹费
|
||||
self.bigpackage_3 = 130
|
||||
self.bigpackage_5 = 145
|
||||
self.bigpackage_7 = 152.5
|
||||
self.bigpackage_peak =43.94 # 42.25# 大包裹旺季附加费
|
||||
self.return_package = 1325 # 超大包裹(不可发)
|
||||
self.fuel_rate = 0.16 # 燃油费率
|
||||
|
||||
class FedexHOMELogistics_US(FedexLogistics):
|
||||
"""FEDEX-HOME (1-35%-30%)"""
|
||||
company = "Fedex-HOME"
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
price_path = parent_current_directory.joinpath("data")
|
||||
_price_files = price_path.joinpath("美国快递.xlsx")
|
||||
base_price = None
|
||||
def __new__(cls):
|
||||
if cls.base_price is None:
|
||||
cls.base_price = pandas.read_excel(cls._price_files,sheet_name='FEDEX')
|
||||
return super().__new__(cls)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.volume_weight_ratio = 250 # lbs抛重系数
|
||||
self.residential = 2.08
|
||||
self.residential_peak =0 # 0.57 0.31 # 报价表没写,账单有
|
||||
self.oversize_2 = 6.02
|
||||
self.oversize_3 = 6.67
|
||||
self.oversize_5 = 7.31
|
||||
self.oversize_7 = 8.17
|
||||
self.overweight_2 =9.35
|
||||
self.overweight_3 = 10.21
|
||||
self.overweight_5 =10.86
|
||||
self.overweight_7 = 11.83
|
||||
self.overhanding_2 = 5.375# 额外操作费
|
||||
self.overhanding_3 = 6.235
|
||||
self.overhanding_5 = 6.5575
|
||||
self.overhanding_7 = 6.7725
|
||||
self.ahs_peak = 0 #1.3 # 操作费旺季附加费
|
||||
self.residential_das = 2.17 # 偏远费
|
||||
self.residential_extended = 2.91 # 超偏远费
|
||||
self.residential_remote = 5.43# 超超偏远费
|
||||
self.bigpackage_2 = 45.58 # 大包裹费
|
||||
self.bigpackage_3 = 49.28
|
||||
self.bigpackage_5 = 55.04
|
||||
self.bigpackage_7 = 57.405
|
||||
self.bigpackage_peak =0 # 42.25# 大包裹旺季附加费
|
||||
self.return_package = 1325 # 超大包裹(不可发)
|
||||
self.fuel_rate = 0.18 # 燃油费率
|
||||
class FedexGROUDLogistics_US(FedexLogistics):
|
||||
"""FEDEX-GROUD (1-35%-30%)"""
|
||||
company = "Fedex-GROUD"
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
price_path = parent_current_directory.joinpath("data")
|
||||
_price_files = price_path.joinpath("美国快递.xlsx")
|
||||
base_price = None
|
||||
def __new__(cls):
|
||||
if cls.base_price is None:
|
||||
cls.base_price = pandas.read_excel(cls._price_files,sheet_name='FEDEX')
|
||||
return super().__new__(cls)
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.volume_weight_ratio = 250 # lbs抛重系数
|
||||
self.residential = 5.95
|
||||
self.residential_peak =0 # 0.57 0.31 # 报价表没写,账单有
|
||||
self.oversize_2 = 6.02
|
||||
self.oversize_3 = 6.67
|
||||
self.oversize_5 = 7.31
|
||||
self.oversize_7 = 8.17
|
||||
self.overweight_2 =9.35
|
||||
self.overweight_3 = 10.21
|
||||
self.overweight_5 =10.86
|
||||
self.overweight_7 = 11.83
|
||||
self.overhanding_2 = 5.375# 额外操作费
|
||||
self.overhanding_3 = 6.235
|
||||
self.overhanding_5 = 6.5575
|
||||
self.overhanding_7 = 6.7725
|
||||
self.ahs_peak = 0 #1.3 # 操作费旺季附加费
|
||||
self.residential_das = 6.2 # 偏远费
|
||||
self.residential_extended = 8.3 # 超偏远费
|
||||
self.residential_remote = 15.5# 超超偏远费
|
||||
self.bigpackage_2 = 38.06 # 大包裹费
|
||||
self.bigpackage_3 = 41.76
|
||||
self.bigpackage_5 = 44.29
|
||||
self.bigpackage_7 = 47.73
|
||||
self.bigpackage_peak =0 # 42.25# 大包裹旺季附加费
|
||||
self.return_package = 1325 # 超大包裹(不可发)
|
||||
self.fuel_rate = 0.18 # 燃油费率
|
||||
|
||||
class GIGALogistics_US(TailLogistics):
|
||||
"""卡派:GIGA,美东美西不变"""
|
||||
country = "United States"
|
||||
country_code = "US"
|
||||
company = "大健-GIGA"
|
||||
logistics_type = LogisticsType.COURIER
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
data_path = parent_current_directory.joinpath("data")
|
||||
_price_files = data_path.joinpath("GIGA base_fee_20240607223514.xlsx")
|
||||
price_df = None
|
||||
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if cls.price_df is None:
|
||||
cls.price_df = pandas.read_excel(cls._price_files)
|
||||
return super().__new__(cls)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee:float
|
||||
self.warehouse_fee:float
|
||||
self.discount = 0.88
|
||||
|
||||
def calculate_fee(self,packages,postcode):
|
||||
"""计算运费,基础费用只跟邮编有关"""
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"warehouse_fee":0.00,
|
||||
"discount":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
if not re.match(r'\d{5}-\d{4}|\d{5}', postcode):
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
postcode = int(postcode[:5])
|
||||
base_result = self.price_df[self.price_df['Zip Code']==postcode]['Delivery Fee Rate']
|
||||
if base_result.empty:
|
||||
detail_amount['tail_amount'] =99999
|
||||
return detail_amount
|
||||
self.base_fee = base_result.iloc[0]
|
||||
self.warehouse_fee = self.price_df[self.price_df['Zip Code']==postcode]['Warehouse Handling Fee'].iloc[0]
|
||||
detail_amount['base'] = self.base_fee *self.discount
|
||||
detail_amount['warehouse_fee'] = self.warehouse_fee * self.discount
|
||||
detail_amount['discount'] = self.discount
|
||||
detail_amount['tail_amount'] = detail_amount['base'] + detail_amount['warehouse_fee']
|
||||
return detail_amount
|
||||
|
||||
class CEVALogistics_US(TailLogistics):
|
||||
"""卡派:CEVA"""
|
||||
country = "United States"
|
||||
country_code = "US"
|
||||
company = "大健-CEVA"
|
||||
logistics_type = LogisticsType.COURIER
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
data_path = parent_current_directory.joinpath("data")
|
||||
_price_files = data_path.joinpath("CEVA.xlsx")
|
||||
zone_df = None
|
||||
price_df = None
|
||||
grade_df = None
|
||||
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if cls.price_df is None or cls.zone_df is None:
|
||||
cls.price_df = pandas.read_excel(cls._price_files,sheet_name="ceva_base_rate")
|
||||
cls.zone_df = pandas.read_excel(cls._price_files,sheet_name="remote_zone")
|
||||
cls.grade_df = pandas.read_excel(cls._price_files,sheet_name="ceva_zone")
|
||||
return super().__new__(cls)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.surcharge_CA = 18.24 # 出发地和目的地有一个是CA 都会收
|
||||
self.metro_NY = 37.99 # 出发邮编和目的邮编前三位在100-104,110-114会收
|
||||
self.big_package = 30 # 单件包裹重>=250lbs,长>=75in
|
||||
self.oversize = [0,131.04,303,532] # 96,120,240,240以上
|
||||
|
||||
def calculate_fee(self,packages,postcode):
|
||||
"""
|
||||
计算运费,in,lbs,250
|
||||
from邮编固定,
|
||||
to邮编先根据remote_df获取State和remote_type,
|
||||
再根据zone_df分组,
|
||||
最后从price_df取对应价格
|
||||
只有big_package和oversize是以单包裹为单位,其他费用混合计算
|
||||
"""
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"remote":0.00,
|
||||
"surcharge_CA":0.00,
|
||||
"metro_NY":0.00,
|
||||
"oversize":0.00,
|
||||
"big_package":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
if not re.match(r'\d{5}-\d{4}|\d{5}', postcode):
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
postcode = int(postcode[:5])
|
||||
|
||||
# 获取分区和偏远
|
||||
result = self.zone_df[self.zone_df['PostalCode'] == postcode]
|
||||
if result.empty:
|
||||
detail_amount['tail_amount']=99999
|
||||
return detail_amount
|
||||
zone = result['State'].iloc[0]
|
||||
remote = result['remote_type'].iloc[0] # standard 和remote
|
||||
|
||||
# 假设美西出发,CA是美西,NJ是美东
|
||||
zone_west = self.grade_df[self.grade_df['To'] ==zone]['CA'].iloc[0]
|
||||
|
||||
# 最后查看价格表,磅重-grade
|
||||
total_weight = 0
|
||||
for package in packages:
|
||||
billing_weight = max(package.lbs_weight,package.volume_inch/250)
|
||||
total_weight +=billing_weight
|
||||
if package.lbs_weight >=250 or package.fst_inch >=75:
|
||||
detail_amount["big_package"] +=self.big_package
|
||||
if 96<=package.fst_inch <120:
|
||||
detail_amount["oversize"] +=self.oversize[1]
|
||||
elif 120<=package.fst_inch <240:
|
||||
detail_amount["oversize"] +=self.oversize[2]
|
||||
elif package.fst_inch >=240:
|
||||
detail_amount["oversize"] +=self.oversize[3]
|
||||
|
||||
billed_weight = round(min(max(101,total_weight),1000),0)
|
||||
extrat = 0
|
||||
price_result = self.price_df[self.price_df['CEVA']==billed_weight]
|
||||
if total_weight >1000:
|
||||
per_result = self.price_df[self.price_df['CEVA']=="1000+"][zone_west].iloc[0]
|
||||
extrat = per_result * (total_weight -1000)
|
||||
detail_amount["base"] = price_result[zone_west].iloc[0] + extrat
|
||||
detail_amount['remote'] = price_result['Remote area surcharge'].iloc[0] if remote == 'remote' else 0
|
||||
detail_amount["surcharge_CA"] = self.surcharge_CA
|
||||
detail_amount["metro_NY"] = self.metro_NY if 100<=postcode//100<=104 or 110<=postcode//100<=114 else 0
|
||||
|
||||
for key in detail_amount:
|
||||
if key!= 'tail_amount':
|
||||
detail_amount['tail_amount'] += detail_amount[key]
|
||||
return detail_amount
|
||||
|
||||
class MetroLogistics_US(TailLogistics):
|
||||
"""卡派:Metro"""
|
||||
country = "United States"
|
||||
country_code = "US"
|
||||
company = "Metro-SAIR"
|
||||
logistics_type = LogisticsType.COURIER
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
data_path = parent_current_directory.joinpath("data")
|
||||
_price_files = data_path.joinpath("Metro.xlsx")
|
||||
cuft_25 = None
|
||||
cuft_35 = None
|
||||
over_35_per_cuft = None
|
||||
over_35_min = None
|
||||
zone_table = None
|
||||
remote_table = None
|
||||
@staticmethod
|
||||
def refresh():
|
||||
"""清空已加载的数据,以便下次重新加载"""
|
||||
MetroLogistics_US.cuft_25 = None
|
||||
MetroLogistics_US.cuft_35 = None
|
||||
MetroLogistics_US.over_35_per_cuft = None
|
||||
MetroLogistics_US.over_35_min = None
|
||||
MetroLogistics_US.zone_table = None
|
||||
MetroLogistics_US.remote_table = None
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if cls.cuft_25 is None or cls.cuft_35 is None:
|
||||
cls.cuft_25 = pandas.read_excel(cls._price_files,sheet_name="cuft_25")
|
||||
cls.cuft_35 = pandas.read_excel(cls._price_files,sheet_name="cuft_35")
|
||||
cls.over_35_per_cuft = pandas.read_excel(cls._price_files,sheet_name="over35_per_cuft")
|
||||
cls.over_35_min = pandas.read_excel(cls._price_files,sheet_name="over35_min")
|
||||
cls.zone_table = pandas.read_excel(cls._price_files,sheet_name="zone_table")
|
||||
cls.remote_table = pandas.read_excel(cls._price_files,sheet_name="remote_table")
|
||||
return super().__new__(cls)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.fuel_rate = 0.4 # 基础费用必*1.4,附加费不用
|
||||
self.discount = 0.88 # 折扣,所有费用都吃
|
||||
|
||||
def calculate_fee(self,packages,postcode):
|
||||
"""
|
||||
计算运费,in,lbs,250
|
||||
from邮编固定,
|
||||
to邮编先根据remote_df获取State和remote_type,
|
||||
再根据zone_df分组,
|
||||
最后从price_df取对应价格
|
||||
只有big_package和oversize是以单包裹为单位,其他费用混合计算
|
||||
"""
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"oversize":0.00,
|
||||
"remote":0.00,
|
||||
"tail_amount":0.00
|
||||
}
|
||||
if not re.match(r'\d{5}-\d{4}|\d{5}', postcode):
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
postcode = int(postcode[:5])
|
||||
# 出发地分区为 Zone 2 L
|
||||
# 取目的地分区
|
||||
zone_result = self.zone_table[self.zone_table['Zip Code'] ==postcode]['New Zone Name']
|
||||
if zone_result.empty:
|
||||
detail_amount["tail_amount"] = 99999
|
||||
return detail_amount
|
||||
zone = zone_result.iloc[0]
|
||||
|
||||
|
||||
total_cuft = 0
|
||||
max_weight = 0
|
||||
max_length = 0
|
||||
for package in packages:
|
||||
total_cuft += round(package.volume_inch/1728,2)
|
||||
# cm则为长*宽*高/28316.846592,单位是立方英尺
|
||||
max_weight = max(max_weight,package.lbs_weight)
|
||||
max_length = max(max_length,package.fst_inch)
|
||||
|
||||
# base费用 25立方英尺 ,美西是Zone 2 L 美东是Zone 17 L
|
||||
if total_cuft < 25:
|
||||
detail_amount["base"] = self.cuft_25[self.cuft_25['Origins']==zone]["Zone 2 L"].iloc[0]
|
||||
elif 25<=total_cuft <35:
|
||||
detail_amount["base"] = self.cuft_35[self.cuft_35['Origins']==zone]["Zone 2 L"].iloc[0]
|
||||
else:
|
||||
per_cuft = self.over_35_per_cuft[self.over_35_per_cuft['Origins']==zone]["Zone 2 L"].iloc[0]
|
||||
base_min = self.over_35_min[self.over_35_min['Origins']==zone]["Zone 2 L"].iloc[0]
|
||||
detail_amount["base"] = max(per_cuft*total_cuft,base_min)
|
||||
|
||||
# 超尺寸,包括超重或超长
|
||||
if max_weight >400 or max_length>125: # 不可派
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
if 300<max_weight <=400:
|
||||
detail_amount["oversize"] += 110
|
||||
if 100<max_length <=125:
|
||||
detail_amount["oversize"] += 110
|
||||
|
||||
# 偏远费
|
||||
remote_result = self.remote_table[self.remote_table['ZIPCode'] == postcode]['Area Type']
|
||||
if not remote_result.empty:
|
||||
value = remote_result.iloc[0]
|
||||
if value.startswith("Beyond"):
|
||||
detail_amount["remote"] = 110
|
||||
|
||||
detail_amount["base"] = detail_amount["base"] * (1+self.fuel_rate)
|
||||
for key in detail_amount:
|
||||
if key!= 'tail_amount':
|
||||
detail_amount[key] *= self.discount
|
||||
detail_amount['tail_amount'] += detail_amount[key]
|
||||
return detail_amount
|
||||
|
||||
class XmilesLogistics_US(TailLogistics):
|
||||
"""一票多件快递:XMILES,
|
||||
包含美东和美西,
|
||||
美东只在美西基础上+0.5*抛重6000的转运费
|
||||
"""
|
||||
country = "United States"
|
||||
country_code = "US"
|
||||
company = "XMILES-SAIR"
|
||||
logistics_type = LogisticsType.COURIER
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
data_path = parent_current_directory.joinpath("data")
|
||||
_price_files = data_path.joinpath("XMILES.xlsx")
|
||||
postcode_table = None
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if cls.postcode_table is None:
|
||||
cls.postcode_table = pandas.read_excel(cls._price_files,sheet_name="postcode_table")
|
||||
return super().__new__(cls)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def calculate_fee(self,packages,postcode):
|
||||
"""
|
||||
实重计费
|
||||
AH/OS/OM,取最大类型费用做第一件费用,第二件开始全部半价
|
||||
重量<=500lbs(226kg)
|
||||
最长边<=144inch(365cm)
|
||||
围长<=225inch(571cm)
|
||||
美东美西费用一样,可派送邮编不同
|
||||
"""
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"transfer":0.00, # 美西到美东的中转费
|
||||
"tail_amount":0.00
|
||||
}
|
||||
if not re.match(r'\d{5}-\d{4}|\d{5}', postcode):
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
postcode = int(postcode[:5])
|
||||
|
||||
base_fee_list = set() # 将每个包裹费用都记录在列表中
|
||||
volume_weight = 0 # 计算抛重,用于计算美西转运美东价格
|
||||
for package in packages:
|
||||
volume_weight +=package.get_volume_weight(6000)
|
||||
if package.lbs_weight >500 or package.fst_inch >144 or package.girth_inch >225:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
if package.lbs_weight <=90 and package.fst_inch <=96 and package.girth_inch <=130:
|
||||
base_fee_list.add(20)
|
||||
elif package.lbs_weight <=150 and package.fst_inch <=96 and package.girth_inch <=130:
|
||||
base_fee_list.add(38)
|
||||
elif package.lbs_weight <=150 and package.fst_inch <=108 and package.girth_inch <=165:
|
||||
base_fee_list.add(50)
|
||||
elif package.lbs_weight <=200 and package.fst_inch <=144 and package.girth_inch <=225:
|
||||
base_fee_list.add(75)
|
||||
else:
|
||||
base_fee_list.add(75+0.55*math.ceil(package.lbs_weight))
|
||||
# 取所有费用,最大的费用取全部费用,其他取半价然后求和
|
||||
base_fee_list = sorted(base_fee_list,reverse=True)
|
||||
# 计算base_fee_list最大值+其他费用的一半
|
||||
if len(base_fee_list) == 0:
|
||||
return detail_amount
|
||||
elif len(base_fee_list) == 1:
|
||||
detail_amount["base"] = base_fee_list[0]
|
||||
else:
|
||||
detail_amount["base"] = base_fee_list[0] + sum(base_fee_list[1:])/2
|
||||
|
||||
|
||||
# 判断邮编是美东还是美西
|
||||
zone = self.postcode_table[self.postcode_table['POSTCODE'] == postcode]['地区']
|
||||
if zone.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
zone = zone.iloc[0]
|
||||
if zone =="NJ":
|
||||
detail_amount["transfer"] = 0.5*volume_weight
|
||||
|
||||
for key in detail_amount:
|
||||
if key!= 'tail_amount':
|
||||
detail_amount['tail_amount'] += detail_amount[key]
|
||||
return detail_amount
|
||||
|
||||
class AMWestLogistics_US(TailLogistics):
|
||||
"""卡派:AM,
|
||||
美西派送,
|
||||
"""
|
||||
country = "United States"
|
||||
country_code = "US"
|
||||
company = "AM-美西"
|
||||
logistics_type = LogisticsType.COURIER
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
data_path = parent_current_directory.joinpath("data")
|
||||
_price_files = data_path.joinpath("美国卡派-AM.xlsx")
|
||||
postcode_table = None
|
||||
price_df = None
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if cls.postcode_table is None or cls.price_df is None:
|
||||
cls.price_df = pandas.read_excel(cls._price_files,sheet_name="price")
|
||||
cls.postcode_table = pandas.read_excel(cls._price_files,sheet_name="postcode_table")
|
||||
return super().__new__(cls)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.PUZone = "CA" # 派送起点
|
||||
|
||||
def calculate_fee(self,packages,postcode):
|
||||
"""
|
||||
实重计费
|
||||
lbs
|
||||
重量<=1000lbs(453kg)
|
||||
美西PUZone:CA, DLZone根据目的地邮编得
|
||||
美东PUZone:NY, DLZone根据目的地邮编得
|
||||
"""
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"surcharge":0.00, # 附加费用
|
||||
"transfer":0.00, # 美西到美东的中转费
|
||||
"tail_amount":0.00
|
||||
}
|
||||
if not re.match(r'\d{5}-\d{4}|\d{5}', postcode):
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
postcode = int(postcode[:5])
|
||||
# postcode = str(postcode).zfill(5)
|
||||
# 先拿到目的地邮编对应的DLZone
|
||||
zone_match = self.postcode_table[self.postcode_table['Zip Code'] == postcode]
|
||||
if zone_match.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
else:
|
||||
DLZone = zone_match['Zone'].iloc[0]
|
||||
|
||||
volume_weight = 0 # 计算抛重,用于计算美西转运美东价格
|
||||
total_lbs_weight = 0 # 计算总磅重
|
||||
for package in packages:
|
||||
volume_weight +=package.get_volume_weight(6000)
|
||||
total_lbs_weight += package.lbs_weight
|
||||
if package.lbs_weight >1000:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
|
||||
zone = self.PUZone+DLZone
|
||||
row = self.price_df[(self.price_df['Zone Combo']==zone)
|
||||
&(self.price_df['Minimum']<=total_lbs_weight)
|
||||
&(self.price_df['Maximum']>=total_lbs_weight)]
|
||||
if row.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
detail_amount["base"] = row['w/o SC'].iloc[0]
|
||||
detail_amount["surcharge"] = row['Surcharge'].iloc[0]
|
||||
if detail_amount['base']==0:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
for key in detail_amount:
|
||||
if key!= 'tail_amount':
|
||||
detail_amount['tail_amount'] += detail_amount[key]
|
||||
return detail_amount
|
||||
|
||||
class AMEastLogistics_US(TailLogistics):
|
||||
"""卡派:AM,
|
||||
美东派送,
|
||||
美东在费用上额外+0.5*抛重6000的转运费
|
||||
"""
|
||||
country = "United States"
|
||||
country_code = "US"
|
||||
company = "AM-美东"
|
||||
logistics_type = LogisticsType.COURIER
|
||||
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
data_path = parent_current_directory.joinpath("data")
|
||||
_price_files = data_path.joinpath("美国卡派-AM.xlsx")
|
||||
postcode_table = None
|
||||
price_df = None
|
||||
def __new__(cls):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if cls.postcode_table is None or cls.price_df is None:
|
||||
cls.price_df = pandas.read_excel(cls._price_files,sheet_name="price")
|
||||
cls.postcode_table = pandas.read_excel(cls._price_files,sheet_name="postcode_table")
|
||||
return super().__new__(cls)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.PUZone = "NY"
|
||||
|
||||
def calculate_fee(self,packages,postcode):
|
||||
"""
|
||||
实重计费
|
||||
lbs
|
||||
重量<=1000lbs(453kg)
|
||||
美西PUZone:CA, DLZone根据目的地邮编得
|
||||
美东PUZone:NY, DLZone根据目的地邮编得
|
||||
"""
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"surcharge":0.00, # 附加费用
|
||||
"transfer":0.00, # 美西到美东的中转费
|
||||
"tail_amount":0.00
|
||||
}
|
||||
if not re.match(r'\d{5}-\d{4}|\d{5}', postcode):
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
postcode = int(postcode[:5])
|
||||
# postcode = str(postcode).zfill(5)
|
||||
# 先拿到目的地邮编对应的DLZone
|
||||
zone_match = self.postcode_table[self.postcode_table['Zip Code'] == postcode]
|
||||
if zone_match.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
else:
|
||||
DLZone = zone_match['Zone'].iloc[0]
|
||||
volume_weight = 0 # 计算抛重,用于计算美西转运美东价格
|
||||
total_lbs_weight = 0 # 计算总磅重
|
||||
for package in packages:
|
||||
volume_weight +=package.get_volume_weight(6000)
|
||||
total_lbs_weight += package.lbs_weight
|
||||
if package.lbs_weight >1000:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
|
||||
zone = self.PUZone+DLZone
|
||||
row = self.price_df[(self.price_df['Zone Combo']==zone)
|
||||
&(self.price_df['Minimum']<=total_lbs_weight)
|
||||
&(self.price_df['Maximum']>=total_lbs_weight)]
|
||||
if row.empty:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
detail_amount["base"] = row['w/o SC'].iloc[0]
|
||||
detail_amount["surcharge"] = row['Surcharge'].iloc[0]
|
||||
detail_amount["transfer"] = 0.5*volume_weight
|
||||
if detail_amount['base']==0:
|
||||
detail_amount['tail_amount'] = 99999
|
||||
return detail_amount
|
||||
for key in detail_amount:
|
||||
if key!= 'tail_amount':
|
||||
detail_amount['tail_amount'] += detail_amount[key]
|
||||
return detail_amount
|
||||
if __name__ == '__main__':
|
||||
FedexPPLogistics_US.close_logistics()
|
||||
print(1)
|
||||
Binary file not shown.
|
|
@ -0,0 +1,139 @@
|
|||
# 物流模块介绍
|
||||
## [数据模块](data)
|
||||
> 数据存放地,部分物流的价格表和部分国家的邮编
|
||||
- [澳洲ALL,TOLL,POST价格表](data/澳洲三大渠道.xlsx)
|
||||
- [美国偏远邮编](data/contiguous_us.txt)
|
||||
- [美国超偏远邮编](data/configuous_us_extended.txt)
|
||||
- [美国超超偏远邮编(夏威夷,阿拉斯加等)](data/contiguous_us_remote.txt)
|
||||
## [物流实现类](logisticsClass)
|
||||
> 美国,英国,澳洲,欧洲部分国家(德国,法国)
|
||||
- [头程/尾端基类](logisticsClass/logisticsBaseClass.py)
|
||||
- [美国头程](logisticsClass/logisticsHead_US.py)
|
||||
- [美国尾端](logisticsClass/logisticsTail_US.py)
|
||||
## [工具模块](utils)
|
||||
- [数据库连接](utils/gtools.py)
|
||||
- [包裹类](utils/Package.py)
|
||||
- [国家类/操作类](utils/countryOperator.py)
|
||||
- [账单工厂类](utils/logisticsBill.py)
|
||||
|
||||
# 新增物流渠道流程
|
||||
## 1. 新增头程,继承自[HeadLogistics](logisticsClass/logisticsBaseClass.py)
|
||||
|
||||
### 1.1 类属性(*必填)
|
||||
|
||||
*company: str # 物流公司名称
|
||||
*country_code: list[str] # 国家代码,若只有一个国家,用str即可
|
||||
*country: list[str] # 国家全名,若只有一个国家,用str即可
|
||||
|
||||
logistics_type = LogisticsType.OCEAN # 运输方式,空运|海运,默认海运
|
||||
currency = 'CNY' # 货币类型,默认人民币
|
||||
port: PortType = PortType.DEFAULT # 到达港口,东南西北,默认港口
|
||||
active: bool = True # 物流状态,默认启用
|
||||
|
||||
### 1.2 实例属性(*必填)
|
||||
|
||||
*head_ratio: int # 它*6000体积重为头程费用
|
||||
|
||||
### 1.3 样例代码
|
||||
|
||||
class AirLAXLogistics_US(HeadLogistics):
|
||||
"""美国空运LAX"""
|
||||
company = "空LAX"
|
||||
country_code = "US"
|
||||
country = "United States"
|
||||
logistics_type = LogisticsType.AIR
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.head_ratio = 40
|
||||
|
||||
## 2.新增尾端,继承自[TailLogistics](logisticsClass/logisticsBaseClass.py)
|
||||
### 2.1 类属性(*必填)
|
||||
|
||||
*company: str # 物流公司名称
|
||||
*country_code: str # 国家代码
|
||||
*country: str # 国家全名
|
||||
|
||||
logistics_type = LogisticsType.EXPRESS # 运输方式,快递|卡派,默认快递
|
||||
currency:str = 'USD' # 货币类型,默认美元
|
||||
port: PortType = PortType.DEFAULT # 到达港口,东南西北,默认港口
|
||||
active: bool = True # 物流状态,默认启用
|
||||
|
||||
### 2.2 实例属性(*必填)
|
||||
|
||||
由各渠道情况自行定义实际物流实现类属性,如:
|
||||
self.oversize_fee: float # 超尺寸费
|
||||
self.remote_fee: float # 偏远费
|
||||
self.fuel_rate: float # 燃油费率
|
||||
|
||||
### 2.3 样例代码
|
||||
|
||||
class Logistics_UK(TailLogistics):
|
||||
company = '智谷'
|
||||
country_code = 'UK'
|
||||
country = 'United Kingdom'
|
||||
currency = 'GBP'
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.base_fee = 3.7
|
||||
self.oversize = 26
|
||||
self.fuel_rate = 0.2 # BT开头的有20%燃油费
|
||||
self.remote_fee = 35
|
||||
|
||||
# 必须实现的接口方法
|
||||
def calculate_fee(self, packages, postcode):
|
||||
detail_amount = {
|
||||
"base":0.00,
|
||||
"oversize":0.00,
|
||||
"remote":0.00,
|
||||
"fuel":0.00,
|
||||
"total_amount":0.00
|
||||
}
|
||||
***
|
||||
***
|
||||
return detail_amount
|
||||
|
||||
------
|
||||
# 账单输出流程
|
||||
1. 实例化[国家类/操作类](utils/countryOperator.py),传入国家或国家代码
|
||||
|
||||
opCountry = OperateCountry('UK')
|
||||
2. 实例化[账单工厂](utils/logisticsBill.py),初始为空
|
||||
|
||||
billFactory = BillFactory()
|
||||
# 该工厂可以对所有账单进行统一管理,包括:
|
||||
billFactory.create_bill(id,opCountry,packages,postcode,company_name,head_type,beizhu) # 创建账单
|
||||
billFactory.add_bill(bill) # 添加账单
|
||||
billFactory.get_bills() # 输出全部账单
|
||||
billFactory.get_bill(id) # 输出指定账单,id为账单ID
|
||||
billFactory.bills_to_df() # 输出全部账单到DataFrame格式
|
||||
3. 实例化[账单类](utils/logisticsBill.py)
|
||||
|
||||
bill = Billing(id,operator,packages,postcode,**kwargs)
|
||||
# 账单传参属性(*必填)
|
||||
*self.id:str = id
|
||||
*self.operator:OperateCountry = opCountry
|
||||
*self.packages:Package_group = packages
|
||||
*self.postcode:str = postcode
|
||||
|
||||
self.company_name = kwargs.get('company_name', None)
|
||||
self.head_type = kwargs.get('head_type', 1)
|
||||
self.beizhu = kwargs.get('beizhu', None)
|
||||
|
||||
4. 账单传参属性(*必填)
|
||||
|
||||
*opCountry = OperateCountry("US") # 实例化国家类/操作类
|
||||
填写范围:US~UK~AU~JP~DE~CA~FR~SP~IT
|
||||
|
||||
*bill = Billing(id,operator,packages,postcode,**kwargs)
|
||||
*id = "xxx" # str, 账单ID
|
||||
*operator = opCountry # 国家类/操作类
|
||||
*packages = Package_group() # Package_group, 包裹组
|
||||
*postcode = "xxx" # str, 邮编
|
||||
|
||||
company_name = "xxx" # str, 物流公司名称(不填则默认获取最小账单费用)
|
||||
填写范围:operator.companies
|
||||
head_type = 1 # int, 头程类型,1-海运,0-空运
|
||||
beizhu = "xxx" # str, 备注信息
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,425 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from utils.gtools import MySQLconnect
|
||||
|
||||
|
||||
# 根据SPU判断空运或海运,1为海运
|
||||
def spu_head_type(base_df: pd.DataFrame):
|
||||
df = base_df.copy(deep=True)
|
||||
spu_type = {} # 字典的键为SPU,值为一个元组(start, end),表示该SPU在原始DataFrame中的索引范围
|
||||
last_spu = None
|
||||
spu_start_index = None
|
||||
|
||||
for i, row in df.iterrows():
|
||||
current_spu = row['SPU']
|
||||
if current_spu != last_spu:
|
||||
spu_start_index = i
|
||||
last_spu = current_spu
|
||||
else:
|
||||
# 如果是同一个SPU的连续行,则更新该SPU组的结束索引
|
||||
if current_spu in spu_type:
|
||||
spu_type[current_spu][1] = i
|
||||
else:
|
||||
# 如果SPU组的起始索引存在,则初始化一个新的SPU组,并更新结束索引
|
||||
spu_type[current_spu] = [spu_start_index, i]
|
||||
if i == len(df) - 1 or current_spu != df.iloc[i + 1]['SPU']:
|
||||
if current_spu not in spu_type:
|
||||
spu_type[current_spu] = [i, i]
|
||||
# 单独处理最后一行
|
||||
if last_spu and last_spu not in spu_type:
|
||||
spu_type[last_spu] = [spu_start_index, spu_start_index]
|
||||
|
||||
spu_flag_values = {}
|
||||
for spu, index_range in spu_type.items():
|
||||
# 获取每个 SPU 组的起始索引和结束索引
|
||||
start_index = index_range[0]
|
||||
end_index = index_range[1]
|
||||
sub_df = df.loc[start_index:end_index]
|
||||
flag = 0
|
||||
for index, row in sub_df.iterrows():
|
||||
item_list = [
|
||||
{key: float(value) for key, value in package.items()}
|
||||
for package in row['packages'].values()
|
||||
]
|
||||
for item in item_list:
|
||||
bag_list = [item['长'], item['宽'], item['高']]
|
||||
sizes = sorted(bag_list, reverse=True)
|
||||
volume_weight = (sizes[0] * sizes[1] * sizes[2]) / 6000
|
||||
if sizes[0] > 50 or sizes[1] > 40 or sizes[2] > 30 or item['重量'] > 2718 or volume_weight > 4.5:
|
||||
flag = 1
|
||||
break
|
||||
if flag:
|
||||
break
|
||||
spu_flag_values[spu] = flag
|
||||
|
||||
df.loc[start_index:end_index, 'flag'] = flag
|
||||
return df
|
||||
|
||||
|
||||
class SellPriceBase:
|
||||
parent_current_directory = Path(__file__).parent.parent
|
||||
price_path = parent_current_directory.joinpath("data")
|
||||
_price_files = price_path.joinpath("售价尾端价格.xlsx")
|
||||
df_2025 = None
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""实现单例模式,只加载一次文件"""
|
||||
if cls.df_2025 is None:
|
||||
cls.df_2025 = pd.read_excel(cls._price_files,sheet_name="Sheet1")
|
||||
return super().__new__(cls)
|
||||
def __init__(self, packages, purchase_price, shipping_type, ocean_first_cny, ocean_first_usd, air_first_usd,
|
||||
air_cny_type, air_first_fix, exchange_rate, profit_rate, air_rate,tax_rate):
|
||||
self.packages = packages # 单sku包裹数据
|
||||
self.purchase_price = purchase_price # 采购价/成本价
|
||||
self.ocean_first_cny = ocean_first_cny # 海运头程单价CNY
|
||||
self.ocean_first_usd = ocean_first_usd # 海运头程单价USD
|
||||
self.air_first_usd = air_first_usd # 空运头程单价USD
|
||||
self.air_cny_type = air_cny_type # 空运头程货型单价CNY
|
||||
self.air_first_fix = air_first_fix # 空运头程固定单价CNY
|
||||
self.exchange_rate = exchange_rate # 汇率
|
||||
self.profit_rate = profit_rate # 利润系数
|
||||
self.air_rate = air_rate # 空运分配占比
|
||||
self.shipping_type = shipping_type # 1为海运0为空运,由spu定好的
|
||||
self.tax_rate = tax_rate # 税率
|
||||
|
||||
# 获取对应价格表
|
||||
def get_fee(self, head_type):
|
||||
mat = MySQLconnect('mat')
|
||||
engine = mat.engine()
|
||||
|
||||
try:
|
||||
if head_type == 0:
|
||||
df = pd.read_sql('SELECT * FROM `usps_0814`', engine)
|
||||
elif head_type == 1:
|
||||
df = pd.read_sql('SELECT * FROM `uandf_0814`', engine)
|
||||
elif head_type == 2:
|
||||
df = pd.read_sql('SELECT * FROM `fedex_2504`', engine)
|
||||
else:
|
||||
df = pd.DataFrame([99999], columns=['错误'])
|
||||
except Exception as e:
|
||||
print(f"发生错误: {e}")
|
||||
df = pd.DataFrame()
|
||||
finally:
|
||||
engine.dispose()
|
||||
return df
|
||||
|
||||
# 计算快递费用
|
||||
def cal_express_fee(self):
|
||||
head_type = 0 # 默认为USPS
|
||||
index = 0 # 标记卡派和不可售
|
||||
for package in self.packages:
|
||||
volume_weight = package.get_volume_weight(6000)
|
||||
if package.weight > 420:
|
||||
head_type = 1 # UandF
|
||||
if package.fst_size > 50 or package.sed_size > 40 or package.trd_size > 30 or package.weight > 2718 or volume_weight > 3624:
|
||||
head_type = 2 # FEDEX
|
||||
if package.fst_size > 264 or package.weight > 67000 or volume_weight > 95000 or package.girth > 391:
|
||||
head_type = 3 # 卡派
|
||||
index = 1
|
||||
if package.fst_size > 310:
|
||||
head_type = 4 # 不可售
|
||||
index = 1
|
||||
|
||||
lbs_weight_sum = 0
|
||||
express_base_fee1 = 0
|
||||
sku_express_fee = 0 # 单产品快递基础费
|
||||
for package in self.packages:
|
||||
oz_weight = math.ceil(package.weight / 28)
|
||||
lbs_weight = math.ceil(max(package.get_volume_weight(8500) / 0.453, package.weight / 453))
|
||||
lbs_weight_sum += lbs_weight
|
||||
express_base_fee = 0
|
||||
oversize_charge = 0
|
||||
ahs_weight = 0
|
||||
ahs_dimension = 0
|
||||
if head_type == 0:
|
||||
try:
|
||||
df = self.get_fee(head_type)
|
||||
express_base_fee = df[df['oz'] == oz_weight]['最终费用'].iloc[0] / self.profit_rate
|
||||
except:
|
||||
head_type = 1
|
||||
# USPSA2/FEDEXA1
|
||||
if head_type == 1:
|
||||
try:
|
||||
df = self.get_fee(head_type)
|
||||
express_base_fee = df[df['lbs'] == lbs_weight]['加权价格'].iloc[0] / self.profit_rate
|
||||
except:
|
||||
head_type = 2
|
||||
# FEDEX
|
||||
if head_type == 2:
|
||||
try:
|
||||
df = self.get_fee(head_type)
|
||||
express_base_fee = df[df['lbs'] == lbs_weight]['售价尾端价格'].iloc[0]
|
||||
except:
|
||||
express_base_fee = 99999
|
||||
head_type = 3
|
||||
|
||||
# 额外费用计算
|
||||
if package.fst_size > 238 or package.girth > 315:
|
||||
# 如果包裹符合大包裹
|
||||
oversize_charge = 109.4
|
||||
elif package.weight > 21000:
|
||||
# 注意是个负数
|
||||
ahs_weight = -5.6
|
||||
elif package.weight > 116 or package.girth > 251:
|
||||
# 虽然是0,但是保留这一条逻辑
|
||||
ahs_dimension = 0
|
||||
|
||||
express_price = (express_base_fee + oversize_charge + ahs_weight + ahs_dimension)
|
||||
express_base_fee1 += express_base_fee
|
||||
sku_express_fee += express_price
|
||||
# 卡派
|
||||
if head_type in (3, 4):
|
||||
continue
|
||||
|
||||
# 计算合并的卡派价格
|
||||
ltl_over_weightfee = 0
|
||||
sku_total_cubic_feet = 0 # 库存总体立方英尺
|
||||
ltlover_size = 0
|
||||
for package in self.packages:
|
||||
cubic_feet = math.ceil(package.volume / 1000000 * 35.3147) # 1立方米 = 35.3147立方英尺
|
||||
sku_total_cubic_feet += cubic_feet
|
||||
# 额外费用
|
||||
if package.fst_size > 250:
|
||||
ltlover_size = round(110 / self.profit_rate - 118, 1) # 订单物流放118,其他在售价里
|
||||
if 111000 <= package.weight < 130000:
|
||||
ltl_over_weightfee = max(ltl_over_weightfee, round(70 / self.profit_rate - 78, 1)) # 订单物流放78,其他在售价里
|
||||
if 130000 <= package.weight < 157000:
|
||||
ltl_over_weightfee = max(ltl_over_weightfee, round(110 / self.profit_rate - 118, 1)) # 订单物流放118,其他在售价里
|
||||
# 基础费用
|
||||
if sku_total_cubic_feet < 25:
|
||||
ltl_base_fee1 = round(163 / self.profit_rate / 2, 2)
|
||||
elif sku_total_cubic_feet < 35:
|
||||
ltl_base_fee1 = round(180 / self.profit_rate / 2, 2)
|
||||
else:
|
||||
# 大于一个立方的(35立方英尺) 按照mei立方英尺*5美金
|
||||
# 最低为190美金
|
||||
ltl_base_fee1 = round(max(190, 5 * sku_total_cubic_feet) / self.profit_rate / 2, 2)
|
||||
ltl_base_fee = ltl_base_fee1 * 1.025 # 2.5%平摊偏远附加费成本
|
||||
# 最后卡派费用等于基础费用+ 超长+ 超重费
|
||||
ltl_fee = ltl_base_fee + ltlover_size + ltl_over_weightfee
|
||||
|
||||
# 最后根据卡派费用和快递费用,取小值
|
||||
if index == 0: # 该sku没有卡派和不可售包裹
|
||||
sku_total_express_fee = min(ltl_fee, sku_express_fee)
|
||||
else:
|
||||
sku_total_express_fee = ltl_fee
|
||||
return sku_total_express_fee
|
||||
|
||||
def cal_sell_price(self):
|
||||
sell_price = 0
|
||||
express = self.cal_express_fee()
|
||||
if self.shipping_type == 0: # 空运
|
||||
for package in self.packages:
|
||||
volume_weight = package.get_volume_weight(6000)
|
||||
type_weight = min(337, max(37, package.density)) # 货型 = 密度?
|
||||
air_first_cny = (self.air_cny_type * type_weight + self.air_first_fix)
|
||||
air_first_cny_total = air_first_cny * volume_weight
|
||||
air_first_usd_total = self.air_first_usd * volume_weight
|
||||
sell_price += (self.purchase_price / self.exchange_rate / self.profit_rate +
|
||||
((air_first_cny_total / self.exchange_rate + air_first_usd_total) /
|
||||
self.profit_rate + express) * self.air_rate)
|
||||
|
||||
else: # 海运 or 不可售
|
||||
volume_weight = sum([package.get_volume_weight(6000) for package in self.packages])
|
||||
ocean_first_cny_total = self.ocean_first_cny * volume_weight
|
||||
ocean_first_usd_total = self.ocean_first_usd * volume_weight
|
||||
sell_price = ((ocean_first_cny_total + self.purchase_price) /
|
||||
self.exchange_rate + ocean_first_usd_total) / self.profit_rate + express
|
||||
return sell_price
|
||||
|
||||
|
||||
# 计算快递费用
|
||||
def cal_express_fee_2025(self):
|
||||
head_type = 0 # 默认为USPS
|
||||
index = 0 # 标记卡派和不可售
|
||||
for package in self.packages:
|
||||
volume_weight = package.get_volume_weight(6000)
|
||||
if package.weight > 420:
|
||||
head_type = 1 # UandF
|
||||
if package.fst_size > 50 or package.sed_size > 40 or package.trd_size > 30 or package.weight > 2718 or volume_weight > 3624:
|
||||
head_type = 2 # FEDEX
|
||||
if package.fst_size > 264 or package.weight > 67000 or volume_weight > 95000 or package.girth > 391:
|
||||
head_type = 3 # 卡派
|
||||
index = 1
|
||||
if package.fst_size > 310:
|
||||
head_type = 4 # 不可售
|
||||
index = 1
|
||||
|
||||
lbs_weight_sum = 0
|
||||
express_base_fee1 = 0
|
||||
sku_express_fee = 0 # 单产品快递基础费
|
||||
for package in self.packages:
|
||||
oz_weight = math.ceil(package.weight / 28)
|
||||
lbs_weight = math.ceil(max(package.get_volume_weight(8500) / 0.453, package.weight / 453))
|
||||
lbs_weight_sum += lbs_weight
|
||||
express_base_fee = 0
|
||||
oversize_charge = 0
|
||||
ahs_weight = 0
|
||||
ahs_dimension = 0
|
||||
# 本来是从数据库拿数据,我这里直接把数据放这里
|
||||
if head_type == 0:
|
||||
try:
|
||||
express_base_fee = self.df_2025[self.df_2025['oz'] == oz_weight]['最终费用'].iloc[0] / self.profit_rate
|
||||
except:
|
||||
head_type = 1
|
||||
# USPSA2/FEDEXA1
|
||||
if head_type == 1:
|
||||
try:
|
||||
express_base_fee = self.df_2025[self.df_2025['lbs小'] == lbs_weight]['加权价格'].iloc[0] / self.profit_rate
|
||||
except:
|
||||
head_type = 2
|
||||
# FEDEX
|
||||
if head_type == 2:
|
||||
try:
|
||||
express_base_fee = self.df_2025[self.df_2025['lbs大'] == lbs_weight]['售价尾端价格'].iloc[0]
|
||||
except:
|
||||
express_base_fee = 99999
|
||||
head_type = 3
|
||||
|
||||
# 额外费用计算
|
||||
if package.fst_size > 238 or package.girth > 315:
|
||||
# 如果包裹符合大包裹
|
||||
oversize_charge = 109.4
|
||||
elif package.weight > 21000:
|
||||
# 注意是个负数
|
||||
ahs_weight = 6.1
|
||||
elif package.length > 116 or package.girth > 251:
|
||||
# 虽然是0,但是保留这一条逻辑
|
||||
ahs_dimension = 7.4
|
||||
|
||||
express_price = (express_base_fee + oversize_charge + ahs_weight + ahs_dimension)
|
||||
express_base_fee1 += express_base_fee
|
||||
sku_express_fee += express_price
|
||||
# 卡派
|
||||
if head_type in (3, 4):
|
||||
continue
|
||||
|
||||
# 计算合并的卡派价格
|
||||
ltl_over_weightfee = 0
|
||||
sku_total_cubic_feet = 0 # 库存总体立方英尺
|
||||
ltlover_size = 0
|
||||
for package in self.packages:
|
||||
cubic_feet = math.ceil(package.volume / 1000000 * 35.3147) # 1立方米 = 35.3147立方英尺
|
||||
sku_total_cubic_feet += cubic_feet
|
||||
# 额外费用
|
||||
if package.fst_size > 250:
|
||||
ltlover_size = round(110 / self.profit_rate - 118, 1) # 订单物流放118,其他在售价里
|
||||
if 111000 <= package.weight < 130000:
|
||||
ltl_over_weightfee = max(ltl_over_weightfee, round(70 / self.profit_rate - 78, 1)) # 订单物流放78,其他在售价里
|
||||
if 130000 <= package.weight < 157000:
|
||||
ltl_over_weightfee = max(ltl_over_weightfee, round(110 / self.profit_rate - 118, 1)) # 订单物流放118,其他在售价里
|
||||
# 基础费用
|
||||
if sku_total_cubic_feet < 25:
|
||||
ltl_base_fee1 = round(163 / self.profit_rate / 2, 2)
|
||||
elif sku_total_cubic_feet < 35:
|
||||
ltl_base_fee1 = round(180 / self.profit_rate / 2, 2)
|
||||
else:
|
||||
# 大于一个立方的(35立方英尺) 按照mei立方英尺*5美金
|
||||
# 最低为190美金
|
||||
ltl_base_fee1 = round(max(190, 5 * sku_total_cubic_feet) / self.profit_rate / 2, 2)
|
||||
ltl_base_fee = ltl_base_fee1 * 1.025 # 2.5%平摊偏远附加费成本
|
||||
# 最后卡派费用等于基础费用+ 超长+ 超重费
|
||||
ltl_fee = ltl_base_fee + ltlover_size + ltl_over_weightfee
|
||||
|
||||
# 最后根据卡派费用和快递费用,取小值
|
||||
if index == 0: # 该sku没有卡派和不可售包裹
|
||||
sku_total_express_fee = min(ltl_fee, sku_express_fee)
|
||||
else:
|
||||
sku_total_express_fee = ltl_fee
|
||||
return sku_total_express_fee
|
||||
|
||||
def cal_sell_price_2025(self):
|
||||
sell_price = 0
|
||||
express = self.cal_express_fee_2025()
|
||||
if self.shipping_type == 0: # 空运
|
||||
for package in self.packages:
|
||||
volume_weight = package.get_volume_weight(6000)
|
||||
type_weight = min(337, max(37, package.density)) # 货型 = 密度?
|
||||
air_first_cny = (self.air_cny_type * type_weight + self.air_first_fix)
|
||||
air_first_cny_total = air_first_cny * volume_weight
|
||||
air_first_usd_total = self.air_first_usd * volume_weight
|
||||
sell_price += (self.purchase_price / self.exchange_rate / self.profit_rate +
|
||||
(((air_first_cny_total / self.exchange_rate + air_first_usd_total)+
|
||||
self.purchase_price*self.tax_rate/self.exchange_rate)/
|
||||
self.profit_rate + express) * self.air_rate)
|
||||
|
||||
else: # 海运 or 不可售
|
||||
volume_weight = sum([package.get_volume_weight(6000) for package in self.packages])
|
||||
ocean_first_cny_total = self.ocean_first_cny * volume_weight
|
||||
ocean_first_usd_total = self.ocean_first_usd * volume_weight
|
||||
sell_price = (((ocean_first_cny_total + self.purchase_price) /
|
||||
self.exchange_rate + ocean_first_usd_total)
|
||||
+ self.purchase_price*self.tax_rate/self.exchange_rate) / self.profit_rate + express
|
||||
|
||||
return (sell_price,express,ocean_first_cny_total,ocean_first_usd_total)
|
||||
|
||||
@classmethod
|
||||
def litfad(cls, packages, purchase_price, shipping_type):
|
||||
return cls(
|
||||
packages, # 单sku包裹数据
|
||||
purchase_price, # 采购价/成本价
|
||||
shipping_type, # 1为海运0为空运,由spu定好的
|
||||
ocean_first_cny=1, #1.077, # 海运头程单价CNY
|
||||
ocean_first_usd=1, #1.06, # 海运头程单价USD
|
||||
air_first_usd=0.65, # 空运头程单价USD
|
||||
air_cny_type=0.093, # 空运头程货型单价CNY
|
||||
air_first_fix=22.7, # 空运头程固定单价CNY
|
||||
exchange_rate=6.5, # 汇率
|
||||
profit_rate=0.359, #0.45, # 利润系数
|
||||
air_rate=0.7, # 空运分配占比
|
||||
tax_rate = 0.145 # 税率
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def runnup(cls, packages, purchase_price, shipping_type):
|
||||
return cls(
|
||||
packages, # 单sku包裹数据
|
||||
purchase_price, # 采购价/成本价
|
||||
shipping_type, # 1为海运0为空运,由spu定好的
|
||||
ocean_first_cny=1.077, # 海运头程单价CNY
|
||||
ocean_first_usd=1.06, # 海运头程单价USD
|
||||
air_first_usd=0.65, # 空运头程单价USD
|
||||
air_cny_type=0.093, # 空运头程货型单价CNY
|
||||
air_first_fix=27.7, # 空运头程固定单价CNY
|
||||
exchange_rate=6, # 汇率
|
||||
profit_rate=0.45, # 利润系数
|
||||
air_rate=0.7, # 空运分配占比
|
||||
tax_rate = 0.145 # 税率
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def lakiq(cls, packages, purchase_price, shipping_type):
|
||||
return cls(
|
||||
packages, # 单sku包裹数据
|
||||
purchase_price, # 采购价/成本价
|
||||
shipping_type, # 1为海运0为空运,由spu定好的
|
||||
ocean_first_cny=1.1, # 海运头程单价CNY
|
||||
ocean_first_usd=1.1, # 海运头程单价USD
|
||||
air_first_usd=0.65, # 空运头程单价USD
|
||||
air_cny_type=0.093, # 空运头程货型单价CNY
|
||||
air_first_fix=27.7, # 空运头程固定单价CNY
|
||||
exchange_rate=6, # 汇率
|
||||
profit_rate=0.45, # 利润系数
|
||||
air_rate=0.7, # 空运分配占比
|
||||
tax_rate = 0.145 # 税率
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def kwoking(cls, packages, purchase_price, shipping_type):
|
||||
return cls(
|
||||
packages, # 单sku包裹数据
|
||||
purchase_price, # 采购价/成本价
|
||||
shipping_type, # 1为海运0为空运,由spu定好的
|
||||
ocean_first_cny=1.08, # 海运头程单价CNY
|
||||
ocean_first_usd=1.07, # 海运头程单价USD
|
||||
air_first_usd=0.65, # 空运头程单价USD
|
||||
air_cny_type=0.093, # 空运头程货型单价CNY
|
||||
air_first_fix=27.7, # 空运头程固定单价CNY
|
||||
exchange_rate=6, # 汇率
|
||||
profit_rate=0.45, # 利润系数
|
||||
air_rate=0.7, # 空运分配占比
|
||||
tax_rate = 0.145 # 税率
|
||||
)
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
import math
|
||||
import sys
|
||||
sys.path.append(r'D:\test\logistics\sell')
|
||||
import pandas as pd
|
||||
from sell.base_sell_price import SellPriceBase
|
||||
from utils.Package import Package, Package_group
|
||||
from utils.gtools import MySQLconnect
|
||||
import math
|
||||
import re
|
||||
express_price = pd.read_excel(r'D:\test\logistics\data\售价尾端价格.xlsx', sheet_name='Sheet1')
|
||||
key_column = express_price.iloc[:, 8] # 第 I 列
|
||||
value_column = express_price.iloc[:, 9] # 第 J 列
|
||||
small_column = express_price.iloc[:, 10] # 第 K 列
|
||||
big_column = express_price.iloc[:, 11] # 第 L 列
|
||||
air_small_dict = dict(zip(key_column, small_column))
|
||||
air_big_dict = dict(zip(key_column, big_column))
|
||||
# 转换成字典
|
||||
ocean_price_dict = dict(zip(key_column, value_column))
|
||||
def ocean_order_price(packages):
|
||||
express_fee = 0 # 快递基础费
|
||||
long_fee = 0 # 超长费
|
||||
weight_fee = 0 # 超重费
|
||||
big_fee = 0 # 大包裹费
|
||||
express_type = ''
|
||||
express_type_weight = ''
|
||||
express_type_length = ''
|
||||
for package in packages:
|
||||
for key, value in ocean_price_dict.items():
|
||||
if package.weight <=key:
|
||||
express_fee+=value
|
||||
break
|
||||
if package.fst_size>=116 or package.sed_size>=71 or package.girth>=251:
|
||||
long_fee += 16.3
|
||||
if express_type_length == '':
|
||||
express_type_length ="超长"
|
||||
if package.weight>=21000 and package.fst_size<238 and package.girth<315:
|
||||
weight_fee+=25.5
|
||||
express_type_weight ="超重"
|
||||
if package.fst_size>=238 or package.girth>=315:
|
||||
big_fee+=61.6
|
||||
express_type_length ="大包裹"
|
||||
express_fee = express_fee + long_fee + weight_fee + big_fee
|
||||
express_type = express_type_length + express_type_weight
|
||||
|
||||
# 卡派(步长为3)
|
||||
ltl_base = 0
|
||||
ltl_fee = 0
|
||||
count1 = 0
|
||||
count2 = 0
|
||||
count3 = 0
|
||||
count4 = 0
|
||||
ltl_type = '卡派'
|
||||
order_type_length=''
|
||||
order_type_weight=''
|
||||
order_ltl_oversize = 0
|
||||
order_ltl_overweight1 = 0
|
||||
order_ltl_overweight2 = 0
|
||||
order_ltl_overpackage = 0
|
||||
sku_total_cubic_feet = 0
|
||||
for package in packages:
|
||||
cubic_feet= package.length * package.width * package.height / 1000000 * 35.3
|
||||
sku_total_cubic_feet += cubic_feet
|
||||
# 卡派额外费用
|
||||
if package.fst_size>= 250:
|
||||
count1 += 1
|
||||
order_ltl_oversize = 118
|
||||
if order_type_length == '':
|
||||
order_type_length = '超长'
|
||||
if package.weight >= 111000:
|
||||
count2 += 1
|
||||
order_ltl_overweight1 = 78
|
||||
order_type_weight = '超重'
|
||||
if package.weight >= 130000:
|
||||
count3 += 1
|
||||
order_ltl_overweight2 = 30
|
||||
if package.fst_size >= 310:
|
||||
count4 += 1
|
||||
order_ltl_overpackage = 30
|
||||
order_type_length = '大包裹'
|
||||
order_type2 = ltl_type +order_type_length+ order_type_weight
|
||||
|
||||
# 卡派基础费用 体积/1000000 *35.3
|
||||
if sku_total_cubic_feet < 25:
|
||||
ltl_base = round(163 / 0.45 / 2, 2) # 181.11
|
||||
|
||||
elif sku_total_cubic_feet < 35:
|
||||
ltl_base = round(180 / 0.45 / 2, 2) # 200
|
||||
else:
|
||||
# 大于一个立方的(35立方英尺) 按照每立方英尺*5美金
|
||||
# 最低为190美金
|
||||
ltl_base = round(max(190, 5 * sku_total_cubic_feet) / 0.359 / 2)
|
||||
|
||||
|
||||
ltl_fee = math.ceil(count1 / 3) * order_ltl_oversize + math.ceil(count2 / 3) * order_ltl_overweight1 + math.ceil(
|
||||
count3 / 3) * order_ltl_overweight2 + math.ceil(count4 / 3) * order_ltl_overpackage + ltl_base
|
||||
|
||||
if ltl_fee < express_fee:
|
||||
ocean_fee = ltl_fee
|
||||
order_type = order_type2
|
||||
else:
|
||||
ocean_fee = express_fee
|
||||
order_type = express_type
|
||||
return ocean_fee, order_type
|
||||
|
||||
def air_order_price(packages):
|
||||
express_fee = 0
|
||||
express_type = ''
|
||||
for package in packages:
|
||||
price=0
|
||||
bill_weight = max(package.weight, package.get_volume_weight(8500))
|
||||
if package.weight<=420 and package.fst_size<=50 and package.sed_size<=40 and package.trd_size<=30:
|
||||
for key, value in air_small_dict.items():
|
||||
if package.weight <=key:
|
||||
price =value
|
||||
break
|
||||
elif package.weight<=2718 and package.fst_size<=50 and package.sed_size<=40 and package.trd_size<=30:
|
||||
for key, value in air_small_dict.items():
|
||||
if bill_weight <=key:
|
||||
price =value
|
||||
break
|
||||
else:
|
||||
for key, value in air_big_dict.items():
|
||||
if bill_weight <=key:
|
||||
price =value
|
||||
break
|
||||
if package.weight<=420:
|
||||
express_fee+=((((min(max(package.density,37),337)*0.093+27.7)/6+0.65)*package.get_volume_weight(6000))*0.3+price)/0.45
|
||||
if express_type == '':
|
||||
express_type='USPS'
|
||||
elif package.weight<=2718:
|
||||
express_fee+=(((min(max(package.density,37),337)*0.093+27.7)/6+0.65)*package.get_volume_weight(8500)*0.3+price)/0.45
|
||||
if express_type == '' or express_type == 'USPS':
|
||||
express_type='UandF'
|
||||
else:
|
||||
express_fee+=(((min(max(package.density,37),337)*0.093+27.7-1.08)/6+0.65-1.06)*package.get_volume_weight(8500))/0.45+price
|
||||
express_type='FEDEX'
|
||||
return express_fee, express_type
|
||||
|
||||
# 美国售价,订单物流费,订单类型,分海运空运
|
||||
def call_sell_and_order_price(price, package_dict,head_type):
|
||||
"""
|
||||
price:采购价
|
||||
package_dict:包裹数据
|
||||
head_type:头程类型 海运/空运
|
||||
"""
|
||||
packages = Package_group()
|
||||
def extract_number(value):
|
||||
# 提取字符串中的第一个数字
|
||||
match = re.search(r"[-+]?\d*\.\d+|\d+", str(value))
|
||||
return float(match.group()) if match else 0.0
|
||||
|
||||
for key, package in package_dict.items():
|
||||
package['长'] = extract_number(package['长'])
|
||||
package['宽'] = extract_number(package['宽'])
|
||||
package['高'] = extract_number(package['高'])
|
||||
package['重量'] = extract_number(package['重量'])
|
||||
|
||||
if package['长'] == 0 or package['宽'] == 0 or package['高'] == 0 or package['重量'] == 0:
|
||||
return (0,0,0,0),0,0
|
||||
packages.add_package(Package(key,package['长'], package['宽'], package['高'], package['重量']))
|
||||
|
||||
if packages is None:
|
||||
return (0,0,0,0),0,0
|
||||
litfad = SellPriceBase.litfad(packages, price,1)
|
||||
# 修改版本,网站售价
|
||||
sell_price = litfad.cal_sell_price_2025()
|
||||
# 订单物流费
|
||||
if "海" in head_type:
|
||||
order_price, order_type = ocean_order_price(packages)
|
||||
else:
|
||||
order_price, order_type = air_order_price(packages)
|
||||
return sell_price,order_price,order_type
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
package_dict = {
|
||||
'1': {'长': 10, '宽': 10, '高': 10, '重量': 10},
|
||||
'2': {'长': 20, '宽': 20, '高': 20, '重量': 20},
|
||||
'3': {'长': 30, '宽': 30, '高': 30, '重量': 30},
|
||||
'4': {'长': 40, '宽': 40, '高': 40, '重量': 40},
|
||||
'5': {'长': 50, '宽': 50, '高': 50, '重量': 50},
|
||||
}
|
||||
price = 100
|
||||
sell_price,order_price,order_type = call_sell_and_order_price(price, package_dict)
|
||||
print(sell_price,order_price,order_type)
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,99 @@
|
|||
|
||||
from math import ceil
|
||||
def data_to_package(package_data):
|
||||
"""
|
||||
输入{"包裹1": {"宽": 20.0, "长": 200.0, "高": 20.0, "重量": 7500}, "包裹2": {"宽": 15.0, "长": 150.0, "高": 15.0, "重量": 5000}}
|
||||
返回一个packages对象
|
||||
"""
|
||||
packages = Package_group()
|
||||
for name, data in package_data.items():
|
||||
package = Package(name, data["长"],data["宽"], data["高"], data["重量"])
|
||||
packages.add_package(package)
|
||||
return packages
|
||||
|
||||
class Package:
|
||||
def __init__(self,name, length, width, height, weight):
|
||||
self.length = length
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.weight = weight
|
||||
self.name = name
|
||||
sizes = sorted([self.length, self.width, self.height])
|
||||
self.fst_size = sizes[2]
|
||||
self.sed_size = sizes[1]
|
||||
self.trd_size = sizes[0]
|
||||
self.girth = (self.sed_size + self.trd_size) * 2 + self.fst_size
|
||||
self.volume = self.length * self.width * self.height
|
||||
self.size_sum = self.fst_size + self.sed_size + self.trd_size # 三边和
|
||||
self.density = ceil(self.weight / self.volume * 1000)
|
||||
|
||||
self.fst_inch = self.fst_size * 0.3937
|
||||
self.sed_inch = self.sed_size * 0.3937
|
||||
self.trd_inch = self.trd_size * 0.3937
|
||||
self.girth_inch = self.girth * 0.3937
|
||||
self.volume_inch = self.fst_inch *self.sed_inch*self.trd_inch
|
||||
self.lbs_weight = self.weight/1000 * 2.2046
|
||||
|
||||
def get_volume_weight(self, volume_weight_ratio):
|
||||
volume_weight = (self.length * self.width * self.height) / volume_weight_ratio
|
||||
return volume_weight
|
||||
def __repr__(self):
|
||||
result = "{"
|
||||
result += f"\"长\":{self.length}, \"宽\"{self.width}, \"高\"{self.height}, \"重量\"{self.weight}"
|
||||
result +="}"
|
||||
return result
|
||||
|
||||
class PackageLogistics(Package):
|
||||
def __init__(self, length, width, height, weight,convey,company):
|
||||
super().__init__(length,width,height,weight)
|
||||
self.company = company
|
||||
self.convey = convey
|
||||
|
||||
class Package_group:
|
||||
def __init__(self, packages=None):
|
||||
self.packages = packages if packages else []
|
||||
self.volume_weight_key = None # 用于存储体积重系数的键
|
||||
self.density = self.density()
|
||||
|
||||
def add_package(self, package):
|
||||
self.packages.append(package)
|
||||
|
||||
# 设置体积重系数的键
|
||||
def set_volume_weight_key(self, key):
|
||||
self.volume_weight_key = key
|
||||
|
||||
def total_volume_weight(self):
|
||||
total_volume_weight = 0
|
||||
for package in self.packages:
|
||||
if self.volume_weight_key: # 如果设置了体积重系数的键
|
||||
volume_weight = package[self.volume_weight_key]
|
||||
total_volume_weight += volume_weight
|
||||
return total_volume_weight
|
||||
|
||||
def density(self):
|
||||
volume = 0
|
||||
weight = 0
|
||||
density = 0
|
||||
for package in self.packages:
|
||||
volume += package.length * package.width * package.height
|
||||
weight += package.weight
|
||||
density = ceil(weight / volume * 1000)
|
||||
return density
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.packages)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.packages)
|
||||
def __repr__(self):
|
||||
result = "{"
|
||||
i=1
|
||||
for package in self.packages:
|
||||
result +=f"\"包裹{i}\": {package.__repr__()}"
|
||||
i+=1
|
||||
if i>len(self.packages):
|
||||
break
|
||||
result += ","
|
||||
result += "}"
|
||||
return result
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
from logisticsClass.logisticsBaseClass import HeadLogistics, PortType, TailLogistics,BaseLogistics
|
||||
# logistics_config.py
|
||||
|
||||
ACTIVE_LOGISTICS = {
|
||||
"FedexPPLogistics_US": False,
|
||||
"FedexKHLogistics_US": False,
|
||||
"FedexHOMELogistics_US": True,
|
||||
"FedexGROUDLogistics_US": True,
|
||||
"MetroLogistics_US": True,
|
||||
"GIGALogistics_US": False,
|
||||
"CEVALogistics_US": False,
|
||||
"XmilesLogistics_US":True,
|
||||
"AMWestLogistics_US":True,
|
||||
"AMEastLogistics_US":True
|
||||
}
|
||||
def apply_active_config():
|
||||
stack = list(BaseLogistics.__subclasses__())
|
||||
while stack:
|
||||
subclass = stack.pop()
|
||||
name = subclass.__name__
|
||||
if name in ACTIVE_LOGISTICS:
|
||||
subclass.active = ACTIVE_LOGISTICS[name]
|
||||
# 否则保持默认的 active
|
||||
stack.extend(subclass.__subclasses__())
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
|
||||
from logisticsClass.logisticsBaseClass import HeadLogistics, PortType, TailLogistics,BaseLogistics
|
||||
from logisticsClass.logisticsTail_EUR import *
|
||||
from logisticsClass.logisticsHead_EUR import *
|
||||
from logisticsClass.logisticsTail_US import *
|
||||
from logisticsClass.logisticsHead_US import *
|
||||
from logisticsClass.logisticsTail_AU import *
|
||||
from logisticsClass.logisticsHead_AU import *
|
||||
from logisticsClass.logisticsTail_UK import *
|
||||
from logisticsClass.logisticsHead_UK import *
|
||||
from utils.apply_active_config import apply_active_config
|
||||
|
||||
|
||||
class OperateCountry:
|
||||
"""操作国家"""
|
||||
def __init__(self, country_name,port = PortType.DEFAULT,packeges = None, postcode = None):
|
||||
# head表示头程方式,默认海运,0为空运
|
||||
self.country_name = country_name
|
||||
self.port = port
|
||||
self.packages = packeges
|
||||
self.postcode = postcode
|
||||
self.companies = self.get_tail_subclasses()
|
||||
|
||||
def set_packages_and_postcode(self,packages,postcode):
|
||||
self.packages = packages
|
||||
self.postcode = postcode
|
||||
|
||||
def set_parameter(self,parameter,self_parameter):
|
||||
"""设置参数,优先考虑函数参数,其次考虑类属性"""
|
||||
return parameter if parameter is not None else self_parameter
|
||||
|
||||
def get_head_class(self,port=None,head_type=None):
|
||||
"""获取头程类,头程与港口的关联更密切,所以可能一个头程类包含多个国家"""
|
||||
port = self.set_parameter(port, self.port)
|
||||
headclass = None
|
||||
apply_active_config()
|
||||
subclasses = HeadLogistics.get_open_subclasses()
|
||||
for cls in subclasses:
|
||||
if hasattr(cls, "country") and self.country_name in cls.country and cls.port == port and cls.logistics_type.value == head_type:
|
||||
headclass = cls()
|
||||
elif hasattr(cls, "country") and self.country_name in cls.country_code and cls.port ==port and cls.logistics_type.value == head_type:
|
||||
headclass = cls()
|
||||
if headclass is None:
|
||||
raise ValueError(f"No head class found for port {port}")
|
||||
return headclass
|
||||
|
||||
def get_air_fee(self, packages=None,port=None):
|
||||
"""获取空运头程费用"""
|
||||
packages = self.set_parameter(packages, self.packages)
|
||||
if packages is None:
|
||||
raise ValueError("No packages or port provided")
|
||||
air_class = self.get_head_class(port,"空运")
|
||||
air_head_fee = air_class.calculate_fee(packages)
|
||||
return air_head_fee
|
||||
def get_ocean_fee(self, packages=None,port=None):
|
||||
"""获取海运头程费用"""
|
||||
packages = self.set_parameter(packages, self.packages)
|
||||
if packages is None:
|
||||
raise ValueError("No packages or port provided")
|
||||
ocean_class = self.get_head_class(port,"海运")
|
||||
ocean_head_fee = ocean_class.calculate_fee(packages)
|
||||
return ocean_head_fee
|
||||
|
||||
def get_tail_subclasses(self):
|
||||
"""获取该国家对应港口的尾端物流实现类列表"""
|
||||
# 如果没有指定 port, 则返回默认港口的物流公司及其实例
|
||||
companies = {}
|
||||
apply_active_config()
|
||||
subclasses = TailLogistics.get_open_subclasses()
|
||||
for cls in subclasses:
|
||||
# 获取符合条件的公司,国家名相等且港口名相等
|
||||
if hasattr(cls, "country") and self.country_name == cls.country and cls.port == self.port:
|
||||
if hasattr(cls, "company"):
|
||||
companies[cls.company] = cls()
|
||||
else:
|
||||
continue
|
||||
elif hasattr(cls, "country") and self.country_name == cls.country_code and cls.port ==self.port:
|
||||
if hasattr(cls, "company"):
|
||||
companies[cls.company] = cls()
|
||||
else:
|
||||
continue
|
||||
return companies
|
||||
def get_all_tail_info(self, packages=None,postcode=None):
|
||||
"""计算所有物流公司尾端费用"""
|
||||
packages = self.set_parameter(packages, self.packages)
|
||||
postcode = self.set_parameter(postcode, self.postcode)
|
||||
if packages is None or postcode is None:
|
||||
raise ValueError("No packages or postcode provided")
|
||||
results = {}
|
||||
# 存储物流公司及其对应的实例
|
||||
for company, fee_instance in self.companies.items():
|
||||
results[company] = self.get_tail_amount(company,packages,postcode)
|
||||
return results
|
||||
|
||||
def get_min_company(self, packages=None,postcode=None):
|
||||
"""获取最低尾端费用公司"""
|
||||
packages = self.set_parameter(packages, self.packages)
|
||||
postcode = self.set_parameter(postcode, self.postcode)
|
||||
if packages is None or postcode is None:
|
||||
raise ValueError("No packages or postcode provided")
|
||||
all_fees = self.get_all_tail_info(packages,postcode)
|
||||
min_company = min(all_fees, key=all_fees.get)
|
||||
return min_company
|
||||
|
||||
def get_company_class(self, company_name):
|
||||
"""获取指定物流公司的物流类"""
|
||||
if company_name not in self.companies:
|
||||
raise ValueError(f"No such company: {company_name}")
|
||||
company_class = self.companies[company_name]
|
||||
return company_class
|
||||
|
||||
def get_tail_amount(self, company_name, packages=None,postcode=None):
|
||||
"""获取指定物流公司的费用"""
|
||||
packages = self.set_parameter(packages, self.packages)
|
||||
postcode = self.set_parameter(postcode, self.postcode)
|
||||
if packages is None or postcode is None:
|
||||
raise ValueError("No packages or postcode provided")
|
||||
if company_name not in self.companies:
|
||||
raise ValueError(f"No such company: {company_name}")
|
||||
detail_amount = self.get_detail_amount(company_name, packages,postcode)
|
||||
return round(detail_amount['tail_amount'], 2)
|
||||
def get_tail_currency(self, company_name):
|
||||
"""获取指定物流公司的货币"""
|
||||
company = self.get_company_class(company_name)
|
||||
return company.currency
|
||||
def get_logistic_type(self, company_name):
|
||||
"""获取指定物流公司的物流类型"""
|
||||
company = self.get_company_class(company_name)
|
||||
return company.logistics_type.value
|
||||
def get_detail_amount(self, company_name, packages=None,postcode=None):
|
||||
"""获取指定物流公司的费用明细"""
|
||||
packages = self.set_parameter(packages, self.packages)
|
||||
postcode = self.set_parameter(postcode, self.postcode)
|
||||
if packages is None or postcode is None:
|
||||
raise ValueError("No packages or postcode provided")
|
||||
company_class = self.get_company_class(company_name)
|
||||
detail_amount = company_class.calculate_fee(packages,postcode)
|
||||
return detail_amount
|
||||
|
||||
def convert_to_usd(self,amount,currency):
|
||||
"""将货币转换为美元"""
|
||||
if currency == "USD":
|
||||
return amount
|
||||
elif currency == "EUR": # 欧元
|
||||
return amount * 1.12
|
||||
elif currency == "GBP": # 英镑
|
||||
return amount * 1.3
|
||||
elif currency == "AUD": # 澳元
|
||||
return amount * 0.7
|
||||
elif currency == "CAD": # 加元
|
||||
return amount * 0.75
|
||||
elif currency == "JPY": # 日元
|
||||
return amount * 0.007
|
||||
elif currency == "CNY": # 人民币
|
||||
return amount * 0.14
|
||||
elif currency == "HKD": # 港币
|
||||
return amount * 0.13
|
||||
def usd_to_currency(self,amount,currency):
|
||||
"""将美元转换为指定货币"""
|
||||
if currency == "USD":
|
||||
return amount
|
||||
else:
|
||||
return amount / self.convert_to_usd(1,currency)
|
||||
def convert_currency(self,amount,current_currency,target_currency):
|
||||
"""先转美元,再转目标货币"""
|
||||
if current_currency == "USD":
|
||||
return self.usd_to_currency(amount,target_currency)
|
||||
else:
|
||||
return self.usd_to_currency(self.convert_to_usd(amount,current_currency),target_currency)
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import pymysql
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
|
||||
class MySQLconnect():
|
||||
def __init__(self, dbname: str):
|
||||
if isinstance(dbname, str):
|
||||
self.dbname = dbname
|
||||
self.host = '192.168.100.33'
|
||||
else:
|
||||
raise TypeError("dbname must be a string")
|
||||
|
||||
def __enter__(self):
|
||||
self.eng = self.engine()
|
||||
self.con = self.connect()
|
||||
self.cur = self.con.cursor()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.eng.dispose()
|
||||
self.cur.close()
|
||||
self.con.close()
|
||||
if exc_val:
|
||||
raise
|
||||
|
||||
def engine(self):
|
||||
return create_engine("mysql+pymysql://zhenggantian:123456@" + self.host + f":3306/{self.dbname}",
|
||||
pool_size=10, max_overflow=5, pool_recycle=3600)
|
||||
|
||||
def connect(self):
|
||||
return pymysql.connect(host=self.host, port=3306, database=self.dbname, user="zhenggantian", password="123456",
|
||||
charset="utf8")
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import List, Literal
|
||||
import pandas as pd
|
||||
from utils.countryOperator import OperateCountry
|
||||
from utils.Package import Package, Package_group
|
||||
|
||||
@dataclass
|
||||
class BillItem:
|
||||
"""账单项"""
|
||||
amount: float # 金额
|
||||
amount_usd: float # 金额(美元)
|
||||
item_type: Literal['头程','尾程'] # 项目类型 头程或尾程
|
||||
item_detail: str # 项目明细(如提单费,尾端基础运费,尾端附加费等)
|
||||
currency: str # 货币单位(如 USD, EUR 等)
|
||||
|
||||
|
||||
|
||||
# 应该有个模式,计算实际账单金额,或者得出最小费用金额,这个由工厂来控制,写两个账单类
|
||||
class Billing:
|
||||
"""账单"""
|
||||
def __init__(self, bill_name="wxx", operator: OperateCountry = None, packages: Package_group=None,postcode=None, company_name=None,head_type = 1,beizhu = None):
|
||||
self.operator = operator
|
||||
self.name = bill_name
|
||||
self.packages = packages
|
||||
self.postcode = postcode
|
||||
self.country = operator.country_name
|
||||
self.company_name = company_name
|
||||
self.head_type = head_type
|
||||
self.beizhu = beizhu
|
||||
self.items: List[BillItem] = [] # 存储账单项
|
||||
self.volume_weight = 0
|
||||
self.head_per = 0
|
||||
self.add_items_from_operator()
|
||||
|
||||
def add_item(self, item: BillItem):
|
||||
"""添加账单项"""
|
||||
self.items.append(item)
|
||||
|
||||
def add_items_from_operator(self):
|
||||
"""从Operator获取费用并添加账单项"""
|
||||
if self.packages is not None or self.postcode is not None:
|
||||
self.operator.set_packages_and_postcode(self.packages, self.postcode) # 设置包裹信息
|
||||
company_name = self.company_name if self.company_name is not None else self.operator.get_min_company()
|
||||
self.company_name = company_name
|
||||
|
||||
# 获取头程费用
|
||||
head_detail = self.operator.get_ocean_fee() if self.head_type == 1 else self.operator.get_air_fee()
|
||||
head_fee = head_detail['head_amount']
|
||||
self.head_per = head_detail['head_per']
|
||||
self.volume_weight = head_detail['volume_weight']
|
||||
|
||||
# 获取尾程费用
|
||||
detail_amount = self.operator.get_detail_amount(company_name)
|
||||
|
||||
# 获取货币单位
|
||||
tail_currency = self.operator.get_tail_currency(company_name)
|
||||
# 添加账单项
|
||||
self.add_item(BillItem(amount = head_fee,amount_usd = self.operator.convert_to_usd(head_fee, 'CNY'), item_type = "头程", item_detail = "head_amount",currency = 'CNY'))
|
||||
for item_detail,amount in detail_amount.items():
|
||||
if amount == 0 and item_detail != "tail_amount":
|
||||
continue
|
||||
self.add_item(BillItem(amount = round(amount,2),amount_usd = self.operator.convert_to_usd(round(amount,2), tail_currency), item_type = "尾程",item_detail=item_detail, currency=tail_currency))
|
||||
|
||||
def get_items(self):
|
||||
return self.items
|
||||
@property
|
||||
def tail_amount(self):
|
||||
for item in self.items:
|
||||
if item.item_type == "尾程" and item.item_detail == "tail_amount":
|
||||
tailfee = item.amount
|
||||
tailcurrency = item.currency
|
||||
break
|
||||
return tailfee,tailcurrency
|
||||
@property
|
||||
def head_amount(self):
|
||||
for item in self.items:
|
||||
if item.item_type == "头程" and item.item_detail == "head_amount":
|
||||
headfee = item.amount
|
||||
headcurrency = item.currency
|
||||
break
|
||||
return headfee,headcurrency
|
||||
@property
|
||||
def total_amount_usd(self):
|
||||
for item in self.items:
|
||||
if item.item_type == "头程" and item.item_detail == "head_amount":
|
||||
headfee = item.amount_usd
|
||||
if item.item_type == "尾程" and item.item_detail == "tail_amount":
|
||||
tailfee = item.amount_usd
|
||||
return headfee + tailfee
|
||||
|
||||
def bill_dict(self):
|
||||
"""返回账单字典"""
|
||||
result = {}
|
||||
result['ID'] = self.name
|
||||
result['体积重'] = self.volume_weight
|
||||
result['头程单价'] = self.head_per
|
||||
result['预测头程CNY'] = self.head_amount[0]
|
||||
result['预测尾端'] = self.tail_amount[0]
|
||||
|
||||
for item in self.items:
|
||||
if item.item_type == "头程" or item.item_detail == 'tail_amount':
|
||||
continue
|
||||
result[item.item_detail] = item.amount
|
||||
result['总金额USD'] = self.total_amount_usd
|
||||
result['货币单位'] = self.tail_amount[1]
|
||||
result['尾端渠道'] = self.company_name
|
||||
result['备注'] = self.beizhu
|
||||
return result
|
||||
# 附加费明细
|
||||
def get_other_fee(self):
|
||||
"""返回账单附加费明细"""
|
||||
detail_items = {}
|
||||
for item in self.items:
|
||||
if item.item_type != "头程" and item.item_detail != "tail_amount" and item.item_detail != "base":
|
||||
detail_items[item.item_detail] = item.amount
|
||||
return detail_items
|
||||
|
||||
def __repr__(self):
|
||||
result = "账单名称: " + str(self.name) + "\n"
|
||||
result += "账单项:"
|
||||
result += " ".join(f"{item.item_detail}: {item.amount} {item.currency}" for item in self.items)
|
||||
result += " 总金额: " + str(self.total_amount_usd) + " USD"
|
||||
return result
|
||||
|
||||
|
||||
class BillFactory:
|
||||
"""账单工厂"""
|
||||
def __init__(self):
|
||||
self.bills = []
|
||||
def create_bill(self, bill_name,operator: OperateCountry,packages = None,postcode = None,company_name = None,head_type=1,beizhu = None):
|
||||
bill = Billing(bill_name,operator,packages,postcode,company_name,head_type,beizhu)
|
||||
self.add_bill(bill)
|
||||
return bill
|
||||
|
||||
def add_bill(self, bill):
|
||||
self.bills.append(bill)
|
||||
def get_bills(self):
|
||||
"""获取全部账单列表"""
|
||||
return self.bills
|
||||
def bills_to_df(self):
|
||||
"""将账单列表导存为df"""
|
||||
# 先定义数据结构
|
||||
bills_dict = []
|
||||
for bill in self.bills:
|
||||
bills_dict.append(bill.bill_dict())
|
||||
df = pd.DataFrame(bills_dict)
|
||||
# 重排列顺序
|
||||
columns_to_move = ['预测尾端', '总金额USD', '货币单位', '尾端渠道', '备注']
|
||||
reordered_columns = [col for col in df.columns if col not in columns_to_move] + columns_to_move
|
||||
df = df[reordered_columns]
|
||||
return df
|
||||
|
||||
def __repr__(self):
|
||||
result = "\n".join(str(bill) for bill in self.bills)
|
||||
return f"账单列表:\n{result}"
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 测试
|
||||
|
||||
package = Package("wxx",63,59,48,8000)
|
||||
packages= Package_group([package])
|
||||
postcode = "PA2 9BF"
|
||||
billFactory = BillFactory()
|
||||
opCountry= OperateCountry('UK')
|
||||
company_name = "智谷"
|
||||
bill = billFactory.create_bill("wxx",opCountry,packages,postcode,company_name,1,"无")
|
||||
print(billFactory)
|
||||
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"df = pd.read_clipboard()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"得到每个SKU的最长边,围长,总重量,6000抛重,采购体积比(采购/6000抛重)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import re\n",
|
||||
"from utils import Package,Package_group\n",
|
||||
"def extract_number(value):\n",
|
||||
" # 提取字符串中的第一个数字\n",
|
||||
" match = re.search(r\"[-+]?\\d*\\.\\d+|\\d+\", str(value))\n",
|
||||
" return float(match.group()) if match else 0.0\n",
|
||||
"for index, row in df.iterrows():\n",
|
||||
" package_dict = json.loads(row['erp_package_vol'])\n",
|
||||
" max_length = 0 # 最长边\n",
|
||||
" max_girth = 0 # 最大围长\n",
|
||||
" all_weight = 0 # 总重量\n",
|
||||
" all_vol_weight = 0 # 总抛重\n",
|
||||
" for key, package in package_dict.items():\n",
|
||||
" package['长'] = extract_number(package['长'])\n",
|
||||
" package['宽'] = extract_number(package['宽'])\n",
|
||||
" package['高'] = extract_number(package['高'])\n",
|
||||
" package['重量'] = extract_number(package['重量'])\n",
|
||||
" "
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "base",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,309 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 根据订单号,查询实际包裹尺寸,实际包裹实重,实际包裹体积重,\n",
|
||||
"# bill账单给头程单价,实际尾端计费重,预估尾端成本,尾端附加费\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"import pandas as pd\n",
|
||||
"from utils.Package import Package,Package_group\n",
|
||||
"from utils.gtools import MySQLconnect\n",
|
||||
"from utils.countryOperator import OperateCountry\n",
|
||||
"from utils.logisticsBill import BillFactory, Billing\n",
|
||||
"import requests\n",
|
||||
"import json\n",
|
||||
"ods = MySQLconnect(\"ods\")\n",
|
||||
"engine = ods.engine()\n",
|
||||
"def get_package_info_by_sql(order_df):\n",
|
||||
" packages = Package_group()\n",
|
||||
" packages_str = \"\"\n",
|
||||
" for index, row in order_df.iterrows():\n",
|
||||
" if row['包裹数据'] is None:\n",
|
||||
" return None,\"\"\n",
|
||||
" row['包裹数据'] = json.loads(row['包裹数据'])\n",
|
||||
" item_list = [\n",
|
||||
" {key: float(value) for key, value in package.items()} for package in row['包裹数据'].values()\n",
|
||||
" ]\n",
|
||||
" for item in item_list:\n",
|
||||
" if item['长'] == 0 or item['宽'] == 0 or item['高'] == 0 or item['重量'] == 0:\n",
|
||||
" return None,\"\"\n",
|
||||
" package = Package(row['SKU'], item['长'], item['宽'], item['高'], item['重量'])\n",
|
||||
" packages.add_package(package)\n",
|
||||
" # 实际包裹尺寸\n",
|
||||
" package_str = f\"{ item['重量']}|{item['长']}*{ item['宽']}*{item['高']}\"\n",
|
||||
" packages_str += package_str + \",\"\n",
|
||||
" return packages,packages_str\n",
|
||||
"\n",
|
||||
"def get_biaozhun_bill(opCountry,order_id,postcode,convey,amount):\n",
|
||||
" print(order_id)\n",
|
||||
" # 取标准包裹数据\n",
|
||||
" sql = \"\"\"SELECT\n",
|
||||
" order_id,\n",
|
||||
" opl.SKU,\n",
|
||||
" 包裹数据\n",
|
||||
" FROM\n",
|
||||
" dws.order_product_list opl\n",
|
||||
" LEFT JOIN ads.sku_package_new spi ON opl.SKU =spi.SKU\n",
|
||||
" WHERE\n",
|
||||
" order_id = %s\n",
|
||||
" AND\n",
|
||||
" opl.order_product_id REGEXP \"[0-9]{15}_[0-9]*$\"\n",
|
||||
" \"\"\"\n",
|
||||
" order_package_date = pd.read_sql(sql, engine, params=(order_id,))\n",
|
||||
" print(order_package_date)\n",
|
||||
" if order_package_date['包裹数据'] is None:\n",
|
||||
" return \"\",0,0,0,0,0,\"\",\"\",0\n",
|
||||
" # 取账单数据\n",
|
||||
" beizhu = amount\n",
|
||||
" conveys = 1 if convey == \"海运\" else 0\n",
|
||||
" try:\n",
|
||||
" packages,packages_str = get_package_info_by_sql(order_package_date)\n",
|
||||
" if packages is None:\n",
|
||||
" return \"\",0,0,0,0,0,\"\",\"\",0\n",
|
||||
" bill = Billing(str(order_id),opCountry,packages,postcode,company_name=None,head_type=conveys,beizhu=beizhu)\n",
|
||||
" print(bill)\n",
|
||||
" tail_amount = bill.bill_dict()[\"预测尾端\"]\n",
|
||||
" total_weight = sum([p.weight/1000 for p in packages.packages])\n",
|
||||
" _type = bill.bill_dict()[\"尾端渠道\"]\n",
|
||||
" head_amount = bill.bill_dict()[\"预测头程CNY\"]\n",
|
||||
" total_amount = bill.bill_dict()[\"总金额USD\"]\n",
|
||||
" volume_weight = bill.bill_dict()[\"体积重\"]\n",
|
||||
" per_head = bill.bill_dict()[\"头程单价\"]\n",
|
||||
" other_detail = bill.get_other_fee()\n",
|
||||
" return packages_str,total_weight,volume_weight,per_head,head_amount,tail_amount,other_detail,_type,total_amount\n",
|
||||
" except ZeroDivisionError as e:\n",
|
||||
" print(e)\n",
|
||||
" return \"\",0,0,0,0,0,\"\",\"\",0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"250111100026591\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250111100026591 2206000579 None\n",
|
||||
"250111180013924\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250111180013924 2204856252 None\n",
|
||||
"250111231028179\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250111231028179 2205859024 None\n",
|
||||
"250112064002567\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250112064002567 2205793890 None\n",
|
||||
"250112105838284\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250112105838284 2205594270 None\n",
|
||||
"250112131002633\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250112131002633 2205979853 None\n",
|
||||
"250113011509113\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250113011509113 2205380959 None\n",
|
||||
"250113020908795\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250113020908795 2201598238 None\n",
|
||||
"250113090041954\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250113090041954 2205665390 None\n",
|
||||
"250113090613446\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250113090613446 2201598243 None\n",
|
||||
"250113100616085\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250113100616085 2204814402 None\n",
|
||||
"250114055637547\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250114055637547 2205905912 None\n",
|
||||
"250115164425035\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250115164425035 2205672778 None\n",
|
||||
"250117005435286\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250117005435286 2205415197 None\n",
|
||||
"1 250117005435286 2205415198 None\n",
|
||||
"2 250117005435286 2205415200 None\n",
|
||||
"250117024809584\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250117024809584 2203733825 None\n",
|
||||
"250117053614017\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250117053614017 2205969448 None\n",
|
||||
"250117100906322\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250117100906322 2201598255 None\n",
|
||||
"250117234053287\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250117234053287 2205565699 None\n",
|
||||
"250119153413596\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250119153413596 2205878673 None\n",
|
||||
"250119211402563\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250119211402563 2205520825 None\n",
|
||||
"250121125819971\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250121125819971 2202074323 None\n",
|
||||
"250121212647006\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250121212647006 2205788907 None\n",
|
||||
"250122061036006\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250122061036006 2206135206 None\n",
|
||||
"250124213014331\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250124213014331 2205788467 None\n",
|
||||
"250127001437947\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250127001437947 2205511833 None\n",
|
||||
"250127175227235\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250127175227235 2205780462 None\n",
|
||||
"250128015414537\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250128015414537 2206279144 None\n",
|
||||
"250129051002053\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250129051002053 2205511832 None\n",
|
||||
"250129075002066\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250129075002066 2205905094 None\n",
|
||||
"250129112827456\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250129112827456 2205550813 None\n",
|
||||
"250130194409986\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250130194409986 2205025464 None\n",
|
||||
"1 250130194409986 2205025465 None\n",
|
||||
"2 250130194409986 2205025466 None\n",
|
||||
"250201063711441\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250201063711441 2205524464 None\n",
|
||||
"250202204402848\n",
|
||||
" order_id SKU 包裹数据\n",
|
||||
"0 250202204402848 2205780463 None\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"sql = \"\"\"SELECT\n",
|
||||
" CONCAT(\"[\",GROUP_CONCAT(pr.包裹号),\"]\") AS package_group ,\n",
|
||||
" ol.order_id ,\n",
|
||||
" ol.postcode,\n",
|
||||
" ol.delivery_country,\n",
|
||||
" ol.convey,\n",
|
||||
"\t\t\t\tols.`货币`,\n",
|
||||
"\t\t\t\tols.`订单总额`,\n",
|
||||
"\t\t\t\tols.支付方式,\n",
|
||||
" ol.order_price_dollar\n",
|
||||
" FROM\n",
|
||||
" parcel pr\n",
|
||||
" LEFT JOIN dwd.order_list ol ON ol.order_id = pr.订单号 \n",
|
||||
"\t\t\t\tleft JOIN ods.order_list_supplement ols ON ol.order_id = ols.订单号\n",
|
||||
" WHERE\n",
|
||||
" order_id IN (250111180013924 ,\n",
|
||||
"250114055637547 ,\n",
|
||||
"250113020908795 ,\n",
|
||||
"250113090041954 ,\n",
|
||||
"250112064002567 ,\n",
|
||||
"250112105838284 ,\n",
|
||||
"250111100026591 ,\n",
|
||||
"250112131002633 ,\n",
|
||||
"250113090613446 ,\n",
|
||||
"250115164425035 ,\n",
|
||||
"250111231028179 ,\n",
|
||||
"250117024809584 ,\n",
|
||||
"250117005435286 ,\n",
|
||||
"250117100906322 ,\n",
|
||||
"250113100616085 ,\n",
|
||||
"250117053614017 ,\n",
|
||||
"250113011509113 ,\n",
|
||||
"250119211402563 ,\n",
|
||||
"250119153413596 ,\n",
|
||||
"250117234053287 ,\n",
|
||||
"250127001437947 ,\n",
|
||||
"250127175227235 ,\n",
|
||||
"250129051002053 ,\n",
|
||||
"250121125819971 ,\n",
|
||||
"250122061036006 ,\n",
|
||||
"250129112827456 ,\n",
|
||||
"250121212647006 ,\n",
|
||||
"250124213014331 ,\n",
|
||||
"250202204402848 ,\n",
|
||||
"250130194409986 ,\n",
|
||||
"250128015414537 ,\n",
|
||||
"250201063711441 ,\n",
|
||||
"250129075002066 \n",
|
||||
"\n",
|
||||
"\n",
|
||||
")\n",
|
||||
"GROUP BY ol.order_id\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"order_id_df = pd.read_sql(sql,engine)\n",
|
||||
"order_id_df[[\"包裹尺寸详情\",\"实重\",\"体积重\",\"头程单价\",\"头程费用(CNY)\",\"尾端费用(当地货币)\",\"尾端附加费\",\"尾端渠道\",\"总金额(USD)\"]] = order_id_df.apply(lambda x: get_biaozhun_bill(OperateCountry(x['delivery_country']),\n",
|
||||
" x['order_id'],\n",
|
||||
" x['postcode'], \n",
|
||||
" x['convey'],\n",
|
||||
" x['order_price_dollar']),axis=1,result_type='expand')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"order_id_df.to_clipboard()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"#"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "base",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"1.查询5.1-5.28测量包裹尺寸的订单数据\n",
|
||||
"2.以美国的售价计算它当前应该有的售价\n",
|
||||
"3.判断"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sql = \"\"\"SELECT * FROM 表名\"\"\""
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "base",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.11.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,700 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 根据订单号,查询实际包裹尺寸,实际包裹实重,实际包裹体积重,\n",
|
||||
"# bill账单给头程单价,实际尾端计费重,预估尾端成本,尾端附加费\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"import re\n",
|
||||
"import pandas as pd\n",
|
||||
"from utils.Package import Package,Package_group\n",
|
||||
"from utils.gtools import MySQLconnect\n",
|
||||
"from utils.countryOperator import OperateCountry\n",
|
||||
"from utils.logisticsBill import BillFactory, Billing\n",
|
||||
"import requests\n",
|
||||
"import json\n",
|
||||
"ods = MySQLconnect(\"ods\")\n",
|
||||
"engine = ods.engine()\n",
|
||||
"def get_SKU_info(order_id):\n",
|
||||
" \"\"\"\n",
|
||||
" 根据订单号获取SKU信息,以及入库采购价\n",
|
||||
" \"\"\"\n",
|
||||
" SKULIST = \"\"\"\n",
|
||||
" SELECT\n",
|
||||
" order_id,\n",
|
||||
" CASE WHEN COUNT(DISTINCT opl.SKU) = 1 THEN \"单SKU\" ELSE \"多SKU\" end as \"SKU类型\",\n",
|
||||
" CONCAT(group_concat(opl.SKU),\",\") as \"SKU明细\",\n",
|
||||
" sum(s1.`成本价`*product_num) AS \"入库采购价\"\n",
|
||||
" FROM\n",
|
||||
" dws.order_product_list opl LEFT JOIN ods.stg_bayshop_litfad_sku s1 on s1.sku = opl.sku\n",
|
||||
" LEFT JOIN dwd.dim_erp_sku_package_vol_info des on des.erp_sku = opl.sku\n",
|
||||
" \n",
|
||||
" WHERE\n",
|
||||
" order_id = %s\n",
|
||||
" AND order_product_id regexp \"[0-9]{15}_[0-9]*$\"\n",
|
||||
" GROUP BY order_id \n",
|
||||
" \n",
|
||||
" \"\"\"\n",
|
||||
" engine = ods.engine()\n",
|
||||
" sku_df = pd.read_sql(SKULIST, engine,params=(order_id,))\n",
|
||||
" # print(\"SKU类型:\",sku_df['SKU类型'][0],\"SKU明细:\",sku_df['SKU明细'][0],\"入库采购价:\",sku_df['入库采购价'][0])\n",
|
||||
" return sku_df['SKU类型'][0],sku_df['SKU明细'][0],sku_df['入库采购价'][0]\n",
|
||||
"\n",
|
||||
"def get_package_real_vol_by_api(packages_id):\n",
|
||||
" \"\"\"\n",
|
||||
" 根据订单号的包裹ID,返回包裹类,包裹明细,包裹实重,包裹体积重6000\n",
|
||||
" \"\"\"\n",
|
||||
" packages = Package_group()\n",
|
||||
" packages_str = \"\"\n",
|
||||
" packages_weight=0\n",
|
||||
" packages_vol = 0\n",
|
||||
" for package_id in packages_id:\n",
|
||||
" url = f'https://cp.maso.hk/index.php?main=biphp&act=package_fund&key=W6BOYJ7BH27YCGRFCA0LWBVKMU1KRU5Q&package={package_id}'\n",
|
||||
" resp = requests.get(url).json()\n",
|
||||
" if resp['code'] == \"0\":\n",
|
||||
" weight = int(float(resp['data'][0]['weight'])*1000)\n",
|
||||
" package_length = resp['data'][0]['l'].replace(\",\",\"\") if len(resp['data'][0]['l'])>0 else \"0\"\n",
|
||||
" package_width = resp['data'][0]['w'].replace(\",\",\"\") if len(resp['data'][0]['w'])>0 else \"0\"\n",
|
||||
" package_hight = resp['data'][0]['h'].replace(\",\",\"\") if len(resp['data'][0]['h'])>0 else \"0\"\n",
|
||||
" package = Package(str(package_id),float(package_length),float(package_width),float(package_hight),weight)\n",
|
||||
" packages.add_package(package)\n",
|
||||
" # 实际包裹尺寸\n",
|
||||
" package_str = f\"{weight}|{package_length}*{package_width}*{package_hight}\"\n",
|
||||
" packages_str += package_str + \",\"\n",
|
||||
" return packages,packages_str\n",
|
||||
"\n",
|
||||
"def get_shiji_bill(opCountry,order_id,packages_id,postcode,convey,amount):\n",
|
||||
" # print(order_id)\n",
|
||||
" beizhu = amount\n",
|
||||
" conveys = 1 if convey == \"海运\" else 0\n",
|
||||
" try:\n",
|
||||
" packages,packages_str = get_package_real_vol_by_api(packages_id=packages_id)\n",
|
||||
" bill = Billing(str(order_id),opCountry,packages,postcode,company_name=None,head_type=conveys,beizhu=beizhu)\n",
|
||||
" print(bill)\n",
|
||||
" tail_amount = bill.bill_dict()[\"预测尾端\"]\n",
|
||||
" total_weight = sum([p.weight/1000 for p in packages.packages])\n",
|
||||
" _type = bill.bill_dict()[\"尾端渠道\"]\n",
|
||||
" head_amount = bill.bill_dict()[\"预测头程CNY\"]\n",
|
||||
" total_amount = bill.bill_dict()[\"总金额USD\"]\n",
|
||||
" volume_weight = bill.bill_dict()[\"体积重\"]\n",
|
||||
" per_head = bill.bill_dict()[\"头程单价\"]\n",
|
||||
" other_detail = bill.get_other_fee()\n",
|
||||
" return packages_str,total_weight,volume_weight,per_head,head_amount,tail_amount,other_detail,_type,total_amount\n",
|
||||
" except ZeroDivisionError as e:\n",
|
||||
" print(e)\n",
|
||||
" return \"\",0,0,0,0,0,\"\",\"\",0\n",
|
||||
"def get_package_info_by_sql(order_df):\n",
|
||||
" packages = Package_group()\n",
|
||||
" packages_str = \"\"\n",
|
||||
" for index, row in order_df.iterrows():\n",
|
||||
" if row['包裹数据'] is None:\n",
|
||||
" return None,\"\"\n",
|
||||
" row['包裹数据'] = json.loads(row['包裹数据'])\n",
|
||||
" item_list = [\n",
|
||||
" {key: float(re.search(r\"\\d+\\.?\\d*\", str(value)).group()) for key, value in package.items()}\n",
|
||||
" for package in row['包裹数据'].values()\n",
|
||||
"]\n",
|
||||
" for item in item_list:\n",
|
||||
" if item['长'] == 0 or item['宽'] == 0 or item['高'] == 0 or item['重量'] == 0:\n",
|
||||
" return None,\"\"\n",
|
||||
" package = Package(row['SKU'], item['长'], item['宽'], item['高'], item['重量'])\n",
|
||||
" packages.add_package(package)\n",
|
||||
" # 实际包裹尺寸\n",
|
||||
" package_str = f\"{ item['重量']}|{item['长']}*{ item['宽']}*{item['高']}\"\n",
|
||||
" packages_str += package_str + \",\"\n",
|
||||
" return packages,packages_str\n",
|
||||
"\n",
|
||||
"def get_biaozhun_bill(country,order_id,postcode,convey,amount):\n",
|
||||
" print(order_id)\n",
|
||||
" # 取标准包裹数据\n",
|
||||
" sql = \"\"\"SELECT\n",
|
||||
" order_id,\n",
|
||||
" opl.SKU,\n",
|
||||
" 包裹数据\n",
|
||||
" FROM\n",
|
||||
" dws.order_product_list opl\n",
|
||||
" LEFT JOIN ads.new_erp_sku_size spi ON opl.SKU =spi.SKU\n",
|
||||
" WHERE\n",
|
||||
" order_id = %s\n",
|
||||
" AND\n",
|
||||
" opl.order_product_id REGEXP \"[0-9]{15}_[0-9]*$\"\n",
|
||||
" \"\"\"\n",
|
||||
" order_package_date = pd.read_sql(sql, engine, params=(order_id,))\n",
|
||||
" print(order_package_date['SKU'])\n",
|
||||
" if order_package_date['包裹数据'] is None:\n",
|
||||
" return \"\",0,0,0,0,0,\"\",\"\"\n",
|
||||
" # 取账单数据\n",
|
||||
" beizhu = amount\n",
|
||||
" conveys = 1 if convey == \"海运\" else 0\n",
|
||||
" opCountry = OperateCountry(country)\n",
|
||||
" try:\n",
|
||||
" packages,packages_str = get_package_info_by_sql(order_package_date)\n",
|
||||
" if packages is None:\n",
|
||||
" return \"\",0,0,0,0,0,\"\",\"\"\n",
|
||||
" bill = Billing(str(order_id),opCountry,packages,postcode,company_name=None,head_type=conveys,beizhu=beizhu)\n",
|
||||
" print(bill)\n",
|
||||
" tail_amount = bill.bill_dict()[\"预测尾端\"]\n",
|
||||
" total_weight = sum([p.weight/1000 for p in packages.packages])\n",
|
||||
" _type = bill.bill_dict()[\"尾端渠道\"]\n",
|
||||
" head_amount = bill.bill_dict()[\"预测头程CNY\"]\n",
|
||||
" total_amount = bill.bill_dict()[\"总金额USD\"]\n",
|
||||
" volume_weight = bill.bill_dict()[\"体积重\"]\n",
|
||||
" per_head = bill.bill_dict()[\"头程单价\"]\n",
|
||||
" other_detail = bill.get_other_fee()\n",
|
||||
" return packages_str,total_weight,volume_weight,per_head,head_amount,tail_amount,other_detail,_type\n",
|
||||
" except ZeroDivisionError as e:\n",
|
||||
" print(e)\n",
|
||||
" return \"\",0,0,0,0,0,\"\",\"\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"sql = \"\"\"SELECT\n",
|
||||
" ol.order_id ,\n",
|
||||
" CONCAT(\"[\",GROUP_CONCAT(pr.包裹号),\"]\") AS package_group ,\n",
|
||||
" ol.postcode,\n",
|
||||
" ol.delivery_country,\n",
|
||||
" ol.convey,\n",
|
||||
"\t\t\t\tols.`货币`,\n",
|
||||
"\t\t\t\tols.`订单总额`,\n",
|
||||
"\t\t\t\tols.支付方式,\n",
|
||||
" ol.order_price_dollar\n",
|
||||
" FROM\n",
|
||||
" parcel pr\n",
|
||||
" LEFT JOIN dwd.order_list ol ON ol.order_id = pr.订单号 \n",
|
||||
"\t\t\t\tleft JOIN ods.order_list_supplement ols ON ol.order_id = ols.订单号\n",
|
||||
" WHERE\n",
|
||||
" NOT EXISTS (\n",
|
||||
" SELECT\n",
|
||||
" 1 \n",
|
||||
" FROM\n",
|
||||
" `order_express` oe \n",
|
||||
" WHERE\n",
|
||||
" oe.包裹号 = pr.包裹号 \n",
|
||||
" AND oe.包裹状态 = \"已作废\")\n",
|
||||
" AND order_id IN (250610233027170 ,\n",
|
||||
"250608163427468 ,\n",
|
||||
"250521222214528 ,\n",
|
||||
"250610230415406 ,\n",
|
||||
"250607044202071 ,\n",
|
||||
"250607144614471 ,\n",
|
||||
"250607211414736 ,\n",
|
||||
"250401032025780 ,\n",
|
||||
"250531061202721 ,\n",
|
||||
"250611112802162 ,\n",
|
||||
"250614114015175 ,\n",
|
||||
"250610185002525 ,\n",
|
||||
"250610022439644 ,\n",
|
||||
"250404163802384 ,\n",
|
||||
"250531191002625 ,\n",
|
||||
"250610035602297 ,\n",
|
||||
"250609232002941 \n",
|
||||
"\n",
|
||||
")\n",
|
||||
" GROUP BY ol.order_id\n",
|
||||
"\"\"\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"250401032025780\n",
|
||||
"0 2205003721\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250401032025780\n",
|
||||
"账单项:head_amount: 22.307399999999998 CNY base: 7.17 USD residential_delivery: 2.08 USD fuel: 1.66 USD tail_amount: 10.91 USD 总金额: 14.033036 USD\n",
|
||||
"250404163802384\n",
|
||||
"0 2205811679\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250404163802384\n",
|
||||
"账单项:head_amount: 79.78666666666666 CNY base: 11.9 EUR bigpackage: 44.5 EUR fuel: 7.33 EUR tail_amount: 63.73 EUR 总金额: 82.54773333333334 USD\n",
|
||||
"250521222214528\n",
|
||||
"0 2202859120\n",
|
||||
"1 2202859129\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250521222214528\n",
|
||||
"账单项:head_amount: 2470.0 CNY base: 7.4 GBP tail_amount: 7.4 GBP 总金额: 355.42 USD\n",
|
||||
"250531061202721\n",
|
||||
"0 227752959\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250531061202721\n",
|
||||
"账单项:head_amount: 109.83735000000001 CNY base: 21.9 USD oversize: 8.17 USD remote: 2.17 USD residential_delivery: 2.08 USD fuel: 6.18 USD tail_amount: 40.5 USD 总金额: 55.877229 USD\n",
|
||||
"250531191002625\n",
|
||||
"0 2205993938\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250531191002625\n",
|
||||
"账单项:head_amount: 72.83966666666667 CNY base: 11.9 EUR fuel: 1.55 EUR tail_amount: 13.45 EUR 总金额: 25.261553333333335 USD\n",
|
||||
"250607044202071\n",
|
||||
"0 228294469\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250607044202071\n",
|
||||
"账单项:head_amount: 168.73212180000002 CNY base: 20 USD transfer: 9.37 USD tail_amount: 29.37 USD 总金额: 52.992497052000004 USD\n",
|
||||
"250607144614471\n",
|
||||
"0 227555929\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250607144614471\n",
|
||||
"账单项:head_amount: 37.5 CNY base: 8.18 USD residential_delivery: 2.08 USD fuel: 1.85 USD tail_amount: 12.11 USD 总金额: 17.36 USD\n",
|
||||
"250607211414736\n",
|
||||
"0 229586263\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250607211414736\n",
|
||||
"账单项:head_amount: 47.25 CNY base: 7.77 USD residential_delivery: 2.08 USD fuel: 1.77 USD tail_amount: 11.62 USD 总金额: 18.235 USD\n",
|
||||
"250608163427468\n",
|
||||
"0 227658471\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250608163427468\n",
|
||||
"账单项:head_amount: 554.4 CNY base: 3.7 GBP tail_amount: 3.7 GBP 总金额: 82.426 USD\n",
|
||||
"250609232002941\n",
|
||||
"0 2206109629\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250609232002941\n",
|
||||
"账单项:head_amount: 94.5 CNY base: 10.2 EUR fuel: 1.33 EUR tail_amount: 11.53 EUR 总金额: 26.1436 USD\n",
|
||||
"250610022439644\n",
|
||||
"0 2206929691\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250610022439644\n",
|
||||
"账单项:head_amount: 280.0 CNY base: 11.9 EUR overweight: 2.89 EUR fuel: 1.92 EUR tail_amount: 16.71 EUR 总金额: 57.915200000000006 USD\n",
|
||||
"250610035602297\n",
|
||||
"0 2204180587\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250610035602297\n",
|
||||
"账单项:head_amount: 177.072 CNY base: 20.4 EUR overweight: 2.89 EUR bigpackage: 44.5 EUR fuel: 8.81 EUR tail_amount: 76.6 EUR 总金额: 110.58208 USD\n",
|
||||
"250610185002525\n",
|
||||
"0 2203543601\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250610185002525\n",
|
||||
"账单项:head_amount: 95.33333333333333 CNY base: 11.9 EUR overweight: 2.89 EUR bigpackage: 44.5 EUR fuel: 7.71 EUR tail_amount: 67.0 EUR 总金额: 88.38666666666667 USD\n",
|
||||
"250610230415406\n",
|
||||
"0 2205149996\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250610230415406\n",
|
||||
"账单项:head_amount: 735.0 CNY base: 32.25 USD oversize: 11.83 USD remote: 2.17 USD residential_delivery: 2.08 USD fuel: 8.7 USD tail_amount: 57.02 USD 总金额: 159.92000000000002 USD\n",
|
||||
"250610233027170\n",
|
||||
"0 229445257\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250610233027170\n",
|
||||
"账单项:head_amount: 1645.6533333333334 CNY base: 3.7 GBP tail_amount: 3.7 GBP 总金额: 235.2014666666667 USD\n",
|
||||
"250611112802162\n",
|
||||
"0 2206393292\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250611112802162\n",
|
||||
"账单项:head_amount: 49.6 CNY base: 6.02 AUD fuel: 0.31 AUD tail_amount: 6.33 AUD 总金额: 11.375 USD\n",
|
||||
"250614114015175\n",
|
||||
"0 2202252863\n",
|
||||
"Name: SKU, dtype: int64\n",
|
||||
"账单名称: 250614114015175\n",
|
||||
"账单项:head_amount: 270.9 CNY base: 17.08 AUD fuel: 0.87 AUD tail_amount: 17.95 AUD 总金额: 50.491 USD\n",
|
||||
"账单名称: 250401032025780\n",
|
||||
"账单项:head_amount: 121.93650000000001 CNY base: 20 USD transfer: 6.77 USD tail_amount: 26.77 USD 总金额: 43.84111 USD\n",
|
||||
"账单名称: 250404163802384\n",
|
||||
"账单项:head_amount: 176.11733333333333 CNY base: 111.67 EUR notify: 5 EUR responsibility: 1.85 EUR management: 2 EUR toll: 0.72 EUR tail_amount: 121.24 EUR 总金额: 160.44522666666668 USD\n",
|
||||
"账单名称: 250521222214528\n",
|
||||
"账单项:head_amount: 14967.906666666668 CNY base: 55.0 GBP tail_amount: 55.0 GBP 总金额: 2167.0069333333336 USD\n",
|
||||
"账单名称: 250531061202721\n",
|
||||
"账单项:head_amount: 694.17 CNY base: 210 USD tail_amount: 210.0 USD 总金额: 307.1838 USD\n",
|
||||
"账单名称: 250531191002625\n",
|
||||
"账单项:head_amount: 381.6 CNY base: 11.9 EUR overweight: 2.89 EUR bigpackage: 44.5 EUR fuel: 7.71 EUR tail_amount: 67.0 EUR 总金额: 128.464 USD\n",
|
||||
"账单名称: 250607044202071\n",
|
||||
"账单项:head_amount: 1059.24 CNY base: 75 USD transfer: 58.85 USD tail_amount: 133.85 USD 总金额: 282.1436 USD\n",
|
||||
"账单名称: 250607144614471\n",
|
||||
"账单项:head_amount: 533.5740000000001 CNY base: 53.85 USD oversize: 8.17 USD residential_delivery: 4.16 USD fuel: 11.91 USD tail_amount: 78.09 USD 总金额: 152.79036000000002 USD\n",
|
||||
"账单名称: 250607211414736\n",
|
||||
"账单项:head_amount: 655.1999999999999 CNY base: 59.17 USD oversize: 8.17 USD residential_delivery: 4.16 USD fuel: 12.87 USD tail_amount: 84.38 USD 总金额: 176.108 USD\n",
|
||||
"账单名称: 250608163427468\n",
|
||||
"账单项:head_amount: 2968.0 CNY base: 48.02 GBP fuel: 4.8 GBP tail_amount: 52.82 GBP 总金额: 484.18600000000004 USD\n",
|
||||
"账单名称: 250609232002941\n",
|
||||
"账单项:head_amount: 351.488 CNY base: 111.41 EUR notify: 5 EUR responsibility: 1.85 EUR management: 2 EUR toll: 1.2 EUR tail_amount: 121.46 EUR 总金额: 185.24352000000002 USD\n",
|
||||
"账单名称: 250610022439644\n",
|
||||
"账单项:head_amount: 3410.88 CNY base: 272.46 EUR notify: 5 EUR responsibility: 1.85 EUR management: 2 EUR toll: 2.16 EUR tail_amount: 283.47 EUR 总金额: 795.0096000000001 USD\n",
|
||||
"账单名称: 250610035602297\n",
|
||||
"账单项:head_amount: 1189.824 CNY base: 310 EUR fuel: 24.8 EUR tail_amount: 334.8 EUR 总金额: 541.55136 USD\n",
|
||||
"账单名称: 250610185002525\n",
|
||||
"账单项:head_amount: 119.25333333333333 CNY base: 136.7 EUR notify: 5 EUR responsibility: 1.85 EUR management: 2 EUR toll: 0.96 EUR tail_amount: 146.51 EUR 总金额: 180.7866666666667 USD\n",
|
||||
"账单名称: 250610230415406\n",
|
||||
"账单项:head_amount: 1500.9866666666667 CNY base: 39.39 USD remote: 6.2 USD big_package: 47.73 USD residential_delivery: 5.95 USD fuel: 17.87 USD tail_amount: 117.14 USD 总金额: 327.27813333333336 USD\n",
|
||||
"账单名称: 250610233027170\n",
|
||||
"账单项:head_amount: 4055.04 CNY base: 76.65 GBP fuel: 7.66 GBP tail_amount: 84.31 GBP 总金额: 677.3086000000001 USD\n",
|
||||
"账单名称: 250611112802162\n",
|
||||
"账单项:head_amount: 793.8000000000001 CNY base: 26.82 AUD fuel: 1.94 AUD tail_amount: 28.76 AUD 总金额: 131.264 USD\n",
|
||||
"账单名称: 250614114015175\n",
|
||||
"账单项:head_amount: 1276.5200000000002 CNY base: 71.19 AUD oversize: 15.5 AUD fuel: 6.29 AUD tail_amount: 92.98 AUD 总金额: 243.79880000000003 USD\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"\n",
|
||||
"order_id_df = pd.read_sql(sql,engine)\n",
|
||||
"# SKU类型和SKU明细,以及入库采购价\n",
|
||||
"order_id_df[['订单SKU类型','订单SKU明细','入库采购价']] = order_id_df.apply(lambda x: get_SKU_info(x['order_id']), axis=1, result_type='expand')\n",
|
||||
"\n",
|
||||
"# 获取标准账单\n",
|
||||
"order_id_df[[\"标准包裹尺寸详情\",\"标准实重\",\"标准体积重\",\"标准头程单价\",\"标准头程费用(CNY)\",\"标准尾端费用(当地货币)\",\"标准尾端附加费\",\"标准尾端渠道\"]] = order_id_df.apply(lambda x: get_biaozhun_bill(\n",
|
||||
" x['delivery_country'],\n",
|
||||
" x['order_id'],\n",
|
||||
" x['postcode'], \n",
|
||||
" x['convey'],\n",
|
||||
" x['order_price_dollar']),axis=1,result_type='expand')\n",
|
||||
"# 获取实际采购价\n",
|
||||
"# order_id_df['实际采购价'] = order_id_df['order_id'].apply(lambda x: get_purchase_price(x))\n",
|
||||
"\n",
|
||||
"# 获取实际账单\n",
|
||||
"order_id_df[[\"实际包裹尺寸详情\",\"实际实重\",\"实际体积重\",\"实际头程单价\",\"实际头程费用(CNY)\",\"实际尾端费用(当地货币)\",\"实际尾端附加费\",\"实际尾端渠道\",\"总金额(USD)\"]] = order_id_df.apply(lambda x: get_shiji_bill(OperateCountry(x['delivery_country']),\n",
|
||||
" x['order_id'],\n",
|
||||
" eval(x['package_group']),\n",
|
||||
" x['postcode'], \n",
|
||||
" x['convey'],\n",
|
||||
" x['order_price_dollar']),axis=1,result_type='expand')\n",
|
||||
"\n",
|
||||
"order_id_df.to_clipboard()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 取订单号和包裹号\n",
|
||||
"sql = \"\"\"\n",
|
||||
"SELECT\n",
|
||||
" pr.包裹号,pr.订单号\n",
|
||||
" FROM\n",
|
||||
" parcel pr \t\n",
|
||||
" WHERE\n",
|
||||
" 订单号 IN (250516031202445 \n",
|
||||
")\n",
|
||||
"\"\"\"\n",
|
||||
"package_df = pd.read_sql(sql,engine)\n",
|
||||
"package_df.to_clipboard(index=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from utils.gtools import MySQLconnect\n",
|
||||
"import pandas as pd\n",
|
||||
"import os\n",
|
||||
"# date = \"2025/3/5\"\n",
|
||||
"# 连接数据库\n",
|
||||
"ods = MySQLconnect(\"ods\")\n",
|
||||
"engine = ods.engine()\n",
|
||||
"\n",
|
||||
"# 读取 Excel 数据\n",
|
||||
"excel_path = r\"D:\\test\\logistics\\拦截数据\\拦截订单登记明细.xlsx\"\n",
|
||||
"order_df = pd.read_excel(excel_path, sheet_name=\"拦截明细\", skiprows=1)\n",
|
||||
"# 读取 Excel 数据时,指定 '订单号' 列为字符串类型\n",
|
||||
"order_df = pd.read_excel(excel_path, sheet_name=\"拦截明细\", skiprows=1, dtype={'订单号': str})\n",
|
||||
"# order_df = order_df[order_df['拦截计算日期'] == date]\n",
|
||||
"order_df = order_df[['订单号', '提交拦截申请时间']]\n",
|
||||
"\n",
|
||||
"# 提取订单号并转为字符串\n",
|
||||
"order_id = [str(x) for x in order_df[\"订单号\"].dropna()]\n",
|
||||
"\n",
|
||||
"if not order_id:\n",
|
||||
" print(\"没有符合条件的订单号,查询终止。\")\n",
|
||||
"else:\n",
|
||||
" try:\n",
|
||||
" # 生成占位符并执行查询\n",
|
||||
" placeholders = ', '.join(['%s'] * len(order_id))\n",
|
||||
" sql = f\"\"\"\n",
|
||||
" SELECT\n",
|
||||
" 单号,\n",
|
||||
" 包裹号,\n",
|
||||
" 包裹状态,\n",
|
||||
" 快递跟踪号,\n",
|
||||
" `关联卡板号`,\n",
|
||||
" 关联提单号,\n",
|
||||
" 打包时间,\n",
|
||||
" `快递公司` AS 打单渠道,\n",
|
||||
" `包裹测量时间`,\n",
|
||||
" 运单生成时间 AS 打单时间,\n",
|
||||
" 绑定卡板时间,\n",
|
||||
" 绑定提单时间,\n",
|
||||
" `提单发货时间`\n",
|
||||
" FROM\n",
|
||||
" ods.`order_express`\n",
|
||||
" WHERE\n",
|
||||
" 单号 IN ({placeholders})\n",
|
||||
" \"\"\"\n",
|
||||
" df = pd.read_sql(sql, engine, params=tuple(order_id))\n",
|
||||
"\n",
|
||||
" # 合并数据\n",
|
||||
" order_df[\"订单号\"] = order_df[\"订单号\"].astype(str)\n",
|
||||
" df[\"单号\"] = df[\"单号\"].astype(str)\n",
|
||||
" merged_df = df.merge(order_df, left_on=\"单号\", right_on=\"订单号\", how=\"left\")\n",
|
||||
" merged_df = merged_df.drop(columns=[\"订单号\"]) # 删除重复的 '订单号' 列\n",
|
||||
"\n",
|
||||
" # 重新排列列顺序\n",
|
||||
" merged_df = merged_df[[\n",
|
||||
" \"单号\",\n",
|
||||
" \"包裹号\",\n",
|
||||
" \"提交拦截申请时间\",\n",
|
||||
" \"包裹状态\",\n",
|
||||
" \"快递跟踪号\",\n",
|
||||
" \"关联卡板号\",\n",
|
||||
" \"关联提单号\",\n",
|
||||
" \"打包时间\",\n",
|
||||
" \"包裹测量时间\",\n",
|
||||
" \"打单时间\",\n",
|
||||
" \"打单渠道\",\n",
|
||||
" \"绑定卡板时间\",\n",
|
||||
" \"绑定提单时间\",\n",
|
||||
" \"提单发货时间\"\n",
|
||||
" ]]\n",
|
||||
"\n",
|
||||
" # 检查文件是否存在,并读取目标工作表\n",
|
||||
" if os.path.exists(excel_path):\n",
|
||||
" with pd.ExcelFile(excel_path) as xls:\n",
|
||||
" sheets = xls.sheet_names\n",
|
||||
" if \"包裹时间记录\" in sheets:\n",
|
||||
" existing_df = pd.read_excel(excel_path, sheet_name=\"包裹时间记录\")\n",
|
||||
" else:\n",
|
||||
" existing_df = pd.DataFrame(columns=merged_df.columns) # 如果表不存在,创建一个空 DataFrame\n",
|
||||
" else:\n",
|
||||
" existing_df = pd.DataFrame(columns=merged_df.columns) # 如果文件不存在,创建一个空 DataFrame\n",
|
||||
"\n",
|
||||
" # 过滤掉已存在的包裹号\n",
|
||||
" existing_package_ids = existing_df[\"包裹号\"].astype(str).tolist()\n",
|
||||
" new_data = merged_df[~merged_df[\"包裹号\"].astype(str).isin(existing_package_ids)]\n",
|
||||
"\n",
|
||||
" # 如果没有新数据,直接退出\n",
|
||||
" if new_data.empty:\n",
|
||||
" print(\"没有新数据需要追加。\")\n",
|
||||
" else:\n",
|
||||
" # 追加新数据\n",
|
||||
" \n",
|
||||
" combined_df = pd.concat([existing_df, new_data], ignore_index=True)\n",
|
||||
"\n",
|
||||
" # 写入 Excel(保留其他工作表)\n",
|
||||
" with pd.ExcelWriter(excel_path, engine='openpyxl', mode='a' if os.path.exists(excel_path) else 'w', if_sheet_exists='replace') as writer:\n",
|
||||
" combined_df.to_excel(writer, sheet_name=\"包裹时间记录\", index=False)\n",
|
||||
" print(\"数据已成功追加到工作表!\")\n",
|
||||
" \n",
|
||||
" except Exception as e:\n",
|
||||
" print(f\"操作失败,错误详情:{e}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# 手动输入包裹的包裹信息\n",
|
||||
"from logisticsClass.logisticsTail_EUR import KPASLLogistics_FR\n",
|
||||
"from utils.Package import Package,Package_group\n",
|
||||
"from utils.gtools import MySQLconnect\n",
|
||||
"from utils.countryOperator import OperateCountry\n",
|
||||
"from utils.logisticsBill import BillFactory, Billing\n",
|
||||
"packages = Package_group()\n",
|
||||
"\n",
|
||||
"packages.add_package(Package(\"包裹1\", 78,35,15, 15000))\n",
|
||||
"# packages.add_package(Package(\"包裹1\", 55,45,40, 9000))\n",
|
||||
"# packages.add_package(Package(\"包裹1\", 210,55,20, 32000))\n",
|
||||
"# packages.add_package(Package(\"包裹1\", 103,42,12, 8000))\n",
|
||||
"# packages.add_package(Package(\"包裹1\", 103,42,12, 8000))\n",
|
||||
"\n",
|
||||
"country = 'United Kingdom'\n",
|
||||
"postcode ='DY13 0RX'\n",
|
||||
"conveys= 0 # 1海运0空运\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"opCountry = OperateCountry(country)\n",
|
||||
"bill = Billing(str(1),opCountry,packages,postcode,company_name=None,head_type=conveys,beizhu=\"1\")\n",
|
||||
"print(bill)\n",
|
||||
"tail_amount = bill.bill_dict()[\"预测尾端\"]\n",
|
||||
"total_weight = sum([p.weight/1000 for p in packages.packages])\n",
|
||||
"_type = bill.bill_dict()[\"尾端渠道\"]\n",
|
||||
"head_amount = bill.bill_dict()[\"预测头程CNY\"]\n",
|
||||
"total_amount = bill.bill_dict()[\"总金额USD\"]\n",
|
||||
"volume_weight = bill.bill_dict()[\"体积重\"]\n",
|
||||
"per_head = bill.bill_dict()[\"头程单价\"]\n",
|
||||
"other_detail = bill.get_other_fee()\n",
|
||||
"\n",
|
||||
"packages_str = \"\"\n",
|
||||
"for package in packages.packages:\n",
|
||||
" package_str = f\"{package.weight}|{package.length}*{package.width}*{package.height}\"\n",
|
||||
" packages_str += package_str + \",\"\n",
|
||||
"print(f\"packages_str:{packages_str}\")\n",
|
||||
"print(f\"实重:{total_weight}\")\n",
|
||||
"print(f\"体积重:{volume_weight}\")\n",
|
||||
"print(f\"头程单价:{per_head}\")\n",
|
||||
"print(f\"预测头程CNY:{head_amount}\")\n",
|
||||
"print(f\"预测尾端(当地货币):{tail_amount}\")\n",
|
||||
"print(f\"其他费用:{other_detail}\")\n",
|
||||
"print(f\"尾端渠道:{_type}\")\n",
|
||||
"print(f\"总金额USD:{total_amount}\")\n",
|
||||
"print(KPASLLogistics_FR.active)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"查找供应商退货退款金额"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"from utils.gtools import MySQLconnect\n",
|
||||
"ods = MySQLconnect('ods')\n",
|
||||
"engine = ods.engine()\n",
|
||||
"\n",
|
||||
"# 读取 Excel 数据\n",
|
||||
"excel_path = r\"D:\\test\\logistics\\拦截数据\\拦截订单登记明细.xlsx\"\n",
|
||||
"order_df = pd.read_excel(excel_path, sheet_name=\"拦截明细\", skiprows=1)\n",
|
||||
"# 读取 Excel 数据时,指定 '订单号' 列为字符串类型\n",
|
||||
"order_df = pd.read_excel(excel_path, sheet_name=\"拦截明细\", skiprows=1, dtype={'订单号': str})\n",
|
||||
"\n",
|
||||
"# 提取订单号并转为字符串\n",
|
||||
"order_id = [str(x) for x in order_df[\"订单号\"].dropna()]\n",
|
||||
"placeholders = ','.join(['%s'] * len(order_id))\n",
|
||||
"sql = f\"\"\"\n",
|
||||
"WITH\n",
|
||||
"t1 AS (\n",
|
||||
"SELECT\n",
|
||||
" -- 提取交易号\n",
|
||||
" TRIM(SUBSTRING(\n",
|
||||
" `外部关联信息`,\n",
|
||||
" LOCATE('交易号:', `外部关联信息`) + CHAR_LENGTH('交易号:'),\n",
|
||||
" CASE \n",
|
||||
" WHEN LOCATE('单品号:', `外部关联信息`) > 0\n",
|
||||
" THEN LOCATE('单品号:', `外部关联信息`) - LOCATE('交易号:', `外部关联信息`) - CHAR_LENGTH('交易号:')\n",
|
||||
" ELSE LOCATE('[', `外部关联信息`) - LOCATE('交易号:', `外部关联信息`) - CHAR_LENGTH('交易号:')\n",
|
||||
" END\n",
|
||||
" )) AS 交易号,\n",
|
||||
"\n",
|
||||
" -- 提取单品号(如果存在)\n",
|
||||
" CASE \n",
|
||||
" WHEN LOCATE('单品号:', `外部关联信息`) > 0 THEN\n",
|
||||
" TRIM(SUBSTRING(\n",
|
||||
" `外部关联信息`,\n",
|
||||
" LOCATE('单品号:', `外部关联信息`) + CHAR_LENGTH('单品号:'),\n",
|
||||
" LOCATE('[', `外部关联信息`) - LOCATE('单品号:', `外部关联信息`) - CHAR_LENGTH('单品号:')\n",
|
||||
" ))\n",
|
||||
" ELSE NULL\n",
|
||||
" END AS 单品号,\n",
|
||||
"\n",
|
||||
" `支付账号`,\n",
|
||||
" `处理金额`,\n",
|
||||
" `添加时间`,\n",
|
||||
" `资金状态`,\n",
|
||||
" `完成时间`,\n",
|
||||
" 备注\n",
|
||||
"\n",
|
||||
"FROM cpmaso_procurement_fund_manage\n",
|
||||
"WHERE `添加时间` >= DATE_SUB(NOW(), INTERVAL 8 MONTH)\n",
|
||||
")\n",
|
||||
"SELECT\n",
|
||||
"LEFT(order_product_id,15) AS 订单号,\n",
|
||||
"\torder_product_id,\n",
|
||||
"\tbuy_audit AS 状态,\n",
|
||||
"\ttrans_pay_date AS 采购时间,\n",
|
||||
"\tlog_receiving_date AS 到货时间,\n",
|
||||
"\tt1.*\n",
|
||||
"FROM\n",
|
||||
"\t`warehouse_purchasing` wp\n",
|
||||
"LEFT JOIN t1 ON wp.trans_id = t1.交易号\n",
|
||||
"WHERE\n",
|
||||
"交易号>0 AND\n",
|
||||
"LEFT(wp.order_product_id,15) IN ({placeholders})\n",
|
||||
"ORDER BY 添加时间,订单号\n",
|
||||
"\"\"\"\n",
|
||||
"df = pd.read_sql(sql, engine,params=tuple(order_id))\n",
|
||||
"df.to_clipboard(index = False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"入库\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd\n",
|
||||
"import numpy as np\n",
|
||||
"from sqlalchemy import text\n",
|
||||
"from utils.gtools import MySQLconnect\n",
|
||||
"ods = MySQLconnect('ods')\n",
|
||||
"engine = ods.engine()\n",
|
||||
"\n",
|
||||
"# 读取 Excel 数据\n",
|
||||
"excel_path = r\"D:\\test\\logistics\\拦截数据\\拦截订单登记明细.xlsx\"\n",
|
||||
"# 读取 Excel 数据时,指定 '订单号' 列为字符串类型\n",
|
||||
"order_df = pd.read_excel(excel_path, sheet_name=\"拦截明细\", skiprows=1, dtype={'订单号': str})\n",
|
||||
"\n",
|
||||
"# 提取订单号并转为字符串\n",
|
||||
"order_id = df['订单号'].astype(str).tolist()\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"bind_names = [f\":p{i}\" for i in range(len(order_id))]\n",
|
||||
"placeholders = ', '.join(bind_names)\n",
|
||||
"\n",
|
||||
"sql = text(f\"\"\"\n",
|
||||
"SELECT\n",
|
||||
"REGEXP_SUBSTR(note, '[0-9]{{15}}') AS 订单号,\n",
|
||||
"stock_in_id AS `入库单号`,\n",
|
||||
"instock_date AS `入库时间`\n",
|
||||
"FROM stockinid_list\n",
|
||||
"WHERE note REGEXP '[0-9]{{15}}_'\n",
|
||||
"AND REGEXP_SUBSTR(note, '[0-9]{{15}}') IN ({placeholders})\n",
|
||||
"\"\"\")\n",
|
||||
"\n",
|
||||
"params = {f\"p{i}\": v for i, v in enumerate(order_id)}\n",
|
||||
"df = pd.read_sql(sql, engine, params=params)\n",
|
||||
"df.to_clipboard(index = False)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "base",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,253 @@
|
|||
|
||||
import os
|
||||
import pandas as pd
|
||||
from utils.Package import Package,Package_group
|
||||
from utils.gtools import MySQLconnect
|
||||
from utils.countryOperator import OperateCountry
|
||||
from utils.logisticsBill import BillFactory, Billing
|
||||
import requests
|
||||
|
||||
ods = MySQLconnect("ods")
|
||||
engine = ods.engine()
|
||||
cursor = ods.connect().cursor()
|
||||
|
||||
def get_package_real_vol_by_api(packages_id):
|
||||
packages = Package_group()
|
||||
packages_str = ""
|
||||
index = 1
|
||||
for package_id in packages_id:
|
||||
# 计算往年利润率
|
||||
# sql = f"SELECT length,width,hight,weight FROM package_vol_info WHERE package = %s"
|
||||
# cursor.execute(sql, (package_id,))
|
||||
# resp = cursor.fetchall()
|
||||
# if len(resp) == 0:
|
||||
# continue
|
||||
# else:
|
||||
# weight = resp[0][3]
|
||||
# package_length = resp[0][0]
|
||||
# package_width = resp[0][1]
|
||||
# package_hight = resp[0][2]
|
||||
# 拦截
|
||||
url = f'https://cp.maso.hk/index.php?main=biphp&act=package_fund&key=W6BOYJ7BH27YCGRFCA0LWBVKMU1KRU5Q&package={package_id}'
|
||||
resp = requests.get(url).json()
|
||||
if resp['code'] == "0":
|
||||
weight = int(float(resp['data'][0]['weight'])*1000)
|
||||
package_length = resp['data'][0]['l'].replace(",","") if len(resp['data'][0]['l'])>0 else "0"
|
||||
package_width = resp['data'][0]['w'].replace(",","") if len(resp['data'][0]['w'])>0 else "0"
|
||||
package_hight = resp['data'][0]['h'].replace(",","") if len(resp['data'][0]['h'])>0 else "0"
|
||||
package_str = f"{weight/1000}|{package_length}*{package_width}*{package_hight}"
|
||||
packages_str += package_str + ","
|
||||
index += 1
|
||||
package = Package(str(package_id),float(package_length),float(package_width),float(package_hight),weight)
|
||||
packages.add_package(package)
|
||||
return packages,packages_str
|
||||
|
||||
def get_order_bill(opCountry,order_id,packages_id,postcode,convey,amount):
|
||||
print(order_id)
|
||||
beizhu = amount
|
||||
conveys = 1 if convey == "海运" else 0
|
||||
|
||||
try:
|
||||
|
||||
packages,packages_str = get_package_real_vol_by_api(packages_id=packages_id)
|
||||
bill = Billing(str(order_id),opCountry,packages,postcode,company_name=None,head_type=conveys,beizhu=beizhu)
|
||||
print(bill)
|
||||
bill_data = bill.bill_dict() # ✅ 只调用一次
|
||||
|
||||
result = bill_data.get("预测尾端", 0)
|
||||
_type = bill_data.get("尾端渠道", "")
|
||||
head_amount = bill_data.get("预测头程CNY", 0)
|
||||
total_amount = bill_data.get("总金额USD", 0)
|
||||
volume_weight = bill_data.get("体积重", 0)
|
||||
per_head = bill_data.get("头程单价", 0)
|
||||
return volume_weight,_type,per_head,head_amount,result,total_amount,packages_str
|
||||
except ZeroDivisionError as e:
|
||||
print(e)
|
||||
return "没测量" ,0,0,0,0,0,""
|
||||
|
||||
excel_path = r'D:\test\logistics\拦截数据\订单数据.xlsx'
|
||||
|
||||
country_list = ['United Kingdom','United States','Australia','Germany','Spain','France']
|
||||
# country_list = ['United States']
|
||||
for country in country_list:
|
||||
order_id = f"""
|
||||
SELECT
|
||||
# ol.order_date,
|
||||
CONCAT("[",GROUP_CONCAT(pr.包裹号),"]") AS package_group ,
|
||||
ol.order_id ,
|
||||
ol.postcode,
|
||||
ol.delivery_country,
|
||||
ol.convey,
|
||||
ol.order_price_dollar
|
||||
|
||||
FROM
|
||||
parcel pr
|
||||
LEFT JOIN dwd.order_list ol ON ol.order_id = pr.订单号
|
||||
WHERE
|
||||
# ol.order_date between "2024-10-01" and "2025-03-01"
|
||||
pr.生成时间 >= DATE_SUB(NOW(), INTERVAL 20 DAY)
|
||||
AND fund_status NOT REGEXP "等待"
|
||||
AND site_name REGEXP "litfad|kwoking|lakiq"
|
||||
# AND convey = "海运"
|
||||
# AND delivery_country = "United States"
|
||||
AND pr.订单号 = ol.order_id
|
||||
AND delivery_country regexp '{country}'
|
||||
AND NOT EXISTS (
|
||||
SELECT
|
||||
1
|
||||
FROM
|
||||
`order_express` oe
|
||||
WHERE
|
||||
oe.包裹号 = pr.包裹号
|
||||
AND oe.包裹状态 = "已作废")
|
||||
GROUP BY ol.order_id
|
||||
"""
|
||||
order_id_df = pd.read_sql(order_id,engine)
|
||||
if order_id_df.empty:
|
||||
print(f"{country}无订单")
|
||||
continue
|
||||
order_id_df.sort_values(by=['delivery_country'],inplace=True)
|
||||
countries = order_id_df['delivery_country'].unique()
|
||||
opCountry = OperateCountry(country)
|
||||
order_id_df_cal = order_id_df.copy()
|
||||
# 删除order_id_df_cal中已经存在的订单
|
||||
if os.path.exists(excel_path):
|
||||
existing_df = pd.read_excel(excel_path)
|
||||
order_id_df_cal = order_id_df_cal[~(order_id_df_cal['order_id'].isin(existing_df['order_id']) &
|
||||
order_id_df_cal['package_group'].isin(existing_df['package_group']))]
|
||||
if order_id_df_cal.empty:
|
||||
continue
|
||||
order_id_df_cal[["体积重","_type","头程单价","头程(CNY)","尾端","总金额(USD)","实际体积"]] = order_id_df_cal.apply(lambda x: get_order_bill(opCountry,
|
||||
x['order_id'],
|
||||
eval(x['package_group']),
|
||||
x['postcode'],
|
||||
x['convey'],
|
||||
x['order_price_dollar']),axis=1,result_type='expand')
|
||||
order_id_df_cal = order_id_df_cal[~(order_id_df_cal['体积重']== "没测量")]
|
||||
order_id_df_cal['总金额(USD)'] = pd.to_numeric(order_id_df_cal['总金额(USD)'], errors='coerce')
|
||||
order_id_df_cal = order_id_df_cal.dropna(subset=['总金额(USD)'])
|
||||
order_id_df_cal = order_id_df_cal[order_id_df_cal['总金额(USD)'] <9999]
|
||||
order_id_df_cal['生成日期'] = pd.Timestamp.now().strftime('%Y/%m/%d')
|
||||
order_id_df_cal['order_id'] = order_id_df_cal['order_id'].astype(int)
|
||||
if order_id_df_cal.empty:
|
||||
print(f"{country}没有需要测量的订单。")
|
||||
continue
|
||||
if not os.path.exists(excel_path):
|
||||
order_id_df_cal.to_excel(excel_path, index=False, sheet_name='Sheet1')
|
||||
else:
|
||||
exist_df = pd.read_excel(excel_path)
|
||||
new_rows = order_id_df_cal[~(order_id_df_cal['order_id'].isin(exist_df['order_id']) &
|
||||
order_id_df_cal['package_group'].isin(exist_df['package_group']))]
|
||||
updated_df = pd.concat([exist_df, new_rows], ignore_index=True)
|
||||
# order_id去重
|
||||
updated_df = updated_df.drop_duplicates(subset=['order_id'], keep='last')
|
||||
if not new_rows.empty:
|
||||
# 将更新后的 DataFrame 写回 Excel 文件
|
||||
updated_df.to_excel(excel_path, index=False, sheet_name='Sheet1')
|
||||
print(f"已写入 {len(new_rows)} 条新数据。")
|
||||
|
||||
order_id_df_cal = order_id_df_cal.drop(columns=['生成日期'])
|
||||
order_id_list = order_id_df_cal["order_id"].tolist()
|
||||
param = ",".join(f"'{order_id}'" for order_id in order_id_list)
|
||||
purchase_order_sql = f"""
|
||||
with t1 AS (SELECT LEFT
|
||||
( ol.out_detials_outlink_id, 15 ) AS order_id,
|
||||
SUM( out_detials_qty * price )/ 7 AS instock_cost,
|
||||
NULL AS buy_cost
|
||||
FROM
|
||||
ods.outstock_list ol
|
||||
JOIN ods.instock_list il ON ol.store_in_id = il.id
|
||||
WHERE
|
||||
LEFT ( ol.out_detials_outlink_id, 15 ) IN ({param})
|
||||
GROUP BY
|
||||
LEFT ( ol.out_detials_outlink_id, 15 )
|
||||
UNION ALL
|
||||
SELECT
|
||||
LEFT ( order_product_id, 15 ) as order_id,
|
||||
NULL as instock_cost,
|
||||
SUM(buy_num * actual_price)/7 AS buy_cost
|
||||
FROM
|
||||
`warehouse_purchasing`
|
||||
WHERE
|
||||
LEFT ( order_product_id, 15 ) IN ({param})
|
||||
AND buy_audit = "采购完成"
|
||||
group by LEFT ( order_product_id, 15 )
|
||||
)
|
||||
|
||||
SELECT
|
||||
order_id,
|
||||
SUM(CASE
|
||||
WHEN instock_cost is null THEN
|
||||
buy_cost
|
||||
ELSE
|
||||
instock_cost END) AS pur_cost
|
||||
FROM
|
||||
t1
|
||||
GROUP BY order_id
|
||||
|
||||
"""
|
||||
purchase_order_df = pd.read_sql(purchase_order_sql, con=engine)
|
||||
purchase_order_df["order_id"] = purchase_order_df["order_id"].astype(str)
|
||||
order_id_df_cal["order_id"] = order_id_df_cal["order_id"].astype(str)
|
||||
order_id_df_cal = pd.merge(order_id_df_cal, purchase_order_df, on='order_id', how='left')
|
||||
|
||||
def profit_cal(amount,cost):
|
||||
transaction_fees_rate = 0.031
|
||||
cur_tran_rate = 0
|
||||
store_cost = 2
|
||||
|
||||
|
||||
|
||||
|
||||
profit = amount * (1 - transaction_fees_rate - cur_tran_rate) - store_cost - cost
|
||||
return round(profit, 2)
|
||||
|
||||
|
||||
order_id_df_cal["profit"] = order_id_df_cal.apply(lambda x: profit_cal(x["order_price_dollar"] , x["pur_cost"] + x ["总金额(USD)"]), axis=1)
|
||||
order_id_df_cal["profit_rate"] = round(order_id_df_cal["profit"] / order_id_df_cal["order_price_dollar"],2)
|
||||
order_id_df_cal["rejust"] = order_id_df_cal.apply(lambda x: 1 if x["profit_rate"] < 0 else 0, axis=1)
|
||||
order_id_df_cal[order_id_df_cal['rejust'] == 1]
|
||||
order_id_df_cal['拦截处理日期'] = pd.to_datetime(pd.Timestamp.now().date())
|
||||
|
||||
|
||||
# order_id_df_cal = order_id_df_cal[order_id_df_cal['rejust'] == 1]
|
||||
# import pendulum
|
||||
# dt = pendulum.now()
|
||||
|
||||
# # 将日期时间对象转换为字符串
|
||||
# dt_str = dt.to_format('YYYY-MM-DD')
|
||||
# finename = dt_str + "拦截.xlsx"
|
||||
|
||||
def update_excel_with_new_data(excel_path, new_data_df):
|
||||
try:
|
||||
new_data_df['order_id'] = new_data_df['order_id'].astype(str)
|
||||
if not os.path.exists(excel_path):
|
||||
# 如果文件不存在,创建一个新的 Excel 文件
|
||||
new_data_df = new_data_df[new_data_df['pur_cost'].notna()]
|
||||
new_data_df.to_excel(excel_path, index=False, sheet_name='Sheet1')
|
||||
print(f"文件不存在,已创建新文件并写入 {len(new_data_df)} 条数据。")
|
||||
df['拦截处理日期'] = pd.to_datetime(df['拦截处理日期']).dt.strftime('%Y/%m/%d')
|
||||
else:
|
||||
existing_df = pd.read_excel(excel_path)
|
||||
existing_df['order_id'] = existing_df['order_id'].astype(str)
|
||||
new_rows = new_data_df[~(new_data_df['order_id'].isin(existing_df['order_id']) &
|
||||
new_data_df['package_group'].isin(existing_df['package_group']))]
|
||||
new_rows = new_rows[new_rows['pur_cost'].notna()]
|
||||
|
||||
updated_df = pd.concat([existing_df, new_rows], ignore_index=True)
|
||||
# order_id去重
|
||||
updated_df = updated_df.drop_duplicates(subset=['order_id'], keep='last')
|
||||
updated_df.to_excel(excel_path, index=False, sheet_name='Sheet1')
|
||||
df = pd.read_excel(excel_path)
|
||||
df = df.drop_duplicates(subset=['order_id'], keep='last')
|
||||
df['拦截处理日期'] = pd.to_datetime(df['拦截处理日期'])
|
||||
df.to_excel(excel_path, index=False, sheet_name='Sheet1')
|
||||
|
||||
if not new_rows.empty:
|
||||
# 将更新后的 DataFrame 写回 Excel 文件
|
||||
print(f"已写入 {len(new_rows)} 条新数据。")
|
||||
else:
|
||||
print("没有新数据需要添加。")
|
||||
except Exception as e:
|
||||
print(f"发生错误: {e}")
|
||||
update_excel_with_new_data(r'D:\test\logistics\拦截数据\拦截总表.xlsx',order_id_df_cal)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue