add ProductStockRecordDetail.vue
This commit is contained in:
+24
-2
@@ -1,5 +1,17 @@
|
||||
import { axios } from "timi-web-api";
|
||||
import type { Product, ProductCategory, ProductCategoryPage, ProductPage, ProductPageResult, ProductStock, ProductStockChangeRequest, ProductStockPage, StockRecord, StockRecordPage } from "@/types/Product";
|
||||
import type {
|
||||
Product,
|
||||
ProductCategory,
|
||||
ProductCategoryPage,
|
||||
ProductPage,
|
||||
ProductPageResult,
|
||||
ProductStock,
|
||||
ProductStockChangeRequest,
|
||||
ProductStockManualRevokeRequest,
|
||||
ProductStockPage,
|
||||
StockRecord,
|
||||
StockRecordPage
|
||||
} from "@/types/Product";
|
||||
import { getCurrentStoreId, withCurrentStoreId } from "@/utils/store";
|
||||
|
||||
const PRODUCT_URI = "/gao/product";
|
||||
@@ -71,6 +83,10 @@ async function manualOut(request: ProductStockChangeRequest): Promise<ProductSto
|
||||
return await axios.post(`${STOCK_URI}/manual-out`, withCurrentStoreId(request));
|
||||
}
|
||||
|
||||
async function manualRevoke(request: ProductStockManualRevokeRequest): Promise<ProductStock> {
|
||||
return await axios.post(`${STOCK_URI}/manual-revoke`, request);
|
||||
}
|
||||
|
||||
async function stocktakeGain(request: ProductStockChangeRequest): Promise<ProductStock> {
|
||||
return await axios.post(`${STOCK_URI}/stocktake-gain`, withCurrentStoreId(request));
|
||||
}
|
||||
@@ -83,6 +99,10 @@ async function recordList(page: StockRecordPage): Promise<ProductPageResult<Stoc
|
||||
return await axios.post(`${STOCK_URI}/record/list`, withCurrentStoreId(page));
|
||||
}
|
||||
|
||||
async function recordDetail(id: string): Promise<StockRecord> {
|
||||
return await axios.post(`${STOCK_URI}/record/detail`, undefined, { params: { id } });
|
||||
}
|
||||
|
||||
export default {
|
||||
categoryList,
|
||||
categoryCreate,
|
||||
@@ -100,7 +120,9 @@ export default {
|
||||
initialIn,
|
||||
manualIn,
|
||||
manualOut,
|
||||
manualRevoke,
|
||||
stocktakeGain,
|
||||
stocktakeLoss,
|
||||
recordList
|
||||
recordList,
|
||||
recordDetail
|
||||
};
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
<template>
|
||||
<div v-can="permissionCodes.productStock.read" class="product-stock-record-detail">
|
||||
<t-loading v-if="isLoading" class="loading" size="32px" />
|
||||
<template v-else-if="record && product">
|
||||
<product-profile :product="product" v-entrance-transition>
|
||||
<template #tags>
|
||||
<t-tag
|
||||
size="small"
|
||||
variant="light-outline"
|
||||
:theme="getOperationTheme(record.onHandDelta)"
|
||||
:content="getStockOperationText(record.operationType)"
|
||||
/>
|
||||
</template>
|
||||
</product-profile>
|
||||
<t-cell-group title="库存流水" theme="card" v-entrance-transition="70">
|
||||
<t-cell title="操作类型" :note="getStockOperationText(record.operationType)" />
|
||||
<t-cell title="业务数量" :note="getQuantityText(record)" />
|
||||
<t-cell title="基础单位数量" :note="getProductQuantityText(record.baseQuantity)" />
|
||||
<t-cell title="实际库存变动">
|
||||
<template #note>
|
||||
<span :class="getDeltaClass(record.onHandDelta)" v-text="getDeltaText(record.onHandDelta)" />
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell title="预占库存变动">
|
||||
<template #note>
|
||||
<span :class="getDeltaClass(record.reservedDelta)" v-text="getDeltaText(record.reservedDelta)" />
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell title="实际库存">
|
||||
<template #note>
|
||||
<span class="balance">
|
||||
<span v-text="getProductQuantityText(record.onHandBefore)" />
|
||||
<t-icon name="arrow-right" />
|
||||
<span :class="getDeltaClass(record.onHandDelta)" v-text="getProductQuantityText(record.onHandAfter)" />
|
||||
<span class="unit" v-text="getProductBaseUnitName(product) || '基础单位'" />
|
||||
</span>
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell title="预占库存">
|
||||
<template #note>
|
||||
<span class="balance">
|
||||
<span v-text="getProductQuantityText(record.reservedBefore)" />
|
||||
<t-icon name="arrow-right" />
|
||||
<span :class="getDeltaClass(record.reservedDelta)" v-text="getProductQuantityText(record.reservedAfter)" />
|
||||
<span class="unit" v-text="getProductBaseUnitName(product) || '基础单位'" />
|
||||
</span>
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell title="业务类型" :note="getBizTypeText(record.bizType)" />
|
||||
<t-cell title="发生时间">
|
||||
<template #note>
|
||||
<pass-time-label :timestamp="record.occurredAt" underline />
|
||||
</template>
|
||||
</t-cell>
|
||||
<t-cell title="操作人" :note="record.operator?.name || record.operatorUserId || '未知操作人'" />
|
||||
<t-cell v-if="record.sourceRecordId" title="原库存流水" :note="record.sourceRecordId" arrow @click="openRelatedRecord(record.sourceRecordId)" />
|
||||
<t-cell v-if="record.revokeRecordId" title="冲正流水" :note="record.revokeRecordId" arrow @click="openRelatedRecord(record.revokeRecordId)" />
|
||||
<t-cell v-if="record.remark" title="备注" :note="record.remark" />
|
||||
<t-cell>
|
||||
<template #title>
|
||||
<span></span>
|
||||
</template>
|
||||
<template #description>
|
||||
<photo-scroller
|
||||
:model-value="photoURLList"
|
||||
:photo-items="photoItemList"
|
||||
:show-add="false"
|
||||
:show-remove="false"
|
||||
:show-viewer-actions="false"
|
||||
title="操作照片"
|
||||
/>
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
<t-button
|
||||
v-if="canManualRevoke"
|
||||
class="cell-button"
|
||||
theme="danger"
|
||||
@click="openManualRevoke"
|
||||
v-entrance-transition="140"
|
||||
content="手工流水冲正"
|
||||
/>
|
||||
</template>
|
||||
<t-empty v-else :description="isLoading ? '正在加载库存流水' : '库存流水不存在'" />
|
||||
<bottom-popup
|
||||
v-model="revokeVisible"
|
||||
title="手工流水冲正"
|
||||
left-button-text="取消"
|
||||
:right-button-text="isOperating ? '提交中' : '提交'"
|
||||
@left-button-click="revokeVisible = false"
|
||||
@right-button-click="submitManualRevoke"
|
||||
>
|
||||
<div class="revoke-popup">
|
||||
<p class="tip">将新增一条反向库存流水,原流水不会被修改或删除。</p>
|
||||
<t-form>
|
||||
<t-form-item label="冲正备注" required-mark>
|
||||
<t-textarea v-model="revokeRemark" borderless autosize maxlength="500" placeholder="请输入冲正备注" />
|
||||
</t-form-item>
|
||||
</t-form>
|
||||
<package-photo-field v-model="revokePhotoItems" required title="冲正凭证照片" />
|
||||
</div>
|
||||
</bottom-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { BottomPopup } from "timi-tdesign-mobile";
|
||||
import { PassTimeLabel } from "timi-web";
|
||||
import { useUserStore, type Attachment } from "timi-web-api";
|
||||
import ProductAPI from "@/api/ProductAPI";
|
||||
import PhotoScroller from "@/components/PhotoScroller.vue";
|
||||
import ProductProfile from "@/pages/product/component/ProductProfile.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { PhotoItem } from "@/types/Photo";
|
||||
import type { Product, StockBizType, StockRecord } from "@/types/Product";
|
||||
import { getAttachmentReadURL } from "@/utils/attachment";
|
||||
import { getErrorMessage } from "@/utils/error";
|
||||
import { createProductRequestNo, getProductBaseUnitName, getProductQuantityText, getStockOperationText } from "@/utils/product";
|
||||
import { Message } from "@/utils/message";
|
||||
import { permissionCodes } from "@/utils/permission";
|
||||
|
||||
defineOptions({ name: "ProductStockRecordDetail" });
|
||||
|
||||
const route = useRoute();
|
||||
const navBarStore = useNavBarStore();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
const record = ref<StockRecord>();
|
||||
const product = ref<Product>();
|
||||
const isLoading = ref(false);
|
||||
const isOperating = ref(false);
|
||||
const revokeVisible = ref(false);
|
||||
const revokeRemark = ref("");
|
||||
const revokeRequestNo = ref("");
|
||||
const revokePhotoItems = ref<PhotoItem[]>([]);
|
||||
const canManualRevoke = computed(() => {
|
||||
const operationType = record.value?.operationType;
|
||||
return (operationType === "MANUAL_IN" || operationType === "MANUAL_OUT")
|
||||
&& !record.value?.sourceRecordId
|
||||
&& !record.value?.revokeRecordId
|
||||
&& userStore.hasPermissions(permissionCodes.productStock.manualRevoke);
|
||||
});
|
||||
const photoItemList = computed<PhotoItem[]>(() => (record.value?.attachmentList || [])
|
||||
.filter(item => !!item.id)
|
||||
.map(item => ({
|
||||
attachment: item,
|
||||
attachType: item.attachType || "PHOTO",
|
||||
previewUrl: getAttachmentReadURL(item.id || ""),
|
||||
url: getAttachmentReadURL(item.id || "", 128)
|
||||
})));
|
||||
const photoURLList = computed(() => photoItemList.value.map(item => item.url));
|
||||
|
||||
function getOperationTheme(value?: number | string): "success" | "danger" | "default" {
|
||||
const amount = Number(value || 0);
|
||||
return 0 < amount ? "success" : amount < 0 ? "danger" : "default";
|
||||
}
|
||||
|
||||
function getDeltaText(value?: number | string): string {
|
||||
const amount = Number(value || 0);
|
||||
const text = getProductQuantityText(String(value || "0").replace(/^-/, ""));
|
||||
return `${0 < amount ? "+" : amount < 0 ? "-" : ""}${text}`;
|
||||
}
|
||||
|
||||
function getDeltaClass(value?: number | string): string {
|
||||
const amount = Number(value || 0);
|
||||
return 0 < amount ? "positive" : amount < 0 ? "negative" : "gray";
|
||||
}
|
||||
|
||||
function getQuantityText(item: StockRecord): string {
|
||||
return `${getProductQuantityText(item.quantity)} ${item.unitSnapshot || "业务单位"}`;
|
||||
}
|
||||
|
||||
function getBizTypeText(type?: StockBizType): string {
|
||||
return ({ PRODUCT_STOCK: "产品库存", ORDER_FULFILLMENT: "订单履约" } as Record<string, string>)[type || ""] || "未知";
|
||||
}
|
||||
|
||||
function buildAttachments(): Partial<Attachment>[] {
|
||||
return revokePhotoItems.value.flatMap(item => item.tempFileId ? [{ tempFileId: item.tempFileId, attachType: "PHOTO" }] : []);
|
||||
}
|
||||
|
||||
function openManualRevoke(): void {
|
||||
if (!canManualRevoke.value) {
|
||||
return;
|
||||
}
|
||||
revokeRemark.value = "";
|
||||
revokeRequestNo.value = "";
|
||||
revokePhotoItems.value = [];
|
||||
revokeVisible.value = true;
|
||||
}
|
||||
|
||||
async function openRelatedRecord(id?: string): Promise<void> {
|
||||
if (id) {
|
||||
await router.push({ name: "ProductStockRecordDetail", params: { id } });
|
||||
}
|
||||
}
|
||||
|
||||
async function submitManualRevoke(): Promise<void> {
|
||||
if (!canManualRevoke.value || !record.value?.id || isOperating.value) {
|
||||
return;
|
||||
}
|
||||
if (!revokeRemark.value.trim()) {
|
||||
Message.warning("冲正备注不能为空");
|
||||
return;
|
||||
}
|
||||
if (!revokePhotoItems.value.some(item => !!item.tempFileId)) {
|
||||
Message.warning("库存流水冲正必须上传至少一张照片");
|
||||
return;
|
||||
}
|
||||
isOperating.value = true;
|
||||
try {
|
||||
if (!revokeRequestNo.value) {
|
||||
revokeRequestNo.value = createProductRequestNo();
|
||||
}
|
||||
await ProductAPI.manualRevoke({
|
||||
requestNo: revokeRequestNo.value,
|
||||
sourceRecordId: record.value.id,
|
||||
remark: revokeRemark.value.trim(),
|
||||
attachmentList: buildAttachments()
|
||||
});
|
||||
revokeVisible.value = false;
|
||||
Message.success("手工库存流水已冲正");
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "库存流水冲正失败,重试会复用同一请求号"));
|
||||
} finally {
|
||||
isOperating.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
const id = String(route.params.id || "");
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const result = await ProductAPI.recordDetail(id);
|
||||
record.value = result;
|
||||
if (result.productId) {
|
||||
product.value = await ProductAPI.detail(result.productId);
|
||||
}
|
||||
} catch (error) {
|
||||
Message.error(getErrorMessage(error, "库存流水加载失败"));
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
navBarStore.clearRight();
|
||||
await loadData();
|
||||
});
|
||||
|
||||
watch(() => String(route.params.id || ""), async (id, previousId) => {
|
||||
if (id && id !== previousId) {
|
||||
await loadData();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => navBarStore.clearRight());
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.product-stock-record-detail {
|
||||
gap: 1rem;
|
||||
display: flex;
|
||||
padding: calc(var(--app-nav-height) + 1rem) 0 2rem;
|
||||
flex-direction: column;
|
||||
|
||||
.loading {
|
||||
margin: 3rem auto;
|
||||
}
|
||||
|
||||
.positive {
|
||||
color: var(--td-success-color);
|
||||
}
|
||||
|
||||
.negative {
|
||||
color: var(--td-error-color);
|
||||
}
|
||||
|
||||
.balance {
|
||||
gap: .25rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
.arrow,
|
||||
.unit {
|
||||
color: var(--td-text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.revoke-popup {
|
||||
padding: .5rem 1rem calc(1rem + env(safe-area-inset-bottom, 0));
|
||||
|
||||
.tip {
|
||||
margin: 0 0 .75rem;
|
||||
color: var(--td-text-color-secondary);
|
||||
font-size: .875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -10,10 +10,6 @@
|
||||
placeholder="搜索产品名称或编码"
|
||||
/>
|
||||
</div>
|
||||
<t-cell-group class="summary" theme="card">
|
||||
<t-cell title="查询范围" :note="scopeText" />
|
||||
<t-cell title="记录数量" :note="`共 ${total} 条`" />
|
||||
</t-cell-group>
|
||||
<t-pull-down-refresh
|
||||
v-model="isRefreshing"
|
||||
:refresh-timeout="30000"
|
||||
@@ -25,7 +21,10 @@
|
||||
v-for="(record, index) in recordList"
|
||||
:key="record.id || `${record.occurredAt}-${record.requestNo}-${index}`"
|
||||
class="record"
|
||||
:arrow="!!record.id"
|
||||
:hover="!!record.id"
|
||||
v-entrance-transition="index * 30"
|
||||
@click="toDetail(record)"
|
||||
>
|
||||
<template #leftIcon>
|
||||
<span class="icon" :class="getDeltaClass(record.onHandDelta)">
|
||||
@@ -35,29 +34,31 @@
|
||||
<template #title>
|
||||
<div class="title">
|
||||
<strong v-text="getStockOperationText(record.operationType)" />
|
||||
<span :class="getDeltaClass(record.onHandDelta)" v-text="getDeltaText(record.onHandDelta)" />
|
||||
</div>
|
||||
</template>
|
||||
<template #note>
|
||||
<span v-text="record.occurredAt ? Time.toDateTime(record.occurredAt) : '时间未知'" />
|
||||
</template>
|
||||
</template>
|
||||
<template #note>
|
||||
<span
|
||||
class="delta"
|
||||
:class="getDeltaClass(record.onHandDelta)"
|
||||
v-text="getDeltaText(record.onHandDelta)"
|
||||
/>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="description">
|
||||
<span v-text="getQuantityText(record)" />
|
||||
<span v-text="getBalanceText(record)" />
|
||||
<span v-if="record.remark" class="gray" v-text="record.remark" />
|
||||
<span v-text="record.productNameSnapshot || '未命名产品'" />
|
||||
<pass-time-label :timestamp="record.occurredAt" underline />
|
||||
</div>
|
||||
</template>
|
||||
</t-cell>
|
||||
</t-cell-group>
|
||||
<div v-else class="empty" v-text="emptyText" />
|
||||
<div v-else class="center-tips" v-text="emptyText" />
|
||||
<div v-if="recordList.length" class="center-tips" v-text="loadStateText" />
|
||||
</t-pull-down-refresh>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Time } from "timi-web";
|
||||
import { PassTimeLabel } from "timi-web";
|
||||
import ProductAPI from "@/api/ProductAPI";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import type { StockOperationType, StockRecord } from "@/types/Product";
|
||||
@@ -70,6 +71,7 @@ defineOptions({ name: "ProductStockRecordList" });
|
||||
|
||||
const pageSize = 15;
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const recordList = ref<StockRecord[]>([]);
|
||||
const pageIndex = ref(0);
|
||||
@@ -81,12 +83,6 @@ const productId = computed(() => String(route.query.productId || ""));
|
||||
const keyword = ref(getQueryText("keyword"));
|
||||
let keywordTimer: number | undefined;
|
||||
let requestToken = 0;
|
||||
const scopeText = computed(() => {
|
||||
const scope = productId.value
|
||||
? `指定产品 · ${recordList.value[0]?.productNameSnapshot || "当前产品"}`
|
||||
: "全部产品";
|
||||
return keyword.value.trim() ? `${scope} · 搜索:${keyword.value.trim()}` : scope;
|
||||
});
|
||||
const emptyText = computed(() => {
|
||||
if (loading.value) {
|
||||
return "正在加载库存流水";
|
||||
@@ -102,12 +98,12 @@ function getQueryText(key: string): string {
|
||||
|
||||
function getOperationIcon(type?: StockOperationType): string {
|
||||
return ({
|
||||
INITIAL_IN: "download",
|
||||
MANUAL_IN: "add-circle",
|
||||
MANUAL_OUT: "remove-circle",
|
||||
INITIAL_IN: "import",
|
||||
MANUAL_IN: "import",
|
||||
MANUAL_OUT: "export",
|
||||
ORDER_RESERVE: "lock-on",
|
||||
ORDER_RELEASE: "unlock",
|
||||
ORDER_OUT: "remove-circle",
|
||||
ORDER_RELEASE: "lock-off",
|
||||
ORDER_OUT: "task",
|
||||
ORDER_REVOKE: "rollback",
|
||||
STOCKTAKE_GAIN: "arrow-up",
|
||||
STOCKTAKE_LOSS: "arrow-down"
|
||||
@@ -115,9 +111,9 @@ function getOperationIcon(type?: StockOperationType): string {
|
||||
}
|
||||
|
||||
function getDeltaText(value?: number | string): string {
|
||||
const amount = Number(value || 0);
|
||||
const text = String(value || "0");
|
||||
return `${0 < amount ? "+" : ""}${amount < 0 ? getProductQuantityText(text.slice(1)) : getProductQuantityText(text)}`;
|
||||
const amount = Number(value || 0);
|
||||
const text = String(value || "0").replace(/^-/, "");
|
||||
return `${0 < amount ? "+" : amount < 0 ? "-" : ""}${getProductQuantityText(text)}`;
|
||||
}
|
||||
|
||||
function getDeltaClass(value?: number | string): string {
|
||||
@@ -125,13 +121,10 @@ function getDeltaClass(value?: number | string): string {
|
||||
return 0 < amount ? "positive" : amount < 0 ? "negative" : "gray";
|
||||
}
|
||||
|
||||
function getQuantityText(record: StockRecord): string {
|
||||
return `${record.productNameSnapshot || "未命名产品"} · ${getProductQuantityText(record.quantity)} ${record.unitSnapshot || "业务单位"} · 基础单位 ${getProductQuantityText(record.baseQuantity)}`;
|
||||
}
|
||||
|
||||
function getBalanceText(record: StockRecord): string {
|
||||
const operator = record.operator?.name || record.operatorUserId || "未知操作人";
|
||||
return `库存 ${getProductQuantityText(record.onHandAfter)} · 预占 ${getProductQuantityText(record.reservedAfter)} · ${operator}`;
|
||||
async function toDetail(record: StockRecord): Promise<void> {
|
||||
if (record.id) {
|
||||
await router.push({ name: "ProductStockRecordDetail", params: { id: record.id } });
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMore(force = false): Promise<void> {
|
||||
@@ -230,34 +223,35 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.summary {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.record {
|
||||
.icon {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
justify-content: center;
|
||||
background: var(--td-bg-color-secondarycontainer);
|
||||
}
|
||||
.record {
|
||||
|
||||
.title {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.icon {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
display: inline-flex;
|
||||
background: var(--td-bg-color-secondarycontainer);
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
gap: .125rem;
|
||||
display: grid;
|
||||
.title {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.delta {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
gap: .125rem;
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,11 +262,5 @@ onUnmounted(() => {
|
||||
.negative {
|
||||
color: var(--td-error-color);
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: var(--td-text-color-secondary);
|
||||
padding: 2rem 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,6 +6,7 @@ import ProductEdit from "@/pages/product/ProductEdit.vue";
|
||||
import ProductList from "@/pages/product/ProductList.vue";
|
||||
import ProductStockDetail from "@/pages/product/ProductStockDetail.vue";
|
||||
import ProductStockList from "@/pages/product/ProductStockList.vue";
|
||||
import ProductStockRecordDetail from "@/pages/product/ProductStockRecordDetail.vue";
|
||||
import ProductStockRecordList from "@/pages/product/ProductStockRecordList.vue";
|
||||
|
||||
function pageMeta(title: string, depth = 3, background = "#F5F5F8"): RouteRecordRaw["meta"] {
|
||||
@@ -69,6 +70,12 @@ const productRoutes: RouteRecordRaw[] = [
|
||||
component: ProductStockRecordList,
|
||||
meta: pageMeta("库存流水")
|
||||
},
|
||||
{
|
||||
path: "/management/product-stock-records/:id",
|
||||
name: "ProductStockRecordDetail",
|
||||
component: ProductStockRecordDetail,
|
||||
meta: pageMeta("库存流水详情", 4)
|
||||
},
|
||||
{
|
||||
path: "/management/product-stocks/:productId",
|
||||
name: "ProductStockDetail",
|
||||
|
||||
@@ -108,6 +108,7 @@ export type StockRecord = {
|
||||
bizType?: StockBizType;
|
||||
bizId?: string;
|
||||
sourceRecordId?: string;
|
||||
revokeRecordId?: string;
|
||||
requestNo?: string;
|
||||
operatorUserId?: string;
|
||||
occurredAt?: number;
|
||||
@@ -139,6 +140,13 @@ export type ProductStockChangeRequest = {
|
||||
attachmentList: Partial<Attachment>[];
|
||||
};
|
||||
|
||||
export type ProductStockManualRevokeRequest = {
|
||||
requestNo: string;
|
||||
sourceRecordId: string;
|
||||
remark: string;
|
||||
attachmentList: Partial<Attachment>[];
|
||||
};
|
||||
|
||||
export type ProductPageResult<T> = PageResult<T> & {
|
||||
pages?: number;
|
||||
};
|
||||
|
||||
@@ -57,7 +57,8 @@ export const permissionCodes = {
|
||||
},
|
||||
productStock: {
|
||||
create: "GAO:PRODUCT_STOCK:CREATE",
|
||||
read: "GAO:PRODUCT_STOCK:READ"
|
||||
read: "GAO:PRODUCT_STOCK:READ",
|
||||
manualRevoke: "GAO:PRODUCT_STOCK:MANUAL_REVOKE"
|
||||
},
|
||||
packageConfig: {
|
||||
create: "GAO:PACKAGE_CONFIG:CREATE",
|
||||
|
||||
@@ -68,6 +68,7 @@ export function getStockOperationText(type?: StockOperationType): string {
|
||||
INITIAL_IN: "期初入库",
|
||||
MANUAL_IN: "手工入库",
|
||||
MANUAL_OUT: "手工出库",
|
||||
MANUAL_REVOKE: "手工流水冲正",
|
||||
ORDER_RESERVE: "履约预占",
|
||||
ORDER_RELEASE: "履约释放预占",
|
||||
ORDER_OUT: "履约出库",
|
||||
|
||||
Reference in New Issue
Block a user