| | |
| | | "BTYX": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "河南帮太优选进出口有限公司", |
| | | "VITE_BASE_API": "http://127.0.0.1:9001", |
| | | "VITE_JAVA_API": "http://127.0.0.1:9000" |
| | | "VITE_BASE_API": "http://1.15.17.182:9056", |
| | | "VITE_JAVA_API": "http://1.15.17.182:9057" |
| | | }, |
| | | "logo": "logo/BTYXLogo.png", |
| | | "favicon": "favicon/BTYXfavicon.ico" |
| | |
| | | "logo": "logo/WTXCLogo.png", |
| | | "favicon": "favicon/WTXCfavicon.ico" |
| | | }, |
| | | "KYHG": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "山西坤源化工有限公司", |
| | | "VITE_BASE_API": "http://36.137.13.29:9001", |
| | | "VITE_JAVA_API": "http://36.137.13.29:9000" |
| | | }, |
| | | "logo": "logo/KYHGLogo.png", |
| | | "favicon": "favicon/KYHGfavicon.ico" |
| | | }, |
| | | "DYKJ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "山西德益科技有限公司", |
| | | "VITE_BASE_API": "http://36.137.12.37:9001", |
| | | "VITE_JAVA_API": "http://36.137.12.37:9000" |
| | | }, |
| | | "logo": "logo/DYKJLogo.png", |
| | | "favicon": "favicon/DYKJfavicon.ico" |
| | | }, |
| | | "logo": "/src/assets/logo/logo.png", |
| | | "favicon": "/public/favicon.ico" |
| | | } |
| | |
| | | }); |
| | | } |
| | | |
| | | // 根据采购台账 ID 查询可退产品等信息 |
| | | export function getPurchaseReturnOrderByPurchaseLedgerId(query) { |
| | | return request({ |
| | | url: "/purchaseReturnOrders/getByPurchaseLedgerId", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // 查看详情 |
| | | // purchaseReturnOrders/selectById/xxx |
| | | export function getPurchaseReturnOrderDetail(id) { |
| | |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #originalValue="{ row }"> |
| | |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-form :model="form" :rules="rules" :disabled="isView" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="资产编号" prop="assetCode"> |
| | |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">确定</el-button> |
| | | <el-button v-if="!isView" type="primary" @click="submitForm">确定</el-button> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | </template> |
| | | </FormDialog> |
| | |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const multipleList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const isView = ref(false); |
| | | const currentId = ref(null); |
| | | const selectedIds = computed(() => |
| | | multipleList.value |
| | | .map(item => item?.id) |
| | | .filter(id => id !== undefined && id !== null && id !== "") |
| | | ); |
| | | |
| | | const createDefaultForm = () => ({ |
| | | assetCode: "", |
| | |
| | | status: filters.status, |
| | | }); |
| | | dataList.value = data?.records || []; |
| | | multipleList.value = []; |
| | | pagination.total = Number(data?.total || 0); |
| | | } catch (error) { |
| | | // 提示由全局请求拦截器处理,这里仅防止未捕获异常 |
| | | } |
| | | }; |
| | | |
| | | const handleSelectionChange = (selectionList) => { |
| | | multipleList.value = selectionList; |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | currentId.value = null; |
| | | dialogTitle.value = "新增固定资产"; |
| | | Object.assign(form, createDefaultForm(), { |
| | |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | isView.value = false; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "编辑固定资产"; |
| | | Object.assign(form, createDefaultForm(), row); |
| | |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`查看资产: ${row.assetName}`); |
| | | edit(row); |
| | | isView.value = true; |
| | | }; |
| | | |
| | | const handleDelete = (row) => { |
| | |
| | | }; |
| | | |
| | | const handleDepreciation = () => { |
| | | ElMessageBox.confirm("确认进行本月折旧计提吗?", "提示", { |
| | | const ids = selectedIds.value; |
| | | const confirmText = ids.length |
| | | ? `确认对选中的 ${ids.length} 条资产进行本月折旧计提吗?` |
| | | : "确认进行本月折旧计提吗?"; |
| | | ElMessageBox.confirm(confirmText, "提示", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "info", |
| | | }).then(async () => { |
| | | await depreciateFixedAsset({}); |
| | | await depreciateFixedAsset({ ids }); |
| | | ElMessage.success("折旧计提完成"); |
| | | await getTableData(); |
| | | }); |
| | |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | if (isView.value) { |
| | | dialogVisible.value = false; |
| | | return; |
| | | } |
| | | formRef.value.validate(async valid => { |
| | | if (valid) { |
| | | try { |
| | |
| | | </div> |
| | | <PIMTable |
| | | rowKey="id" |
| | | isSelection |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @selection-change="handleSelectionChange" |
| | | @pagination="changePage" |
| | | > |
| | | <template #originalValue="{ row }"> |
| | |
| | | </div> |
| | | |
| | | <FormDialog :title="dialogTitle" v-model="dialogVisible" width="800px" @confirm="submitForm" @cancel="dialogVisible = false"> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="120px"> |
| | | <el-form :model="form" :rules="rules" :disabled="isView" ref="formRef" label-width="120px"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="资产编号" prop="assetCode"> |
| | |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <el-button type="primary" @click="submitForm">确定</el-button> |
| | | <el-button v-if="!isView" type="primary" @click="submitForm">确定</el-button> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | </template> |
| | | </FormDialog> |
| | |
| | | ]; |
| | | |
| | | const dataList = ref([]); |
| | | const multipleList = ref([]); |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const isView = ref(false); |
| | | const currentId = ref(null); |
| | | const selectedIds = computed(() => |
| | | multipleList.value |
| | | .map(item => item?.id) |
| | | .filter(id => id !== undefined && id !== null && id !== "") |
| | | ); |
| | | |
| | | const createDefaultForm = () => ({ |
| | | assetCode: "", |
| | |
| | | status: filters.status, |
| | | }); |
| | | dataList.value = data?.records || []; |
| | | multipleList.value = []; |
| | | pagination.total = Number(data?.total || 0); |
| | | } catch (error) { |
| | | // 提示由全局请求拦截器处理,这里仅防止未捕获异常 |
| | | } |
| | | }; |
| | | |
| | | const handleSelectionChange = (selectionList) => { |
| | | multipleList.value = selectionList; |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | |
| | | |
| | | const add = () => { |
| | | isEdit.value = false; |
| | | isView.value = false; |
| | | currentId.value = null; |
| | | dialogTitle.value = "新增无形资产"; |
| | | Object.assign(form, createDefaultForm(), { |
| | |
| | | |
| | | const edit = (row) => { |
| | | isEdit.value = true; |
| | | isView.value = false; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "编辑无形资产"; |
| | | Object.assign(form, createDefaultForm(), row); |
| | |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`查看资产: ${row.assetName}`); |
| | | edit(row); |
| | | isView.value = true; |
| | | }; |
| | | |
| | | const handleDelete = (row) => { |
| | |
| | | }; |
| | | |
| | | const handleAmortization = () => { |
| | | ElMessageBox.confirm("确认进行本月摊销计提吗?", "提示", { |
| | | const ids = selectedIds.value; |
| | | const confirmText = ids.length |
| | | ? `确认对选中的 ${ids.length} 条资产进行本月摊销计提吗?` |
| | | : "确认进行本月摊销计提吗?"; |
| | | ElMessageBox.confirm(confirmText, "提示", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "info", |
| | | }).then(async () => { |
| | | await amortizeIntangibleAsset({}); |
| | | await amortizeIntangibleAsset({ ids }); |
| | | ElMessage.success("摊销计提完成"); |
| | | await getTableData(); |
| | | }); |
| | |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | if (isView.value) { |
| | | dialogVisible.value = false; |
| | | return; |
| | | } |
| | | formRef.value.validate(async valid => { |
| | | if (valid) { |
| | | try { |
| | |
| | | icon="Download">导出</el-button> |
| | | </div> |
| | | </div> |
| | | <PIMTable rowKey="id" |
| | | :column="columns" |
| | | :tableData="dataList" |
| | | :page="{ |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | total: pagination.total, |
| | | }" |
| | | @pagination="changePage"> |
| | | </PIMTable> |
| | | <el-table ref="tableRef" |
| | | v-loading="loading" |
| | | :data="dataList" |
| | | row-key="id" |
| | | :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" |
| | | height="calc(100vh - 280px)" |
| | | border |
| | | stripe |
| | | highlight-current-row |
| | | class="subject-table"> |
| | | <el-table-column label="科目编码" prop="subjectCode" width="140"> |
| | | <template #default="scope"> |
| | | <span class="subject-code">{{ scope.row.subjectCode }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="科目名称" prop="subjectName" min-width="180"> |
| | | <template #default="scope"> |
| | | <span class="subject-name" :class="{ 'is-parent': scope.row.children?.length > 0 }"> |
| | | {{ scope.row.subjectName }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="科目类型" prop="subjectType" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag size="small" :type="getSubjectTypeType(scope.row.subjectType)"> |
| | | {{ scope.row.subjectType }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="余额方向" prop="balanceDirection" width="100" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag size="small" :type="scope.row.balanceDirection === '借方' ? 'primary' : 'danger'"> |
| | | {{ scope.row.balanceDirection }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="状态" prop="status" width="80" align="center"> |
| | | <template #default="scope"> |
| | | <el-tag size="small" :type="scope.row.status === 0 || scope.row.status === '0' ? 'success' : 'info'"> |
| | | {{ scope.row.status === 0 || scope.row.status === '0' ? '启用' : '禁用' }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="备注" prop="remark" show-overflow-tooltip min-width="150" /> |
| | | <el-table-column label="操作" align="center" fixed="right" width="240"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" icon="Plus" @click="addChild(scope.row)">新增</el-button> |
| | | <el-button link type="primary" icon="Edit" @click="edit(scope.row)">编辑</el-button> |
| | | <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)">删除</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <FormDialog :title="dialogTitle" |
| | | v-model="dialogVisible" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, getCurrentInstance } from "vue"; |
| | | import { ref, reactive, onMounted, getCurrentInstance, nextTick } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import { |
| | |
| | | const dialogTitle = ref(""); |
| | | const parentSubjectLabel = ref("顶级科目"); |
| | | const formRef = ref(null); |
| | | const tableRef = ref(null); |
| | | const isEdit = ref(false); |
| | | const loading = ref(false); |
| | | |
| | | const form = reactive({ |
| | | id: undefined, |
| | |
| | | }; |
| | | |
| | | const getTableData = () => { |
| | | loading.value = true; |
| | | const query = { |
| | | current: pagination.currentPage, |
| | | size: pagination.pageSize, |
| | | ...filters, |
| | | }; |
| | | listAccountSubject(query).then(response => { |
| | | dataList.value = response.data.records; |
| | | pagination.total = response.data.total; |
| | | dataList.value = response.data.records || []; |
| | | loading.value = false; |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | }); |
| | | }; |
| | | |
| | |
| | | justify-content: space-between; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .subject-table { |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | |
| | | :deep(.el-table__row) { |
| | | transition: background-color 0.3s; |
| | | } |
| | | |
| | | :deep(.el-table__row:hover) { |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .subject-code { |
| | | color: #606266; |
| | | } |
| | | |
| | | .subject-name { |
| | | font-weight: 500; |
| | | |
| | | &.is-parent { |
| | | color: #409eff; |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="会计科目:"> |
| | | <el-cascader v-model="filters.subject" :options="subjectOptions" :props="{ label: 'name', value: 'code' }" placeholder="请选择会计科目" clearable style="width: 250px;" filterable /> |
| | | </el-form-item> |
| | | <el-form-item label="辅助核算:"> |
| | | <el-select v-model="filters.auxiliary" placeholder="请选择辅助核算" clearable style="width: 180px;"> |
| | | <el-option label="客户" value="customer" /> |
| | | <el-option label="供应商" value="supplier" /> |
| | | <el-option label="部门" value="department" /> |
| | | <el-option label="员工" value="employee" /> |
| | | <el-option label="项目" value="project" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="核算对象:"> |
| | | <el-select v-model="filters.auxiliaryItem" placeholder="请选择核算对象" clearable style="width: 200px;" :disabled="!filters.auxiliary"> |
| | | <el-option v-for="item in auxiliaryItems" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="期间:"> |
| | | <el-date-picker v-model="filters.startMonth" type="month" placeholder="开始月份" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <span style="margin: 0 10px;">至</span> |
| | | <el-date-picker v-model="filters.endMonth" type="month" placeholder="结束月份" value-format="YYYY-MM" style="width: 140px;" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">查询</el-button> |
| | | <el-button @click="resetFilters">重置</el-button> |
| | | <el-button @click="handlePrint" icon="Printer">打印</el-button> |
| | | <el-button @click="handleOut" icon="Download">导出</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="app-container ledger-page"> |
| | | <div class="ledger-layout"> |
| | | <aside class="subject-panel"> |
| | | <el-input v-model="subjectKeyword" placeholder="请输入科目名称/编号" clearable prefix-icon="Search" /> |
| | | <el-scrollbar class="subject-tree-scroll"> |
| | | <el-tree |
| | | ref="subjectTreeRef" |
| | | :data="subjectOptions" |
| | | node-key="code" |
| | | :props="{ label: 'name', children: 'children' }" |
| | | highlight-current |
| | | default-expand-all |
| | | :expand-on-click-node="false" |
| | | :filter-node-method="filterSubjectNode" |
| | | @node-click="handleSubjectClick" |
| | | > |
| | | <template #default="{ data }"> |
| | | <span class="subject-node">{{ data.code }} {{ data.name }}</span> |
| | | </template> |
| | | </el-tree> |
| | | </el-scrollbar> |
| | | </aside> |
| | | |
| | | <div class="ledger-header" v-if="currentSubject"> |
| | | <h2>科目明细账</h2> |
| | | <p>科目: {{ currentSubject.code }} {{ currentSubject.name }}</p> |
| | | <p v-if="filters.auxiliary && filters.auxiliaryItem">辅助核算: {{ getAuxiliaryLabel() }}</p> |
| | | <p>期间: {{ filters.startMonth }} 至 {{ filters.endMonth }}</p> |
| | | <section class="ledger-content"> |
| | | <el-form :model="filters" :inline="true" class="filter-form"> |
| | | <el-form-item label="期间:"> |
| | | <el-date-picker v-model="filters.startMonth" type="month" placeholder="开始月份" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <span style="margin: 0 10px;">至</span> |
| | | <el-date-picker v-model="filters.endMonth" type="month" placeholder="结束月份" value-format="YYYY-MM" style="width: 140px;" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">查询</el-button> |
| | | <el-button @click="resetFilters">重置</el-button> |
| | | <el-button @click="handlePrint" icon="Printer">打印</el-button> |
| | | <el-button @click="handleOut" icon="Download">导出</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="table_list"> |
| | | <el-table :data="dataList" border style="width: 100%"> |
| | | <el-table-column prop="date" label="日期" width="120" /> |
| | | <el-table-column prop="voucherNo" label="凭证字号" width="120" /> |
| | | <el-table-column prop="summary" label="摘要" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column prop="debit" label="借方" width="150"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.debit > 0" class="text-danger">¥{{ formatMoney(row.debit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="credit" label="贷方" width="150"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.credit > 0" class="text-success">¥{{ formatMoney(row.credit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="方向" width="80"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.direction === '借' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="余额" width="150"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">¥{{ formatMoney(Math.abs(row.balance)) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <el-empty v-if="!currentSubject" description="请选择会计科目查询" style="margin-top: 50px;" /> |
| | | </section> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <el-table :data="dataList" border style="width: 100%" show-summary :summary-method="getSummaries"> |
| | | <el-table-column prop="date" label="日期" width="120" /> |
| | | <el-table-column prop="voucherNo" label="凭证字号" width="120" /> |
| | | <el-table-column prop="summary" label="摘要" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column prop="debit" label="借方" width="150"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.debit > 0" class="text-danger">¥{{ formatMoney(row.debit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="credit" label="贷方" width="150"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.credit > 0" class="text-success">¥{{ formatMoney(row.credit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="方向" width="80"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.direction === '借' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="余额" width="150"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">¥{{ formatMoney(Math.abs(row.balance)) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <el-empty v-if="!currentSubject" description="请选择会计科目查询" style="margin-top: 50px;" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed, watch } from "vue"; |
| | | import { ref, reactive, onMounted, computed, watch, nextTick } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { listAccountSubject } from "@/api/financialManagement/accountSubject"; |
| | | import { getDetailLedger } from "@/api/financialManagement/ledger"; |
| | |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | subject: [], |
| | | auxiliary: "", |
| | | auxiliaryItem: "", |
| | | startMonth: "2024-01", |
| | | endMonth: "2024-03", |
| | | subject: "", |
| | | startMonth: "", |
| | | endMonth: "", |
| | | }); |
| | | |
| | | const dataList = ref([]); |
| | | const subjectOptions = ref([]); |
| | | const subjectKeyword = ref(""); |
| | | const subjectTreeRef = ref(); |
| | | |
| | | const getPreviousMonth = () => { |
| | | const date = new Date(); |
| | | date.setDate(1); |
| | | date.setMonth(date.getMonth() - 1); |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | return `${year}-${month}`; |
| | | }; |
| | | |
| | | const defaultMonth = getPreviousMonth(); |
| | | filters.startMonth = defaultMonth; |
| | | filters.endMonth = defaultMonth; |
| | | |
| | | const fallbackSubjects = [ |
| | | { code: "1122", name: "应收账款" }, |
| | |
| | | { code: "6602", name: "管理费用" }, |
| | | ]; |
| | | |
| | | const loadSubjectOptions = async () => { |
| | | try { |
| | | const { data } = await listAccountSubject({ |
| | | current: 1, |
| | | size: 1000, |
| | | }); |
| | | const records = data?.records || []; |
| | | if (records.length > 0) { |
| | | subjectOptions.value = records |
| | | .filter(item => item.subjectCode && item.subjectName) |
| | | .map(item => ({ |
| | | code: item.subjectCode, |
| | | name: item.subjectName, |
| | | children: [], |
| | | })); |
| | | return; |
| | | } |
| | | } catch (error) { |
| | | // 全局拦截器已提示,下面走兜底科目 |
| | | } |
| | | subjectOptions.value = fallbackSubjects.map(item => ({ ...item, children: [] })); |
| | | }; |
| | | |
| | | const auxiliaryItems = computed(() => { |
| | | const map = { |
| | | customer: [ |
| | | { value: "1", label: "北京科技有限公司" }, |
| | | { value: "2", label: "上海贸易公司" }, |
| | | { value: "3", label: "广州实业有限公司" }, |
| | | ], |
| | | supplier: [ |
| | | { value: "1", label: "北京原材料供应商" }, |
| | | { value: "2", label: "上海电子元器件公司" }, |
| | | { value: "3", label: "广州包装材料厂" }, |
| | | ], |
| | | department: [ |
| | | { value: "1", label: "财务部" }, |
| | | { value: "2", label: "销售部" }, |
| | | { value: "3", label: "采购部" }, |
| | | ], |
| | | employee: [ |
| | | { value: "1", label: "张三" }, |
| | | { value: "2", label: "李四" }, |
| | | { value: "3", label: "王五" }, |
| | | ], |
| | | project: [ |
| | | { value: "1", label: "项目A" }, |
| | | { value: "2", label: "项目B" }, |
| | | { value: "3", label: "项目C" }, |
| | | ], |
| | | }; |
| | | return map[filters.auxiliary] || []; |
| | | }); |
| | | |
| | | watch(() => filters.auxiliary, () => { |
| | | filters.auxiliaryItem = ""; |
| | | }); |
| | | |
| | | const currentSubject = computed(() => { |
| | | if (!filters.subject || filters.subject.length === 0) return null; |
| | | const code = filters.subject[filters.subject.length - 1]; |
| | | return findSubject(subjectOptions.value, code); |
| | | }); |
| | | const toTree = (nodes = []) => |
| | | nodes |
| | | .filter(item => item.subjectCode && item.subjectName) |
| | | .map(item => ({ |
| | | code: item.subjectCode, |
| | | name: item.subjectName, |
| | | children: toTree(item.children || []), |
| | | })); |
| | | |
| | | const findSubject = (options, code) => { |
| | | for (const item of options) { |
| | |
| | | return null; |
| | | }; |
| | | |
| | | const getAuxiliaryLabel = () => { |
| | | const item = auxiliaryItems.value.find(i => i.value === filters.auxiliaryItem); |
| | | return item ? item.label : ""; |
| | | const currentSubject = computed(() => { |
| | | if (!filters.subject) return null; |
| | | return findSubject(subjectOptions.value, filters.subject); |
| | | }); |
| | | |
| | | const getFirstSubjectCode = (nodes = []) => { |
| | | for (const item of nodes) { |
| | | if (item.code) return item.code; |
| | | if (item.children && item.children.length > 0) { |
| | | const childCode = getFirstSubjectCode(item.children); |
| | | if (childCode) return childCode; |
| | | } |
| | | } |
| | | return ""; |
| | | }; |
| | | |
| | | const setDefaultSubjectSelection = async () => { |
| | | const firstCode = getFirstSubjectCode(subjectOptions.value); |
| | | if (!firstCode) { |
| | | filters.subject = ""; |
| | | subjectTreeRef.value?.setCurrentKey(null); |
| | | return; |
| | | } |
| | | filters.subject = firstCode; |
| | | await nextTick(); |
| | | subjectTreeRef.value?.setCurrentKey(firstCode); |
| | | }; |
| | | |
| | | const filterSubjectNode = (value, data) => { |
| | | const keyword = value?.trim(); |
| | | if (!keyword) return true; |
| | | return `${data.code}${data.name}`.includes(keyword); |
| | | }; |
| | | |
| | | watch(subjectKeyword, (value) => { |
| | | subjectTreeRef.value?.filter(value || ""); |
| | | }); |
| | | |
| | | const handleSubjectClick = async (data) => { |
| | | filters.subject = data.code; |
| | | await getTableData(); |
| | | }; |
| | | |
| | | const loadSubjectOptions = async () => { |
| | | let options = []; |
| | | try { |
| | | const { data } = await listAccountSubject({ |
| | | current: 1, |
| | | size: 1000, |
| | | }); |
| | | options = toTree(data?.records || []); |
| | | } catch (error) { |
| | | // 全局拦截器已提示,下面走兜底科目 |
| | | } |
| | | if (options.length === 0) { |
| | | options = fallbackSubjects.map(item => ({ ...item, children: [] })); |
| | | } |
| | | subjectOptions.value = options; |
| | | await setDefaultSubjectSelection(); |
| | | if (filters.subject) { |
| | | await getTableData(); |
| | | } |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | |
| | | return Number(value).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ","); |
| | | }; |
| | | |
| | | // 联调约定:明细账接口可按辅助核算过滤(auxiliaryType/auxiliaryId) |
| | | // 联调约定:明细账按科目与期间过滤 |
| | | const getTableData = async () => { |
| | | if (!currentSubject.value) { |
| | | dataList.value = []; |
| | |
| | | try { |
| | | const { data } = await getDetailLedger({ |
| | | subjectCode: currentSubject.value.code, |
| | | auxiliaryType: filters.auxiliary, |
| | | auxiliaryId: filters.auxiliaryItem, |
| | | startMonth: filters.startMonth, |
| | | endMonth: filters.endMonth, |
| | | }); |
| | |
| | | } |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.subject = []; |
| | | filters.auxiliary = ""; |
| | | filters.auxiliaryItem = ""; |
| | | filters.startMonth = "2024-01"; |
| | | filters.endMonth = "2024-03"; |
| | | const resetFilters = async () => { |
| | | filters.startMonth = defaultMonth; |
| | | filters.endMonth = defaultMonth; |
| | | dataList.value = []; |
| | | }; |
| | | |
| | | const getSummaries = (param) => { |
| | | const { columns, data } = param; |
| | | const sums = []; |
| | | columns.forEach((column, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "合计"; |
| | | return; |
| | | } |
| | | if (column.property === "debit") { |
| | | const values = data.map(item => Number(item.debit)); |
| | | const sum = values.reduce((prev, curr) => prev + curr, 0); |
| | | sums[index] = "¥" + formatMoney(sum); |
| | | } else if (column.property === "credit") { |
| | | const values = data.map(item => Number(item.credit)); |
| | | const sum = values.reduce((prev, curr) => prev + curr, 0); |
| | | sums[index] = "¥" + formatMoney(sum); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | subjectKeyword.value = ""; |
| | | subjectTreeRef.value?.filter(""); |
| | | await setDefaultSubjectSelection(); |
| | | if (filters.subject) { |
| | | await getTableData(); |
| | | } |
| | | }; |
| | | |
| | | const handlePrint = () => { |
| | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .ledger-header { |
| | | text-align: center; |
| | | margin-bottom: 20px; |
| | | h2 { |
| | | margin: 0 0 10px 0; |
| | | } |
| | | p { |
| | | color: #606266; |
| | | margin: 5px 0; |
| | | } |
| | | .ledger-layout { |
| | | display: flex; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .subject-panel { |
| | | width: 260px; |
| | | flex-shrink: 0; |
| | | padding: 12px; |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 8px; |
| | | background-color: #fff; |
| | | } |
| | | |
| | | .subject-tree-scroll { |
| | | height: 600px; |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .subject-node { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .ledger-content { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .filter-form { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .text-primary { |
| | |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .subject-panel :deep(.el-tree-node__content) { |
| | | height: 34px; |
| | | } |
| | | |
| | | .subject-panel :deep(.el-tree-node.is-current > .el-tree-node__content) { |
| | | background-color: #f0f7ff; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <el-form :model="filters" :inline="true"> |
| | | <el-form-item label="会计科目:"> |
| | | <el-cascader v-model="filters.subject" :options="subjectOptions" :props="{ label: 'name', value: 'code' }" placeholder="请选择会计科目" clearable style="width: 250px;" filterable /> |
| | | </el-form-item> |
| | | <el-form-item label="期间:"> |
| | | <el-date-picker v-model="filters.startMonth" type="month" placeholder="开始月份" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <span style="margin: 0 10px;">至</span> |
| | | <el-date-picker v-model="filters.endMonth" type="month" placeholder="结束月份" value-format="YYYY-MM" style="width: 140px;" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">查询</el-button> |
| | | <el-button @click="resetFilters">重置</el-button> |
| | | <el-button @click="handlePrint" icon="Printer">打印</el-button> |
| | | <el-button @click="handleOut" icon="Download">导出</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="app-container ledger-page"> |
| | | <div class="ledger-layout"> |
| | | <aside class="subject-panel"> |
| | | <el-input v-model="subjectKeyword" placeholder="请输入科目名称/编号" clearable prefix-icon="Search" /> |
| | | <el-scrollbar class="subject-tree-scroll"> |
| | | <el-tree |
| | | ref="subjectTreeRef" |
| | | :data="subjectOptions" |
| | | node-key="code" |
| | | :props="{ label: 'name', children: 'children' }" |
| | | highlight-current |
| | | default-expand-all |
| | | :expand-on-click-node="false" |
| | | :filter-node-method="filterSubjectNode" |
| | | @node-click="handleSubjectClick" |
| | | > |
| | | <template #default="{ data }"> |
| | | <span class="subject-node">{{ data.code }} {{ data.name }}</span> |
| | | </template> |
| | | </el-tree> |
| | | </el-scrollbar> |
| | | </aside> |
| | | |
| | | <div class="ledger-header" v-if="currentSubject"> |
| | | <h2>科目总账</h2> |
| | | <p>科目: {{ currentSubject.code }} {{ currentSubject.name }}</p> |
| | | <p>期间: {{ filters.startMonth }} 至 {{ filters.endMonth }}</p> |
| | | <section class="ledger-content"> |
| | | <el-form :model="filters" :inline="true" class="filter-form"> |
| | | <el-form-item label="期间:"> |
| | | <el-date-picker v-model="filters.startMonth" type="month" placeholder="开始月份" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <span style="margin: 0 10px;">至</span> |
| | | <el-date-picker v-model="filters.endMonth" type="month" placeholder="结束月份" value-format="YYYY-MM" style="width: 140px;" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">查询</el-button> |
| | | <el-button @click="resetFilters">重置</el-button> |
| | | <el-button @click="handlePrint" icon="Printer">打印</el-button> |
| | | <el-button @click="handleOut" icon="Download">导出</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div class="table_list"> |
| | | <el-table :data="dataList" border style="width: 100%"> |
| | | <el-table-column prop="date" label="日期" width="120" /> |
| | | <el-table-column prop="voucherNo" label="凭证字号" width="120" /> |
| | | <el-table-column prop="summary" label="摘要" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column prop="debit" label="借方" width="150"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.debit > 0" class="text-danger">¥{{ formatMoney(row.debit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="credit" label="贷方" width="150"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.credit > 0" class="text-success">¥{{ formatMoney(row.credit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="方向" width="80"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.direction === '借' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="余额" width="150"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">¥{{ formatMoney(Math.abs(row.balance)) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <el-empty v-if="!currentSubject" description="请选择会计科目查询" style="margin-top: 50px;" /> |
| | | </section> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <el-table :data="dataList" border style="width: 100%" show-summary :summary-method="getSummaries"> |
| | | <el-table-column prop="date" label="日期" width="120" /> |
| | | <el-table-column prop="voucherNo" label="凭证字号" width="120" /> |
| | | <el-table-column prop="summary" label="摘要" min-width="200" show-overflow-tooltip /> |
| | | <el-table-column prop="debit" label="借方" width="150"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.debit > 0" class="text-danger">¥{{ formatMoney(row.debit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="credit" label="贷方" width="150"> |
| | | <template #default="{ row }"> |
| | | <span v-if="row.credit > 0" class="text-success">¥{{ formatMoney(row.credit) }}</span> |
| | | <span v-else>-</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="方向" width="80"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="row.direction === '借' ? 'success' : 'danger'" size="small">{{ row.direction }}</el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="余额" width="150"> |
| | | <template #default="{ row }"> |
| | | <span :class="row.balance >= 0 ? 'text-primary' : 'text-warning'">¥{{ formatMoney(Math.abs(row.balance)) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <el-empty v-if="!currentSubject" description="请选择会计科目查询" style="margin-top: 50px;" /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from "vue"; |
| | | import { ref, reactive, onMounted, computed, watch, nextTick } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { listAccountSubject } from "@/api/financialManagement/accountSubject"; |
| | | import { getGeneralLedger } from "@/api/financialManagement/ledger"; |
| | |
| | | }); |
| | | |
| | | const filters = reactive({ |
| | | subject: [], |
| | | startMonth: "2024-01", |
| | | endMonth: "2024-03", |
| | | subject: "", |
| | | startMonth: "", |
| | | endMonth: "", |
| | | }); |
| | | |
| | | const dataList = ref([]); |
| | | const subjectOptions = ref([]); |
| | | const subjectKeyword = ref(""); |
| | | const subjectTreeRef = ref(); |
| | | |
| | | const getPreviousMonth = () => { |
| | | const date = new Date(); |
| | | date.setDate(1); |
| | | date.setMonth(date.getMonth() - 1); |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, "0"); |
| | | return `${year}-${month}`; |
| | | }; |
| | | |
| | | const defaultMonth = getPreviousMonth(); |
| | | filters.startMonth = defaultMonth; |
| | | filters.endMonth = defaultMonth; |
| | | |
| | | const fallbackSubjects = [ |
| | | { code: "1001", name: "库存现金" }, |
| | |
| | | { code: "6001", name: "主营业务收入" }, |
| | | ]; |
| | | |
| | | const toCascaderTree = (nodes = []) => |
| | | const toTree = (nodes = []) => |
| | | nodes |
| | | .filter(item => item.subjectCode && item.subjectName) |
| | | .map(item => ({ |
| | | code: item.subjectCode, |
| | | name: item.subjectName, |
| | | children: toCascaderTree(item.children || []), |
| | | children: toTree(item.children || []), |
| | | })); |
| | | |
| | | const loadSubjectOptions = async () => { |
| | | try { |
| | | const { data } = await listAccountSubject({ |
| | | current: 1, |
| | | size: 1000, |
| | | status: 0, |
| | | }); |
| | | const options = toCascaderTree(data?.records || []); |
| | | if (options.length > 0) { |
| | | subjectOptions.value = options; |
| | | return; |
| | | } |
| | | } catch (error) { |
| | | // 全局拦截器已提示,下面走兜底科目 |
| | | } |
| | | subjectOptions.value = fallbackSubjects.map(item => ({ ...item, children: [] })); |
| | | }; |
| | | |
| | | const currentSubject = computed(() => { |
| | | if (!filters.subject || filters.subject.length === 0) return null; |
| | | const code = filters.subject[filters.subject.length - 1]; |
| | | return findSubject(subjectOptions.value, code); |
| | | }); |
| | | |
| | | const findSubject = (options, code) => { |
| | | for (const item of options) { |
| | |
| | | } |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const currentSubject = computed(() => { |
| | | if (!filters.subject) return null; |
| | | return findSubject(subjectOptions.value, filters.subject); |
| | | }); |
| | | |
| | | const getFirstSubjectCode = (nodes = []) => { |
| | | for (const item of nodes) { |
| | | if (item.code) return item.code; |
| | | if (item.children && item.children.length > 0) { |
| | | const childCode = getFirstSubjectCode(item.children); |
| | | if (childCode) return childCode; |
| | | } |
| | | } |
| | | return ""; |
| | | }; |
| | | |
| | | const setDefaultSubjectSelection = async () => { |
| | | const firstCode = getFirstSubjectCode(subjectOptions.value); |
| | | if (!firstCode) { |
| | | filters.subject = ""; |
| | | subjectTreeRef.value?.setCurrentKey(null); |
| | | return; |
| | | } |
| | | filters.subject = firstCode; |
| | | await nextTick(); |
| | | subjectTreeRef.value?.setCurrentKey(firstCode); |
| | | }; |
| | | |
| | | const filterSubjectNode = (value, data) => { |
| | | const keyword = value?.trim(); |
| | | if (!keyword) return true; |
| | | return `${data.code}${data.name}`.includes(keyword); |
| | | }; |
| | | |
| | | watch(subjectKeyword, (value) => { |
| | | subjectTreeRef.value?.filter(value || ""); |
| | | }); |
| | | |
| | | const handleSubjectClick = async (data) => { |
| | | filters.subject = data.code; |
| | | await getTableData(); |
| | | }; |
| | | |
| | | const loadSubjectOptions = async () => { |
| | | let options = []; |
| | | try { |
| | | const { data } = await listAccountSubject({ |
| | | current: 1, |
| | | size: 1000, |
| | | status: 0, |
| | | }); |
| | | options = toTree(data?.records || []); |
| | | } catch (error) { |
| | | // 全局拦截器已提示,下面走兜底科目 |
| | | } |
| | | if (options.length === 0) { |
| | | options = fallbackSubjects.map(item => ({ ...item, children: [] })); |
| | | } |
| | | subjectOptions.value = options; |
| | | await setDefaultSubjectSelection(); |
| | | if (filters.subject) { |
| | | await getTableData(); |
| | | } |
| | | }; |
| | | |
| | | const formatMoney = (value) => { |
| | |
| | | } |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.subject = []; |
| | | filters.startMonth = "2024-01"; |
| | | filters.endMonth = "2024-03"; |
| | | const resetFilters = async () => { |
| | | filters.startMonth = defaultMonth; |
| | | filters.endMonth = defaultMonth; |
| | | dataList.value = []; |
| | | }; |
| | | |
| | | const getSummaries = (param) => { |
| | | const { columns, data } = param; |
| | | const sums = []; |
| | | columns.forEach((column, index) => { |
| | | if (index === 0) { |
| | | sums[index] = "合计"; |
| | | return; |
| | | } |
| | | if (column.property === "debit") { |
| | | const values = data.map(item => Number(item.debit)); |
| | | const sum = values.reduce((prev, curr) => prev + curr, 0); |
| | | sums[index] = "¥" + formatMoney(sum); |
| | | } else if (column.property === "credit") { |
| | | const values = data.map(item => Number(item.credit)); |
| | | const sum = values.reduce((prev, curr) => prev + curr, 0); |
| | | sums[index] = "¥" + formatMoney(sum); |
| | | } else { |
| | | sums[index] = ""; |
| | | } |
| | | }); |
| | | return sums; |
| | | subjectKeyword.value = ""; |
| | | subjectTreeRef.value?.filter(""); |
| | | await setDefaultSubjectSelection(); |
| | | if (filters.subject) { |
| | | await getTableData(); |
| | | } |
| | | }; |
| | | |
| | | const handlePrint = () => { |
| | |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .ledger-header { |
| | | text-align: center; |
| | | margin-bottom: 20px; |
| | | h2 { |
| | | margin: 0 0 10px 0; |
| | | } |
| | | p { |
| | | color: #606266; |
| | | margin: 5px 0; |
| | | } |
| | | .ledger-layout { |
| | | display: flex; |
| | | gap: 16px; |
| | | } |
| | | |
| | | .subject-panel { |
| | | width: 260px; |
| | | flex-shrink: 0; |
| | | padding: 12px; |
| | | border: 1px solid #e4e7ed; |
| | | border-radius: 8px; |
| | | background-color: #fff; |
| | | } |
| | | |
| | | .subject-tree-scroll { |
| | | height: 600px; |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .subject-node { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .ledger-content { |
| | | flex: 1; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .filter-form { |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .text-primary { |
| | |
| | | color: #e6a23c; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .subject-panel :deep(.el-tree-node__content) { |
| | | height: 34px; |
| | | } |
| | | |
| | | .subject-panel :deep(.el-tree-node.is-current > .el-tree-node__content) { |
| | | background-color: #f0f7ff; |
| | | } |
| | | </style> |
| | |
| | | </el-form-item> |
| | | <el-form-item label="制单人:"> |
| | | <el-select v-model="filters.creator" placeholder="请选择制单人" clearable style="width: 150px;"> |
| | | <el-option label="张三" value="张三" /> |
| | | <el-option label="李四" value="李四" /> |
| | | <el-option label="王五" value="王五" /> |
| | | <el-option |
| | | v-for="item in creatorOptions" |
| | | :key="item" |
| | | :label="item" |
| | | :value="item" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="状态:"> |
| | |
| | | <h2 class="voucher-title">记账凭证</h2> |
| | | <div class="voucher-period">{{ form.voucherDate ? form.voucherDate.substring(0, 7) + '期' : '' }}</div> |
| | | </div> |
| | | <el-form :model="form" :rules="rules" ref="formRef" label-width="0"> |
| | | <el-form :model="form" :rules="rules" :disabled="isViewMode" ref="formRef" label-width="0"> |
| | | <div class="voucher-info"> |
| | | <div class="voucher-no-section"> |
| | | <span class="label">凭证字:</span> |
| | | <el-select v-model="form.voucherPrefix" style="width: 70px;"> |
| | | <el-select v-model="form.voucherPrefix" :disabled="isViewMode" style="width: 70px;"> |
| | | <el-option label="记" value="记" /> |
| | | </el-select> |
| | | <el-input v-model="form.voucherNum" style="width: 60px;" /> |
| | | <el-input v-model="form.voucherNum" :disabled="isViewMode" style="width: 60px;" /> |
| | | <span class="label" style="margin-left: 5px;">号</span> |
| | | </div> |
| | | <div class="voucher-date-section"> |
| | | <span class="label">日期:</span> |
| | | <el-date-picker v-model="form.voucherDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 140px;" /> |
| | | <el-date-picker v-model="form.voucherDate" :disabled="isViewMode" type="date" placeholder="选择日期" value-format="YYYY-MM-DD" style="width: 140px;" /> |
| | | </div> |
| | | <div class="voucher-attachment-section"> |
| | | <span class="label">附件:</span> |
| | | <el-input-number v-model="form.attachmentCount" :min="0" :controls="false" style="width: 60px;" /> |
| | | <el-input-number v-model="form.attachmentCount" :disabled="isViewMode" :min="0" :controls="false" style="width: 60px;" /> |
| | | <span class="label" style="margin-left: 5px;">张</span> |
| | | <el-button type="primary" link style="margin-left: 10px;">上传文件</el-button> |
| | | <el-button type="primary" link :disabled="isViewMode" style="margin-left: 10px;">上传文件</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="voucher-table"> |
| | |
| | | <tbody> |
| | | <tr v-for="(entry, rowIndex) in form.entries" :key="rowIndex" @click="selectRow(rowIndex)" :class="{ 'selected-row': selectedRowIndex === rowIndex }"> |
| | | <td class="col-summary"> |
| | | <el-input v-model="entry.summary" placeholder="请输入摘要" @focus="selectRow(rowIndex)" /> |
| | | <el-input v-model="entry.summary" :disabled="isViewMode" placeholder="请输入摘要" @focus="selectRow(rowIndex)" /> |
| | | </td> |
| | | <td class="col-subject"> |
| | | <el-tree-select |
| | | v-model="entry.subjectCode" |
| | | :data="subjectTreeOptions" |
| | | :props="subjectTreeSelectProps" |
| | | :disabled="isViewMode" |
| | | placeholder="选择科目" |
| | | filterable |
| | | check-strictly |
| | |
| | | <!-- 借方11列 --> |
| | | <template v-if="editingCell.row === rowIndex && editingCell.type === 'debit'"> |
| | | <td colspan="11" class="debit-input-cell"> |
| | | <el-input-number ref="amountInputRef" v-model="entry.debit" :min="0" :precision="2" :controls="false" size="small" @blur="finishEdit" class="full-width-input" /> |
| | | <el-input-number ref="amountInputRef" v-model="entry.debit" :disabled="isViewMode" :min="0" :precision="2" :controls="false" size="small" @blur="finishEdit" class="full-width-input" /> |
| | | </td> |
| | | </template> |
| | | <template v-else> |
| | |
| | | <!-- 贷方11列 --> |
| | | <template v-if="editingCell.row === rowIndex && editingCell.type === 'credit'"> |
| | | <td colspan="11" class="credit-input-cell"> |
| | | <el-input-number ref="amountInputRef" v-model="entry.credit" :min="0" :precision="2" :controls="false" size="small" @blur="finishEdit" class="full-width-input" /> |
| | | <el-input-number ref="amountInputRef" v-model="entry.credit" :disabled="isViewMode" :min="0" :precision="2" :controls="false" size="small" @blur="finishEdit" class="full-width-input" /> |
| | | </td> |
| | | </template> |
| | | <template v-else> |
| | |
| | | </td> |
| | | </template> |
| | | <td class="col-action"> |
| | | <el-button type="danger" link size="small" @click="removeEntry(rowIndex)" icon="Delete" :disabled="form.entries.length <= 2">删除</el-button> |
| | | <el-button type="danger" link size="small" @click="removeEntry(rowIndex)" icon="Delete" :disabled="isViewMode || form.entries.length <= 2">删除</el-button> |
| | | </td> |
| | | </tr> |
| | | <tr class="total-row"> |
| | |
| | | </table> |
| | | </div> |
| | | <div class="voucher-toolbar"> |
| | | <el-button type="primary" link @click="addEntry" icon="Plus">新增行</el-button> |
| | | <el-button type="primary" link @click="addEntry" icon="Plus" :disabled="isViewMode">新增行</el-button> |
| | | </div> |
| | | <div class="voucher-footer"> |
| | | <div class="creator-section"> |
| | | <span class="label">制单人:{{ form.creator }}</span> |
| | | <span class="label">制单人:</span> |
| | | <el-select |
| | | v-model="form.creator" |
| | | :disabled="isViewMode" |
| | | placeholder="请选择制单人" |
| | | filterable |
| | | clearable |
| | | style="width: 200px;" |
| | | > |
| | | <el-option |
| | | v-for="item in creatorOptions" |
| | | :key="item" |
| | | :label="item" |
| | | :value="item" |
| | | /> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | </el-form> |
| | | </div> |
| | | <template #footer> |
| | | <div> |
| | | <el-button type="primary" @click="submitForm" :disabled="!isBalanced">保存</el-button> |
| | | <el-button @click="dialogVisible = false">取消</el-button> |
| | | <el-button v-if="!isViewMode" type="primary" @click="submitForm" :disabled="!isBalanced">保存</el-button> |
| | | <el-button @click="dialogVisible = false">{{ isViewMode ? '关闭' : '取消' }}</el-button> |
| | | </div> |
| | | </template> |
| | | </FormDialog> |
| | |
| | | import { ref, reactive, onMounted, computed, nextTick } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import FormDialog from "@/components/Dialog/FormDialog.vue"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user"; |
| | | import { listAccountSubject } from "@/api/financialManagement/accountSubject"; |
| | | import { |
| | | listVoucherPage, |
| | |
| | | defineOptions({ |
| | | name: "凭证管理", |
| | | }); |
| | | |
| | | const userStore = useUserStore(); |
| | | const getDefaultCreator = () => userStore.nickName || userStore.name || "张三"; |
| | | |
| | | const filters = reactive({ |
| | | voucherNo: "", |
| | |
| | | const dialogVisible = ref(false); |
| | | const dialogTitle = ref(""); |
| | | const formRef = ref(null); |
| | | const dialogMode = ref("add"); |
| | | const isEdit = ref(false); |
| | | const currentId = ref(null); |
| | | const isViewMode = computed(() => dialogMode.value === "view"); |
| | | |
| | | const fallbackSubjectTree = [ |
| | | { subjectCode: "1001", subjectName: "库存现金", balanceDirection: "借方", children: [] }, |
| | |
| | | voucherDate: "", |
| | | attachmentCount: 0, |
| | | entries: [createEmptyEntry(), createEmptyEntry()], |
| | | creator: "张三", |
| | | creator: getDefaultCreator(), |
| | | remark: "", |
| | | }); |
| | | |
| | | const form = reactive({ |
| | | ...createDefaultForm(), |
| | | }); |
| | | |
| | | const userOptions = ref([]); |
| | | |
| | | const creatorOptions = computed(() => { |
| | | const source = [ |
| | | ...userOptions.value.map(item => item.nickName || item.userName || item.name), |
| | | getDefaultCreator(), |
| | | form.creator, |
| | | filters.creator, |
| | | ]; |
| | | return [...new Set(source.filter(Boolean))]; |
| | | }); |
| | | |
| | | const selectedRowIndex = ref(-1); |
| | |
| | | } |
| | | }; |
| | | |
| | | const loadUserOptions = async () => { |
| | | try { |
| | | const { data } = await userListNoPageByTenantId(); |
| | | userOptions.value = Array.isArray(data) ? data : []; |
| | | } catch (error) { |
| | | userOptions.value = []; |
| | | } |
| | | }; |
| | | |
| | | const resetFilters = () => { |
| | | filters.voucherNo = ""; |
| | | filters.dateRange = []; |
| | |
| | | }; |
| | | |
| | | const addEntry = () => { |
| | | if (isViewMode.value) { |
| | | return; |
| | | } |
| | | form.entries.push(createEmptyEntry()); |
| | | }; |
| | | |
| | |
| | | }; |
| | | |
| | | const openAmountInput = (index, type) => { |
| | | if (isViewMode.value) { |
| | | return; |
| | | } |
| | | editingCell.row = index; |
| | | editingCell.type = type; |
| | | nextTick(() => { |
| | |
| | | }; |
| | | |
| | | const removeEntry = (index) => { |
| | | if (isViewMode.value) { |
| | | return; |
| | | } |
| | | if (form.entries.length <= 2) { |
| | | return; |
| | | } |
| | |
| | | }; |
| | | |
| | | const add = () => { |
| | | dialogMode.value = "add"; |
| | | isEdit.value = false; |
| | | currentId.value = null; |
| | | dialogTitle.value = "新增凭证"; |
| | |
| | | dialogVisible.value = true; |
| | | }; |
| | | |
| | | const edit = async row => { |
| | | const openVoucherDialog = async (row, mode = "edit") => { |
| | | try { |
| | | isEdit.value = true; |
| | | dialogMode.value = mode; |
| | | isEdit.value = mode === "edit"; |
| | | currentId.value = row.id; |
| | | dialogTitle.value = "编辑凭证"; |
| | | dialogTitle.value = mode === "view" ? "查看凭证" : "编辑凭证"; |
| | | const { data } = await getVoucherDetail(row.id); |
| | | const detail = data || row; |
| | | const parts = (detail.voucherNo || "").split("-"); |
| | | Object.assign(form, createDefaultForm(), detail, { |
| | | voucherPrefix: parts[0] || "记", |
| | | voucherNum: parts[1] || "", |
| | | creator: detail.creator || getDefaultCreator(), |
| | | entries: |
| | | detail.entries?.map(item => ({ |
| | | subjectCode: item.subjectCode || "", |
| | |
| | | } |
| | | }; |
| | | |
| | | const view = (row) => { |
| | | ElMessage.info(`查看凭证: ${row.voucherNo}`); |
| | | const edit = async row => { |
| | | await openVoucherDialog(row, "edit"); |
| | | }; |
| | | |
| | | const view = async row => { |
| | | await openVoucherDialog(row, "view"); |
| | | }; |
| | | |
| | | const handlePost = (row) => { |
| | |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | if (isViewMode.value) { |
| | | dialogVisible.value = false; |
| | | return; |
| | | } |
| | | formRef.value.validate(async valid => { |
| | | if (valid) { |
| | | // 前置校验:与后端规则对齐,减少无效请求 |
| | |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | await loadUserOptions(); |
| | | await loadSubjectList(); |
| | | await getTableData(); |
| | | }); |
| | |
| | | batchApproveStockInRecords, |
| | | } from "@/api/inventoryManagement/stockInRecord.js"; |
| | | import { |
| | | findAllQualifiedStockInRecordTypeOptions, findAllUnQualifiedStockInRecordTypeOptions, |
| | | findAllQualifiedStockInRecordTypeOptions, |
| | | // findAllUnQualifiedStockInRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | |
| | | const {proxy} = getCurrentInstance(); |
| | |
| | | }) |
| | | return |
| | | } |
| | | findAllUnQualifiedStockInRecordTypeOptions() |
| | | .then(res => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | }) |
| | | // findAllUnQualifiedStockInRecordTypeOptions() |
| | | // .then(res => { |
| | | // stockRecordTypeOptions.value = res.data; |
| | | // }) |
| | | } |
| | | |
| | | // 表格选择数据 |
| | |
| | | } from "@/api/inventoryManagement/stockInventory.js"; |
| | | import { |
| | | findAllQualifiedStockInRecordTypeOptions, |
| | | findAllUnQualifiedStockInRecordTypeOptions, |
| | | // findAllUnQualifiedStockInRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | |
| | | const fetchStockRecordTypeOptions = () => { |
| | | findAllQualifiedStockInRecordTypeOptions().then(res => { |
| | | stockRecordTypeOptions.value = res.data; |
| | | findAllUnQualifiedStockInRecordTypeOptions().then(res => { |
| | | stockRecordTypeOptions.value = [ |
| | | ...stockRecordTypeOptions.value, |
| | | ...res.data, |
| | | ]; |
| | | }); |
| | | // findAllUnQualifiedStockInRecordTypeOptions().then(res => { |
| | | // stockRecordTypeOptions.value = [ |
| | | // ...stockRecordTypeOptions.value, |
| | | // ...res.data, |
| | | // ]; |
| | | // }); |
| | | }); |
| | | }; |
| | | |
| | |
| | | <span class="title-text">产品列表</span> |
| | | </div> |
| | | <el-button type="primary" size="small" style="margin-bottom: 20px" @click="isShowProductsModal = true" :disabled="!formState.purchaseLedgerId">添加产品</el-button> |
| | | <el-table :data="formState.purchaseReturnOrderProductsDtos" |
| | | <div class="product-table-scroll"> |
| | | <el-table class="product-table-inner" |
| | | :data="formState.purchaseReturnOrderProductsDtos" |
| | | border |
| | | max-height="400" |
| | | :scroll-y="true" |
| | | show-summary |
| | | :summary-method="summarizeChildrenTable"> |
| | | <el-table-column align="center" |
| | |
| | | label="序号" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="入库单号" |
| | | prop="inboundBatches" |
| | | width="150" /> |
| | | <el-table-column label="批次号" |
| | | prop="batchNo" |
| | | width="150" /> |
| | | <el-table-column label="产品大类" |
| | | prop="productCategory" /> |
| | | <el-table-column label="规格型号" |
| | |
| | | prop="unit" |
| | | width="70" /> |
| | | <el-table-column label="数量" |
| | | prop="quantity" |
| | | prop="stockInNum" |
| | | width="100" /> |
| | | <el-table-column label="可退货数量" |
| | | prop="availableQuality" |
| | | prop="unQuantity" |
| | | width="130" /> |
| | | <el-table-column label="已退货数量" |
| | | width="130"> |
| | | <template #default="scope"> |
| | | {{ calcAlreadyReturned(scope.row) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="退货数量" |
| | | prop="returnQuantity" |
| | | width="180"> |
| | |
| | | placeholder="请输入退货数量" /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="库存预警数量" |
| | | <!-- <el-table-column label="库存预警数量" |
| | | prop="warnNum" |
| | | width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="税率(%)" |
| | | prop="taxRate" |
| | | width="80" /> |
| | | width="80" /> --> |
| | | <el-table-column label="含税单价(元)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | width="120" /> |
| | | <el-table-column label="退货总价(元)" |
| | | prop="taxInclusiveTotalPrice" |
| | | width="180"> |
| | | width="120"> |
| | | <template #default="scope"> |
| | | {{ formatAmount(getReturnTotal(scope.row)) || '--' }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="是否质检" |
| | | prop="isChecked" |
| | | width="150"> |
| | | width="100"> |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.isChecked ? 'success' : 'info'"> |
| | | {{ scope.row.isChecked ? '是' : '否' }} |
| | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="section-title"> |
| | |
| | | import {getOptions, purchaseList} from "@/api/procurementManagement/procurementLedger.js"; |
| | | import {userListNoPageByTenantId} from "@/api/system/user.js"; |
| | | const ProductList = defineAsyncComponent(() => import("@/views/procurementManagement/purchaseReturnOrder/ProductList.vue")); |
| | | import { |
| | | productList, |
| | | } from "@/api/procurementManagement/procurementLedger.js"; |
| | | const props = defineProps({ |
| | | visible: { |
| | | type: Boolean, |
| | |
| | | return Number.isNaN(num) ? 0 : num |
| | | } |
| | | |
| | | /** 已退货数量 = 入库行总数量 − 当前可退货数量(剩余) */ |
| | | const calcAlreadyReturned = (row) => { |
| | | const total = Number(row?.stockInNum ?? row?.totalQuantity ?? row?.quantity ?? 0) |
| | | const un = Number(row?.unQuantity ?? 0) |
| | | if (!Number.isFinite(total) || !Number.isFinite(un)) return 0 |
| | | return Math.max(total - un, 0) |
| | | } |
| | | |
| | | const getReturnTotal = (row) => { |
| | | const qty = toNumber(row?.returnQuantity) |
| | | const unitPrice = toNumber(row?.taxInclusiveUnitPrice) |
| | |
| | | } |
| | | |
| | | const getReturnQtyMax = (row) => { |
| | | const max = Number(row?.availableQuality) |
| | | const max = Number(row?.unQuantity) |
| | | if (Number.isNaN(max) || max < 0) { |
| | | return 0 |
| | | } |
| | |
| | | return proxy.summarizeTable( |
| | | param, |
| | | [ |
| | | "quantity", |
| | | "availableQuality", |
| | | "stockInNum", |
| | | "unQuantity", |
| | | "returnQuantity", |
| | | "taxInclusiveUnitPrice", |
| | | "taxInclusiveTotalPrice", |
| | | "taxExclusiveTotalPrice", |
| | | ], |
| | | { |
| | | quantity: { noDecimal: true }, // 不保留小数 |
| | | stockInNum: { noDecimal: true }, // 不保留小数 |
| | | returnQuantity: { noDecimal: true }, // 不保留小数 |
| | | availableQuality: { noDecimal: true }, // 不保留小数 |
| | | unQuantity: { noDecimal: true }, // 不保留小数 |
| | | } |
| | | ); |
| | | }; |
| | |
| | | } |
| | | } |
| | | |
| | | // 处理改变采购台账数据 |
| | | const handleChangePurchaseLedgerId = async () => { |
| | | // 处理改变采购台账数据(不请求接口回显产品,产品仅在「添加产品」弹窗勾选后写入) |
| | | const handleChangePurchaseLedgerId = () => { |
| | | resetFeeInfo() |
| | | if (!formState.value.purchaseLedgerId) { |
| | | formState.value.purchaseReturnOrderProductsDtos = [] |
| | | return |
| | | } |
| | | const res = await productList({ salesLedgerId: formState.value.purchaseLedgerId, type: 2 }); |
| | | formState.value.purchaseReturnOrderProductsDtos = res.data.map(item => ({ |
| | | ...item, |
| | | returnQuantity: undefined, |
| | | taxInclusiveTotalPrice: 0, |
| | | salesLedgerProductId: item.id, |
| | | })) |
| | | formState.value.purchaseReturnOrderProductsDtos = [] |
| | | syncTotalAmount() |
| | | } |
| | | |
| | |
| | | ...item, |
| | | returnQuantity: undefined, |
| | | taxInclusiveTotalPrice: 0, |
| | | salesLedgerProductId: item.id, |
| | | // salesLedgerProductId: item.salesLedgerProductId, |
| | | })); |
| | | formState.value.purchaseReturnOrderProductsDtos.push(...newProducts); |
| | | syncTotalAmount() |
| | |
| | | // 逐行校验退货数量:任意一行未填/非法/超限都不允许提交 |
| | | const invalidRowIndex = productList.findIndex((item) => { |
| | | const qty = Number(item.returnQuantity) |
| | | const maxQty = Number(item.availableQuality) |
| | | const maxQty = Number(item.unQuantity) |
| | | |
| | | if (item.returnQuantity === null || item.returnQuantity === undefined || item.returnQuantity === "") { |
| | | return true |
| | |
| | | |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (valid) { |
| | | createPurchaseReturnOrder(formState.value).then(res => { |
| | | console.log(productList) |
| | | const submitPayload = { |
| | | ...formState.value, |
| | | purchaseReturnOrderProductsDtos: productList.map((row) => ({ |
| | | ...row, |
| | | stockInRecordId: row.id, |
| | | })), |
| | | } |
| | | createPurchaseReturnOrder(submitPayload).then(res => { |
| | | // 关闭模态框 |
| | | isShow.value = false; |
| | | // 告知父组件已完成 |
| | |
| | | border-radius: 50%; |
| | | margin-right: 8px; |
| | | } |
| | | |
| | | .product-table-scroll { |
| | | width: 100%; |
| | | overflow-x: auto; |
| | | } |
| | | |
| | | .product-table-inner { |
| | | min-width: 1280px; |
| | | } |
| | | </style> |
| | |
| | | width="1200" |
| | | @close="closeModal" |
| | | > |
| | | <div class="table_list"> |
| | | <div class="table_list" v-loading="tableLoading"> |
| | | <el-table :data="tableData" |
| | | border |
| | | row-key="id" |
| | | @selection-change="handleChangeSelection"> |
| | | <el-table-column align="center" |
| | | type="selection" |
| | |
| | | label="序号" |
| | | type="index" |
| | | width="60" /> |
| | | <el-table-column label="入库单号" |
| | | prop="inboundBatches" |
| | | width="150" /> |
| | | <el-table-column label="批次号" |
| | | prop="batchNo" |
| | | width="150" /> |
| | | <el-table-column label="产品大类" |
| | | prop="productCategory" /> |
| | | <el-table-column label="规格型号" |
| | |
| | | prop="unit" |
| | | width="70" /> |
| | | <el-table-column label="数量" |
| | | prop="quantity" |
| | | prop="stockInNum" |
| | | width="70" /> |
| | | <el-table-column label="库存预警数量" |
| | | <el-table-column label="可退货数量" |
| | | prop="unQuantity" |
| | | width="130" /> |
| | | <el-table-column label="已退货数量" |
| | | width="130"> |
| | | <template #default="scope"> |
| | | {{ calcAlreadyReturned(scope.row) }} |
| | | </template> |
| | | </el-table-column> |
| | | <!-- <el-table-column label="库存预警数量" |
| | | prop="warnNum" |
| | | width="120" |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="税率(%)" |
| | | prop="taxRate" |
| | | width="80" /> |
| | | width="80" /> --> |
| | | <el-table-column label="含税单价(元)" |
| | | prop="taxInclusiveUnitPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | <el-table-column label="含税总价(元)" |
| | | <!-- <el-table-column label="含税总价(元)" |
| | | prop="taxInclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | <el-table-column label="不含税总价(元)" |
| | | prop="taxExclusiveTotalPrice" |
| | | :formatter="formattedNumber" |
| | | width="150" /> |
| | | width="150" /> --> |
| | | <el-table-column label="是否质检" |
| | | prop="isChecked" |
| | | width="150"> |
| | |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <pagination v-show="total > 0" :total="total" layout="total, sizes, prev, pager, next, jumper" |
| | | :page="page.current" :limit="page.size" @pagination="paginationChange" /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {computed, reactive, ref, onMounted} from "vue"; |
| | | import {productList} from "@/api/procurementManagement/procurementLedger.js"; |
| | | import {computed, ref, onMounted} from "vue"; |
| | | import {getPurchaseReturnOrderByPurchaseLedgerId} from "@/api/procurementManagement/purchase_return_order.js"; |
| | | import {ElMessage} from "element-plus"; |
| | | |
| | | const props = defineProps({ |
| | |
| | | }, |
| | | |
| | | purchaseLedgerId: { |
| | | type: Number, |
| | | type: [Number, String], |
| | | required: true, |
| | | } |
| | | }); |
| | |
| | | const tableData = ref([]) |
| | | const selectedRows = ref([]) |
| | | const tableLoading = ref(false) |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | const total = ref(0) |
| | | const formattedNumber = (row, column, cellValue) => { |
| | | return parseFloat(cellValue).toFixed(2); |
| | | }; |
| | | |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList() |
| | | /** 已退货数量 = 入库行总数量 − 当前可退货数量(剩余) */ |
| | | const calcAlreadyReturned = (row) => { |
| | | const total = Number(row?.stockInNum ?? row?.totalQuantity ?? row?.quantity ?? 0) |
| | | const un = Number(row?.unQuantity ?? 0) |
| | | if (!Number.isFinite(total) || !Number.isFinite(un)) return 0 |
| | | return Math.max(total - un, 0) |
| | | } |
| | | |
| | | const handleChangeSelection = (val) => { |
| | | selectedRows.value = val; |
| | | } |
| | | |
| | | /** 与 New.vue 中采购台账变更时解析 getByPurchaseLedgerId 的规则一致 */ |
| | | const parseProductRowsFromLedgerResponse = (res) => { |
| | | const payload = res?.data |
| | | let list = [] |
| | | if (Array.isArray(payload)) { |
| | | list = payload |
| | | } else if (payload && typeof payload === 'object') { |
| | | const nested = |
| | | payload.purchaseReturnOrderProductsDtos || |
| | | payload.purchaseReturnOrderProductsDetailVoList |
| | | list = Array.isArray(nested) ? nested : [] |
| | | if (list.length && list[0]?.salesLedgerProduct) { |
| | | list = list.map((item) => ({ ...item, ...item.salesLedgerProduct })) |
| | | } |
| | | } |
| | | return list |
| | | } |
| | | |
| | | const fetchData = () => { |
| | | tableLoading.value = true; |
| | | productList({salesLedgerId: props.purchaseLedgerId, type: 2}).then((res) => { |
| | | tableData.value = res.data; |
| | | }).finally(() => { |
| | | tableLoading.value = false; |
| | | if (props.purchaseLedgerId === undefined || props.purchaseLedgerId === null || props.purchaseLedgerId === '') { |
| | | tableData.value = [] |
| | | return |
| | | } |
| | | tableLoading.value = true |
| | | getPurchaseReturnOrderByPurchaseLedgerId({ |
| | | purchaseLedgerId: props.purchaseLedgerId, |
| | | }) |
| | | .then((res) => { |
| | | const list = parseProductRowsFromLedgerResponse(res) |
| | | tableData.value = list |
| | | }) |
| | | .catch(() => { |
| | | tableData.value = [] |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false |
| | | }) |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form"> |
| | | <el-form :model="searchForm" |
| | | :inline="true"> |
| | | <el-form :model="searchForm" :inline="true"> |
| | | <el-form-item label="退料单号:"> |
| | | <el-input v-model="searchForm.no" |
| | | placeholder="请输入" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" /> |
| | | <el-input |
| | | v-model="searchForm.no" |
| | | placeholder="请输入" |
| | | clearable |
| | | prefix-icon="Search" |
| | | @change="handleQuery" |
| | | /> |
| | | </el-form-item> |
| | | |
| | | <el-form-item> |
| | | <el-button type="primary" |
| | | @click="handleQuery"> 搜索 </el-button> |
| | | <el-button type="primary" @click="handleQuery"> 搜索 </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | |
| | | <div> |
| | | <el-button type="primary" @click="isShowNewModal = true">新增</el-button> |
| | | <el-button type="primary" @click="isShowNewModal = true" |
| | | >新增</el-button |
| | | > |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | @pagination="paginationChange" |
| | | > |
| | | <template #operation="{ row }"> |
| | | <el-button link type="primary" size="small" style="color: #67C23A" @click="handleDetail(row)">详情</el-button> |
| | | <el-button link size="small" @click="handleDelete(row)">删除</el-button> |
| | | <el-button |
| | | link |
| | | type="primary" |
| | | size="small" |
| | | style="color: #67c23a" |
| | | @click="handleDetail(row)" |
| | | >详情</el-button |
| | | > |
| | | <el-button link size="small" @click="handleDelete(row)" |
| | | >删除</el-button |
| | | > |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | <new v-if="isShowNewModal" |
| | | v-model:visible="isShowNewModal" |
| | | @completed="handleQuery" /> |
| | | <new |
| | | v-if="isShowNewModal" |
| | | v-model:visible="isShowNewModal" |
| | | @completed="handleQuery" |
| | | /> |
| | | |
| | | <el-dialog |
| | | v-model="detailVisible" |
| | |
| | | > |
| | | <div v-loading="detailLoading"> |
| | | <el-descriptions :column="3" border> |
| | | <el-descriptions-item label="退料单号">{{ detailData.no || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="退货方式">{{ getReturnTypeLabel(detailData.returnType) }}</el-descriptions-item> |
| | | <el-descriptions-item label="供应商名称">{{ detailData.supplierName || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="项目阶段">{{ getProjectPhaseLabel(detailData.projectPhase) }}</el-descriptions-item> |
| | | <el-descriptions-item label="关联单号">{{ detailData.purchaseContractNumber || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="制作日期">{{ detailData.preparedAt || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="制单人">{{ detailData.preparedUserName || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="退料人">{{ detailData.returnUserName || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="整单折扣额">{{ formatAmount(detailData.totalDiscountAmount) }}</el-descriptions-item> |
| | | <el-descriptions-item label="整单折扣率">{{ detailData.totalDiscountRate ?? '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="成交金额">{{ formatAmount(detailData.totalAmount) }}</el-descriptions-item> |
| | | <el-descriptions-item label="创建人">{{ detailData.createUserName || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="创建时间">{{ detailData.createTime || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="最近更新时间">{{ detailData.updateTime || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="备注" :span="3">{{ detailData.remark || '--' }}</el-descriptions-item> |
| | | <el-descriptions-item label="退料单号">{{ |
| | | detailData.no || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="退货方式">{{ |
| | | getReturnTypeLabel(detailData.returnType) |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="供应商名称">{{ |
| | | detailData.supplierName || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="项目阶段">{{ |
| | | getProjectPhaseLabel(detailData.projectPhase) |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="关联的采购订单号">{{ |
| | | detailData.purchaseContractNumber || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="制作日期">{{ |
| | | detailData.preparedAt || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="制单人">{{ |
| | | detailData.preparedUserName || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="退料人">{{ |
| | | detailData.returnUserName || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="整单折扣额">{{ |
| | | formatAmount(detailData.totalDiscountAmount) |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="整单折扣率">{{ |
| | | detailData.totalDiscountRate ?? "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="成交金额">{{ |
| | | formatAmount(detailData.totalAmount) |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="创建人">{{ |
| | | detailData.createUserName || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="创建时间">{{ |
| | | detailData.createTime || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="最近更新时间">{{ |
| | | detailData.updateTime || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="备注" :span="3">{{ |
| | | detailData.remark || "--" |
| | | }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | |
| | | <el-divider content-position="left">产品列表</el-divider> |
| | |
| | | max-height="420" |
| | | style="width: 100%" |
| | | > |
| | | <el-table-column align="center" label="序号" type="index" width="60" /> |
| | | <el-table-column label="产品大类" prop="productCategory" min-width="120" show-overflow-tooltip /> |
| | | <el-table-column label="规格型号" prop="specificationModel" min-width="140" show-overflow-tooltip /> |
| | | <el-table-column |
| | | align="center" |
| | | label="序号" |
| | | type="index" |
| | | width="60" |
| | | /> |
| | | <el-table-column label="入库单号" prop="inboundBatches" width="150" /> |
| | | <el-table-column label="批次号" prop="batchNo" width="150" /> |
| | | <el-table-column |
| | | label="产品大类" |
| | | prop="productCategory" |
| | | min-width="120" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="规格型号" |
| | | prop="specificationModel" |
| | | min-width="140" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column label="单位" prop="unit" width="80" /> |
| | | <el-table-column label="数量" prop="quantity" width="80" /> |
| | | <el-table-column label="退货数量" prop="returnQuantity" width="100" /> |
| | | <el-table-column label="库存预警数量" prop="warnNum" width="120" /> |
| | | <el-table-column label="税率(%)" prop="taxRate" width="90" /> |
| | | <el-table-column label="含税单价(元)" prop="taxInclusiveUnitPrice" width="130"> |
| | | <template #default="scope">{{ formatAmount(scope.row.taxInclusiveUnitPrice) }}</template> |
| | | <el-table-column label="数量" prop="stockInNum" width="80" /> |
| | | <el-table-column label="可退货数量" |
| | | prop="unQuantity" |
| | | width="100" /> |
| | | <el-table-column label="已退货数量" |
| | | width="100"> |
| | | <template #default="scope"> |
| | | {{ calcAlreadyReturned(scope.row) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="退货总价(元)" prop="taxInclusiveTotalPrice" width="130"> |
| | | <template #default="scope">{{ formatAmount(scope.row.taxInclusiveTotalPrice) }}</template> |
| | | <!-- <el-table-column label="库存预警数量" prop="warnNum" width="120" /> --> |
| | | <!-- <el-table-column label="税率(%)" prop="taxRate" width="90" /> --> |
| | | <el-table-column |
| | | label="含税单价(元)" |
| | | prop="taxInclusiveUnitPrice" |
| | | width="130" |
| | | > |
| | | <template #default="scope">{{ |
| | | formatAmount(scope.row.taxInclusiveUnitPrice) |
| | | }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="不退货总价(元)" prop="taxExclusiveTotalPrice" width="140"> |
| | | <template #default="scope">{{ formatAmount(scope.row.taxExclusiveTotalPrice) }}</template> |
| | | <!-- <el-table-column |
| | | label="退货总价(元)" |
| | | prop="taxInclusiveTotalPrice" |
| | | width="130" |
| | | > |
| | | <template #default="scope">{{ |
| | | formatAmount(scope.row.taxInclusiveTotalPrice) |
| | | }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="是否质检" prop="isChecked" width="100" align="center"> |
| | | <el-table-column |
| | | label="不退货总价(元)" |
| | | prop="taxExclusiveTotalPrice" |
| | | width="140" |
| | | > |
| | | <template #default="scope">{{ |
| | | formatAmount(scope.row.taxExclusiveTotalPrice) |
| | | }}</template> |
| | | </el-table-column> --> |
| | | <el-table-column |
| | | label="是否质检" |
| | | prop="isChecked" |
| | | width="100" |
| | | align="center" |
| | | > |
| | | <template #default="scope"> |
| | | <el-tag :type="scope.row.isChecked ? 'success' : 'info'"> |
| | | {{ scope.row.isChecked ? '是' : '否' }} |
| | | {{ scope.row.isChecked ? "是" : "否" }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import { ref, reactive, toRefs, onMounted, defineAsyncComponent, getCurrentInstance } from 'vue' |
| | | const { proxy } = getCurrentInstance() |
| | | import {findPurchaseReturnOrderListPage, getPurchaseReturnOrderDetail, deletePurchaseReturnOrder} from "@/api/procurementManagement/purchase_return_order.js"; |
| | | const New = defineAsyncComponent(() => import("@/views/procurementManagement/purchaseReturnOrder/New.vue")); |
| | | const tableData = ref([]) |
| | | const selectedRows = ref([]) |
| | | const tableLoading = ref(false) |
| | | import PIMTable from "@/components/PIMTable/PIMTable.vue"; |
| | | import { |
| | | ref, |
| | | reactive, |
| | | toRefs, |
| | | onMounted, |
| | | defineAsyncComponent, |
| | | getCurrentInstance, |
| | | } from "vue"; |
| | | const { proxy } = getCurrentInstance(); |
| | | import { |
| | | findPurchaseReturnOrderListPage, |
| | | getPurchaseReturnOrderDetail, |
| | | deletePurchaseReturnOrder, |
| | | } from "@/api/procurementManagement/purchase_return_order.js"; |
| | | const New = defineAsyncComponent(() => |
| | | import("@/views/procurementManagement/purchaseReturnOrder/New.vue") |
| | | ); |
| | | const tableData = ref([]); |
| | | const selectedRows = ref([]); |
| | | const tableLoading = ref(false); |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 100, |
| | | total: 0, |
| | | }) |
| | | const detailVisible = ref(false) |
| | | const detailLoading = ref(false) |
| | | const detailData = ref({}) |
| | | const detailProducts = ref([]) |
| | | }); |
| | | const detailVisible = ref(false); |
| | | const detailLoading = ref(false); |
| | | const detailData = ref({}); |
| | | const detailProducts = ref([]); |
| | | // 是否显示新增弹框 |
| | | const isShowNewModal = ref(false) |
| | | const isShowNewModal = ref(false); |
| | | const returnTypeOptions = [ |
| | | { label: '退货退款', value: 0 }, |
| | | { label: '拒收', value: 1 }, |
| | | ] |
| | | { label: "退货退款", value: 0 }, |
| | | { label: "拒收", value: 1 }, |
| | | ]; |
| | | const projectPhaseOptions = [ |
| | | { label: '立项', value: 0 }, |
| | | { label: '设计', value: 1 }, |
| | | { label: '采购', value: 2 }, |
| | | { label: '生产', value: 3 }, |
| | | { label: '出货', value: 4 }, |
| | | ] |
| | | { label: "立项", value: 0 }, |
| | | { label: "设计", value: 1 }, |
| | | { label: "采购", value: 2 }, |
| | | { label: "生产", value: 3 }, |
| | | { label: "出货", value: 4 }, |
| | | ]; |
| | | const tableColumn = ref([ |
| | | { |
| | | label: '退料单号', |
| | | prop: 'no', |
| | | label: "退料单号", |
| | | prop: "no", |
| | | }, |
| | | { |
| | | label: '退货方式', |
| | | prop: 'returnType', |
| | | formatData: (val) => returnTypeOptions.find(item => item.value === val)?.label || '--', |
| | | label: "退货方式", |
| | | prop: "returnType", |
| | | formatData: (val) => |
| | | returnTypeOptions.find((item) => item.value === val)?.label || "--", |
| | | }, |
| | | { |
| | | label: '供应商名称', |
| | | prop: 'supplierName', |
| | | label: "供应商名称", |
| | | prop: "supplierName", |
| | | width: 180, |
| | | }, |
| | | { |
| | | label: '项目阶段', |
| | | prop: 'projectPhase', |
| | | label: "项目阶段", |
| | | prop: "projectPhase", |
| | | width: 100, |
| | | formatData: (val) => projectPhaseOptions.find(item => String(item.value) === String(val))?.label || '--', |
| | | formatData: (val) => |
| | | projectPhaseOptions.find((item) => String(item.value) === String(val)) |
| | | ?.label || "--", |
| | | }, |
| | | { |
| | | label: '关联单号', |
| | | prop: 'purchaseContractNumber', |
| | | label: "关联的采购订单号", |
| | | prop: "purchaseContractNumber", |
| | | width: 160, |
| | | }, |
| | | { |
| | | label: '制作日期', |
| | | prop: 'preparedAt', |
| | | label: "制作日期", |
| | | prop: "preparedAt", |
| | | width: 130, |
| | | }, |
| | | { |
| | | label: '制单人', |
| | | prop: 'preparedUserName', |
| | | label: "制单人", |
| | | prop: "preparedUserName", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: '退料人', |
| | | prop: 'returnUserName', |
| | | label: "退料人", |
| | | prop: "returnUserName", |
| | | width: 110, |
| | | }, |
| | | |
| | | { |
| | | label: '整单折扣额', |
| | | prop: 'totalDiscountAmount', |
| | | label: "整单折扣额", |
| | | prop: "totalDiscountAmount", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: '整单折扣率', |
| | | prop: 'totalDiscountRate', |
| | | label: "整单折扣率", |
| | | prop: "totalDiscountRate", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: '成交金额', |
| | | prop: 'totalAmount', |
| | | label: "成交金额", |
| | | prop: "totalAmount", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: '创建人', |
| | | prop: 'createUserName', |
| | | label: "创建人", |
| | | prop: "createUserName", |
| | | width: 110, |
| | | }, |
| | | { |
| | | label: '创建时间', |
| | | prop: 'createTime', |
| | | label: "创建时间", |
| | | prop: "createTime", |
| | | width: 170, |
| | | }, |
| | | { |
| | | label: '最近更新时间', |
| | | prop: 'updateTime', |
| | | label: "最近更新时间", |
| | | prop: "updateTime", |
| | | width: 170, |
| | | }, |
| | | { |
| | | label: '备注', |
| | | prop: 'remark', |
| | | label: "备注", |
| | | prop: "remark", |
| | | width: 180, |
| | | }, |
| | | { |
| | | dataType: "action", |
| | | width: 120, |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | label: "操作", |
| | | align: "center", |
| | | fixed: "right", |
| | | operation: [ |
| | | { |
| | | name: "详情", |
| | | type: "text", |
| | | clickFun: row => {handleDetail(row);}, |
| | | }, |
| | | name: "详情", |
| | | type: "text", |
| | | clickFun: (row) => { |
| | | handleDetail(row); |
| | | }, |
| | | }, |
| | | { |
| | | name: "删除", |
| | | clickFun: row => {handleDelete(row)}, |
| | | clickFun: (row) => { |
| | | handleDelete(row); |
| | | }, |
| | | }, |
| | | ], |
| | | ], |
| | | }, |
| | | |
| | | ]) |
| | | ]); |
| | | const data = reactive({ |
| | | searchForm: { |
| | | no: '', |
| | | } |
| | | }) |
| | | const { searchForm } = toRefs(data) |
| | | no: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | // 查询列表 |
| | | /** 搜索按钮操作 */ |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | getList() |
| | | } |
| | | page.current = 1; |
| | | getList(); |
| | | }; |
| | | |
| | | // 删除操作 |
| | | const handleDelete = (row) => { |
| | | console.log('删除行数据:', row) |
| | | proxy?.$modal?.confirm('确定要删除吗?删除将无法恢复').then(() => { |
| | | // 这里调用删除接口,传入 row.id |
| | | deletePurchaseReturnOrder(row.id).then(() => { |
| | | proxy?.$modal?.msgSuccess?.("删除成功"); |
| | | getList() |
| | | }).catch(() => { |
| | | proxy?.$modal?.msgError?.('删除失败') |
| | | console.log("删除行数据:", row); |
| | | proxy?.$modal |
| | | ?.confirm("确定要删除吗?删除将无法恢复") |
| | | .then(() => { |
| | | // 这里调用删除接口,传入 row.id |
| | | deletePurchaseReturnOrder(row.id) |
| | | .then(() => { |
| | | proxy?.$modal?.msgSuccess?.("删除成功"); |
| | | getList(); |
| | | }) |
| | | .catch(() => { |
| | | proxy?.$modal?.msgError?.("删除失败"); |
| | | }); |
| | | }) |
| | | }).catch(() => { |
| | | // 取消删除 |
| | | proxy?.$modal?.msgInfo?.('已取消删除') |
| | | |
| | | }) |
| | | } |
| | | .catch(() => { |
| | | // 取消删除 |
| | | proxy?.$modal?.msgInfo?.("已取消删除"); |
| | | }); |
| | | }; |
| | | // 查看详情 |
| | | const handleDetail = (row) => { |
| | | if (!row?.id) { |
| | | proxy?.$modal?.msgWarning?.('未获取到单据ID') |
| | | return |
| | | proxy?.$modal?.msgWarning?.("未获取到单据ID"); |
| | | return; |
| | | } |
| | | detailVisible.value = true |
| | | detailLoading.value = true |
| | | getPurchaseReturnOrderDetail(row.id).then(res => { |
| | | const payload = res?.data || {} |
| | | detailData.value = payload |
| | | // 拼接连个对象成一个对象,方便展示 item 和 item.salesLedgerProduct 里的字段 |
| | | detailVisible.value = true; |
| | | detailLoading.value = true; |
| | | getPurchaseReturnOrderDetail(row.id) |
| | | .then((res) => { |
| | | const payload = res?.data || {}; |
| | | detailData.value = payload; |
| | | // 拼接连个对象成一个对象,方便展示 item 和 item.salesLedgerProduct 里的字段 |
| | | |
| | | |
| | | detailProducts.value = |
| | | payload.purchaseReturnOrderProductsDetailVoList.map(item => ({ ...item, ...item.salesLedgerProduct })) || |
| | | [] |
| | | }).catch(() => { |
| | | proxy?.$modal?.msgError?.('获取详情失败') |
| | | }).finally(() => { |
| | | detailLoading.value = false |
| | | }) |
| | | } |
| | | |
| | | detailProducts.value = |
| | | payload.purchaseReturnOrderProductsDetailVoList.map((item) => ({ |
| | | ...item, |
| | | ...item.salesLedgerProduct, |
| | | })) || []; |
| | | }) |
| | | .catch(() => { |
| | | proxy?.$modal?.msgError?.("获取详情失败"); |
| | | }) |
| | | .finally(() => { |
| | | detailLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const paginationChange = (obj) => { |
| | | page.current = obj.page; |
| | | page.size = obj.limit; |
| | | getList() |
| | | } |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true |
| | | findPurchaseReturnOrderListPage({ ...searchForm.value, ...page }).then(res => { |
| | | tableLoading.value = false |
| | | tableData.value = res.data.records |
| | | page.total = res.data.total |
| | | }).catch(() => { |
| | | tableLoading.value = false |
| | | }) |
| | | } |
| | | tableLoading.value = true; |
| | | findPurchaseReturnOrderListPage({ ...searchForm.value, ...page }) |
| | | .then((res) => { |
| | | tableLoading.value = false; |
| | | tableData.value = res.data.records; |
| | | page.total = res.data.total; |
| | | }) |
| | | .catch(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | // 表格选择数据 |
| | | const handleSelectionChange = (selection) => { |
| | | // 过滤掉子数据 |
| | | selectedRows.value = selection.filter(item => item.id); |
| | | } |
| | | selectedRows.value = selection.filter((item) => item.id); |
| | | }; |
| | | |
| | | const getReturnTypeLabel = (value) => { |
| | | return returnTypeOptions.find(item => String(item.value) === String(value))?.label || '--' |
| | | } |
| | | return ( |
| | | returnTypeOptions.find((item) => String(item.value) === String(value)) |
| | | ?.label || "--" |
| | | ); |
| | | }; |
| | | |
| | | const getProjectPhaseLabel = (value) => { |
| | | return projectPhaseOptions.find(item => String(item.value) === String(value))?.label || '--' |
| | | } |
| | | return ( |
| | | projectPhaseOptions.find((item) => String(item.value) === String(value)) |
| | | ?.label || "--" |
| | | ); |
| | | }; |
| | | |
| | | const formatAmount = (value) => { |
| | | if (value === null || value === undefined || value === '') { |
| | | return '--' |
| | | if (value === null || value === undefined || value === "") { |
| | | return "--"; |
| | | } |
| | | const num = Number(value) |
| | | const num = Number(value); |
| | | if (Number.isNaN(num)) { |
| | | return value |
| | | return value; |
| | | } |
| | | return num.toFixed(2) |
| | | } |
| | | return num.toFixed(2); |
| | | }; |
| | | |
| | | /** 已退货数量 = 入库行总数量 − 当前可退货数量(剩余) */ |
| | | const calcAlreadyReturned = (row) => { |
| | | const total = Number(row?.stockInNum ?? row?.totalQuantity ?? row?.quantity ?? 0); |
| | | const un = Number(row?.unQuantity ?? 0); |
| | | if (!Number.isFinite(total) || !Number.isFinite(un)) return 0; |
| | | return Math.max(total - un, 0); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | getList(); |
| | | }); |
| | | </script> |
| | | <style scoped> |
| | | .table_list { |
| | | margin-top: unset; |
| | | margin-top: unset; |
| | | } |
| | | </style> |
| | | |
| | |
| | | <template> |
| | | <div> |
| | | <el-dialog |
| | | v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? '新增过程检验' : '编辑过程检验'" |
| | | width="70%" |
| | | @close="closeDia" |
| | | > |
| | | <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef"> |
| | | <el-dialog v-model="dialogFormVisible" |
| | | :title="operationType === 'add' ? '新增过程检验' : '编辑过程检验'" |
| | | width="70%" |
| | | @close="closeDia"> |
| | | <el-form :model="form" |
| | | label-width="140px" |
| | | label-position="top" |
| | | :rules="rules" |
| | | ref="formRef"> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="工序:" prop="process"> |
| | | <el-select v-model="form.process" placeholder="请选择工序" clearable :disabled="processQuantityDisabled" style="width: 100%"> |
| | | <el-option v-for="item in processList" :key="item.name" :label="item.name" :value="item.name"/> |
| | | <el-form-item label="工序:" |
| | | prop="process"> |
| | | <el-select v-model="form.process" |
| | | placeholder="请选择工序" |
| | | clearable |
| | | :disabled="processQuantityDisabled" |
| | | style="width: 100%"> |
| | | <el-option v-for="item in processList" |
| | | :key="item.name" |
| | | :label="item.name" |
| | | :value="item.name" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="产品名称:" prop="productId"> |
| | | <el-tree-select |
| | | v-model="form.productId" |
| | | placeholder="请选择" |
| | | clearable |
| | | check-strictly |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | :disabled="operationType === 'edit'" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="产品名称:" |
| | | prop="productId"> |
| | | <el-tree-select v-model="form.productId" |
| | | placeholder="请选择" |
| | | clearable |
| | | check-strictly |
| | | @change="getModels" |
| | | :data="productOptions" |
| | | :render-after-expand="false" |
| | | :disabled="operationType === 'edit'" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="规格型号:" prop="productModelId"> |
| | | <el-select v-model="form.productModelId" placeholder="请选择" clearable :disabled="operationType === 'edit'" |
| | | filterable readonly @change="handleChangeModel"> |
| | | <el-option v-for="item in modelOptions" :key="item.id" :label="item.model" :value="item.id" /> |
| | | <el-form-item label="规格型号:" |
| | | prop="productModelId"> |
| | | <el-select v-model="form.productModelId" |
| | | placeholder="请选择" |
| | | clearable |
| | | :disabled="operationType === 'edit'" |
| | | filterable |
| | | readonly |
| | | @change="handleChangeModel"> |
| | | <el-option v-for="item in modelOptions" |
| | | :key="item.id" |
| | | :label="item.model" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="指标选择:" prop="testStandardId"> |
| | | <el-select |
| | | v-model="form.testStandardId" |
| | | placeholder="请选择指标" |
| | | clearable |
| | | @change="handleTestStandardChange" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="item in testStandardOptions" |
| | | :key="item.id" |
| | | :label="item.standardName || item.standardNo" |
| | | :value="item.id" |
| | | /> |
| | | <el-form-item label="指标选择:" |
| | | prop="testStandardId"> |
| | | <el-select v-model="form.testStandardId" |
| | | placeholder="请选择指标" |
| | | clearable |
| | | @change="handleTestStandardChange" |
| | | style="width: 100%"> |
| | | <el-option v-for="item in testStandardOptions" |
| | | :key="item.id" |
| | | :label="item.standardName || item.standardNo" |
| | | :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="单位:" prop="unit"> |
| | | <el-input v-model="form.unit" placeholder="请输入" disabled/> |
| | | <el-form-item label="单位:" |
| | | prop="unit"> |
| | | <el-input v-model="form.unit" |
| | | placeholder="请输入" |
| | | disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="数量:" prop="quantity"> |
| | | <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入" clearable :precision="2" :disabled="processQuantityDisabled"/> |
| | | <el-form-item label="数量:" |
| | | prop="quantity"> |
| | | <el-input-number :step="0.01" |
| | | :min="0" |
| | | style="width: 100%" |
| | | v-model="form.quantity" |
| | | placeholder="请输入" |
| | | clearable |
| | | :precision="2" |
| | | :disabled="processQuantityDisabled" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="检测单位:" prop="checkCompany"> |
| | | <el-input v-model="form.checkCompany" placeholder="请输入" clearable/> |
| | | <el-form-item label="检测单位:" |
| | | prop="checkCompany"> |
| | | <el-input v-model="form.checkCompany" |
| | | placeholder="请输入" |
| | | clearable /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="检测结果:" prop="checkResult"> |
| | | <el-form-item label="检测结果:" |
| | | prop="checkResult"> |
| | | <el-select v-model="form.checkResult"> |
| | | <el-option label="合格" value="合格" /> |
| | | <el-option label="不合格" value="不合格" /> |
| | | <el-option label="合格" |
| | | value="合格" /> |
| | | <el-option label="不合格" |
| | | value="不合格" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row :gutter="30"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="检验员:" prop="checkName"> |
| | | <el-select v-model="form.checkName" placeholder="请选择" clearable> |
| | | <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName" |
| | | :value="item.nickName"/> |
| | | </el-select> |
| | | <el-form-item label="检验员:" |
| | | prop="checkName"> |
| | | <el-select v-model="form.checkName" |
| | | placeholder="请选择" |
| | | clearable> |
| | | <el-option v-for="item in userList" |
| | | :key="item.nickName" |
| | | :label="item.nickName" |
| | | :value="item.nickName" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="检测日期:" prop="checkTime"> |
| | | <el-date-picker |
| | | v-model="form.checkTime" |
| | | type="date" |
| | | placeholder="请选择日期" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="检测日期:" |
| | | prop="checkTime"> |
| | | <el-date-picker v-model="form.checkTime" |
| | | type="date" |
| | | placeholder="请选择日期" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | clearable |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="400" |
| | | > |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" clearable/> |
| | | </template> |
| | | </PIMTable> |
| | | <PIMTable rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | height="400"> |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" |
| | | clearable /> |
| | | </template> |
| | | </PIMTable> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="submitForm">确认</el-button> |
| | | <el-button type="primary" |
| | | @click="submitForm">确认</el-button> |
| | | <el-button @click="closeDia">取消</el-button> |
| | | </div> |
| | | </template> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref, reactive, toRefs, computed, getCurrentInstance, nextTick} from "vue"; |
| | | import {getOptions} from "@/api/procurementManagement/procurementLedger.js"; |
| | | import {modelList, productTreeList} from "@/api/basicData/product.js"; |
| | | import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import {qualityInspectDetailByProductId, getQualityTestStandardParamByTestStandardId} from "@/api/qualityManagement/metricMaintenance.js"; |
| | | import {userListNoPage} from "@/api/system/user.js"; |
| | | import {qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js"; |
| | | import { list } from "@/api/productionManagement/productionProcess"; |
| | | const { proxy } = getCurrentInstance() |
| | | const emit = defineEmits(['close']) |
| | | import { |
| | | ref, |
| | | reactive, |
| | | toRefs, |
| | | computed, |
| | | getCurrentInstance, |
| | | nextTick, |
| | | } from "vue"; |
| | | import { getOptions } from "@/api/procurementManagement/procurementLedger.js"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import { |
| | | qualityInspectAdd, |
| | | qualityInspectUpdate, |
| | | } from "@/api/qualityManagement/rawMaterialInspection.js"; |
| | | import { |
| | | qualityInspectDetailByProductId, |
| | | getQualityTestStandardParamByTestStandardId, |
| | | } from "@/api/qualityManagement/metricMaintenance.js"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | import { qualityInspectParamInfo } from "@/api/qualityManagement/qualityInspectParam.js"; |
| | | import { list } from "@/api/productionManagement/productionProcess"; |
| | | const { proxy } = getCurrentInstance(); |
| | | const emit = defineEmits(["close"]); |
| | | |
| | | |
| | | |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref('') |
| | | const data = reactive({ |
| | | form: { |
| | | checkTime: "", |
| | | process: "", |
| | | checkName: "", |
| | | productName: "", |
| | | productId: "", |
| | | productModelId: "", |
| | | model: "", |
| | | testStandardId: "", |
| | | unit: "", |
| | | quantity: "", |
| | | checkCompany: "", |
| | | checkResult: "", |
| | | }, |
| | | rules: { |
| | | checkTime: [{ required: true, message: "请输入", trigger: "blur" },], |
| | | process: [{ required: true, message: "请选择工序", trigger: "change" }], |
| | | checkName: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | productId: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | productModelId: [{ required: true, message: "请选择", trigger: "change" }], |
| | | testStandardId: [{required: false, message: "请选择指标", trigger: "change"}], |
| | | unit: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | checkCompany: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | checkResult: [{ required: true, message: "请输入", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const userList = ref([]); |
| | | const { form, rules } = toRefs(data); |
| | | // 编辑时:productMainId 或 purchaseLedgerId 任一有值则工序、数量置灰 |
| | | const processQuantityDisabled = computed(() => { |
| | | const v = form.value || {}; |
| | | return !!(v.productMainId != null || v.purchaseLedgerId != null); |
| | | }); |
| | | const processList = ref([]); // 工序下拉列表(工序名称 name) |
| | | const supplierList = ref([]); |
| | | const productOptions = ref([]); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "指标", |
| | | prop: "parameterItem", |
| | | }, |
| | | { |
| | | label: "单位", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "标准值", |
| | | prop: "standardValue", |
| | | }, |
| | | { |
| | | label: "内控值", |
| | | prop: "controlValue", |
| | | }, |
| | | { |
| | | label: "检验值", |
| | | prop: "testValue", |
| | | dataType: 'slot', |
| | | slot: 'slot', |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const currentProductId = ref(0); |
| | | const testStandardOptions = ref([]); // 指标选择下拉框数据 |
| | | const modelOptions = ref([]); |
| | | |
| | | // 打开弹框 |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | | getOptions().then((res) => { |
| | | supplierList.value = res.data; |
| | | }); |
| | | // 加载工序下拉列表 |
| | | try { |
| | | const res = await list(); |
| | | processList.value = res.data || []; |
| | | } catch (e) { |
| | | console.error("加载工序列表失败", e); |
| | | processList.value = []; |
| | | } |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | // 先重置表单数据(保持字段完整,避免弹窗首次渲染时触发必填红框“闪一下”) |
| | | form.value = { |
| | | checkTime: "", |
| | | process: "", |
| | | checkName: "", |
| | | productName: "", |
| | | productId: "", |
| | | productModelId: "", |
| | | model: "", |
| | | testStandardId: "", |
| | | unit: "", |
| | | quantity: "", |
| | | checkCompany: "", |
| | | checkResult: "", |
| | | } |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | | // 先确保产品树已加载,否则编辑时产品/规格型号无法反显 |
| | | await getProductOptions(); |
| | | if (operationType.value === 'edit') { |
| | | // 先保存 testStandardId,避免被清空 |
| | | const savedTestStandardId = row.testStandardId; |
| | | // 先设置表单数据,但暂时清空 testStandardId,等选项加载完成后再设置 |
| | | form.value = {...row, testStandardId: ''} |
| | | currentProductId.value = row.productId || 0 |
| | | // 关键:编辑时加载规格型号下拉选项,才能反显 productModelId |
| | | if (currentProductId.value) { |
| | | try { |
| | | const res = await modelList({ id: currentProductId.value }); |
| | | modelOptions.value = res || []; |
| | | // 同步回填 model / unit(有些接口返回的 row 里可能没带全) |
| | | if (form.value.productModelId) { |
| | | handleChangeModel(form.value.productModelId); |
| | | } |
| | | } catch (e) { |
| | | console.error("加载规格型号失败", e); |
| | | modelOptions.value = []; |
| | | } |
| | | } |
| | | // 编辑模式下,先加载指标选项,然后加载参数列表 |
| | | if (currentProductId.value) { |
| | | // 先加载指标选项 |
| | | let params = { |
| | | productId: currentProductId.value, |
| | | inspectType: 1, |
| | | process: form.value.process || '' |
| | | } |
| | | qualityInspectDetailByProductId(params).then(res => { |
| | | testStandardOptions.value = res.data || []; |
| | | // 使用 nextTick 和 setTimeout 确保选项已经渲染到 DOM |
| | | nextTick(() => { |
| | | setTimeout(() => { |
| | | // 如果编辑数据中有 testStandardId,则设置并加载对应的参数 |
| | | if (savedTestStandardId) { |
| | | // 确保类型匹配(item.id 可能是数字或字符串) |
| | | const matchedOption = testStandardOptions.value.find(item => |
| | | item.id == savedTestStandardId || String(item.id) === String(savedTestStandardId) |
| | | ); |
| | | if (matchedOption) { |
| | | // 确保使用匹配项的 id(保持类型一致) |
| | | form.value.testStandardId = matchedOption.id; |
| | | // 编辑保留原检验值,直接拉取原参数数据 |
| | | getQualityInspectParamList(row.id); |
| | | } else { |
| | | // 如果找不到匹配项,尝试直接使用原值 |
| | | console.warn('未找到匹配的指标选项,testStandardId:', savedTestStandardId, '可用选项:', testStandardOptions.value); |
| | | form.value.testStandardId = savedTestStandardId; |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | } else { |
| | | // 否则使用旧的逻辑 |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | }, 100); |
| | | }); |
| | | }); |
| | | } else { |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | } |
| | | // 最后再打开弹窗,并清理校验态,避免必填提示闪烁 |
| | | dialogFormVisible.value = true; |
| | | nextTick(() => { |
| | | proxy.$refs?.formRef?.clearValidate?.(); |
| | | }); |
| | | } |
| | | const getProductOptions = () => { |
| | | return productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res); |
| | | return productOptions.value; |
| | | const dialogFormVisible = ref(false); |
| | | const operationType = ref(""); |
| | | const data = reactive({ |
| | | form: { |
| | | checkTime: "", |
| | | process: "", |
| | | checkName: "", |
| | | productName: "", |
| | | productId: "", |
| | | productModelId: "", |
| | | model: "", |
| | | testStandardId: "", |
| | | unit: "", |
| | | quantity: "", |
| | | checkCompany: "", |
| | | checkResult: "", |
| | | }, |
| | | rules: { |
| | | checkTime: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | process: [{ required: true, message: "请选择工序", trigger: "change" }], |
| | | checkName: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | productId: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | productModelId: [{ required: true, message: "请选择", trigger: "change" }], |
| | | testStandardId: [ |
| | | { required: false, message: "请选择指标", trigger: "change" }, |
| | | ], |
| | | unit: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请输入", trigger: "blur" }], |
| | | checkCompany: [{ required: false, message: "请输入", trigger: "blur" }], |
| | | checkResult: [{ required: true, message: "请输入", trigger: "change" }], |
| | | }, |
| | | }); |
| | | }; |
| | | const getModels = (value) => { |
| | | form.value.productModelId = undefined; |
| | | form.value.unit = undefined; |
| | | modelOptions.value = []; |
| | | currentProductId.value = value |
| | | form.value.productName = findNodeById(productOptions.value, value); |
| | | modelList({ id: value }).then((res) => { |
| | | modelOptions.value = res; |
| | | }) |
| | | if (currentProductId.value) { |
| | | getList(); |
| | | } |
| | | }; |
| | | const userList = ref([]); |
| | | const { form, rules } = toRefs(data); |
| | | // 编辑时:productMainId 或 purchaseLedgerId 任一有值则工序、数量置灰 |
| | | const processQuantityDisabled = computed(() => { |
| | | const v = form.value || {}; |
| | | return !!(v.productMainId != null || v.purchaseLedgerId != null); |
| | | }); |
| | | const processList = ref([]); // 工序下拉列表(工序名称 name) |
| | | const supplierList = ref([]); |
| | | const productOptions = ref([]); |
| | | const tableColumn = ref([ |
| | | { |
| | | label: "指标", |
| | | prop: "parameterItem", |
| | | }, |
| | | { |
| | | label: "单位", |
| | | prop: "unit", |
| | | }, |
| | | { |
| | | label: "标准值", |
| | | prop: "standardValue", |
| | | }, |
| | | { |
| | | label: "内控值", |
| | | prop: "controlValue", |
| | | }, |
| | | { |
| | | label: "检验值", |
| | | prop: "testValue", |
| | | dataType: "slot", |
| | | slot: "slot", |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | const currentProductId = ref(0); |
| | | const testStandardOptions = ref([]); // 指标选择下拉框数据 |
| | | const modelOptions = ref([]); |
| | | |
| | | const handleChangeModel = (value) => { |
| | | form.value.model = modelOptions.value.find(item => item.id == value)?.model || ''; |
| | | form.value.unit = modelOptions.value.find(item => item.id == value)?.unit || ''; |
| | | } |
| | | |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | | return nodes[i].label; // 找到节点,返回该节点 |
| | | // 打开弹框 |
| | | const openDialog = async (type, row) => { |
| | | operationType.value = type; |
| | | getOptions().then(res => { |
| | | supplierList.value = res.data; |
| | | }); |
| | | // 加载工序下拉列表 |
| | | try { |
| | | const res = await list({ size: -1, current: -1 }); |
| | | processList.value = res.data.records || []; |
| | | } catch (e) { |
| | | console.error("加载工序列表失败", e); |
| | | processList.value = []; |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const foundNode = findNodeById(nodes[i].children, productId); |
| | | if (foundNode) { |
| | | return foundNode; // 在子节点中找到,返回该节点 |
| | | } |
| | | } |
| | | } |
| | | return null; // 没有找到节点,返回null |
| | | }; |
| | | function convertIdToValue(data) { |
| | | return data.map((item) => { |
| | | const { id, children, ...rest } = item; |
| | | const newItem = { |
| | | ...rest, |
| | | value: id, // 将 id 改为 value |
| | | let userLists = await userListNoPage(); |
| | | userList.value = userLists.data; |
| | | // 先重置表单数据(保持字段完整,避免弹窗首次渲染时触发必填红框“闪一下”) |
| | | form.value = { |
| | | checkTime: "", |
| | | process: "", |
| | | checkName: "", |
| | | productName: "", |
| | | productId: "", |
| | | productModelId: "", |
| | | model: "", |
| | | testStandardId: "", |
| | | unit: "", |
| | | quantity: "", |
| | | checkCompany: "", |
| | | checkResult: "", |
| | | }; |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | } |
| | | |
| | | return newItem; |
| | | }); |
| | | } |
| | | // 工序变化处理 |
| | | // 提交产品表单 |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | form.value.inspectType = 1 |
| | | const processName = form.value.process || ''; |
| | | if (operationType.value === "add") { |
| | | tableData.value.forEach((item) => { |
| | | delete item.id |
| | | }) |
| | | } |
| | | const data = { |
| | | ...form.value, |
| | | process: processName, // 保留 process 字段以兼容后端 |
| | | qualityInspectParams: tableData.value |
| | | } |
| | | if (operationType.value === "add") { |
| | | qualityInspectAdd(data).then(res => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | | }) |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | | // 先确保产品树已加载,否则编辑时产品/规格型号无法反显 |
| | | await getProductOptions(); |
| | | if (operationType.value === "edit") { |
| | | // 先保存 testStandardId,避免被清空 |
| | | const savedTestStandardId = row.testStandardId; |
| | | // 先设置表单数据,但暂时清空 testStandardId,等选项加载完成后再设置 |
| | | form.value = { ...row, testStandardId: "" }; |
| | | currentProductId.value = row.productId || 0; |
| | | // 关键:编辑时加载规格型号下拉选项,才能反显 productModelId |
| | | if (currentProductId.value) { |
| | | try { |
| | | const res = await modelList({ id: currentProductId.value }); |
| | | modelOptions.value = res || []; |
| | | // 同步回填 model / unit(有些接口返回的 row 里可能没带全) |
| | | if (form.value.productModelId) { |
| | | handleChangeModel(form.value.productModelId); |
| | | } |
| | | } catch (e) { |
| | | console.error("加载规格型号失败", e); |
| | | modelOptions.value = []; |
| | | } |
| | | } |
| | | // 编辑模式下,先加载指标选项,然后加载参数列表 |
| | | if (currentProductId.value) { |
| | | // 先加载指标选项 |
| | | let params = { |
| | | productId: currentProductId.value, |
| | | inspectType: 1, |
| | | process: form.value.process || "", |
| | | }; |
| | | qualityInspectDetailByProductId(params).then(res => { |
| | | testStandardOptions.value = res.data || []; |
| | | // 使用 nextTick 和 setTimeout 确保选项已经渲染到 DOM |
| | | nextTick(() => { |
| | | setTimeout(() => { |
| | | // 如果编辑数据中有 testStandardId,则设置并加载对应的参数 |
| | | if (savedTestStandardId) { |
| | | // 确保类型匹配(item.id 可能是数字或字符串) |
| | | const matchedOption = testStandardOptions.value.find( |
| | | item => |
| | | item.id == savedTestStandardId || |
| | | String(item.id) === String(savedTestStandardId) |
| | | ); |
| | | if (matchedOption) { |
| | | // 确保使用匹配项的 id(保持类型一致) |
| | | form.value.testStandardId = matchedOption.id; |
| | | // 编辑保留原检验值,直接拉取原参数数据 |
| | | getQualityInspectParamList(row.id); |
| | | } else { |
| | | // 如果找不到匹配项,尝试直接使用原值 |
| | | console.warn( |
| | | "未找到匹配的指标选项,testStandardId:", |
| | | savedTestStandardId, |
| | | "可用选项:", |
| | | testStandardOptions.value |
| | | ); |
| | | form.value.testStandardId = savedTestStandardId; |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | } else { |
| | | // 否则使用旧的逻辑 |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | }, 100); |
| | | }); |
| | | }); |
| | | } else { |
| | | qualityInspectUpdate(data).then(res => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | | }) |
| | | getQualityInspectParamList(row.id); |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | const getList = () => { |
| | | if (!currentProductId.value) { |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | | return; |
| | | } |
| | | const processName = form.value.process || ''; |
| | | let params = { |
| | | productId: currentProductId.value, |
| | | inspectType: 1, |
| | | process: processName |
| | | } |
| | | qualityInspectDetailByProductId(params).then(res => { |
| | | // 保存下拉框选项数据 |
| | | testStandardOptions.value = res.data || []; |
| | | // 清空表格数据,等待用户选择指标 |
| | | tableData.value = []; |
| | | // 清空指标选择 |
| | | form.value.testStandardId = ''; |
| | | }) |
| | | } |
| | | // 最后再打开弹窗,并清理校验态,避免必填提示闪烁 |
| | | dialogFormVisible.value = true; |
| | | nextTick(() => { |
| | | proxy.$refs?.formRef?.clearValidate?.(); |
| | | }); |
| | | }; |
| | | const getProductOptions = () => { |
| | | return productTreeList().then(res => { |
| | | productOptions.value = convertIdToValue(res); |
| | | return productOptions.value; |
| | | }); |
| | | }; |
| | | const getModels = value => { |
| | | form.value.productModelId = undefined; |
| | | form.value.unit = undefined; |
| | | modelOptions.value = []; |
| | | currentProductId.value = value; |
| | | form.value.productName = findNodeById(productOptions.value, value); |
| | | modelList({ id: value }).then(res => { |
| | | modelOptions.value = res; |
| | | }); |
| | | if (currentProductId.value) { |
| | | getList(); |
| | | } |
| | | }; |
| | | |
| | | // 指标选择变化处理 |
| | | const handleTestStandardChange = (testStandardId) => { |
| | | if (!testStandardId) { |
| | | tableData.value = []; |
| | | return; |
| | | } |
| | | tableLoading.value = true; |
| | | getQualityTestStandardParamByTestStandardId(testStandardId).then(res => { |
| | | tableData.value = res.data || []; |
| | | }).catch(error => { |
| | | console.error('获取标准参数失败:', error); |
| | | tableData.value = []; |
| | | }).finally(() => { |
| | | tableLoading.value = false; |
| | | }) |
| | | } |
| | | const getQualityInspectParamList = (id) => { |
| | | qualityInspectParamInfo(id).then(res => { |
| | | tableData.value = res.data; |
| | | }) |
| | | } |
| | | // 关闭弹框 |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | tableData.value = []; |
| | | testStandardOptions.value = []; |
| | | form.value.testStandardId = ''; |
| | | dialogFormVisible.value = false; |
| | | emit('close') |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | const handleChangeModel = value => { |
| | | form.value.model = |
| | | modelOptions.value.find(item => item.id == value)?.model || ""; |
| | | form.value.unit = |
| | | modelOptions.value.find(item => item.id == value)?.unit || ""; |
| | | }; |
| | | |
| | | const findNodeById = (nodes, productId) => { |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | if (nodes[i].value === productId) { |
| | | return nodes[i].label; // 找到节点,返回该节点 |
| | | } |
| | | if (nodes[i].children && nodes[i].children.length > 0) { |
| | | const foundNode = findNodeById(nodes[i].children, productId); |
| | | if (foundNode) { |
| | | return foundNode; // 在子节点中找到,返回该节点 |
| | | } |
| | | } |
| | | } |
| | | return null; // 没有找到节点,返回null |
| | | }; |
| | | function convertIdToValue(data) { |
| | | return data.map(item => { |
| | | const { id, children, ...rest } = item; |
| | | const newItem = { |
| | | ...rest, |
| | | value: id, // 将 id 改为 value |
| | | }; |
| | | if (children && children.length > 0) { |
| | | newItem.children = convertIdToValue(children); |
| | | } |
| | | |
| | | return newItem; |
| | | }); |
| | | } |
| | | // 工序变化处理 |
| | | // 提交产品表单 |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate(valid => { |
| | | if (valid) { |
| | | form.value.inspectType = 1; |
| | | const processName = form.value.process || ""; |
| | | if (operationType.value === "add") { |
| | | tableData.value.forEach(item => { |
| | | delete item.id; |
| | | }); |
| | | } |
| | | const data = { |
| | | ...form.value, |
| | | process: processName, // 保留 process 字段以兼容后端 |
| | | qualityInspectParams: tableData.value, |
| | | }; |
| | | if (operationType.value === "add") { |
| | | qualityInspectAdd(data).then(res => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | | }); |
| | | } else { |
| | | qualityInspectUpdate(data).then(res => { |
| | | proxy.$modal.msgSuccess("提交成功"); |
| | | closeDia(); |
| | | }); |
| | | } |
| | | } |
| | | }); |
| | | }; |
| | | const getList = () => { |
| | | if (!currentProductId.value) { |
| | | testStandardOptions.value = []; |
| | | tableData.value = []; |
| | | return; |
| | | } |
| | | const processName = form.value.process || ""; |
| | | let params = { |
| | | productId: currentProductId.value, |
| | | inspectType: 1, |
| | | process: processName, |
| | | }; |
| | | qualityInspectDetailByProductId(params).then(res => { |
| | | // 保存下拉框选项数据 |
| | | testStandardOptions.value = res.data || []; |
| | | // 清空表格数据,等待用户选择指标 |
| | | tableData.value = []; |
| | | // 清空指标选择 |
| | | form.value.testStandardId = ""; |
| | | }); |
| | | }; |
| | | |
| | | // 指标选择变化处理 |
| | | const handleTestStandardChange = testStandardId => { |
| | | if (!testStandardId) { |
| | | tableData.value = []; |
| | | return; |
| | | } |
| | | tableLoading.value = true; |
| | | getQualityTestStandardParamByTestStandardId(testStandardId) |
| | | .then(res => { |
| | | tableData.value = res.data || []; |
| | | }) |
| | | .catch(error => { |
| | | console.error("获取标准参数失败:", error); |
| | | tableData.value = []; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | const getQualityInspectParamList = id => { |
| | | qualityInspectParamInfo(id).then(res => { |
| | | tableData.value = res.data; |
| | | }); |
| | | }; |
| | | // 关闭弹框 |
| | | const closeDia = () => { |
| | | proxy.resetForm("formRef"); |
| | | tableData.value = []; |
| | | testStandardOptions.value = []; |
| | | form.value.testStandardId = ""; |
| | | dialogFormVisible.value = false; |
| | | emit("close"); |
| | | }; |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | |
| | | </style> |
| | |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="发货数量" |
| | | prop="totalQuantity" |
| | | show-overflow-tooltip |
| | | /> |
| | | <el-table-column |
| | | label="发货车牌号" |
| | | prop="shippingCarNumber" |
| | | show-overflow-tooltip |
| | |
| | | label="审核状态" |
| | | prop="status" |
| | | align="center" |
| | | width="120" |
| | | width="100" |
| | | > |
| | | <template #default="scope"> |
| | | <el-tag :type="getApprovalStatusType(scope.row.status)"> |
| | |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column |
| | | label="出库单号" |
| | | prop="outboundBatches" |
| | | show-overflow-tooltip |
| | | width="130" |
| | | /> |
| | | <el-table-column fixed="right" label="操作" width="220" align="center"> |
| | | <template #default="scope"> |
| | | <!-- <el-button--> |
| | |
| | | <el-descriptions-item label="快递单号" :span="2">{{ |
| | | detailRow.expressNumber || "--" |
| | | }}</el-descriptions-item> |
| | | <el-descriptions-item label="出库单号" :span="2">{{ |
| | | detailRow.outboundBatches || "--" |
| | | }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <el-table |
| | | :data="getDeliveryProductInfoList()" |
| | |
| | | <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.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> |
| | |
| | | |
| | | <div style="padding-top: 20px"> |
| | | <span class="descriptions">产品列表</span> |
| | | <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData" /> |
| | | <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData"> |
| | | <template #totalReturnNum="{ row }"> |
| | | {{ calcAlreadyReturned(row) }} |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | |
| | | const tableData = ref([]); |
| | | const availableProducts = ref([]); |
| | | |
| | | const sameKey = (a, b) => a != null && b != null && String(a) === String(b); |
| | | |
| | | /** 与 formDia 一致:两份列表按 id 合并,避免只取 productDtoData 时缺出库单号/批次/数量 */ |
| | | const mergeShippingProductLists = (data) => { |
| | | const lists = [data?.shippingProductVoList, data?.productDtoData].filter(Array.isArray); |
| | | if (!lists.length) return []; |
| | | const map = new Map(); |
| | | for (const list of lists) { |
| | | for (const p of list) { |
| | | if (p == null) continue; |
| | | const key = p.id != null ? String(p.id) : null; |
| | | if (!key) continue; |
| | | const prev = map.get(key); |
| | | map.set(key, prev ? { ...prev, ...p } : { ...p }); |
| | | } |
| | | } |
| | | return Array.from(map.values()); |
| | | }; |
| | | |
| | | const pickShippingLine = (normalized) => { |
| | | const pid = normalized?.returnSaleLedgerProductId ?? normalized?.id; |
| | | const sid = normalized?.stockOutRecordId ?? normalized?.shippingProductId; |
| | | const direct = availableProducts.value.find( |
| | | (p) => |
| | | sameKey(p?.id, pid) || |
| | | sameKey(p?.stockOutRecordId, pid) || |
| | | sameKey(p?.id, sid) || |
| | | sameKey(p?.stockOutRecordId, sid) |
| | | ); |
| | | if (direct) return direct; |
| | | const pmid = normalized?.productModelId; |
| | | if (pmid == null || pmid === "") return undefined; |
| | | const candidates = availableProducts.value.filter((p) => sameKey(p?.productModelId, pmid)); |
| | | if (!candidates.length) return undefined; |
| | | if (candidates.length === 1) return candidates[0]; |
| | | const spec = String(normalized?.specificationModel ?? normalized?.model ?? ""); |
| | | if (spec) { |
| | | const hit = candidates.find((p) => { |
| | | const ps = String(p?.specificationModel ?? p?.model ?? ""); |
| | | return ps && ps === spec; |
| | | }); |
| | | if (hit) return hit; |
| | | } |
| | | return candidates[0]; |
| | | }; |
| | | |
| | | const isEmptyText = (v) => v === "" || v == null || v === undefined; |
| | | |
| | | const firstFiniteNumber = (...vals) => { |
| | | for (const v of vals) { |
| | | if (v === "" || v == null || v === undefined) continue; |
| | | const n = Number(v); |
| | | if (Number.isFinite(n)) return n; |
| | | } |
| | | return undefined; |
| | | }; |
| | | |
| | | const firstNonEmptyText = (...vals) => { |
| | | const hit = vals.find((v) => !isEmptyText(v)); |
| | | return hit === undefined ? "" : hit; |
| | | }; |
| | | |
| | | const calcAlreadyReturned = (row) => { |
| | | const total = Number(row?.stockOutNum ?? 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); |
| | | }; |
| | | |
| | | /** 详情表用 productName / model;合并时勿让空串盖掉出库行字段 */ |
| | | const mergeDetailProductRow = (product, normalized) => { |
| | | const row = { ...product, ...normalized }; |
| | | row.outboundBatches = firstNonEmptyText( |
| | | row.outboundBatches, |
| | | product?.outboundBatches, |
| | | product?.shippingNo, |
| | | product?.outboundNo, |
| | | normalized?.outboundBatches, |
| | | normalized?.outboundNo, |
| | | normalized?.shippingNo |
| | | ); |
| | | row.batchNo = firstNonEmptyText( |
| | | row.batchNo, |
| | | product?.batchNo, |
| | | product?.batchNumber, |
| | | product?.lotNo, |
| | | product?.batchCode, |
| | | product?.shippingBatchNo, |
| | | normalized?.batchNo, |
| | | normalized?.batchNumber, |
| | | normalized?.lotNo, |
| | | normalized?.shippingBatchNo |
| | | ); |
| | | const stock = firstFiniteNumber( |
| | | row.stockOutNum, |
| | | product?.stockOutNum, |
| | | product?.totalQuantity, |
| | | product?.shippingQuantity, |
| | | product?.deliveryQuantity, |
| | | product?.quantity, |
| | | product?.outQuantity, |
| | | normalized?.stockOutNum, |
| | | normalized?.totalQuantity, |
| | | normalized?.shippingQuantity, |
| | | normalized?.deliveryQuantity |
| | | ); |
| | | if (stock !== undefined) row.stockOutNum = stock; |
| | | const un = firstFiniteNumber( |
| | | row.unQuantity, |
| | | product?.unQuantity, |
| | | product?.remainingQuantity, |
| | | product?.noReturnQuantity, |
| | | product?.canReturnQuantity, |
| | | product?.availableReturnNum, |
| | | normalized?.unQuantity, |
| | | normalized?.remainingQuantity, |
| | | normalized?.noReturnQuantity, |
| | | normalized?.canReturnQuantity |
| | | ); |
| | | if (un !== undefined) row.unQuantity = un; |
| | | else { |
| | | const s = Number(row.stockOutNum); |
| | | const ret = Number(row.totalReturnNum ?? 0); |
| | | if (Number.isFinite(s) && s >= 0 && Number.isFinite(ret) && ret >= 0) { |
| | | row.unQuantity = Math.max(0, s - ret); |
| | | } |
| | | } |
| | | const returned = firstFiniteNumber( |
| | | row.totalReturnNum, |
| | | product?.totalReturnNum, |
| | | product?.totalReturnedNum, |
| | | normalized?.totalReturnNum, |
| | | normalized?.totalReturnedNum |
| | | ); |
| | | if (returned !== undefined) row.totalReturnNum = returned; |
| | | else if (isEmptyText(row.totalReturnNum)) row.totalReturnNum = 0; |
| | | if (isEmptyText(row.unit)) { |
| | | row.unit = firstNonEmptyText(product?.unit, normalized?.unit); |
| | | } |
| | | row.productName = firstNonEmptyText( |
| | | row.productName, |
| | | normalized?.productName, |
| | | normalized?.productCategory, |
| | | product?.productName, |
| | | product?.productCategory |
| | | ); |
| | | row.model = firstNonEmptyText( |
| | | row.model, |
| | | normalized?.model, |
| | | normalized?.specificationModel, |
| | | product?.model, |
| | | product?.specificationModel |
| | | ); |
| | | return row; |
| | | }; |
| | | |
| | | const normalizeDetailRow = (raw) => { |
| | | const ledgerId = |
| | | raw?.returnSaleLedgerProductId ?? |
| | | raw?.saleLedgerProductId ?? |
| | | raw?.stockOutRecordId ?? |
| | | raw?.shippingProductId; |
| | | const productId = ledgerId ?? raw?.id; |
| | | const num = Number(raw?.num ?? raw?.returnQuantity ?? 0); |
| | | return { |
| | | ...raw, |
| | | id: productId, |
| | | returnSaleLedgerProductId: productId, |
| | | productModelId: raw?.productModelId, |
| | | stockOutRecordId: raw?.stockOutRecordId, |
| | | shippingProductId: raw?.shippingProductId, |
| | | productName: raw?.productName ?? raw?.productCategory ?? raw?.productTypeName ?? "", |
| | | model: raw?.model ?? raw?.specificationModel ?? raw?.specModel ?? "", |
| | | outboundBatches: raw?.outboundBatches ?? raw?.outboundNo ?? raw?.shippingNo, |
| | | batchNo: |
| | | raw?.batchNo ?? |
| | | raw?.batchNumber ?? |
| | | raw?.lotNo ?? |
| | | raw?.batchCode ?? |
| | | raw?.shippingBatchNo, |
| | | stockOutNum: |
| | | raw?.stockOutNum ?? |
| | | raw?.totalQuantity ?? |
| | | raw?.shippingQuantity ?? |
| | | raw?.deliveryQuantity ?? |
| | | raw?.quantity, |
| | | totalReturnNum: raw?.totalReturnNum ?? raw?.totalReturnedNum, |
| | | unQuantity: |
| | | raw?.unQuantity ?? |
| | | raw?.remainingQuantity ?? |
| | | raw?.noReturnQuantity ?? |
| | | raw?.canReturnQuantity, |
| | | 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 tableColumn = [ |
| | | {align: "center", label: "产品大类", prop: "productCategory"}, |
| | | {align: "center", label: "规格型号", prop: "specificationModel"}, |
| | | {align: "center", label: "出库单号", prop: "outboundBatches"}, |
| | | {align: "center", label: "批次号", prop: "batchNo"}, |
| | | {align: "center", label: "产品大类", prop: "productName"}, |
| | | {align: "center", label: "规格型号", prop: "model"}, |
| | | {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: "stockOutNum", width: 120}, |
| | | {align: "center", label: "已退货数量", prop: "totalReturnNum", width: 120, dataType: "slot", slot: "totalReturnNum"}, |
| | | {align: "center", label: "未退货数量", prop: "unQuantity", width: 120}, |
| | | {align: "center", label: "退货数量", prop: "returnQuantity", width: 120}, |
| | | {align: "center", label: "退货产品单价", prop: "price", width: 120}, |
| | |
| | | if (detail.value.shippingId) { |
| | | const productRes = await returnManagementGetByShippingId({ shippingId: detail.value.shippingId }); |
| | | if (productRes.code === 200) { |
| | | availableProducts.value = productRes.data.productDtoData || []; |
| | | availableProducts.value = mergeShippingProductLists(productRes.data); |
| | | } |
| | | } |
| | | |
| | | |
| | | 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; |
| | | }) : []; |
| | | detail.value?.returnSaleProductList || |
| | | detail.value?.returnSaleProductDtoData || |
| | | []; |
| | | |
| | | tableData.value = Array.isArray(list) |
| | | ? list.map((raw) => { |
| | | const normalized = normalizeDetailRow(raw); |
| | | const product = pickShippingLine(normalized); |
| | | return product ? mergeDetailProductRow(product, normalized) : normalized; |
| | | }) |
| | | : []; |
| | | |
| | | const headerShipNo = detail.value?.shippingNo; |
| | | if (headerShipNo && Array.isArray(tableData.value) && tableData.value.length) { |
| | | tableData.value = tableData.value.map((r) => |
| | | isEmptyText(r.outboundBatches) ? { ...r, outboundBatches: headerShipNo } : r |
| | | ); |
| | | } |
| | | } catch (e) { |
| | | console.error("Failed to load detail", e); |
| | | } finally { |
| | |
| | | <el-button type="primary" @click="openProductSelection" :disabled="!form.shippingId">添加产品</el-button> |
| | | </div> |
| | | <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData"> |
| | | <template #totalReturnNum="{ row }"> |
| | | {{ calcAlreadyReturned(row) }} |
| | | </template> |
| | | <template #returnQuantity="{ row }"> |
| | | <el-input |
| | | v-model="row.returnQuantity" |
| | |
| | | placeholder="请输入" |
| | | /> |
| | | </template> |
| | | <template #action="{ row, index }"> |
| | | <template #action="{ index }"> |
| | | <el-button type="danger" link @click="deleteRow(index)">删除</el-button> |
| | | </template> |
| | | </PIMTable> |
| | |
| | | row-key="id" |
| | | > |
| | | <el-table-column align="center" type="selection" width="55" /> |
| | | <el-table-column align="center" prop="outboundBatches" label="出库单号" /> |
| | | <el-table-column align="center" prop="batchNo" label="批次号" /> |
| | | <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="stockOutNum" label="总数量" /> |
| | | <el-table-column align="center" prop="unQuantity" label="未退货数量" /> |
| | | <el-table-column align="center" label="已退货数量"> |
| | | <template #default="{ row }">{{ calcAlreadyReturned(row) }}</template> |
| | |
| | | const { form, rules } = toRefs(data); |
| | | |
| | | const calcAlreadyReturned = (row) => { |
| | | const total = Number(row?.quantity ?? row?.totalQuantity ?? row?.totalReturnNum ?? 0); |
| | | const total = Number(row?.stockOutNum ?? 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: "outboundBatches" }, |
| | | {align: "center", label: "批次号", prop: "batchNo" }, |
| | | {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: "stockOutNum", width: 120 }, |
| | | {align: "center", label: "已退货数量", prop: "totalReturnNum", width: 120, dataType: "slot", slot: "totalReturnNum" }, |
| | | {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 }, |
| | |
| | | tableData.value.splice(index, 1); |
| | | }; |
| | | |
| | | const sameKey = (a, b) => a != null && b != null && String(a) === String(b); |
| | | |
| | | /** 接口可能拆成 shippingProductVoList / productDtoData 两份,只取其一会缺批次、数量等字段 */ |
| | | const mergeShippingProductLists = (data) => { |
| | | const lists = [data?.shippingProductVoList, data?.productDtoData].filter(Array.isArray); |
| | | if (!lists.length) return []; |
| | | const map = new Map(); |
| | | for (const list of lists) { |
| | | for (const p of list) { |
| | | if (p == null) continue; |
| | | const key = p.id != null ? String(p.id) : null; |
| | | if (!key) continue; |
| | | const prev = map.get(key); |
| | | map.set(key, prev ? { ...prev, ...p } : { ...p }); |
| | | } |
| | | } |
| | | return Array.from(map.values()); |
| | | }; |
| | | |
| | | const pickShippingLine = (normalized) => { |
| | | const pid = normalized?.returnSaleLedgerProductId ?? normalized?.id; |
| | | const sid = normalized?.stockOutRecordId ?? normalized?.shippingProductId; |
| | | const direct = availableProducts.value.find( |
| | | (p) => |
| | | sameKey(p?.id, pid) || |
| | | sameKey(p?.stockOutRecordId, pid) || |
| | | sameKey(p?.id, sid) || |
| | | sameKey(p?.stockOutRecordId, sid) |
| | | ); |
| | | if (direct) return direct; |
| | | const pmid = normalized?.productModelId; |
| | | if (pmid == null || pmid === "") return undefined; |
| | | const candidates = availableProducts.value.filter((p) => sameKey(p?.productModelId, pmid)); |
| | | if (!candidates.length) return undefined; |
| | | if (candidates.length === 1) return candidates[0]; |
| | | const spec = String(normalized?.specificationModel ?? normalized?.model ?? ""); |
| | | if (spec) { |
| | | const hit = candidates.find((p) => { |
| | | const ps = String(p?.specificationModel ?? p?.model ?? ""); |
| | | return ps && ps === spec; |
| | | }); |
| | | if (hit) return hit; |
| | | } |
| | | return candidates[0]; |
| | | }; |
| | | |
| | | const isEmptyText = (v) => v === "" || v == null || v === undefined; |
| | | |
| | | const firstFiniteNumber = (...vals) => { |
| | | for (const v of vals) { |
| | | if (v === "" || v == null || v === undefined) continue; |
| | | const n = Number(v); |
| | | if (Number.isFinite(n)) return n; |
| | | } |
| | | return undefined; |
| | | }; |
| | | |
| | | const firstNonEmptyText = (...vals) => { |
| | | const hit = vals.find((v) => !isEmptyText(v)); |
| | | return hit === undefined ? "" : hit; |
| | | }; |
| | | |
| | | /** 详情接口字段常不全;{...product,...normalized} 会被 normalized 里的空串盖掉出库行上的展示字段 */ |
| | | const mergeShippingLineWithDetail = (product, normalized) => { |
| | | const row = { ...product, ...normalized }; |
| | | row.outboundBatches = firstNonEmptyText( |
| | | row.outboundBatches, |
| | | product?.outboundBatches, |
| | | product?.shippingNo, |
| | | product?.outboundNo, |
| | | normalized?.outboundBatches, |
| | | normalized?.outboundNo, |
| | | normalized?.shippingNo |
| | | ); |
| | | row.batchNo = firstNonEmptyText( |
| | | row.batchNo, |
| | | product?.batchNo, |
| | | product?.batchNumber, |
| | | product?.lotNo, |
| | | product?.batchCode, |
| | | product?.shippingBatchNo, |
| | | normalized?.batchNo, |
| | | normalized?.batchNumber, |
| | | normalized?.lotNo, |
| | | normalized?.shippingBatchNo |
| | | ); |
| | | const stock = firstFiniteNumber( |
| | | row.stockOutNum, |
| | | product?.stockOutNum, |
| | | product?.totalQuantity, |
| | | product?.shippingQuantity, |
| | | product?.deliveryQuantity, |
| | | product?.quantity, |
| | | product?.outQuantity, |
| | | normalized?.stockOutNum, |
| | | normalized?.totalQuantity, |
| | | normalized?.shippingQuantity, |
| | | normalized?.deliveryQuantity |
| | | ); |
| | | if (stock !== undefined) row.stockOutNum = stock; |
| | | const un = firstFiniteNumber( |
| | | row.unQuantity, |
| | | product?.unQuantity, |
| | | product?.remainingQuantity, |
| | | product?.noReturnQuantity, |
| | | product?.canReturnQuantity, |
| | | product?.availableReturnNum, |
| | | normalized?.unQuantity, |
| | | normalized?.remainingQuantity, |
| | | normalized?.noReturnQuantity, |
| | | normalized?.canReturnQuantity |
| | | ); |
| | | if (un !== undefined) row.unQuantity = un; |
| | | else { |
| | | const s = Number(row.stockOutNum); |
| | | const ret = Number(row.totalReturnNum ?? 0); |
| | | if (Number.isFinite(s) && s >= 0 && Number.isFinite(ret) && ret >= 0) { |
| | | row.unQuantity = Math.max(0, s - ret); |
| | | } |
| | | } |
| | | const returned = firstFiniteNumber( |
| | | row.totalReturnNum, |
| | | product?.totalReturnNum, |
| | | product?.totalReturnedNum, |
| | | normalized?.totalReturnNum, |
| | | normalized?.totalReturnedNum |
| | | ); |
| | | if (returned !== undefined) row.totalReturnNum = returned; |
| | | else if (isEmptyText(row.totalReturnNum)) row.totalReturnNum = 0; |
| | | if (isEmptyText(row.unit)) { |
| | | row.unit = firstNonEmptyText(product?.unit, normalized?.unit); |
| | | } |
| | | if (isEmptyText(row.productCategory)) { |
| | | row.productCategory = firstNonEmptyText( |
| | | normalized?.productCategory, |
| | | normalized?.productName, |
| | | product?.productCategory, |
| | | product?.productName |
| | | ); |
| | | } |
| | | if (isEmptyText(row.specificationModel)) { |
| | | row.specificationModel = firstNonEmptyText( |
| | | normalized?.specificationModel, |
| | | normalized?.model, |
| | | product?.specificationModel, |
| | | product?.model |
| | | ); |
| | | } |
| | | return row; |
| | | }; |
| | | |
| | | const normalizeDetailRow = (raw) => { |
| | | const productId = raw?.returnSaleLedgerProductId ?? raw?.saleLedgerProductId ?? raw?.id; |
| | | const ledgerId = |
| | | raw?.returnSaleLedgerProductId ?? |
| | | raw?.saleLedgerProductId ?? |
| | | raw?.stockOutRecordId ?? |
| | | raw?.shippingProductId; |
| | | const productId = ledgerId ?? raw?.id; |
| | | const returnSaleProductId = raw?.returnSaleProductId ?? raw?.id; |
| | | const num = Number(raw?.num ?? raw?.returnQuantity ?? 0); |
| | | return { |
| | |
| | | returnSaleProductId, |
| | | returnSaleLedgerProductId: productId, |
| | | productModelId: raw?.productModelId, |
| | | stockOutRecordId: raw?.stockOutRecordId, |
| | | shippingProductId: raw?.shippingProductId, |
| | | productCategory: raw?.productCategory ?? raw?.productName ?? raw?.productTypeName ?? "", |
| | | specificationModel: raw?.specificationModel ?? raw?.model ?? raw?.specModel ?? "", |
| | | outboundBatches: raw?.outboundBatches ?? raw?.outboundNo ?? raw?.shippingNo, |
| | | batchNo: |
| | | raw?.batchNo ?? |
| | | raw?.batchNumber ?? |
| | | raw?.lotNo ?? |
| | | raw?.batchCode ?? |
| | | raw?.shippingBatchNo, |
| | | stockOutNum: |
| | | raw?.stockOutNum ?? |
| | | raw?.totalQuantity ?? |
| | | raw?.shippingQuantity ?? |
| | | raw?.deliveryQuantity ?? |
| | | raw?.quantity, |
| | | totalReturnNum: raw?.totalReturnNum ?? raw?.totalReturnedNum, |
| | | unQuantity: |
| | | raw?.unQuantity ?? |
| | | raw?.remainingQuantity ?? |
| | | raw?.noReturnQuantity ?? |
| | | raw?.canReturnQuantity, |
| | | num, |
| | | returnQuantity: Number.isFinite(num) ? num : 0, |
| | | price: Number(raw?.taxInclusiveUnitPrice ?? raw?.price ?? 0), |
| | |
| | | |
| | | 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); |
| | |
| | | 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; |
| | | const product = pickShippingLine(normalized); |
| | | return product ? mergeShippingLineWithDetail(product, normalized) : normalized; |
| | | }) |
| | | : []; |
| | | |
| | | |
| | | const headerShipNo = detail?.shippingNo ?? form.value?.shippingNo; |
| | | if (headerShipNo && Array.isArray(tableData.value) && tableData.value.length) { |
| | | tableData.value = tableData.value.map((r) => |
| | | isEmptyText(r.outboundBatches) ? { ...r, outboundBatches: headerShipNo } : r |
| | | ); |
| | | } |
| | | |
| | | calculateTotalRefund(); |
| | | }; |
| | | |
| | |
| | | proxy.$refs["formRef"].validate(valid => { |
| | | if (!valid) return; |
| | | const returnSaleProducts = (tableData.value || []).map(el => ({ |
| | | returnSaleLedgerProductId: el.returnSaleLedgerProductId ?? el.id, |
| | | stockOutRecordId: el.returnSaleLedgerProductId ?? el.id, |
| | | productModelId: el.productModelId, |
| | | unit: el.unit, |
| | | num: Number(el.num ?? el.returnQuantity ?? 0), |
| | |
| | | // 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 || []; |
| | | availableProducts.value = mergeShippingProductLists(res.data); |
| | | if (clearTable) tableData.value = []; |
| | | } |
| | | }; |
| | |
| | | }; |
| | | |
| | | const calculateRowAmount = (row) => { |
| | | const quantity = Number(row.returnQuantity || 0); |
| | | const stockOutNum = Number(row.returnQuantity || 0); |
| | | const price = Number(row.price || 0); |
| | | row.amount = (quantity * price).toFixed(2); |
| | | row.amount = (stockOutNum * price).toFixed(2); |
| | | }; |
| | | |
| | | const calculateTotalRefund = () => { |
| | |
| | | amount: "0.00", |
| | | isQuality: 2, |
| | | remark: "", |
| | | productCategory: product.productCategory ?? product.productName ?? "", |
| | | productName: product.productName, |
| | | specificationModel: product.specificationModel, |
| | | specificationModel: product.specificationModel ?? product.model ?? "", |
| | | unit: product.unit, |
| | | quantity: product.quantity, |
| | | stockOutNum: product.stockOutNum, |
| | | totalReturnNum: product.totalReturnNum, |
| | | unQuantity: product.unQuantity |
| | | }); |
| | |
| | | <el-form-item label="销售单号"> |
| | | <el-input v-model="searchForm.salesContractNo" placeholder="销售单号" clearable /> |
| | | </el-form-item> |
| | | <el-form-item label="关联出库单号"> |
| | | <el-input v-model="searchForm.shippingNo" placeholder="关联出库单号" clearable /> |
| | | <el-form-item label="关联发货单号"> |
| | | <el-input v-model="searchForm.shippingNo" placeholder="关联发货单号" clearable /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleQuery">搜索</el-button> |
| | |
| | | ]); |
| | | |
| | | 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: "returnNo", minWidth: 160 }, |
| | | { label: "单据状态", prop: "status", minWidth: 90, dataType: "slot", slot: "status" }, |
| | | { label: "制单时间", prop: "makeTime", minWidth: 170 }, |
| | | { label: "客户名称", prop: "customerName", minWidth: 220 }, |
| | | { label: "销售单号", prop: "salesContractNo", minWidth: 160 }, |
| | | { label: "业务员", prop: "salesman", minWidth: 120 }, |
| | | { label: "关联发货单号", prop: "shippingNo", minWidth: 170 }, |
| | | { label: "项目名称", prop: "projectName", minWidth: 180 }, |
| | | { label: "制单人", prop: "maker", minWidth: 120 }, |
| | | { |
| | | label: "操作", |
| | | prop: "operation", |
| | | dataType: "action", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 240, |
| | | minWidth: 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) }, |
| | |
| | | 审核拒绝: "审核拒绝", |
| | | 审核通过: "审核通过", |
| | | 已发货: "已发货", |
| | | 部分发货: "部分发货", |
| | | }; |
| | | return statusTextMap[statusStr] || "待发货"; |
| | | }; |
| | |
| | | 审核拒绝: "danger", |
| | | 审核通过: "success", |
| | | 已发货: "success", |
| | | 部分发货: "warning", |
| | | }; |
| | | return typeTextMap[statusStr] || "info"; |
| | | }; |