| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- import re
- import sys
- import json
- import time
- import pymysql
- from tw.config import conn, table
- from datetime import datetime
- from random import randint
- from collections import namedtuple
- from openpyxl import Workbook
- from openpyxl.styles import PatternFill, colors
- '''
- twong 辅助工具,扫描商品表,及其关联表
- 自动更新不合理数据
- 对于价格方面的数据,则导出为 excel 表
- 运行需自行配置参数,并打开 commit 语句
- '''
- SHOW_MAX_DAYS = 800 # 超过天数自动下架
- NOW = int(time.time()) # 当前时间戳
- INVALID_CHAR=r'[\s,。 ,.{}()\[\]<>]+'
- PTN_INVALID = r'^{}|{}$'.format(INVALID_CHAR, INVALID_CHAR)
- # config excel
- excel = Workbook()
- sheet = excel.active
- sheet.title = '问题商品'
- sheet.freeze_panes = 'A2' # freeze above or left
- # init sheet header
- sheet['A1'] = 'ID'
- sheet['B1'] = '商品'
- sheet['C1'] = 'SKU'
- sheet['D1'] = '成本'
- sheet['E1'] = '价格'
- sheet['F1'] = '原价'
- sheet['G1'] = '利润率'
- sheet['H1'] = '其他问题'
- for cols in sheet.iter_cols(min_col=1,max_col=8,min_row=1,max_row=1):
- for cell in cols:
- cell.fill = PatternFill('solid', fgColor='C5C1AA')
- sheet['A1'].fill = PatternFill('solid', fgColor='C5C1AA')
- def updateResult(productId, result, skuName, stock=0, price=0, otPrice=0, brokerage=0, brokerage2=0):
- '''
- 更新 result 表中 result 字段中的值
- '''
- if not result:
- print('product={} result={}'.format(productId, result))
- return
- # print('updateResult:', productId, skuName)
- values = result.get('value')
- if not values:
- print('ERROR: no value field')
- return
- if isinstance(values, list):
- pass
- elif isinstance(values, dict):
- values = values.values()
- else:
- print('ERROR: bad value type')
- for value in values:
- detail = value.get('detail')
- if not detail:
- print('ERROR: no detail')
- return
- values = detail.values()
- values = [str(v) for v in values]
- sku = ','.join(sorted(values))
- if sku == skuName:
- value['stock'] = stock if stock else value.get('stock')
- value['ot_price'] = otPrice if otPrice else value['ot_price']
- value['price'] = price if price else value['price']
- value['brokerage'] = 0 if brokerage else 0
- value['brokerage_two'] = 0 if brokerage2 else 0
- return result
-
- def fixResultInvalidChar(productId, result):
- '''
- 检查 result 中规格中的各种非法字符
- '''
- if not result:
- return
- attrs = result.get('attr', [])
- if attrs:
- for attr in attrs:
- value = attr.get('value', '')
- attr['value'] = re.sub(PTN_INVALID, '', value)
-
- detail = attr.get('detail', [])
- for i, d in enumerate(detail):
- detail[i] = re.sub(PTN_INVALID, '', str(d))
- values = result.get('value', [])
- if isinstance(values, list):
- pass
- elif isinstance(values, dict):
- values = values.values()
- else:
- print('unknown value type')
- return
- for value in values:
- for i in range(len(attrs)):
- key = 'value{}'.format(i+1)
- valuex = value.get(key)
- value[key] = re.sub(PTN_INVALID, '', valuex)
-
- detail = value.get('detail', {})
- removing_keys = []
- adding_keys = {}
- for k, v in detail.items():
- if re.search(PTN_INVALID, k):
- removing_keys.append(k)
- newk = re.sub(PTN_INVALID, '', k)
- adding_keys[newk] = re.sub(PTN_INVALID, '', v)
- detail[k] = re.sub(PTN_INVALID, '', v)
-
- for k in removing_keys:
- del detail[k]
- for k, v in adding_keys.items():
- detail[k] = v
- with conn:
- with conn.cursor() as cursor:
- sql = '''SELECT id, store_name, image, slider_image, spec_type, price, cost, stock, ficti, add_time
- FROM {}
- WHERE is_show=1 AND is_del=0 '''.format(table('store_product'))
- cursor.execute(sql)
- products = cursor.fetchall()
- errors = {}
- # 遍历所有商品
- for pd in products:
- productId = pd[0]
- productName = pd[1]
- specType = pd[4]
- image =pd[2]
- images = json.loads(pd[3])
- ficti = pd[8]
- addTime = pd[9]
- chFicti = False # 是否需要更新虚拟销量
- chProductStock = False # 是否需要更新库存
- errors[productId] = {
- 'name': productName,
- }
- sq2 = '''SELECT result
- FROM {}
- WHERE product_id={}'''.format(table('store_product_attr_result'), productId)
- cursor.execute(sq2)
- result = cursor.fetchone()
- if not result or len(result) <= 0:
- # print('{}-{} 在 result 表中无数据.'.format(productId, productName))
- errors[productId]['others'] = errors[productId].get('others', '') + 'attr_result 表中无数据;'
- result = {}
- else:
- result = json.loads(result[0])
- fixResultInvalidChar(productId, result)
- sq3 = '''SELECT suk, stock, sales, price, cost, ot_price, brokerage, brokerage_two, `unique`
- FROM {}
- WHERE product_id={}'''.format(table('store_product_attr_value'), productId)
- cursor.execute(sq3)
- values = cursor.fetchall()
- # 检查商品图片是否完整
- if not image or not images:
- errors[productId]['others'] = errors[productId].get('others', '') + ' 没有设置产品图;'
-
- # 虚拟销量必须为 0-20
- if ficti < 0 or ficti > 20:
- chFicti = True
- # 上架超过一定时间自动下架,并将 add_time 改为当前日期
- days = int( (NOW - addTime) / 86400 )
- if days > SHOW_MAX_DAYS:
- sql = '''UPDATE {} SET is_show=0, add_time={} WHERE id={}'''.format(table('store_product'), NOW, productId)
- with conn.cursor() as cs:
- cs.execute(sql)
- print('{}-{} 过期{}天 已启动下架'.format(productId, productName, days))
- # 检查 price, cost, stock 必须为正值
- chResult = False # 是否需要更新 attr_result 表
- errors[productId]['sku'] = {} # 商品 sku 错误记录
- totalStock = 0
- eachStock = 0 # 每个 sku 的库存,为了让sku 一致
- for sku in values:
- chStock, chOtPrice, chBrokerage, chName = False, False, False, False
- skuName = sku[0]
- skuNameList = skuName.split(',')
- for i, p in enumerate(skuNameList):
- if re.search(PTN_INVALID, str(p)):
- chName = True
- skuNameList[i] = re.sub(PTN_INVALID, '', p)
- # print(skuNameList)
- newNameList = list(set(skuNameList))
- if chName or len(newNameList) != len(skuNameList):
- skuName = ','.join(sorted(newNameList))
- stock = sku[1]
- sales = sku[2]
- price = sku[3]
- cost = sku[4]
- otPrice = sku[5]
- brokerage = sku[6]
- brokerage2 = sku[7]
- unique = sku[8]
- errors[productId]['sku'][skuName] = {}
- # 库存为 5-200
- if stock < 5 or stock > 200: # stock
- chStock = True
- chProductStock = True
- else:
- totalStock += stock
- # 利润率 >= 30%
- if price <= 0: # price
- errors[productId]['sku'][skuName]['price'] = float(price)
- if cost <= 0: # cost
- errors[productId]['sku'][skuName]['cost'] = float(cost)
- if price < cost:
- errors[productId]['sku'][skuName]['price'] = float(price)
- errors[productId]['sku'][skuName]['cost'] = float(cost)
- errors[productId]['sku'][skuName]['others'] = errors[productId]['sku'][skuName].get('others', '') + '价格低于成本;'
- if price > 0:
- profitRate = round(float(1 - cost/price), 2)
- if profitRate < 0.3:
- errors[productId]['sku'][skuName]['price'] = float(price)
- errors[productId]['sku'][skuName]['cost'] = float(cost)
- errors[productId]['sku'][skuName]['profit'] = profitRate
- errors[productId]['sku'][skuName]['others'] = errors[productId]['sku'][skuName].get('others', '') + ' 利润率不足 30%;'
- if otPrice < price:
- chOtPrice = True
- # 商品自定义返利必须为0
- if brokerage !=0 or brokerage2 != 0:
- chBrokerage = True
- # 更新 result 结构
- if not eachStock or eachStock < sales:
- eachStock = randint(50, 150)+sales if chStock else 0
- newOtPrice = int(float(price) * 1.4) if chOtPrice else 0
- newBrokerage = 1 if chBrokerage else 0
- newBrokerage2 = 1 if chBrokerage else 0
- updateResult(productId, result, skuName, stock=eachStock, price=0, otPrice=newOtPrice, brokerage=newBrokerage, brokerage2=newBrokerage2)
- # print(result)
- # 更新 SQL (attr_value 表)
- parts = []
- if chName:
- parts.append("suk='{}'".format(skuName))
- if chStock:
- totalStock += eachStock
- parts.append('stock={}'.format(eachStock - sales))
- if chOtPrice:
- parts.append('ot_price={}'.format(newOtPrice))
- if parts:
- sparts = ','.join(parts)
- sql = ''' UPDATE {}
- SET {}, brokerage=0, brokerage_two=0
- WHERE `unique`='{}' '''.format(table('store_product_attr_value'), sparts, unique)
- with conn.cursor() as cs:
- cs.execute(sql)
- print('{}-{}-{} 自动修改库存名称等: sql={}'.format(productId, productName, skuName, sql))
-
- chResult = (chResult or chStock or chOtPrice or chBrokerage)
- # 更新 attr_result 表
- if chResult:
- sql = '''UPDATE {}
- SET result='{}'
- WHERE product_id={}'''.format(table('store_product_attr_result'), json.dumps(result, ensure_ascii=False), productId)
- with conn.cursor() as cs:
- cs.execute(sql)
- print('更新 result. reason: {}, : sql={}'.format(sparts, sql))
- # update products
- parts = []
- if chFicti:
- newVal = randint(0, 20)
- parts.append('ficti={}'.format(newVal))
- if chProductStock:
- parts.append('stock={}'.format(totalStock))
-
- if parts:
- sparts = ','.join(parts)
- sql = '''UPDATE {}
- SET {}
- WHERE id={}'''.format(table('store_product'), sparts, productId)
- with conn.cursor() as cs:
- cs.execute(sql)
- print('{}-{} 更改虚拟销量和库存 {}'.format(productId, productName, sparts))
- conn.commit()
- counter = 1 # skip header line
- cell = lambda ch,c: '{}{}'.format(ch, c)
- for pId, v in errors.items():
- writen = False
- if len(v) > 2:
- counter += 1
- sheet[cell('A', counter)] = pId
- sheet[cell('B', counter)] = v['name']
- sheet[cell('H', counter)] = v.get('others', '')
- writen = True
- for k, s in v['sku'].items():
- if len(s) > 0:
- counter += 1
- if not writen:
- sheet[cell('A', counter)] = pId
- sheet[cell('B', counter)] = v['name']
- sheet[cell('H', counter)] = v.get('other', '')
- writen = True
- sheet[cell('C', counter)] = k
- sheet[cell('D', counter)] = s.get('cost', '')
- sheet[cell('E', counter)] = s.get('price', '')
- sheet[cell('F', counter)] = s.get('ot_price', '')
- sheet[cell('G', counter)] = s.get('profit', '')
- sheet[cell('H', counter)] = s.get('others', '')
-
- # sheet['A1'] = 'ID'
- # sheet['B1'] = '商品'
- # sheet['C1'] = 'SKU'
- # sheet['D1'] = '成本'
- # sheet['E1'] = '价格'
- # sheet['F1'] = '原价'
- # sheet['G1'] = '利润率'
- # sheet['H1'] = '其他问题'
- excel.save(filename=datetime.now().strftime('%Y-%m-%d.xls'))
- sys.exit()
|