Jelajahi Sumber

统计和预警

fanzherong_v 3 bulan lalu
induk
melakukan
6c0b50e3c2
25 mengubah file dengan 1911 tambahan dan 19 penghapusan
  1. 24 0
      snowy-admin-web/src/api/biz/consumptionRecordApi.js
  2. 2 1
      snowy-admin-web/src/config/index.js
  3. 44 4
      snowy-admin-web/src/views/biz/member/form.vue
  4. 38 6
      snowy-admin-web/src/views/biz/member/index.vue
  5. 822 0
      snowy-admin-web/src/views/biz/statisty/total.vue
  6. 122 0
      snowy-admin-web/src/views/biz/warn/detail.vue
  7. 190 0
      snowy-admin-web/src/views/biz/warn/index.vue
  8. 5 1
      snowy-plugin/snowy-plugin-biz/pom.xml
  9. 74 1
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/controller/ConsumptionRecordController.java
  10. 7 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/entity/ConsumptionRecord.java
  11. 16 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/mapper/ConsumptionRecordMapper.java
  12. 156 2
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/mapper/mapping/ConsumptionRecordMapper.xml
  13. 20 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/param/ConsumptionChart.java
  14. 4 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/param/ConsumptionRecordAddParam.java
  15. 41 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/param/ConsumptionRecordExportResult.java
  16. 6 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/param/ConsumptionRecordPageParam.java
  17. 17 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/param/UserBalanceResult.java
  18. 18 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/service/ConsumptionRecordService.java
  19. 142 3
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/service/impl/ConsumptionRecordServiceImpl.java
  20. 5 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/couponrecord/param/BizCouponRecordPageParam.java
  21. 6 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/couponrecord/service/impl/BizCouponRecordServiceImpl.java
  22. 3 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/user/param/BizUserPageParam.java
  23. 6 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/user/result/BizMemberUserResult.java
  24. 32 1
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/user/service/impl/BizUserServiceImpl.java
  25. 111 0
      snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/utils/CommonExportUtil.java

+ 24 - 0
snowy-admin-web/src/api/biz/consumptionRecordApi.js

@@ -24,5 +24,29 @@ export default {
 	// 获取消费记录详情
 	consumptionRecordDetail(data) {
 		return request('detail', data, 'get')
+	},
+	// 获取消费金额统计
+	queryRecordTotal(data){
+		return request('queryRecordTotal',data,'get')
+	},
+	//获取账户余额&会员数统计
+	queryBalanceTotal(data){
+		return request('queryBalanceTotal',data,'get')
+	},
+	//近七日消费金额和订单数折线图
+	queryConsumptionChart(data){
+		return request('queryConsumptionChart',data,'get')
+	},
+	queryEachStore(data){
+		return request('queryEachStore',data,'get')
+	},
+	//导出
+	exportRecord(data){
+		return request('exportRecord', data, 'get', {
+			responseType: 'blob'
+		})
+	},
+	warnPage(data){
+		return request('warnPage',data,'get')
 	}
 }

+ 2 - 1
snowy-admin-web/src/config/index.js

@@ -70,7 +70,8 @@ const DEFAULT_CONFIG = {
 	LANG: 'zh-cn',
 
 	// 主题颜色
-	COLOR: '#1677FF',
+	//COLOR: '#1677FF',
+	COLOR:'#FA541C',
 
 	// 默认整体主题
 	SNOWY_THEME: 'dark',

+ 44 - 4
snowy-admin-web/src/views/biz/member/form.vue

@@ -16,8 +16,8 @@
 			<a-form-item label="代金券余额:" name="voucherBalance">
 				<a-input v-model:value="formData.voucherBalance" placeholder="请输入代金券余额" disabled allow-clear />
 			</a-form-item>
-			<a-form-item label="调整方式:" name="consumptionOperate">
-				<a-radio-group button-style="solid" v-model:value="formData.consumptionOperate">
+			<a-form-item label="调整操作:" name="consumptionOperate">
+				<a-radio-group button-style="solid" @change="onOperateChange" v-model:value="formData.consumptionOperate">
 					<a-radio-button value="1">
 						赠送
 					</a-radio-button>
@@ -26,7 +26,20 @@
 					</a-radio-button>
 				</a-radio-group>
 			</a-form-item>
-			<a-form-item label="调整类型:" name="consumptionType">
+			<a-form-item label="调整类别:" name="adjustType">
+				<a-radio-group button-style="solid" v-model:value="formData.adjustType">
+					<a-radio-button value="1" v-show="chongzhiFlag">
+						充值
+					</a-radio-button>
+					<a-radio-button value="2" v-show="tuikuanFlag">
+						退款
+					</a-radio-button>
+					<a-radio-button value="3" v-show="qitaFlag">
+						其他
+					</a-radio-button>
+				</a-radio-group>
+			</a-form-item>
+			<a-form-item label="调整方式:" name="consumptionType">
 				<a-radio-group button-style="solid" v-model:value="formData.consumptionType">
 					<a-radio-button value="1">
 						代金券
@@ -68,6 +81,10 @@
 	const labelCol = ref({ span: 5 })
 	const wrapperCol = ref({ span: 18 })
 
+	const chongzhiFlag = ref(false)
+	const tuikuanFlag = ref(false)
+	const qitaFlag = ref(false)
+
 	// 打开抽屉
 	const onOpen = (record) => {
 		open.value = true
@@ -78,8 +95,30 @@
 			formData.value.consumptionType = ref('1')
 			formData.value.userId = formData.value.id
 			formData.value.id = ''
+			formData.value.adjustType = ref('1')
+			chongzhiFlag.value = true
+			qitaFlag.value = true
+			tuikuanFlag.value = false
 		}
 	}
+
+	const onOperateChange = (e) => {
+		console.log("value:"+e.target.value)
+		if(e.target.value == '1'){
+			//选择了赠送,展示充值和其他
+			chongzhiFlag.value = true
+			qitaFlag.value = true
+			tuikuanFlag.value = false
+			formData.value.adjustType = ref('1')
+		}else{
+			//展示退款和其他
+			chongzhiFlag.value = false
+			tuikuanFlag.value = true
+			qitaFlag.value = true
+			formData.value.adjustType = ref('2')
+		}
+	}
+
 	// 关闭抽屉
 	const onClose = () => {
 		formRef.value.resetFields()
@@ -90,11 +129,12 @@
 	const formRules = {
 		name: [required('请输入消费者姓名')],
 		consumptionOperate:  [required('请选择调整操作')],
-		consumptionType: [required('调整方式')],
+		consumptionType: [required('请选择调整方式')],
 		consumptionMoney: [required('请输入调整金额')],
 		consumptionRemark: [required('请输入说明')],
 		accountBalance:  [required('请输入账户余额')],
 		voucherBalance:  [required('请输入代金券余额')],
+		adjustType:  [required('请选择调整类别')],
 	}
 	// 验证并提交数据
 	//title: '确定要为'+formData.value.name+(formData.value.consumptionOperate=='1'?'赠送':'扣减')+(formData.value.consumptionType=='1'?'代金券':'余额')+formData.value.consumptionMoney+'元吗?',

+ 38 - 6
snowy-admin-web/src/views/biz/member/index.vue

@@ -45,14 +45,21 @@
 					</span>
 				</template>
 				<template v-if="column.dataIndex === 'userStatus'">
-					<a-tag :color="record.userStatus === 'ENABLE' ? 'blue' : 'pink'">{{
-						$TOOL.dictTypeData('COMMON_STATUS', record.userStatus)
-					}}</a-tag>
+					<a-switch
+						:loading="loading"
+						:checked="record.userStatus === 'ENABLE'"
+						@change="editStatus(record)"
+						v-if="hasPerm('bizUserUpdataStatus') && record.disableFlag == '1'"
+					/>
+					<a-tag v-else :color="record.userStatus === 'ENABLE' ? 'blue' : 'pink'">{{
+							$TOOL.dictTypeData('COMMON_STATUS', record.userStatus)
+						}}</a-tag>
+<!--					<span v-else>{{ $TOOL.dictTypeData('COMMON_STATUS', record.userStatus) }}</span>-->
 				</template>
 				<template v-if="column.dataIndex === 'action'">
-					<a @click="formRef.onOpen(record)" v-if="hasPerm('bizUserEdit')">余额调整</a>
-					<a-divider type="vertical" v-if="hasPerm('bizUserConsumption')"/>
-					<a @click="consumptionRef.onOpen(record)" v-if="hasPerm('bizUserConsumption')">消费结算</a>
+					<a @click="formRef.onOpen(record)" v-if="hasPerm('bizUserEdit') && record.userStatus == 'ENABLE'">余额调整</a>
+					<a-divider type="vertical" v-if="hasPerm('bizUserConsumption') && record.userStatus == 'ENABLE'"/>
+					<a @click="consumptionRef.onOpen(record)" v-if="hasPerm('bizUserConsumption') && record.userStatus == 'ENABLE'">消费结算</a>
 				</template>
 			</template>
 		</s-table>
@@ -67,6 +74,7 @@
 	import Consumption from './consumption.vue'
 
 	const consumptionRef = ref()
+	const loading = ref(false)
 
 	const columns = [
 		{
@@ -144,6 +152,30 @@
 		searchFormRef.value.resetFields()
 		tableRef.value.refresh(true)
 	}
+
+	// 修改状态
+	const editStatus = (record) => {
+		loading.value = true
+		if (record.userStatus === 'ENABLE') {
+			bizUserApi
+				.userDisableUser(record)
+				.then(() => {
+					tableRef.value.refresh()
+				})
+				.finally(() => {
+					loading.value = false
+				})
+		} else {
+			bizUserApi
+				.userEnableUser(record)
+				.then(() => {
+					tableRef.value.refresh()
+				})
+				.finally(() => {
+					loading.value = false
+				})
+		}
+	}
 </script>
 
 <style scoped>

+ 822 - 0
snowy-admin-web/src/views/biz/statisty/total.vue

@@ -0,0 +1,822 @@
+<template>
+	<a-card :bordered="false" :body-style="{ 'padding-bottom': '20px' }" class="mb-2">
+		<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
+			<a-row :gutter="24">
+				<a-col :span="6">
+					<a-form-item label="门店" name="orgId">
+						<a-tree-select
+							v-model:value="searchFormState.orgId"
+							show-search
+							style="width: 100%"
+							:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
+							placeholder="请选择门店"
+							allow-clear
+							multiple
+							tree-default-expand-all
+							:tree-data="treeData"
+							:field-names="{
+								children: 'children',
+								label: 'name',
+								value: 'id'
+							}"
+							selectable="false"
+
+						/>
+					</a-form-item>
+				</a-col>
+				<a-col :span="6">
+					<a-form-item label="日期段" name="times">
+						<a-range-picker
+							style="width: 100%;"
+							v-model:value="searchFormState.times"
+
+							format="YYYY-MM-DD"
+							:placeholder="['开始时间', '结束时间']"
+							@change="onRangeChange"
+						/>
+					</a-form-item>
+				</a-col>
+
+				<a-col :span="6">
+					<a-button type="primary" html-type="submit" @click="refresh()">查询</a-button>
+					<a-button style="margin: 0 8px" @click="reset">重置</a-button>
+					<a-button @click="exportTotal">
+						<template #icon>
+							<export-outlined/>
+						</template>
+						导出
+					</a-button>
+				</a-col>
+			</a-row>
+		</a-form>
+	</a-card>
+	<a-row>
+		<a-col :span="8" style="margin-bottom: 10px;">
+			<a-card style="height: 180px">
+				<a-statistic title="消费金额" :value="consumerCount"/>
+				<a-statistic title="订单数" :value="consumerOrderCount"/>
+			</a-card>
+		</a-col>
+		<a-col :span="8" style="margin-bottom: 10px; margin-left: 20px">
+			<a-card style="height: 180px">
+				<a-statistic title="注册会员数" :value="userCount"/>
+<!--				<a-statistic title="订单数" :value="rechargeOrderCount"/>-->
+			</a-card>
+		</a-col>
+		<a-col :span="7" style="margin-bottom: 10px; margin-left: 20px;">
+			<a-card style="height: 180px">
+				<a-statistic title="账户余额" :value="accountBalance"/>
+				<a-statistic title="代金券余额" :value="voucherBalance"/>
+			</a-card>
+		</a-col>
+	</a-row>
+
+	<a-card :bordered="false">
+
+		<div style="display:flex;justify-content:space-between" id="totalDiv">
+			<span id="totalSpan">
+				<left-outlined :style="{fontSize:'13px',color:'#C9C9C9',cursor:'pointer'}" @click="prePage" v-show="leftFlag"/>
+			</span>
+			<span>
+				<right-outlined style="width:1.5em;height:1.5em;color:#C9C9C9;cursor:pointer" @click="nextPage" v-show="rightFlag"/>
+			</span>
+		</div>
+
+
+	</a-card>
+
+<!--	<a-card :bordered="false" :body-style="{ 'padding-bottom': '20px' }" class="mb-2">
+		<div id="main" style="height: 340px"></div>
+	</a-card>-->
+
+	<a-card :bordered="false" :body-style="{ 'padding-bottom': '20px' }" class="mb-2">
+		<div id="main2" style="height: 340px"></div>
+	</a-card>
+
+	<a-card :bordered="false" :body-style="{ 'padding-bottom': '20px' }" class="mb-2">
+
+		<a-table
+		ref="table"
+		:columns="columns4"
+		:data-source = "data4"
+		bordered
+		:row-key="(record) => record.id"
+
+		>
+		</a-table>
+
+	</a-card>
+
+
+	<a-card :bordered="false">
+
+			<a-tabs v-model:activeKey="activeKey" @change="clickTab(value)">
+				<a-tab-pane key="1">
+					<template #tab>
+						<span>
+						<red-envelope-outlined />
+						消费列表
+						</span>
+					</template>
+					<div id="printForm1">
+						<a-table
+							ref="table"
+							:columns="columns1"
+							:data-source = "data1"
+							bordered
+							:row-key="(record) => record.id"
+
+						>
+						<template #bodyCell="{ column, record }">
+							<template v-if="column.dataIndex === 'consumptionOperate'">
+								<a-tag
+									:color="
+							record.consumptionOperate === '1'
+								? 'orange'
+								: record.consumptionOperate === '2'
+								  ? 'green'
+								  : record.consumptionOperate === '3'
+								    ? 'cyan'
+								      : 'purple'
+						"
+								>
+									{{ $TOOL.dictTypeData('consumption_operate', record.consumptionOperate) }}
+								</a-tag>
+							</template>
+						</template>
+						</a-table>
+					</div>
+				</a-tab-pane>
+				<a-tab-pane key="2">
+					<template #tab>
+						<span>
+						<HddOutlined />
+						优惠券列表
+						</span>
+					</template>
+					<div id="printForm2">
+						<a-table
+							ref="table"
+							:columns="columns2"
+							:data-source = "data2"
+							bordered
+							:row-key="(record) => record.id"
+
+						>
+							<template #bodyCell="{ column, record }">
+								<template v-if="column.dataIndex === 'couponStatus'">
+									<a-tag
+										:color="
+							record.couponStatus === '0'
+								? '#87d068'
+								: record.couponStatus === '1'
+								  ? '#f50'
+								      : '#f50'
+						"
+									>
+										{{ $TOOL.dictTypeData('is_destroy', record.couponStatus) }}
+									</a-tag>
+								</template>
+							</template>
+						</a-table>
+					</div>
+				</a-tab-pane>
+				<a-tab-pane key="3">
+					<template #tab>
+						<span>
+						<ScheduleOutlined />
+						会员列表
+						</span>
+					</template>
+					<div id="printForm3">
+						<a-table
+							ref="table"
+							:columns="columns3"
+							:data-source = "data3"
+							bordered
+							:row-key="(record) => record.id"
+
+						>
+						</a-table>
+					</div>
+				</a-tab-pane>
+
+			</a-tabs>
+
+	</a-card>
+
+</template>
+
+<script setup name="customerinfo">
+	import bizOrgApi from '@/api/biz/bizOrgApi'
+	import downloadUtil from '@/utils/downloadUtil'
+	import consumptionRecordApi from '@/api/biz/consumptionRecordApi'
+	import bizCouponRecordApi from '@/api/biz/bizCouponRecordApi'
+	import bizUserApi from '@/api/biz/bizUserApi'
+	import { onMounted } from 'vue'
+	import orgApi from '@/api/biz/bizOrgApi'
+	import * as echarts from 'echarts'
+
+	//消费
+	const consumerCount = ref(0)
+	const consumerOrderCount = ref(0)
+	//充值统计数量
+	const rechargeCount = ref(0)
+	const rechargeOrderCount = ref(0)
+	//会员
+	const userCount = ref(0)
+	//账户余额&代金券余额
+	const accountBalance = ref(0)
+	const voucherBalance = ref(0)
+
+	const treeData = ref([])
+	const activeKey = ref('1');
+
+	const searchFormRef = ref()
+	let searchFormState = reactive({})
+
+	const data1 = ref([])
+	const data2 = ref([])
+	const data3 = ref([])
+	const data4 = ref([])
+	const data5 = ref([])
+
+	const dayTime = ref()
+	const zhibiao = ref()
+	const fuhe = ref()
+	const typeTitle = ref()
+
+	//三条数据一页
+	const echartsPage = ref(7)
+	//当前显示页数
+	const currentPage = ref(1)
+	// 共有多少页
+	const totalPage = ref(0)
+
+	const leftFlag = ref(false)
+	const rightFlag = ref(false)
+
+
+
+
+	onMounted(() => {
+		//金额汇总
+		loadData1();
+		loadData3();
+		clickTab()
+		// 获取机构树并加入顶级
+		bizOrgApi.orgTreeSelector().then((res) => {
+			treeData.value = res
+		})
+		//折线图
+		customer();
+		//各个门店消费统计
+		loadData4()
+	})
+
+	const onRangeChange = (value, dateString) => {
+		console.log('Formatted Selected Time: ', dateString);
+		searchFormState.startTime = dateString[0]
+		searchFormState.endTime = dateString[1]
+		delete searchFormState.times
+	};
+
+	const table = ref()
+	const formRef = ref()
+	const toolConfig = { refresh: true, height: true, columnSetting: true, striped: false }
+	const columns1 = [
+		{
+			title: '会员姓名',
+			dataIndex: 'userName',
+			align: 'center',
+		},
+		{
+			title: '会员手机号',
+			dataIndex: 'phone',
+			align: 'center',
+		},
+		{
+			title: '消费操作',
+			dataIndex: 'consumptionOperate',
+			align: 'center'
+		},
+		{
+			title: '消费金额',
+			dataIndex: 'consumptionMoney',
+			align: 'center'
+		},
+		{
+			title: '账户余额',
+			dataIndex: 'newAccountBalance',
+			align: 'center'
+		},
+		{
+			title: '代金券余额',
+			dataIndex: 'newVoucherBalance',
+			align: 'center'
+		},
+		{
+			title: '消费时间',
+			dataIndex: 'consumptionTime',
+			align: 'center'
+		},
+		{
+			title: '会员编码',
+			dataIndex: 'userCode',
+			align: 'center'
+		},
+		{
+			title: '消费门店',
+			dataIndex: 'orgName',
+			align: 'center'
+		},
+	]
+
+	const columns2 = [
+		{
+			title: '优惠券编码',
+			dataIndex: 'couponNo',
+			align: 'center',
+		},
+		{
+			title: '优惠券生成时间',
+			dataIndex: 'time',
+			align: 'center',
+		},
+		{
+			title: '是否核销',
+			dataIndex: 'couponStatus',
+			align: 'center'
+		},
+		{
+			title: '有效期开始时间',
+			dataIndex: 'startTime',
+			align: 'center'
+		},
+		{
+			title: '有效期截止时间',
+			dataIndex: 'endTime',
+			align: 'center'
+		},
+		{
+			title: '核销人',
+			dataIndex: 'destroyUserName',
+			align: 'center'
+		},
+		{
+			title: '核销时间',
+			dataIndex: 'destroyTime',
+			align: 'center'
+		},
+		{
+			title: '核销门店',
+			dataIndex: 'destroyOrgName',
+			align: 'center'
+		},
+	]
+
+
+	const columns3 = [
+		{
+			title: '会员编码',
+			dataIndex: 'userReferralCode',
+			align: 'center',
+		},
+		{
+			title: '手机号',
+			dataIndex: 'phone',
+			align: 'center',
+		},
+		{
+			title: '姓名',
+			dataIndex: 'name',
+			align: 'center',
+		},
+		{
+			title: '账户余额',
+			dataIndex: 'accountBalance',
+			align: 'center',
+		},
+		{
+			title: '代金券余额',
+			dataIndex: 'voucherBalance',
+			align: 'center',
+		},
+		{
+			title: '注册时间',
+			dataIndex: 'createTime',
+			align: 'center',
+		},
+		/*{
+			title: '门店',
+			dataIndex: 'orgName',
+			align: 'center',
+		},*/
+	]
+
+
+	const columns4 = [
+		{
+			title: '门店名称',
+			dataIndex: 'name',
+			align: 'center'
+		},
+		{
+			title: '门店编码',
+			dataIndex: 'code',
+			align: 'center'
+		},
+		{
+			title: '消费',
+			children: [
+				{
+					title: '订单数',
+					dataIndex: 'orderCount',
+					align: 'center',
+				},
+				{
+					title: '账户消费金额',
+					dataIndex: 'accountMoney',
+					align: 'center',
+				},
+				{
+					title: '代金券消费金额',
+					dataIndex: 'voucherMoney',
+					align: 'center',
+				},
+			]
+		},
+		{
+			title: '注册会员数',
+			dataIndex: 'userCount',
+			align: 'center'
+		},
+
+	]
+
+
+	const selectedRowKeys = ref([])
+	// 列表选择配置
+	const options = {
+		// columns数字类型字段加入 needTotal: true 可以勾选自动算账
+		alert: {
+			show: true,
+			clear: () => {
+				selectedRowKeys.value = ref([])
+			}
+		},
+		rowSelection: {
+			onChange: (selectedRowKey, selectedRows) => {
+				selectedRowKeys.value = selectedRowKey
+			}
+		}
+	}
+
+	const totalMoney = ref(0)
+
+	//各个门店消费记录
+	const loadData4 = () => {
+		const orgId = ref('')
+		if(searchFormState.orgId == null || searchFormState.orgId== '' || searchFormState.orgId== 'undefined' ){
+
+		}else{
+			for(let i = 0;i<searchFormState.orgId.length;i++){
+				orgId.value = orgId.value + searchFormState.orgId[i]+","
+			}
+			orgId.value = orgId.value.slice(0,orgId.value.length-1)
+		}
+		const param = {
+			"orgId":orgId.value,
+			"consumptionTimeBegin":searchFormState.startTime,
+			"consumptionTimeEnd":searchFormState.endTime
+		}
+		consumptionRecordApi.queryEachStore(param).then((data)=>{
+			data4.value = data.dataList
+		})
+	}
+
+
+	//导出
+	const exportTotal = () => {
+		const orgId = ref('')
+		if(searchFormState.orgId == null || searchFormState.orgId== '' || searchFormState.orgId== 'undefined' ){
+
+		}else{
+			for(let i = 0;i<searchFormState.orgId.length;i++){
+				orgId.value = orgId.value + searchFormState.orgId[i]+","
+			}
+			orgId.value = orgId.value.slice(0,orgId.value.length-1)
+		}
+		const param = {
+			"orgId":orgId.value,
+			"consumptionTimeBegin":searchFormState.startTime,
+			"consumptionTimeEnd":searchFormState.endTime
+		}
+		consumptionRecordApi.exportRecord(param).then((res)=>{
+			downloadUtil.resultDownload(res)
+		})
+	}
+
+	//会员折线图
+	const customer = () => {
+		const orgId = ref('')
+		if(searchFormState.orgId == null || searchFormState.orgId== '' || searchFormState.orgId== 'undefined' ){
+
+		}else{
+			for(let i = 0;i<searchFormState.orgId.length;i++){
+				orgId.value = orgId.value + searchFormState.orgId[i]+","
+			}
+			orgId.value = orgId.value.slice(0,orgId.value.length-1)
+		}
+		const param = {
+			"orgId":orgId.value,
+			"customerName":searchFormState.customerName,
+			"startTime":searchFormState.startTime,
+			"endTime":searchFormState.endTime
+		}
+		consumptionRecordApi.queryConsumptionChart(param).then((res)=>{
+			const xAxis = res.dateTime
+
+			let series = [
+				{
+					name: '消费金额',
+					type: 'line',
+					//stack: '总量',
+					showSymbol: false,
+					itemStyle: {
+					normal: {
+						color: '#00FF00', //折线点自定义
+						lineStyle: {
+						color: '#00FF00'
+						}
+					}
+					},
+					data: res.consumptionMoney
+				},
+				{
+					name: '订单数',
+					type: 'line',
+					//stack: '总量',
+					showSymbol: false,
+					data: res.consumptionCount
+				},
+				{
+					name: '会员',
+					type: 'line',
+					//stack: '总量',
+					showSymbol: false,
+					itemStyle: {
+						normal: {
+							color: '#FAD337', //折线点自定义
+							lineStyle: {
+							color: '#FAD337'
+							}
+						}
+					},
+					data: res.userCount
+				},
+
+			]
+			drow("main2",xAxis, series)
+		})
+	}
+
+	const drow = (id, xAxis, series) => {
+	let myChart = echarts.init(document.getElementById(id))
+
+	let option = {
+		color: ['#1890FF', '#52C41A'],
+		title: {
+		//text: '会话量',
+		},
+		tooltip: {
+		trigger: 'axis',
+		},
+		grid: {
+		left: '40px',
+		right: '40px',
+		top: '50px',
+		bottom: '30px'
+		},
+		legend: {
+		itemWidth: 20, // 图例图形宽度
+		itemHeight: 4,
+		icon: 'roundRect',
+		},
+		xAxis: {
+		type: 'category',
+		boundaryGap: false, //x轴两边不留空白
+		axisLabel: {
+			color: 'rgba(0, 0, 0, 0.65)',
+		},
+		axisLine: {
+			lineStyle: {
+			color: '#D9D9D9',
+			},
+		},
+		axisTick: {
+			lineStyle: {
+			color: '#D9D9D9',
+			},
+		},
+		data: xAxis,
+		},
+		yAxis: {
+		axisLine: {
+			show: false,
+			lineStyle: {
+			color: 'rgba(0, 0, 0, 0.65)',
+			},
+		},
+		splitLine: {
+			lineStyle: {
+			color: ['#E8E8E8'],
+			type: 'dashed',
+			},
+		},
+		axisLabel: {
+			color: 'rgba(0, 0, 0, 0.65)',
+		},
+		axisTick: {
+			show: false,
+			lineStyle: { color: 'rgb(150,150,150)' }, //y轴坐标刻度颜色(与axisLabel.textStyle是相同的效果)
+		},
+		type: 'value',
+		},
+		series: series
+	}
+	myChart.setOption(option)
+	}
+
+	//消费金额值统计
+	const loadData1 = () => {
+		const orgId = ref('')
+		if(searchFormState.orgId == null || searchFormState.orgId== '' || searchFormState.orgId== 'undefined' ){
+
+		}else{
+			for(let i = 0;i<searchFormState.orgId.length;i++){
+				orgId.value = orgId.value + searchFormState.orgId[i]+","
+			}
+			orgId.value = orgId.value.slice(0,orgId.value.length-1)
+		}
+		const param = {
+			"orgId":orgId.value,
+			"consumptionTimeBegin":searchFormState.startTime,
+			"consumptionTimeEnd":searchFormState.endTime
+		}
+		consumptionRecordApi.queryRecordTotal(param).then((data) => {
+			consumerCount.value = '¥ ' + data.orderMoney
+			consumerOrderCount.value = data.orderCount
+		})
+	}
+
+	//账户余额值统计
+	const loadData3 = () => {
+		const orgId = ref('')
+		if(searchFormState.orgId == null || searchFormState.orgId== '' || searchFormState.orgId== 'undefined' ){
+
+		}else{
+			for(let i = 0;i<searchFormState.orgId.length;i++){
+				orgId.value = orgId.value + searchFormState.orgId[i]+","
+			}
+			orgId.value = orgId.value.slice(0,orgId.value.length-1)
+		}
+		const param = {
+			"orgId":orgId.value,
+			"consumptionTimeBegin":searchFormState.startTime,
+			"consumptionTimeEnd":searchFormState.endTime
+		}
+		consumptionRecordApi.queryBalanceTotal(param).then((data)=>{
+			userCount.value = data.userCount
+			accountBalance.value = data.accountBalance
+			voucherBalance.value = data.voucherBalance
+		})
+	}
+
+
+	//切换
+	const clickTab = () => {
+		consumptionPage()
+		userPage()
+		couponPage()
+	}
+
+	const consumptionPage = () =>{
+		const orgId = ref('')
+		if(searchFormState.orgId == null || searchFormState.orgId== '' || searchFormState.orgId== 'undefined' ){
+
+		}else{
+			for(let i = 0;i<searchFormState.orgId.length;i++){
+				orgId.value = orgId.value + searchFormState.orgId[i]+","
+			}
+			orgId.value = orgId.value.slice(0,orgId.value.length-1)
+		}
+		const param = {
+			"orgId":orgId.value,
+			"consumptionTimeBegin":searchFormState.startTime,
+			"consumptionTimeEnd":searchFormState.endTime,
+			"consumptionOperate":'3,4'
+		}
+		consumptionRecordApi.consumptionRecordPage(param).then((data) => {
+			data1.value = data.records
+		})
+	}
+
+	//会员列表
+	const userPage = () => {
+		const orgId = ref('')
+		if(searchFormState.orgId == null || searchFormState.orgId== '' || searchFormState.orgId== 'undefined' ){
+
+		}else{
+			for(let i = 0;i<searchFormState.orgId.length;i++){
+				orgId.value = orgId.value + searchFormState.orgId[i]+","
+			}
+			orgId.value = orgId.value.slice(0,orgId.value.length-1)
+		}
+		const param = {
+			"orgId":orgId.value,
+			"beginTime":searchFormState.startTime,
+			"endTime":searchFormState.endTime
+		}
+		bizUserApi.memberPage(param).then((res) => {
+			data3.value = res.records
+		})
+	}
+
+
+	//优惠券列表
+	const couponPage = () => {
+		const orgId = ref('')
+		if(searchFormState.orgId == null || searchFormState.orgId== '' || searchFormState.orgId== 'undefined' ){
+
+		}else{
+			for(let i = 0;i<searchFormState.orgId.length;i++){
+				orgId.value = orgId.value + searchFormState.orgId[i]+","
+			}
+			orgId.value = orgId.value.slice(0,orgId.value.length-1)
+		}
+		const param = {
+			"orgId":orgId.value,
+			"beginTime":searchFormState.startTime,
+			"endTime":searchFormState.endTime
+		}
+		bizCouponRecordApi.bizCouponRecordPage(param).then((data) => {
+			data2.value = data.records
+		})
+	}
+
+
+	//查询
+	const refresh = () => {
+		loadData1();
+		loadData3();
+		clickTab()
+		loadData4()
+	}
+
+	//导出
+	const exportReport = () => {
+		if(activeKey.value == '1'){
+			searchFormState.queryFlag = '1'
+			console.log("searchFormState.queryFlag:"+searchFormState.queryFlag)
+			reportApi.exportReport(searchFormState).then((res) => {
+				downloadUtil.resultDownload(res)
+			})
+
+		}
+	}
+
+	// 重置
+	const reset = () => {
+		searchFormRef.value.resetFields()
+		searchFormState.times = []
+		searchFormState.startTime = null
+		searchFormState.endTime = null
+		searchFormState.orgId = []
+	}
+
+</script>
+
+<style lang="less" scoped>
+.ant-table-thead > tr > th {
+    text-align: center;
+}
+
+.dashboard-analysis-iconGroup {
+  i {
+    margin-left: 16px;
+    color: rgba(0, 0, 0, .45);
+    cursor: pointer;
+    transition: color .32s;
+    color: black;
+	height: 20px;
+  }
+}
+
+#totalDiv #totalSpan{
+	svg{
+		width: 1.5em;
+		height: 1.5em;
+	}
+}
+</style>

+ 122 - 0
snowy-admin-web/src/views/biz/warn/detail.vue

@@ -0,0 +1,122 @@
+<template>
+	<xn-form-container
+		title="详情"
+		:width="1000"
+		:visible="visible"
+		:destroy-on-close="true"
+		@close="onClose"
+	>
+
+		<a-form ref="formRef" :model="formData" layout="vertical">
+			<s-table
+				ref="tableRef"
+				:columns="columns"
+				:data="loadData"
+				:alert="false"
+				:showPagination="true"
+				bordered
+				:row-key="(record) => record.id"
+			>
+				<template #bodyCell="{ column, record }">
+					<template v-if="column.dataIndex === 'adjustType'">
+						<a-tag
+							:color="
+							record.adjustType === '1'
+								? 'orange'
+								: record.adjustType === '2'
+								  ? 'green'
+								  : record.adjustType === '3'
+								    ? 'cyan'
+								      : 'purple'
+						"
+						>
+							{{ $TOOL.dictTypeData('adjust_type', record.adjustType) }}
+						</a-tag>
+					</template>
+				</template>
+			</s-table>
+
+		</a-form>
+	</xn-form-container>
+</template>
+
+<script setup name="messageDetail">
+	import consumptionRecordApi from '@/api/biz/consumptionRecordApi'
+	import bizUserApi from "@/api/biz/bizUserApi";
+	import {cloneDeep} from "lodash-es";
+	const receiveInfoList = ref([])
+	const emit = defineEmits({ successful: null })
+
+	// 默认是关闭状态
+	const visible = ref(false)
+	const formRef = ref()
+	// 表单数据
+	const formData = ref({})
+	const tableRef = ref()
+	const columns = [
+		{
+			title: '姓名',
+			dataIndex: 'userName',
+			align: 'center',
+		},
+		{
+			title: '手机号',
+			dataIndex: 'phone',
+			align: 'center',
+		},
+		{
+			title: '调整操作',
+			dataIndex: 'adjustType',
+			align: 'center',
+		},
+		{
+			title: '退款金额',
+			dataIndex: 'consumptionMoney',
+			align: 'center',
+		},
+		{
+			title: '退款日期',
+			dataIndex: 'consumptionTime',
+			align: 'center',
+		},
+		{
+			title: '退款门店',
+			dataIndex: 'orgName',
+			align: 'center',
+		},
+		{
+			title: '说明',
+			dataIndex: 'consumptionRemark',
+			align: 'center',
+		},
+	]
+	// 打开抽屉
+	const onOpen = (record) => {
+		visible.value = true
+		if (record) {
+			let recordData = cloneDeep(record)
+			formData.value = Object.assign({}, recordData)
+		}
+	}
+
+	const loadData = () => {
+		let param = {
+			recordId: formData.value.id,
+			consumptionOperate:'2',
+			adjustType:'2'
+		}
+		return consumptionRecordApi.consumptionRecordPage(param).then((res) => {
+			return res
+		})
+	}
+	// 关闭抽屉
+	const onClose = () => {
+		receiveInfoList.value = []
+		visible.value = false
+		emit('successful')
+	}
+	// 调用这个函数将子组件的一些数据和方法暴露出去
+	defineExpose({
+		onOpen
+	})
+</script>

+ 190 - 0
snowy-admin-web/src/views/biz/warn/index.vue

@@ -0,0 +1,190 @@
+<template>
+	<a-card :bordered="false" style="margin-bottom: 10px" class="mb-2">
+		<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
+			<a-row :gutter="24">
+				<a-col :span="6">
+					<a-form-item label="姓名" name="userName">
+						<a-input v-model:value="searchFormState.userName" placeholder="姓名查询" allow-clear />
+					</a-form-item>
+				</a-col>
+				<a-col :span="6">
+					<a-button type="primary" html-type="submit" @click="tableRef.refresh()">查询</a-button>
+					<a-button style="margin: 0 8px" @click="reset">重置</a-button>
+<!--					<a @click="toggleAdvanced" style="margin-left: 8px">
+						{{ advanced ? '收起' : '展开' }}
+						<component :is="advanced ? 'up-outlined' : 'down-outlined'" />
+					</a>-->
+				</a-col>
+			</a-row>
+		</a-form>
+	</a-card>
+	<a-card :bordered="false">
+		<s-table
+			ref="tableRef"
+			:columns="columns"
+			:data="loadData"
+			bordered
+			:row-key="(record) => record.id"
+		>
+			<template #operator class="table-operator">
+				<a-space>
+					<a-button type="primary" @click="formRef.onOpen()" v-if="hasPerm('consumptionRecordAdd')">
+						<template #icon><plus-outlined /></template>
+						新增
+					</a-button>
+				</a-space>
+			</template>
+			<template #bodyCell="{ column, record,index }">
+				<template v-if="column.dataIndex === 'action'">
+					<a-space>
+						<a @click="detailForm.onOpen(record)" v-if="hasPerm('warnDetail')">详情</a>
+					</a-space>
+				</template>
+				<template v-if="column.dataIndex === 'consumptionOperate'">
+					<a-tag
+						:color="
+							record.consumptionOperate === '1'
+								? 'orange'
+								: record.consumptionOperate === '2'
+								  ? 'green'
+								  : record.consumptionOperate === '3'
+								    ? 'cyan'
+								      : 'purple'
+						"
+					>
+						{{ $TOOL.dictTypeData('consumption_operate', record.consumptionOperate) }}
+					</a-tag>
+				</template>
+				<template v-if="column.dataIndex === 'serial'">
+					{{ index + 1 }}
+				</template>
+			</template>
+		</s-table>
+	</a-card>
+	<DetailForm ref="detailForm" @successful="tableRef.refresh()" />
+</template>
+
+<script setup name="consumptionrecord">
+	import { cloneDeep } from 'lodash-es'
+	import consumptionRecordApi from '@/api/biz/consumptionRecordApi'
+	import tool from '@/utils/tool'
+	import bizOrgApi from '@/api/biz/bizOrgApi'
+	import DetailForm from './detail.vue'
+	import Consumption from "@/views/biz/member/consumption.vue";
+
+	const tableRef = ref()
+	const formRef = ref()
+	const detailForm = ref()
+
+	const searchFormRef = ref()
+	let searchFormState = reactive({})
+	// 查询区域显示更多控制
+	const advanced = ref(false)
+	const toggleAdvanced = () => {
+		advanced.value = !advanced.value
+	}
+
+	const consumptionOperateList = tool.dictList('consumption_operate')
+	const treeData = ref([])
+
+	const toolConfig = { refresh: true, height: true, columnSetting: true, striped: false }
+	const columns = [
+		{
+			title: '序号',
+			dataIndex: 'serial',
+			align: 'center',
+			width: 50
+		},
+		{
+			title: '推荐人姓名',
+			dataIndex: 'name',
+			align: 'center',
+		},
+		{
+			title: '账号',
+			dataIndex: 'account',
+			align: 'center',
+		},
+		{
+			title: '手机号',
+			dataIndex: 'phone',
+			align: 'center',
+		},
+		{
+			title: '退款次数',
+			dataIndex: 'count',
+			align: 'center',
+		},
+
+		{
+			title: '操作',
+			dataIndex: 'action',
+			align: 'center',
+		},
+	]
+	// 操作栏通过权限判断是否显示
+	/*if (hasPerm(['consumptionRecordEdit', 'consumptionRecordDelete'])) {
+		columns.push({
+			title: '操作',
+			dataIndex: 'action',
+			align: 'center',
+			width: 150
+		})
+	}*/
+
+	// 获取机构树并加入顶级
+	bizOrgApi.orgTreeSelector().then((res) => {
+		treeData.value = res
+	})
+
+	const selectedRowKeys = ref([])
+	// 列表选择配置
+	const options = {
+		// columns数字类型字段加入 needTotal: true 可以勾选自动算账
+		alert: {
+			show: true,
+			clear: () => {
+				selectedRowKeys.value = ref([])
+			}
+		},
+		rowSelection: {
+			onChange: (selectedRowKey, selectedRows) => {
+				selectedRowKeys.value = selectedRowKey
+			}
+		}
+	}
+	const loadData = (parameter) => {
+		const searchFormParam = JSON.parse(JSON.stringify(searchFormState))
+		// planTime范围查询条件重载
+		if (searchFormParam.consumptionTime) {
+			searchFormParam.consumptionTimeBegin = searchFormParam.consumptionTime[0]
+			searchFormParam.consumptionTimeEnd = searchFormParam.consumptionTime[1]
+			delete searchFormParam.consumptionTime
+		}
+		return consumptionRecordApi.warnPage(Object.assign(parameter, searchFormParam)).then((data) => {
+			return data
+		})
+	}
+	// 重置
+	const reset = () => {
+		searchFormRef.value.resetFields()
+		tableRef.value.refresh(true)
+	}
+	// 删除
+	const deleteConsumptionRecord = (record) => {
+		let params = [
+			{
+				id: record.id
+			}
+		]
+		consumptionRecordApi.consumptionRecordDelete(params).then(() => {
+			tableRef.value.refresh(true)
+		})
+	}
+	// 批量删除
+	const deleteBatchConsumptionRecord = (params) => {
+		consumptionRecordApi.consumptionRecordDelete(params).then(() => {
+			tableRef.value.clearRefreshSelected()
+		})
+	}
+</script>

+ 5 - 1
snowy-plugin/snowy-plugin-biz/pom.xml

@@ -38,5 +38,9 @@
             <groupId>vip.xiaonuo</groupId>
             <artifactId>snowy-plugin-dev-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+        </dependency>
     </dependencies>
-</project>
+</project>

+ 74 - 1
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/controller/ConsumptionRecordController.java

@@ -14,14 +14,18 @@ package vip.xiaonuo.biz.modular.consumptionrecord.controller;
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.http.MediaType;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 import vip.xiaonuo.biz.modular.consumptionrecord.param.*;
+import vip.xiaonuo.biz.modular.user.result.BizMemberUserResult;
 import vip.xiaonuo.common.annotation.CommonLog;
 import vip.xiaonuo.common.annotation.CommonNoRepeat;
 import vip.xiaonuo.common.pojo.CommonResult;
@@ -31,7 +35,10 @@ import vip.xiaonuo.biz.modular.consumptionrecord.service.ConsumptionRecordServic
 import jakarta.annotation.Resource;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotEmpty;
+
+import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 消费记录控制器
@@ -120,10 +127,76 @@ public class ConsumptionRecordController {
         return CommonResult.data(consumptionRecordService.detail(consumptionRecordIdParam));
     }
 
-    @Operation(summary = "门店每日销售额统计")
+    @Operation(summary = "小程序门店每日销售额统计")
     @SaCheckPermission("/biz/consumptionrecord/getRecordTotal")
     @GetMapping("/biz/consumptionrecord/getRecordTotal")
     public CommonResult<ConsumptionResult> getRecordTotal() {
         return CommonResult.data(consumptionRecordService.getRecordTotal());
     }
+
+    /**
+     * 后台销售额统计
+     * @return
+     */
+    @Operation(summary = "后台销售额统计")
+    @SaCheckPermission("/biz/consumptionrecord/queryRecordTotal")
+    @GetMapping("/biz/consumptionrecord/queryRecordTotal")
+    public CommonResult<ConsumptionResult> getRecordTotal(ConsumptionRecordPageParam consumptionRecordPageParam) {
+        return CommonResult.data(consumptionRecordService.getRecordTotal(consumptionRecordPageParam));
+    }
+
+    /**
+     * 后台账户余额&会员数统计
+     * @return
+     */
+    @Operation(summary = "后台账户余额&会员数统计")
+    @SaCheckPermission("/biz/consumptionrecord/queryBalanceTotal")
+    @GetMapping("/biz/consumptionrecord/queryBalanceTotal")
+    public CommonResult<UserBalanceResult> queryBalanceTotal(ConsumptionRecordPageParam consumptionRecordPageParam) {
+        return CommonResult.data(consumptionRecordService.queryBalanceTotal(consumptionRecordPageParam));
+    }
+
+    /**
+     * 后台消费金额订单数折线图
+     * @return
+     */
+    @Operation(summary = "后台消费金额订单数折线图")
+    @SaCheckPermission("/biz/consumptionrecord/queryConsumptionChart")
+    @GetMapping("/biz/consumptionrecord/queryConsumptionChart")
+    public CommonResult<Map<String,Object>> queryConsumptionChart(ConsumptionRecordPageParam consumptionRecordPageParam) {
+        return CommonResult.data(consumptionRecordService.queryConsumptionChart(consumptionRecordPageParam));
+    }
+
+    /**
+     * 各个门店消费记录
+     * @return
+     */
+    @Operation(summary = "各个门店消费记录")
+    @SaCheckPermission("/biz/consumptionrecord/queryEachStore")
+    @GetMapping("/biz/consumptionrecord/queryEachStore")
+    public CommonResult<Map<String,Object>> queryEachStore(ConsumptionRecordPageParam consumptionRecordPageParam) {
+        return CommonResult.data(consumptionRecordService.queryEachStore(consumptionRecordPageParam));
+    }
+
+    /**
+     * 导出汇总统计报表
+     */
+    @Operation(summary = "导出汇总统计报表")
+    @SaCheckPermission("/biz/consumptionrecord/exportRecord")
+    @GetMapping(value = "/biz/consumptionrecord/exportRecord", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
+    public void exportRecord(ConsumptionRecordPageParam consumptionRecordPageParam, HttpServletResponse response) throws IOException {
+        consumptionRecordService.exportRecord(consumptionRecordPageParam,response);
+    }
+
+    /**
+     * 预警记录
+     * @param consumptionRecordPageParam
+     * @return
+     */
+    @Operation(summary = "预警记录")
+    @SaCheckPermission("/biz/consumptionrecord/warnPage")
+    @GetMapping("/biz/consumptionrecord/warnPage")
+    public CommonResult<Page<BizMemberUserResult>> warnPage(ConsumptionRecordPageParam consumptionRecordPageParam) {
+        return CommonResult.data(consumptionRecordService.warnPage(consumptionRecordPageParam));
+    }
 }

+ 7 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/entity/ConsumptionRecord.java

@@ -103,4 +103,11 @@ public class ConsumptionRecord extends CommonEntity implements TransPojo {
     /**现代金券余额*/
     @TableField(exist = false)
     private BigDecimal newVoucherBalance;
+
+    /**用户编码*/
+    @TableField(exist = false)
+    private String userCode;
+
+    /**调整类型*/
+    private String adjustType;
 }

+ 16 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/mapper/ConsumptionRecordMapper.java

@@ -17,7 +17,13 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Param;
 import vip.xiaonuo.biz.modular.consumptionrecord.entity.ConsumptionRecord;
+import vip.xiaonuo.biz.modular.consumptionrecord.param.ConsumptionChart;
 import vip.xiaonuo.biz.modular.consumptionrecord.param.ConsumptionResult;
+import vip.xiaonuo.biz.modular.consumptionrecord.param.UserBalanceResult;
+import vip.xiaonuo.biz.modular.user.result.BizMemberUserResult;
+
+import java.util.List;
+import java.util.Map;
 
 /**
  * 消费记录Mapper接口
@@ -28,5 +34,15 @@ import vip.xiaonuo.biz.modular.consumptionrecord.param.ConsumptionResult;
 public interface ConsumptionRecordMapper extends BaseMapper<ConsumptionRecord> {
     Page<ConsumptionRecord> getPageList(@Param("page") Page<ConsumptionRecord> page, @Param("ew") QueryWrapper<ConsumptionRecord> ew);
 
+    Page<BizMemberUserResult> getWarnPageList(@Param("page") Page<ConsumptionRecord> page, @Param("ew") QueryWrapper<ConsumptionRecord> ew);
+
     ConsumptionResult getRecordTotal(@Param("ew") QueryWrapper<ConsumptionRecord> ew);
+
+    UserBalanceResult queryBalanceTotal(@Param("ew") QueryWrapper<ConsumptionRecord> ew);
+
+    List<Map<String,Object>> queryConsumptionChart(@Param("ew") QueryWrapper<ConsumptionRecord> ew);
+
+    List<Map<String,Object>> queryEachStore(@Param("orgIds") List<String> orgIds,@Param("startTime") String startTime, @Param("endTime") String endTime);
+
+
 }

+ 156 - 2
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/mapper/mapping/ConsumptionRecordMapper.xml

@@ -16,9 +16,11 @@
             IFNULL(cr.consumption_money,0) consumption_money,
             IFNULL(cr.voucher_balance,0) voucher_balance,
             IFNULL(cr.account_balance,0) account_balance,
+            cr.adjust_type,
             su.PHONE,
             su.ACCOUNT_BALANCE newAccountBalance,
-            su.VOUCHER_BALANCE newVoucherBalance
+            su.VOUCHER_BALANCE newVoucherBalance,
+            su.USER_REFERRAL_CODE userCode
         from biz_consumption_record cr
         left join SYS_USER su on cr.user_id = su.id
         left join SYS_ORG so on so.id = cr.consumption_org
@@ -30,10 +32,162 @@
     <select id="getRecordTotal" resultType="vip.xiaonuo.biz.modular.consumptionrecord.param.ConsumptionResult">
         select
             count(*) orderCount,
-            sum(bcr.consumption_money) orderMoney
+            ifnull(sum(bcr.consumption_money),0) orderMoney
         from biz_consumption_record bcr
         <where>
             ${ew.sqlSegment}
         </where>
     </select>
+
+    <select id="queryBalanceTotal"
+            resultType="vip.xiaonuo.biz.modular.consumptionrecord.param.UserBalanceResult">
+        select
+            count(*) userCount,
+            ifnull(sum(su.ACCOUNT_BALANCE),0) accountBalance,
+            ifnull(sum(su.VOUCHER_BALANCE),0) voucherBalance
+        from SYS_USER su
+        <where>
+            ${ew.sqlSegment}
+        </where>
+    </select>
+
+    <select id="queryConsumptionChart" resultType="java.util.Map">
+        SELECT
+            DATE_FORMAT( t1.dateTime, '%m-%d' ) dateTime,
+            IFNULL( t2.consumptionMoney, 0 ) AS consumptionMoney,
+            IFNULL( t2.consumptionCount, 0 ) consumptionCount,
+            IFNULL( t3.userCount, 0 ) userCount
+        FROM
+            (
+                SELECT
+                    CURDATE() AS dateTime UNION ALL
+                SELECT
+                    DATE_SUB( CURDATE(), INTERVAL 1 DAY ) AS dateTime UNION ALL
+                SELECT
+                    DATE_SUB( CURDATE(), INTERVAL 2 DAY ) AS dateTime UNION ALL
+                SELECT
+                    DATE_SUB( CURDATE(), INTERVAL 3 DAY ) AS dateTime UNION ALL
+                SELECT
+                    DATE_SUB( CURDATE(), INTERVAL 4 DAY ) AS dateTime UNION ALL
+                SELECT
+                    DATE_SUB( CURDATE(), INTERVAL 5 DAY ) AS dateTime UNION ALL
+                SELECT
+                    DATE_SUB( CURDATE(), INTERVAL 6 DAY ) AS dateTime
+            ) t1
+                LEFT JOIN (
+                SELECT LEFT
+                    ( bcr.consumption_time, 10 ) AS dayTime,
+                    IFNULL( SUM( bcr.consumption_money ), 0 ) consumptionMoney,
+                    count(*) consumptionCount
+                FROM
+                    biz_consumption_record bcr
+                WHERE
+                    bcr.delete_flag = 'NOT_DELETE'
+                GROUP BY
+                    LEFT ( bcr.consumption_time, 10 )
+                ORDER BY
+                    dayTime DESC
+            ) t2 ON t1.dateTime = t2.dayTime
+                LEFT JOIN (
+                SELECT LEFT
+                    ( bcr.create_time, 10 ) AS userDate,
+                    count(*) userCount
+                FROM
+                    SYS_USER bcr
+                WHERE
+                    bcr.delete_flag = 'NOT_DELETE'
+                GROUP BY
+                    LEFT ( bcr.create_time, 10 )
+                ORDER BY
+                    userDate DESC
+            ) t3 ON t1.dateTime = t3.userDate
+        ORDER BY
+            t1.dateTime
+    </select>
+
+    <select id="queryEachStore" resultType="java.util.Map">
+        SELECT
+            so.`name`,
+            so.CODE code,
+            ifnull(bcr.account_money,0) accountMoney,
+            ifnull(bcr.orderCount,0) orderCount,
+            ifnull(bcr.voucher_money,0) voucherMoney,
+            ifnull(su.userCount,0) userCount
+        FROM
+            SYS_ORG so
+            LEFT JOIN (
+                SELECT
+                    count(*) orderCount,
+                    IFNULL( sum( account_money ), 0 ) account_money,
+                    IFNULL( sum( voucher_money ), 0 ) voucher_money,
+                    bcr.consumption_org
+                FROM
+                    biz_consumption_record bcr
+                where bcr.DELETE_FLAG = 'NOT_DELETE'
+                and bcr.consumption_operate in ('3','4')
+                <if test="orgIds != null and orgIds.size > 0">
+                    AND bcr.consumption_org IN
+                    <foreach collection="orgIds" item="item" separator="," open="(" close=")">
+                        #{item}
+                    </foreach>
+                </if>
+                <if test="startTime !=null and endTime !=null and startTime!='' and endTime != '' ">
+                    and bcr.consumption_time between #{startTime} and #{endTime}
+                </if>
+                GROUP BY
+                    bcr.consumption_org
+            ) bcr ON bcr.consumption_org = so.id
+            LEFT JOIN ( SELECT count(*) userCount, su.ORG_ID FROM SYS_USER su
+                where su.DELETE_FLAG = 'NOT_DELETE'
+                <if test="orgIds != null and orgIds.size > 0">
+                    AND su.ORG_ID IN
+                    <foreach collection="orgIds" item="item" separator="," open="(" close=")">
+                        #{item}
+                    </foreach>
+                </if>
+                <if test="startTime !=null and endTime !=null and startTime!='' and endTime != '' ">
+                    and su.create_time between #{startTime} and #{endTime}
+                </if>
+                GROUP BY su.ORG_ID ) su ON su.ORG_ID = so.id
+            where so.PARENT_ID!=0
+            and so.DELETE_FLAG = 'NOT_DELETE'
+    </select>
+
+    <select id="getWarnPageList" resultType="vip.xiaonuo.biz.modular.user.result.BizMemberUserResult">
+        SELECT
+            a.userId,
+            su.NAME name,
+            su.PHONE phone,
+            su.ACCOUNT account,
+            a.count
+        FROM
+            (
+                SELECT
+                    a.REFERRAL_USER userId,
+                    ifnull( sum( a.count ), 0 ) count
+                FROM
+                    (
+                    SELECT
+                    count(*) count,
+                    bcr.user_id,
+                    su.REFERRAL_USER
+                    FROM
+                    biz_consumption_record bcr
+                    LEFT JOIN SYS_USER su ON bcr.user_id = su.id
+                    WHERE
+                    bcr.consumption_operate = '2'
+                    and bcr.DELETE_FLAG = 'NOT_DELETE'
+                    AND bcr.adjust_type = '2'
+                    GROUP BY
+                    bcr.user_id
+                    ) a
+                GROUP BY
+                    a.REFERRAL_USER
+            ) a
+                LEFT JOIN SYS_USER su ON su.id = a.userId
+        <where>
+            ${ew.sqlSegment}
+        </where>
+    </select>
+
 </mapper>

+ 20 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/param/ConsumptionChart.java

@@ -0,0 +1,20 @@
+package vip.xiaonuo.biz.modular.consumptionrecord.param;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class ConsumptionChart {
+    /**日期*/
+    private String dateTime;
+
+    /**销售额*/
+    private BigDecimal consumptionMoney;
+
+    /**订单数*/
+    private Integer consumptionCount;
+
+    /**注册数*/
+    private Integer userCount;
+}

+ 4 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/param/ConsumptionRecordAddParam.java

@@ -75,4 +75,8 @@ public class ConsumptionRecordAddParam {
     @Schema(description = "验证码")
     private String phoneCode;
 
+    /**调整类型*/
+    @Schema(description = "调整类型")
+    private String adjustType;
+
 }

+ 41 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/param/ConsumptionRecordExportResult.java

@@ -0,0 +1,41 @@
+package vip.xiaonuo.biz.modular.consumptionrecord.param;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.write.style.HeadStyle;
+import com.alibaba.excel.enums.poi.FillPatternTypeEnum;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class ConsumptionRecordExportResult {
+    /** 门店名称 */
+    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 120)
+    @ExcelProperty({"汇总统计", "门店名称"})
+    private String name;
+
+    /** 门店电话*/
+    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 120)
+    @ExcelProperty({"汇总统计", "门店编码"})
+    private String code;
+
+    /**订单数*/
+    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 120)
+    @ExcelProperty({"汇总统计", "消费","订单数"})
+    private Integer orderCount;
+
+    /**金额*/
+    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 120)
+    @ExcelProperty({"汇总统计", "消费","账户消费金额"})
+    private BigDecimal accountMoney;
+
+    /**金额*/
+    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 120)
+    @ExcelProperty({"汇总统计", "消费","代金券消费金额"})
+    private BigDecimal voucherMoney;
+
+    /**注册会员数*/
+    @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 120)
+    @ExcelProperty({"汇总统计", "注册会员数"})
+    private Integer userCount;
+}

+ 6 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/param/ConsumptionRecordPageParam.java

@@ -61,4 +61,10 @@ public class ConsumptionRecordPageParam {
     private String consumptionTimeBegin;
     private String consumptionTimeEnd;
 
+    /**调整类别*/
+    private String adjustType;
+
+    /**推荐人id*/
+    private String recordId;
+
 }

+ 17 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/param/UserBalanceResult.java

@@ -0,0 +1,17 @@
+package vip.xiaonuo.biz.modular.consumptionrecord.param;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class UserBalanceResult {
+    /**账户余额*/
+    private BigDecimal accountBalance;
+
+    /**代金券余额*/
+    private BigDecimal voucherBalance;
+
+    /**会员数*/
+    private Integer userCount;
+}

+ 18 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/service/ConsumptionRecordService.java

@@ -12,12 +12,17 @@
  */
 package vip.xiaonuo.biz.modular.consumptionrecord.service;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
+import jakarta.servlet.http.HttpServletResponse;
 import vip.xiaonuo.biz.modular.consumptionrecord.entity.ConsumptionRecord;
 import vip.xiaonuo.biz.modular.consumptionrecord.param.*;
+import vip.xiaonuo.biz.modular.user.result.BizMemberUserResult;
 
+import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 消费记录Service接口
@@ -76,4 +81,17 @@ public interface ConsumptionRecordService extends IService<ConsumptionRecord> {
     ConsumptionRecord queryEntity(String id);
 
     ConsumptionResult getRecordTotal();
+
+    ConsumptionResult getRecordTotal(ConsumptionRecordPageParam consumptionRecordPageParam);
+
+    UserBalanceResult queryBalanceTotal(ConsumptionRecordPageParam consumptionRecordPageParam);
+
+    Map<String,Object> queryConsumptionChart(ConsumptionRecordPageParam consumptionRecordPageParam);
+
+    Map<String,Object> queryEachStore(ConsumptionRecordPageParam consumptionRecordPageParam);
+
+    Page<BizMemberUserResult> warnPage(ConsumptionRecordPageParam consumptionRecordPageParam);
+
+    void exportRecord(ConsumptionRecordPageParam consumptionRecordPageParam, HttpServletResponse response) throws IOException;
+
 }

+ 142 - 3
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/consumptionrecord/service/impl/ConsumptionRecordServiceImpl.java

@@ -21,7 +21,11 @@ import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.google.common.collect.Maps;
 import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.compress.utils.Lists;
 import org.apache.commons.lang3.StringUtils;
 import org.checkerframework.checker.units.qual.C;
 import org.springframework.stereotype.Service;
@@ -29,9 +33,11 @@ import org.springframework.transaction.annotation.Transactional;
 import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
 import vip.xiaonuo.biz.modular.consumptionrecord.param.*;
 import vip.xiaonuo.biz.modular.user.entity.BizUser;
+import vip.xiaonuo.biz.modular.user.result.BizMemberUserResult;
 import vip.xiaonuo.biz.modular.user.service.BizUserService;
 import vip.xiaonuo.biz.modular.userfundchangerecord.entity.BizUserFundChangeRecord;
 import vip.xiaonuo.biz.modular.userfundchangerecord.service.BizUserFundChangeRecordService;
+import vip.xiaonuo.biz.modular.utils.CommonExportUtil;
 import vip.xiaonuo.common.cache.CommonCacheOperator;
 import vip.xiaonuo.common.enums.CommonSortOrderEnum;
 import vip.xiaonuo.common.exception.CommonException;
@@ -40,11 +46,11 @@ import vip.xiaonuo.biz.modular.consumptionrecord.entity.ConsumptionRecord;
 import vip.xiaonuo.biz.modular.consumptionrecord.mapper.ConsumptionRecordMapper;
 import vip.xiaonuo.biz.modular.consumptionrecord.service.ConsumptionRecordService;
 import vip.xiaonuo.common.util.CommonCryptogramUtil;
+import vip.xiaonuo.dev.api.DevConfigApi;
 
+import java.io.IOException;
 import java.math.BigDecimal;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 
 /**
  * 消费记录Service接口实现类
@@ -64,6 +70,9 @@ public class ConsumptionRecordServiceImpl extends ServiceImpl<ConsumptionRecordM
     @Resource
     private BizUserFundChangeRecordService bizUserFundChangeRecordService;
 
+    @Resource
+    private DevConfigApi devConfigApi;
+
     @Override
     public Page<ConsumptionRecord> page(ConsumptionRecordPageParam consumptionRecordPageParam) {
         QueryWrapper<ConsumptionRecord> queryWrapper = new QueryWrapper<ConsumptionRecord>().checkSqlInjection();
@@ -81,6 +90,16 @@ public class ConsumptionRecordServiceImpl extends ServiceImpl<ConsumptionRecordM
                 ObjectUtil.isNotEmpty(consumptionRecordPageParam.getConsumptionTimeEnd())){
             queryWrapper.between("cr.consumption_time",consumptionRecordPageParam.getConsumptionTimeBegin()+" 00:00:00",consumptionRecordPageParam.getConsumptionTimeEnd()+" 23:59:59");
         }
+        if(ObjectUtil.isNotEmpty(consumptionRecordPageParam.getAdjustType())){
+            queryWrapper.eq("cr.adjust_type",consumptionRecordPageParam.getAdjustType());
+        }
+        if(ObjectUtil.isNotEmpty(consumptionRecordPageParam.getRecordId())){
+            List<String> idList = Lists.newArrayList();
+            bizUserService.list(new QueryWrapper<BizUser>().lambda().
+                    eq(BizUser::getReferralUser, consumptionRecordPageParam.getRecordId())).
+                    forEach(user->idList.add(user.getId()));
+            queryWrapper.in("cr.user_id",idList);
+        }
         // 校验数据范围
         List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
         if (ObjectUtil.isNotEmpty(loginUserDataScope)) {
@@ -88,6 +107,7 @@ public class ConsumptionRecordServiceImpl extends ServiceImpl<ConsumptionRecordM
         } else {
             queryWrapper.in("cr.user_id", StpLoginUserUtil.getLoginUser().getId());
         }
+        queryWrapper.eq("cr.DELETE_FLAG","NOT_DELETE");
         queryWrapper.orderByDesc("cr.create_time");
         Page<ConsumptionRecord> pageList = this.getBaseMapper().getPageList(CommonPageRequest.defaultPage(), queryWrapper);
         for(ConsumptionRecord consumptionRecord : pageList.getRecords()){
@@ -292,11 +312,130 @@ public class ConsumptionRecordServiceImpl extends ServiceImpl<ConsumptionRecordM
             consumptionResult.setOrderMoney(new BigDecimal(0));
             return consumptionResult;
         }
+        queryWrapper.eq("bcr.DELETE_FLAG","NOT_DELETE");
         queryWrapper.in("bcr.consumption_operate",'3','4');
         queryWrapper.between("bcr.consumption_time", format+" 00:00:00",format+" 23:59:59");
         return this.getBaseMapper().getRecordTotal(queryWrapper);
     }
 
+    @Override
+    public ConsumptionResult getRecordTotal(ConsumptionRecordPageParam consumptionRecordPageParam) {
+        QueryWrapper<ConsumptionRecord> queryWrapper = new QueryWrapper<>();
+        if(ObjectUtil.isNotEmpty(consumptionRecordPageParam.getOrgId())){
+            queryWrapper.eq("bcr.consumption_org",consumptionRecordPageParam.getOrgId());
+        }
+        if(ObjectUtil.isNotEmpty(consumptionRecordPageParam.getConsumptionTimeBegin()) && ObjectUtil.isNotEmpty(consumptionRecordPageParam.getConsumptionTimeEnd())){
+            queryWrapper.between("bcr.consumption_time",consumptionRecordPageParam.getConsumptionTimeBegin()+" 00:00:00",consumptionRecordPageParam.getConsumptionTimeEnd()+" 23:59:59");
+        }
+        queryWrapper.eq("bcr.DELETE_FLAG","NOT_DELETE");
+        queryWrapper.in("bcr.consumption_operate","3","4");
+        return this.getBaseMapper().getRecordTotal(queryWrapper);
+    }
+
+
+
+    @Override
+    public UserBalanceResult queryBalanceTotal(ConsumptionRecordPageParam consumptionRecordPageParam) {
+        QueryWrapper<ConsumptionRecord> queryWrapper = new QueryWrapper<>();
+        if(ObjectUtil.isNotEmpty(consumptionRecordPageParam.getOrgId())){
+            queryWrapper.eq("su.ORG_ID",consumptionRecordPageParam.getOrgId());
+        }
+        if(ObjectUtil.isNotEmpty(consumptionRecordPageParam.getConsumptionTimeBegin()) && ObjectUtil.isNotEmpty(consumptionRecordPageParam.getConsumptionTimeEnd())){
+            queryWrapper.between("su.CREATE_TIME",consumptionRecordPageParam.getConsumptionTimeBegin()+" 00:00:00",consumptionRecordPageParam.getConsumptionTimeEnd()+" 23:59:59");
+        }
+        queryWrapper.in("su.USER_TYPE","2","3");
+        queryWrapper.eq("su.DELETE_FLAG","NOT_DELETE");
+        return this.getBaseMapper().queryBalanceTotal(queryWrapper);
+    }
+
+    @Override
+    public Map<String,Object> queryConsumptionChart(ConsumptionRecordPageParam consumptionRecordPageParam) {
+        List<String> timeList = Lists.newArrayList();
+        List<BigDecimal> moneyList = Lists.newArrayList();
+        List<Integer> orderList = Lists.newArrayList();
+        List<Integer> userCountList = Lists.newArrayList();
+        Map<String,Object> result = Maps.newHashMap();
+        QueryWrapper<ConsumptionRecord> queryWrapper = new QueryWrapper<>();
+        List<Map<String, Object>> mapList = this.getBaseMapper().queryConsumptionChart(queryWrapper);
+        for(Map<String,Object> map: mapList){
+            timeList.add(MapUtils.getString(map,"dateTime"));
+            moneyList.add(new BigDecimal(MapUtils.getString(map,"consumptionMoney")));
+            orderList.add(MapUtils.getInteger(map,"consumptionCount"));
+            userCountList.add(MapUtils.getInteger(map,"userCount"));
+        }
+        result.put("dateTime",timeList);
+        result.put("consumptionMoney",moneyList);
+        result.put("consumptionCount",orderList);
+        result.put("userCount",userCountList);
+        return result;
+    }
+
+    @Override
+    public Map<String, Object> queryEachStore(ConsumptionRecordPageParam consumptionRecordPageParam) {
+        Map<String,Object> result = Maps.newHashMap();
+        List<Map<String, Object>> mapList = getList(consumptionRecordPageParam);
+        result.put("dataList",mapList);
+        return result;
+    }
+
+    @Override
+    public Page<BizMemberUserResult> warnPage(ConsumptionRecordPageParam consumptionRecordPageParam) {
+        Page<BizMemberUserResult> warnPageList = new Page();
+        String warnSwitch = devConfigApi.getValueByKey("warnSwitch");
+        if(ObjectUtil.isNotEmpty(warnSwitch) && StringUtils.equals(warnSwitch,"true")){
+            //预警开关开启
+            long warnCount = Long.parseLong(devConfigApi.getValueByKey("warnCount"));
+            QueryWrapper queryWrapper = new QueryWrapper<ConsumptionRecord>();
+            queryWrapper.gt("a.count",warnCount);
+            if(ObjectUtil.isNotEmpty(consumptionRecordPageParam.getUserName())){
+                queryWrapper.like("su.NAME",consumptionRecordPageParam.getUserName());
+            }
+            warnPageList = this.getBaseMapper().getWarnPageList(CommonPageRequest.defaultPage(), queryWrapper);
+            for(BizMemberUserResult bizMemberUserResult : warnPageList.getRecords()){
+                if(ObjectUtil.isNotEmpty(bizMemberUserResult.getPhone())){
+                    bizMemberUserResult.setPhone(CommonCryptogramUtil.doSm4CbcDecrypt(bizMemberUserResult.getPhone()));
+                }
+            }
+        }
+        return warnPageList;
+    }
+
+
+    public List<Map<String, Object>> getList(ConsumptionRecordPageParam consumptionRecordPageParam){
+        List<String> orgIds = new ArrayList<>();
+        if(ObjectUtil.isNotEmpty(consumptionRecordPageParam.getOrgId())){
+            orgIds = Arrays.asList(consumptionRecordPageParam.getOrgId().split(","));
+        }
+        String startTime = null;
+        String endTime = null;
+        if(ObjectUtil.isNotEmpty(consumptionRecordPageParam.getConsumptionTimeBegin())){
+            startTime=consumptionRecordPageParam.getConsumptionTimeBegin()+" 00:00:00";
+        }
+        if(ObjectUtil.isNotEmpty(consumptionRecordPageParam.getConsumptionTimeEnd())){
+            endTime = consumptionRecordPageParam.getConsumptionTimeEnd()+" 23:59:59";
+        }
+        List<Map<String, Object>> mapList = this.getBaseMapper().queryEachStore(orgIds, startTime, endTime);
+        return mapList;
+    }
+
+    @Override
+    public void exportRecord(ConsumptionRecordPageParam consumptionRecordPageParam, HttpServletResponse response) throws IOException {
+        List<Map<String, Object>> mapList = getList(consumptionRecordPageParam);
+        String fileName = "汇总统计报表.xlsx";
+        List<ConsumptionRecordExportResult> list = Lists.newArrayList();
+        for(Map<String,Object> map:mapList){
+            ConsumptionRecordExportResult consumeRecordExportResult = new ConsumptionRecordExportResult();
+            consumeRecordExportResult.setName(MapUtils.getString(map,"name"));
+            consumeRecordExportResult.setCode(MapUtils.getString(map,"code"));
+            consumeRecordExportResult.setOrderCount(MapUtils.getInteger(map,"orderCount"));
+            consumeRecordExportResult.setAccountMoney(new BigDecimal(MapUtils.getString(map,"accountMoney")));
+            consumeRecordExportResult.setVoucherMoney(new BigDecimal(MapUtils.getString(map,"voucherMoney")));
+            consumeRecordExportResult.setUserCount(MapUtils.getInteger(map,"userCount"));
+            list.add(consumeRecordExportResult);
+        }
+        CommonExportUtil.export(fileName, ConsumptionRecordExportResult.class,list,response,"汇总统计报表");
+    }
+
 
     public static void main(String[] args) {
         System.out.printf("加密后手机号:"+CommonCryptogramUtil.doSm4CbcEncrypt("15240260262"));

+ 5 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/couponrecord/param/BizCouponRecordPageParam.java

@@ -56,4 +56,9 @@ public class BizCouponRecordPageParam {
     @Schema(description = "是否核销: 0.否 1.是")
     private Boolean couponStatus;
 
+    private String beginTime;
+    private String endTime;
+
+    private String orgId;
+
 }

+ 6 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/couponrecord/service/impl/BizCouponRecordServiceImpl.java

@@ -56,6 +56,12 @@ public class BizCouponRecordServiceImpl extends ServiceImpl<BizCouponRecordMappe
         if(ObjectUtil.isNotEmpty(bizCouponRecordPageParam.getCouponStatus())) {
             queryWrapper.eq("bcr.coupon_status", bizCouponRecordPageParam.getCouponStatus());
         }
+        if(ObjectUtil.isNotEmpty(bizCouponRecordPageParam.getBeginTime()) && ObjectUtil.isNotEmpty(bizCouponRecordPageParam.getEndTime())){
+            queryWrapper.between("bcr.time",bizCouponRecordPageParam.getBeginTime()+" 00:00:00",bizCouponRecordPageParam.getEndTime()+" 23:59:59");
+        }
+        if(ObjectUtil.isNotEmpty(bizCouponRecordPageParam.getOrgId())){
+            queryWrapper.eq("bcr.org_id",bizCouponRecordPageParam.getOrgId());
+        }
         // 校验数据范围
         List<String> loginUserDataScope = StpLoginUserUtil.getLoginUserDataScope();
         if (ObjectUtil.isEmpty(loginUserDataScope)) {

+ 3 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/user/param/BizUserPageParam.java

@@ -53,4 +53,7 @@ public class BizUserPageParam {
     /** 所属机构 */
     @Schema(description = "所属机构")
     private String orgId;
+
+    private String beginTime;
+    private String endTime;
 }

+ 6 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/user/result/BizMemberUserResult.java

@@ -87,4 +87,10 @@ public class BizMemberUserResult extends CommonEntity {
     @Schema(description = "手机号")
     private String phone;
 
+    /**1:可冻结  2:不可冻结*/
+    private String disableFlag;
+
+    /**退款次数*/
+    private String count;
+
 }

+ 32 - 1
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/user/service/impl/BizUserServiceImpl.java

@@ -50,6 +50,8 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fhs.trans.service.impl.TransService;
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.compress.utils.Lists;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
 import org.springframework.beans.factory.annotation.Value;
@@ -58,6 +60,9 @@ import org.springframework.transaction.annotation.Transactional;
 import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
 import vip.xiaonuo.biz.core.enums.BizBuildInEnum;
 import vip.xiaonuo.biz.core.enums.BizDataTypeEnum;
+import vip.xiaonuo.biz.modular.consumptionrecord.entity.ConsumptionRecord;
+import vip.xiaonuo.biz.modular.consumptionrecord.service.ConsumptionRecordService;
+import vip.xiaonuo.biz.modular.consumptionrecord.service.impl.ConsumptionRecordServiceImpl;
 import vip.xiaonuo.biz.modular.org.entity.BizOrg;
 import vip.xiaonuo.biz.modular.org.service.BizOrgService;
 import vip.xiaonuo.biz.modular.position.entity.BizPosition;
@@ -134,6 +139,8 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
     private String templateCode;
     @Resource
     private BizRecommendRecordMapper bizRecommendRecordMapper;
+    @Resource
+    private ConsumptionRecordService consumptionRecordService;
 
 
     @Override
@@ -180,7 +187,10 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
         if (ObjectUtil.isNotEmpty(bizUserPageParam.getUserStatus())) {
             queryWrapper.lambda().eq(BizUser::getUserStatus, bizUserPageParam.getUserStatus());
         }
-        queryWrapper.lambda().eq(BizUser::getUserType, 3);
+        if (ObjectUtil.isNotEmpty(bizUserPageParam.getBeginTime()) && ObjectUtil.isNotEmpty(bizUserPageParam.getEndTime())){
+            queryWrapper.lambda().between(BizUser::getCreateTime,bizUserPageParam.getBeginTime()+ " 00:00:00",bizUserPageParam.getEndTime()+" 23:59:59");
+        }
+        queryWrapper.lambda().in(BizUser::getUserType, 3,2);
         queryWrapper.lambda().eq(CommonEntity::getDeleteFlag, CommonDeleteFlagEnum.NOT_DELETE);
         queryWrapper.lambda().orderByDesc(CommonEntity::getCreateTime);
         Page<BizMemberUserResult> bizMemberUserResultPage = baseMapper.memberPage(CommonPageRequest.defaultPage(), queryWrapper);
@@ -188,6 +198,27 @@ public class BizUserServiceImpl extends ServiceImpl<BizUserMapper, BizUser> impl
             if (ObjectUtil.isNotEmpty(bizMemberUserResult.getPhone())) {
                 bizMemberUserResult.setPhone(CommonCryptogramUtil.doSm4CbcDecrypt(bizMemberUserResult.getPhone()));
             }
+            String warnSwitch = devConfigApi.getValueByKey("warnSwitch");
+            if(ObjectUtil.isNotEmpty(warnSwitch) && StringUtils.equals(warnSwitch,"true")){
+                //查询推荐的所有用户id
+                List<String> idList = Lists.newArrayList();
+                this.list(new QueryWrapper<BizUser>().lambda().eq(BizUser::getReferralUser, bizMemberUserResult.getId())).forEach(user->idList.add(user.getId()));
+                if(ObjectUtil.isNotEmpty(idList)){
+                    long count = consumptionRecordService.count(new QueryWrapper<ConsumptionRecord>().lambda().
+                            eq(ConsumptionRecord::getConsumptionOperate, "2").
+                            eq(ConsumptionRecord::getAdjustType, "2").
+                            in(ConsumptionRecord::getUserId, idList));
+                    if(count > Long.parseLong(devConfigApi.getValueByKey("warnCount"))){
+                        //如果查询到的退款次数大于配置的次数,开始预警
+                        bizMemberUserResult.setDisableFlag("1");
+                    }else{
+                        bizMemberUserResult.setDisableFlag("2");
+                    }
+                }
+            }else{
+                bizMemberUserResult.setDisableFlag("2");
+            }
+
         }
         return bizMemberUserResultPage;
     }

+ 111 - 0
snowy-plugin/snowy-plugin-biz/src/main/java/vip/xiaonuo/biz/modular/utils/CommonExportUtil.java

@@ -0,0 +1,111 @@
+package vip.xiaonuo.biz.modular.utils;
+
+import cn.hutool.core.io.FileUtil;
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.metadata.data.WriteCellData;
+import com.alibaba.excel.write.handler.CellWriteHandler;
+import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
+import com.alibaba.excel.write.metadata.style.WriteCellStyle;
+import com.alibaba.excel.write.metadata.style.WriteFont;
+import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
+import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
+import com.alibaba.excel.write.style.row.AbstractRowHeightStyleStrategy;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.poi.ss.usermodel.*;
+import vip.xiaonuo.common.util.CommonDownloadUtil;
+import java.io.File;
+import java.util.List;
+
+public class CommonExportUtil {
+
+    public static void export(String fileName, Class c, List list, HttpServletResponse response , String sheetName){
+        File tempFile = null;
+        try {
+            // 创建临时文件
+            tempFile = FileUtil.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
+
+            // 头的策略
+            WriteCellStyle headWriteCellStyle = new WriteCellStyle();
+            WriteFont headWriteFont = new WriteFont();
+            headWriteFont.setFontHeightInPoints((short) 14);
+            headWriteCellStyle.setWriteFont(headWriteFont);
+            // 水平垂直居中
+            headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
+            headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+
+            // 内容的策略
+            WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
+            // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
+            contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
+            // 内容背景白色
+            contentWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
+            WriteFont contentWriteFont = new WriteFont();
+
+            // 内容字体大小
+            contentWriteFont.setFontHeightInPoints((short) 12);
+            contentWriteCellStyle.setWriteFont(contentWriteFont);
+
+            //设置边框样式,细实线
+            contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);
+            contentWriteCellStyle.setBorderTop(BorderStyle.THIN);
+            contentWriteCellStyle.setBorderRight(BorderStyle.THIN);
+            contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);
+
+            // 水平垂直居中
+            contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);
+            contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+
+            // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
+            HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle,
+                    contentWriteCellStyle);
+
+            // 写excel
+            EasyExcel.write(tempFile.getPath(), c)
+                    // 自定义样式
+                    .registerWriteHandler(horizontalCellStyleStrategy)
+                    // 自动列宽
+                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+                    // 设置第一行字体
+                    .registerWriteHandler(new CellWriteHandler() {
+                        @Override
+                        public void afterCellDispose(CellWriteHandlerContext context) {
+                            WriteCellData<?> cellData = context.getFirstCellData();
+                            WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
+                            Integer rowIndex = context.getRowIndex();
+                            if(rowIndex == 0) {
+                                WriteFont headWriteFont = new WriteFont();
+                                headWriteFont.setFontName("宋体");
+                                headWriteFont.setBold(true);
+                                headWriteFont.setFontHeightInPoints((short) 16);
+                                writeCellStyle.setWriteFont(headWriteFont);
+                            }
+                        }
+                    })
+                    // 设置表头行高
+                    .registerWriteHandler(new AbstractRowHeightStyleStrategy() {
+                        @Override
+                        protected void setHeadColumnHeight(Row row, int relativeRowIndex) {
+                            if(relativeRowIndex == 0) {
+                                // 表头第一行
+                                row.setHeightInPoints(34);
+                            } else {
+                                // 表头其他行
+                                row.setHeightInPoints(30);
+                            }
+                        }
+                        @Override
+                        protected void setContentColumnHeight(Row row, int relativeRowIndex) {
+                            // 内容行
+                            row.setHeightInPoints(20);
+                        }
+                    })
+                    .sheet(sheetName)
+                    .doWrite(list);
+            CommonDownloadUtil.download(tempFile, response);
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            FileUtil.del(tempFile);
+        }
+    }
+}