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

举报
愚公搬代码 发表于 2023/03/31 22:53:23 2023/03/31
【摘要】 前言这节主要分为两个模块:• 库存管理:库存管理的作用是确保有足够的库存量以满足消费者需求,减少库存空置和库存损耗,并有效地控制库存成本。• 仓内作业:仓内作业的作用是帮助仓库提高组织效率和完成仓库管理。它提高了仓库存储条件、运输条件和信息系统的效率,并节省了人力成本。一、库存管理库存管理数据主要是来源于收获管理,所哟库存和库位基本只有查询功能。1.1 页面代码<!-- Warehouse ...

前言

这节主要分为两个模块:

• 库存管理:库存管理的作用是确保有足够的库存量以满足消费者需求,减少库存空置和库存损耗,并有效地控制库存成本。

• 仓内作业:仓内作业的作用是帮助仓库提高组织效率和完成仓库管理。它提高了仓库存储条件、运输条件和信息系统的效率,并节省了人力成本。


一、库存管理


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

1.1 页面代码

<!-- 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>


<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>


<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 接口代码

[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 页面代码

<!-- 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>


<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 接口代码

[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 页面代码

<!-- 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>


<!-- 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 接口代码

[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

}



【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。