更新优化商品耗材、库存

This commit is contained in:
gyq
2026-04-14 18:29:20 +08:00
parent 50a5aeb8e5
commit fa67997c86
9 changed files with 515 additions and 28411 deletions

View File

@@ -104,7 +104,6 @@ export function shopStoragePut(data) {
});
}
/**
* 查询存取酒记录
* @param {*} data
@@ -118,3 +117,17 @@ export function shopStorageRecord(params) {
});
}
/**
* 耗材库存列表接口
* @param {*} data
* @returns
*/
export function consStock(params) {
return request({
method: "get",
url: "/product/admin/product/cons/consStock",
params,
});
}

View File

@@ -0,0 +1,83 @@
<template>
<el-dialog title="提示" width="450px" v-model="visible">
<div class="refund_content">
<div class="title_wrap">请确认当前菜品是否已上菜</div>
<div class="list_wrap">
<div class="item" v-for="(item, index) in list" :key="index">
<span>{{ item.name }}</span>
<span>x{{ item.num }}</span>
</div>
</div>
</div>
<div class="dialog_footer">
<div class="btn">
<el-button @click="handleCancel" style="width: 100%;">未上菜退还库存</el-button>
</div>
<div class="btn">
<el-button type="primary" @click="handleOk" style="width: 100%;">已上菜不退库存</el-button>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue'
const visible = ref(false)
const props = defineProps({
list: {
type: Array,
default: () => []
}
})
const emits = defineEmits(['success'])
// 未上菜 1退菜图库存
function handleCancel() {
visible.value = false
emits('success', 1)
}
// 已上菜 2仅退菜不退库存
function handleOk() {
visible.value = false
emits('success', 2)
}
function show() {
visible.value = true
}
defineExpose({
show
})
</script>
<style scoped lang="scss">
.refund_content {
.title_wrap {
font-size: 16px;
}
.list_wrap {
padding-top: 14px;
.item {
display: flex;
justify-content: space-between;
}
}
}
.dialog_footer {
display: flex;
justify-content: center;
gap: 20px;
padding-top: 20px;
.btn {
flex: 1;
}
}
</style>

View File

@@ -18,7 +18,7 @@
<span class="i">¥</span>
<span class="n">{{ formatDecimal(+goodsInfo.salePrice) }}</span>
</div>
<span>库存:{{ stockNumber }}</span>
<span>库存:{{ stockNumber || 0 }}</span>
</div>
<div class="btn_wrap">
<div class="btn">

View File

@@ -2,7 +2,7 @@ import _ from "lodash";
import dayjs from "dayjs";
import { defineStore } from "pinia";
import { ref, computed, nextTick } from "vue";
import { productPage, categoryList } from "@/api/product_new.js";
import { productPage, categoryList, consStock } from "@/api/product_new.js";
import { historyOrder, cancelOrder, rmPlaceOrder } from "@/api/order.js";
import { getLimitTimeDiscount, getDiscountActivity, getDiscountByUserId } from '@/api/market'
import { useUser } from "@/store/user.js";
@@ -62,6 +62,7 @@ export const useGoods = defineStore("goods", {
goodsListLoading: false, // 商品列表加载状态
goodsList: [], // 商品列表
originGoodsList: [], // 原始商品列表
consList: [], // 耗材列表
orderList: [], // 订单列表
orderListInfo: "", // 历史订单信息
cartType: "cart", // cart order
@@ -88,7 +89,6 @@ export const useGoods = defineStore("goods", {
// 清除会员信息
async clearVipUserInfo() {
// console.log('清除会员信息');
this.vipUserInfo = {};
this.showVipPrice = 0;
@@ -304,8 +304,18 @@ export const useGoods = defineStore("goods", {
console.log(error);
}
},
// 耗材库存列表接口
async consStockAjax() {
try {
const store = useUser()
this.consList = await consStock({ shopId: store.shopInfo.id })
} catch (error) {
console.log(error);
}
},
// 初始化商品
async initGoods() {
await this.consStockAjax()
await this.getCategoryList();
await this.getGoodsList();
@@ -373,6 +383,50 @@ export const useGoods = defineStore("goods", {
console.log(error);
}
},
/**
* 给商品列表批量添加 isSoldOut 售罄状态字段
* @param {Array} goodsList - 商品列表 [ { consList, isAutoSoldStock } ]
* @param {Array} consStockList - 真实耗材库存列表 [ { consId, stockNumber } ]
* @returns 带 isSoldOut 字段的新商品列表
*/
addGoodsSoldOutStatus(goodsList, consStockList) {
// console.log('addGoodsSoldOutStatus.goodsList', goodsList);
// console.log('addGoodsSoldOutStatus.consStockList', consStockList);
// 耗材ID映射真实库存保留
const consMap = _.keyBy(consStockList, item => String(item.consId));
return _.map(goodsList, goods => {
let isSoldOut = false;
// 开启自动售罄才判断
if (goods.isAutoSoldStock === 1 || goods.isAutoSoldStock === true) {
const goodsConsList = goods.consList || [];
// 无耗材 → 不售罄
if (goodsConsList.length === 0) {
isSoldOut = false;
} else {
// 核心:只要有一个耗材 真实库存 < 商品需要量 → 售罄
isSoldOut = _.some(goodsConsList, consItem => {
// 商品绑定的耗材ID对应真实库存ID
const consId = String(consItem.consInfoId);
// 商品需要消耗的数量(你的需求量)
const needStock = consItem.surplusStock || 0;
// 起售数量
const suitNum = goods.type == 'single' ? goods.skuList[0].suitNum : 1;
// 真实库存
const realStock = _.get(consMap, [consId, 'stockNumber'], 0);
// 真实库存 < 需要量 → 不足 → 售罄
return realStock < needStock * suitNum;
});
}
}
return { ...goods, isSoldOut };
});
},
// 获取商品列表/更新商品列表
async getGoodsList() {
try {
@@ -388,7 +442,11 @@ export const useGoods = defineStore("goods", {
name: this.goodsName,
});
this.originGoodsList = await productPage();
let ores = await productPage();
this.originGoodsList = this.addGoodsSoldOutStatus(ores, this.consList)
console.log('添加库存售罄的商品原始数据', this.originGoodsList);
// 获取限时折扣
this.limitDiscountRes = await getLimitTimeDiscount({ shopId: store.shopInfo.id })
@@ -443,18 +501,6 @@ export const useGoods = defineStore("goods", {
this.goodsList.forEach(val => {
val.forEach((item, index) => {
// console.log('this.goodsList.index', index);
// console.log('this.goodsList.item', item);
// console.log('this.goodsList.limitDiscountRes', this.limitDiscountRes);
// console.log('this.goodsList.store.shopInfo', store.shopInfo);
// console.log('this.goodsList.this.vipUserInfo', this.vipUserInfo);
// console.log('this.goodsList.this.vipUserInfo', JSON.stringify(this.vipUserInfo));
// console.log('this.goodsList.this.canUseLimitTimeDiscount', limitUtils.canUseLimitTimeDiscount(item,
// this.limitDiscountRes,
// store.shopInfo,
// this.vipUserInfo, 'id'));
item.showMore = false;
item.orderCount = 0;
item.memberPrice = item.lowMemberPrice

View File

@@ -112,11 +112,61 @@ export const useSocket = defineStore("socket", {
switch (data.operate_type) {
case "add":
// 添加购物车商品
goodsStore.successAddCart(data.data);
{
// console.log('socket终极操作数据.add===', data.data);
const cartItem = goodsStore.cartList.find(item => item.id == data.data.id) || { number: 0 }
// console.log('socket终极操作数据.cartItem===', cartItem);
let arr = []
goodsStore.orderList.forEach(item => {
arr.push(...item.goods)
})
const isExist = _.some(arr, item => Number(item.productId) === data.data.product_id)
if (data.data.number == 1 && data.data.number > cartItem.number) {
if (isExist) {
ElMessage({
type: 'warning',
message: '该商品已下单过,请确认是否重复',
duration: 4000
})
}
}
goodsStore.successAddCart(data.data);
}
break;
case "edit":
// 编辑购物车商品
goodsStore.successEditCart(data.data);
{
// 编辑购物车商品
// console.log('socket终极操作数据.edit===', data.data);
const cartItem = goodsStore.cartList.find(item => item.id == data.data.id)
// console.log('socket终极操作数据.cartItem===', cartItem);
let arr = []
goodsStore.orderList.forEach(item => {
arr.push(...item.goods)
})
const isExist = _.some(arr, item => Number(item.productId) === data.data.product_id)
if (data.data.number == 2 && data.data.number > cartItem.number) {
if (isExist) {
ElMessage({
type: 'warning',
message: '该商品已下单过,请确认是否重复',
duration: 4000
})
} else {
ElMessage({
type: 'warning',
message: '购物车已有该商品,请确认是否重复',
duration: 4000
})
}
}
goodsStore.successEditCart(data.data);
}
break;
case "del":
// 删除购物车商品

View File

@@ -248,6 +248,8 @@
</div>
</div>
</el-dialog>
<!-- 退款推库存的操作弹窗 -->
<refundConsModal ref="refundConsModalRef" :list="refundList" @success="refundConsModalSuccess" />
</template>
<script setup>
@@ -263,6 +265,9 @@ import { refundOrder } from '@/api/order.js'
import { useSocket } from '@/store/socket.js'
import { usePrint } from '@/store/print.js'
import { useUser } from '@/store/user.js'
import refundConsModal from '@/components/refundConsModal.vue'
const refundConsModalRef = ref(null)
const goodsStore = useGoods()
const socket = useSocket()
@@ -316,15 +321,21 @@ async function returnFormSubmit() {
returnFormLoading.value = true
await returnOrderItemAjax(returnForm.value.num)
showReturnForm.value = false
ElMessage.success('退菜成功')
// ElMessage.success('退菜成功')
} catch (error) {
console.log('退菜失败了');
}
returnFormLoading.value = false
}
// 提交退菜
async function returnOrderItemAjax(num = 1) {
const refundStock = ref('')
// 退款推库存的操作
function refundConsModalSuccess(e) {
refundStock.value = e
refundNext()
}
async function refundNext() {
try {
let data = {
orderId: goodsStore.orderListInfo.id,
@@ -336,23 +347,25 @@ async function returnOrderItemAjax(num = 1) {
{
id: goodsStore.cartOrderItem.id,
returnAmount: goodsStore.cartOrderItem.lowPrice,
num: num
num: refundNum.value
}
],
operator: store.userInfo.name || store.shopInfo.shopName,
print: printStore.deviceNoteList.length ? false : true
print: printStore.deviceNoteList.length ? false : true,
refundStock: refundStock.value, // 是否推库存 1退菜图库存 2仅退菜不退库存
}
await refundOrder(data)
goodsStore.cartOrderItem.returnNum += num
ElMessage.success('退菜成功')
goodsStore.cartOrderItem.returnNum += refundNum.value
goodsStore.calcCartInfo()
getOrderByIdAjax(goodsStore.orderListInfo.id).then(res => {
let originOrderInfo = res
let index = originOrderInfo.cartList.findIndex(item => item.id == goodsStore.cartOrderItem.id)
originOrderInfo.cartList = _.at(originOrderInfo.cartList, index);
originOrderInfo.cartList[0].num = num
originOrderInfo.cartList[0].num = refundNum.value
originOrderInfo.cartList[0].returnNum = 0
originOrderInfo.cartList[0].payAmount = num * originOrderInfo.cartList[0].price
originOrderInfo.cartList[0].payAmount = refundNum.value * originOrderInfo.cartList[0].price
printStore.printRefundDish(commOrderPrintData({ ...originOrderInfo, isRefundDish: true }));
}).catch(err => {
@@ -364,6 +377,51 @@ async function returnOrderItemAjax(num = 1) {
}
}
// 提交退菜
const refundNum = ref(1)
const refundList = ref([])
async function returnOrderItemAjax(num = 1) {
try {
refundNum.value = num
let item = goodsStore.cartOrderItem
console.log('returnOrderItemAjax===', item);
// 在这里给订单的商品补全库存信息 start
goodsStore.originGoodsList.forEach(val => {
if (item.productId == val.id) {
if (store.shopInfo.refundMode == 1) {
// 跟随分类退款模式
goodsStore.categoryList.forEach(v => {
if (val.categoryId == v.id) {
item.refundMode = v.refundMode
}
})
} else {
// 跟随商品退款模式及
item.refundMode = val.refundMode
}
}
})
console.log('item===', item);
if (item.refundMode == 3) {
refundList.value = [
{
name: item.product_name,
num: refundNum.value
}
]
refundConsModalRef.value.show()
return
}
refundNext()
// 在这里给订单的商品补全库存信息 end
} catch (error) {
console.log(error);
}
}
// 显示打包
function packHandle() {
let item = goodsStore.cartList[goodsStore.cartActiveIndex]

View File

@@ -95,7 +95,7 @@
<img class="sell_out_icon" src="@/assets/icon_goods_wks.svg">
</div>
<!-- 售罄 -->
<div class="sell_out" v-else-if="item.isSoldStock">
<div class="sell_out" v-else-if="item.isSoldStock || item.isSoldOut">
<img class="sell_out_icon" src="@/assets/icon_goods_sq.svg">
</div>
<!-- 库存不足 -->
@@ -559,7 +559,7 @@ function showSkuHandle(item) {
message: '不在可售时间内',
})
return
} else if (item.isSoldStock) {
} else if (item.isSoldStock || item.isSoldOut) {
ElMessage({
type: 'error',
message: '该商品已售罄',

View File

@@ -73,8 +73,8 @@
</template>
</el-table-column>
</el-table>
<!-- <template v-if="item.returnGoods.length">
<div class="tips" style="margin-top: 20px;padding-bottom: 10px;">以下为已退部分退单/退</div>
<template v-if="item.returnGoods.length">
<div class="tips" style="margin-top: 20px;padding-bottom: 10px;">以下已完成退菜/退</div>
<el-table :data="item.returnGoods" brder stripe>
<el-table-column label="商品信息">
<template v-slot="scope">
@@ -105,7 +105,7 @@
</template>
</el-table-column>
</el-table>
</template> -->
</template>
<div class="ipt">
<el-input type="textarea" :rows="4" v-model="remark" placeholder="请输入退单原因" />
</div>
@@ -126,17 +126,19 @@
<div class="drawer_footer">
<div class="btn">
<el-button type="danger" style="width: 100%;" :loading="loading"
@click="handleRefund">手动退款</el-button>
:disabled="item.onGoods && !item.onGoods.length" @click="handleRefund">手动退款</el-button>
</div>
<div class="btn">
<el-button type="primary" style="width: 100%;" :loading="loading"
@click="refundHandle()">原路退回</el-button>
:disabled="item.onGood && !item.onGoods.length" @click="refundHandle()">原路退回</el-button>
</div>
</div>
</template>
</el-drawer>
<takeFoodCode ref="takeFoodCodeRef" title="退款密码" :type="2" input-type="password" placeholder="请输入退款密码"
@success="passwordSuccess" />
<!-- 退款退菜推库存的操作弹窗 -->
<refundConsModal ref="refundConsModalRef" :list="returnList" @success="refundConsModalSuccess" />
</template>
<script setup>
@@ -150,9 +152,13 @@ import { usePrint } from "@/store/print.js";
import { useUser } from '@/store/user.js'
import dayjs from 'dayjs'
import takeFoodCode from "@/components/takeFoodCode.vue";
import { useGoods } from '@/store/goods.js'
import refundConsModal from '@/components/refundConsModal.vue';
const refundConsModalRef = ref(null)
const emits = defineEmits(['success'])
const goodsStore = useGoods()
const store = useUser()
const printStore = usePrint();
const globalStore = useGlobal()
@@ -176,43 +182,83 @@ const takeFoodCodeRef = ref(null)
const cash = ref(false)
const amountInputRef = ref(null)
// 退款密码
async function passwordSuccess(e = '') {
try {
loading.value = true
let rows = tableRef.value.getSelectionRows()
let refundDetails = []
// if (refundType.value != 1) {
refundDetails = tableRef.value.getSelectionRows().map(val => {
return {
id: val.id,
returnAmount: val.payAmount,
num: val.refund_number
}
})
// }
// 退款推库存的操作
function refundConsModalSuccess(e) {
refundStock.value = e
refundNext()
}
// 退款密码
const returnList = ref([])
const refundDetails = ref([])
const pwd = ref('')
const refundStock = ref('')
const rows = ref([])
async function passwordSuccess(e = '') {
pwd.value = e
loading.value = true
rows.value = tableRef.value.getSelectionRows()
// if (refundType.value != 1) {
refundDetails.value = tableRef.value.getSelectionRows().map(val => {
return {
refundMode: val.refundMode,
name: val.productName,
id: val.id,
returnAmount: val.payAmount,
num: val.refund_number
}
})
// }
// 处理退菜退款的库存逻辑 判断有没有returnMode = 3,然后弹窗提示 start
console.log('refundDetails===', refundDetails.value);
refundDetails.value.forEach(item => {
if (item.refundMode == 3) {
returnList.value.push({
name: item.name,
num: item.num
})
}
})
console.log('returnList===', returnList.value);
// 显示退菜退款的弹窗
if (returnList.value.length > 0) {
refundConsModalRef.value.show()
return
}
// 处理退菜退款的库存逻辑 判断有没有returnMode = 3,然后弹窗提示 end
refundNext()
}
// 最后的退款操作
async function refundNext() {
try {
let data = {
orderId: item.value.id,
refundAmount: formatDecimal(+refundAmount.value),
modify: modify.value,
cash: cash.value,
refundReason: remark.value,
refundDetails: refundDetails,
pwd: e,
refundDetails: refundDetails.value,
pwd: pwd.value,
operator: store.userInfo.name || store.shopInfo.shopName,
print: printStore.deviceNoteList.length ? false : true
print: printStore.deviceNoteList.length ? false : true,
refundStock: refundStock.value, // 是否推库存 1退菜图库存 2仅退菜不退库存
};
await refundOrder(data)
ElMessage.success('退款成功')
await printRefund(rows)
await printRefund(rows.value)
isShow.value = false
emits('success')
} catch (error) {
console.log(error);
} finally {
loading.value = false
}
loading.value = false
}
// 显示手动退款
@@ -349,6 +395,29 @@ function show(row) {
isShow.value = true
let newRow = { ...row }
// 在这里给订单的商品补全库存信息 start
console.log('originGoodsList===', goodsStore.originGoodsList);
newRow.goods.forEach(item => {
goodsStore.originGoodsList.forEach(val => {
if (item.productId == val.id) {
if (store.shopInfo.refundMode == 1) {
// 跟随分类退款模式
goodsStore.categoryList.forEach(v => {
if (val.categoryId == v.id) {
item.refundMode = v.refundMode
}
})
} else {
// 跟随商品退款模式及
item.refundMode = val.refundMode
}
}
})
})
console.log('newRow.goods===', newRow.goods);
// 在这里给订单的商品补全库存信息 end
remark.value = ''
let onGoods = []
@@ -372,7 +441,11 @@ function show(row) {
// returnGoods.push(item)
// }
// 可以操作的退款数量
onGoods.push(item)
if (refundMaxNum > 0) {
onGoods.push(item)
} else {
returnGoods.push(item)
}
})
newRow.onGoods = onGoods