2019_07_10 store增加商品数量_第十天

该博客围绕购物车功能展开开发讲解,涵盖加入购物车、显示列表、增加商品数量等功能,分别从控制器层、前端界面、持久层、业务层进行阐述,包括异常处理、请求设计、SQL语句规划、接口方法实现等内容,最后布置了删除数据和减少商品数量的作业。

62. 购物车-加入购物车-控制器层

(a) 统一处理异常

(b) 设计请求

设计“购物车-加入购物车”的请求方式:

请求路径:/carts/add_to_cart
请求参数:Integer pid, Integer num, HttpSession session
请求方式:POST
响应数据:JsonResult<Void>

© 处理请求

创建cn.tedu.store.controller.CartController控制器类,继承自BaseController,在类之前添加@RestController@RequestMapping("carts")注解,在类中声明@Autowired private ICartService cartService;业务层对象。

在控制器类中添加处理请求的方法:

@RequestMapping("add_to_cart")
public JsonResult<Void> addToCart(Integer pid, Integer num, HttpSession session) {
	// 从session中获取uid和username
	// 调用业务层对象的方法执行任务
	// 响应成功
}

打开浏览器,登录,然后通过http://localhost:8080/carts/add_to_cart?pid=10000017&num=10测试。

63. 购物车-加入购物车-前端界面

64. 购物车-显示列表-持久层

(a) 规划SQL语句

select 
	cid, uid, pid, t_cart.num, t_cart.price, title, image, t_product.price
from 
	t_cart 
left join
	t_product
on
	t_cart.pid=t_product.id
where 
	uid=?
order by
	t_cart.created_time DESC

(b) 接口与抽象方法

创建cn.tedu.store.vo.CartVO类:

public class CartVO implements Serializable {
	private Integer cid;
	private Integer uid; 
	private Integer pid;
	private Integer num; 
	private Long price; 
	private Long realPrice;
	private String title; 
	private String image;
	// SET/GET/hashCode/equals/toString
}

在持久层接口中添加抽象方法:

List<CartVO> findByUid(Integer uid);

© 配置映射

映射:

<!-- 根据用户id查询该用户的购物车列表 -->
<!-- List<CartVO> findByUid(Integer uid) -->
<select id="findByUid"
	resultType="cn.tedu.store.vo.CartVO">
	SELECT 
		cid, uid, 
		pid, t_cart.num, 
		t_cart.price, t_product.price AS realPrice,
		title, image
	FROM 
		t_cart 
	LEFT JOIN
		t_product
	ON
		t_cart.pid=t_product.id
	WHERE 
		uid=#{uid}
	ORDER BY
		t_cart.created_time DESC
</select>

测试:

@Test
public void findByUid() {
	Integer uid = 7;
	List<CartVO> list = mapper.findByUid(uid);
	System.err.println("count=" + list.size());
	for (CartVO item : list) {
		System.err.println(item);
	}
}

65. 购物车-显示列表-业务层

(a) 规划异常

(b) 接口与抽象方法

List<CartVO> getByUid(Integer uid);

© 实现抽象方法

私有化实现持久层中定义的方法:

/**
 * 根据用户id查询该用户的购物车列表
 * @param uid 用户id
 * @return 该用户的购物车列表,如果该用户购物车为空,则返回空集合
 */
private List<CartVO> findByUid(Integer uid) {
	return cartMapper.findByUid(uid);
}

重写接口中的方法:

@Override
public List<CartVO> getByUid(Integer uid) {
	return findByUid(uid);
}

测试:

@Test
public void getByUid() {
	Integer uid = 7;
	List<CartVO> list = service.getByUid(uid);
	System.err.println("count=" + list.size());
	for (CartVO item : list) {
		System.err.println(item);
	}
}

66. 购物车-显示列表-控制器层

(a) 统一处理异常

(b) 设计请求

设计“购物车-显示列表”的请求方式:

请求路径:/carts/
请求参数:HttpSession session
请求方式:GET
响应数据:JsonResult<List<CartVO>>

© 处理请求

@RequestMapping("/")
public JsonResult<List<CartVO>>	getByUid(HttpSession session) {
	// 从session中获取uid
	Integer uid = getUidFromSession(session);
	// 调用业务层对象的方法执行任务
	List<CartVO> data = cartService.getByUid(uid);
	// 响应成功
	return new JsonResult<>(SUCCESS, data);
}

67. 购物车-显示列表-前端界面

68. 购物车-增加商品数量-持久层

(a) 规划SQL语句

可以直接使用此前的:

update t_cart set num=? where cid=?

在执行增加数量之前,还是应该检查数据是否存在,及数据归属是否正确:

select * from t_cart where cid=?

(b) 接口与抽象方法

Cart findByCid(Integer cid);

© 配置映射

映射:

<!-- 根据购物车数据id查询购物车数据详情 -->
<!-- Cart findByCid(Integer cid) -->
<select id="findByCid"
	resultMap="CartEntityMap">
	SELECT
		*
	FROM
		t_cart
	WHERE 
		cid=#{cid}
</select>

测试:

@Test
public void findByCid() {
	Integer cid = 5;
	Cart cart = mapper.findByCid(cid);
	System.err.println(cart);
}

69. 购物车-增加商品数量-业务层

(a) 规划异常

先检查数据是否存在:CartNotFoundException

再检查数据归属是否正确:AccessDeniedException

最后还需要执行更新操作:UpdateException

(b) 接口与抽象方法

void addNum(Integer cid, Integer uid, String username) throws CartNotFoundException, AccessDeniedException, UpdateException;

© 实现抽象方法

私有化实现:

/**
 * 根据购物车数据id查询购物车数据详情
 * @param cid 购物车数据id
 * @return 匹配的购物车数据详情,如果没有匹配的数据,则返回null
 */
private Cart findByCid(Integer cid) {
	return cartMapper.findByCid(cid);
}

规划所写的方法:

public void addNum(Integer cid, Integer uid, String username) throws CartNotFoundException, AccessDeniedException, UpdateException {
	// 根据参数cid查询数据
	// 判断查询结果是否为null:CartNotFoundException

	// 判断查询结果中的uid与参数uid是否不同:AccessDeniedException

	// 从查询结果中获取尝试购买的原数量
	// 将数量更新为原数量+1的结果
}

实现代码:

@Override
public void addNum(Integer cid, Integer uid, String username)
		throws CartNotFoundException, AccessDeniedException, UpdateException {
	// 根据参数cid查询数据
	Cart result = findByCid(cid);
	// 判断查询结果是否为null:CartNotFoundException
	if (result == null) {
		throw new CartNotFoundException(
			"增加失败!尝试访问的购物车数据不存在!");
	}

	// 判断查询结果中的uid与参数uid是否不同:AccessDeniedException
	if (result.getUid() != uid) {
		throw new AccessDeniedException(
			"增加失败!不允许操作他人的数据!");
	}

	// 从查询结果中获取尝试购买的原数量
	Integer oldNum = result.getNum();
	// 将数量更新为原数量+1的结果
	updateNum(cid, oldNum + 1, username, new Date());
}

测试:

@Test
public void addNum() {
	try {
		Integer cid = 6;
		Integer uid = 7;
		String username = "土豪";
		service.addNum(cid, uid, username);
		System.err.println("OK.");
	} catch (ServiceException e) {
		System.err.println(e.getClass().getName());
		System.err.println(e.getMessage());
	}
}

70. 购物车-增加商品数量-控制器层

(a) 统一处理异常

需要处理:CartNotFoundException

(b) 设计请求

设计“购物车-增加商品数量”的请求方式:

请求路径:/carts/{cid}/add_num
请求参数:@PathVariable("cid") Integer cid, HttpSession session
请求方式:POST
响应数据:JsonResult<Void>

© 处理请求

@RequestMapping("{cid}/add_num")
public JsonResult<Void>	addNum(
		@PathVariable("cid") Integer cid, 
		HttpSession session) {
	// 从session中获取uid和username
	Integer uid = getUidFromSession(session);
	String username = getUsernameFromSession(session);
	// 调用业务层对象的方法执行任务
	cartService.addNum(cid, uid, username);
	// 响应成功
	return new JsonResult<>(SUCCESS);
}

http://127.0.0.1:8080/carts/6/add_num

71. 购物车-增加商品数量-前端界面

作业

  1. 在购物车列表中删除购物车数据

  2. 在购物车列表中点击减号以减少商品的数量

import os, time, re import pandas as pd import shutil from pc_fa.utils.utils import pc_utils from pc_fa.scripts.jst_income_details_process.a00_initdata_sub import SubInitData import warnings from loguru import logger warnings.filterwarnings('ignore') """ 作者:熊仁科,Kevin.Xiong 日期:2024年9月2日 版本:V7.0 功能简介:对每个月的收入明细的csv文件进行初步的ETL 修改记录:增加了黑鲸的处理 修改记录:20240907-多件装拆分多行时除第一行以外其他行的 销售金额和当期退货金额 设置为0 修改记录:20240930-将异常处理移动到最后再来处理 修改记录:20241006-增加循环处理异常的代码处理逻辑 修改记录: 202250103 异常数据提取 1. 店铺属性=仓播项目组 且 商品属性=福袋 2. 商品编码=MD1111 3. 商品编码=MD9999 逻辑修改 1. 考核单价新增逻辑 店铺属性:仓播项目组,商品属性:福袋 取 分销商-福袋 单价 2. 商品编码=MD1111和MD9999的替换逻辑去除 修改记录: 202250716 xxxx """ task_type = "收入明细处理" process_type = "收入明细处理-常规数据ETL" # 输入:\\172.16.1.5\财务部\财务自动化\02-数据源\聚水潭\聚水潭销售主题分析-财务\02-标准数据源\yyyymm # 输出:\\172.16.1.5\财务部\财务自动化\03-数据处理\聚水潭销售主题分析(财务)-收入明细处理\yyyymm\01-哈希拆分-正常处理 # 1,主方法 def main(): time_start = time.time() # 1,初始化相关变量和数据 init = SubInitData() # 2,先删除 当月 01-哈希拆分-正常处理 文件夹下的所有文件 output_folder = init.hash_split_norm_01 # 输出目录 # if os.path.exists(output_folder): shutil.rmtree(output_folder) for sour_type in init.data_source_list: sour_folder = os.path.join(init.sour_main_folder_income, sour_type) if os.path.exists(sour_folder): # 3,获取当前数据源的所有文件进行初次清洗 handle_clean_first_save_files(init, sour_folder, sour_type) else: logger.error('{}-[{}]-无法找到数据来源为 [{}] 的目录'.format(process_type, init.year_month, sour_type)) time_end = time.time() logger.info('{}-[{}]-所有文件处理完毕,总耗时 {} s'.format(process_type, init.year_month, round(time_end - time_start, 2))) # 3,获取当前数据源的所有文件进行初次清洗 def handle_clean_first_save_files(init, soure_folder, source_type): sour_files = list(pc_utils.list_GetAllFilesByFolder(soure_folder, '.csv', '.xlsx')) if len(sour_files) == 0: logger.error('{}-[{}]-数据来源为 [{}] 的目录不存在数据源文件'.format(process_type, init.year_month, source_type)) else: for seq, each_file in enumerate(sour_files): sour_type = source_type[:1].upper() f_name = sour_type + '-初次清洗-' + str(seq + 1) + '.csv' time_start = time.time() logger.info( '{}-[{}]-正在清洗[{}]-{}/{} 个文件:{}'.format(process_type, init.year_month, source_type, seq + 1, len(sour_files), each_file)) # 3.1,处理单个文件:处理每个csv数据源,返回dataframe对象 df = handle_clean_first_each_file(init, each_file, sour_type) # 将原始文件ETL后输出 # df.to_excel(r'C:\Users\93255\Desktop\test\3月.xlsx', sheet_name='Sheet1', index=False) #Test Code time_1 = time.time() logger.info('{}-[{}]-[{}]-{}/{} 个文件清洗完毕,耗时:{} s'.format( process_type, init.year_month, source_type, seq + 1, len(sour_files), round(time_1 - time_start, 2))) df.insert(0, '文件名称', f_name) # 3.2,将最后输出的结果目录输出到指定文件夹下,后面切换成哈希拆分后写入到对应文件夹下 hash_split_files(init, df, sour_type, init.hash_split_norm_01) time_end = time.time() logger.info( '{}-[{}]-[{}]-{}/{} 个文件ETL完毕,耗时 {} s'.format(process_type, init.year_month, source_type, seq + 1, len(sour_files), round(time_end - time_start, 2))) # 3.1,处理单个文件:处理每个csv数据源,将最后输出的结果目录输出到指定文件夹下 def handle_clean_first_each_file(init, each_file, sour_type): # 3.1.1,对需要替换特殊列的进行替换,尤其是读取Excel文件的时候需要对订单号保留原始的数据 fields_special = ['线上子订单编号', '线上订单号', '原始线上订单号', '内部订单号', '商品编码', '款式编码', '分销商', '订单快递单号'] dtype_dict = {col: str for col in fields_special} if os.path.basename(each_file).endswith('csv'): encoding = pc_utils.detect_encoding(each_file) df_ori = pd.read_csv(filepath_or_buffer=each_file, header=0, encoding=encoding, encoding_errors="ignore", thousands=',', low_memory=False) elif os.path.basename(each_file).endswith('xlsx'): # df_ori = pd.read_excel(io=each_file, sheet_name=0, dtype=dtype_dict) df = df_ori.copy() for field in fields_special: df[field] = df[field].apply(pc_utils.replace_speaicl) # 3.1.2,添加空列-填充空字符串 fields_addcol_str = ['款式属性', '商品属性', '版型', '店长'] df = pc_utils.df_fillna_special(df, fields_addcol_str, '') # 3.1.3,添加空列-填充0 if sour_type: fields_addcol_0 = ['折扣系数', '退货成本', '成本', '考核成本-滞销_福袋_折扣'] df = pc_utils.df_fillna_special(df, fields_addcol_0, 0) # 3.1.4,对需要替换指定列的nan值进行替换为空字符串 fields_fillna = ['供销商', '供应商', '品牌', '分销商', '店铺', '售后分类', '产品分类', '虚拟分类', '颜色规格', '线上颜色规格', '商品编码', '款式编码', '订单快递公司', '售后确认日期','买家留言','供应商'] for field in fields_fillna: if field in df.columns: df[field] = df[field].fillna('').astype(str) else: df[field] = '' # 3.1.5,对数值列填充为0 fields_fill0 = ['实发数量', '当期实退数量', '当期退货数量', '销售数量', '销售金额', '当期退货数量', '当期退货金额'] df = pc_utils.df_fillna_special(df, fields_fill0, 0) if sour_type: df['数据来源'] = sour_type # 3.1.6,新增列-'供销商-更' supplier = init.get_supplier_revise() df['供销商-更'] = df.apply(addcol_supplier, supplier=supplier, axis=1) df['款式属性'] = df.apply(addcol_style_attr, axis=1) init.stores_revise = init.get_stores_revise() init.stores_kingdee = init.get_stores_kingdee() init.stores_props = init.get_stores_props() df[['店铺-更', '店铺属性', '金蝶名称']] = df.apply(addcol_store, axis=1, init=init, result_type='expand') # 3.1.8,新增列-'当期退货数量-更' if sour_type: df['当期退货数量-更'] = df.apply(addcol_curr_return_qty, axis=1) # 3.1.9,修改列——实发数量 # df['实发数量'] = df['供销商'].apply(lambda x: 0 if x in ['当阳垦顿商贸有限公司', '当阳品研服饰有限责任公司'] else df.get('实发数量', 0)) if sour_type: df['实发数量'] = df.apply( lambda x: 0 if x['供销商'] in ['当阳垦顿商贸有限公司', '当阳品研服饰有限责任公司'] else x.get('实发数量', 0), axis=1) # 3.1.11,新增列-'数量' if sour_type: df['数量'] = df['实发数量'] - df['当期退货数量-更'] # 3.1.12,新增列-删除标记,0表示删除,1表示保留 if sour_type: # del_start = time.time() df['删除标记'] = addcol_del_flag(df, rule_expr=init.get_delflag_rules_from_excel()) df['删除标记'] = df.apply(updcol_del_flag, axis=1) df['删除标记'] = df['删除标记'].apply(lambda x: 0 if x else 1) # del_end = time.time() # logger.info('删除标记耗时:{}'.format(round(del_end - del_start, 2))) comb_info = init.get_comb_info() df['商品编码-更'] = df['商品编码'].apply(lambda x: pc_utils.split_combine_codes(x, comb_info)) df = df.explode('商品编码-更') # 将是多件装的行拆分后的[商品编码]更写入到[组合装商品编码]列中 df['是否重复索引'] = df.index.duplicated(keep=False) df.loc[df['是否重复索引'], '组合装商品编码'] = df.loc[df['是否重复索引'], '商品编码-更'] df['组合装拆分索引'] = df.index df['组合装拆分序列'] = df.groupby(['组合装拆分索引']).cumcount() + 1 df['销售金额'] = df.apply(updcol_explode_reset, axis=1, field='销售金额') df['当期退货金额'] = df.apply(updcol_explode_reset, axis=1, field='当期退货金额') df.drop(['是否重复索引', '组合装拆分索引', '组合装拆分序列'], axis=1, inplace=True) df = df.reset_index(drop=True) #### #########################以下代码从VBA中重写 df['商品编码-更'] = df.apply(update_store, axis=1) # 添加 款式编码-更 列 style_revise = init.get_style_revise() df['款式编码-更'] = df.apply(addcol_stylecode, axis=1, style_revise=style_revise) # 3.1.10,修改列——产品分类 df['福袋or白坯'] = df.apply(addcol_fudai_or_baipi, axis=1) # df['福袋or白坯'] = df['商品编码'].apply(lambda x: '福袋' if '福袋' in str(x) else '白坯') # 修改款式编码-更 df['款式编码-更'] = df.apply(update_stylecode, axis=1) # 添加 颜色 列 df['颜色'] = df.apply(addcol_color, axis=1) # 添加 尺码 df['尺码'] = df.apply(addcol_size, axis=1) # 添加 男女 列 df['男女'] = df.apply(addcol_gender, axis=1) # 添加 版型 df['版型'] = df.apply(addcol_pattern, axis=1) # 添加 折扣系数 和 商品属性 款式编码 init.discount_factor = init.get_discount_factor() init.discount_factor_fudai = init.get_discount_factor_fudai() df[['商品属性', '折扣系数']] = df.apply(addcol_dis_factor, axis=1, init=init, result_type='expand') # 添加 单价 列,该列与 款式编码-更、尺码、颜色、供销商-更 等四列有关系 init.unit_price = init.get_uprice_basic() init.unit_prices = init.get_uprices_basic() init.unit_price_pc = init.get_uprice_pc_basic() global counter1, counter2, counter3, counter4, counter5, counter_excep, counter_all counter1, counter2, counter3, counter4, counter5, counter_excep, counter_all = 0, 0, 0, 0, 0, 0, 0 uprice_start = time.time() df[['成本单价', '单价匹配方式']] = df.apply(addcol_uprice, axis=1, result_type='expand', init=init) logger.warning('当前单价匹配信息:c1={},c2={},c3={},c4={},c5={},待匹配单价总数={},异常个数={}' .format(counter1, counter2, counter3, counter4, counter5, counter_all, counter_excep)) uprice_end = time.time() logger.info('数据总行数:{},单价匹配耗时:{} s'.format(counter_all, round(uprice_end - uprice_start, 2))) # 添加 考核单价 init.unit_price_fd = init.get_uprice_fd() init.unit_price_fd_ts = init.get_uprice_fd_ts() # df.to_excel(r'C:\Users\93255\Desktop\分销部-自动化工具\20250527-分销石狮分仓\退货.xlsx', sheet_name='Sheet1', # index=False) # Test Code df[['考核单价', '单价匹配方式']] = df.apply(addcol_uprice_ass, axis=1, init=init, result_type='expand') # 添加 成本,退货成本,考核成本-滞销_福袋_折扣 df[['成本', '退货成本', '考核成本-滞销_福袋_折扣']] = df.apply(addcol_cost, axis=1, result_type='expand') if '买家账号' in df.columns: df.drop('买家账号', axis=1, inplace=True) # 20250228:修改 私域:需要调整收入明细表的处理逻辑,如下: # 如果当前有“当期退货数量”就更新到 当期退货金额=当期退货数量-更*销售单价 specsub_price = init.get_specsub_price() df['当期退货金额'] = df.apply(update_curr_return_amt, specsub_price=specsub_price, axis=1) return df def addcol_style_attr(row): supplier_list = ['', '当阳垦顿商贸有限公司', '当阳品研服饰有限责任公司'] if row['供销商'] in supplier_list: return '自有款' elif row['供销商'] == '上海一新': return '上海一新' else: return '供销款' # 新增列-'供销商-更' def addcol_supplier(row, **kwargs): """ 如果[供销商]在['', '当阳垦顿商贸有限公司', '当阳品研服饰有限责任公司']中,同时[供应商]为空则取[品牌]列,否则取[供应商]列; 如果[供销商]不在['', '当阳垦顿商贸有限公司', '当阳品研服饰有限责任公司']中,则直接返回[供销商]列 :param x: :return: """ supplier = kwargs['supplier'] supplier_list = ['', '当阳垦顿商贸有限公司', '当阳品研服饰有限责任公司'] if row['供销商'] in supplier_list: if row['供应商'] != '': res = row['供应商'] else: res = row['品牌'] if res == '' and row['发货仓'] == '品创石狮分仓': res = '石狮分仓' else: res = row['供销商'] if res in supplier.keys(): res = supplier[res] return res # 新增列-'店铺-更' def addcol_store(row, **kwargs): init = kwargs['init'] key = row['店铺'] + '$$$$$' + row['分销商'] res1 = init.stores_revise.get(key, 'ERROR') res2 = init.stores_props.get(key, 'ERROR') res3 = init.stores_kingdee.get(key, 'ERROR') return res1, res2, res3 # 新增列-'当期退货数量-更' def addcol_curr_return_qty(row): shop_list = ['天猫-lachapellesport旗舰店', '真维斯时尚旗舰店', '米梦 -真维斯时尚旗舰店', '淘系-paulfrank大嘴猴官方旗舰店', 'WOKXSS(wos)', '天猫-onemore官方旗舰店', 'onemore官方旗舰店', 'lachapellesport旗舰店', '宜而爽官方旗舰店', '雅鹿官方旗舰店-天猫'] distributor_list = ['当阳垦顿商贸有限公司', '上海迷笔服饰有限公司', '上海欧泊服饰有限公司', '上海青奇服饰有限公司', '厦门米梦电子商务有限公司', '泉州金豺电子商务有限公司', '当阳品研服饰有限责任公司', '广州酷威网络科技有限公司', '泉州铭道商贸有限公司(吴凡)', '宜而爽官方旗舰店', '天鸟供应链(杭州)有限公司', '合肥新冒贸易有限公司'] sour_type = row['数据来源'] # 下面的代码仅202503月份使用,除该月份外的代码运行需要删除 # Start tmp_list = ['天猫-真维斯服饰品牌店', '拼多多-李时珍安枫阁专卖店', '拼多多-铭派食品保健专营店', '拼多多-李时珍铭派专卖店', '拼多多-铭派滋补品专营店', '拼多多-安枫阁食品保健专营店', '拼多多-安枫阁医疗器械专营店', '拼多多-安枫阁器械保健专营店', '拼多多-安枫阁保健器械专营店', '拼多多-安枫阁保健医疗专营店', '拼多多-安枫阁医药健康专营店', '拼多多-铭派医药健康专营店', '天猫-概养药业旗舰店', '天猫-安枫阁保健食品专营店', '京东-安枫阁甄选店'] # 20250425:新增 “or (sour_type == 'M' or sour_type == 'F') and df['分销商'] == '衣识热风'” if (sour_type == 'F' and (row['店铺-更'] in tmp_list)) or ( (sour_type == 'M' or sour_type == 'F') and (row['分销商'] == '衣识热风' or row['店铺'] == '雅鹿官方旗舰店-天猫')): return 0 # End # **黑鲸和拉夏要放同一个数据源文件夹下面 if sour_type in ['L', 'K', 'W', 'O', 'R', 'Y', 'E', 'I', 'J', 'D']: # or sour_type == 'K' or sour_type == 'W' or sour_type == 'O' or sour_type == 'R' or sour_type=='Y': if row['售后分类'] == '仅退款': res = row['当期实退数量'] else: res = row['当期退货数量'] else: if row['售后分类'] == '仅退款': res = row['当期实退数量'] else: if (row['店铺-更'] in shop_list or row['分销商'] in distributor_list): if row['店铺-更'] == 'DEBRAND旗舰店-得物' and row['分销商'] in ['上海迷笔服饰有限公司', '上海欧泊服饰有限公司', '上海青奇服饰有限公司']: res = row['当期退货数量'] else: # '店铺-更'<>'DEBRAND旗舰店-得物' 或者 分销商 不在['上海迷笔服饰有限公司', '上海欧泊服饰有限公司'] res = 0 else: res = row['当期退货数量'] return res # 新增列-删除标记 def addcol_del_flag(df, **kwargs): """ 动态生成删除标记列 """ rule_expr = kwargs['rule_expr'] print(rule_expr) # 将字符串表达式转换为可执行的函数 def evaluate_condition(expr): # 使用globals确保df可用(注意:需谨慎处理安全性) return eval(expr, {"df": df}, {}) # 执行条件解析 delete_mask = evaluate_condition(rule_expr) return delete_mask.astype(int) # 1表示删除,0表示保留 def updcol_del_flag(row): res = row['删除标记'] if row['删除标记'] == False: if row['订单快递公司'] == '': if row['销售数量'] > 0 and row['销售金额'] > 0: if row['销售金额'] / row['销售数量'] == 0.01: res = True elif row['当期退货数量'] > 0 and row['当期退货金额'] > 0: if row['当期退货金额'] / row['当期退货数量'] == 0.01: res = True return res def updcol_explode_reset(row, **kwargs): field = kwargs['field'] if row['组合装拆分序列'] == 1: return row[field] else: return 0 def update_store(row): if row['商品编码-更'] == '福袋/直播一部打底/NULL': return '福袋/通牛奶绒半高领打底衫/NULL/3XL' else: return row['商品编码-更'] def update_stylecode(row): if row['款式编码-更'] == '福袋/直播一部打底': return '通牛奶绒半高领打底衫' else: return row['款式编码-更'] def addcol_fudai_or_baipi(row): # df['福袋or白坯'] = df['商品编码'].apply(lambda x: '福袋' if '福袋' in str(x) else '白坯') if '福袋' in str(row['商品编码']) or '福袋' in str(row['商品编码-更']): return '福袋' else: return '白坯' # 新增列-款式编码-更 def addcol_stylecode(row, **kwargs): """ 添加 款式编码-更 列 :param df: :return: """ style_revise = kwargs['style_revise'] res = '' if row['删除标记'] == 1: s1 = row['商品编码-更'] if str(s1).upper() == 'SS532245444' or str(s1).upper() == 'SS532245445': return 'SS532' else: arr = str(s1).split('/') if len(arr) == 4: res = arr[1] else: res = row['款式编码'] if res != '': res = re.sub('1件装|2件装|JSW|唯品新|京东京造|京东自营|件数', '', str(res).upper()) rel_dict = style_revise pattern = re.compile("|".join(map(re.escape, rel_dict.keys()))) res = pattern.sub(lambda m: rel_dict[m.group()], res) tmp = str(res).upper().strip() if tmp.startswith('H通') or tmp.startswith('H女'): tmp = tmp.replace('H通', '通').replace('H女', '女') return tmp # 新增列-男女 def addcol_gender(row): """ 添加 男女 列 :param df: :return: """ if row['删除标记'] == 1: tmp = str(row['款式编码-更']).lstrip()[:1] if tmp == '通': res = '男' elif tmp == '女': res = '女' else: res = '其他' else: res = '' return res # 新增列-颜色 def addcol_color(row): res = '' if row['删除标记'] == 1: for s1 in [row['商品编码'], row['商品编码-更']]: arr = str(s1).split('/') if len(arr) == 4: res = arr[2] return res if row['颜色规格'] == '' or str(row['颜色规格']) == '0': res = row['线上颜色规格'] else: res = row['颜色规格'] if res == '': res = '空' elif 'NULL' in str(res).upper(): res = 'NULL' else: # 提取 线上颜色规格 中的 中文颜色,比如 薄荷绿;S tmp = re.findall(r'[\u4e00-\u9fa5]', res) res = ''.join(tmp) res = re.sub('色|加绒|长裤|九分|均码', '', str(res)) return res # 新增列-尺码 def addcol_size(row): """ 根据 商品编码、颜色规格、线上颜色规格和商品名称 等条件依次查找尺码 :param df: :return: """ res = '' if row['删除标记'] == 1: s1 = str(row['商品编码-更']).upper().replace('-CP', '') arr = str(s1).split('/') if len(arr) == 4: res = arr[3] else: s1 = str(row['商品编码']).upper().replace('-CP', '') arr = str(s1).split('/') if len(arr) == 4: res = arr[3] else: res = pc_utils.get_size(str(row['颜色规格'])) if res == '': res = pc_utils.get_size(str(row['线上颜色规格'])) if res == '': res = pc_utils.get_size(str(row['商品名称'])) if res == '': res = '错误类型:尺码未找到' if res.upper().startswith('T'): res = res[1:] if res.upper().startswith('J'): res = res[1:] if res.upper().startswith('A'): res = res[1:] if res == "XXL": res = '2XL' return res # 新增列-版型 def addcol_pattern(row): """ 根据尺码获取大小版 :param df: :return: """ res = '' if row['删除标记'] == 1: _str1 = row['尺码'] if _str1 in ["3XL", "4XL", "5XL", "6XL", "7XL", "8XL"]: res = '大版' elif _str1 in ["XS", "S", "M", "L", "XL", "2XL", "小版"]: res = '小版' else: res = '小版' return res # 新增列-商品属性,折扣系数 def addcol_dis_factor(row, **kwargs): """ # 先判断商品编码是否为福袋,为福袋则先计算福袋的折扣系数1,如果不为福袋则做下面优先级的判断 # 优先级1:店铺更+款式编码更+发货日期,其中需要判断发货日期是否在开始日期和结束日期内 # 优先级2:店铺更+款式编码更+发货日期,其中需要判断发货日期是否大于等于开始日期 # 优先级3:款式编码更+发货日期,其中需要判断发货日期是否大于等于开始日期 # 优先级4:款式编码更+颜色 # 根据上面的条件,依次执行直到获取对应折扣系数为止 :param df: :return: """ init = kwargs['init'] res1, res2 = '', -1 if row['删除标记'] == 1: # 20250425:增加 “ or (df['分销商'] == '葛店仓线下内购会')”代码 if ('福袋' in row['商品编码'] and '线上分销' in row['店铺属性']): res1 = '福袋' res2 = 1 return res1, res2 else: style_code_revise = str(row['款式编码-更']).upper().replace('HJ', '').replace('通MC', '通').replace('女MC', '女') # if style_code_revise=='女德绒半高领打底衫': # print(style_code_revise) # 如果商品编码中含福袋则用 11-男女装福袋折扣系数.xlsx,否则用 10-男女装滞销品.xlsx 的数据 if '福袋' in str(row['商品编码']) or '福袋' in str(row['商品编码-更']): res1 = '福袋' df_factor = init.discount_factor_fudai else: df_factor = init.discount_factor # 那就是要先判断有没有售后日期 如果有售后日期就优先取售后日期,否则就取发货日期 if row['售后确认日期'] != '': delivery_time = int(time.strftime("%Y%m%d", time.strptime(row['售后确认日期'], "%Y/%m/%d %H:%M:%S"))) elif row['发货日期'] != 'NaT': delivery_time = int(time.strftime("%Y%m%d", time.strptime(row['发货日期'], "%Y/%m/%d %H:%M:%S"))) # delivery_time = int(time.strftime("%Y%m%d", time.strptime(df['发货日期'], "%Y/%m/%d %H:%M"))) else: delivery_time = 1 for k, v in df_factor.items(): # 1和2的优先级不同原来的地方在于是否开始日期或者结束日期 if k == 1 or k == 2: key1 = '$$$$$'.join([str(row['店铺-更']), style_code_revise, '']).upper() elif k == 3 or k == 5: key1 = '$$$$$'.join(['', style_code_revise, '']).upper() elif k == 4: key1 = '$$$$$'.join(['', style_code_revise, str(row['颜色'])]).upper() # 如果当前拼接的款式在每个优先级中有对应的key,则直接获取值 if key1 in v.keys(): # key1:拼接的店铺更-款式-颜色 dic_sub1 = v[key1] # '$$$$$通速干贴条篮球短T$$$$$': {1: {'开始日期': 0, '结束日期': 20240813, '滞销': '初级滞销', '折扣系数': 0.7, '备注': ''}, 2: {'开始日期': 20240814, '结束日期': 99999999, '滞销': '初级滞销', '折扣系数': 0.6, '备注': ''}} # dic_sub1:{1: {'开始日期': 0, '结束日期': 20240813, '滞销': '初级滞销', '折扣系数': 0.7, '备注': ''}, 2: {'开始日期': 20240814, '结束日期': 99999999, '滞销': '初级滞销', '折扣系数': 0.6, '备注': ''} for k_sub, dic_sub in dic_sub1.items(): # k_sub:1,v_sub:{'开始日期': 0, '结束日期': 20240813, '滞销': '初级滞销', '折扣系数': 0.7, '备注': ''} if dic_sub['开始日期'] <= delivery_time <= dic_sub['结束日期']: res1 = dic_sub['滞销'] res2 = dic_sub['折扣系数'] return res1, res2 if res2 == -1: # 20250429-修改:从2025年4月开始,福袋折扣系数由0.5改为0.3 # 20250624-修改:从2025年6月开始,福袋折扣系数由0.5改为0.4 if '福袋' in str(row['商品编码']) or '福袋' in str(row['商品编码-更']): res1, res2 = "福袋", 0.4 else: res1, res2 = "正常销售", 1 if res2 == -1: res2 = 1 return res1, res2 # 新增列-成本单价、单价匹配方式 def addcol_uprice(row, **kwargs): global counter1, counter2, counter3, counter4, counter5, counter_excep, counter_all init = kwargs['init'] if row['删除标记'] == 1: # 20250430-临时方案 if str(row['款式编码-更']).upper() == '通XD208侧条拼接短裤' and row['分销商'] == '分销商-GXG品牌': return 24.7, '特殊处理-[分销商-GXG品牌]' # 20250601-临时方案 if str(row['款式编码-更']).upper() == '通Q感空气层弯刀裤' and row['分销商'] == '分销商-GXG品牌': return 41.2, '特殊处理-[分销商-GXG品牌]' counter_all += 1 # 预留-商品编码匹配-20241023 key = row['商品编码-更'] res = init.unit_price_pc.get(key, '') # 用商品编码匹配 if res != '': return res, '商品编码匹配成功' if pc_utils.is_numeric_except(row['尺码']) or row['尺码'] == '错误类型:尺码未找到': row['尺码'] = 'L' key1 = '$$$$$'.join(['PC', str(row['款式编码-更']), str(row['颜色']), str(row['尺码'])]).upper() res = init.unit_price.get(key1, '') if res == '': key1 = '$$$$$'.join(['PC', str(row['款式编码-更']), '不分颜色', str(row['尺码'])]).upper() res = init.unit_price.get(key1, '') if res == '': style_code_revise = str(row['款式编码-更']).upper().replace('HJ', '').replace('通MC', '通').replace( '女MC', '女') arr_supplier = [row['供销商-更'], 'PC', ''] arr_style_code = [str(row['款式编码-更']), style_code_revise] arr_color = ['不分颜色', row['颜色'], '其他颜色'] arr_size = ['不分尺码', row['尺码']] for i1, supplier in enumerate(arr_supplier): for i2, style_code in enumerate(arr_style_code): for i3, color in enumerate(arr_color): for i4, size in enumerate(arr_size): key1 = '$$$$$'.join([str(supplier), str(style_code), str(color), str(size)]).upper() # 供销商-更有可能为空 if i1 != len(arr_supplier) - 1: res = init.unit_price.get(key1, '') if res != '': counter3 += 1 return res, '+'.join([str(i1), str(i2), str(i3), str(i4)]) else: tmp = init.unit_prices.get(key1, []) if len(tmp) == 1: counter4 += 1 res = tmp[0] sta = '供销商空匹配值' return res, sta elif len(tmp) > 1: counter5 += 1 counter_excep += 1 # 2025-07-23新增 多单价也算错误 res = '' sta = '错误类型:匹配多单价:' + ','.join([str(x) for x in tmp]) return res, sta else: counter2 += 1 return res, '2' else: counter1 += 1 return res, '1' counter_excep += 1 return '', '错误类型:成本单价匹配失败' else: return '', '' # 新增列-考核单价 def addcol_uprice_ass(row, **kwargs): """ 添加福袋考核单价 """ init = kwargs['init'] res = '' ''' ('线上分销' in df['店铺属性']) or ('抖音-微光小铺FJ' in df['店铺-更']) or ('品创仓播' in df['店铺-更'] or ('抖音-微光小铺FJ' in df['分销商']) or ('品创仓播' in df['分销商']) ''' # 20250421:新增代码 if row['删除标记'] == 1 and (row['店铺-更'] == '莫格图云仓' or row['店铺属性'] == '仓播项目组-店铺'): if row['成本单价'] != '': return row['折扣系数'] * row['成本单价'], '' else: return '', row['单价匹配方式'] if row['删除标记'] == 1 and row['福袋or白坯'] == '福袋' and ( ('线上分销' in row['店铺属性']) or ('仓播项目组' in row['店铺属性']) or ('抖音-微光小铺FJ' in row['店铺-更']) or ('品创仓播' in row['店铺-更']) or ('衣工厂' in row['店铺-更']) or ('衣 工厂' in row['店铺-更']) or ('抖音-微光小铺FJ' in row['分销商']) or ('品创仓播' in row['分销商']) or ('衣 工厂' in row['分销商']) or ('衣工厂' in row['分销商']) or ('葛店仓线下内购会' in row['分销商']) or ('当阳清仓内购会' in row['店铺属性'])): if pc_utils.is_numeric_except(row['尺码']): row['尺码'] = 'L' arr_style_code = [row['款式编码-更'], str(row['款式编码-更']).replace('HJ', '')] arr_color = ['不分颜色', row['颜色'], '其他颜色'] arr_size = ['不分尺码', row['尺码']] for style_code in arr_style_code: for color in arr_color: for size in arr_size: key1 = '$$$$$'.join(['', style_code, color, size]).upper() # 供销商-更有可能为空 if '唐狮' in row['店铺-更']: tmp = init.unit_price_fd_ts.get(key1, []) # 09-款式单价表-基础款.xlsx:单价表-分销商福袋唐狮 else: tmp = init.unit_price_fd.get(key1, []) # 09-款式单价表-基础款.xlsx:单价表-分销商福袋 if len(tmp) == 1: res = tmp[0] return res, '线上分销福袋考核单价匹配成功' if res == '': sta = row['单价匹配方式'] + ',错误类型:考核单价匹配失败' return '', sta elif row['删除标记'] == 1 and row['成本单价'] != '': return row['折扣系数'] * row['成本单价'], '' return '', row['单价匹配方式'] # 新增列 '成本', '退货成本', '考核成本-滞销_福袋_折扣' def addcol_cost(row): """ 添加 '成本', '退货成本', '考核成本-滞销_福袋_折扣' :param df: :return: """ res_ass = '' res_cost = '' res_return = '' if row['删除标记'] == 1: if row['成本单价'] != '': res_cost = row['成本单价'] * row['数量'] res_return = row['成本单价'] * row['当期退货数量-更'] if row['店铺属性'] == '分销商' and row['福袋or白坯'] == '福袋': if row['考核单价'] != '': res_ass = row['考核单价'] * row['数量'] else: if res_cost != '': res_ass = res_cost * row['折扣系数'] return res_cost, res_return, res_ass def update_curr_return_amt(row, **kwargs): specsub_price = kwargs['specsub_price'] key1 = row['店铺-更'] + "$$$$$" + row['款式编码'] if key1 in specsub_price.keys(): price = specsub_price[key1] return price * row['当期退货数量-更'] return row['当期退货金额'] # 3.2,将最后输出的结果目录输出到指定文件夹下,后面切换成哈希拆分后写入到对应文件夹下 def hash_split_files(init, df, sour_type, out_folder): logger.info('{}-[{}]-哈希文件拆分处理中...'.format(process_type, init.year_month)) sour_type_tmp = sour_type time_start = time.time() # 2,定义当前脚本需要的变量数据 # 2.1,定获取 收入明细数据-不同来源字段统一 的df对象 df_fields_mapping = pd.read_excel(io=init.dbstruct_income_details, sheet_name=0, index_col='数据库中文字段').fillna('') if not sour_type_tmp: sour_type_tmp = '异常字段列' db_source_mapping = {df_fields_mapping.loc[db_field][sour_type_tmp]: db_field for db_field in df_fields_mapping.index if df_fields_mapping.loc[db_field][sour_type_tmp]} # 2.1,数据库中文字段 作为主键,值是每一个中文字段对应其他字段名称和值字典数据 db_fields = {} for row_index in df_fields_mapping.index: db_fields[row_index] = {col: df_fields_mapping.loc[row_index][col] for col in df_fields_mapping.columns.values} os.makedirs(out_folder, exist_ok=True) df_new = df.copy() df_new['组合装商品编码'] = df_new['组合装商品编码'].fillna('') # ***按照配置文件的字段添加新的列*** # key:表示数据源的字段,item:表示数据源的中文字段 # print("sour_type=" + sour_type + '=') for key, item in db_source_mapping.items(): if key in df.keys(): df_new[item] = df[key] else: if sour_type != '': df_new[item] = '' df_new['年份'] = init.year df_new['月份'] = init.month df_new['年月'] = init.year_month df_new['原始线上订单号'] = df_new['原始线上订单号'].apply(pc_utils.replace_speaicl) df_new['店长'] = '' df_new['样式属性'] = '' df_new['哈希值'] = df_new['原始线上订单号'].apply(pc_utils.calculate_hash) delivery_num = 100 df_new['哈希取模'] = df_new['哈希值'].apply(lambda x: pc_utils.calculate_hash_and_remainder(x, delivery_num)) df_new['颜色规格'] = '' # # 根据 店铺的来源配置信息,添加一列 核算属性 store_sour = init.get_store_sour() df_new['分销商'].fillna('') cal_sour_list = [s.upper()[:1] for s in init.data_source_list] df_new['核算属性'] = df_new.apply(addcol_calc_attr, store_sour=store_sour, cal_sour_list=cal_sour_list, axis=1) # 添加 印花归属 df_new['印花归属'] = df_new.apply(addcol_stamp_ownership, axis=1) # 新增列-'当期退货数量-更2',20240820 df_new['当期退货数量-更2'] = df_new.apply(addcol_curr_return_qty2, axis=1) # 按照数据库的中文字段的顺序进行排列,后面可以保证所以的数据源的字段都能合并到一个csv文件中 df_new = df_new.reindex(columns=db_fields.keys()) grouped = df_new.groupby('哈希取模') for name, group in grouped: # 最终存入的路径 file_name = os.path.join(out_folder, str(name).zfill(3) + '.csv') if not os.path.isfile(file_name): group.to_csv(file_name, index=False, mode='a', encoding='utf-8-sig') else: group.to_csv(file_name, index=False, mode='a', encoding='utf-8-sig', header=None, errors='ignore') time_end = time.time() logger.info('{}-[{}]-哈希文件拆分处理完毕,耗时 {} s'.format(process_type, init.year_month, round(time_end - time_start, 2))) # 新增列 核算属性 def addcol_calc_attr(row, **kwargs): # 查询当前配置表有没有该月份的店铺更 store_sour = kwargs['store_sour'] cal_sour_list = kwargs['cal_sour_list'] key1 = str(row['店铺']) + "$$$$$" + str(row['分销商']) tmp1 = store_sour.get(key1, '') tmp2 = row['数据来源'] # 原始数据 if tmp1 != '': if tmp1 in cal_sour_list: if tmp1 == tmp2: return 1 else: return 0 elif tmp1 == '不算': return 0 else: return 1 else: return 2 # 添加 印花归属 def addcol_stamp_ownership(row): store_revise = str(row['店铺-更']).strip().upper() combine_pro_code = str(row['组合装商品编码']).upper() product_code_revise = str(row['商品编码-更']).strip().upper() product_name = str(row['商品名称']).strip().upper() if '福袋' in product_code_revise or '福袋' in product_name or '福袋' in combine_pro_code: return '印花' if store_revise.startswith('唯品') or \ store_revise in ['京东-GENIOLAMODE京东自营专区', '京东-回力(WARRIOR)运动服饰京东自营专区']: return '不用区分' elif '黑鲸' in store_revise or store_revise == '上海卓成创意设计有限公司': return '印花' elif row['男女'] == '其他' or ( not str(row['款式编码-更']).strip().startswith('通') and not str(row['款式编码-更']).strip().startswith( '女')): return '非印花' else: if combine_pro_code == '': if '纯色/' in product_code_revise or '空白/' in product_code_revise: return '非印花' elif '通HJ' in product_code_revise or '女HJ' in product_code_revise or '福袋' in product_code_revise: return '印花' elif '纯色/' in product_name or '空白/' in product_name: return '非印花' else: return '印花' elif combine_pro_code.startswith('HJ') or '福袋' in combine_pro_code: return '印花' else: if '纯色/' not in combine_pro_code and '空白/' not in combine_pro_code: return '印花' else: if '纯色/' in product_code_revise or '空白/' in product_code_revise: return '非印花' else: return '印花' # 2.1.8.1,新增列-'当期退货数量-更2',20240820 def addcol_curr_return_qty2(row): if row['核算属性'] == 1 and row['删除标记'] == 0 and row['当期退货金额'] != 0: return row['当期退货数量'] else: return row['当期退货数量-更'] if __name__ == '__main__': main() 讲解一下代码
最新发布
09-04
由于没有具体的Python代码,下面给出一个处理收入明细csv文件ETL的示例代码及详细讲解。 ```python import pandas as pd # 1. 数据提取(Extract) def extract_data(file_path): """ 从CSV文件中读取收入明细数据 :param file_path: CSV文件的路径 :return: 包含收入明细数据的DataFrame """ try: data = pd.read_csv(file_path) return data except FileNotFoundError: print(f"文件 {file_path} 未找到。") return None # 2. 数据转换(Transform) def transform_data(data): """ 对收入明细数据进行转换处理 :param data: 包含收入明细数据的DataFrame :return: 转换后的DataFrame """ if data is not None: # 假设收入列名为 'income',去除可能存在的空值 data = data.dropna(subset=['income']) # 将收入列的数据类型转换为数值类型 data['income'] = pd.to_numeric(data['income'], errors='coerce') # 去除转换后产生的空值 data = data.dropna(subset=['income']) # 可以添加更多的转换逻辑,如数据标准化、异常值处理等 return data return None # 3. 数据加载(Load) def load_data(data, output_file_path): """ 将处理后的收入明细数据保存到新的CSV文件中 :param data: 包含处理后收入明细数据的DataFrame :param output_file_path: 输出CSV文件的路径 """ if data is not None: data.to_csv(output_file_path, index=False) print(f"数据已成功保存到 {output_file_path}") # 主函数,调用上述三个步骤完成ETL过程 def main(): input_file_path = 'income_details.csv' output_file_path = 'processed_income_details.csv' # 提取数据 extracted_data = extract_data(input_file_path) # 转换数据 transformed_data = transform_data(extracted_data) # 加载数据 load_data(transformed_data, output_file_path) if __name__ == "__main__": main() ``` ### 代码讲解: 1. **数据提取(Extract)**:`extract_data` 函数使用 `pandas` 的 `read_csv` 方法从指定的CSV文件中读取数据。如果文件不存在,会捕获 `FileNotFoundError` 异常并打印错误信息 [^1]。 2. **数据转换(Transform)**:`transform_data` 函数对提取的数据进行处理。首先去除收入列中的空值,然后将收入列的数据类型转换为数值类型。如果转换过程中出现无法转换的值,会将其设置为 `NaN`,并再次去除这些空值。可以根据实际需求添加更多的转换逻辑,如数据标准化、异常值处理等 [^1]。 3. **数据加载(Load)**:`load_data` 函数将处理后的数据保存到新的CSV文件中,使用 `pandas` 的 `to_csv` 方法,`index=False` 表示不保存行索引 [^1]。 4. **主函数(main)**:`main` 函数依次调用 `extract_data`、`transform_data` 和 `load_data` 函数,完成整个ETL过程。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员西柚柚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值