Pārlūkot izejas kodu

版本号修改

unknown 2 mēneši atpakaļ
vecāks
revīzija
87b6e887dc

+ 8 - 0
src/api/material/stockInAndOut/index.ts

@@ -140,4 +140,12 @@ export const exportMaterialOutbound = (id) => {
   return request.download({
     url: 'material-io/outbound/export?id='+id
   })
+}
+
+// 生成出库单
+export const generateMaterialOutboundByInbound = (params) => {
+  return request.post({
+    url: 'material-io/outbound-by-inbound',
+    data: params
+  })
 }

+ 1 - 1
src/main.ts

@@ -51,7 +51,7 @@ import { Flex } from 'ant-design-vue'
 import 'ant-design-vue/dist/reset.css'
 import fetchPlugin from './config/axios/fetch';
 //版本号
-export const MAIN_VERSION = '4.3.1'
+export const MAIN_VERSION = '4.3.3'
 
 // 创建实例
 const setupAll = async () => {

+ 12 - 5
src/views/elderly/restaurant/elderFood-order/index.vue

@@ -15,12 +15,13 @@
       <el-form-item label="楼层名称" prop="floorName" label-width="80px">
         <el-input v-model="queryParams.floorName" clearable />
       </el-form-item>
-      <el-form-item label="日期" prop="orderFoodDate" label-width="60px">
+      <el-form-item label="时间段" prop="orderFoodDateRange" label-width="60px">
         <TgDatePicker
-          type="date"
-          v-model="queryParams.orderFoodDate"
+          type="daterange"
+          v-model="queryParams.orderFoodDateRange"
           class="!w-240px"
-          placeholder="请选择日期"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
         />
       </el-form-item>
       <el-form-item>
@@ -77,7 +78,9 @@ const queryParams = reactive({
   tenantIds: userStore.orgTenantId,
   buildName:'',
   floorName:'',
-  orderFoodDate: undefined as string | undefined
+  orderFoodDateRange: [] as string[] | undefined,
+  orderFoodStartDate: undefined as string | undefined,
+  orderFoodEndDate: undefined as string | undefined
 })
 
 const getWeekText = (dateStr?: string) => {
@@ -103,6 +106,10 @@ const getParams = (isExport = false) => {
     ...queryParams,
     tenantIds: queryParams.tenantIds && queryParams.tenantIds.length ? queryParams.tenantIds : undefined
   }
+  const dateRange = queryParams.orderFoodDateRange || []
+  params.orderFoodStartDate = dateRange[0]
+  params.orderFoodEndDate = dateRange[1]
+  delete params.orderFoodDateRange
   if (isExport) {
     params.pageNo = 1
     params.pageSize = 99999

+ 204 - 0
src/views/system/food/CateringPlan/importForm.vue

@@ -0,0 +1,204 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="props.config.title" width="500">
+    <div class="primary-wrap" v-if="props.config.downloadUrl">请先下载模版再导入数据!({{props.config.desc}})</div>
+    <div class="box-wrap">
+      <div class="left" @click="handleDownLoad" v-if="props.config.downloadUrl">
+        <Icon icon="fa:cloud-download" :size="46" />
+        <p>下载模版</p>
+      </div>
+      <div class="right">
+        <el-upload
+          ref="uploadRef"
+          v-model:file-list="fileList"
+          :action="importUrl"
+          :disabled="formLoading"
+          :headers="uploadHeaders"
+          class="upload-demo"
+          :on-progress="onProgressHandel"
+          :on-error="submitFormError"
+          :on-exceed="handleExceed"
+          :on-success="submitFormSuccess"
+          accept=".xlsx, .xls"
+        >
+
+        <Icon icon="fa:cloud-upload" :size="46" />
+        <p>立即导入</p>
+      </el-upload>
+      </div>
+    </div>
+    <template #footer>
+      <!-- <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button> -->
+      <el-button :disabled="formLoading" :loading="formLoading" @click="dialogVisible = false">关 闭</el-button>
+    </template>
+  </Dialog>
+</template>
+<script lang="ts" setup>
+import { getAccessToken, getTenantId } from '@/utils/auth'
+import download from '@/utils/download'
+import request from '@/config/axios'
+import {PropType} from "vue";
+defineOptions({ name: 'ImportFile' })
+
+interface configType {
+  title?: string
+  importUrl?: string
+  baseImportUrl?: string
+  exportUrl?: string
+  excelTempName: string
+  downloadUrl: string
+  desc: string
+  failExportUrl: string;
+  tenantId?: string
+}
+const props = defineProps({
+  config: {
+    type: Object as PropType<configType>,
+    default: () => {
+      return {
+        title: '导入',
+        importUrl: '',
+        baseImportUrl: '',
+        exportUrl: '',
+        excelTempName: '',
+        desc:'',
+        downloadUrl: '',
+        failExportUrl: '', // 上传失败的接口
+        tenantId: '',
+      }
+    }
+  }
+})
+const message = useMessage() // 消息弹窗
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const formLoading = ref(false) // 表单的加载中
+const uploadRef = ref()
+const importUrl = ref('')
+const uploadHeaders = ref({
+  Authorization: 'Bearer ' + getAccessToken(),
+  'tenant-id': getTenantId()
+}) // 上传 Header 头
+const fileList = ref([]) // 文件列表
+
+/** 打开弹窗 */
+const open = (restaurantId?: string | number) => {
+  dialogVisible.value = true
+  fileList.value = []
+  const baseUrl = import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL
+  const baseImportUrl = props.config.baseImportUrl || props.config.importUrl
+  if (restaurantId !== undefined && restaurantId !== null && restaurantId !== '') {
+    importUrl.value = `${baseUrl}${baseImportUrl}?restaurantId=${restaurantId}`
+  } else {
+    importUrl.value = `${baseUrl}${baseImportUrl}`
+  }
+  resetForm()
+}
+defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+
+/** 文件上传成功 */
+const emits = defineEmits(['success'])
+const submitFormSuccess = async (response: any) => {
+  if (response.code !== 0) {
+    message.error(response.msg)
+    formLoading.value = false
+    return
+  }
+  // 拼接提示语
+  const data = response.data
+  formLoading.value = false
+  dialogVisible.value = false
+  if(data.failCount > 0){
+    // 导出的二次确认
+    await message.exportConfirm(`上传成功${data.successCount}条,失败${data.failCount}条,是否导出错误信息?`)
+    if(!props.config.failExportUrl){
+      message.warning(`功能暂未开放,敬请期待~`)
+    }else{
+      // 下载
+      const res = await request.download({ url: props.config.failExportUrl, data: data.errorList, method: 'POST' })
+      download.excel(res, `${props.config.excelTempName}错误信息.xls`)
+    }
+  }else if(data.successCount > 0){
+    message.success(`上传成功${data.successCount}条`)
+  }
+
+  // 发送操作成功的事件
+  emits('success')
+}
+
+const onProgressHandel = () => {
+    formLoading.value = true
+}
+
+/** 上传错误提示 */
+const submitFormError = (): void => {
+  message.error('上传失败,请您重新上传!')
+  formLoading.value = false
+}
+
+/** 重置表单 */
+const resetForm = async (): Promise<void> => {
+  // 重置上传状态和文件
+  formLoading.value = false
+  await nextTick()
+  uploadRef.value?.clearFiles()
+}
+
+/** 文件数超出提示 */
+const handleExceed = (): void => {
+  message.error('最多只能上传一个文件!')
+}
+
+/** 下载模板操作 */
+const handleDownLoad = async () => {
+  const res = await  request.download({ url: props.config.downloadUrl })
+  download.excel(res, `${props.config.excelTempName}.xls`)
+}
+</script>
+<style lang="scss" scoped>
+.primary-wrap {
+  background-color: var(--el-color-primary-light-9);
+  color: var(--el-color-primary);
+  padding: 10px 5px;
+  border-radius: 4px;
+}
+.box-wrap {
+  display: flex;
+  justify-content: space-around;
+  .left,
+  .right {
+    margin-top: 20px;
+    width: 150px;
+    height: 150px;
+    border-radius: 8px;
+    text-align: center;
+    border: 1px solid var(--el-color-primary-light-7);
+    color: var(--el-color-primary);
+    cursor: pointer;
+  }
+  .right {
+    margin-left: 20px;
+    border: 1px solid var(--el-color-info-light-7);
+    color: var(--el-color-info);
+    .upload-demo{
+      height: 100%;
+    }
+  }
+  .el-icon {
+    margin-top: 30px;
+  }
+  p {
+    padding: 10px 0;
+  }
+}
+</style>
+<style lang="scss">
+.box-wrap {
+  .right{
+    .upload-demo{
+      .el-upload{
+        flex-direction: column !important;
+      }
+    }
+  }
+}
+</style>

+ 11 - 6
src/views/system/food/CateringPlan/index.vue

@@ -69,7 +69,7 @@
         <Icon icon="ep:delete" class="mr-5px" />
         复制计划
       </el-button>
-      <!-- <el-button type="success" @click="openImport"><Icon icon="ep:download" class="mr-5px" />导入</el-button> -->
+      <el-button v-hasPermi="['food:catering-plan:import']" type="success" @click="openImport"><Icon icon="ep:download" class="mr-5px" />导入</el-button>
     </div>
 
     <el-table
@@ -165,7 +165,7 @@
   <AddForm ref="addFoodRef" @success="getList" />
   <DetailsForm ref="foodDetailsForm" @success="getList" />
   <!-- 导入弹窗(复用 system/user 的 ImportFile 组件) -->
-  <Import ref="importRef" :config="importConfig" @success="getList" />
+  <ImportForm ref="importRef" :config="importConfig" @success="getList" />
 </template>
 
 <script setup lang="ts">
@@ -175,7 +175,7 @@ import AddForm from '@/views/system/food/CateringPlan/AddForm.vue'
 import DetailsForm from '@/views/system/food/CateringPlan/DetailsForm.vue'
 import { chargeCategoryDel } from '@/api/elderly/fee/chargeCategory'
 import {cateringPlanDelete, getCateringPlanPage, getListRestaurant} from "@/api/system/foods";
-import Import from '@/components/ImportFile/index.vue'
+import ImportForm from './importForm.vue'
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 
@@ -376,17 +376,22 @@ const importRef = ref()
 const importConfig = reactive({
   title: '餐饮计划导入',
   // TODO: 替换成你的真实导入接口
-  importUrl: '/materialInfo/import',
+  importUrl: '/restaurant/cateringPlan/import',
+  baseImportUrl: '/restaurant/cateringPlan/import',
   excelTempName: '餐饮计划导入模板',
   // TODO: 替换成你的真实模板下载接口
-  downloadUrl: '/materialInfo/downloadExcel',
+  downloadUrl: '/restaurant/cateringPlan/import-template',
   desc: '餐饮计划',
   // TODO: 如果后端支持失败信息导出,填真实接口;不支持可留空
   failExportUrl: '',
 })
 
 const openImport = () => {
-  importRef.value?.open?.()
+  if (!queryParams.restaurantId) {
+    message.warning('请先选择餐厅后再导入')
+    return
+  }
+  importRef.value?.open?.(queryParams.restaurantId)
 }
 
 /** 初始化 **/

+ 122 - 0
src/views/warehouses/materialsIn/components/GenerateOutboundDialog.vue

@@ -0,0 +1,122 @@
+<template>
+  <Dialog v-model="visible" :title="dialogTitle" width="600px" @close="handleDialogClose">
+    <el-form ref="formRef" :model="form" label-width="100px" :rules="formRules">
+      <el-form-item label="出库日期" prop="orderDate">
+        <el-date-picker
+          v-model="form.orderDate"
+          type="date"
+          placeholder="选择日期"
+          value-format="YYYY-MM-DD"
+          style="width: 100%"
+        />
+      </el-form-item>
+      <el-form-item label="领用部门" prop="outDeptId">
+        <el-tree-select
+          v-model="form.outDeptId"
+          :data="departmentOptions"
+          :props="defaultProps"
+          check-strictly
+          node-key="id"
+          placeholder="请选择领用部门"
+        />
+      </el-form-item>
+      <el-form-item label="领用人" prop="outUser">
+        <el-input v-model="form.outUser" placeholder="请输入领用人" />
+      </el-form-item>
+      <el-form-item label="出库原因" prop="outReason">
+        <el-input v-model="form.outReason" placeholder="请输入出库原因" />
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <el-button @click="visible = false">取消</el-button>
+      <el-button type="primary" :loading="submitLoading" @click="handleSubmit">保存</el-button>
+    </template>
+  </Dialog>
+</template>
+
+<script setup>
+import { computed, reactive, ref, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+import { defaultProps } from '@/utils/tree'
+import { generateMaterialOutboundByInbound } from '@/api/material/stockInAndOut'
+
+defineOptions({ name: 'GenerateOutboundDialog' })
+
+const props = defineProps({
+  modelValue: { type: Boolean, default: false },
+  inboundOrderId: { type: [Number, String], default: undefined },
+  tenantId: { type: [Number, String], default: undefined },
+  departmentOptions: { type: Array, default: () => [] },
+})
+
+const emit = defineEmits(['update:modelValue', 'success'])
+
+const visible = computed({
+  get: () => props.modelValue,
+  set: (val) => emit('update:modelValue', val),
+})
+
+const formRef = ref(null)
+const submitLoading = ref(false)
+
+const form = reactive({
+  orderDate: new Date().toISOString().split('T')[0],
+  outDeptId: undefined,
+  outUser: '',
+  outReason: '',
+  remark: '',
+})
+
+const formRules = reactive({
+  orderDate: [{ required: true, message: '请选择出库日期' }],
+  outDeptId: [{ required: true, message: '请选择领用部门' }],
+  outUser: [{ required: true, message: '请输入领用人' }],
+  outReason: [{ required: true, message: '请输入出库原因' }],
+})
+
+watch(
+  () => props.modelValue,
+  (opened) => {
+    if (!opened) return
+    form.orderDate = new Date().toISOString().split('T')[0]
+    form.outDeptId = undefined
+    form.outUser = ''
+    form.outReason = ''
+    form.remark = ''
+  }
+)
+
+const dialogTitle = computed(() => '生成出库单')
+
+const handleDialogClose = () => {
+  formRef.value?.resetFields?.()
+}
+
+const handleSubmit = async () => {
+  await formRef.value?.validate?.()
+  submitLoading.value = true
+  try {
+    const payload = {
+      inboundOrderId: Number(props.inboundOrderId),
+      orderDate: form.orderDate,
+      outDeptId: form.outDeptId,
+      outUser: form.outUser,
+      outReason: form.outReason,
+      remark: form.remark || undefined,
+      tenantId: props.tenantId,
+    }
+    await generateMaterialOutboundByInbound(payload)
+    ElMessage.success('生成成功')
+    visible.value = false
+    emit('success')
+  } catch (e) {
+    ElMessage.error(e?.message || '生成失败')
+  } finally {
+    submitLoading.value = false
+  }
+}
+</script>

+ 32 - 1
src/views/warehouses/materialsIn/index.vue

@@ -58,6 +58,7 @@
             <el-button v-hasPermi="['warehouses:materialsIn:export']" link type="success" @click="handleExport(row.id)" :loading="exportLoading">导出</el-button>
             <el-button v-hasPermi="['warehouses:materialsIn:print']" link type="primary" @click="handlePrint(row)">打印</el-button>
             <el-button v-hasPermi="['warehouses:materialsIn:delete']" link type="danger" @click="handleDelete(row)">删除</el-button>
+            <el-button v-hasPermi="['warehouses:materialsIn:generateOutbound']" link type="primary" @click="handleGenerateOutbound(row)">生成出库单</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -83,6 +84,13 @@
       :warehouse-options="warehouseOptions"
       @refresh="getTableData"
     />
+    <GenerateOutboundDialog
+      v-model="generateOutboundVisible"
+      :inbound-order-id="generateInboundOrderId"
+      :tenant-id="generateTenantId"
+      :department-options="departmentOptions"
+      @success="getTableData"
+    />
     <Print ref="printRef" />
     <!-- 导入弹窗(复用 system/user 的 ImportFile 组件) -->
     <Import ref="importRef" :config="importConfig" @success="handleImportSuccess" />
@@ -99,10 +107,13 @@ import { materialList } from '@/api/material/basicMessage'
 import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
 import download from '@/utils/download'
 import Import from '@/components/ImportFile/index.vue'
+import { handleTree } from '@/utils/tree'
+import * as DeptApi from '@/api/system/dept'
 
 const message = useMessage()
 
 const MaterialsInDialog = defineAsyncComponent(() => import('./components/MaterialsInDialog.vue'))
+const GenerateOutboundDialog = defineAsyncComponent(() => import('./components/GenerateOutboundDialog.vue'))
 const Print = defineAsyncComponent(() => import('./components/Print.vue'))
 
 const userStore = useUserStore()  
@@ -123,6 +134,9 @@ const exportLoading = ref(false)
 // 弹窗
 const dialogVisible = ref(false)
 const dialogMode = ref('add') // 'add', 'edit', 'view'
+const generateOutboundVisible = ref(false)
+const generateInboundOrderId = ref(undefined)
+const generateTenantId = ref(undefined)
 
 const initialForm = {
   id: null,
@@ -141,13 +155,14 @@ const initialForm = {
 
 const form = reactive({ ...initialForm })
 
-
 // 模拟的物资选项 (用于 el-select-v2)
 const materialOptions = ref([])
 
 // 模拟的仓库选项
 const warehouseOptions = ref([])
 
+// 部门下拉
+const departmentOptions = ref([])
 
 // 导入(复用 ImportFile 组件)
 const importRef = ref()
@@ -222,6 +237,10 @@ const fetchStoreOptions = async () => {
   }
 }
 
+const fetchDepartmentOptions = async () => {
+  departmentOptions.value = handleTree(await DeptApi.getSimpleDeptList())
+}
+
 // 获取表格数据(分页接口)
 const getTableData = async () => {
   loading.value = true
@@ -387,6 +406,18 @@ const handleDelete = (row) => {
     })
     .catch(() => {})
 }
+
+// 生成出库单
+const handleGenerateOutbound = async (row) => {
+  if(queryParams.tenantIds.length == 0 || queryParams.tenantIds.length > 1){
+    ElMessage.error('生成出库单只能选择一个机构')
+    return
+  }
+  await fetchDepartmentOptions()
+  generateInboundOrderId.value = row.id
+  generateTenantId.value = row.tenantId || (queryParams.tenantIds ? queryParams.tenantIds[0] : undefined)
+  generateOutboundVisible.value = true
+}
 </script>
 
 <style scoped>