add ProductStockPackageSummary.vue
This commit is contained in:
@@ -55,6 +55,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</t-cell>
|
</t-cell>
|
||||||
|
<product-stock-package-summary :sku="sku" :stock="stock" />
|
||||||
<t-cell title="查看库存流水" arrow @click="openRecordList" />
|
<t-cell title="查看库存流水" arrow @click="openRecordList" />
|
||||||
<t-cell v-if="0 < Number(stock.reservedQuantity || 0)" title="查看库存预占流水" arrow @click="openReservedRecordList" />
|
<t-cell v-if="0 < Number(stock.reservedQuantity || 0)" title="查看库存预占流水" arrow @click="openReservedRecordList" />
|
||||||
</t-cell-group>
|
</t-cell-group>
|
||||||
@@ -100,6 +101,7 @@ import { shallowRef } from "vue";
|
|||||||
import { useUserStore } from "timi-web-api";
|
import { useUserStore } from "timi-web-api";
|
||||||
import ProductAPI from "@/api/ProductAPI";
|
import ProductAPI from "@/api/ProductAPI";
|
||||||
import ProductProfile from "@/pages/product/component/ProductProfile.vue";
|
import ProductProfile from "@/pages/product/component/ProductProfile.vue";
|
||||||
|
import ProductStockPackageSummary from "@/pages/product/component/ProductStockPackageSummary.vue";
|
||||||
import ProductStockOperationPopup from "@/pages/product/ProductStockOperationPopup.vue";
|
import ProductStockOperationPopup from "@/pages/product/ProductStockOperationPopup.vue";
|
||||||
import { useNavBarStore } from "@/store/navBarStore";
|
import { useNavBarStore } from "@/store/navBarStore";
|
||||||
import type { Product, ProductSku, ProductStock, ProductSkuUnit, StockOperationType } from "@/types/Product";
|
import type { Product, ProductSku, ProductStock, ProductSkuUnit, StockOperationType } from "@/types/Product";
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<template v-if="packageUnits.length">
|
||||||
|
<t-cell class="packages" title="整包装换算">
|
||||||
|
<template #note>
|
||||||
|
<div class="options" role="group" aria-label="选择整包装换算单位">
|
||||||
|
<t-button
|
||||||
|
v-for="unit in packageUnits"
|
||||||
|
:key="unit.id"
|
||||||
|
size="extra-small"
|
||||||
|
:theme="selectedUnit?.id === unit.id ? 'primary' : 'default'"
|
||||||
|
:variant="selectedUnit?.id === unit.id ? 'base' : 'outline'"
|
||||||
|
:aria-label="`按${unit.name || '未命名单位'}换算`"
|
||||||
|
:aria-pressed="selectedUnit?.id === unit.id"
|
||||||
|
@click="selectedUnitId = unit.id || ''"
|
||||||
|
>
|
||||||
|
<span v-text="`按${unit.name || '未命名单位'}`" />
|
||||||
|
</t-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</t-cell>
|
||||||
|
<t-cell title="库存折合" :note="summary.onHand" />
|
||||||
|
<t-cell title="可用折合" :note="summary.available" />
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { ProductSku, ProductStock } from "@/types/Product";
|
||||||
|
import { splitBaseQuantityIntoPackages } from "@/utils/package";
|
||||||
|
|
||||||
|
const props = defineProps<{ sku: ProductSku; stock: ProductStock }>();
|
||||||
|
|
||||||
|
// ---------- 整包装换算 ----------
|
||||||
|
|
||||||
|
const selectedUnitId = ref("");
|
||||||
|
const baseUnits = computed(() => (props.sku.unitList || []).filter(unit => unit.isBase));
|
||||||
|
const packageUnits = computed(() => (props.sku.unitList || [])
|
||||||
|
.filter(unit => unit.id && !unit.isBase && 1 < Number(unit.baseQuantityPerUnit))
|
||||||
|
.sort((left, right) => Number(right.baseQuantityPerUnit) - Number(left.baseQuantityPerUnit)));
|
||||||
|
const selectedUnit = computed(() => packageUnits.value.find(unit => unit.id === selectedUnitId.value) || packageUnits.value[0]);
|
||||||
|
const summary = computed(() => ({
|
||||||
|
onHand: getSummaryText(props.stock.onHandQuantity),
|
||||||
|
available: getSummaryText(props.stock.availableQuantity)
|
||||||
|
}));
|
||||||
|
|
||||||
|
/** 使用原始基础库存换算,保留基础单位余数,异常数据不按零库存展示。 */
|
||||||
|
function getSummaryText(quantity?: number | string): string {
|
||||||
|
const baseUnit = baseUnits.value[0];
|
||||||
|
const unit = selectedUnit.value;
|
||||||
|
if (quantity === undefined || baseUnits.value.length !== 1 || !baseUnit?.name
|
||||||
|
|| Number(baseUnit.baseQuantityPerUnit) !== 1 || !unit?.name) {
|
||||||
|
return "无法换算";
|
||||||
|
}
|
||||||
|
const result = splitBaseQuantityIntoPackages(quantity, unit.baseQuantityPerUnit);
|
||||||
|
if (!result) {
|
||||||
|
return "无法换算";
|
||||||
|
}
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (result.whole !== "0") {
|
||||||
|
parts.push(`${result.whole} ${unit.name}`);
|
||||||
|
}
|
||||||
|
if (result.remainder !== "0" || !parts.length) {
|
||||||
|
parts.push(`${result.remainder} ${baseUnit.name}`);
|
||||||
|
}
|
||||||
|
return parts.join(" ");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.packages {
|
||||||
|
.options {
|
||||||
|
--td-button-border-radius: .5rem;
|
||||||
|
|
||||||
|
gap: .5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -100,6 +100,29 @@ export function convertBaseQuantityToPackageQuantity(baseQuantity: number | stri
|
|||||||
return formatDecimal(rounded, 3, 3);
|
return formatDecimal(rounded, 3, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 将非负基础数量精确拆分为整包装和基础单位余数,不对余数取整。 */
|
||||||
|
export function splitBaseQuantityIntoPackages(baseQuantity: number | string, baseQuantityPerUnit: number | string): { whole: string; remainder: string } | undefined {
|
||||||
|
const quantityParts = toDecimalParts(baseQuantity, 3);
|
||||||
|
const unitParts = toDecimalParts(baseQuantityPerUnit, 6);
|
||||||
|
if (!quantityParts || !unitParts) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const scale = Math.max(quantityParts.scale, unitParts.scale);
|
||||||
|
const factor = 10n ** BigInt(scale);
|
||||||
|
const quantity = quantityParts.value * 10n ** BigInt(scale - quantityParts.scale);
|
||||||
|
const unit = unitParts.value * 10n ** BigInt(scale - unitParts.scale);
|
||||||
|
if (unit <= factor) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const remainder = quantity % unit;
|
||||||
|
const integer = (remainder / factor).toString();
|
||||||
|
const fraction = (remainder % factor).toString().padStart(scale, "0").replace(/0+$/, "");
|
||||||
|
return {
|
||||||
|
whole: (quantity / unit).toString(),
|
||||||
|
remainder: fraction ? `${integer}.${fraction}` : integer
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/** 精确相加两个最多三位小数的数量。 */
|
/** 精确相加两个最多三位小数的数量。 */
|
||||||
export function addPackageQuantity(left: number | string, right: number | string): string | undefined {
|
export function addPackageQuantity(left: number | string, right: number | string): string | undefined {
|
||||||
const leftParts = toQuantityParts(left);
|
const leftParts = toQuantityParts(left);
|
||||||
|
|||||||
Reference in New Issue
Block a user