huminmin
9 小时以前 4619d7c1944afbc85eb680167ca188fccc822259
根据产品大类联合查询库存信息
已添加1个文件
已修改6个文件
417 ■■■■ 文件已修改
src/api/inventoryManagement/stockInventory.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/basicData/product/ProductSelectDialog.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/FrozenAndThaw.vue 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/New.vue 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Record.vue 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/Subtract.vue 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockManagement/index.vue 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockInventory.js
@@ -8,6 +8,15 @@
    });
};
// åˆ†é¡µæŸ¥è¯¢è”合库存记录列表(包含商品信息)
export const getStockInventoryListPageCombined = (params) => {
    return request({
        url: "/stockInventory/pageListCombinedStockInventory",
        method: "get",
        params,
    });
};
// åˆ›å»ºåº“存记录
export const createStockInventory = (params) => {
    return request({
src/views/basicData/product/ProductSelectDialog.vue
@@ -55,6 +55,7 @@
const props = defineProps<{
  modelValue: boolean;
  single?: boolean; // æ˜¯å¦åªèƒ½é€‰æ‹©ä¸€ä¸ªï¼Œé»˜è®¤false(可选择多个)
  topProductParentId?: number; // ä¸€çº§äº§å“id
}>();
const emit = defineEmits(['update:modelValue', 'confirm']);
@@ -159,6 +160,7 @@
      model: query.model.trim(),
      current: page.pageNum,
      size: page.pageSize,
      topProductParentId: props.topProductParentId,
    });
    tableData.value = res.records;
    total.value = res.total;
src/views/inventoryManagement/stockManagement/FrozenAndThaw.vue
@@ -8,10 +8,27 @@
    >
      <el-form label-width="140px" :model="formState" ref="formRef">
        <el-form-item
            label="库存类型"
            prop="type"
            :rules="[
                {
                required: true,
                message: '请选择库存类型',
                trigger: 'change',
              }
            ]"
        >
          <el-select v-model="formState.type" placeholder="请选择库存类型" @change="handleChangeType">
            <el-option label="合格库存" value="qualified" :disabled="(operationType === 'frozen' && props.record.qualifiedUnLockedQuantity <= 0) || (operationType === 'thaw' && props.record.qualifiedLockedQuantity <= 0)" />
            <el-option label="不合格库存" value="unqualified" :disabled="(operationType === 'frozen' && props.record.unQualifiedUnLockedQuantity <= 0) || (operationType === 'thaw' && props.record.unQualifiedLockedQuantity <= 0)" />
          </el-select>
        </el-form-item>
        <el-form-item
            :label="operationType === 'frozen' ? '冻结数量:' : '解冻数量:'"
            prop="lockedQuantity"
        >
          <el-input-number v-model="formState.lockedQuantity" :step="1" :min="1" precision="0" style="width: 100%" :max="maxCount" />
          <el-input-number v-model="formState.lockedQuantity" :step="1" :min="maxCount > 0 ? 1 : 0" precision="0" style="width: 100%" :max="maxCount" :disabled="maxCount < 1" />
        </el-form-item>
      </el-form>
@@ -26,7 +43,7 @@
</template>
<script setup>
import {ref, computed, getCurrentInstance} from "vue";
import {ref, computed, getCurrentInstance, onMounted} from "vue";
import {frozenStockInventory, thawStockInventory} from "@/api/inventoryManagement/stockInventory.js";
import {frozenStockUninventory, thawStockUninventory} from "@/api/inventoryManagement/stockUninventory.js";
@@ -42,12 +59,6 @@
    default: 'frozen',
  },
  type: {
    type: String,
    required: true,
    default: 'qualified',
  },
  record: {
    type: Object,
    default: () => {},
@@ -58,7 +69,8 @@
// å“åº”式数据(替代选项式的 data)
const formState = ref({
  lockedQuantity: 0,
  type: undefined,
  lockedQuantity: undefined,
});
const isShow = computed({
@@ -76,7 +88,8 @@
const closeModal = () => {
  // é‡ç½®è¡¨å•数据
  formState.value = {
    lockedQuantity: undefined
    lockedQuantity: undefined,
    type: undefined,
  };
  isShow.value = false;
};
@@ -84,17 +97,32 @@
const maxCount = computed(() => {
  // å†»ç»“库存最大数量为未解冻数量
  if (props.operationType === 'frozen') {
    return props.record.unLockedQuantity
    // å†»ç»“合格库存最大数量为未解冻合格数量
    if (formState.value.type === 'qualified') {
      return Math.max(0, props.record.qualifiedUnLockedQuantity || 0)
    }
    // å†»ç»“不合格库存最大数量为未解冻不合格数量
    return Math.max(0, props.record.unQualifiedUnLockedQuantity || 0)
  }
  // è§£å†»åº“存最大数量为已冻结数量
  return props.record.lockedQuantity
  if (formState.value.type === 'qualified') {
    // è§£å†»åˆæ ¼åº“存最大数量为已冻结合格数量
    return Math.max(0, props.record.qualifiedLockedQuantity || 0)
  }
  // è§£å†»ä¸åˆæ ¼åº“存最大数量为已冻结不合格数量
  return Math.max(0, props.record.unQualifiedLockedQuantity || 0)
})
const handleChangeType = (type) => {
  formState.value.lockedQuantity = maxCount.value;
}
const handleSubmit = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (valid) {
      const data = Object.assign({id: props.record.id}, formState.value);
      if (props.type === 'qualified') {
      const data = Object.assign({}, formState.value);
      if (formState.value.type === 'qualified') {
        data.id = props.record.qualifiedId;
        // å†»ç»“
        if (props.operationType === 'frozen') {
          frozenStockInventory(data).then(res => {
@@ -122,6 +150,7 @@
          })
        }
      } else {
        data.id = props.record.unQualifiedId;
        if (props.operationType === 'frozen') {
          frozenStockUninventory(data).then(res => {
            if (res.code === 200) {
@@ -153,7 +182,6 @@
};
onMounted(() => {
  formState.value.lockedQuantity = maxCount.value;
})
defineExpose({
src/views/inventoryManagement/stockManagement/New.vue
@@ -38,6 +38,23 @@
        </el-form-item>
        <el-form-item
            label="库存类型"
            prop="type"
            :rules="[
                {
                required: true,
                message: '请选择库存类型',
                trigger: 'change',
              }
            ]"
        >
          <el-select v-model="formState.type" placeholder="请选择库存类型">
            <el-option label="合格库存" value="qualified" />
            <el-option label="不合格库存" value="unqualified" />
          </el-select>
        </el-form-item>
        <el-form-item
            label="库存数量"
            prop="qualitity"
        >
@@ -45,7 +62,7 @@
        </el-form-item>
        <el-form-item
            v-if="type === 'qualified'"
            v-if="formState.type === 'qualified'"
            label="库存预警数量"
            prop="warnNum"
        >
@@ -61,6 +78,7 @@
      <ProductSelectDialog
          v-model="showProductSelectDialog"
          @confirm="handleProductSelect"
          :top-product-parent-id="props.topProductParentId"
          single
      />
      <template #footer>
@@ -84,12 +102,11 @@
    type: Boolean,
    required: true,
  },
  type: {
    type: String,
    required: true,
    default: 'qualified',
  },
  topProductParentId: {
    type: Number,
    default: undefined,
    required: false,
  }
});
const emit = defineEmits(['update:visible', 'completed']);
@@ -101,6 +118,7 @@
  productName: "",
  productModelName: "",
  unit: "",
  type: undefined,
  qualitity: 0,
  warnNum: 0,
  remark: '',
@@ -158,7 +176,7 @@
        proxy.$modal.msgError("请选择规格");
        return;
      }
      if (props.type === 'qualified') {
      if (formState.value.type === 'qualified') {
        createStockInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
@@ -167,6 +185,7 @@
          proxy.$modal.msgSuccess("提交成功");
        })
      } else {
        formState.value.warnNum = 0;
        createStockUnInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
src/views/inventoryManagement/stockManagement/Record.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,222 @@
<template>
  <div class="app-container">
    <div class="search_form">
      <div>
        <span class="search_title ml10">产品大类:</span>
        <el-input v-model="searchForm.productName"
                  style="width: 240px"
                  placeholder="请输入"
                  clearable/>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px">搜索</el-button>
      </div>
      <div>
         <el-button type="primary" @click="isShowNewModal = true">新增库存</el-button>
        <el-button type="info" plain icon="Upload" @click="isShowImportModal = true">
          å¯¼å…¥åº“å­˜
        </el-button>
        <el-button @click="handleOut">导出</el-button>
      </div>
    </div>
    <div class="table_list">
      <el-table :data="tableData" border v-loading="tableLoading" @selection-change="handleSelectionChange"
        :expand-row-keys="expandedRowKeys" :row-key="(row, index) => index" style="width: 100%"
        :row-class-name="tableRowClassName" height="calc(100vh - 18.5em)">
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <el-table-column label="产品大类" prop="productName" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="model" show-overflow-tooltip />
        <el-table-column label="单位" prop="unit" show-overflow-tooltip />
        <el-table-column label="合格库存数量" prop="qualifiedQuantity" show-overflow-tooltip />
        <el-table-column label="不合格库存数量" prop="unQualifiedQuantity" show-overflow-tooltip />
        <el-table-column label="合格冻结数量" prop="qualifiedLockedQuantity" show-overflow-tooltip />
        <el-table-column label="不合格冻结数量" prop="unQualifiedLockedQuantity" show-overflow-tooltip />
        <el-table-column label="库存预警数量" prop="warnNum"  show-overflow-tooltip />
        <el-table-column label="备注" prop="remark"  show-overflow-tooltip />
        <el-table-column label="最近更新时间" prop="updateTime" show-overflow-tooltip />
        <el-table-column fixed="right" label="操作" min-width="90" align="center">
          <template #default="scope">
            <el-button link type="primary" @click="showSubtractModal(scope.row)" :disabled="scope.row.unQualifiedUnLockedQuantity === 0 && scope.row.qualifiedUnLockedQuantity === 0">领用</el-button>
            <el-button link type="primary" v-if="scope.row.unQualifiedUnLockedQuantity > 0 || scope.row.qualifiedUnLockedQuantity > 0" @click="showFrozenModal(scope.row)">冻结</el-button>
            <el-button link type="primary" v-if="scope.row.qualifiedLockedQuantity > 0 || scope.row.unQualifiedLockedQuantity > 0" @click="showThawModal(scope.row)">解冻</el-button>
          </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>
    <new-stock-inventory v-if="isShowNewModal"
                 v-model:visible="isShowNewModal"
                 :top-product-parent-id="props.productId"
                 @completed="handleQuery" />
    <subtract-stock-inventory v-if="isShowSubtractModal"
                 v-model:visible="isShowSubtractModal"
                 :record="record"
                 :type="record.stockType"
                 @completed="handleQuery" />
    <!-- å¯¼å…¥åº“å­˜-->
    <import-stock-inventory v-if="isShowImportModal"
                 v-model:visible="isShowImportModal"
                 type="qualified"
                 @uploadSuccess="handleQuery" />
    <!-- å†»ç»“/解冻库存-->
    <frozen-and-thaw-stock-inventory v-if="isShowFrozenAndThawModal"
                 v-model:visible="isShowFrozenAndThawModal"
                 :record="record"
                 :operation-type="operationType"
                 :type="record.stockType"
                 @completed="handleQuery" />
  </div>
</template>
<script setup>
import pagination from '@/components/PIMTable/Pagination.vue'
import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue'
import {ElMessage, ElMessageBox} from "element-plus";
import {
  getStockInventoryListPageCombined
} from "@/api/inventoryManagement/stockInventory.js";
const props = defineProps({
  productId: {
    type: Number,
    required: true,
    default: 0
  }
});
const NewStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/New.vue"));
const SubtractStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Subtract.vue"));
const ImportStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/Import.vue"));
const FrozenAndThawStockInventory = defineAsyncComponent(() => import("@/views/inventoryManagement/stockManagement/FrozenAndThaw.vue"));
const { proxy } = getCurrentInstance()
const tableData = ref([])
const selectedRows = ref([])
const record = ref({})
const tableLoading = ref(false)
const page = reactive({
  current: 1,
  size: 100,
})
const total = ref(0)
// æ˜¯å¦æ˜¾ç¤ºæ–°å¢žå¼¹æ¡†
const isShowNewModal = ref(false)
// æ˜¯å¦æ˜¾ç¤ºé¢†ç”¨å¼¹æ¡†
const isShowSubtractModal = ref(false)
// æ˜¯å¦æ˜¾ç¤ºå†»ç»“/解冻弹框
const isShowFrozenAndThawModal = ref(false)
// æ“ä½œç±»åž‹
const operationType = ref('frozen')
// æ˜¯å¦æ˜¾ç¤ºå¯¼å…¥å¼¹æ¡†
const isShowImportModal = ref(false)
const data = reactive({
  searchForm: {
    productName: '',
    topParentProductId: props.productId,
  }
})
const { searchForm } = toRefs(data)
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
  page.current = 1
  getList()
}
const paginationChange = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList()
}
const getList = () => {
  tableLoading.value = true
  getStockInventoryListPageCombined({ ...searchForm.value, ...page }).then(res => {
    tableLoading.value = false
    tableData.value = res.data.records
    total.value = res.data.total
    // æ•°æ®åŠ è½½å®ŒæˆåŽæ£€æŸ¥åº“å­˜
    // checkStockAndCreatePurchase();
  }).catch(() => {
    tableLoading.value = false
  })
}
const handleFileSuccess = (response) => {
  const { code, msg } = response;
  if (code == 200) {
    ElMessage({ message: "导入成功", type: "success" });
    upload.open = false;
    emits("uploadSuccess");
  } else {
    ElMessage({ message: msg, type: "error" });
  }
};
// ç‚¹å‡»é¢†ç”¨
const showSubtractModal = (row) => {
  record.value = row
  isShowSubtractModal.value = true
}
// ç‚¹å‡»å†»ç»“
const showFrozenModal = (row) => {
  record.value = row
  isShowFrozenAndThawModal.value = true
  operationType.value = 'frozen'
}
// ç‚¹å‡»è§£å†»
const showThawModal = (row) => {
  record.value = row
  isShowFrozenAndThawModal.value = true
  operationType.value = 'thaw'
}
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
  // è¿‡æ»¤æŽ‰å­æ•°æ®
  selectedRows.value = selection.filter(item => item.id);
  console.log('selection', selectedRows.value)
}
const expandedRowKeys = ref([])
// è¡¨æ ¼è¡Œç±»å
const tableRowClassName = ({ row }) => {
  const stock = Number(row?.qualifiedUnLockedQuantity ?? 0);
  const warn = Number(row?.warnNum ?? 0);
  if (!Number.isFinite(stock) || !Number.isFinite(warn)) {
    return '';
  }
  return stock < warn ? 'row-low-stock' : '';
};
// å¯¼å‡º
const handleOut = () => {
  ElMessageBox.confirm(
    '是否确认导出?',
    '导出', {
    confirmButtonText: '确认',
    cancelButtonText: '取消',
    type: 'warning',
  }
  ).then(() => {
    proxy.download("/stockInventory/exportStockInventory", {topParentProductId: props.productId}, '库存信息.xlsx')
  }).catch(() => {
    proxy.$modal.msg("已取消")
  })
}
onMounted(() => {
  getList()
})
</script>
<style scoped lang="scss">
:deep(.row-low-stock td) {
  background-color: #fde2e2;
  color: #c45656;
}
:deep(.row-low-stock:hover > td) {
  background-color: #fcd4d4;
}
</style>
src/views/inventoryManagement/stockManagement/Subtract.vue
@@ -38,6 +38,23 @@
        </el-form-item>
        <el-form-item
            label="库存类型"
            prop="type"
            :rules="[
                {
                required: true,
                message: '请选择库存类型',
                trigger: 'change',
              }
            ]"
        >
          <el-select v-model="formState.type" placeholder="请选择库存类型" @change="handleTypeChange">
            <el-option label="合格库存" value="qualified" :disabled="props.record.qualifiedUnLockedQuantity <= 0" />
            <el-option label="不合格库存" value="unqualified" :disabled="props.record.unQualifiedUnLockedQuantity <= 0" />
          </el-select>
        </el-form-item>
        <el-form-item
            label="数量"
            prop="qualitity"
        >
@@ -79,12 +96,7 @@
  record: {
    type: Object,
    default: () => {},
  },
  type: {
    type: String,
    required: true,
    default: 'qualified',
  },
  }
});
const emit = defineEmits(['update:visible', 'completed']);
@@ -94,8 +106,19 @@
})
const maxQuality = computed(() => {
  return props.record.unLockedQuantity ? props.record.unLockedQuantity :  0;
  let max = 0;
  if (formState.value.type === 'qualified') {
    max = props.record.qualifiedUnLockedQuantity ? props.record.qualifiedUnLockedQuantity :  0;
  } else {
    max = props.record.unQualifiedUnLockedQuantity ? props.record.unQualifiedUnLockedQuantity :  0;
  }
  // ç¡®ä¿ max è‡³å°‘为 1,避免 min > max çš„错误
  return Math.max(max, 1);
})
const handleTypeChange = () => {
  formState.value.qualitity = undefined;
}
const initFormData = () => {
  if (props.record) {
@@ -145,7 +168,6 @@
const handleProductSelect = async (products) => {
  if (products && products.length > 0) {
    const product = products[0];
    console.log(product)
    formState.value.productId = product.productId;
    formState.value.productName = product.productName;
    formState.value.productModelName = product.model;
@@ -169,7 +191,7 @@
        proxy.$modal.msgError("请选择规格");
        return;
      }
      if (props.type === 'qualified') {
      if (formState.value.type === 'qualified') {
        subtractStockInventory(formState.value).then(res => {
          // å…³é—­æ¨¡æ€æ¡†
          isShow.value = false;
src/views/inventoryManagement/stockManagement/index.vue
@@ -1,33 +1,44 @@
<template>
  <div class="app-container">
    <el-tabs v-model="activeTab" @tab-change="handleTabChange">
      <el-tab-pane v-for="tab in tabs"
                   :label="tab.label"
                   :name="tab.name"
                   :key="tab.name">
        <component :is="tab.name === 'qualified' ? QualifiedRecord : UnqualifiedRecord" />
    <div v-loading="loading" element-loading-text="加载中..." style="min-height: 80vh;">
      <el-tabs v-model="activeTab" @tab-change="handleTabChange" v-if="!loading">
        <el-tab-pane v-for="tab in products"
                     :label="tab.productName"
                     :name="tab.id"
                     :key="tab.id">
          <Record :product-id="tab.id" v-if="tab.id === activeTab" />
      </el-tab-pane>
    </el-tabs>
    </div>
  </div>
</template>
<script setup>
import QualifiedRecord from "@/views/inventoryManagement/stockManagement/Qualified.vue";
import UnqualifiedRecord from "@/views/inventoryManagement/stockManagement/Unqualified.vue";
const activeTab = ref('qualified')
const tabs = ref([
  {
    label: '合格库存',
    name: 'qualified'
  },
  {
    label: '不合格库存',
    name: 'unqualified'
  }
])
import { ref, onMounted } from 'vue';
import { productTreeList } from "@/api/basicData/product.js";
import Record from "@/views/inventoryManagement/stockManagement/Record.vue";
const products = ref([])
const activeTab = ref(null)
const loading = ref(false)
const handleTabChange = (tabName) => {
  activeTab.value = tabName;
}
const fetchProducts = async () => {
  loading.value = true;
  try {
    const res = await productTreeList();
    products.value = res.filter((item) => item.parentId === null).map(({ id, productName }) => ({ id, productName }));
    if (products.value.length > 0) {
      activeTab.value = products.value[0].id;
    }
  } finally {
    loading.value = false;
  }
}
onMounted(() => {
  fetchProducts();
})
</script>