| | |
| | | }) |
| | | } |
| | | // å®åå¤ç-éä»¶å é¤ |
| | | export function afterSalesServiceFileDel(ids) { |
| | | export function afterSalesServiceFileDel(id) { |
| | | return request({ |
| | | url: '/afterSalesService/file/del', |
| | | url: `/afterSalesService/file/del/${id}`, |
| | | method: 'delete', |
| | | data: ids, |
| | | }) |
| | | } |
| | | |
| | | // å®åå¤ç-维修记å½å表 |
| | | export function afterSalesServiceRepairListPage(query) { |
| | | return request({ |
| | | url: '/afterSalesService/repair/listPage', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | |
| | | // 临æå®å管ç-å页æ¥è¯¢ |
| | | export function expiryAfterSalesListPage(query) { |
| | | return request({ |
| | | url: '/expiryAfterSales/listPage', |
| | | url: '/afterSalesNearExpiryService/listPage', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | |
| | | // 临æå®å管ç-æ°å¢ |
| | | export function expiryAfterSalesAdd(query) { |
| | | return request({ |
| | | url: '/expiryAfterSales/add', |
| | | url: '/afterSalesNearExpiryService/add', |
| | | method: 'post', |
| | | data: query, |
| | | }) |
| | |
| | | // 临æå®å管ç-æ´æ° |
| | | export function expiryAfterSalesUpdate(query) { |
| | | return request({ |
| | | url: '/expiryAfterSales/update', |
| | | url: '/afterSalesNearExpiryService/update', |
| | | method: 'post', |
| | | data: query, |
| | | }) |
| | | } |
| | | |
| | | // 临æå®å管ç-å é¤ |
| | | export function expiryAfterSalesDelete(query) { |
| | | export function expiryAfterSalesDelete(ids) { |
| | | return request({ |
| | | url: '/expiryAfterSales/delete', |
| | | url: '/afterSalesNearExpiryService/delete?ids=' + ids, |
| | | method: 'delete', |
| | | data: query, |
| | | }) |
| | | } |
| | | } |
| | | |
| | | // æ¥è¯¢ææå®¢æ·ä¿¡æ¯ |
| | | // /basic/customer/list |
| | | export function getAllCustomerList(query) { |
| | | return request({ |
| | | url: '/basic/customer/list', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | |
| | | // æ ¹æ®å®¢æ·æ¥è¯¢éå®è®¢åå· |
| | | // afterSalesService/listSalesLedger |
| | | export function getSalesLedger(query) { |
| | | return request({ |
| | | url: '/afterSalesService/listSalesLedger', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | |
| | | // æ ¹æ®éå®è®¢åå·æ¥è¯¢éå®è®¢å详æ
|
| | | // afterSalesService/count |
| | | export function getSalesLedgerDetail(query) { |
| | | return request({ |
| | | url: '/afterSalesService/count', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | |
| | | // éå®éè´§-æ¥è¯¢ |
| | | // /returnManagement/listPage |
| | | export function returnManagementList(query) { |
| | | return request({ |
| | | url: "/returnManagement/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // éå®éè´§-æ·»å |
| | | // /returnManagement/add |
| | | export function returnManagementAdd(data) { |
| | | return request({ |
| | | url: "/returnManagement/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // éå®éè´§-ä¿®æ¹ |
| | | // /returnManagement/update |
| | | export function returnManagementUpdate(data) { |
| | | return request({ |
| | | url: "/returnManagement/update", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // éå®éè´§-å é¤ |
| | | // /returnManagement/del |
| | | export function returnManagementDel(data) { |
| | | return request({ |
| | | url: "/returnManagement/del", |
| | | method: "delete", |
| | | data, |
| | | }); |
| | | } |
| | | |
| | | // éå®éè´§-æ¥è¯¢ |
| | | // /returnManagement/getById |
| | | export function returnManagementGetById(query) { |
| | | return request({ |
| | | url: "/returnManagement/getById", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // éå®éè´§-æ ¹æ®åºåºåæ¥è¯¢éå®è®¢å以å产åä¿¡æ¯ |
| | | // /returnManagement/getByShippingId |
| | | export function returnManagementGetByShippingId(query) { |
| | | return request({ |
| | | url: "/returnManagement/getByShippingId", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // éè¿å®¢æ·åç§°æ¥è¯¢ |
| | | // /shippingInfo/getByCustomerName |
| | | export function getSalesLedger(query) { |
| | | return request({ |
| | | url: '/shippingInfo/getByCustomerName', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | |
| | | // å¤ç |
| | | // /returnManagement/handle |
| | | export function returnManagementHandle(data) { |
| | | return request({ |
| | | url: "/returnManagement/handle", |
| | | method: "get", |
| | | params: data, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <el-dialog v-model="dialogVisible" title="éè´§å详æ
" width="90%" @close="closeDia"> |
| | | <div v-loading="loading"> |
| | | <span class="descriptions">åºæ¬ä¿¡æ¯</span> |
| | | <el-descriptions :column="4" border> |
| | | <el-descriptions-item label="éè´§åå·">{{ detail.returnNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="åæ®ç¶æ"> |
| | | <el-tag :type="getStatusType(detail.status)">{{ getStatusText(detail.status) }}</el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="客æ·åç§°">{{ detail.customerName }}</el-descriptions-item> |
| | | <el-descriptions-item label="éå®åå·">{{ detail.salesContractNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="ä¸å¡å">{{ detail.salesman }}</el-descriptions-item> |
| | | <el-descriptions-item label="å
³èåºåºåå·">{{ detail.shippingNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="项ç®åç§°">{{ detail.projectName }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¶å人">{{ detail.maker }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¶åæ¶é´">{{ detail.makeTime }}</el-descriptions-item> |
| | | <el-descriptions-item label="éè´§åå ">{{ detail.returnReason }}</el-descriptions-item> |
| | | <el-descriptions-item label="鿬¾æ»é¢">{{ detail.refundAmount }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <div style="padding-top: 20px"> |
| | | <span class="descriptions">产åå表</span> |
| | | <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData" /> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="closeDia">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from "vue"; |
| | | import { returnManagementGetById, returnManagementGetByShippingId } from "@/api/salesManagement/returnOrder.js"; |
| | | |
| | | const dialogVisible = ref(false); |
| | | const loading = ref(false); |
| | | const detail = ref({}); |
| | | const tableData = ref([]); |
| | | const availableProducts = ref([]); |
| | | |
| | | const tableColumn = [ |
| | | {align: "center", label: "产å大类", prop: "productCategory"}, |
| | | {align: "center", label: "è§æ ¼åå·", prop: "specificationModel"}, |
| | | {align: "center", label: "åä½", prop: "unit", width: 80}, |
| | | {align: "center", label: "æ»æ°é", prop: "quantity", width: 120}, |
| | | {align: "center", label: "å·²éè´§æ°é", prop: "totalReturnNum", width: 120}, |
| | | {align: "center", label: "æªéè´§æ°é", prop: "unQuantity", width: 120}, |
| | | {align: "center", label: "éè´§æ°é", prop: "returnQuantity", width: 120}, |
| | | {align: "center", label: "é货产ååä»·", prop: "price", width: 120}, |
| | | {align: "center", label: "é货产åéé¢", prop: "amount", width: 120}, |
| | | {align: "center", label: "æ¯å¦æè´¨éé®é¢", prop: "isQuality", width: 140, formatData: (v) => ({ "1": "æ¯", "2": "å¦" }[String(v)] ?? v)}, |
| | | {align: "center", label: "夿³¨", prop: "remark", width: 150}, |
| | | ]; |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 0: "warning", |
| | | 1: "success" |
| | | }; |
| | | return statusMap[status] || "info"; |
| | | }; |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | 0: "å¾
å¤ç", |
| | | 1: "å·²å¤ç" |
| | | }; |
| | | return statusMap[status] || "æªç¥"; |
| | | }; |
| | | |
| | | const openDialog = async (row) => { |
| | | if (!row?.id) return; |
| | | dialogVisible.value = true; |
| | | loading.value = true; |
| | | try { |
| | | const res = await returnManagementGetById({ returnManagementId: row.id }); |
| | | detail.value = res?.data ?? res ?? {}; |
| | | |
| | | if (detail.value.shippingId) { |
| | | const productRes = await returnManagementGetByShippingId({ shippingId: detail.value.shippingId }); |
| | | if (productRes.code === 200) { |
| | | availableProducts.value = productRes.data.productDtoData || []; |
| | | } |
| | | } |
| | | |
| | | const list = |
| | | detail.value?.returnSaleProducts || |
| | | detail.value?.returnSaleProductList || |
| | | detail.value?.returnSaleProductDtoData || |
| | | []; |
| | | |
| | | tableData.value = Array.isArray(list) ? list.map(raw => { |
| | | const productId = raw?.returnSaleLedgerProductId ?? raw?.saleLedgerProductId ?? raw?.id; |
| | | const product = availableProducts.value.find((p) => p.id === productId); |
| | | const normalized = { |
| | | ...raw, |
| | | id: productId, |
| | | returnQuantity: Number(raw?.num ?? raw?.returnQuantity ?? 0), |
| | | price: Number(raw?.taxInclusiveUnitPrice ?? raw?.price ?? 0), |
| | | amount: Number(raw?.amount ?? 0).toFixed(2), |
| | | isQuality: raw?.isQuality ?? 2, |
| | | remark: raw?.remark ?? "", |
| | | }; |
| | | return product ? { ...product, ...normalized } : normalized; |
| | | }) : []; |
| | | } catch (e) { |
| | | console.error("Failed to load detail", e); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const closeDia = () => { |
| | | dialogVisible.value = false; |
| | | detail.value = {}; |
| | | tableData.value = []; |
| | | availableProducts.value = []; |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .descriptions { |
| | | margin-bottom: 20px; |
| | | display: inline-block; |
| | | font-size: 1rem; |
| | | font-weight: 600; |
| | | padding-left: 12px; |
| | | position: relative; |
| | | } |
| | | .descriptions::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 1rem; |
| | | background-color: #002FA7; |
| | | border-radius: 2px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog v-model="dialogFormVisible" :title="operationType === 'edit' ? 'ç¼è¾éè´§å' : 'æ°å¢éè´§å'" width="90%" @close="closeDia"> |
| | | <div> |
| | | <span class="descriptions">åºæ¬ä¿¡æ¯</span> |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="4"> |
| | | <el-form-item label="éè´§åå·ï¼" prop="returnNo"> |
| | | <el-input |
| | | :disabled="operationType === 'edit' || form.returnNoCheckbox" |
| | | v-model="form.returnNo" |
| | | placeholder="使ç¨ç³»ç»ç¼å·" |
| | | class="input-with-select" |
| | | > |
| | | <template v-if="operationType !== 'edit'" #append> |
| | | <el-checkbox v-model="form.returnNoCheckbox" @change="handleReturnNoCheckboxChange"></el-checkbox> |
| | | </template> |
| | | </el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="客æ·åç§°ï¼" prop="customerId"> |
| | | <el-select v-model="form.customerId" filterable placeholder="è¯·éæ©å®¢æ·" @change="customerNameChange"> |
| | | <el-option |
| | | v-for="item in customerNameOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.id" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="å
³èåºåºåå·ï¼" prop="shippingId"> |
| | | <el-select v-model="form.shippingId" filterable placeholder="è¯·éæ©åºåºåå·" @change="outboundNoChange"> |
| | | <el-option |
| | | v-for="item in outboundOptions" |
| | | :key="item.value" |
| | | :label="item.label" |
| | | :value="item.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="å¶å人ï¼" prop="maker"> |
| | | <el-select v-model="form.maker" filterable placeholder="è¯·éæ©å¶å人"> |
| | | <el-option v-for="u in userOptions" :key="u.value" :label="u.label" :value="u.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="å¶åæ¶é´ï¼" prop="makeTime"> |
| | | <el-date-picker v-model="form.makeTime" type="datetime" style="width:100%" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="ç¶æï¼" prop="status"> |
| | | <el-select v-model="form.status" placeholder="è¯·éæ©ç¶æ"> |
| | | <el-option label="å¾
å¤ç" :value="0" /> |
| | | <el-option label="å·²å¤ç" :value="1" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="éè´§åå ï¼" prop="returnReason"> |
| | | <el-input v-model="form.returnReason" placeholder="请è¾å
¥éè´§åå " /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="鿬¾æ»é¢ï¼" prop="refundAmount"> |
| | | <el-input v-model="form.refundAmount" disabled placeholder="èªå¨è®¡ç®" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <hr> |
| | | <div style="padding-top: 20px"> |
| | | <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px"> |
| | | <span class="descriptions" style="margin-bottom:0">产åå表</span> |
| | | <el-button type="primary" @click="openProductSelection" :disabled="!form.shippingId">æ·»å 产å</el-button> |
| | | </div> |
| | | <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData"> |
| | | <template #returnQuantity="{ row }"> |
| | | <el-input |
| | | v-model="row.returnQuantity" |
| | | style="width:100px" |
| | | placeholder="请è¾å
¥" |
| | | type="number" |
| | | @input="(val) => handleReturnQuantityChange(val, row)" |
| | | /> |
| | | </template> |
| | | <template #price="{ row }"> |
| | | <el-input |
| | | v-model="row.price" |
| | | style="width:100px" |
| | | placeholder="请è¾å
¥" |
| | | type="number" |
| | | @input="(val) => handlePriceChange(val, row)" |
| | | /> |
| | | </template> |
| | | <template #amount="{ row }"> |
| | | <el-input |
| | | v-model="row.amount" |
| | | style="width:100px" |
| | | placeholder="èªå¨è®¡ç®" |
| | | type="number" |
| | | disabled |
| | | /> |
| | | </template> |
| | | <template #isQuality="{ row }"> |
| | | <el-select v-model="row.isQuality" placeholder="è¯·éæ©" style="width:120px"> |
| | | <el-option label="æ¯" :value="1" /> |
| | | <el-option label="å¦" :value="2" /> |
| | | </el-select> |
| | | </template> |
| | | <template #remark="{ row }"> |
| | | <el-input |
| | | v-model="row.remark" |
| | | style="width:130px" |
| | | placeholder="请è¾å
¥" |
| | | /> |
| | | </template> |
| | | <template #action="{ row, index }"> |
| | | <el-button type="danger" link @click="deleteRow(index)">å é¤</el-button> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | |
| | | <el-dialog v-model="productSelectionVisible" title="éæ©äº§å" width="70%" append-to-body> |
| | | <el-table |
| | | :data="availableProducts" |
| | | style="width: 100%" |
| | | @selection-change="handleSelectionChange" |
| | | ref="productTableRef" |
| | | row-key="id" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" prop="productCategory" label="产å大类" /> |
| | | <el-table-column align="center" prop="specificationModel" label="è§æ ¼åå·" /> |
| | | <el-table-column align="center" prop="unit" label="åä½" /> |
| | | <el-table-column align="center" prop="quantity" label="æ»æ°é" /> |
| | | <el-table-column align="center" prop="unQuantity" label="æªéè´§æ°é" /> |
| | | <el-table-column align="center" label="å·²éè´§æ°é"> |
| | | <template #default="{ row }">{{ calcAlreadyReturned(row) }}</template> |
| | | </el-table-column> |
| | | |
| | | </el-table> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="confirmProductSelection">确认添å </el-button> |
| | | <el-button @click="productSelectionVisible = false">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref, toRefs, getCurrentInstance } from "vue"; |
| | | import { returnManagementAdd, returnManagementUpdate, returnManagementGetByShippingId, getSalesLedger, returnManagementGetById } from "@/api/salesManagement/returnOrder.js"; |
| | | import { getAllCustomerList } from "@/api/customerService/index.js"; |
| | | import useUserStore from "@/store/modules/user.js"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { listProject } from "@/api/oaSystem/projectManagement.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(['close']) |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref(''); |
| | | const formRef = ref(null); |
| | | const userStore = useUserStore(); |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | returnNoCheckbox: true, |
| | | returnNo: "", |
| | | customerId: "", |
| | | shippingId: "", |
| | | projectId: "", |
| | | maker: "", |
| | | makeTime: "", |
| | | status: 0, |
| | | returnReason: "", |
| | | refundAmount: "", |
| | | }, |
| | | rules: { |
| | | returnNo: [{ |
| | | validator: (rule, value, callback) => { |
| | | if (form.value.returnNoCheckbox) return callback(); |
| | | if (!value) return callback(new Error("请è¾å
¥éè´§åå·")); |
| | | callback(); |
| | | }, trigger: "blur" |
| | | }], |
| | | customerId: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], |
| | | shippingId: [{ required: true, message: "è¯·éæ©å
³èåºåºåå·", trigger: "change" }], |
| | | } |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | |
| | | const calcAlreadyReturned = (row) => { |
| | | const total = Number(row?.quantity ?? row?.totalQuantity ?? row?.totalReturnNum ?? 0); |
| | | const un = Number(row?.unQuantity ?? 0); |
| | | if (!Number.isFinite(total) || !Number.isFinite(un)) return 0; |
| | | return Math.max(total - un, 0); |
| | | }; |
| | | |
| | | const tableColumn = ref([ |
| | | {align: "center", label: "产å大类", prop: "productCategory" }, |
| | | {align: "center", label: "è§æ ¼åå·", prop: "specificationModel" }, |
| | | {align: "center", label: "åä½", prop: "unit", width: 80 }, |
| | | {align: "center", label: "æ»æ°é", prop: "quantity", width: 120 }, |
| | | {align: "center", label: "å·²éè´§æ°é", prop: "totalReturnNum", width: 120 }, |
| | | {align: "center", label: "æªéè´§æ°é", prop: "unQuantity", width: 120 }, |
| | | {align: "center", label: "éè´§æ°é", prop: "returnQuantity", dataType: "slot", slot: "returnQuantity", width: 120 }, |
| | | {align: "center", label: "é货产ååä»·", prop: "price", dataType: "slot", slot: "price", width: 120 }, |
| | | {align: "center", label: "é货产åéé¢", prop: "amount", dataType: "slot", slot: "amount", width: 120 }, |
| | | {align: "center", label: "æ¯å¦æè´¨éé®é¢", prop: "isQuality", dataType: "slot", slot: "isQuality", width: 140 }, |
| | | {align: "center", label: "夿³¨", prop: "remark", dataType: "slot", slot: "remark", width: 150 }, |
| | | {align: "center", label: "æä½" , prop: "action", dataType: "slot", slot: "action", width: 120 }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const customerNameOptions = ref([]); |
| | | const outboundOptions = ref([]); |
| | | const userOptions = ref([]); |
| | | const projectOptions = ref([]); |
| | | |
| | | const deleteRow = (index) => { |
| | | tableData.value.splice(index, 1); |
| | | }; |
| | | |
| | | const normalizeDetailRow = (raw) => { |
| | | const productId = raw?.returnSaleLedgerProductId ?? raw?.saleLedgerProductId ?? raw?.id; |
| | | const returnSaleProductId = raw?.returnSaleProductId ?? raw?.id; |
| | | const num = Number(raw?.num ?? raw?.returnQuantity ?? 0); |
| | | return { |
| | | ...raw, |
| | | id: productId, |
| | | returnSaleProductId, |
| | | returnSaleLedgerProductId: productId, |
| | | productModelId: raw?.productModelId, |
| | | num, |
| | | returnQuantity: Number.isFinite(num) ? num : 0, |
| | | price: Number(raw?.taxInclusiveUnitPrice ?? raw?.price ?? 0), |
| | | amount: Number(raw?.amount ?? 0).toFixed(2), |
| | | isQuality: raw?.isQuality ?? 2, |
| | | remark: raw?.remark ?? "", |
| | | }; |
| | | }; |
| | | |
| | | const setFormForEdit = async (row) => { |
| | | const res = await returnManagementGetById({ returnManagementId: row?.id }); |
| | | console.log("res", res); |
| | | const detail = res?.data ?? res ?? {}; |
| | | |
| | | Object.assign(form.value, detail); |
| | | form.value.returnNoCheckbox = true; |
| | | |
| | | if (form.value.customerId) { |
| | | await customerNameChange(form.value.customerId, false); |
| | | } |
| | | if (form.value.shippingId) { |
| | | await outboundNoChange(form.value.shippingId, false); |
| | | } |
| | | |
| | | const list = |
| | | detail?.returnSaleProducts || |
| | | detail?.returnSaleProductList || |
| | | detail?.returnSaleProductDtoData || |
| | | []; |
| | | |
| | | tableData.value = Array.isArray(list) |
| | | ? list.map((raw) => { |
| | | const normalized = normalizeDetailRow(raw); |
| | | const product = availableProducts.value.find((p) => p.id === normalized.id); |
| | | return product ? { ...product, ...normalized } : normalized; |
| | | }) |
| | | : []; |
| | | |
| | | calculateTotalRefund(); |
| | | }; |
| | | |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | | dialogFormVisible.value = true; |
| | | proxy.resetForm("formRef"); |
| | | await Promise.all([initCustomers(), initUsers(), initProjects()]); |
| | | if (type === "edit") { |
| | | await setFormForEdit(row); |
| | | } else { |
| | | tableData.value = []; |
| | | Object.assign(form.value, { |
| | | returnNoCheckbox: true, |
| | | returnNo: "", |
| | | customerId: "", |
| | | shippingId: "", |
| | | projectId: "", |
| | | maker: "", |
| | | makeTime: "", |
| | | status: 0, |
| | | returnReason: "", |
| | | refundAmount: "", |
| | | }); |
| | | form.value.maker = userStore.nickName || userStore.name || ""; |
| | | form.value.makeTime = new Date().toISOString().replace('T', ' ').split('.')[0]; // Default to now |
| | | form.value.status = 0; // Default status |
| | | } |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (!valid) return; |
| | | const returnSaleProducts = (tableData.value || []).map(el => ({ |
| | | returnSaleLedgerProductId: el.returnSaleLedgerProductId ?? el.id, |
| | | productModelId: el.productModelId, |
| | | unit: el.unit, |
| | | num: Number(el.num ?? el.returnQuantity ?? 0), |
| | | price: Number(el.price ?? 0), |
| | | amount: Number(el.amount ?? 0), |
| | | isQuality: el.isQuality ?? 2, |
| | | remark: el.remark ?? "", |
| | | id: operationType.value === "edit" ? (el.returnSaleProductId ?? "") : "" |
| | | })); |
| | | const payload = { ...form.value, returnSaleProducts }; |
| | | delete payload.returnNoCheckbox; |
| | | if (operationType.value === "add" && form.value.returnNoCheckbox) delete payload.returnNo; |
| | | if (operationType.value === "add") { |
| | | returnManagementAdd(payload).then(() => { |
| | | proxy.$modal.msgSuccess("æ°å¢æå"); |
| | | closeDia(); |
| | | }); |
| | | } else { |
| | | returnManagementUpdate(payload).then(() => { |
| | | proxy.$modal.msgSuccess("ä¿®æ¹æå"); |
| | | closeDia(); |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | dialogFormVisible.value = false; |
| | | emit('close'); |
| | | }; |
| | | |
| | | const initCustomers = async () => { |
| | | const res = await getAllCustomerList({}); |
| | | if (res?.records) { |
| | | customerNameOptions.value = res.records.map(item => ({ |
| | | label: item.customerName, |
| | | value: item.customerName, // Keep value as name if needed for other logic, but request says customerId |
| | | id: item.id, |
| | | code: item.customerCode |
| | | })); |
| | | } |
| | | }; |
| | | |
| | | const initUsers = async () => { |
| | | const res = await userListNoPageByTenantId(); |
| | | if (res?.data) { |
| | | userOptions.value = res.data.map(u => ({ label: u.nickName || u.userName, value: u.nickName || u.userName })); |
| | | } |
| | | }; |
| | | |
| | | const initProjects = async () => { |
| | | try { |
| | | const res = await listProject({ pageSize: 1000 }); |
| | | if (res?.rows) { |
| | | projectOptions.value = res.rows.map(p => ({ label: p.projectName, value: p.id })); |
| | | } |
| | | } catch (e) { |
| | | console.error("Failed to load projects", e); |
| | | } |
| | | }; |
| | | |
| | | const handleReturnNoCheckboxChange = (checked) => { |
| | | if (checked) form.value.returnNo = ""; |
| | | formRef.value?.validateField('returnNo'); |
| | | }; |
| | | |
| | | const customerNameChange = async (val, clearDownstream = true) => { |
| | | // val is customerId now |
| | | if (clearDownstream) { |
| | | form.value.shippingId = ""; |
| | | outboundOptions.value = []; |
| | | } |
| | | |
| | | // Find customer name for getSalesLedger if it requires name |
| | | const customer = customerNameOptions.value.find(c => c.id === val); |
| | | if (!customer) return; |
| | | |
| | | // Assuming getSalesLedger takes customerName. If it takes ID, adjust accordingly. |
| | | // Previous code used customerName. Let's try passing customerName. |
| | | getSalesLedger({ |
| | | customerName: customer.label, |
| | | }).then(res => { |
| | | if(res.code === 200){ |
| | | outboundOptions.value = res.data.map(item => ({ |
| | | label: item.salesContractNo, // Or whatever the outbound number field is |
| | | value: item.id, |
| | | })) |
| | | } |
| | | }) |
| | | }; |
| | | |
| | | const outboundNoChange = async (val, clearTable = true) => { |
| | | // val is shippingId |
| | | let res = await returnManagementGetByShippingId({ shippingId: val }); |
| | | if(res.code === 200){ |
| | | // If backend returns project info, set it |
| | | if (res.data.projectId) form.value.projectId = res.data.projectId; |
| | | |
| | | // Store available products for selection |
| | | availableProducts.value = res.data.productDtoData || []; |
| | | if (clearTable) tableData.value = []; |
| | | } |
| | | }; |
| | | |
| | | const handleReturnQuantityChange = (val, row) => { |
| | | if (val === "" || val === null) return; |
| | | const max = row.unQuantity === undefined || row.unQuantity === null ? Infinity : Number(row.unQuantity || 0); |
| | | const current = Number(val); |
| | | |
| | | if (current > max) { |
| | | proxy.$nextTick(() => { |
| | | row.returnQuantity = max; |
| | | row.num = max; |
| | | }); |
| | | proxy.$modal.msgWarning(`éè´§æ°éä¸è½è¶
è¿æªéè´§æ°é(${max})`); |
| | | } else if (current < 0) { |
| | | proxy.$nextTick(() => { |
| | | row.returnQuantity = 0; |
| | | row.num = 0; |
| | | }); |
| | | } else { |
| | | row.num = current; |
| | | } |
| | | calculateRowAmount(row); |
| | | calculateTotalRefund(); |
| | | }; |
| | | |
| | | const handlePriceChange = (val, row) => { |
| | | if (val === "" || val === null) { |
| | | row.price = 0; |
| | | } |
| | | calculateRowAmount(row); |
| | | calculateTotalRefund(); |
| | | }; |
| | | |
| | | const calculateRowAmount = (row) => { |
| | | const quantity = Number(row.returnQuantity || 0); |
| | | const price = Number(row.price || 0); |
| | | row.amount = (quantity * price).toFixed(2); |
| | | }; |
| | | |
| | | const calculateTotalRefund = () => { |
| | | const total = tableData.value.reduce((sum, row) => { |
| | | return sum + Number(row.amount || 0); |
| | | }, 0); |
| | | form.value.refundAmount = total.toFixed(2); |
| | | }; |
| | | |
| | | const availableProducts = ref([]); |
| | | const productSelectionVisible = ref(false); |
| | | const selectedProducts = ref([]); |
| | | |
| | | const openProductSelection = () => { |
| | | productSelectionVisible.value = true; |
| | | // Pre-select items already in tableData |
| | | proxy.$nextTick(() => { |
| | | if (proxy.$refs.productTableRef) { |
| | | proxy.$refs.productTableRef.clearSelection(); |
| | | availableProducts.value.forEach(row => { |
| | | if (tableData.value.some(item => item.id === row.id)) { |
| | | proxy.$refs.productTableRef.toggleRowSelection(row, true); |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const handleSelectionChange = (val) => { |
| | | selectedProducts.value = val; |
| | | }; |
| | | |
| | | // Removed checkSelectable to allow toggling existing items |
| | | const confirmProductSelection = () => { |
| | | const newTableData = []; |
| | | |
| | | selectedProducts.value.forEach(product => { |
| | | const existing = tableData.value.find(item => item.id === product.id); |
| | | if (existing) { |
| | | newTableData.push(existing); |
| | | } else { |
| | | newTableData.push({ |
| | | ...product, |
| | | returnSaleLedgerProductId: product.id, |
| | | productModelId: product.productModelId, |
| | | returnQuantity: 0, |
| | | num: 0, |
| | | price: Number(product.taxInclusiveUnitPrice ?? 0), |
| | | amount: "0.00", |
| | | isQuality: 2, |
| | | remark: "", |
| | | productName: product.productName, |
| | | specificationModel: product.specificationModel, |
| | | unit: product.unit, |
| | | quantity: product.quantity, |
| | | totalReturnNum: product.totalReturnNum, |
| | | unQuantity: product.unQuantity |
| | | }); |
| | | } |
| | | }); |
| | | |
| | | tableData.value = newTableData; |
| | | productSelectionVisible.value = false; |
| | | }; |
| | | |
| | | defineExpose({ openDialog }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .descriptions { |
| | | margin-bottom: 20px; |
| | | display: inline-block; |
| | | font-size: 1rem; |
| | | font-weight: 600; |
| | | padding-left: 12px; |
| | | position: relative; |
| | | } |
| | | .descriptions::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 0; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 1rem; |
| | | background-color: #002FA7; |
| | | border-radius: 2px; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search-wrapper"> |
| | | <el-form :model="searchForm" class="demo-form-inline"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="4"> |
| | | <el-form-item label="éè´§åå·"> |
| | | <el-input v-model="searchForm.returnNo" placeholder="请è¾å
¥éè´§åå·" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="客æ·åç§°"> |
| | | <el-input v-model="searchForm.customerName" placeholder="客æ·åç§°" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="éå®åå·"> |
| | | <el-input v-model="searchForm.salesContractNo" placeholder="éå®åå·" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item label="å
³èåºåºåå·"> |
| | | <el-input v-model="searchForm.shippingNo" placeholder="å
³èåºåºåå·" clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">æç´¢</el-button> |
| | | <el-button @click="handleReset">éç½®</el-button> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | </div> |
| | | <div class="table_list"> |
| | | <div class="table_header" style="display: flex;justify-content: flex-end;margin-bottom: 10px;"> |
| | | <el-button type="primary" @click="openForm('add')">æ°å»ºéå®éè´§</el-button> |
| | | <el-button type="danger" plain @click="handleDelete">å é¤</el-button> |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="true" |
| | | @selection-change="handleSelectionChange" |
| | | :tableLoading="tableLoading" |
| | | @pagination="pagination" |
| | | > |
| | | <template #status="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag> |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | <form-dia ref="formDia" @close="handleQuery" /> |
| | | <detail-dia ref="detailDia" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref, toRefs, computed, getCurrentInstance, nextTick, onMounted } from "vue"; |
| | | import { ElMessageBox } from "element-plus"; |
| | | import FormDia from "./components/formDia.vue"; |
| | | import DetailDia from "./components/detailDia.vue"; |
| | | import { returnManagementList, returnManagementDel, returnManagementHandle } from "@/api/salesManagement/returnOrder.js"; |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const formDia = ref(); |
| | | const detailDia = ref(); |
| | | const openForm = (type, row) => { |
| | | nextTick(() => formDia.value?.openDialog(type, row)); |
| | | }; |
| | | |
| | | const openDetail = (row) => { |
| | | nextTick(() => detailDia.value?.openDialog(row)); |
| | | }; |
| | | |
| | | const handleRowDelete = (row) => { |
| | | if (!row?.id) return; |
| | | ElMessageBox.confirm("该éè´§åå°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | returnManagementDel([row.id]).then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const handleRowHandle = (row) => { |
| | | if (!row?.id) return; |
| | | ElMessageBox.confirm("æ¯å¦å¤ç该éè´§åï¼å¤çåå°æ æ³ä¿®æ¹", "å¤çæç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | returnManagementHandle({ returnManagementId: String(row.id) }).then(() => { |
| | | proxy.$modal.msgSuccess("å¤çæå"); |
| | | getList(); |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | returnNo: "", |
| | | status: "", |
| | | customerName: "", |
| | | salesContractNo: "", |
| | | salesman: "", |
| | | shippingNo: "", |
| | | projectName: "", |
| | | salesLedgerId: "", |
| | | makeTime: "" |
| | | } |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const documentStatusOptions = ref([ |
| | | { label: "å¾
å¤ç", value: 0 }, |
| | | { label: "å·²å¤ç", value: 1 } |
| | | ]); |
| | | |
| | | const defaultColumns = [ |
| | | { label: "éè´§åå·", prop: "returnNo", width: 160 }, |
| | | { label: "åæ®ç¶æ", prop: "status", width: 90, dataType: "slot", slot: "status" }, |
| | | { label: "å¶åæ¶é´", prop: "makeTime", width: 170 }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: 220 }, |
| | | { label: "éå®åå·", prop: "salesContractNo", width: 160 }, |
| | | { label: "ä¸å¡å", prop: "salesman", width: 120 }, |
| | | { label: "å
³èåºåºåå·", prop: "shippingNo", width: 170 }, |
| | | { label: "项ç®åç§°", prop: "projectName", width: 180 }, |
| | | { label: "å¶å人", prop: "maker", width: 120 }, |
| | | { |
| | | label: "æä½", |
| | | prop: "operation", |
| | | dataType: "action", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 240, |
| | | operation: [ |
| | | { name: "ç¼è¾", disabled: (row) => row.status !== 0, type: "text", clickFun: (row) => openForm("edit", row) }, |
| | | { name: "鿬¾å¤ç", disabled: (row) => row.status !== 0, type: "text", clickFun: (row) => handleRowHandle(row) }, |
| | | { name: "详æ
", type: "text", clickFun: (row) => openDetail(row) }, |
| | | { name: "å é¤", disabled: (row) => row.status !== 0, type: "text", clickFun: (row) => handleRowDelete(row) }, |
| | | ], |
| | | }, |
| | | ]; |
| | | const tableColumn = defaultColumns; |
| | | |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ current: 1, size: 10, total: 0 }); |
| | | const selectedRows = ref([]); |
| | | const tableHeight = computed(() => "calc(100% - 80px)"); |
| | | |
| | | const handleReset = () => { |
| | | Object.keys(searchForm.value).forEach(k => searchForm.value[k] = ""); |
| | | handleQuery(); |
| | | }; |
| | | const handleSelectionChange = (selection) => { |
| | | selectedRows.value = selection; |
| | | }; |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | const pagination = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList(); |
| | | }; |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | returnManagementList({ ...searchForm.value, ...page }).then(res => { |
| | | tableLoading.value = false; |
| | | tableData.value = res?.data?.records || []; |
| | | page.total = res?.data?.total || 0; |
| | | }).finally(() => tableLoading.value = false); |
| | | }; |
| | | const handleOut = () => { |
| | | ElMessageBox.alert("导åºåè½å¾
æ¥å
¥æ¥å£", "æç¤º"); |
| | | }; |
| | | const handleDelete = () => { |
| | | let ids = []; |
| | | if (selectedRows.value.length === 0) { |
| | | proxy.$modal.msgWarning("è¯·éæ©æ°æ®"); |
| | | return; |
| | | } |
| | | ids = selectedRows.value.map(i => i.id); |
| | | console.log(ids); |
| | | ElMessageBox.confirm("éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", "å é¤æç¤º", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "åæ¶", |
| | | type: "warning", |
| | | }).then(() => { |
| | | returnManagementDel( ids ).then(() => { |
| | | proxy.$modal.msgSuccess("å 餿å"); |
| | | getList(); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const getStatusType = (status) => { |
| | | const statusMap = { |
| | | 0: "warning", |
| | | 1: "success" |
| | | }; |
| | | return statusMap[status] || "info"; |
| | | }; |
| | | |
| | | const getStatusText = (status) => { |
| | | const statusMap = { |
| | | 0: "å¾
å¤ç", |
| | | 1: "å·²å¤ç" |
| | | }; |
| | | return statusMap[status] || "æªç¥"; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .search-wrapper { |
| | | background: white; |
| | | padding: 1rem 1rem 0 1rem; |
| | | border: 8px; |
| | | border-radius: 16px; |
| | | } |
| | | </style> |