shish 4 days ago
parent
commit
f6602b19cf

+ 137 - 0
ghsApp/src/admin/billing/forwardResult.vue

@@ -0,0 +1,137 @@
+<!--
+  冲销成功结果页(方案A)
+  用途:create 成功后跳转;fundType=2(返余额)时展示收款小程序码海报,其它仅简单成功提示。
+-->
+<template>
+	<appResult title="冲销成功">
+		<view class="forward-info" v-if="Number(fundType) === 2">
+			<text class="forward-info__amount">已返充 ¥{{ actPrice }} 到 {{ customName || '客户' }} 余额</text>
+			<text class="forward-info__sn" v-if="forwardSn">冲销单号 {{ forwardSn }}</text>
+		</view>
+		<view class="forward-info" v-else>
+			<text class="forward-info__amount">冲销成功 ¥{{ actPrice }}</text>
+			<text class="forward-info__sn" v-if="forwardSn">冲销单号 {{ forwardSn }}</text>
+			<text class="forward-info__tip">{{ fundTypeTip }}</text>
+		</view>
+
+		<!-- 返余额:展示小程序码海报,客户可扫码充值 -->
+		<view class="poster-wrap" v-if="Number(fundType) === 2 && miniCodeUrl">
+			<image class="poster-img" :src="miniCodeUrl" mode="widthFix" show-menu-by-longpress />
+			<text class="poster-tip">长按图片可转发或保存,客户扫码向您充值</text>
+		</view>
+
+		<button class="admin-button-com big blue" v-if="orderId" @click="goOrder">查看原单</button>
+		<button class="admin-button-com big" @click="goHome">返回首页</button>
+	</appResult>
+</template>
+
+<script>
+import appResult from '@/components/app-result'
+import { getGatheringMiniCode } from '@/api/custom'
+
+export default {
+	name: 'forwardResult',
+	components: { appResult },
+	data() {
+		return {
+			fundType: 0,
+			actPrice: 0,
+			customId: 0,
+			customName: '',
+			forwardSn: '',
+			forwardId: 0,
+			orderId: 0,
+			miniCodeUrl: ''
+		}
+	},
+	computed: {
+		fundTypeTip() {
+			const map = { 1: '已发起原路退回', 2: '已返充余额', 3: '仅记账,未动资金' }
+			return map[Number(this.fundType)] || ''
+		}
+	},
+	onLoad(option) {
+		this.fundType = Number(option.fundType || 0)
+		this.actPrice = parseFloat(option.amount || option.actPrice || 0)
+		this.customId = Number(option.customId || 0)
+		this.customName = option.customName || ''
+		this.forwardSn = option.forwardSn || ''
+		this.forwardId = Number(option.forwardId || 0)
+		this.orderId = Number(option.orderId || 0)
+		// 返余额时拉取收款小程序码做海报
+		if (this.fundType === 2 && this.customId > 0) {
+			this.loadMiniCode()
+		}
+	},
+	methods: {
+		loadMiniCode() {
+			getGatheringMiniCode({ id: this.customId }).then(res => {
+				if (res.code == 1 && res.data && res.data.imgUrl) {
+					this.miniCodeUrl = res.data.imgUrl
+				}
+			})
+		},
+		goOrder() {
+			if (!this.orderId) {
+				return
+			}
+			this.$util.pageTo({ url: '/pagesOrder/detail', type: 2, query: { id: this.orderId } })
+		},
+		goHome() {
+			this.$util.pageTo({ url: '/admin/home/workbench', type: 2 })
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.forward-info {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	margin-bottom: 30upx;
+
+	&__amount {
+		font-size: 32upx;
+		font-weight: bold;
+		color: #049e2c;
+		text-align: center;
+	}
+
+	&__sn {
+		margin-top: 12upx;
+		font-size: 26upx;
+		color: #666;
+	}
+
+	&__tip {
+		margin-top: 12upx;
+		font-size: 26upx;
+		color: #999;
+	}
+}
+
+.poster-wrap {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	margin-bottom: 30upx;
+}
+
+.poster-img {
+	width: 420upx;
+	background: #fff;
+	border-radius: 12upx;
+}
+
+.poster-tip {
+	margin-top: 16upx;
+	font-size: 24upx;
+	color: #999;
+	text-align: center;
+}
+
+.admin-button-com {
+	margin-top: 20upx;
+}
+</style>

+ 10 - 0
ghsApp/src/admin/custom/selectCustom.vue

@@ -115,7 +115,17 @@ export default {
       let book = this.option.book ? this.option.book : 0
       //显示所有花材,包括没库存的花材
       let getAll = this.option.getAll ? this.option.getAll : 0
+      // forward=1:自由冲销选客,进入凭证确认页(非负销售开单)
+      let forward = this.option.forward ? this.option.forward : 0
       this.selectStyle = style
+      if(Number(forward) === 1){
+        this.$util.pageTo({
+          url: '/pagesOrder/forwardConfirm',
+          type: 2,
+          query: { customId: item.id, customName: item.name }
+        })
+        return
+      }
       if(style == 1){
         //选择客户并开单情况
         if(this.$util.isScanEnv() || scanEnv == 1){

+ 5 - 0
ghsApp/src/api/custom/index.js

@@ -12,6 +12,11 @@ export const getGatheringCode = data => {
 	return https.get('/custom/get-gathering-code', data)
 }
 
+/** 客户向供货商充值的小程序码(冲销返余额海报用) */
+export const getGatheringMiniCode = data => {
+	return https.get('/custom/get-gathering-mini-code', data)
+}
+
 export const getCustomBalanceChange = data => {
 	return https.get('/custom-balance-change/list', data)
 }

+ 30 - 0
ghsApp/src/api/forward/index.js

@@ -0,0 +1,30 @@
+/**
+ * ghsApp 冲销凭证 API(方案A)
+ * 用途:创建/预检/按原单列表/详情;对接 app-ghs ForwardController,不走负销售单。
+ */
+import https from '@/plugins/luch-request_0.0.7/request'
+
+/** 创建冲销凭证:orderId / refundId / customId 三选一入口 */
+export const createForward = data => {
+	return https.post('/forward/create', data)
+}
+
+/** 按原销售单查冲销凭证列表 */
+export const listByOrder = data => {
+	return https.get('/forward/list-by-order', data)
+}
+
+/** 冲销凭证详情(含明细) */
+export const forwardDetail = data => {
+	return https.get('/forward/detail', data)
+}
+
+/** 原单是否可冲销(售后页预检) */
+export const checkEligible = data => {
+	return https.get('/forward/check-eligible', data)
+}
+
+/** 待审售后是否可转冲销 */
+export const checkRefundEligible = data => {
+	return https.get('/forward/check-refund-eligible', data)
+}

+ 308 - 0
ghsApp/src/components/forward-options-popup.vue

@@ -0,0 +1,308 @@
+<!--
+  冲销凭证选项弹框(方案A)
+  用途:售后转冲销 / 原单冲销时选择资金处理与是否回库存;由 refund / refundDetail / forwardConfirm 复用。
+-->
+<template>
+	<uni-popup ref="popupRef" background-color="#fff" type="center" :animation="false" class="class-popup-style">
+		<view class="forward-popup">
+			<view class="forward-popup__title">冲销确认</view>
+
+			<view class="forward-popup__row">
+				<view class="forward-popup__label">退款方式</view>
+				<view class="forward-popup__value forward-popup__value--locked">{{ payWayLabel }}(原单支付方式)</view>
+			</view>
+
+			<!-- 线上微信/支付宝:原路退 与 返余额互斥,可选仅记账 -->
+			<view class="forward-popup__row" v-if="isOnlinePay">
+				<view class="forward-popup__label">资金处理</view>
+				<view class="forward-popup__value">
+					<button
+						class="admin-button-com middle forward-popup__btn"
+						:class="fundType === 1 ? 'blue' : 'default'"
+						@click="pickFundType(1)"
+					>原路退</button>
+					<button
+						class="admin-button-com middle forward-popup__btn forward-popup__btn--ml"
+						:class="fundType === 2 ? 'blue' : 'default'"
+						@click="pickFundType(2)"
+					>返余额</button>
+					<button
+						class="admin-button-com middle forward-popup__btn forward-popup__btn--ml"
+						:class="fundType === 3 ? 'blue' : 'default'"
+						@click="pickFundType(3)"
+					>仅记账</button>
+				</view>
+			</view>
+
+			<!-- 挂账/余额:强制返充余额,只读展示 -->
+			<view class="forward-popup__row" v-else-if="isDebtOrBalance">
+				<view class="forward-popup__label">资金处理</view>
+				<view class="forward-popup__value forward-popup__value--locked">自动返充余额</view>
+			</view>
+
+			<!-- 现金等其它:返余额 / 仅记账 -->
+			<view class="forward-popup__row" v-else>
+				<view class="forward-popup__label">资金处理</view>
+				<view class="forward-popup__value">
+					<button
+						class="admin-button-com middle forward-popup__btn"
+						:class="fundType === 2 ? 'blue' : 'default'"
+						@click="pickFundType(2)"
+					>返余额</button>
+					<button
+						class="admin-button-com middle forward-popup__btn forward-popup__btn--ml"
+						:class="fundType === 3 ? 'blue' : 'default'"
+						@click="pickFundType(3)"
+					>仅记账</button>
+				</view>
+			</view>
+
+			<view class="forward-popup__row">
+				<view class="forward-popup__label">库存</view>
+				<view class="forward-popup__value">
+					<button
+						class="admin-button-com middle forward-popup__btn"
+						:class="forwardStock === 0 ? 'blue' : 'default'"
+						@click="forwardStock = 0"
+					>回库存</button>
+					<button
+						class="admin-button-com middle forward-popup__btn forward-popup__btn--ml"
+						:class="forwardStock === 1 ? 'blue' : 'default'"
+						@click="forwardStock = 1"
+					>不回库存</button>
+				</view>
+			</view>
+
+			<view class="forward-popup__row">
+				<view class="forward-popup__label">备注</view>
+				<view class="forward-popup__value">
+					<input
+						class="forward-popup__input"
+						type="text"
+						v-model="remark"
+						placeholder="选填"
+						placeholder-style="color:#999"
+					/>
+				</view>
+			</view>
+
+			<view class="forward-popup__row forward-popup__row--amount">
+				<view class="forward-popup__label">冲销金额</view>
+				<view class="forward-popup__value forward-popup__amount">¥{{ displayAmount }}</view>
+			</view>
+
+			<view class="forward-popup__btns">
+				<button class="admin-button-com big default forward-popup__submitBtn" @click="onCancel">取消</button>
+				<button class="admin-button-com big blue forward-popup__submitBtn forward-popup__submitBtn--ml" @click="onConfirm">确认冲销</button>
+			</view>
+		</view>
+	</uni-popup>
+</template>
+
+<script>
+export default {
+	name: 'forwardOptionsPopup',
+	props: {
+		// 控制弹框显隐,父组件改 show 即可
+		show: {
+			type: Boolean,
+			default: false
+		},
+		// 原单支付方式:0微信 1支付宝 2余额 3挂账 4现金 5银行卡
+		payWay: {
+			type: [Number, String],
+			default: 0
+		},
+		// 是否线上支付:2=线上(与订单详情一致)
+		onlinePay: {
+			type: [Number, String],
+			default: 0
+		},
+		// 本次冲销金额,仅展示
+		amount: {
+			type: [Number, String],
+			default: 0
+		}
+	},
+	data() {
+		return {
+			fundType: 0, // 1原路退 2返余额 3仅记账
+			forwardStock: -1, // -1未选 0回库存 1不回
+			remark: ''
+		}
+	},
+	computed: {
+		displayAmount() {
+			const n = Number(this.amount)
+			return isNaN(n) ? '0' : parseFloat(n.toFixed(2))
+		},
+		// 线上微信/支付宝才允许原路退
+		isOnlinePay() {
+			const way = Number(this.payWay)
+			return Number(this.onlinePay) === 2 && (way === 0 || way === 1)
+		},
+		isDebtOrBalance() {
+			const way = Number(this.payWay)
+			return way === 2 || way === 3
+		},
+		payWayLabel() {
+			const map = {
+				0: '微信',
+				1: '支付宝',
+				2: '余额',
+				3: '挂账',
+				4: '现金',
+				5: '银行卡'
+			}
+			return map[Number(this.payWay)] || '原支付方式'
+		}
+	},
+	watch: {
+		show(val) {
+			if (val) {
+				this.resetOptions()
+				this.$nextTick(() => {
+					if (this.$refs.popupRef) {
+						this.$refs.popupRef.open('center')
+					}
+				})
+			} else if (this.$refs.popupRef) {
+				this.$refs.popupRef.close()
+			}
+		}
+	},
+	methods: {
+		/** 打开时按支付方式重置默认资金选项 */
+		resetOptions() {
+			this.forwardStock = -1
+			this.remark = ''
+			if (this.isDebtOrBalance) {
+				this.fundType = 2
+			} else if (this.isOnlinePay) {
+				this.fundType = 0
+			} else {
+				this.fundType = 2
+			}
+		},
+		/** 互斥选择资金处理方式 */
+		pickFundType(type) {
+			this.fundType = type
+		},
+		onCancel() {
+			this.$emit('update:show', false)
+			this.$emit('close')
+		},
+		/** 校验后把 fundType/forwardStock/remark 交给父组件去调 create */
+		onConfirm() {
+			if (this.forwardStock === -1) {
+				this.$msg('请选择是否回库存')
+				return
+			}
+			let fundType = this.fundType
+			if (this.isDebtOrBalance) {
+				fundType = 2
+			}
+			if (!fundType) {
+				this.$msg('请选择资金处理方式')
+				return
+			}
+			this.$emit('confirm', {
+				fundType,
+				forwardStock: this.forwardStock,
+				remark: this.remark || ''
+			})
+			this.$emit('update:show', false)
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.forward-popup {
+	width: 640upx;
+	padding: 36upx 32upx 28upx;
+	box-sizing: border-box;
+	background: #fff;
+	border-radius: 16upx;
+}
+
+.forward-popup__title {
+	font-size: 34upx;
+	font-weight: bold;
+	color: #333;
+	text-align: center;
+	margin-bottom: 28upx;
+}
+
+.forward-popup__row {
+	display: flex;
+	flex-direction: row;
+	align-items: flex-start;
+	padding: 18upx 0;
+	border-bottom: 1upx solid #f0f2f5;
+
+	&--amount {
+		border-bottom: none;
+		align-items: center;
+	}
+}
+
+.forward-popup__label {
+	width: 140upx;
+	flex-shrink: 0;
+	font-size: 28upx;
+	color: #909399;
+	line-height: 56upx;
+}
+
+.forward-popup__value {
+	flex: 1;
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+	align-items: center;
+
+	&--locked {
+		font-size: 28upx;
+		color: #333;
+		line-height: 56upx;
+	}
+}
+
+.forward-popup__btn {
+	min-width: 140upx;
+
+	&--ml {
+		margin-left: 16upx;
+	}
+}
+
+.forward-popup__input {
+	flex: 1;
+	height: 64upx;
+	padding: 0 16upx;
+	font-size: 28upx;
+	background: #f5f7fa;
+	border-radius: 8upx;
+}
+
+.forward-popup__amount {
+	font-size: 36upx;
+	font-weight: bold;
+	color: #ff4757;
+}
+
+.forward-popup__btns {
+	display: flex;
+	flex-direction: row;
+	margin-top: 28upx;
+}
+
+.forward-popup__submitBtn {
+	flex: 1;
+
+	&--ml {
+		margin-left: 20upx;
+	}
+}
+</style>

+ 6 - 2
ghsApp/src/pages.json

@@ -123,7 +123,10 @@
 				{"path": "bookChange","style": {"navigationBarTitleText": "变动记录", "enablePullDownRefresh": false}},
 				{"path": "combine","style": {"navigationBarTitleText": "分配结果", "enablePullDownRefresh": false}},
 				{"path": "printWlList","style": {"navigationBarTitleText": "打印物流列表", "enablePullDownRefresh": false}},
-				{"path": "modify","style": {"navigationBarTitleText": "修改地址", "enablePullDownRefresh": false}}
+				{"path": "modify","style": {"navigationBarTitleText": "修改地址", "enablePullDownRefresh": false}},
+				{"path": "forwardList","style": {"navigationBarTitleText": "冲销记录", "enablePullDownRefresh": true}},
+				{"path": "forwardDetail","style": {"navigationBarTitleText": "冲销详情"}},
+				{"path": "forwardConfirm","style": {"navigationBarTitleText": "自由冲销"}}
 			]
 		},
 		{
@@ -334,7 +337,8 @@
 				{ "path": "customerAffirm", "style": { "navigationBarTitleText": "确认下单" } },
 				{ "path": "confirm", "style": { "navigationBarTitleText": "待确认" } },
 				{"path": "toPay","style": {"navigationBarTitleText": "等待付款"}},
-				{"path": "affirmGhsBook","style": {"navigationBarTitleText": "等待付款"}}
+				{"path": "affirmGhsBook","style": {"navigationBarTitleText": "等待付款"}},
+				{ "path": "forwardResult", "style": { "navigationBarTitleText": "冲销成功" } }
 			]
 		},
 		{

+ 8 - 0
ghsApp/src/pagesOrder/detail.vue

@@ -240,6 +240,14 @@
 						<button @click="pageTo({url: '/pagesOrder/refundList',query: {orderSn: detailInfo.orderSn}})" style="color:red;border: 1upx solid red;" class="admin-button-com">售后记录</button>
 					</view>
 				</view>
+				<!-- 方案A:有冲销凭证时展示累计冲销入口,不展示假冲销单卡片 -->
+				<view v-if="detailInfo.hasForward == 1" class="order-info_box" style="margin-top:20upx;">
+					<view>累计冲销:</view>
+					<view style="color:red;font-weight:bold;font-size:30upx;">{{detailInfo.forwardPrice?parseFloat(detailInfo.forwardPrice):0}}</view>
+					<view class="price">
+						<button @click="pageTo({url: '/pagesOrder/forwardList',query: {orderId: detailInfo.id}})" style="color:red;border: 1upx solid red;" class="admin-button-com">冲销记录</button>
+					</view>
+				</view>
 			</view>
 
 			<view class="content-box shipping-address" v-if="detailInfo.expressProgress!=''">

+ 454 - 0
ghsApp/src/pagesOrder/forwardConfirm.vue

@@ -0,0 +1,454 @@
+<!--
+  自由冲销确认页(方案A)
+  用途:selectCustom?forward=1 选客后进入;按客户选花材/金额提交 create(customId),不是负销售开单。
+-->
+<template>
+	<view class="forward-confirm">
+		<view class="card">
+			<view class="card__title">客户:{{ customName || '' }}</view>
+			<view class="search-row">
+				<input
+					class="search-input"
+					type="text"
+					v-model="py"
+					placeholder="拼音首字母搜索花材"
+					placeholder-style="color:#999"
+					@confirm="searchProduct"
+				/>
+				<button class="admin-button-com middle blue search-btn" @click="searchProduct">搜索</button>
+			</view>
+		</view>
+
+		<!-- 搜索结果 -->
+		<view class="card" v-if="!$util.isEmpty(searchList)">
+			<view class="card__title">搜索结果</view>
+			<view class="search-item" v-for="(p, idx) in searchList" :key="idx" @click="addProduct(p)">
+				<text class="search-item__name">{{ p.name }}</text>
+				<text class="search-item__price">¥{{ parseFloat(p.price || p.bigPrice || 0) }}</text>
+			</view>
+		</view>
+
+		<!-- 已选冲销明细 -->
+		<view class="card">
+			<view class="card__title">冲销花材</view>
+			<view v-if="$util.isEmpty(items)" class="empty-tip">请搜索并添加花材</view>
+			<view class="item-row" v-for="(it, idx) in items" :key="idx">
+				<view class="item-row__head">
+					<text class="item-row__name">{{ it.name }}</text>
+					<text class="item-row__del" @click="removeItem(idx)">删除</text>
+				</view>
+				<view class="item-row__fields">
+					<view class="field">
+						<text class="field__label">数量</text>
+						<input class="field__input" type="digit" v-model="it.num" @input="recalcAmount(it)" />
+					</view>
+					<view class="field field--ml">
+						<text class="field__label">单价</text>
+						<input class="field__input" type="digit" v-model="it.unitPrice" @input="recalcAmount(it)" />
+					</view>
+					<view class="field field--ml">
+						<text class="field__label">金额</text>
+						<input class="field__input" type="digit" v-model="it.amount" />
+					</view>
+				</view>
+			</view>
+			<view class="total-row">
+				<text>合计</text>
+				<text class="total-row__price">¥{{ totalAmount }}</text>
+			</view>
+		</view>
+
+		<!-- 自由冲销:仅返余额 / 仅记账 -->
+		<view class="card">
+			<view class="opt-row">
+				<text class="opt-label">资金处理</text>
+				<view class="opt-btns">
+					<button
+						class="admin-button-com middle"
+						:class="fundType === 2 ? 'blue' : 'default'"
+						@click="fundType = 2"
+					>返余额</button>
+					<button
+						class="admin-button-com middle opt-btns__ml"
+						:class="fundType === 3 ? 'blue' : 'default'"
+						@click="fundType = 3"
+					>仅记账</button>
+				</view>
+			</view>
+			<view class="opt-row">
+				<text class="opt-label">库存</text>
+				<view class="opt-btns">
+					<button
+						class="admin-button-com middle"
+						:class="forwardStock === 0 ? 'blue' : 'default'"
+						@click="forwardStock = 0"
+					>回库存</button>
+					<button
+						class="admin-button-com middle opt-btns__ml"
+						:class="forwardStock === 1 ? 'blue' : 'default'"
+						@click="forwardStock = 1"
+					>不回库存</button>
+				</view>
+			</view>
+			<view class="opt-row">
+				<text class="opt-label">备注</text>
+				<input class="remark-input" type="text" v-model="remark" placeholder="选填" placeholder-style="color:#999" />
+			</view>
+		</view>
+
+		<view class="bottom-bar">
+			<button class="admin-button-com big default bottom-bar__btn" @click="goBack">取消</button>
+			<button class="admin-button-com big blue bottom-bar__btn bottom-bar__btn--ml" @click="submit">确认冲销</button>
+		</view>
+	</view>
+</template>
+
+<script>
+import { productList } from '@/api/product'
+import { createForward } from '@/api/forward'
+
+export default {
+	name: 'forwardConfirm',
+	data() {
+		return {
+			customId: 0,
+			customName: '',
+			py: '',
+			searchList: [],
+			items: [],
+			fundType: 2,
+			forwardStock: 1,
+			remark: '',
+			submitting: false
+		}
+	},
+	computed: {
+		totalAmount() {
+			let sum = 0
+			this.items.forEach(it => {
+				sum += Number(it.amount) || 0
+			})
+			return parseFloat(sum.toFixed(2))
+		}
+	},
+	onLoad(option) {
+		this.customId = Number(option.customId || 0)
+		this.customName = option.customName || ''
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack()
+		},
+		/** 按拼音搜可冲销花材 */
+		searchProduct() {
+			if (!this.py) {
+				this.$msg('请输入拼音')
+				return
+			}
+			uni.showLoading({ mask: true })
+			productList({ py: this.py, page: 1, pageSize: 30 }).then(res => {
+				uni.hideLoading()
+				if (res.code == 1) {
+					const list = res.data.list || res.data.data || res.data || []
+					this.searchList = Array.isArray(list) ? list : []
+					if (this.$util.isEmpty(this.searchList)) {
+						this.$msg('未找到花材')
+					}
+				}
+			}).catch(() => {
+				uni.hideLoading()
+			})
+		},
+		/** 加入已选明细,默认数量 1 */
+		addProduct(p) {
+			const productId = p.id || p.productId
+			if (!productId) {
+				return
+			}
+			const exist = this.items.find(it => Number(it.productId) === Number(productId))
+			if (exist) {
+				this.$msg('已添加该花材')
+				return
+			}
+			const unitPrice = parseFloat(p.price || p.bigPrice || 0)
+			this.items.push({
+				productId,
+				name: p.name || '',
+				num: 1,
+				bigNum: 1,
+				smallNum: 0,
+				unitPrice,
+				price: unitPrice,
+				amount: unitPrice
+			})
+			this.$msg('已添加')
+		},
+		removeItem(idx) {
+			this.items.splice(idx, 1)
+		},
+		recalcAmount(it) {
+			const num = Number(it.num) || 0
+			const price = Number(it.unitPrice) || 0
+			it.amount = parseFloat((num * price).toFixed(2))
+			it.price = price
+			it.bigNum = num
+			it.smallNum = 0
+		},
+		submit() {
+			if (!this.customId) {
+				this.$msg('缺少客户')
+				return
+			}
+			if (this.$util.isEmpty(this.items)) {
+				this.$msg('请选择冲销花材')
+				return
+			}
+			if (this.totalAmount <= 0) {
+				this.$msg('冲销金额必须大于0')
+				return
+			}
+			if (this.forwardStock !== 0 && this.forwardStock !== 1) {
+				this.$msg('请选择是否回库存')
+				return
+			}
+			if (this.submitting) {
+				return
+			}
+			const payloadItems = this.items.map(it => ({
+				productId: it.productId,
+				name: it.name,
+				num: Number(it.num) || 0,
+				bigNum: Number(it.bigNum) || Number(it.num) || 0,
+				smallNum: Number(it.smallNum) || 0,
+				unitPrice: Number(it.unitPrice) || 0,
+				price: Number(it.price || it.unitPrice) || 0,
+				amount: Number(it.amount) || 0
+			}))
+			this.$util.confirmModal({ content: '确认提交冲销凭证?' }, () => {
+				this.submitting = true
+				uni.showLoading({ title: '提交中', mask: true })
+				createForward({
+					customId: this.customId,
+					fundType: this.fundType,
+					forwardStock: this.forwardStock,
+					amount: this.totalAmount,
+					remark: this.remark || '',
+					items: payloadItems
+				}).then(res => {
+					uni.hideLoading()
+					this.submitting = false
+					if (res.code == 1) {
+						const data = res.data || {}
+						this.$util.pageTo({
+							url: '/admin/billing/forwardResult',
+							type: 2,
+							query: {
+								fundType: data.fundType || this.fundType,
+								amount: data.amount || this.totalAmount,
+								customId: data.customId || this.customId,
+								customName: this.customName,
+								forwardSn: data.forwardSn || '',
+								forwardId: data.forwardId || 0
+							}
+						})
+					}
+				}).catch(() => {
+					uni.hideLoading()
+					this.submitting = false
+				})
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.forward-confirm {
+	min-height: 100vh;
+	padding: 20upx 20upx 180upx;
+	background: #f5f7fa;
+	box-sizing: border-box;
+}
+
+.card {
+	background: #fff;
+	border-radius: 12upx;
+	padding: 24upx;
+	margin-bottom: 20upx;
+
+	&__title {
+		font-size: 30upx;
+		font-weight: bold;
+		color: #333;
+		margin-bottom: 16upx;
+	}
+}
+
+.search-row {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+}
+
+.search-input {
+	flex: 1;
+	height: 68upx;
+	padding: 0 20upx;
+	background: #f5f7fa;
+	border-radius: 8upx;
+	font-size: 28upx;
+}
+
+.search-btn {
+	margin-left: 16upx;
+	min-width: 120upx;
+}
+
+.search-item {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	padding: 18upx 0;
+	border-bottom: 1upx solid #f0f2f5;
+
+	&__name {
+		flex: 1;
+		font-size: 28upx;
+		color: #333;
+	}
+
+	&__price {
+		font-size: 28upx;
+		color: #ff4757;
+		margin-left: 16upx;
+	}
+}
+
+.empty-tip {
+	font-size: 26upx;
+	color: #999;
+	padding: 20upx 0;
+}
+
+.item-row {
+	padding: 16upx 0;
+	border-bottom: 1upx solid #f0f2f5;
+
+	&__head {
+		display: flex;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	&__name {
+		font-size: 28upx;
+		color: #333;
+		font-weight: 600;
+	}
+
+	&__del {
+		font-size: 26upx;
+		color: #ff4757;
+	}
+
+	&__fields {
+		display: flex;
+		flex-direction: row;
+		margin-top: 12upx;
+	}
+}
+
+.field {
+	flex: 1;
+
+	&--ml {
+		margin-left: 12upx;
+	}
+
+	&__label {
+		display: block;
+		font-size: 22upx;
+		color: #999;
+		margin-bottom: 6upx;
+	}
+
+	&__input {
+		height: 60upx;
+		padding: 0 12upx;
+		background: #f5f7fa;
+		border-radius: 8upx;
+		font-size: 26upx;
+	}
+}
+
+.total-row {
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	padding-top: 20upx;
+	font-size: 28upx;
+	color: #333;
+
+	&__price {
+		color: #ff4757;
+		font-weight: bold;
+		font-size: 34upx;
+	}
+}
+
+.opt-row {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	padding: 16upx 0;
+}
+
+.opt-label {
+	width: 140upx;
+	flex-shrink: 0;
+	font-size: 28upx;
+	color: #909399;
+}
+
+.opt-btns {
+	flex: 1;
+	display: flex;
+	flex-direction: row;
+
+	&__ml {
+		margin-left: 16upx;
+	}
+}
+
+.remark-input {
+	flex: 1;
+	height: 64upx;
+	padding: 0 16upx;
+	background: #f5f7fa;
+	border-radius: 8upx;
+	font-size: 28upx;
+}
+
+.bottom-bar {
+	position: fixed;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	display: flex;
+	flex-direction: row;
+	padding: 20upx 24upx;
+	background: #fff;
+	box-shadow: 0 -4upx 16upx rgba(0, 0, 0, 0.06);
+
+	&__btn {
+		flex: 1;
+
+		&--ml {
+			margin-left: 20upx;
+		}
+	}
+}
+</style>

+ 176 - 0
ghsApp/src/pagesOrder/forwardDetail.vue

@@ -0,0 +1,176 @@
+<!--
+  冲销凭证详情(方案A)
+  用途:展示凭证主信息与花材明细;供列表页跳转查看,不可编辑。
+-->
+<template>
+	<view class="forward-detail">
+		<view class="card">
+			<view class="row">
+				<text class="label">冲销单号</text>
+				<text class="value">{{ info.forwardSn || '' }}</text>
+			</view>
+			<view class="row">
+				<text class="label">冲销金额</text>
+				<text class="value value--price">¥{{ info.amount ? parseFloat(info.amount) : 0 }}</text>
+			</view>
+			<view class="row">
+				<text class="label">资金处理</text>
+				<text class="value">{{ fundTypeText }}</text>
+			</view>
+			<view class="row">
+				<text class="label">库存</text>
+				<text class="value">{{ info.forwardStock == 0 ? '已回库' : '未回库' }}</text>
+			</view>
+			<view class="row">
+				<text class="label">原销售单</text>
+				<text class="value">{{ info.orderSn || '自由冲销' }}</text>
+			</view>
+			<view class="row">
+				<text class="label">操作人</text>
+				<text class="value">{{ info.shopAdminName || '' }}</text>
+			</view>
+			<view class="row">
+				<text class="label">时间</text>
+				<text class="value">{{ info.addTime || '' }}</text>
+			</view>
+			<view class="row row--top" v-if="!$util.isEmpty(info.remark)">
+				<text class="label">备注</text>
+				<text class="value">{{ info.remark }}</text>
+			</view>
+		</view>
+
+		<view class="card" v-if="!$util.isEmpty(items)">
+			<view class="card-title">冲销花材</view>
+			<view class="item" v-for="(it, idx) in items" :key="idx">
+				<text class="item__name">{{ it.name }}</text>
+				<text class="item__meta">
+					数量 {{ parseFloat(it.num) }}
+					× ¥{{ parseFloat(it.unitPrice || it.price || 0) }}
+					= ¥{{ parseFloat(it.amount) }}
+				</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { forwardDetail } from '@/api/forward'
+
+export default {
+	name: 'forwardDetail',
+	data() {
+		return {
+			info: {},
+			items: []
+		}
+	},
+	computed: {
+		fundTypeText() {
+			const map = { 1: '原路退', 2: '返余额', 3: '仅记账' }
+			return map[Number(this.info.fundType)] || '--'
+		}
+	},
+	onShow() {
+		this.init()
+	},
+	methods: {
+		init() {
+			const id = this.option.id
+			if (!id) {
+				return
+			}
+			uni.showLoading({ mask: true })
+			forwardDetail({ id }).then(res => {
+				uni.hideLoading()
+				if (res.code == 1) {
+					this.info = res.data || {}
+					this.items = res.data.items || []
+				}
+			}).catch(() => {
+				uni.hideLoading()
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.forward-detail {
+	min-height: 100vh;
+	padding: 24upx;
+	background: #f5f7fa;
+	box-sizing: border-box;
+}
+
+.card {
+	background: #fff;
+	border-radius: 16upx;
+	padding: 8upx 24upx 24upx;
+	margin-bottom: 20upx;
+}
+
+.card-title {
+	font-size: 30upx;
+	font-weight: bold;
+	color: #333;
+	padding: 20upx 0 8upx;
+}
+
+.row {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	padding: 20upx 0;
+	border-bottom: 1upx solid #f0f2f5;
+
+	&:last-child {
+		border-bottom: none;
+	}
+
+	&--top {
+		align-items: flex-start;
+	}
+}
+
+.label {
+	width: 160upx;
+	flex-shrink: 0;
+	font-size: 28upx;
+	color: #909399;
+}
+
+.value {
+	flex: 1;
+	font-size: 28upx;
+	color: #333;
+
+	&--price {
+		color: #ff4757;
+		font-weight: bold;
+		font-size: 32upx;
+	}
+}
+
+.item {
+	padding: 18upx 0;
+	border-bottom: 1upx solid #f0f2f5;
+
+	&:last-child {
+		border-bottom: none;
+	}
+
+	&__name {
+		display: block;
+		font-size: 28upx;
+		color: #333;
+		font-weight: 600;
+	}
+
+	&__meta {
+		display: block;
+		margin-top: 8upx;
+		font-size: 26upx;
+		color: #666;
+	}
+}
+</style>

+ 133 - 0
ghsApp/src/pagesOrder/forwardList.vue

@@ -0,0 +1,133 @@
+<!--
+  冲销凭证列表(方案A)
+  用途:按原销售单 orderId 展示累计冲销凭证,点击进详情;与售后记录分开展示。
+-->
+<template>
+	<view class="app-content">
+		<view v-if="forwardPrice > 0" class="sum-bar">
+			<text>累计冲销</text>
+			<text class="sum-bar__price">¥{{ parseFloat(forwardPrice) }}</text>
+		</view>
+		<block v-if="!$util.isEmpty(list.data)">
+			<view class="flex-space list" v-for="(item, index) in list.data" :key="index" @click="goDetail(item)">
+				<view class="label">
+					<text>日期:{{ item.addTime ? item.addTime.substr(5, 11) : '' }}</text>
+					<text>单号:{{ item.forwardSn }}</text>
+					<text>资金:{{ fundTypeText(item.fundType) }}</text>
+					<text>库存:{{ item.forwardStock == 0 ? '已回库' : '未回库' }}</text>
+					<text v-if="!$util.isEmpty(item.remark)">备注:{{ item.remark }}</text>
+				</view>
+				<view class="flex val">
+					-¥{{ parseFloat(item.amount) }}
+					<view class="iconfont iconxiangyou"></view>
+				</view>
+			</view>
+		</block>
+		<block v-else>
+			<app-wrapper-empty title="暂无冲销记录" :is-empty="$util.isEmpty(list.data)" />
+		</block>
+	</view>
+</template>
+
+<script>
+import { list } from '@/mixins'
+import { listByOrder } from '@/api/forward'
+
+export default {
+	name: 'forwardList',
+	mixins: [list],
+	data() {
+		return {
+			forwardPrice: 0
+		}
+	},
+	onPullDownRefresh() {
+		this.resetList()
+		this._list().then(() => {
+			uni.stopPullDownRefresh()
+		})
+	},
+	onShow() {
+		this.resetList()
+		this._list()
+	},
+	methods: {
+		fundTypeText(type) {
+			const map = { 1: '原路退', 2: '返余额', 3: '仅记账' }
+			return map[Number(type)] || '--'
+		},
+		goDetail(item) {
+			if (!item.id) {
+				return
+			}
+			this.$util.pageTo({ url: '/pagesOrder/forwardDetail', query: { id: item.id } })
+		},
+		/** 冲销列表不分页,一次拉完后标记 finished */
+		_list() {
+			return listByOrder({
+				orderId: this.option.orderId || this.option.id
+			}).then(res => {
+				if (res.code == 1) {
+					this.forwardPrice = res.data.forwardPrice || 0
+					// 适配 list mixin:把 list 放到 data.list
+					this.completes({
+						code: 1,
+						data: {
+							list: res.data.list || [],
+							moreData: 0
+						}
+					})
+				}
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.app-content {
+	.sum-bar {
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		justify-content: space-between;
+		padding: 24upx 30upx;
+		margin-bottom: 16upx;
+		background: #fff2f0;
+		font-size: 28upx;
+		color: #333;
+
+		&__price {
+			color: #ff4757;
+			font-weight: bold;
+			font-size: 32upx;
+		}
+	}
+
+	.list {
+		padding: 20upx 30upx;
+		margin-bottom: 20upx;
+		background: #ffffff;
+
+		.label {
+			position: relative;
+			line-height: 50upx;
+
+			text {
+				display: block;
+			}
+		}
+
+		.val {
+			font-size: 28upx;
+			color: #ff4757;
+			font-weight: bold;
+
+			.iconfont {
+				margin-left: 12upx;
+				color: #999999;
+			}
+		}
+	}
+}
+</style>

+ 116 - 7
ghsApp/src/pagesOrder/refund.vue

@@ -188,20 +188,31 @@
 
 		<!-- 提示信息 -->
 		<view class="tip-section">
-			<text v-if="orderInfo.onlinePay == 2" class="tip-text online-tip">此单线上付款,提交后,钱会原路自动退回给客户</text>
-			<text v-else-if="orderInfo.payWay == 2" class="tip-text online-tip">此单余额付款,提交后,钱会原路自动退回到客户余额</text>
+			<!-- 方案A:符合冲销条件时禁用常规售后,引导走冲销凭证 -->
+			<text v-if="forwardEligible" class="tip-text online-tip">本单需转冲销凭证(非当天/挂账已结清),提交后将创建冲销凭证,不再走常规售后</text>
 			<block v-else>
-				<text v-if="Number(orderInfo.debtPrice)>0" class="tip-text debt-tip">此单为欠款单,提交后,欠款金额会自动减少</text>
-				<text v-else class="tip-text offline-tip">此单线下付款,提交后钱不会原路退回,请线下转账给客户</text>
+				<text v-if="orderInfo.onlinePay == 2" class="tip-text online-tip">此单线上付款,提交后,钱会原路自动退回给客户</text>
+				<text v-else-if="orderInfo.payWay == 2" class="tip-text online-tip">此单余额付款,提交后,钱会原路自动退回到客户余额</text>
+				<block v-else>
+					<text v-if="Number(orderInfo.debtPrice)>0" class="tip-text debt-tip">此单为欠款单,提交后,欠款金额会自动减少</text>
+					<text v-else class="tip-text offline-tip">此单线下付款,提交后钱不会原路退回,请线下转账给客户</text>
+				</block>
 			</block>
 		</view>
 
 		<!-- 底部操作按钮 -->
 		<view class="bottom-actions">
 			<button class="action-btn cancel-btn" @tap="cancelRefund">取消</button>
-			<button class="action-btn confirm-btn" @tap="confirmRefund">确定</button>
+			<button class="action-btn confirm-btn" @tap="confirmRefund">{{ forwardEligible ? '确认冲销' : '确定' }}</button>
 		</view>
 
+		<forward-options-popup
+			:show.sync="showForwardPopup"
+			:pay-way="forwardPayWay"
+			:online-pay="forwardOnlinePay"
+			:amount="refundMoney"
+			@confirm="onForwardConfirm"
+		/>
 	</view>
 </template>
 <script>
@@ -210,10 +221,13 @@ import { getDetail,refund } from "@/api/order/index";
 import { ORDER_STATUS } from "@/utils/declare";
 import { getUnClear } from "@/api/clear"
 import { changeHalf } from "@/api/item"
+import { checkEligible, createForward } from "@/api/forward"
+import ForwardOptionsPopup from "@/components/forward-options-popup.vue"
 export default {
 	name: "orderDetail",
 	components: {
-		TuiListCell
+		TuiListCell,
+		ForwardOptionsPopup
 	},
 	data() {
 		return {
@@ -225,7 +239,13 @@ export default {
 			remark:'',
 			orderInfo:{},
 			couldRefundPrice:0,
-			unClear:[]
+			unClear:[],
+			// 方案A冲销预检
+			forwardEligible: false,
+			showForwardPopup: false,
+			forwardPayWay: 0,
+			forwardOnlinePay: 0,
+			pendingForwardItems: []
 		};
 	},
 	onShow() {
@@ -319,6 +339,23 @@ export default {
 					return false
 				}
 			}
+			// 符合冲销条件:禁用常规售后,打开冲销选项弹框
+			if(this.forwardEligible){
+				if(Number(this.refundMoney) <= 0){
+					uni.showToast({title:'冲销金额必须大于0',icon:'none'})
+					return
+				}
+				// 原单冲销接口要求花材明细,只退款无花材时不能建凭证
+				this.pendingForwardItems = this.buildForwardItems()
+				if(this.$util.isEmpty(this.pendingForwardItems)){
+					uni.showToast({title:'冲销请选择花材',icon:'none'})
+					return
+				}
+				this.forwardPayWay = Number(this.orderInfo.payWay || 0)
+				this.forwardOnlinePay = Number(this.orderInfo.onlinePay || 0)
+				this.showForwardPopup = true
+				return
+			}
 			let refundParams = {
 				id:this.option.id,
 				product:JSON.stringify(product),
@@ -352,6 +389,69 @@ export default {
 				})
 			})
 		},
+		/** 把售后页勾选的花材转成冲销明细(xhUnitType:0大单位 1小单位) */
+		buildForwardItems(){
+			const items = []
+			this.product.forEach(ele => {
+				const count = Number(ele.refundCount) || 0
+				if(count <= 0){
+					return
+				}
+				const isSmall = Number(ele.xhUnitType) === 1
+				const unitPrice = Number(ele.xhUnitPrice) || 0
+				const amount = parseFloat((count * unitPrice).toFixed(2))
+				items.push({
+					productId: ele.productId,
+					orderItemId: ele.id || ele.orderItemId || 0,
+					name: ele.name || '',
+					num: count,
+					bigNum: isSmall ? 0 : count,
+					smallNum: isSmall ? count : 0,
+					unitPrice,
+					price: unitPrice,
+					amount
+				})
+			})
+			return items
+		},
+		/** 弹框确认后创建冲销凭证 */
+		onForwardConfirm(opt){
+			const that = this
+			const items = this.pendingForwardItems || []
+			if(this.$util.isEmpty(items) && this.refundType == 1){
+				this.$msg('请选择商品')
+				return
+			}
+			uni.showLoading({title: '冲销中', mask: true})
+			createForward({
+				orderId: this.option.id,
+				fundType: opt.fundType,
+				forwardStock: opt.forwardStock,
+				amount: this.refundMoney,
+				remark: opt.remark || this.remark || '',
+				items
+			}).then(res => {
+				uni.hideLoading()
+				if(res.code == 1){
+					const data = res.data || {}
+					that.$util.pageTo({
+						url: '/admin/billing/forwardResult',
+						type: 2,
+						query: {
+							fundType: data.fundType || opt.fundType,
+							amount: data.amount || that.refundMoney,
+							customId: data.customId || that.orderInfo.customId,
+							customName: that.orderInfo.customName || '',
+							forwardSn: data.forwardSn || '',
+							forwardId: data.forwardId || 0,
+							orderId: that.option.id
+						}
+					})
+				}
+			}).catch(() => {
+				uni.hideLoading()
+			})
+		},
 		needCancelRemind(id){
 			let that = this
 			that.$util.confirmModal({content:'有结账单取消了才能退款,请点去取消',okText:'去取消',cancelText:'不操作'},() => {
@@ -382,6 +482,15 @@ export default {
 				})
 			}
 
+			// 预检是否走冲销凭证(非当天支付或挂账已结清)
+			this.forwardEligible = false
+			checkEligible({ orderId: this.option.id }).then(checkRes => {
+				if(checkRes.code == 1 && checkRes.data && checkRes.data.ok){
+					this.forwardEligible = true
+					this.forwardPayWay = Number(checkRes.data.payWay || this.orderInfo.payWay || 0)
+					this.forwardOnlinePay = Number(checkRes.data.onlinePay || this.orderInfo.onlinePay || 0)
+				}
+			})
 		},
 		goClear(item){
 			this.$util.pageTo({url:'/admin/clear/customInfo?id='+item.id})

+ 74 - 2
ghsApp/src/pagesOrder/refundDetail.vue

@@ -102,8 +102,14 @@
 		</view>
 
 		<view class="refund-actions">
+			<!-- 待审且符合冲销条件:走转冲销,不再常规通过 -->
 			<button
-				v-if="info.status == 0"
+				v-if="info.status == 0 && forwardEligible"
+				class="admin-button-com big refund-actions__btn refund-actions__btn--pass"
+				@click="openForwardPopup()"
+			>通过(转冲销)</button>
+			<button
+				v-if="info.status == 0 && !forwardEligible"
 				class="admin-button-com big refund-actions__btn refund-actions__btn--pass"
 				@click="pass(0)"
 			>通过</button>
@@ -125,6 +131,14 @@
 			<button class="admin-button-com big default refund-actions__btn" @click="goBack()">返回</button>
 		</view>
 
+		<forward-options-popup
+			:show.sync="showForwardPopup"
+			:pay-way="forwardPayWay"
+			:online-pay="forwardOnlinePay"
+			:amount="refundPrice"
+			@confirm="onForwardConfirm"
+		/>
+
 		<uni-popup ref="rejectRef" background-color="#fff" type="center" :animation="false" class="class-popup-style">
 			<view class="refund-popup">
 				<textarea v-model="rejectReason" class="refund-popup__textarea" placeholder="驳回原因..."></textarea>
@@ -155,8 +169,13 @@
 </template>
 <script>
 import { refundDetail, passRefund, rejectRefund, modifyCause, modifyAmount } from '@/api/refund'
+import { checkRefundEligible, createForward } from '@/api/forward'
+import ForwardOptionsPopup from '@/components/forward-options-popup.vue'
 
 export default {
+	components: {
+		ForwardOptionsPopup
+	},
 	data() {
 		return {
 			product: '',
@@ -167,7 +186,13 @@ export default {
 			rejectReason: '',
 			from: 0,
 			refundAmount: '',
-			showChoice: false
+			showChoice: false,
+			// 方案A:待审售后转冲销
+			forwardEligible: false,
+			showForwardPopup: false,
+			forwardPayWay: 0,
+			forwardOnlinePay: 0,
+			forwardOrderId: 0
 		}
 	},
 	computed: {
@@ -328,6 +353,41 @@ export default {
 		goWaste() {
 			this.$util.pageTo({ url: '/pagesOrder/refundSuccess?id=' + this.option.id })
 		},
+		/** 打开冲销选项弹框 */
+		openForwardPopup() {
+			this.showForwardPopup = true
+		},
+		/** 售后转冲销:后端按 refundId 生成明细,前端只传资金/库存选项 */
+		onForwardConfirm(opt) {
+			const that = this
+			uni.showLoading({ title: '冲销中', mask: true })
+			createForward({
+				refundId: this.option.id,
+				fundType: opt.fundType,
+				forwardStock: opt.forwardStock,
+				amount: this.refundPrice,
+				remark: opt.remark || this.remark || ''
+			}).then(res => {
+				uni.hideLoading()
+				if (res.code == 1) {
+					const data = res.data || {}
+					that.$util.pageTo({
+						url: '/admin/billing/forwardResult',
+						type: 2,
+						query: {
+							fundType: data.fundType || opt.fundType,
+							amount: data.amount || that.refundPrice,
+							customId: data.customId || 0,
+							forwardSn: data.forwardSn || '',
+							forwardId: data.forwardId || 0,
+							orderId: that.forwardOrderId || that.info.relateOrderId || 0
+						}
+					})
+				}
+			}).catch(() => {
+				uni.hideLoading()
+			})
+		},
 		init() {
 			refundDetail({ id: this.option.id }).then(res => {
 				this.product = res.data.itemList || []
@@ -335,6 +395,18 @@ export default {
 				this.refundPrice = res.data.info.refundPrice ? parseFloat(res.data.info.refundPrice) : 0
 				this.remark = res.data.info.remark
 				this.info = res.data.info
+				// 待审售后预检是否可转冲销
+				this.forwardEligible = false
+				if (Number(this.info.status) === 0) {
+					checkRefundEligible({ refundId: this.option.id }).then(checkRes => {
+						if (checkRes.code == 1 && checkRes.data && checkRes.data.ok) {
+							this.forwardEligible = true
+							this.forwardPayWay = Number(checkRes.data.payWay || 0)
+							this.forwardOnlinePay = Number(checkRes.data.onlinePay || 0)
+							this.forwardOrderId = Number(checkRes.data.orderId || 0)
+						}
+					})
+				}
 			})
 		}
 	}

+ 15 - 0
hdApp/src/api/forward/index.js

@@ -0,0 +1,15 @@
+/**
+ * hdApp 冲销凭证只读 API(方案A)
+ * 用途:按采购单查列表/详情;对接 app-hd ForwardController,读本侧 xhCgForward。
+ */
+import https from '@/plugins/luch-request_0.0.7/request'
+
+/** 按原采购单查冲销凭证列表 */
+export const listByCg = data => {
+	return https.get('/forward/list-by-cg', data)
+}
+
+/** 花店侧冲销凭证详情 */
+export const forwardDetail = data => {
+	return https.get('/forward/detail', data)
+}

+ 3 - 1
hdApp/src/pages.json

@@ -563,7 +563,9 @@
 				{ "path": "addCgResult","style": {"navigationBarTitleText": "结果", "enablePullDownRefresh": false } },
 				{ "path": "confirmTake","style": {"navigationBarTitleText": "确认收货", "enablePullDownRefresh": false } },
 				{ "path": "contact","style": {"navigationBarTitleText": "联系我们", "enablePullDownRefresh": false } },
-				{ "path": "appPay","style": {"navigationBarTitleText": "支付", "enablePullDownRefresh": false } }
+				{ "path": "appPay","style": {"navigationBarTitleText": "支付", "enablePullDownRefresh": false } },
+				{ "path": "forwardList","style": {"navigationBarTitleText": "冲销记录", "enablePullDownRefresh": true } },
+				{ "path": "forwardDetail","style": {"navigationBarTitleText": "冲销详情" } }
 			]
 		},
 		{

+ 172 - 0
hdApp/src/pagesPurchase/forwardDetail.vue

@@ -0,0 +1,172 @@
+<!--
+  花店冲销凭证详情(方案A)
+  用途:只读展示本侧 xhCgForward 主信息与明细。
+-->
+<template>
+	<view class="forward-detail">
+		<view class="card">
+			<view class="row">
+				<text class="label">冲销单号</text>
+				<text class="value">{{ info.forwardSn || '' }}</text>
+			</view>
+			<view class="row">
+				<text class="label">冲销金额</text>
+				<text class="value value--price">¥{{ info.amount ? parseFloat(info.amount) : 0 }}</text>
+			</view>
+			<view class="row">
+				<text class="label">资金处理</text>
+				<text class="value">{{ fundTypeText }}</text>
+			</view>
+			<view class="row">
+				<text class="label">库存</text>
+				<text class="value">{{ info.forwardStock == 0 ? '已回库' : '未回库' }}</text>
+			</view>
+			<view class="row">
+				<text class="label">采购单</text>
+				<text class="value">{{ info.cgSn || '' }}</text>
+			</view>
+			<view class="row">
+				<text class="label">时间</text>
+				<text class="value">{{ info.addTime || '' }}</text>
+			</view>
+			<view class="row row--top" v-if="!$util.isEmpty(info.remark)">
+				<text class="label">备注</text>
+				<text class="value">{{ info.remark }}</text>
+			</view>
+		</view>
+
+		<view class="card" v-if="!$util.isEmpty(items)">
+			<view class="card-title">冲销花材</view>
+			<view class="item" v-for="(it, idx) in items" :key="idx">
+				<text class="item__name">{{ it.name }}</text>
+				<text class="item__meta">
+					数量 {{ parseFloat(it.num) }}
+					× ¥{{ parseFloat(it.unitPrice || it.price || 0) }}
+					= ¥{{ parseFloat(it.amount) }}
+				</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { forwardDetail } from '@/api/forward'
+
+export default {
+	name: 'forwardDetail',
+	data() {
+		return {
+			info: {},
+			items: []
+		}
+	},
+	computed: {
+		fundTypeText() {
+			const map = { 1: '原路退', 2: '返余额', 3: '仅记账' }
+			return map[Number(this.info.fundType)] || '--'
+		}
+	},
+	onShow() {
+		this.init()
+	},
+	methods: {
+		init() {
+			const id = this.option.id
+			if (!id) {
+				return
+			}
+			uni.showLoading({ mask: true })
+			forwardDetail({ id }).then(res => {
+				uni.hideLoading()
+				if (res.code == 1) {
+					this.info = res.data || {}
+					this.items = res.data.items || []
+				}
+			}).catch(() => {
+				uni.hideLoading()
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.forward-detail {
+	min-height: 100vh;
+	padding: 24upx;
+	background: #f5f7fa;
+	box-sizing: border-box;
+}
+
+.card {
+	background: #fff;
+	border-radius: 16upx;
+	padding: 8upx 24upx 24upx;
+	margin-bottom: 20upx;
+}
+
+.card-title {
+	font-size: 30upx;
+	font-weight: bold;
+	color: #333;
+	padding: 20upx 0 8upx;
+}
+
+.row {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	padding: 20upx 0;
+	border-bottom: 1upx solid #f0f2f5;
+
+	&:last-child {
+		border-bottom: none;
+	}
+
+	&--top {
+		align-items: flex-start;
+	}
+}
+
+.label {
+	width: 160upx;
+	flex-shrink: 0;
+	font-size: 28upx;
+	color: #909399;
+}
+
+.value {
+	flex: 1;
+	font-size: 28upx;
+	color: #333;
+
+	&--price {
+		color: #ff4757;
+		font-weight: bold;
+		font-size: 32upx;
+	}
+}
+
+.item {
+	padding: 18upx 0;
+	border-bottom: 1upx solid #f0f2f5;
+
+	&:last-child {
+		border-bottom: none;
+	}
+
+	&__name {
+		display: block;
+		font-size: 28upx;
+		color: #333;
+		font-weight: 600;
+	}
+
+	&__meta {
+		display: block;
+		margin-top: 8upx;
+		font-size: 26upx;
+		color: #666;
+	}
+}
+</style>

+ 131 - 0
hdApp/src/pagesPurchase/forwardList.vue

@@ -0,0 +1,131 @@
+<!--
+  花店冲销凭证列表(方案A)
+  用途:按采购单 cgId 展示关联冲销凭证;与售后记录分开展示,点击进详情。
+-->
+<template>
+	<view class="app-content">
+		<view v-if="forwardPrice > 0" class="sum-bar">
+			<text>累计冲销</text>
+			<text class="sum-bar__price">¥{{ parseFloat(forwardPrice) }}</text>
+		</view>
+		<block v-if="!$util.isEmpty(list.data)">
+			<view class="flex-space list" v-for="(item, index) in list.data" :key="index" @click="goDetail(item)">
+				<view class="label">
+					<text>日期:{{ item.addTime ? item.addTime.substr(5, 11) : '' }}</text>
+					<text>单号:{{ item.forwardSn }}</text>
+					<text>资金:{{ fundTypeText(item.fundType) }}</text>
+					<text>库存:{{ item.forwardStock == 0 ? '已回库' : '未回库' }}</text>
+				</view>
+				<view class="flex val">
+					-¥{{ parseFloat(item.amount) }}
+					<view class="iconfont iconxiangyou"></view>
+				</view>
+			</view>
+		</block>
+		<block v-else>
+			<app-wrapper-empty title="暂无冲销记录" :is-empty="$util.isEmpty(list.data)" />
+		</block>
+	</view>
+</template>
+
+<script>
+import { list } from '@/mixins'
+import { listByCg } from '@/api/forward'
+
+export default {
+	name: 'forwardList',
+	mixins: [list],
+	data() {
+		return {
+			forwardPrice: 0
+		}
+	},
+	onPullDownRefresh() {
+		this.resetList()
+		this._list().then(() => {
+			uni.stopPullDownRefresh()
+		})
+	},
+	onShow() {
+		this.resetList()
+		this._list()
+	},
+	methods: {
+		fundTypeText(type) {
+			const map = { 1: '原路退', 2: '返余额', 3: '仅记账' }
+			return map[Number(type)] || '--'
+		},
+		goDetail(item) {
+			if (!item.id) {
+				return
+			}
+			this.$util.pageTo({ url: '/pagesPurchase/forwardDetail', query: { id: item.id } })
+		},
+		/** 列表不分页,一次拉完 */
+		_list() {
+			return listByCg({
+				cgId: this.option.id || this.option.cgId
+			}).then(res => {
+				if (res.code == 1) {
+					this.forwardPrice = res.data.forwardPrice || 0
+					this.completes({
+						code: 1,
+						data: {
+							list: res.data.list || [],
+							moreData: 0
+						}
+					})
+				}
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.app-content {
+	.sum-bar {
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		justify-content: space-between;
+		padding: 24upx 30upx;
+		margin-bottom: 16upx;
+		background: #fff2f0;
+		font-size: 28upx;
+		color: #333;
+
+		&__price {
+			color: #ff4757;
+			font-weight: bold;
+			font-size: 32upx;
+		}
+	}
+
+	.list {
+		padding: 20upx 30upx;
+		margin-bottom: 20upx;
+		background: #FFFFFF;
+
+		.label {
+			position: relative;
+			line-height: 50upx;
+
+			text {
+				display: block;
+			}
+		}
+
+		.val {
+			font-size: 28upx;
+			color: #ff4757;
+			font-weight: bold;
+
+			.iconfont {
+				margin-left: 12upx;
+				color: #999999;
+			}
+		}
+	}
+}
+</style>

+ 8 - 0
hdApp/src/pagesPurchase/purDetails.vue

@@ -133,6 +133,14 @@
           <view>退款方式:</view>
           <view style="color:red;font-weight:bold;font-size:30upx;">{{detailInfo.tkExplain||''}}</view>
         </view>
+        <!-- 方案A:有冲销凭证时展示累计冲销入口 -->
+        <view v-if="detailInfo.hasForward == 1" class="order-info_box">
+          <view>累计冲销:</view>
+          <view style="color:red;font-weight:bold;font-size:30upx;">¥{{detailInfo.forwardPrice?parseFloat(detailInfo.forwardPrice):0}}</view>
+          <view class="price">
+            <button @click="pageTo({url: '/pagesPurchase/forwardList?id='+detailInfo.id})" class="admin-button-com">冲销记录</button>
+          </view>
+        </view>
 
       </view>