浪潮
1.日志前端页面开发与联调
2.仓库盘点页面开发与联调
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // å页æ¥è¯¢çç¹è®¡åå表 |
| | | export const getStockCheckPlanPage = (params) => { |
| | | return request({ |
| | | url: '/stockInventoryCheckPlan/listPage', |
| | | method: 'get', |
| | | params, |
| | | }) |
| | | } |
| | | |
| | | // æ°å¢çç¹è®¡å |
| | | export const addStockCheckPlan = (data) => { |
| | | return request({ |
| | | url: '/stockInventoryCheckPlan/add', |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // ä¿®æ¹çç¹è®¡å |
| | | export const updateStockCheckPlan = (data) => { |
| | | return request({ |
| | | url: '/stockInventoryCheckPlan/update', |
| | | method: 'put', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // å é¤çç¹è®¡å |
| | | export const deleteStockCheckPlan = (ids) => { |
| | | return request({ |
| | | url: '/stockInventoryCheckPlan/delete', |
| | | method: 'delete', |
| | | data: ids, |
| | | }) |
| | | } |
| | | |
| | | // æäº¤å®¡æ¹ |
| | | export const submitApproval = (id) => { |
| | | return request({ |
| | | url: '/stockCheckPlan/submitApproval/' + id, |
| | | method: 'post', |
| | | }) |
| | | } |
| | | |
| | | // 审æ¹éè¿ |
| | | export const approvePlan = (id) => { |
| | | return request({ |
| | | url: '/stockCheckPlan/approve/' + id, |
| | | method: 'post', |
| | | }) |
| | | } |
| | | |
| | | // å®¡æ¹æç» |
| | | export const rejectPlan = (id, reason) => { |
| | | return request({ |
| | | url: '/stockCheckPlan/reject/' + id, |
| | | method: 'post', |
| | | data: { reason }, |
| | | }) |
| | | } |
| | | |
| | | // å¼å§çç¹ |
| | | export const startCheck = (id) => { |
| | | return request({ |
| | | url: '/stockInventoryCheckPlan/start/' + id, |
| | | method: 'post', |
| | | }) |
| | | } |
| | | |
| | | // ç»æçç¹ |
| | | export const completeCheck = (data) => { |
| | | return request({ |
| | | url: '/stockInventoryCheckPlan/end', |
| | | method: 'post', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | // è·åçç¹ååå表 |
| | | export const getCheckItems = (planId) => { |
| | | return request({ |
| | | url: '/stockInventoryCheckPlan/detail/' + planId, |
| | | method: 'get', |
| | | }) |
| | | } |
| | | |
| | | // ä¿åçç¹æ°æ® |
| | | export const saveCheckData = (data) => { |
| | | return request({ |
| | | url: '/stockInventoryCheckPlanItem/batchUpdate', |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // è·å差弿±æ» |
| | | export const getDiffSummary = (planId) => { |
| | | return request({ |
| | | url: '/stockCheckPlan/diffSummary/' + planId, |
| | | method: 'get', |
| | | }) |
| | | } |
| | | |
| | | // çæçäºå¤çå |
| | | export const generateProfitLoss = (planId) => { |
| | | return request({ |
| | | url: '/stockCheckPlan/generateProfitLoss/' + planId, |
| | | method: 'post', |
| | | }) |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // å页æ¥è¯¢çäºè®°å½å表 |
| | | export const getStockProfitLossPage = (params) => { |
| | | return request({ |
| | | url: '/stockProfitLoss/page', |
| | | method: 'get', |
| | | params, |
| | | }) |
| | | } |
| | | |
| | | // æ¥è¯¢çäºè®°å½è¯¦æ
|
| | | export const getStockProfitLossDetail = (id) => { |
| | | return request({ |
| | | url: '/stockProfitLoss/' + id, |
| | | method: 'get', |
| | | }) |
| | | } |
| | | |
| | | // æ°å¢çäºè®°å½ï¼æå¨åå»ºï¼ |
| | | export const addStockProfitLoss = (data) => { |
| | | return request({ |
| | | url: '/stockProfitLoss/add', |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // ä¿®æ¹çäºè®°å½ |
| | | export const updateStockProfitLoss = (data) => { |
| | | return request({ |
| | | url: '/stockProfitLoss/update', |
| | | method: 'put', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // å é¤çäºè®°å½ |
| | | export const deleteStockProfitLoss = (id) => { |
| | | return request({ |
| | | url: '/stockProfitLoss/delete/' + id, |
| | | method: 'delete', |
| | | }) |
| | | } |
| | | |
| | | // æäº¤å®¡æ¹ |
| | | export const submitApproval = (id) => { |
| | | return request({ |
| | | url: '/stockProfitLoss/submitApproval/' + id, |
| | | method: 'post', |
| | | }) |
| | | } |
| | | |
| | | // 审æ¹éè¿ |
| | | export const approveProfitLoss = (id) => { |
| | | return request({ |
| | | url: '/stockProfitLoss/approve/' + id, |
| | | method: 'post', |
| | | }) |
| | | } |
| | | |
| | | // å®¡æ¹æç» |
| | | export const rejectProfitLoss = (id, reason) => { |
| | | return request({ |
| | | url: '/stockProfitLoss/reject/' + id, |
| | | method: 'post', |
| | | data: { reason }, |
| | | }) |
| | | } |
| | | |
| | | // æ§è¡çäºå¤çï¼æ´æ°åºåï¼ |
| | | export const executeProfitLoss = (id) => { |
| | | return request({ |
| | | url: '/stockProfitLoss/execute/' + id, |
| | | method: 'post', |
| | | }) |
| | | } |
| | | |
| | | // æ ¹æ®çç¹è®¡åIDæ¥è¯¢å·®å¼ååï¼ç¨äºçæçäºåï¼ |
| | | export const getDiffItemsByPlanId = (planId) => { |
| | | return request({ |
| | | url: '/stockProfitLoss/diffItems/' + planId, |
| | | method: 'get', |
| | | }) |
| | | } |
| | | |
| | | // 导åºçäºè®°å½ |
| | | export const exportStockProfitLoss = (params) => { |
| | | return request({ |
| | | url: '/stockProfitLoss/export', |
| | | method: 'get', |
| | | params, |
| | | responseType: 'blob', |
| | | }) |
| | | } |
| | |
| | | size: 1000, |
| | | total: 0, |
| | | }); |
| | | if(res.records){ |
| | | customerNameOptions.value = res.records.map(item => ({ |
| | | if(res.data.records){ |
| | | customerNameOptions.value = res.data.records.map(item => ({ |
| | | label: item.customerName, |
| | | value: item.customerName, |
| | | id: item.id |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form" style="margin-bottom: 20px;"> |
| | | <div> |
| | | <span class="search_title">çç¹åå·ï¼</span> |
| | | <el-input v-model="searchForm.planNo" placeholder="请è¾å
¥" clearable style="width: 240px; margin-right: 10px" @change="handleQuery" /> |
| | | <span class="search_title">ç¶æï¼</span> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©" clearable style="width: 240px" @change="handleQuery"> |
| | | <el-option label="å¾
æ§è¡" value="0" /> |
| | | <el-option label="æ§è¡ä¸" value="1" /> |
| | | <el-option label="已宿" value="2" /> |
| | | <el-option label="已忶" value="3" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å¢çç¹æ¹æ¡</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :page="page" |
| | | :isShowPagination="true" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾çç¹è®¡å --> |
| | | <FormDialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="950px" |
| | | @close="resetForm" |
| | | @confirm="handleSubmit" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> |
| | | <el-divider content-position="left">åºæ¬ä¿¡æ¯</el-divider> |
| | | <el-row> |
| | | <el-col :span="8"> |
| | | <el-form-item label="çç¹åå·"> |
| | | <el-input v-model="form.planNo" disabled placeholder="ç³»ç»èªå¨çæ" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="å¶å人" prop="createBy"> |
| | | <el-select v-model="form.createBy" placeholder="è¯·éæ©å¶å人" style="width: 100%" filterable> |
| | | <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="å¶åæ¥æ"> |
| | | <el-date-picker |
| | | v-model="form.createTime" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="8"> |
| | | <el-form-item label="çç¹äºº" prop="checkerId"> |
| | | <el-select v-model="form.checkerId" placeholder="è¯·éæ©çç¹äºº" style="width: 100%" filterable> |
| | | <el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="è®¡åæ¥æ" prop="planDate"> |
| | | <el-date-picker |
| | | v-model="form.planDate" |
| | | type="date" |
| | | placeholder="è¯·éæ©" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="form.remark" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | |
| | | <el-divider content-position="left">çç¹äº§å</el-divider> |
| | | <div class="mb10"> |
| | | <el-button type="primary" size="small" @click="openProductDialog">éæ©äº§å</el-button> |
| | | <el-button size="small" @click="clearProducts">æ¸
空</el-button> |
| | | </div> |
| | | <PIMTable |
| | | :column="productColumn" |
| | | :tableData="form.items" |
| | | :isShowPagination="false" |
| | | height="350px" |
| | | /> |
| | | </el-form> |
| | | </FormDialog> |
| | | |
| | | <!-- éæ©äº§åå¼¹æ¡ --> |
| | | <ProductSelectDialog |
| | | v-model="productDialogVisible" |
| | | requestUrl="/stockInventory/pagestockInventoryNoQua" |
| | | @confirm="handleProductSelect" |
| | | /> |
| | | |
| | | <!-- å½å
¥çç¹æ°æ® --> |
| | | <FormDialog |
| | | v-model="checkDialogVisible" |
| | | title="å½å
¥çç¹æ°æ®" |
| | | width="1000px" |
| | | operationType="detail" |
| | | @close="checkDialogVisible = false" |
| | | > |
| | | <el-alert |
| | | title="æç¤ºï¼å½å
¥å®é
çç¹æ°éï¼ç³»ç»å°èªå¨è®¡ç®å·®å¼" |
| | | type="info" |
| | | :closable="false" |
| | | style="margin-bottom: 15px" |
| | | /> |
| | | <el-table |
| | | v-loading="checkLoading" |
| | | :data="checkItemList" |
| | | height="400" |
| | | border |
| | | > |
| | | <el-table-column type="index" label="åºå·" width="60" align="center" /> |
| | | <el-table-column prop="productName" label="产ååç§°" min-width="150" /> |
| | | <el-table-column prop="model" label="è§æ ¼åå·" min-width="150" /> |
| | | <el-table-column prop="unit" label="åä½" width="80" align="center" /> |
| | | <el-table-column prop="batchNo" label="æ¹å·" width="120" align="center" /> |
| | | <el-table-column prop="systemQuantity" label="ç³»ç»åºå" width="100" align="center" /> |
| | | <el-table-column label="çç¹æ°é" width="120" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-input-number |
| | | v-model="row.actualQuantity" |
| | | :min="0" |
| | | :precision="2" |
| | | :controls="false" |
| | | style="width: 100%" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å·®å¼" width="100" align="center"> |
| | | <template #default="{ row }"> |
| | | <span :class="{ 'text-profit': (row.actualQuantity - row.systemQuantity) > 0, 'text-loss': (row.actualQuantity - row.systemQuantity) < 0 }"> |
| | | {{ row.actualQuantity - row.systemQuantity }} |
| | | </span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="夿³¨" min-width="150"> |
| | | <template #default="{ row }"> |
| | | <el-input v-model="row.remark" placeholder="请è¾å
¥å¤æ³¨" /> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button type="primary" @click="handleSaveCheckData" :loading="submitLoading">ä¿å</el-button> |
| | | <el-button @click="checkDialogVisible = false">åæ¶</el-button> |
| | | </div> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <!-- æ¥çå·®å¼ --> |
| | | <FormDialog |
| | | v-model="diffDialogVisible" |
| | | title="çç¹å·®å¼æ±æ»" |
| | | width="1000px" |
| | | operationType="detail" |
| | | @close="diffDialogVisible = false" |
| | | > |
| | | <div class="diff-summary"> |
| | | <el-row :gutter="20"> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-label">çç¹äº§åæ»æ°</div> |
| | | <div class="stat-value">{{ diffSummary.totalCount }}</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-label">çç产å</div> |
| | | <div class="stat-value text-profit">{{ diffSummary.profitCount }}</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-label">çäºäº§å</div> |
| | | <div class="stat-value text-loss">{{ diffSummary.lossCount }}</div> |
| | | </div> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <div class="stat-item"> |
| | | <div class="stat-label">æ å·®å¼</div> |
| | | <div class="stat-value">{{ diffSummary.normalCount }}</div> |
| | | </div> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | <PIMTable |
| | | :column="diffItemColumn" |
| | | :tableData="diffItemList" |
| | | :isShowPagination="false" |
| | | height="400px" |
| | | /> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <!-- <el-button type="success" @click="handleGenerateFromDiff">çæçäºå¤çå</el-button> --> |
| | | <el-button @click="diffDialogVisible = false">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <!-- æ¥ç详æ
--> |
| | | <FormDialog |
| | | v-model="viewDialogVisible" |
| | | title="çç¹è®¡å详æ
" |
| | | width="900px" |
| | | operationType="detail" |
| | | @close="viewDialogVisible = false" |
| | | > |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="çç¹åå·">{{ viewData.planNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="çç¹äºº">{{ viewData.checkerName }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¶å人">{{ viewData.createBy }}</el-descriptions-item> |
| | | <el-descriptions-item label="å¶åæ¥æ">{{ viewData.createTime }}</el-descriptions-item> |
| | | <el-descriptions-item label="è®¡åæ¥æ">{{ viewData.planDate }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç¶æ"> |
| | | <el-tag :type="getStatusType(viewData.status)">{{ getStatusText(viewData.status) }}</el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="夿³¨" :span="2">{{ viewData.remark || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <div style="margin-top: 20px;"> |
| | | <div class="section-title">çç¹äº§åæ¸
å</div> |
| | | <PIMTable |
| | | :column="viewProductColumn" |
| | | :tableData="viewData.items" |
| | | :isShowPagination="false" |
| | | height="300px" |
| | | /> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="viewDialogVisible = false">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import FormDialog from '@/components/Dialog/FormDialog.vue' |
| | | import ProductSelectDialog from '@/views/basicData/product/ProductSelectDialog.vue' |
| | | import { userListNoPage } from '@/api/system/user.js' |
| | | import { |
| | | getStockCheckPlanPage, |
| | | addStockCheckPlan, |
| | | updateStockCheckPlan, |
| | | deleteStockCheckPlan, |
| | | submitApproval, |
| | | startCheck, |
| | | completeCheck, |
| | | getCheckItems, |
| | | saveCheckData, |
| | | getDiffSummary, |
| | | generateProfitLoss, |
| | | } from '@/api/inventoryManagement/stockCheck.js' |
| | | |
| | | |
| | | |
| | | const tableData = ref([]) |
| | | const tableLoading = ref(false) |
| | | const page = reactive({ current: 1, size: 20, total: 0 }) |
| | | const searchForm = reactive({ planNo: '', status: '' }) |
| | | |
| | | // ä¸»è¡¨æ ¼åé
ç½® |
| | | const tableColumn = ref([ |
| | | { label: 'çç¹åå·', prop: 'planNo' }, |
| | | { label: 'å¶å人', prop: 'createBy' }, |
| | | { label: 'çç¹äºº', prop: 'checkerName' }, |
| | | { label: 'å¶åæ¥æ', prop: 'createTime', align: 'center' }, |
| | | { label: 'è®¡åæ¥æ', prop: 'planDate', align: 'center' }, |
| | | { |
| | | label: 'ç¶æ', |
| | | prop: 'status', |
| | | align: 'center', |
| | | dataType: 'tag', |
| | | formatData: (v) => ({ |
| | | '0': 'å¾
æ§è¡', '1': 'æ§è¡ä¸', '2': '已宿', '3': '已忶', |
| | | }[v] || v), |
| | | formatType: (v) => ({ |
| | | '0': 'info', '1': 'primary', '2': 'success', '3': 'warning', |
| | | }[v] || 'info') |
| | | }, |
| | | { |
| | | label: 'æä½', |
| | | dataType: 'action', |
| | | fixed: 'right', |
| | | align: 'center', |
| | | operation: [ |
| | | { name: 'ç¼è¾', clickFun: (row) => handleEdit(row), showHide: (row) => row.status == '0' }, |
| | | { name: 'æ¥ç', clickFun: (row) => handleView(row) }, |
| | | { name: 'å¼å§çç¹', clickFun: (row) => handleStart(row), showHide: (row) => row.status == '0' }, |
| | | { name: 'å½å
¥çç¹æ°æ®', clickFun: (row) => openCheckDialog(row), showHide: (row) => row.status == '1' }, |
| | | { name: 'æ¥çå·®å¼', clickFun: (row) => openDiffDialog(row), showHide: (row) => row.status == '2' }, |
| | | // { name: 'çæçäºå', clickFun: (row) => handleGenerateProfitLoss(row), showHide: (row) => row.status == '2' }, |
| | | ] |
| | | } |
| | | ]) |
| | | |
| | | // 产åéæ©è¡¨æ ¼åï¼ç¼è¾ç¨ï¼ |
| | | const productColumn = ref([ |
| | | { label: '产ååç§°', prop: 'productName' }, |
| | | { label: 'è§æ ¼åå·', prop: 'model' }, |
| | | { label: 'åä½', prop: 'unit', align: 'center' } |
| | | ]) |
| | | |
| | | // çç¹æ°æ®å½å
¥è¡¨æ ¼å |
| | | const checkItemColumn = ref([ |
| | | { label: '产ååç§°', prop: 'productName' }, |
| | | { label: 'è§æ ¼åå·', prop: 'model' }, |
| | | { label: 'åä½', prop: 'unit', align: 'center' }, |
| | | { label: 'ç³»ç»åºå', prop: 'systemQty', align: 'center' }, |
| | | { |
| | | label: 'çç¹æ°é', |
| | | prop: 'checkQty', |
| | | align: 'center', |
| | | dataType: 'slot', |
| | | slot: 'checkQty' |
| | | }, |
| | | { |
| | | label: 'å·®å¼', |
| | | align: 'center', |
| | | dataType: 'slot', |
| | | slot: 'diff' |
| | | } |
| | | ]) |
| | | |
| | | // 差弿±æ»è¡¨æ ¼å |
| | | const diffItemColumn = ref([ |
| | | { label: '产ååç§°', prop: 'productName' }, |
| | | { label: 'è§æ ¼åå·', prop: 'model' }, |
| | | { label: 'åä½', prop: 'unit', align: 'center' }, |
| | | { label: 'æ¹å·', prop: 'batchNo', align: 'center' }, |
| | | { label: 'ç³»ç»åºå', prop: 'systemQuantity', align: 'center' }, |
| | | { label: 'çç¹æ°é', prop: 'actualQuantity', align: 'center' }, |
| | | { label: '差弿°é', prop: 'differenceQuantity', align: 'center' }, |
| | | { label: '夿³¨', prop: 'remark' } |
| | | ]) |
| | | |
| | | // æ¥ç详æ
产åè¡¨æ ¼å |
| | | const viewProductColumn = ref([ |
| | | { label: '产ååç§°', prop: 'productName' }, |
| | | { label: 'è§æ ¼åå·', prop: 'model' }, |
| | | { label: 'åä½', prop: 'unit', align: 'center' }, |
| | | { label: 'ç³»ç»åºå', prop: 'systemQuantity', align: 'center' } |
| | | ]) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('') |
| | | const dialogMode = ref('add') |
| | | const submitLoading = ref(false) |
| | | const formRef = ref(null) |
| | | |
| | | // è·åå½åæ¥æ |
| | | const getCurrentDate = () => { |
| | | const now = new Date() |
| | | return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}` |
| | | } |
| | | |
| | | const form = reactive({ |
| | | id: null, |
| | | planNo: '', |
| | | checkerId: null, |
| | | checkerName: '', |
| | | createBy: null, |
| | | createByName: '', |
| | | createTime: getCurrentDate(), |
| | | planDate: '', |
| | | remark: '', |
| | | items: [], |
| | | }) |
| | | |
| | | const userList = ref([]) |
| | | |
| | | const productDialogVisible = ref(false) |
| | | |
| | | const checkDialogVisible = ref(false) |
| | | const checkLoading = ref(false) |
| | | const checkItemList = ref([]) |
| | | const currentPlanId = ref(null) |
| | | |
| | | const diffDialogVisible = ref(false) |
| | | const diffItemList = ref([]) |
| | | const diffSummary = reactive({ totalCount: 0, profitCount: 0, lossCount: 0, normalCount: 0 }) |
| | | |
| | | const viewDialogVisible = ref(false) |
| | | const viewData = reactive({ |
| | | planNo: '', checkerName: '', createByName: '', createTime: '', planDate: '', status: '', remark: '', items: [] |
| | | }) |
| | | |
| | | const rules = { |
| | | createBy: [{ required: true, message: 'è¯·éæ©å¶å人', trigger: 'change' }], |
| | | checkerId: [{ required: true, message: 'è¯·éæ©çç¹äºº', trigger: 'change' }], |
| | | planDate: [{ required: true, message: 'è¯·éæ©è®¡åæ¥æ', trigger: 'change' }], |
| | | } |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true |
| | | getStockCheckPlanPage({ current: page.current, size: page.size, ...searchForm }) |
| | | .then(res => { |
| | | tableData.value = res.data?.records || [] |
| | | page.total = res.data?.total || 0 |
| | | }) |
| | | .finally(() => { tableLoading.value = false }) |
| | | } |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | const resetQuery = () => { |
| | | Object.assign(searchForm, { planNo: '', status: '' }) |
| | | handleQuery() |
| | | } |
| | | |
| | | const paginationChange = ({ page: current, limit }) => { |
| | | page.current = current |
| | | page.size = limit |
| | | getList() |
| | | } |
| | | |
| | | const openForm = (mode) => { |
| | | dialogMode.value = mode |
| | | dialogTitle.value = mode === 'add' ? 'æ°å¢çç¹æ¹æ¡' : 'ç¼è¾çç¹æ¹æ¡' |
| | | if (mode === 'add') { |
| | | Object.assign(form, { |
| | | id: null, |
| | | planNo: '', |
| | | checkerId: null, |
| | | checkerName: '', |
| | | createBy: null, |
| | | createByName: '', |
| | | createTime: getCurrentDate(), |
| | | planDate: '', |
| | | remark: '', |
| | | items: [], |
| | | }) |
| | | } |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | Object.assign(form, { |
| | | id: row.id, |
| | | planNo: row.planNo, |
| | | checkerId: row.checkerId, |
| | | checkerName: row.checkerName, |
| | | createBy: row.createBy, |
| | | createByName: row.createByName, |
| | | createTime: row.createTime, |
| | | planDate: row.planDate, |
| | | remark: row.remark, |
| | | items: row.items || [], |
| | | }) |
| | | dialogMode.value = 'edit' |
| | | dialogTitle.value = 'ç¼è¾çç¹æ¹æ¡' |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value?.validate(valid => { |
| | | if (!valid) return |
| | | if (form.items.length === 0) { |
| | | ElMessage.warning('请è³å°éæ©ä¸ä¸ªçç¹äº§å') |
| | | return |
| | | } |
| | | submitLoading.value = true |
| | | const data = { ...form } |
| | | const action = dialogMode.value === 'add' ? addStockCheckPlan : updateStockCheckPlan |
| | | action(data).then(() => { |
| | | ElMessage.success('ä¿åæå') |
| | | dialogVisible.value = false |
| | | getList() |
| | | }).finally(() => { submitLoading.value = false }) |
| | | }) |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | formRef.value?.resetFields() |
| | | } |
| | | |
| | | const handleView = (row) => { |
| | | Object.assign(viewData, { |
| | | planNo: row.planNo, |
| | | checkerName: row.checkerName, |
| | | createBy: row.createBy, |
| | | createTime: row.createTime, |
| | | planDate: row.planDate, |
| | | status: row.status, |
| | | remark: row.remark, |
| | | items: row.items || [], |
| | | }) |
| | | viewDialogVisible.value = true |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥çç¹è®¡åï¼', 'æç¤º', { type: 'warning' }) |
| | | .then(() => deleteStockCheckPlan([row.id])) |
| | | .then(() => { |
| | | ElMessage.success('å 餿å') |
| | | getList() |
| | | }) |
| | | } |
| | | |
| | | const handleSubmitApproval = (row) => { |
| | | ElMessageBox.confirm('确认æäº¤å®¡æ¹ï¼', 'æç¤º', { type: 'warning' }) |
| | | .then(() => submitApproval(row.id)) |
| | | .then(() => { |
| | | ElMessage.success('æäº¤æå') |
| | | getList() |
| | | }) |
| | | } |
| | | |
| | | const handleStart = (row) => { |
| | | ElMessageBox.confirm('确认å¼å§çç¹ï¼å¼å§åå°éå®ç¸å
³åºå', 'æç¤º', { type: 'warning' }) |
| | | .then(() => startCheck(row.id)) |
| | | .then(() => { |
| | | ElMessage.success('çç¹å·²å¼å§') |
| | | getList() |
| | | }) |
| | | } |
| | | |
| | | const openCheckDialog = (row) => { |
| | | currentPlanId.value = row.id |
| | | checkDialogVisible.value = true |
| | | checkLoading.value = true |
| | | getCheckItems(row.id).then(res => { |
| | | const data = res.data || {} |
| | | // ç´æ¥ä½¿ç¨æ¥å£è¿åç checkItems |
| | | checkItemList.value = (data.checkItems || []).map(item => ({ |
| | | ...item, |
| | | actualQuantity: item.actualQuantity || item.systemQuantity, |
| | | })) |
| | | }).finally(() => { checkLoading.value = false }) |
| | | } |
| | | |
| | | const handleSaveCheckData = () => { |
| | | submitLoading.value = true |
| | | completeCheck({ |
| | | id: currentPlanId.value, |
| | | checkItems: checkItemList.value.map(item => ({ |
| | | id: item.id, |
| | | productModelId: item.productModelId, |
| | | productCode: item.productCode, |
| | | productName: item.productName, |
| | | specification: item.model, |
| | | unit: item.unit, |
| | | batchNo: item.batchNo, |
| | | systemQuantity: item.systemQuantity, |
| | | actualQuantity: item.actualQuantity, |
| | | differenceQuantity: item.actualQuantity - item.systemQuantity, |
| | | remark: item.remark, |
| | | })), |
| | | }).then(() => { |
| | | ElMessage.success('çç¹æ°æ®ä¿åæå') |
| | | checkDialogVisible.value = false |
| | | getList() |
| | | }).finally(() => { submitLoading.value = false }) |
| | | } |
| | | |
| | | const handleComplete = (row) => { |
| | | ElMessageBox.confirm('ç¡®è®¤å®æçç¹ï¼å®æåå°çæå·®å¼æ°æ®', 'æç¤º', { type: 'warning' }) |
| | | .then(() => completeCheck({ id: row.id })) |
| | | .then(() => { |
| | | ElMessage.success('çç¹å·²å®æ') |
| | | getList() |
| | | }) |
| | | } |
| | | |
| | | const openDiffDialog = (row) => { |
| | | currentPlanId.value = row.id |
| | | diffDialogVisible.value = true |
| | | getCheckItems(row.id).then(res => { |
| | | const data = res.data || {} |
| | | // ç´æ¥ä½¿ç¨æ¥å£è¿åç checkItems |
| | | diffItemList.value = data.checkItems || [] |
| | | // è®¡ç®æ±æ»æ°æ® |
| | | const items = diffItemList.value |
| | | const profitCount = items.filter(i => i.differenceQuantity > 0).length |
| | | const lossCount = items.filter(i => i.differenceQuantity < 0).length |
| | | const normalCount = items.filter(i => i.differenceQuantity === 0).length |
| | | Object.assign(diffSummary, { |
| | | totalCount: items.length, |
| | | profitCount, |
| | | lossCount, |
| | | normalCount, |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | const handleGenerateProfitLoss = (row) => { |
| | | ElMessageBox.confirm('ç¡®è®¤æ ¹æ®çç¹å·®å¼çæçäºå¤çåï¼', 'æç¤º', { type: 'warning' }) |
| | | .then(() => generateProfitLoss(row.id)) |
| | | .then(() => { |
| | | ElMessage.success('çäºå¤çåå·²çæ') |
| | | getList() |
| | | }) |
| | | } |
| | | |
| | | const handleGenerateFromDiff = () => { |
| | | handleGenerateProfitLoss({ id: currentPlanId.value }) |
| | | diffDialogVisible.value = false |
| | | } |
| | | |
| | | // 产åéæ©ç¸å
³ |
| | | const openProductDialog = () => { |
| | | productDialogVisible.value = true |
| | | } |
| | | |
| | | const handleProductSelect = (selectedRows) => { |
| | | // æé¤å·²åå¨ç产å |
| | | const existingIds = form.items.map(item => item.productModelId) |
| | | const newItems = selectedRows |
| | | .filter(p => !existingIds.includes(p.productModelId || p.id)) |
| | | .map(p => ({ |
| | | productModelId: p.productModelId || p.id, |
| | | productName: p.productName, |
| | | model: p.model, |
| | | unit: p.unit, |
| | | systemQuantity: p.systemQuantity || p.systemQty || 0, |
| | | })) |
| | | if (newItems.length === 0) { |
| | | ElMessage.warning('æé产åå·²åå¨') |
| | | return |
| | | } |
| | | form.items.push(...newItems) |
| | | ElMessage.success(`已添å ${newItems.length} 个产å`) |
| | | } |
| | | |
| | | const clearProducts = () => { |
| | | if (form.items.length === 0) return |
| | | ElMessageBox.confirm('确认æ¸
空ææäº§åï¼', 'æç¤º', { type: 'warning' }) |
| | | .then(() => { |
| | | form.items = [] |
| | | ElMessage.success('å·²æ¸
空') |
| | | }) |
| | | } |
| | | |
| | | const removeProduct = (index) => { |
| | | form.items.splice(index, 1) |
| | | } |
| | | |
| | | const getStatusText = (status) => ({ |
| | | '0': 'å¾
æ§è¡', '1': 'æ§è¡ä¸', '2': '已宿', '3': '已忶', |
| | | }[status] || status) |
| | | |
| | | const getStatusType = (status) => ({ |
| | | '0': 'info', '1': 'primary', '2': 'success', '3': 'warning', |
| | | }[status] || 'info') |
| | | |
| | | // å è½½ç¨æ·å表 |
| | | const loadUserList = async () => { |
| | | try { |
| | | const res = await userListNoPage() |
| | | userList.value = res?.data || [] |
| | | } catch (err) { |
| | | console.error('è·åç¨æ·å表失败:', err) |
| | | userList.value = [] |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | loadUserList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .diff-summary { |
| | | margin-bottom: 20px; |
| | | padding: 15px; |
| | | background: #f5f7fa; |
| | | border-radius: 4px; |
| | | .stat-item { |
| | | text-align: center; |
| | | .stat-label { |
| | | color: #606266; |
| | | font-size: 13px; |
| | | margin-bottom: 8px; |
| | | } |
| | | .stat-value { |
| | | font-size: 24px; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | } |
| | | } |
| | | .text-profit { color: #67c23a; font-weight: bold; } |
| | | .text-loss { color: #f56c6c; font-weight: bold; } |
| | | .mb10 { margin-bottom: 10px; } |
| | | .section-title { |
| | | font-weight: bold; |
| | | margin-bottom: 10px; |
| | | padding-left: 10px; |
| | | border-left: 4px solid #409eff; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form" style="margin-bottom: 20px;"> |
| | | <div> |
| | | <span class="search_title">åå·ï¼</span> |
| | | <el-input v-model="searchForm.orderNo" placeholder="请è¾å
¥" clearable style="width: 240px; margin-right: 10px" @change="handleQuery" /> |
| | | <span class="search_title">ç±»åï¼</span> |
| | | <el-select v-model="searchForm.type" placeholder="è¯·éæ©" clearable style="width: 240px" @change="handleQuery"> |
| | | <el-option label="çç" value="profit" /> |
| | | <el-option label="çäº" value="loss" /> |
| | | </el-select> |
| | | <span class="search_title" style="margin-left: 10px;">ç¶æï¼</span> |
| | | <el-select v-model="searchForm.status" placeholder="è¯·éæ©" clearable style="width: 240px" @change="handleQuery"> |
| | | <el-option label="è稿" value="draft" /> |
| | | <el-option label="审æ¹ä¸" value="approving" /> |
| | | <el-option label="å¾
æ§è¡" value="pending" /> |
| | | <el-option label="å·²æ§è¡" value="executed" /> |
| | | </el-select> |
| | | <span class="search_title" style="margin-left: 10px;">æ¥æºï¼</span> |
| | | <el-select v-model="searchForm.source" placeholder="è¯·éæ©" clearable style="width: 240px" @change="handleQuery"> |
| | | <el-option label="çç¹çæ" value="check" /> |
| | | <el-option label="æå¨å建" value="manual" /> |
| | | </el-select> |
| | | <el-button type="primary" @click="handleQuery" style="margin-left: 10px">æç´¢</el-button> |
| | | </div> |
| | | <div> |
| | | <el-button type="primary" @click="openForm('add')">æ°å»ºçäºå</el-button> |
| | | </div> |
| | | </div> |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :page="page" |
| | | :isShowPagination="true" |
| | | @pagination="paginationChange" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- æ°å¢/ç¼è¾çäºå --> |
| | | <FormDialog |
| | | v-model="dialogVisible" |
| | | :title="dialogTitle" |
| | | width="1000px" |
| | | @close="resetForm" |
| | | @confirm="handleSubmit" |
| | | > |
| | | <el-form ref="formRef" :model="form" :rules="rules" label-width="100px"> |
| | | <el-row> |
| | | <el-col :span="12"> |
| | | <el-form-item label="çäºç±»å" prop="type"> |
| | | <el-radio-group v-model="form.type" :disabled="form.source === 'check'"> |
| | | <el-radio-button label="profit">çç</el-radio-button> |
| | | <el-radio-button label="loss">çäº</el-radio-button> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="ä»åº" prop="warehouseId"> |
| | | <el-select v-model="form.warehouseId" placeholder="è¯·éæ©ä»åº" style="width: 100%" filterable :disabled="form.source === 'check'"> |
| | | <el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row v-if="form.source === 'check'"> |
| | | <el-col :span="24"> |
| | | <el-form-item label="å
³èçç¹è®¡å"> |
| | | <el-input v-model="form.checkPlanNo" disabled /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row> |
| | | <el-col :span="24"> |
| | | <el-form-item label="夿³¨"> |
| | | <el-input v-model="form.remark" type="textarea" :rows="2" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <el-divider content-position="left">æç»ä¿¡æ¯</el-divider> |
| | | <div class="mb10" v-if="form.source === 'manual'"> |
| | | <el-button type="primary" size="small" @click="openSelectProduct">æ·»å 产å</el-button> |
| | | </div> |
| | | <PIMTable |
| | | :column="itemColumn" |
| | | :tableData="form.items" |
| | | :isShowPagination="false" |
| | | height="300px" |
| | | /> |
| | | <div class="total-row" style="margin-top: 10px; text-align: right;"> |
| | | <span style="font-weight: bold;">æ»å·®å¼éé¢ï¼</span> |
| | | <span :class="getDiffClass(totalDiffAmount)" style="font-size: 18px; font-weight: bold;"> |
| | | {{ totalDiffAmount > 0 ? '+' : '' }}Â¥{{ totalDiffAmount.toFixed(2) }} |
| | | </span> |
| | | </div> |
| | | </el-form> |
| | | </FormDialog> |
| | | |
| | | <!-- æ¥ç详æ
--> |
| | | <FormDialog |
| | | v-model="viewDialogVisible" |
| | | title="çäºå详æ
" |
| | | width="950px" |
| | | operationType="detail" |
| | | @close="viewDialogVisible = false" |
| | | > |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="åå·">{{ viewData.orderNo }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç±»å"> |
| | | <el-tag :type="viewData.type === 'profit' ? 'success' : 'danger'"> |
| | | {{ viewData.type === 'profit' ? 'çç' : 'çäº' }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="ä»åº">{{ viewData.warehouseName }}</el-descriptions-item> |
| | | <el-descriptions-item label="ç¶æ"> |
| | | <el-tag :type="getStatusType(viewData.status)">{{ getStatusText(viewData.status) }}</el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="æ¥æº"> |
| | | <el-tag :type="viewData.source === 'check' ? 'primary' : 'info'" size="small"> |
| | | {{ viewData.source === 'check' ? 'çç¹çæ' : 'æå¨å建' }} |
| | | </el-tag> |
| | | </el-descriptions-item> |
| | | <el-descriptions-item label="å
³èçç¹è®¡å">{{ viewData.checkPlanNo || '-' }}</el-descriptions-item> |
| | | <el-descriptions-item label="å建人">{{ viewData.createByName }}</el-descriptions-item> |
| | | <el-descriptions-item label="å建æ¶é´">{{ viewData.createTime }}</el-descriptions-item> |
| | | <el-descriptions-item label="夿³¨" :span="2">{{ viewData.remark || '-' }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <div style="margin-top: 20px;"> |
| | | <div class="section-title">æç»ä¿¡æ¯</div> |
| | | <PIMTable |
| | | :column="viewItemColumn" |
| | | :tableData="viewData.items" |
| | | :isShowPagination="false" |
| | | height="300px" |
| | | /> |
| | | <div class="total-row" style="margin-top: 10px; text-align: right;"> |
| | | <span style="font-weight: bold;">æ»å·®å¼éé¢ï¼</span> |
| | | <span :class="getDiffClass(viewData.totalAmount)" style="font-size: 18px; font-weight: bold;"> |
| | | {{ viewData.totalAmount > 0 ? '+' : '' }}Â¥{{ viewData.totalAmount?.toFixed(2) }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | <template #footer> |
| | | <div class="dialog-footer"> |
| | | <el-button @click="viewDialogVisible = false">å
³é</el-button> |
| | | </div> |
| | | </template> |
| | | </FormDialog> |
| | | |
| | | <!-- éæ©ååå¼¹æ¡ --> |
| | | <FormDialog |
| | | v-model="productDialogVisible" |
| | | title="éæ©äº§å" |
| | | width="850px" |
| | | @close="productDialogVisible = false" |
| | | @confirm="confirmSelectProduct" |
| | | > |
| | | <el-form :model="productSearchForm" inline> |
| | | <el-form-item label="产ååç§°"> |
| | | <el-input v-model="productSearchForm.productName" placeholder="请è¾å
¥" clearable style="width: 180px" /> |
| | | </el-form-item> |
| | | <el-form-item label="è§æ ¼åå·"> |
| | | <el-input v-model="productSearchForm.model" placeholder="请è¾å
¥" clearable style="width: 150px" /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="handleProductSearch">æç´¢</el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="productColumn" |
| | | :tableData="productList" |
| | | :isSelection="true" |
| | | :isShowPagination="false" |
| | | height="350px" |
| | | @selection-change="handleProductSelectionChange" |
| | | /> |
| | | </FormDialog> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import PIMTable from '@/components/PIMTable/PIMTable.vue' |
| | | import FormDialog from '@/components/Dialog/FormDialog.vue' |
| | | import { |
| | | getStockProfitLossPage, |
| | | getStockProfitLossDetail, |
| | | addStockProfitLoss, |
| | | updateStockProfitLoss, |
| | | deleteStockProfitLoss, |
| | | submitApproval, |
| | | executeProfitLoss, |
| | | } from '@/api/inventoryManagement/stockProfitLoss.js' |
| | | |
| | | const tableData = ref([]) |
| | | const tableLoading = ref(false) |
| | | const page = reactive({ current: 1, size: 20, total: 0 }) |
| | | const searchForm = reactive({ orderNo: '', type: '', status: '', source: '' }) |
| | | |
| | | // ä¸»è¡¨æ ¼åé
ç½® |
| | | const tableColumn = ref([ |
| | | { label: 'åå·', prop: 'orderNo' }, |
| | | { |
| | | label: 'ç±»å', |
| | | prop: 'type', |
| | | align: 'center', |
| | | dataType: 'tag', |
| | | formatData: (v) => v === 'profit' ? 'çç' : 'çäº', |
| | | formatType: (v) => v === 'profit' ? 'success' : 'danger' |
| | | }, |
| | | { label: 'ä»åº', prop: 'warehouseName' }, |
| | | { label: 'ååæ°é', prop: 'productCount', align: 'center' }, |
| | | { |
| | | label: 'éé¢', |
| | | prop: 'totalAmount', |
| | | align: 'right', |
| | | formatData: (v) => `Â¥${v?.toFixed(2) || '0.00'}` |
| | | }, |
| | | { |
| | | label: 'æ¥æº', |
| | | prop: 'source', |
| | | align: 'center', |
| | | dataType: 'tag', |
| | | formatData: (v) => v === 'check' ? 'çç¹çæ' : 'æå¨å建', |
| | | formatType: (v) => v === 'check' ? 'primary' : 'info' |
| | | }, |
| | | { |
| | | label: 'ç¶æ', |
| | | prop: 'status', |
| | | align: 'center', |
| | | dataType: 'tag', |
| | | formatData: (v) => ({ |
| | | draft: 'è稿', approving: '审æ¹ä¸', pending: 'å¾
æ§è¡', executed: 'å·²æ§è¡' |
| | | }[v] || v), |
| | | formatType: (v) => ({ |
| | | draft: 'info', approving: 'warning', pending: 'primary', executed: 'success' |
| | | }[v] || '') |
| | | }, |
| | | { label: 'å建æ¶é´', prop: 'createTime', align: 'center' }, |
| | | { |
| | | label: 'æä½', |
| | | dataType: 'action', |
| | | fixed: 'right', |
| | | align: 'center', |
| | | operation: [ |
| | | { name: 'æ¥ç', clickFun: (row) => handleView(row) }, |
| | | { name: 'ç¼è¾', clickFun: (row) => handleEdit(row), showHide: (row) => row.status === 'draft' && row.source === 'manual' }, |
| | | { name: 'æäº¤å®¡æ¹', clickFun: (row) => handleSubmitApproval(row), showHide: (row) => row.status === 'draft' && row.source === 'manual' }, |
| | | { name: 'å é¤', clickFun: (row) => handleDelete(row), showHide: (row) => row.status === 'draft' && row.source === 'manual' }, |
| | | { name: 'æ§è¡', clickFun: (row) => handleExecute(row), showHide: (row) => row.status === 'pending' }, |
| | | ] |
| | | } |
| | | ]) |
| | | |
| | | // æç»è¡¨æ ¼åé
ç½®ï¼ç¼è¾ç¨ï¼ |
| | | const itemColumn = ref([ |
| | | { label: '产ååç§°', prop: 'productName' }, |
| | | { label: 'è§æ ¼åå·', prop: 'model' }, |
| | | { label: 'åä½', prop: 'unit', align: 'center' }, |
| | | { label: 'ç³»ç»åºå', prop: 'systemQty', align: 'center' }, |
| | | { |
| | | label: 'å®é
æ°é', |
| | | align: 'center', |
| | | dataType: 'slot', |
| | | slot: 'actualQty' |
| | | }, |
| | | { |
| | | label: '差弿°é', |
| | | align: 'center', |
| | | dataType: 'slot', |
| | | slot: 'diffQty' |
| | | }, |
| | | { |
| | | label: 'ææ¬åä»·', |
| | | align: 'center', |
| | | dataType: 'slot', |
| | | slot: 'costPrice' |
| | | }, |
| | | { |
| | | label: 'å·®å¼éé¢', |
| | | align: 'center', |
| | | dataType: 'slot', |
| | | slot: 'diffAmount' |
| | | }, |
| | | { |
| | | label: 'æä½', |
| | | align: 'center', |
| | | dataType: 'slot', |
| | | slot: 'action' |
| | | } |
| | | ]) |
| | | |
| | | // æ¥çæç»è¡¨æ ¼åé
ç½® |
| | | const viewItemColumn = ref([ |
| | | { label: '产ååç§°', prop: 'productName' }, |
| | | { label: 'è§æ ¼åå·', prop: 'model' }, |
| | | { label: 'åä½', prop: 'unit', align: 'center' }, |
| | | { label: 'ç³»ç»åºå', prop: 'systemQty', align: 'center' }, |
| | | { label: 'å®é
æ°é', prop: 'actualQty', align: 'center' }, |
| | | { |
| | | label: '差弿°é', |
| | | align: 'center', |
| | | dataType: 'slot', |
| | | slot: 'viewDiffQty' |
| | | }, |
| | | { |
| | | label: 'ææ¬åä»·', |
| | | align: 'right', |
| | | formatData: (v) => `Â¥${v?.toFixed(2) || '0.00'}` |
| | | }, |
| | | { |
| | | label: 'å·®å¼éé¢', |
| | | align: 'right', |
| | | dataType: 'slot', |
| | | slot: 'viewDiffAmount' |
| | | } |
| | | ]) |
| | | |
| | | // 产åéæ©è¡¨æ ¼åé
ç½® |
| | | const productColumn = ref([ |
| | | { label: '产ååç§°', prop: 'productName' }, |
| | | { label: 'è§æ ¼åå·', prop: 'model' }, |
| | | { label: 'åä½', prop: 'unit', align: 'center' }, |
| | | { label: 'å½ååºå', prop: 'currentQty', align: 'center' } |
| | | ]) |
| | | |
| | | const dialogVisible = ref(false) |
| | | const dialogTitle = ref('') |
| | | const dialogMode = ref('add') |
| | | const submitLoading = ref(false) |
| | | const formRef = ref(null) |
| | | const form = reactive({ |
| | | id: null, |
| | | type: 'profit', |
| | | warehouseId: null, |
| | | remark: '', |
| | | source: 'manual', |
| | | checkPlanNo: '', |
| | | items: [], |
| | | }) |
| | | |
| | | const warehouseList = ref([]) |
| | | const productList = ref([]) |
| | | const selectedProducts = ref([]) |
| | | const productDialogVisible = ref(false) |
| | | const productSearchForm = reactive({ productName: '', model: '' }) |
| | | |
| | | const viewDialogVisible = ref(false) |
| | | const viewData = reactive({ |
| | | orderNo: '', type: '', warehouseName: '', status: '', source: '', |
| | | checkPlanNo: '', createByName: '', createTime: '', remark: '', |
| | | items: [], totalAmount: 0, |
| | | }) |
| | | |
| | | const rules = { |
| | | type: [{ required: true, message: 'è¯·éæ©çäºç±»å', trigger: 'change' }], |
| | | warehouseId: [{ required: true, message: 'è¯·éæ©ä»åº', trigger: 'change' }], |
| | | } |
| | | |
| | | const totalDiffAmount = computed(() => { |
| | | return form.items.reduce((sum, item) => sum + (item.actualQty - item.systemQty) * (item.costPrice || 0), 0) |
| | | }) |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true |
| | | getStockProfitLossPage({ current: page.current, size: page.size, ...searchForm }) |
| | | .then(res => { |
| | | tableData.value = res.data?.records || [] |
| | | page.total = res.data?.total || 0 |
| | | }) |
| | | .finally(() => { tableLoading.value = false }) |
| | | } |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | const resetQuery = () => { |
| | | Object.assign(searchForm, { orderNo: '', type: '', status: '', source: '' }) |
| | | handleQuery() |
| | | } |
| | | |
| | | const paginationChange = ({ page: current, limit }) => { |
| | | page.current = current |
| | | page.size = limit |
| | | getList() |
| | | } |
| | | |
| | | const openForm = (mode) => { |
| | | dialogMode.value = mode |
| | | dialogTitle.value = mode === 'add' ? 'æ°å»ºçäºå' : 'ç¼è¾çäºå' |
| | | if (mode === 'add') { |
| | | Object.assign(form, { |
| | | id: null, type: 'profit', warehouseId: null, remark: '', |
| | | source: 'manual', checkPlanNo: '', items: [], |
| | | }) |
| | | } |
| | | dialogVisible.value = true |
| | | } |
| | | |
| | | const handleEdit = (row) => { |
| | | getStockProfitLossDetail(row.id).then(res => { |
| | | const data = res.data |
| | | Object.assign(form, { |
| | | id: data.id, |
| | | type: data.type, |
| | | warehouseId: data.warehouseId, |
| | | remark: data.remark, |
| | | source: data.source, |
| | | checkPlanNo: data.checkPlanNo, |
| | | items: data.items || [], |
| | | }) |
| | | dialogMode.value = 'edit' |
| | | dialogTitle.value = 'ç¼è¾çäºå' |
| | | dialogVisible.value = true |
| | | }) |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | formRef.value?.validate(valid => { |
| | | if (!valid) return |
| | | if (form.items.length === 0) { |
| | | ElMessage.warning('请添å è³å°ä¸ä¸ªäº§å') |
| | | return |
| | | } |
| | | submitLoading.value = true |
| | | const data = { ...form } |
| | | const action = dialogMode.value === 'add' ? addStockProfitLoss : updateStockProfitLoss |
| | | action(data).then(() => { |
| | | ElMessage.success('ä¿åæå') |
| | | dialogVisible.value = false |
| | | getList() |
| | | }).finally(() => { submitLoading.value = false }) |
| | | }) |
| | | } |
| | | |
| | | const resetForm = () => { |
| | | formRef.value?.resetFields() |
| | | } |
| | | |
| | | const handleView = (row) => { |
| | | getStockProfitLossDetail(row.id).then(res => { |
| | | const data = res.data || {} |
| | | Object.assign(viewData, { |
| | | orderNo: data.orderNo, |
| | | type: data.type, |
| | | warehouseName: data.warehouseName, |
| | | status: data.status, |
| | | source: data.source, |
| | | checkPlanNo: data.checkPlanNo, |
| | | createByName: data.createByName, |
| | | createTime: data.createTime, |
| | | remark: data.remark, |
| | | items: data.items || [], |
| | | totalAmount: data.totalAmount || 0, |
| | | }) |
| | | viewDialogVisible.value = true |
| | | }) |
| | | } |
| | | |
| | | const handleDelete = (row) => { |
| | | ElMessageBox.confirm('确认å é¤è¯¥çäºåï¼', 'æç¤º', { type: 'warning' }) |
| | | .then(() => deleteStockProfitLoss(row.id)) |
| | | .then(() => { |
| | | ElMessage.success('å 餿å') |
| | | getList() |
| | | }) |
| | | } |
| | | |
| | | const handleSubmitApproval = (row) => { |
| | | ElMessageBox.confirm('确认æäº¤å®¡æ¹ï¼', 'æç¤º', { type: 'warning' }) |
| | | .then(() => submitApproval(row.id)) |
| | | .then(() => { |
| | | ElMessage.success('æäº¤æå') |
| | | getList() |
| | | }) |
| | | } |
| | | |
| | | const handleExecute = (row) => { |
| | | const actionText = row.type === 'profit' ? 'ççå
¥åº' : 'çäºåºåº' |
| | | ElMessageBox.confirm(`确认æ§è¡${actionText}ï¼æ§è¡åå°æ´æ°åºåæ°é`, 'æç¤º', { type: 'warning' }) |
| | | .then(() => executeProfitLoss(row.id)) |
| | | .then(() => { |
| | | ElMessage.success('æ§è¡æå') |
| | | getList() |
| | | }) |
| | | } |
| | | |
| | | const openSelectProduct = () => { |
| | | productDialogVisible.value = true |
| | | handleProductSearch() |
| | | } |
| | | |
| | | const handleProductSearch = () => { |
| | | // è°ç¨æ¥å£æ¥è¯¢åååºå |
| | | } |
| | | |
| | | const handleProductSelectionChange = (selection) => { |
| | | selectedProducts.value = selection |
| | | } |
| | | |
| | | const confirmSelectProduct = () => { |
| | | if (selectedProducts.value.length === 0) { |
| | | ElMessage.warning('è¯·éæ©äº§å') |
| | | return |
| | | } |
| | | const newItems = selectedProducts.value.map(p => ({ |
| | | productId: p.id, |
| | | productName: p.productName, |
| | | model: p.model, |
| | | unit: p.unit, |
| | | batchNo: p.batchNo || '', |
| | | systemQty: p.currentQty || 0, |
| | | actualQty: p.currentQty || 0, |
| | | costPrice: p.costPrice || 0, |
| | | })) |
| | | form.items.push(...newItems) |
| | | productDialogVisible.value = false |
| | | } |
| | | |
| | | const removeItem = (index) => { |
| | | form.items.splice(index, 1) |
| | | } |
| | | |
| | | const getStatusText = (status) => ({ |
| | | draft: 'è稿', approving: '审æ¹ä¸', pending: 'å¾
æ§è¡', executed: 'å·²æ§è¡', |
| | | }[status] || status) |
| | | |
| | | const getStatusType = (status) => ({ |
| | | draft: 'info', approving: 'warning', pending: 'primary', executed: 'success', |
| | | }[status] || '') |
| | | |
| | | const getDiffClass = (val) => val > 0 ? 'text-profit' : val < 0 ? 'text-loss' : '' |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .search_form { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: flex-start; |
| | | margin-bottom: 16px; |
| | | } |
| | | .dialog-footer { |
| | | display: flex; |
| | | justify-content: center; |
| | | gap: 10px; |
| | | } |
| | | .section-title { |
| | | font-weight: bold; |
| | | margin-bottom: 10px; |
| | | padding-left: 10px; |
| | | border-left: 4px solid #409eff; |
| | | } |
| | | .text-profit { color: #67c23a; font-weight: bold; } |
| | | .text-loss { color: #f56c6c; font-weight: bold; } |
| | | </style> |