前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【愚公系列】2023年02月 WMS智能仓储系统-016.库存管理和仓内作业(库存管理、仓内加工、库存移动)

【愚公系列】2023年02月 WMS智能仓储系统-016.库存管理和仓内作业(库存管理、仓内加工、库存移动)

作者头像
愚公搬代码
发布2023-03-16 17:23:57
6490
发布2023-03-16 17:23:57
举报
文章被收录于专栏:历史专栏历史专栏

文章目录


前言

这节主要分为两个模块:

  • 库存管理:库存管理的作用是确保有足够的库存量以满足消费者需求,减少库存空置和库存损耗,并有效地控制库存成本。
  • 仓内作业:仓内作业的作用是帮助仓库提高组织效率和完成仓库管理。它提高了仓库存储条件、运输条件和信息系统的效率,并节省了人力成本。
在这里插入图片描述
在这里插入图片描述

一、库存管理

在这里插入图片描述
在这里插入图片描述

库存管理数据主要是来源于收获管理,所哟库存和库位基本只有查询功能。

1.1 页面代码

代码语言:javascript
复制
<!-- Warehouse Setting -->
<template>
  <div class="container">
    <div>
      <v-tabs v-model="data.activeTab" stacked @update:model-value="method.changeTabs">
        <v-tab v-for="(item, index) of tabsConfig" :key="index" :value="item.value">
          <v-icon>{{ item.icon }}</v-icon>
          <p class="tabItemTitle">{{ item.tabName }}</p>
        </v-tab>
      </v-tabs>

      <!-- Main Content -->
      <v-card class="mt-5">
        <v-card-text>
          <v-window v-model="data.activeTab">
            <v-window-item value="tabStockLocation">
              <tabStockLocation ref="tabStockLocationRef" />
            </v-window-item>
            <v-window-item value="tabStock">
              <tabStock ref="tabStockRef" />
            </v-window-item>
          </v-window>
        </v-card-text>
      </v-card>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, reactive, onMounted, watch, nextTick } from 'vue'
import i18n from '@/languages/i18n'
import tabStockLocation from './tabStockLocation.vue'
import tabStock from './tabStock.vue'

const tabStockLocationRef = ref()
const tabStockRef = ref()

const tabsConfig = [
  {
    value: 'tabStockLocation',
    icon: 'mdi-database',
    tabName: i18n.global.t('wms.stockManagement.stockLocation')
  },
  {
    value: 'tabStock',
    icon: 'mdi-warehouse',
    tabName: i18n.global.t('wms.stockManagement.stock')
  }
]

const data = reactive({
  activeTab: '',
  isLoadstockLocation: false,
  isLoadstock: false
})

const method = reactive({
  changeTabs: (e: any): void => {
    nextTick(() => {
      switch (e) {
        case 'tabStockLocation':
          // Tips:Must be write the nextTick so that can get DOM!!
          if (tabStockLocationRef?.value?.getStockLocationList) {
            tabStockLocationRef.value.getStockLocationList()
          }
          break
        case 'tabStock':
          if (tabStockRef?.value?.getStockList) {
            tabStockRef.value.getStockList()
          }
          break
      }
    })
  }
})

onMounted(() => { })
</script>

<style scoped lang="less">
.operateArea {
  width: 100%;
  min-width: 760px;
  display: flex;
  align-items: center;
  border-radius: 10px;
  padding: 0 10px;
}

.col {
  display: flex;
  align-items: center;
}
</style>
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
<template>
  <div class="operateArea">
    <v-row no-gutters>
      <!-- Operate Btn -->
      <v-col cols="3" class="col">
        <tooltip-btn icon="mdi-refresh" :tooltip-text="$t('system.page.refresh')" @click="method.refresh"></tooltip-btn>
        <tooltip-btn icon="mdi-export-variant" :tooltip-text="$t('system.page.export')" @click="method.exportTable"> </tooltip-btn>
      </v-col>

      <!-- Search Input -->
      <v-col cols="9">
        <v-row no-gutters @keyup.enter="method.sureSearch">
          <v-col cols="4"></v-col>
          <v-col cols="4"></v-col>
          <v-col cols="4">
            <v-text-field
              v-model="data.searchForm.spu_name"
              clearable
              hide-details
              density="comfortable"
              class="searchInput ml-5 mt-1"
              :label="$t('wms.stockList.spu_name')"
              variant="solo"
            >
            </v-text-field>
          </v-col>
        </v-row>
      </v-col>
    </v-row>
  </div>

  <!-- Table -->
  <div
    class="mt-5"
    :style="{
      height: cardHeight
    }"
  >
    <vxe-table ref="xTableWarehouse" :column-config="{ minWidth: '100px' }" :data="data.tableData" :height="tableHeight" align="center">
      <template #empty>
        {{ i18n.global.t('system.page.noData') }}
      </template>
      <vxe-column type="seq" width="60"></vxe-column>
      <vxe-column type="checkbox" width="50"></vxe-column>
      <vxe-column field="spu_code" :title="$t('wms.stockList.spu_code')"></vxe-column>
      <vxe-column field="spu_name" :title="$t('wms.stockList.spu_name')"></vxe-column>
      <vxe-column field="sku_code" :title="$t('wms.stockList.sku_code')">
        <template #default="{ row }">
          <div :class="'text-decoration-none'" @click="method.showSkuInfo(row)"> {{ row.sku_code }}</div>
        </template>
      </vxe-column>
      <vxe-column field="qty" :title="$t('wms.stockList.qty')"></vxe-column>
      <vxe-column field="qty_available" :title="$t('wms.stockList.qty_available')"></vxe-column>
      <vxe-column field="qty_locked" :title="$t('wms.stockList.qty_locked')"></vxe-column>
      <vxe-column field="qty_frozen" :title="$t('wms.stockList.qty_frozen')"></vxe-column>
      <vxe-column field="qty_asn" :title="$t('wms.stockList.qty_asn')"></vxe-column>
      <vxe-column field="qty_to_unload" :title="$t('wms.stockList.qty_to_unload')"></vxe-column>
      <vxe-column field="qty_to_sort" :title="$t('wms.stockList.qty_to_sort')"></vxe-column>
      <vxe-column field="qty_sorted" :title="$t('wms.stockList.qty_sorted')"></vxe-column>
      <vxe-column field="shortage_qty" :title="$t('wms.stockList.shortage_qty')"></vxe-column>
    </vxe-table>
    <custom-pager
      :current-page="data.tablePage.pageIndex"
      :page-size="data.tablePage.pageSize"
      perfect
      :total="data.tablePage.total"
      :page-sizes="PAGE_SIZE"
      :layouts="PAGE_LAYOUT"
      @page-change="method.handlePageChange"
    >
    </custom-pager>
  </div>
  <skuInfo :show-dialog="data.showDialogShowInfo" :sku_id="data.sku_id" @close="method.closeDialogShowInfo" />
</template>

<script lang="ts" setup>
import { computed, ref, reactive, watch } from 'vue'
import { VxePagerEvents, VxeTablePropTypes } from 'vxe-table'
import { computedCardHeight, computedTableHeight, errorColor } from '@/constant/style'
import { StockVO } from '@/types/WMS/StockManagement'
import { PAGE_SIZE, PAGE_LAYOUT, DEFAULT_PAGE_SIZE } from '@/constant/vxeTable'
import { hookComponent } from '@/components/system'
import { DEBOUNCE_TIME } from '@/constant/system'
import { setSearchObject } from '@/utils/common'
import { SearchObject } from '@/types/System/Form'
import { getStockList } from '@/api/wms/stockManagement'
import tooltipBtn from '@/components/tooltip-btn.vue'
import i18n from '@/languages/i18n'
import customPager from '@/components/custom-pager.vue'
import skuInfo from './sku-info.vue'
import { exportData } from '@/utils/exportTable'

const xTableWarehouse = ref()

const data = reactive({
  sku_id: 0,
  showDialog: false,
  showDialogShowInfo: false,
  searchForm: {
    spu_name: ''
  },
  activeTab: null,
  tableData: ref<StockVO[]>([]),
  tablePage: reactive({
    total: 0,
    pageIndex: 1,
    pageSize: DEFAULT_PAGE_SIZE,
    searchObjects: ref<Array<SearchObject>>([])
  }),
  timer: ref<any>(null)
})

const method = reactive({
  closeDialogShowInfo: () => {
    data.showDialogShowInfo = false
  },
  showSkuInfo(row: StockVO) {
    data.sku_id = row.sku_id
    data.showDialogShowInfo = true
  },
  sumNum: (list: any[], field: string) => {
    let count = 0
    list.forEach((item) => {
      count += Number(item[field])
    })
    return count
  },
  // footerMethod:ref<VxeTablePropTypes.FooterMethod>({ columns, data }) => {
  //   columns.map((column, columnIndex) => {
  //     if (columnIndex === 0) {
  //       return '合计'
  //     }
  //     if (['qty', 'qty_available'].includes(column.field)) {
  //       return method.sumNum(data, column.field)
  //     }
  //     return null
  //   })
  // },
  // Refresh data
  refresh: () => {
    method.getStockList()
  },
  getStockList: async () => {
    const { data: res } = await getStockList(data.tablePage)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }
    data.tableData = res.data.rows
    data.tablePage.total = res.data.totals
  },
  handlePageChange: ref<VxePagerEvents.PageChange>(({ currentPage, pageSize }) => {
    data.tablePage.pageIndex = currentPage
    data.tablePage.pageSize = pageSize

    method.getStockList()
  }),
  exportTable: () => {
    const $table = xTableWarehouse.value
    exportData({
      table: $table,
      filename: i18n.global.t('wms.stockManagement.stock'),
      columnFilterMethod({ column }: any) {
        return !['checkbox'].includes(column?.type) && !['operate'].includes(column?.field)
      }
    })
  },
  sureSearch: () => {
    data.tablePage.searchObjects = setSearchObject(data.searchForm)
    method.getStockList()
  }
})

const cardHeight = computed(() => computedCardHeight({}))
const tableHeight = computed(() => computedTableHeight({}))

defineExpose({
  getStockList: method.getStockList
})
watch(
  () => data.searchForm,
  () => {
    // debounce
    if (data.timer) {
      clearTimeout(data.timer)
    }
    data.timer = setTimeout(() => {
      data.timer = null
      method.sureSearch()
    }, DEBOUNCE_TIME)
  },
  {
    deep: true
  }
)
</script>

<style lang="less" scoped>
.operateArea {
  width: 100%;
  min-width: 760px;
  display: flex;
  align-items: center;
  border-radius: 10px;
  padding: 0 10px;
}

.col {
  display: flex;
  align-items: center;
}
</style>
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
<template>
  <div class="operateArea">
    <v-row no-gutters>
      <!-- Operate Btn -->
      <v-col cols="3" class="col">
        <tooltip-btn icon="mdi-refresh" :tooltip-text="$t('system.page.refresh')" @click="method.refresh"></tooltip-btn>
        <tooltip-btn icon="mdi-export-variant" :tooltip-text="$t('system.page.export')" @click="method.exportTable"> </tooltip-btn>
      </v-col>

      <!-- Search Input -->
      <v-col cols="9">
        <v-row no-gutters @keyup.enter="method.sureSearch">
          <v-col cols="4"></v-col>
          <v-col cols="4"></v-col>
          <v-col cols="4">
            <v-text-field
              v-model="data.searchForm.location_name"
              clearable
              hide-details
              density="comfortable"
              class="searchInput ml-5 mt-1"
              :label="$t('wms.stockLocation.location_name')"
              variant="solo"
            >
            </v-text-field>
          </v-col>
        </v-row>
      </v-col>
    </v-row>
  </div>

  <!-- Table -->
  <div
    class="mt-5"
    :style="{
      height: cardHeight
    }"
  >
    <vxe-table ref="xTableStockLocation" :column-config="{ minWidth: '100px' }" :data="data.tableData" :height="tableHeight" align="center">
      <template #empty>
        {{ i18n.global.t('system.page.noData') }}
      </template>
      <vxe-column type="seq" width="60"></vxe-column>
      <vxe-column type="checkbox" width="50"></vxe-column>
      <vxe-column field="warehouse_name" :title="$t('wms.stockLocation.warehouse_name')"></vxe-column>
      <vxe-column field="location_name" :title="$t('wms.stockLocation.location_name')"></vxe-column>
      <vxe-column field="spu_code" :title="$t('wms.stockLocation.spu_code')">
        <template #default="{ row }">
          <div :class="'text-decoration-none'" @click="method.showSkuInfo(row)"> {{ row.sku_code }}</div>
        </template>
      </vxe-column>
      <vxe-column field="spu_name" :title="$t('wms.stockLocation.spu_name')"></vxe-column>
      <vxe-column field="sku_code" :title="$t('wms.stockLocation.sku_code')"></vxe-column>
      <vxe-column field="sku_name" :title="$t('wms.stockLocation.sku_name')"></vxe-column>
      <vxe-column field="qty" :title="$t('wms.stockLocation.qty')"></vxe-column>
      <vxe-column field="qty_available" :title="$t('wms.stockLocation.qty_available')"></vxe-column>
      <vxe-column field="qty_locked" :title="$t('wms.stockLocation.qty_locked')"></vxe-column>
      <vxe-column field="qty_frozen" :title="$t('wms.stockLocation.qty_frozen')"></vxe-column>
    </vxe-table>
    <custom-pager
      :current-page="data.tablePage.pageIndex"
      :page-size="data.tablePage.pageSize"
      perfect
      :total="data.tablePage.total"
      :page-sizes="PAGE_SIZE"
      :layouts="PAGE_LAYOUT"
      @page-change="method.handlePageChange"
    >
    </custom-pager>
  </div>
  <skuInfo :show-dialog="data.showDialogShowInfo" :sku_id="data.sku_id" @close="method.closeDialogShowInfo" />
</template>

<script lang="ts" setup>
import { computed, ref, reactive, watch } from 'vue'
import { VxePagerEvents } from 'vxe-table'
import { computedCardHeight, computedTableHeight, errorColor } from '@/constant/style'
import { StockLocationVO } from '@/types/WMS/StockManagement'
import { PAGE_SIZE, PAGE_LAYOUT, DEFAULT_PAGE_SIZE } from '@/constant/vxeTable'
import { hookComponent } from '@/components/system'
import { DEBOUNCE_TIME } from '@/constant/system'
import { setSearchObject } from '@/utils/common'
import { SearchObject } from '@/types/System/Form'
import { getStockLocationList } from '@/api/wms/stockManagement'
import tooltipBtn from '@/components/tooltip-btn.vue'
import i18n from '@/languages/i18n'
import customPager from '@/components/custom-pager.vue'
import skuInfo from './sku-info.vue'
import { exportData } from '@/utils/exportTable'

const xTableStockLocation = ref()

const data = reactive({
  sku_id: 0,
  showDialog: false,
  showDialogShowInfo: false,
  searchForm: {
    location_name: ''
  },
  activeTab: null,
  tableData: ref<StockLocationVO[]>([]),
  tablePage: reactive({
    total: 0,
    pageIndex: 1,
    pageSize: DEFAULT_PAGE_SIZE,
    searchObjects: ref<Array<SearchObject>>([])
  }),
  timer: ref<any>(null)
})

const method = reactive({
  closeDialogShowInfo: () => {
    data.showDialogShowInfo = false
  },
  showSkuInfo(row: StockLocationVO) {
    data.sku_id = row.sku_id
    data.showDialogShowInfo = true
  },
  // Refresh data
  refresh: () => {
    method.getStockLocationList()
  },
  getStockLocationList: async () => {
    const { data: res } = await getStockLocationList(data.tablePage)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }
    data.tableData = res.data.rows
    data.tablePage.total = res.data.totals
  },
  handlePageChange: ref<VxePagerEvents.PageChange>(({ currentPage, pageSize }) => {
    data.tablePage.pageIndex = currentPage
    data.tablePage.pageSize = pageSize

    method.getStockLocationList()
  }),
  exportTable: () => {
    const $table = xTableStockLocation.value
    exportData({
      table: $table,
      filename: i18n.global.t('wms.stockManagement.stockLocation'),
      columnFilterMethod({ column }: any) {
        return !['checkbox'].includes(column?.type) && !['operate'].includes(column?.field)
      }
    })
  },
  sureSearch: () => {
    data.tablePage.searchObjects = setSearchObject(data.searchForm)
    method.getStockLocationList()
  }
})

const cardHeight = computed(() => computedCardHeight({}))
const tableHeight = computed(() => computedTableHeight({}))

defineExpose({
  getStockLocationList: method.getStockLocationList
})
watch(
  () => data.searchForm,
  () => {
    // debounce
    if (data.timer) {
      clearTimeout(data.timer)
    }
    data.timer = setTimeout(() => {
      data.timer = null
      method.sureSearch()
    }, DEBOUNCE_TIME)
  },
  {
    deep: true
  }
)
</script>

<style lang="less" scoped>
.operateArea {
  width: 100%;
  min-width: 760px;
  display: flex;
  align-items: center;
  border-radius: 10px;
  padding: 0 10px;
}

.col {
  display: flex;
  align-items: center;
}
</style>
在这里插入图片描述
在这里插入图片描述

1.2 接口代码

代码语言:javascript
复制
     [Route("stock")]
     [ApiController]
     [ApiExplorerSettings(GroupName = "WMS")]
     public class StockController : BaseController
     {
         #region Args
 
         /// <summary>
         /// stock Service
         /// </summary>
         private readonly IStockService _stockService;
 
         /// <summary>
         /// Localizer Service
         /// </summary>
         private readonly IStringLocalizer<ModernWMS.Core.MultiLanguage> _stringLocalizer;
         #endregion
 
         #region constructor
         /// <summary>
         /// constructor
         /// </summary>
         /// <param name="stockService">stock Service</param>
        /// <param name="stringLocalizer">Localizer</param>
         public StockController(
             IStockService stockService
           , IStringLocalizer<ModernWMS.Core.MultiLanguage> stringLocalizer
             )
         {
             this._stockService = stockService;
            this._stringLocalizer= stringLocalizer;
         }
         #endregion
 
         #region Api
         /// <summary>
         /// stock details page search
         /// </summary>
         /// <param name="pageSearch">args</param>
         /// <returns></returns>
         [HttpPost("stock-list")]
         public async Task<ResultModel<PageData<StockManagementViewModel>>> StockPageAsync(PageSearch pageSearch)
         {
             var (data, totals) = await _stockService.StockPageAsync(pageSearch, CurrentUser);
              
             return ResultModel<PageData<StockManagementViewModel>>.Success(new PageData<StockManagementViewModel>
             {
                 Rows = data,
                 Totals = totals
             });
         }
        /// <summary>
        /// location stock page search
        /// </summary>
        /// <param name="pageSearch">args</param>
        /// <returns></returns>
        [HttpPost("location-list")]
        public async Task<ResultModel<PageData<LocationStockManagementViewModel>>> LocationStockPageAsync(PageSearch pageSearch)
        {
            var (data, totals) = await _stockService.LocationStockPageAsync(pageSearch, CurrentUser);

            return ResultModel<PageData<LocationStockManagementViewModel>>.Success(new PageData<LocationStockManagementViewModel>
            {
                Rows = data,
                Totals = totals
            });
        }

        /// <summary>
        /// page search select
        /// </summary>
        /// <param name="pageSearch">args</param>
        /// <returns></returns>
        [HttpPost("select")]
        public async Task<ResultModel<PageData<StockViewModel>>> SelectPageAsync(PageSearch pageSearch)
        {
            var (data, totals) = await _stockService.SelectPageAsync(pageSearch, CurrentUser);

            return ResultModel<PageData<StockViewModel>>.Success(new PageData<StockViewModel>
            {
                Rows = data,
                Totals = totals
            });
        }

        /// <summary>
        /// sku page search select
        /// </summary>
        /// <param name="pageSearch">args</param>
        /// <returns></returns>
        [HttpPost("sku-select")]
        public async Task<ResultModel<PageData<SkuSelectViewModel>>> SkuSelectPageAsync(PageSearch pageSearch)
        {
            var (data, totals) = await _stockService.SkuSelectPageAsync(pageSearch, CurrentUser);

            return ResultModel<PageData<SkuSelectViewModel>>.Success(new PageData<SkuSelectViewModel>
            {
                Rows = data,
                Totals = totals
            });
        }
        #endregion

    }
在这里插入图片描述
在这里插入图片描述

二、仓内作业

1.仓内加工

在这里插入图片描述
在这里插入图片描述

仓内加工主要分为:

  • 拆分加工
  • 组合加工

1.1 页面代码

代码语言:javascript
复制
<!-- Warehouse Processing -->
<template>
  <div class="container">
    <div>
      <!-- Main Content -->
      <v-card class="mt-5">
        <v-card-text>
          <v-window v-model="data.activeTab">
            <v-window-item>
              <div class="operateArea">
                <v-row no-gutters>
                  <!-- Operate Btn -->
                  <v-col cols="3" class="col">
                    <tooltip-btn
                      icon="mdi-arrow-split-vertical"
                      :tooltip-text="$t('wms.warehouseWorking.warehouseProcessing.process_split')"
                      @click="method.add(PROCESS_JOB_SPLIT)"
                    ></tooltip-btn>
                    <tooltip-btn
                      icon="mdi-group"
                      :tooltip-text="$t('wms.warehouseWorking.warehouseProcessing.process_combine')"
                      @click="method.add(PROCESS_JOB_COMBINE)"
                    ></tooltip-btn>
                    <tooltip-btn icon="mdi-refresh" :tooltip-text="$t('system.page.refresh')" @click="method.refresh"></tooltip-btn>
                    <tooltip-btn icon="mdi-export-variant" :tooltip-text="$t('system.page.export')" @click="method.exportTable"> </tooltip-btn>
                  </v-col>

                  <!-- Search Input -->
                  <v-col cols="9">
                    <v-row no-gutters @keyup.enter="method.sureSearch">
                      <v-col cols="4"></v-col>
                      <v-col cols="4"></v-col>
                      <v-col cols="4">
                        <v-text-field
                          v-model="data.searchForm.job_code"
                          clearable
                          hide-details
                          density="comfortable"
                          class="searchInput ml-5 mt-1"
                          :label="$t('wms.warehouseWorking.warehouseProcessing.job_code')"
                          variant="solo"
                        >
                        </v-text-field>
                      </v-col>
                    </v-row>
                  </v-col>
                </v-row>
              </div>

              <!-- Table -->
              <div
                class="mt-5"
                :style="{
                  height: cardHeight
                }"
              >
                <vxe-table ref="xTable" :column-config="{ minWidth: '100px' }" :data="data.tableData" :height="tableHeight" align="center">
                  <template #empty>
                    {{ i18n.global.t('system.page.noData') }}
                  </template>
                  <vxe-column type="seq" width="60"></vxe-column>
                  <vxe-column type="checkbox" width="50"></vxe-column>
                  <vxe-column field="job_code" width="150px" :title="$t('wms.warehouseWorking.warehouseProcessing.job_code')"></vxe-column>
                  <vxe-column field="job_type" :title="$t('wms.warehouseWorking.warehouseProcessing.job_type')">
                    <template #default="{ row, column }">
                      <span>{{ formatProcessJobType(row[column.property]) }}</span>
                    </template>
                  </vxe-column>
                  <vxe-column field="adjust_status" :title="$t('wms.warehouseWorking.warehouseProcessing.adjust_status')">
                    <template #default="{ row, column }">
                      <span>{{ formatIsValid(row[column.property]) }}</span>
                    </template>
                  </vxe-column>
                  <vxe-column field="processor" :title="$t('wms.warehouseWorking.warehouseProcessing.processor')"></vxe-column>
                  <vxe-column field="process_time" width="170px" :title="$t('wms.warehouseWorking.warehouseProcessing.process_time')">
                    <template #default="{ row, column }">
                      <span>{{ formatDate(row[column.property]) }}</span>
                    </template>
                  </vxe-column>
                  <vxe-column field="creator" :title="$t('wms.warehouseWorking.warehouseProcessing.creator')"></vxe-column>
                  <vxe-column field="create_time" width="170px" :title="$t('wms.warehouseWorking.warehouseProcessing.create_time')"></vxe-column>
                  <vxe-column field="operate" :title="$t('system.page.operate')" width="250" :resizable="false" show-overflow>
                    <template #default="{ row }">
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-eye-outline"
                        :tooltip-text="$t('system.page.view')"
                        @click="method.viewRow(row)"
                      ></tooltip-btn>
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-book-check-outline"
                        :tooltip-text="$t('wms.warehouseWorking.warehouseProcessing.confirmProcess')"
                        :disabled="method.confirmProcessBtnDisabled(row)"
                        @click="method.confirmProcess(row)"
                      ></tooltip-btn>
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-book-open-outline"
                        :tooltip-text="$t('wms.warehouseWorking.warehouseProcessing.confirmAdjust')"
                        :disabled="method.confirmAdjustBtnDisabled(row)"
                        @click="method.confirmAdjust(row)"
                      ></tooltip-btn>
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-delete-outline"
                        :tooltip-text="$t('system.page.delete')"
                        :icon-color="errorColor"
                        :disabled="method.confirmProcessBtnDisabled(row)"
                        @click="method.deleteRow(row)"
                      ></tooltip-btn>
                    </template>
                  </vxe-column>
                </vxe-table>
                <custom-pager
                  :current-page="data.tablePage.pageIndex"
                  :page-size="data.tablePage.pageSize"
                  perfect
                  :total="data.tablePage.total"
                  :page-sizes="PAGE_SIZE"
                  :layouts="PAGE_LAYOUT"
                  @page-change="method.handlePageChange"
                >
                </custom-pager>
              </div>
            </v-window-item>
          </v-window>
        </v-card-text>
      </v-card>
      <addOrUpdateDialog
        :show-dialog="data.showDialog"
        :form="data.dialogForm"
        :process-type="data.processType"
        @close="method.closeDialog"
        @saveSuccess="method.saveSuccess"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import { computed, ref, reactive, onActivated, watch, nextTick } from 'vue'
import { VxePagerEvents } from 'vxe-table'
import { computedCardHeight, computedTableHeight, errorColor } from '@/constant/style'
import { WarehouseProcessingVO, WarehouseProcessingDetailVO } from '@/types/WarehouseWorking/WarehouseProcessing'
import { PAGE_SIZE, PAGE_LAYOUT, DEFAULT_PAGE_SIZE } from '@/constant/vxeTable'
import { hookComponent } from '@/components/system'
import { deleteStockProcess, getStockProcessList, getStockProcessOne, confirmAdjustment, confirmProcess } from '@/api/wms/warehouseProcessing'
import { PROCESS_JOB_COMBINE, PROCESS_JOB_SPLIT } from '@/constant/warehouseWorking'
import { DEBOUNCE_TIME } from '@/constant/system'
import { setSearchObject } from '@/utils/common'
import { SearchObject } from '@/types/System/Form'
import { formatIsValid, formatDate } from '@/utils/format/formatSystem'
import { formatProcessJobType } from '@/utils/format/formatWarehouseWorking'
import tooltipBtn from '@/components/tooltip-btn.vue'
import addOrUpdateDialog from './add-or-update-process.vue'
import i18n from '@/languages/i18n'
import customPager from '@/components/custom-pager.vue'
import { exportData } from '@/utils/exportTable'

const xTable = ref()

const data = reactive({
  showDialog: false,
  processType: PROCESS_JOB_COMBINE,
  timer: ref<any>(null),
  activeTab: null,
  searchForm: {
    job_code: ''
  },
  tableData: ref<WarehouseProcessingVO[]>([]),
  dialogForm: {
    id: 0,
    job_code: '',
    job_type: PROCESS_JOB_COMBINE,
    process_status: false,
    processor: '',
    process_time: '',
    source_detail_list: ref<any[]>([]),
    target_detail_list: ref<any[]>([]),
    creator: '',
    create_time: '',
    adjust_status: false
  },
  tablePage: reactive({
    total: 0,
    pageIndex: 1,
    pageSize: DEFAULT_PAGE_SIZE,
    searchObjects: ref<Array<SearchObject>>([])
  })
})

const method = reactive({
  // Open a dialog to add
  add: (jobType: boolean) => {
    data.processType = jobType
    data.dialogForm = {
      id: 0,
      job_code: '',
      job_type: jobType,
      process_status: false,
      processor: '',
      process_time: '',
      source_detail_list: [],
      target_detail_list: [],
      creator: '',
      create_time: '',
      adjust_status: false
    }
    nextTick(() => {
      data.showDialog = true
    })
  },

  // After add or update success.
  saveSuccess: () => {
    method.refresh()
    method.closeDialog()
  },

  // Refresh data
  refresh: () => {
    method.getStockProcessList()
  },

  // Shut add or update dialog
  closeDialog: () => {
    data.showDialog = false
  },

  getStockProcessList: async () => {
    const { data: res } = await getStockProcessList(data.tablePage)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }
    data.tableData = res.data.rows
    data.tablePage.total = res.data.totals
  },

  viewRow: async (row: WarehouseProcessingVO) => {
    await method.getOne(row.id)
    nextTick(() => {
      data.showDialog = true
    })
  },

  getOne: async (id: number) => {
    const { data: res } = await getStockProcessOne(id)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }

    data.dialogForm = res.data
    data.processType = res.data.job_type
  },

  deleteRow(row: WarehouseProcessingVO) {
    hookComponent.$dialog({
      content: i18n.global.t('system.tips.beforeDeleteMessage'),
      handleConfirm: async () => {
        if (row.id) {
          const { data: res } = await deleteStockProcess(row.id)
          if (!res.isSuccess) {
            hookComponent.$message({
              type: 'error',
              content: res.errorMessage
            })
            return
          }

          hookComponent.$message({
            type: 'success',
            content: `${ i18n.global.t('system.page.delete') }${ i18n.global.t('system.tips.success') }`
          })
          method.refresh()
        }
      }
    })
  },

  handlePageChange: ref<VxePagerEvents.PageChange>(({ currentPage, pageSize }) => {
    data.tablePage.pageIndex = currentPage
    data.tablePage.pageSize = pageSize
    method.refresh()
  }),

  exportTable: () => {
    const $table = xTable.value
    exportData({
      table: $table,
      filename: i18n.global.t('router.sideBar.warehouseProcessing'),
      columnFilterMethod({ column }: any) {
        return !['checkbox'].includes(column?.type) && !['operate'].includes(column?.field)
      }
    })
  },

  sureSearch: () => {
    data.tablePage.searchObjects = setSearchObject(data.searchForm)
    method.refresh()
  },

  // The btn will become disabled when the 'process_status' is false
  confirmProcessBtnDisabled: (row: WarehouseProcessingVO) => !!row.process_status,

  // The btn will become disabled when the 'process_status' is false
  // or the 'adjust_status' is true
  confirmAdjustBtnDisabled: (row: WarehouseProcessingVO) => !row.process_status || !!row.adjust_status,

  confirmProcess: async (row: WarehouseProcessingDetailVO) => {
    hookComponent.$dialog({
      content: i18n.global.t('wms.warehouseWorking.warehouseProcessing.beforeConfirmProcess'),
      handleConfirm: async () => {
        if (row.id) {
          const { data: res } = await confirmProcess(row.id)
          if (!res.isSuccess) {
            hookComponent.$message({
              type: 'error',
              content: res.errorMessage
            })
            return
          }
          hookComponent.$message({
            type: 'success',
            content: `${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.confirmProcess') }${ i18n.global.t('system.tips.success') }`
          })
          method.refresh()
        }
      }
    })
  },

  confirmAdjust: async (row: WarehouseProcessingDetailVO) => {
    hookComponent.$dialog({
      content: i18n.global.t('wms.warehouseWorking.warehouseProcessing.beforeConfirmAdjust'),
      handleConfirm: async () => {
        if (row.id) {
          const { data: res } = await confirmAdjustment(row.id)
          if (!res.isSuccess) {
            hookComponent.$message({
              type: 'error',
              content: res.errorMessage
            })
            return
          }
          hookComponent.$message({
            type: 'success',
            content: `${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.confirmAdjust') }${ i18n.global.t('system.tips.success') }`
          })
          method.refresh()
        }
      }
    })
  }
})

onActivated(() => {
  method.refresh()
})

const cardHeight = computed(() => computedCardHeight({ hasTab: false }))
const tableHeight = computed(() => computedTableHeight({ hasTab: false }))

watch(
  () => data.searchForm,
  () => {
    // debounce
    if (data.timer) {
      clearTimeout(data.timer)
    }
    data.timer = setTimeout(() => {
      data.timer = null
      method.sureSearch()
    }, DEBOUNCE_TIME)
  },
  {
    deep: true
  }
)
</script>

<style scoped lang="less">
.operateArea {
  width: 100%;
  min-width: 760px;
  display: flex;
  align-items: center;
  border-radius: 10px;
  padding: 0 10px;
}

.col {
  display: flex;
  align-items: center;
}
</style>
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
<template>
  <v-dialog v-model="isShow" width="80%" transition="dialog-top-transition" :persistent="true">
    <template #default>
      <v-card>
        <v-toolbar color="white" :title="jobTypeComp"></v-toolbar>
        <v-card-text>
          <v-row>
            <!-- Source Table -->
            <v-col cols="6">
              <div class="dataTable">
                <div class="toolbar">
                  <div class="toolbarTitle">
                    <p style="color: #999">{{ $t('wms.warehouseWorking.warehouseProcessing.source') }}</p>
                  </div>
                  <tooltip-btn
                    icon="mdi-plus"
                    :tooltip-text="$t('system.page.add')"
                    size="x-small"
                    :disabled="operateDisabled"
                    @click="method.openSelect('source')"
                  ></tooltip-btn>
                </div>
                <vxe-table
                  ref="xTableSource"
                  :column-config="{ minWidth: '100px' }"
                  :data="data.form.source_detail_list"
                  :height="SYSTEM_HEIGHT.SELECT_TABLE"
                  :edit-config="{ trigger: 'click', mode: 'cell' }"
                  :edit-rules="data.validRulesSource"
                  align="center"
                >
                  <template #empty>
                    {{ i18n.global.t('system.page.noData') }}
                  </template>
                  <vxe-column type="seq" width="60"></vxe-column>
                  <vxe-column field="operate" width="50" :title="$t('system.page.operate')" :resizable="false">
                    <template #default="{ row }">
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-delete-outline"
                        :tooltip-text="$t('system.page.delete')"
                        :icon-color="errorColor"
                        @click="method.deleteRowSource(row)"
                      ></tooltip-btn>
                    </template>
                  </vxe-column>
                  <vxe-column field="spu_code" :title="$t('wms.warehouseWorking.warehouseProcessing.spu_code')"></vxe-column>
                  <vxe-column field="spu_name" :title="$t('wms.warehouseWorking.warehouseProcessing.spu_name')"></vxe-column>
                  <vxe-column field="sku_code" :title="$t('wms.warehouseWorking.warehouseProcessing.sku_code')"></vxe-column>
                  <vxe-column
                    field="qty"
                    :title="$t('wms.warehouseWorking.warehouseProcessing.qty')"
                    :edit-render="{ autofocus: '.vxe-input--inner' }"
                  >
                    <template #edit="{ row }">
                      <vxe-input v-model="row.qty" type="text"></vxe-input>
                    </template>
                  </vxe-column>
                  <vxe-column field="unit" :title="$t('wms.warehouseWorking.warehouseProcessing.unit')"></vxe-column>
                </vxe-table>
              </div>
            </v-col>

            <!-- Target Table -->
            <v-col cols="6">
              <div class="dataTable">
                <div class="toolbar">
                  <div class="toolbarTitle">
                    <p style="color: #999">{{ $t('wms.warehouseWorking.warehouseProcessing.target') }}</p>
                  </div>
                  <tooltip-btn
                    icon="mdi-plus"
                    :tooltip-text="$t('system.page.add')"
                    size="x-small"
                    :disabled="operateDisabled"
                    @click="method.openSelect('target')"
                  ></tooltip-btn>
                </div>
                <vxe-table
                  ref="xTableTarget"
                  :column-config="{ minWidth: '100px' }"
                  :data="data.form.target_detail_list"
                  :height="SYSTEM_HEIGHT.SELECT_TABLE"
                  :edit-config="{ trigger: 'click', mode: 'cell' }"
                  :edit-rules="data.validRulesTarget"
                  align="center"
                >
                  <template #empty>
                    {{ i18n.global.t('system.page.noData') }}
                  </template>
                  <vxe-column type="seq" width="60"></vxe-column>
                  <vxe-column field="operate" width="50" :title="$t('system.page.operate')" :resizable="false">
                    <template #default="{ row }">
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-delete-outline"
                        :tooltip-text="$t('system.page.delete')"
                        :icon-color="errorColor"
                        @click="method.deleteRowTarget(row)"
                      ></tooltip-btn>
                    </template>
                  </vxe-column>
                  <vxe-column field="spu_code" :title="$t('wms.warehouseWorking.warehouseProcessing.spu_code')"></vxe-column>
                  <vxe-column field="spu_name" :title="$t('wms.warehouseWorking.warehouseProcessing.spu_name')"></vxe-column>
                  <vxe-column field="sku_code" :title="$t('wms.warehouseWorking.warehouseProcessing.sku_code')"></vxe-column>
                  <vxe-column
                    field="qty"
                    :title="$t('wms.warehouseWorking.warehouseProcessing.qty')"
                    :edit-render="{ autofocus: '.vxe-input--inner' }"
                  >
                    <template #edit="{ row }">
                      <vxe-input v-model="row.qty" type="text"></vxe-input>
                    </template>
                  </vxe-column>
                  <vxe-column field="unit" width="60" :title="$t('wms.warehouseWorking.warehouseProcessing.unit')"></vxe-column>
                  <vxe-column field="location_name" :title="$t('wms.warehouseWorking.warehouseProcessing.target_location')" :edit-render="{}">
                    <template #edit="{ row }">
                      <vxe-input v-model="row.location_name" readonly type="search" @search-click="method.openLocationSelect(row)"></vxe-input>
                    </template>
                  </vxe-column>
                </vxe-table>
              </div>
            </v-col>
          </v-row>
        </v-card-text>
        <v-card-actions class="justify-end">
          <v-btn variant="text" @click="method.closeDialog">{{ $t('system.page.close') }}</v-btn>
          <v-btn color="primary" variant="text" :disabled="operateDisabled" @click="method.submit">{{ $t('system.page.submit') }}</v-btn>
        </v-card-actions>
      </v-card>
    </template>
  </v-dialog>

  <commodity-select :show-dialog="data.showCommodityDialogSelect" @close="method.closeDialogSelect('source')" @sureSelect="method.sureSelect" />
  <sku-select :show-dialog="data.showSkuDialogSelect" @close="method.closeDialogSelect('target')" @sureSelect="method.sureSelect" />
  <location-select :show-dialog="data.showLocationDialogSelect" @close="method.closeLocationDialogSelect" @sureSelect="method.sureSelectLocation" />
</template>

<script lang="ts" setup>
import { reactive, computed, ref, watch } from 'vue'
import { VxeTablePropTypes } from 'vxe-table'
import { WarehouseProcessingVO, WarehouseProcessingDetailVO } from '@/types/WarehouseWorking/WarehouseProcessing'
import i18n from '@/languages/i18n'
import { hookComponent } from '@/components/system/index'
import { addStockProcess } from '@/api/wms/warehouseProcessing'
import { SYSTEM_HEIGHT, errorColor } from '@/constant/style'
import { removeObjectNull } from '@/utils/common'
import { PROCESS_JOB_COMBINE, PROCESS_JOB_SPLIT } from '@/constant/warehouseWorking'
import commoditySelect from '@/components/select/commodity-select.vue'
import locationSelect from '@/components/select/location-select.vue'
import skuSelect from '@/components/select/sku-select.vue'
import tooltipBtn from '@/components/tooltip-btn.vue'
import { exportData } from '@/utils/exportTable'
import { isInteger } from '@/utils/dataVerification/tableRule'

const emit = defineEmits(['close', 'saveSuccess'])
const xTableSource = ref()
const xTableTarget = ref()

const props = defineProps<{
  showDialog: boolean
  form: WarehouseProcessingVO
  processType: boolean
}>()

const isShow = computed(() => props.showDialog)
const isUpdate = computed(() => props.form.id && props.form.id > 0)
const jobTypeComp = computed(() => (data.form.job_type === PROCESS_JOB_COMBINE
    ? i18n.global.t('wms.warehouseWorking.warehouseProcessing.process_combine')
    : i18n.global.t('wms.warehouseWorking.warehouseProcessing.process_split')))
const operateDisabled = computed(() => !!isUpdate.value)

const method = reactive({
  initForm: () => {
    data.form = props.form
    data.form.job_type = props.processType
  },

  closeDialog: () => {
    emit('close')
  },

  openSelect: (type: string) => {
    data.curSelectType = type

    if (type === 'source') {
      data.showCommodityDialogSelect = true
    } else if (type === 'target') {
      // Target should select the data with 'sku-select-modal'
      data.showSkuDialogSelect = true
    }
  },

  closeDialogSelect: (type: string) => {
    if (type === 'source') {
      data.showCommodityDialogSelect = false
    } else if (type === 'target') {
      // Target should select the data with 'sku-select-modal'
      data.showSkuDialogSelect = false
    }
  },

  sureSelect: (selectRecords: any) => {
    if (data.curSelectType === 'source') {
      method.insertSourceData(selectRecords)
    } else if (data.curSelectType === 'target') {
      method.insertTargetData(selectRecords)
    }
  },

  openLocationSelect: (row: WarehouseProcessingDetailVO) => {
    data.curSelectRow = row
    data.showLocationDialogSelect = true
  },

  closeLocationDialogSelect: () => {
    data.showLocationDialogSelect = false
  },

  sureSelectLocation: (selectRecords: any) => {
    if (selectRecords.length > 0) {
      const $table = xTableTarget.value
      const tableData = $table.getTableData().fullData
      tableData.forEach((row: WarehouseProcessingDetailVO) => {
        if (data.curSelectRow.sku_id === row.sku_id) {
          row.goods_location_id = selectRecords[0].id
          row.location_name = selectRecords[0].location_name
        }
      })
      // Tips: Must to reload!
      $table.reloadData(tableData)
    }
  },

  insertSourceData: (selectRecords: any) => {
    const $table = xTableSource.value
    const tableData = $table.getTableData().fullData

    // Combine: That can select more source commodity
    if (data.form.job_type === PROCESS_JOB_COMBINE) {
      for (const record of selectRecords) {
        const isRepeat = tableData.some((data: WarehouseProcessingDetailVO) => data.sku_id === record.sku_id)
        if (isRepeat) {
          continue
        }

        $table.insertAt(
          {
            id: 0,
            stock_process_id: 0,
            sku_id: record.sku_id,
            goods_owner_id: record.goods_owner_id,
            goods_location_id: record.goods_location_id,
            qty: record.qty_available || 0,
            tenant_id: 0,
            is_source: true,
            spu_code: record.spu_code,
            spu_name: record.spu_name,
            sku_code: record.sku_code,
            unit: record.unit,
            is_update_stock: false,
            qty_available: record.qty_available
          },
          -1
        )
        // })
      }
    } else if (data.form.job_type === PROCESS_JOB_SPLIT) {
      // Split: That just can select one source commodity.
      // It should remove all data before insert.
      $table.remove()
      $table.insertAt(
        {
          id: 0,
          stock_process_id: 0,
          sku_id: selectRecords[0].sku_id,
          goods_owner_id: selectRecords[0].goods_owner_id,
          goods_location_id: selectRecords[0].goods_location_id,
          qty: selectRecords[0].qty_available || 0,
          tenant_id: 0,
          is_source: true,
          spu_code: selectRecords[0].spu_code,
          spu_name: selectRecords[0].spu_name,
          sku_code: selectRecords[0].sku_code,
          unit: selectRecords[0].unit,
          is_update_stock: false,
          qty_available: selectRecords[0].qty_available
        },
        -1
      )
    }
  },

  insertTargetData: (selectRecords: any) => {
    const $table = xTableTarget.value
    const tableData = $table.getTableData().fullData

    // Combine: That just can select one target commodity
    if (data.form.job_type === PROCESS_JOB_COMBINE) {
      // It should remove all data before insert.
      $table.remove()
      $table.insertAt(
        {
          id: 0,
          stock_process_id: 0,
          sku_id: selectRecords[0].sku_id,
          goods_owner_id: 0,
          goods_location_id: 0,
          qty: 0,
          tenant_id: 0,
          is_source: false,
          spu_code: selectRecords[0].spu_code,
          spu_name: selectRecords[0].spu_name,
          sku_code: selectRecords[0].sku_code,
          unit: selectRecords[0].unit,
          is_update_stock: false
        },
        -1
      )
    } else if (data.form.job_type === PROCESS_JOB_SPLIT) {
      // Split: That can select more target commodity
      for (const record of selectRecords) {
        const isRepeat = tableData.some((data: WarehouseProcessingDetailVO) => data.sku_id === record.sku_id)
        if (isRepeat) {
          continue
        }
        $table.insertAt(
          {
            id: 0,
            stock_process_id: 0,
            sku_id: record.sku_id,
            goods_owner_id: 0,
            goods_location_id: 0,
            qty: 0,
            tenant_id: 0,
            is_source: false,
            spu_code: record.spu_code,
            spu_name: record.spu_name,
            sku_code: record.sku_code,
            unit: record.unit,
            is_update_stock: false
          },
          -1
        )
      }
    }
  },

  // Export table
  exportTable: (type: string) => {
    const $table = type === 'source' ? xTableSource.value : xTableTarget.value
    exportData({
      table: $table,
      filename: i18n.global.t('router.sideBar.commodityManagement'),
      columnFilterMethod({ column }: any) {
        return !['checkbox'].includes(column?.type) && !['operate'].includes(column?.field)
      }
    })
  },

  submit: async () => {
    const validSource = await method.validSourceTable()
    const validTarget = await method.validTargetTable()
    if (!validSource || !validTarget) {
      return
    }

    const form = method.constructFormBody()

    const { data: res } = await addStockProcess(form)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }
    hookComponent.$message({
      type: 'success',
      content: `${ i18n.global.t('system.page.submit') }${ i18n.global.t('system.tips.success') }`
    })
    emit('saveSuccess')
  },

  constructFormBody: () => {
    const $tableSource = xTableSource.value
    const $tableTarget = xTableTarget.value

    const tableSourceRecords = $tableSource.getTableData().fullData
    const tableTargetRecords = $tableTarget.getTableData().fullData

    // Need combine source and target to server.
    let form = { ...data.form }
    form.detailList = [...tableSourceRecords, ...tableTargetRecords]
    form = removeObjectNull(form)

    delete form.source_detail_list
    delete form.target_detail_list

    return form
  },

  validSourceTable: async () => {
    const $table = xTableSource.value
    const tableData = $table.getTableData().fullData

    // 1.The table must have data.
    if (!tableData.length) {
      hookComponent.$message({
        type: 'error',
        content: i18n.global.t('system.tips.detailLengthIsZero')
      })
      return false
    }

    // 2.The properties valid.
    const errMap = await $table.validate()
    if (errMap) {
      hookComponent.$message({
        type: 'error',
        content: i18n.global.t('system.checkText.checkFormFail')
      })
      return false
    }

    return true
  },

  validTargetTable: async () => {
    const $table = xTableTarget.value
    const tableData = $table.getTableData().fullData

    // 1.The table must have data.
    if (!tableData.length) {
      hookComponent.$message({
        type: 'error',
        content: i18n.global.t('system.tips.detailLengthIsZero')
      })
      return false
    }

    // 2.The properties valid.
    const errMap = await $table.validate()
    if (errMap) {
      hookComponent.$message({
        type: 'error',
        content: i18n.global.t('system.checkText.checkFormFail')
      })
      return false
    }

    return true
  },

  // The 'qty' can't more than 'qty_available'
  validQty: ({ cellValue, row }: any) => {
    const qty = cellValue || 0
    const qtyAvailable = row.qty_available || 0

    if (qty > qtyAvailable) {
      return new Error(`${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.qtyMoreThanAvailable') } ${ qtyAvailable }`)
    }
  },

  deleteRowSource: (row: WarehouseProcessingDetailVO) => {
    hookComponent.$dialog({
      content: i18n.global.t('system.tips.beforeDeleteDetailMessage'),
      handleConfirm: async () => {
        if (row) {
          const $table = xTableSource.value
          $table.remove(row)
        }
      }
    })
  },

  deleteRowTarget: (row: WarehouseProcessingDetailVO) => {
    hookComponent.$dialog({
      content: i18n.global.t('system.tips.beforeDeleteDetailMessage'),
      handleConfirm: async () => {
        if (row) {
          const $table = xTableTarget.value
          $table.remove(row)
        }
      }
    })
  }
})

const data = reactive({
  tableData: [],
  // 'source' | 'target'
  curSelectType: '',

  showCommodityDialogSelect: false,
  showSkuDialogSelect: false,
  showLocationDialogSelect: false,

  form: ref<WarehouseProcessingVO>({
    id: 0,
    job_code: '',
    job_type: PROCESS_JOB_COMBINE,
    process_status: false,
    processor: '',
    process_time: '',
    source_detail_list: [],
    target_detail_list: []
  }),
  curSelectRow: ref<WarehouseProcessingDetailVO>({
    id: 0,
    stock_process_id: 0,
    sku_id: 0,
    goods_owner_id: 0,
    goods_location_id: 0,
    qty: 0,
    is_source: true,
    spu_code: '',
    spu_name: '',
    sku_code: '',
    unit: '',
    is_update_stock: false
  }),
  validRulesSource: ref<any>({
    qty: [
      { required: true, message: `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.qty') }` },
      {
        validator: method.validQty
      },
      {
        validator: isInteger,
        validNumerical: 'greaterThanZero',
        trigger: 'change'
      }
    ]
  }),
  validRulesTarget: ref<any>({
    qty: [
      { required: true, message: `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.qty') }` },
      {
        validator: isInteger,
        validNumerical: 'greaterThanZero',
        trigger: 'change'
      }
    ],
    location_name: [
      {
        required: true,
        message: `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.target_location') }`
      }
    ]
  })
})

watch(
  () => isShow.value,
  (val) => {
    if (val) {
      method.initForm()
    }
  }
)
</script>

<style scoped lang="less">
.mainForm {
  background-color: #f9f9f9;
  border-radius: 5px;
  padding: 20px;
  box-sizing: border-box;
  overflow: auto;
}

.toolbar {
  height: 40px;
  display: flex;
  justify-content: space-between;
}

.toolbarTitle {
  display: flex;
  // justify-content: center;
  // align-items: center;
}
</style>
在这里插入图片描述
在这里插入图片描述

1.2 接口代码

代码语言:javascript
复制
     [Route("stockprocess")]
     [ApiController]
     [ApiExplorerSettings(GroupName = "WMS")]
     public class StockprocessController : BaseController
     {
         #region Args
 
         /// <summary>
         /// stockprocess Service
         /// </summary>
         private readonly IStockprocessService _stockprocessService;
 
         /// <summary>
         /// Localizer Service
         /// </summary>
         private readonly IStringLocalizer<ModernWMS.Core.MultiLanguage> _stringLocalizer;
         #endregion
 
         #region constructor
         /// <summary>
         /// constructor
         /// </summary>
         /// <param name="stockprocessService">stockprocess Service</param>
        /// <param name="stringLocalizer">Localizer</param>
         public StockprocessController(
             IStockprocessService stockprocessService
           , IStringLocalizer<ModernWMS.Core.MultiLanguage> stringLocalizer
             )
         {
             this._stockprocessService = stockprocessService;
            this._stringLocalizer= stringLocalizer;
         }
         #endregion
 
         #region Api
         /// <summary>
         /// page search
         /// </summary>
         /// <param name="pageSearch">args</param>
         /// <returns></returns>
         [HttpPost("list")]
         public async Task<ResultModel<PageData<StockprocessGetViewModel>>> PageAsync(PageSearch pageSearch)
         {
             var (data, totals) = await _stockprocessService.PageAsync(pageSearch, CurrentUser);
              
             return ResultModel<PageData<StockprocessGetViewModel>>.Success(new PageData<StockprocessGetViewModel>
             {
                 Rows = data,
                 Totals = totals
             });
         }
 
         /// <summary>
         /// get all records
         /// </summary>
         /// <returns>args</returns>
        [HttpGet("all")]
         public async Task<ResultModel<List<StockprocessGetViewModel>>> GetAllAsync()
         {
             var data = await _stockprocessService.GetAllAsync(CurrentUser);
             if (data.Any())
             {
                 return ResultModel<List<StockprocessGetViewModel>>.Success(data);
             }
             else
             {
                 return ResultModel<List<StockprocessGetViewModel>>.Success(new List<StockprocessGetViewModel>());
             }
         }
 
         /// <summary>
         /// get a record by id
         /// </summary>
         /// <returns>args</returns>
         [HttpGet]
         public async Task<ResultModel<StockprocessWithDetailViewModel>> GetAsync(int id)
         {
             var data = await _stockprocessService.GetAsync(id);
             if (data!=null)
             {
                 return ResultModel<StockprocessWithDetailViewModel>.Success(data);
             }
             else
             {
                 return ResultModel<StockprocessWithDetailViewModel>.Error(_stringLocalizer["not_exists_entity"]);
             }
         }
         /// <summary>
         /// add a new record
         /// </summary>
         /// <param name="viewModel">args</param>
         /// <returns></returns>
         [HttpPost]
         public async Task<ResultModel<int>> AddAsync(StockprocessViewModel viewModel)
         {
             var (id, msg) = await _stockprocessService.AddAsync(viewModel,CurrentUser);
             if (id > 0)
             {
                 return ResultModel<int>.Success(id);
             }
             else
             {
                 return ResultModel<int>.Error(msg);
             }
         }
 
         /// <summary>
         /// update a record
         /// </summary>
         /// <param name="viewModel">args</param>
         /// <returns></returns>
         [HttpPut]
         public async Task<ResultModel<bool>> UpdateAsync(StockprocessViewModel viewModel)
         {
             var (flag, msg) = await _stockprocessService.UpdateAsync(viewModel);
             if (flag)
             {
                 return ResultModel<bool>.Success(flag);
             }
             else
             {
                 return ResultModel<bool>.Error(msg, 400, flag);
             }
         }
 
         /// <summary>
         /// delete a record
         /// </summary>
         /// <param name="id">id</param>
         /// <returns></returns>
         [HttpDelete]
         public async Task<ResultModel<string>> DeleteAsync(int id)
         {
             var (flag, msg) = await _stockprocessService.DeleteAsync(id);
             if (flag)
             {
                 return ResultModel<string>.Success(msg);
             }
             else
             {
                 return ResultModel<string>.Error(msg);
             }
         }
        /// <summary>
        /// confirm processing
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpPut("process-confirm")]
        public async Task<ResultModel<string>> ConfirmProcess(int id)
        {
            var (flag, msg) = await _stockprocessService.ConfirmProcess(id,CurrentUser);
            if (flag)
            {
                return ResultModel<string>.Success(msg);
            }
            else
            {
                return ResultModel<string>.Error(msg);
            }
        }

        /// <summary>
        /// confirm adjustment
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpPut("adjustment-confirm")]
        public async Task<ResultModel<string>> ConfirmAdjustment(int id)
        {
            var (flag, msg) = await _stockprocessService.ConfirmAdjustment(id, CurrentUser);
            if (flag)
            {
                return ResultModel<string>.Success(msg);
            }
            else
            {
                return ResultModel<string>.Error(msg);
            }
        }
        #endregion

    }
在这里插入图片描述
在这里插入图片描述

2.库存移动

在这里插入图片描述
在这里插入图片描述

库存移动主要是库位的移动

2.1 页面代码

代码语言:javascript
复制
<!-- Warehouse Move -->
<template>
  <div class="container">
    <div>
      <!-- Main Content -->
      <v-card class="mt-5">
        <v-card-text>
          <v-window v-model="data.activeTab">
            <v-window-item>
              <div class="operateArea">
                <v-row no-gutters>
                  <!-- Operate Btn -->
                  <v-col cols="3" class="col">
                    <tooltip-btn icon="mdi-plus" :tooltip-text="$t('system.page.add')" @click="method.add()"></tooltip-btn>
                    <tooltip-btn icon="mdi-refresh" :tooltip-text="$t('system.page.refresh')" @click="method.refresh"></tooltip-btn>
                    <tooltip-btn icon="mdi-export-variant" :tooltip-text="$t('system.page.export')" @click="method.exportTable"> </tooltip-btn>
                  </v-col>

                  <!-- Search Input -->
                  <v-col cols="9">
                    <v-row no-gutters @keyup.enter="method.sureSearch">
                      <v-col cols="4"></v-col>
                      <v-col cols="4"></v-col>
                      <v-col cols="4">
                        <v-text-field
                          v-model="data.searchForm.job_code"
                          clearable
                          hide-details
                          density="comfortable"
                          class="searchInput ml-5 mt-1"
                          :label="$t('wms.warehouseWorking.warehouseMove.job_code')"
                          variant="solo"
                        >
                        </v-text-field>
                      </v-col>
                    </v-row>
                  </v-col>
                </v-row>
              </div>

              <!-- Table -->
              <div
                class="mt-5"
                :style="{
                  height: cardHeight
                }"
              >
                <vxe-table ref="xTable" :column-config="{ minWidth: '100px' }" :data="data.tableData" :height="tableHeight" align="center">
                  <template #empty>
                    {{ i18n.global.t('system.page.noData') }}
                  </template>
                  <vxe-column type="seq" width="60"></vxe-column>
                  <vxe-column type="checkbox" width="50"></vxe-column>
                  <vxe-column field="job_code" width="150px" :title="$t('wms.warehouseWorking.warehouseMove.job_code')"></vxe-column>
                  <vxe-column field="move_status" width="150px" :title="$t('wms.warehouseWorking.warehouseMove.move_status')">
                    <template #default="{ row, column }">
                      <span>{{ formatMoveStatus(row[column.property]) }}</span>
                    </template>
                  </vxe-column>
                  <vxe-column field="spu_code" width="150px" :title="$t('base.commodityManagement.spu_code')"></vxe-column>
                  <vxe-column field="spu_name" width="150px" :title="$t('base.commodityManagement.spu_name')"></vxe-column>
                  <vxe-column field="sku_code" width="150px" :title="$t('base.commodityManagement.sku_code')"></vxe-column>
                  <vxe-column field="sku_name" width="150px" :title="$t('base.commodityManagement.sku_name')"></vxe-column>
                  <vxe-column field="qty" width="150px" :title="$t('wms.warehouseWorking.warehouseMove.qty')"></vxe-column>
                  <vxe-column
                    field="orig_goods_warehouse"
                    width="150px"
                    :title="$t('wms.warehouseWorking.warehouseMove.orig_goods_warehouse')"
                  ></vxe-column>
                  <vxe-column
                    field="orig_goods_location_name"
                    width="150px"
                    :title="$t('wms.warehouseWorking.warehouseMove.orig_goods_location_name')"
                  ></vxe-column>
                  <vxe-column
                    field="dest_googs_warehouse"
                    width="150px"
                    :title="$t('wms.warehouseWorking.warehouseMove.dest_googs_warehouse')"
                  ></vxe-column>
                  <vxe-column
                    field="dest_googs_location_name"
                    width="150px"
                    :title="$t('wms.warehouseWorking.warehouseMove.dest_googs_location_name')"
                  ></vxe-column>
                  <vxe-column field="handler" width="150px" :title="$t('wms.warehouseWorking.warehouseMove.handler')"></vxe-column>
                  <vxe-column field="handle_time" width="170px" :title="$t('wms.warehouseWorking.warehouseMove.handle_time')">
                    <template #default="{ row, column }">
                      <span>{{ formatDate(row[column.property]) }}</span>
                    </template>
                  </vxe-column>
                  <vxe-column field="creator" :title="$t('wms.warehouseWorking.warehouseMove.creator')"></vxe-column>
                  <vxe-column field="create_time" width="170px" :title="$t('wms.warehouseWorking.warehouseMove.create_time')"></vxe-column>
                  <vxe-column field="operate" :title="$t('system.page.operate')" width="250" :resizable="false" show-overflow>
                    <template #default="{ row }">
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-eye-outline"
                        :tooltip-text="$t('system.page.view')"
                        @click="method.viewRow(row)"
                      ></tooltip-btn>
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-book-open-outline"
                        :tooltip-text="$t('wms.warehouseWorking.warehouseMove.confirmMove')"
                        :disabled="method.confirmMoveBtnDisabled(row)"
                        @click="method.confirmMove(row)"
                      ></tooltip-btn>
                      <tooltip-btn
                        :flat="true"
                        icon="mdi-delete-outline"
                        :tooltip-text="$t('system.page.delete')"
                        :icon-color="errorColor"
                        :disabled="method.confirmMoveBtnDisabled(row)"
                        @click="method.deleteRow(row)"
                      ></tooltip-btn>
                    </template>
                  </vxe-column>
                </vxe-table>
                <custom-pager
                  :current-page="data.tablePage.pageIndex"
                  :page-size="data.tablePage.pageSize"
                  perfect
                  :total="data.tablePage.total"
                  :page-sizes="PAGE_SIZE"
                  :layouts="PAGE_LAYOUT"
                  @page-change="method.handlePageChange"
                >
                </custom-pager>
              </div>
            </v-window-item>
          </v-window>
        </v-card-text>
      </v-card>
      <addOrUpdateDialog
        :show-dialog="data.showDialog"
        :form="data.dialogForm"
        :process-type="data.processType"
        @close="method.closeDialog"
        @saveSuccess="method.saveSuccess"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import { computed, ref, reactive, onActivated, watch, nextTick } from 'vue'
import { VxePagerEvents } from 'vxe-table'
import { computedCardHeight, computedTableHeight, errorColor } from '@/constant/style'
import { WarehouseMoveVO, MoveStatus } from '@/types/WarehouseWorking/WarehouseMove'
import { PAGE_SIZE, PAGE_LAYOUT, DEFAULT_PAGE_SIZE } from '@/constant/vxeTable'
import { hookComponent } from '@/components/system'
import { deleteStockMove, getStockMoveList, getStockMoveOne, confirmMove } from '@/api/wms/warehouseMove'
import { PROCESS_JOB_COMBINE } from '@/constant/warehouseWorking'
import { DEBOUNCE_TIME } from '@/constant/system'
import { setSearchObject } from '@/utils/common'
import { SearchObject } from '@/types/System/Form'
import { formatMoveStatus } from '@/utils/format/formatWarehouseWorking'
import { formatDate } from '@/utils/format/formatSystem'
import tooltipBtn from '@/components/tooltip-btn.vue'
import addOrUpdateDialog from './add-or-update-move.vue'
import i18n from '@/languages/i18n'
import customPager from '@/components/custom-pager.vue'
import { exportData } from '@/utils/exportTable'

const xTable = ref()

const data = reactive({
  showDialog: false,
  processType: PROCESS_JOB_COMBINE,
  timer: ref<any>(null),
  activeTab: null,
  searchForm: {
    job_code: ''
  },
  tableData: ref<WarehouseMoveVO[]>([]),
  dialogForm: {
    id: 0,
    job_code: '',
    move_status: MoveStatus.UNADJUST,
    sku_id: 0,
    orig_goods_location_id: 0,
    dest_googs_location_id: 0,
    qty: 0,
    goods_owner_id: 0,
    handler: '',
    handle_time: '',
    orig_goods_warehouse: '',
    orig_goods_location_name: '',
    dest_googs_warehouse: '',
    dest_googs_location_name: '',
    spu_code: '',
    spu_name: '',
    sku_code: '',
    sku_name: '',
    creator: '',
    create_time: ''
  },
  tablePage: reactive({
    total: 0,
    pageIndex: 1,
    pageSize: DEFAULT_PAGE_SIZE,
    searchObjects: ref<Array<SearchObject>>([])
  })
})

const method = reactive({
  // Open a dialog to add
  add: () => {
    data.dialogForm = {
      id: 0,
      job_code: '',
      move_status: MoveStatus.UNADJUST,
      sku_id: 0,
      orig_goods_location_id: 0,
      dest_googs_location_id: 0,
      qty: 0,
      goods_owner_id: 0,
      handler: '',
      handle_time: '',
      orig_goods_warehouse: '',
      orig_goods_location_name: '',
      dest_googs_warehouse: '',
      dest_googs_location_name: '',
      spu_code: '',
      spu_name: '',
      sku_code: '',
      sku_name: '',
      creator: '',
      create_time: ''
    }
    nextTick(() => {
      data.showDialog = true
    })
  },

  // After add or update success.
  saveSuccess: () => {
    method.refresh()
    method.closeDialog()
  },

  // Refresh data
  refresh: () => {
    method.getStockProcessList()
  },

  // Shut add or update dialog
  closeDialog: () => {
    data.showDialog = false
  },

  getStockProcessList: async () => {
    const { data: res } = await getStockMoveList(data.tablePage)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }
    data.tableData = res.data.rows
    data.tablePage.total = res.data.totals
  },

  viewRow: async (row: WarehouseMoveVO) => {
    await method.getOne(row.id)
    nextTick(() => {
      data.showDialog = true
    })
  },

  getOne: async (id: number) => {
    const { data: res } = await getStockMoveOne(id)
    if (!res.isSuccess) {
      hookComponent.$message({
        type: 'error',
        content: res.errorMessage
      })
      return
    }

    data.dialogForm = res.data
  },

  deleteRow(row: WarehouseMoveVO) {
    hookComponent.$dialog({
      content: i18n.global.t('system.tips.beforeDeleteMessage'),
      handleConfirm: async () => {
        if (row.id) {
          const { data: res } = await deleteStockMove(row.id)
          if (!res.isSuccess) {
            hookComponent.$message({
              type: 'error',
              content: res.errorMessage
            })
            return
          }

          hookComponent.$message({
            type: 'success',
            content: `${ i18n.global.t('system.page.delete') }${ i18n.global.t('system.tips.success') }`
          })
          method.refresh()
        }
      }
    })
  },

  handlePageChange: ref<VxePagerEvents.PageChange>(({ currentPage, pageSize }) => {
    data.tablePage.pageIndex = currentPage
    data.tablePage.pageSize = pageSize
    method.refresh()
  }),

  exportTable: () => {
    const $table = xTable.value
    exportData({
      table: $table,
      filename: i18n.global.t('router.sideBar.warehouseMove'),
      columnFilterMethod({ column }: any) {
        return !['checkbox'].includes(column?.type) && !['operate'].includes(column?.field)
      }
    })
  },

  sureSearch: () => {
    data.tablePage.searchObjects = setSearchObject(data.searchForm)
    method.refresh()
  },

  // The btn will become disabled when the 'process_status' is false
  confirmMoveBtnDisabled: (row: WarehouseMoveVO) => row.move_status === MoveStatus.ADJUSTED,

  confirmMove: async (row: WarehouseMoveVO) => {
    hookComponent.$dialog({
      content: i18n.global.t('wms.warehouseWorking.warehouseMove.beforeConfirmMove'),
      handleConfirm: async () => {
        if (row.id) {
          const { data: res } = await confirmMove(row.id)
          if (!res.isSuccess) {
            hookComponent.$message({
              type: 'error',
              content: res.errorMessage
            })
            return
          }
          hookComponent.$message({
            type: 'success',
            content: `${ i18n.global.t('wms.warehouseWorking.warehouseMove.confirmMove') }${ i18n.global.t('system.tips.success') }`
          })
          method.refresh()
        }
      }
    })
  }
})

onActivated(() => {
  method.refresh()
})

const cardHeight = computed(() => computedCardHeight({ hasTab: false }))
const tableHeight = computed(() => computedTableHeight({ hasTab: false }))

watch(
  () => data.searchForm,
  () => {
    // debounce
    if (data.timer) {
      clearTimeout(data.timer)
    }
    data.timer = setTimeout(() => {
      data.timer = null
      method.sureSearch()
    }, DEBOUNCE_TIME)
  },
  {
    deep: true
  }
)
</script>

<style scoped lang="less">
.operateArea {
  width: 100%;
  min-width: 760px;
  display: flex;
  align-items: center;
  border-radius: 10px;
  padding: 0 10px;
}

.col {
  display: flex;
  align-items: center;
}
</style>
在这里插入图片描述
在这里插入图片描述
代码语言:javascript
复制
<!-- Warehouse Move Operate Dialog -->
<template>
  <v-dialog v-model="isShow" :width="'30%'" transition="dialog-top-transition" :persistent="true">
    <template #default>
      <v-card>
        <v-toolbar class="" color="white" :title="`${$t('router.sideBar.warehouseMove')}`"></v-toolbar>
        <v-card-text>
          <v-form ref="formRef">
            <v-text-field
              v-model="data.form.spu_code"
              :label="$t('base.commodityManagement.spu_code')"
              :rules="data.rules.spu_code"
              variant="outlined"
              readonly
              clearable
              @click="method.openCommoditySelect"
              @click:clear="method.clearCommodity"
            ></v-text-field>
            <v-text-field
              v-model="data.form.spu_name"
              :label="$t('base.commodityManagement.spu_name')"
              :rules="data.rules.spu_name"
              variant="outlined"
              disabled
            ></v-text-field>
            <v-text-field
              v-model="data.form.sku_code"
              :label="$t('base.commodityManagement.sku_code')"
              :rules="data.rules.sku_code"
              variant="outlined"
              disabled
            ></v-text-field>
            <!-- <v-text-field
              v-model="data.form.sku_name"
              :label="$t('base.commodityManagement.sku_name')"
              :rules="data.rules.sku_name"
              variant="outlined"
              disabled
            ></v-text-field> -->
            <v-text-field
              v-model="data.form.orig_goods_warehouse"
              :label="$t('wms.warehouseWorking.warehouseMove.orig_goods_warehouse')"
              :rules="data.rules.orig_goods_warehouse"
              variant="outlined"
              disabled
            ></v-text-field>
            <v-text-field
              v-model="data.form.orig_goods_location_name"
              :label="$t('wms.warehouseWorking.warehouseMove.orig_goods_location_name')"
              :rules="data.rules.orig_goods_location_name"
              variant="outlined"
              disabled
            ></v-text-field>
            <v-text-field
              v-model="data.form.dest_googs_warehouse"
              :label="$t('wms.warehouseWorking.warehouseMove.dest_googs_warehouse')"
              :rules="data.rules.dest_googs_warehouse"
              variant="outlined"
              disabled
            ></v-text-field>
            <v-text-field
              v-model="data.form.dest_googs_location_name"
              :label="$t('wms.warehouseWorking.warehouseMove.dest_googs_location_name')"
              :rules="data.rules.dest_googs_location_name"
              variant="outlined"
              readonly
              clearable
              @click="method.openLocationSelect"
              @click:clear="method.clearLocation"
            ></v-text-field>
            <v-text-field
              v-model="data.form.qty"
              :label="$t('wms.warehouseWorking.warehouseMove.qty')"
              :rules="data.rules.qty"
              variant="outlined"
            ></v-text-field>
          </v-form>
        </v-card-text>
        <v-card-actions class="justify-end">
          <v-btn variant="text" @click="method.closeDialog">{{ $t('system.page.close') }}</v-btn>
          <v-btn color="primary" variant="text" :disabled="operateDisabled" @click="method.submit">{{ $t('system.page.submit') }}</v-btn>
        </v-card-actions>
      </v-card>
    </template>
  </v-dialog>

  <commodity-select
    :show-dialog="data.showCommodityDialogSelect"
    @close="method.closeCommodityDialogSelect"
    @sureSelect="method.sureSelectCommodity"
  />
  <location-select :show-dialog="data.showLocationDialogSelect" @close="method.closeLocationDialogSelect" @sureSelect="method.sureSelectLocation" />
</template>

<script lang="ts" setup>
import { reactive, computed, ref, watch } from 'vue'
import i18n from '@/languages/i18n'
import { hookComponent } from '@/components/system/index'
import { addStockMove } from '@/api/wms/warehouseMove'
import { WarehouseMoveVO, MoveStatus } from '@/types/WarehouseWorking/WarehouseMove'
import { removeObjectNull } from '@/utils/common'
import commoditySelect from '@/components/select/commodity-select.vue'
import locationSelect from '@/components/select/location-select.vue'
import { IsInteger } from '@/utils/dataVerification/formRule'

const formRef = ref()
const emit = defineEmits(['close', 'saveSuccess'])
const isUpdate = computed(() => props.form.id && props.form.id > 0)
const operateDisabled = computed(() => !!isUpdate.value)

const props = defineProps<{
  showDialog: boolean
  form: WarehouseMoveVO
}>()

const isShow = computed(() => props.showDialog)

const data = reactive({
  showCommodityDialogSelect: false,
  showLocationDialogSelect: false,

  // Available Qty
  curAvailableQty: 0,

  form: ref<WarehouseMoveVO>({
    id: 0,
    job_code: '',
    move_status: MoveStatus.UNADJUST,
    sku_id: 0,
    orig_goods_location_id: 0,
    dest_googs_location_id: 0,
    qty: 0,
    goods_owner_id: 0,
    handler: '',
    handle_time: '',
    orig_goods_warehouse: '',
    orig_goods_location_name: '',
    dest_googs_warehouse: '',
    dest_googs_location_name: '',
    spu_code: '',
    spu_name: '',
    sku_code: '',
    sku_name: '',
    creator: '',
    create_time: ''
  }),
  rules: {
    qty: [
      (val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseMove.qty') }!`,
      (val: number) => IsInteger(val, 'greaterThanZero') === '' || IsInteger(val, 'greaterThanZero'),
      (val: string) => method.validQty(val)
    ],
    orig_goods_warehouse: [
      (val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseMove.orig_goods_warehouse') }!`
    ],
    orig_goods_location_name: [
      (val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseMove.orig_goods_location_name') }!`
    ],
    dest_googs_warehouse: [
      (val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseMove.dest_googs_warehouse') }!`
    ],
    dest_googs_location_name: [
      (val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('wms.warehouseWorking.warehouseMove.dest_googs_location_name') }!`
    ],
    spu_code: [(val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('base.commodityManagement.spu_code') }!`],
    spu_name: [(val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('base.commodityManagement.spu_name') }!`],
    sku_code: [(val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('base.commodityManagement.sku_code') }!`],
    sku_name: [(val: string) => !!val || `${ i18n.global.t('system.checkText.mustInput') }${ i18n.global.t('base.commodityManagement.sku_name') }!`]
  }
})

const method = reactive({
  // The 'qty' can't more than 'qty_available'
  validQty: (value: string): boolean | string => {
    let inputQty = Number(value)
    if (Number.isNaN(inputQty)) {
      inputQty = 0
    }

    if (inputQty > data.curAvailableQty) {
      return `${ i18n.global.t('wms.warehouseWorking.warehouseProcessing.qtyMoreThanAvailable') }${ data.curAvailableQty }`
    }
    return true
  },
  closeDialog: () => {
    emit('close')
  },
  initForm: () => {
    data.form = props.form
  },

  openCommoditySelect: () => {
    // Open select modal after UI rendered.
    setTimeout(() => {
      data.showCommodityDialogSelect = true
    }, 100)
  },

  closeCommodityDialogSelect: () => {
    data.showCommodityDialogSelect = false
  },

  openLocationSelect: () => {
    setTimeout(() => {
      data.showLocationDialogSelect = true
    }, 100)
  },

  closeLocationDialogSelect: () => {
    data.showLocationDialogSelect = false
  },

  sureSelectCommodity: (selectRecords: any) => {
    try {
      data.form.sku_id = selectRecords[0].sku_id
      data.form.orig_goods_location_id = selectRecords[0].goods_location_id
      data.form.orig_goods_warehouse = selectRecords[0].warehouse_name
      data.form.orig_goods_location_name = selectRecords[0].location_name
      data.form.goods_owner_id = selectRecords[0].goods_owner_id
      data.form.spu_code = selectRecords[0].spu_code
      data.form.spu_name = selectRecords[0].spu_name
      data.form.sku_code = selectRecords[0].sku_code
      data.form.sku_name = selectRecords[0].sku_name

      data.curAvailableQty = selectRecords[0].qty_available
    } catch (error) {
      console.error(error)
    }
  },

  sureSelectLocation: (selectRecords: any) => {
    try {
      data.form.dest_googs_location_id = selectRecords[0].id
      data.form.dest_googs_warehouse = selectRecords[0].warehouse_name
      data.form.dest_googs_location_name = selectRecords[0].location_name
    } catch (error) {
      console.error(error)
    }
  },

  clearCommodity: () => {
    data.form.sku_id = 0
    data.form.orig_goods_location_id = 0
    data.form.orig_goods_warehouse = ''
    data.form.orig_goods_location_name = ''
    data.form.spu_code = ''
    data.form.spu_name = ''
    data.form.sku_code = ''
    data.form.sku_name = ''

    data.curAvailableQty = 0
  },

  clearLocation: () => {
    data.form.dest_googs_location_id = 0
    data.form.dest_googs_warehouse = ''
    data.form.dest_googs_location_name = ''
  },

  submit: async () => {
    const { valid } = await formRef.value.validate()

    const form = method.constructFormBody()

    if (valid) {
      const { data: res } = await addStockMove(form)
      if (!res.isSuccess) {
        hookComponent.$message({
          type: 'error',
          content: res.errorMessage
        })
        return
      }
      hookComponent.$message({
        type: 'success',
        content: `${ i18n.global.t('system.page.submit') }${ i18n.global.t('system.tips.success') }`
      })
      emit('saveSuccess')
    } else {
      hookComponent.$message({
        type: 'error',
        content: i18n.global.t('system.checkText.checkFormFail')
      })
    }
  },

  constructFormBody: () => {
    let form = { ...data.form }
    form = removeObjectNull(form)

    return form
  }
})

watch(
  () => isShow.value,
  (val) => {
    if (val) {
      method.initForm()
    }
  }
)
</script>

<style scoped lang="less">
.v-form {
  div {
    margin-bottom: 7px;
  }
}
</style>
在这里插入图片描述
在这里插入图片描述

2.2 接口代码

代码语言:javascript
复制
     [Route("stockmove")]
     [ApiController]
     [ApiExplorerSettings(GroupName = "WMS")]
     public class StockmoveController : BaseController
     {
         #region Args
 
         /// <summary>
         /// stockmove Service
         /// </summary>
         private readonly IStockmoveService _stockmoveService;
 
         /// <summary>
         /// Localizer Service
         /// </summary>
         private readonly IStringLocalizer<ModernWMS.Core.MultiLanguage> _stringLocalizer;
         #endregion
 
         #region constructor
         /// <summary>
         /// constructor
         /// </summary>
         /// <param name="stockmoveService">stockmove Service</param>
        /// <param name="stringLocalizer">Localizer</param>
         public StockmoveController(
             IStockmoveService stockmoveService
           , IStringLocalizer<ModernWMS.Core.MultiLanguage> stringLocalizer
             )
         {
             this._stockmoveService = stockmoveService;
            this._stringLocalizer= stringLocalizer;
         }
         #endregion
 
         #region Api
         /// <summary>
         /// page search
         /// </summary>
         /// <param name="pageSearch">args</param>
         /// <returns></returns>
         [HttpPost("list")]
         public async Task<ResultModel<PageData<StockmoveViewModel>>> PageAsync(PageSearch pageSearch)
         {
             var (data, totals) = await _stockmoveService.PageAsync(pageSearch, CurrentUser);
              
             return ResultModel<PageData<StockmoveViewModel>>.Success(new PageData<StockmoveViewModel>
             {
                 Rows = data,
                 Totals = totals
             });
         }
 
         /// <summary>
         /// get all records
         /// </summary>
         /// <returns>args</returns>
        [HttpGet("all")]
         public async Task<ResultModel<List<StockmoveViewModel>>> GetAllAsync()
         {
             var data = await _stockmoveService.GetAllAsync(CurrentUser);
             if (data.Any())
             {
                 return ResultModel<List<StockmoveViewModel>>.Success(data);
             }
             else
             {
                 return ResultModel<List<StockmoveViewModel>>.Success(new List<StockmoveViewModel>());
             }
         }
 
         /// <summary>
         /// get a record by id
         /// </summary>
         /// <returns>args</returns>
         [HttpGet]
         public async Task<ResultModel<StockmoveViewModel>> GetAsync(int id)
         {
             var data = await _stockmoveService.GetAsync(id);
             if (data!=null)
             {
                 return ResultModel<StockmoveViewModel>.Success(data);
             }
             else
             {
                 return ResultModel<StockmoveViewModel>.Error(_stringLocalizer["not_exists_entity"]);
             }
         }
         /// <summary>
         /// add a new record
         /// </summary>
         /// <param name="viewModel">args</param>
         /// <returns></returns>
         [HttpPost]
         public async Task<ResultModel<int>> AddAsync(StockmoveViewModel viewModel)
         {
             var (id, msg) = await _stockmoveService.AddAsync(viewModel,CurrentUser);
             if (id > 0)
             {
                 return ResultModel<int>.Success(id);
             }
             else
             {
                 return ResultModel<int>.Error(msg);
             }
         }
 
         /// <summary>
         /// confirm move
         /// </summary>
         /// <param name="id">id</param>
         /// <returns></returns>
         [HttpPut]
         public async Task<ResultModel<bool>> Confirm(int id)
         {
             var (flag, msg) = await _stockmoveService.Confirm(id,CurrentUser);
             if (flag)
             {
                 return ResultModel<bool>.Success(flag);
             }
             else
             {
                 return ResultModel<bool>.Error(msg, 400, flag);
             }
         }
 
         /// <summary>
         /// delete a record
         /// </summary>
         /// <param name="id">id</param>
         /// <returns></returns>
         [HttpDelete]
         public async Task<ResultModel<string>> DeleteAsync(int id)
         {
             var (flag, msg) = await _stockmoveService.DeleteAsync(id);
             if (flag)
             {
                 return ResultModel<string>.Success(msg);
             }
             else
             {
                 return ResultModel<string>.Error(msg);
             }
         }
         #endregion
 
     }
在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-02-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 前言
  • 一、库存管理
    • 1.1 页面代码
      • 1.2 接口代码
      • 二、仓内作业
        • 1.仓内加工
          • 1.1 页面代码
          • 1.2 接口代码
        • 2.库存移动
          • 2.1 页面代码
          • 2.2 接口代码
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档