| | |
| | | <text class="kv-label">入库数量</text> |
| | | <view class="kv-value stocked-qty-input-wrap"> |
| | | <up-input :key="'stocked-' + idx" |
| | | v-model="item.stockedQuantity" |
| | | v-model="item.operateQuantity" |
| | | type="number" |
| | | placeholder="请输入入库数量" |
| | | clearable |
| | |
| | | }; |
| | | }; |
| | | |
| | | const { detailFieldRows, summaryFieldRows } = useScanOutFieldRows(contractKind); |
| | | const { detailFieldRows: rawDetailFieldRows, summaryFieldRows: rawSummaryFieldRows } = useScanOutFieldRows( |
| | | contractKind, |
| | | "inbound" |
| | | ); |
| | | const shouldShowInboundQuantityField = key => { |
| | | if (type.value === QUALITY_TYPE.qualified) return key !== "unqualifiedStockedQuantity"; |
| | | if (type.value === QUALITY_TYPE.unqualified) |
| | | return key !== "stockedQuantity" && key !== "remainingQuantity"; |
| | | return true; |
| | | }; |
| | | const detailFieldRows = computed(() => |
| | | rawDetailFieldRows.value.filter(row => shouldShowInboundQuantityField(row.key)) |
| | | ); |
| | | const summaryFieldRows = computed(() => |
| | | rawSummaryFieldRows.value.filter(row => shouldShowInboundQuantityField(row.key)) |
| | | ); |
| | | |
| | | const emptyDash = v => { |
| | | if (v === null || v === undefined || v === "") return "-"; |
| | |
| | | return emptyDash(v); |
| | | }; |
| | | |
| | | const shouldValidateStockStatus = computed(() => { |
| | | return ( |
| | | contractKind.value === CONTRACT_KIND.sales && |
| | | type.value === QUALITY_TYPE.qualified |
| | | ); |
| | | }); |
| | | |
| | | const isFullyStocked = item => { |
| | | if (!shouldValidateStockStatus.value) return false; |
| | | const s = item?.productStockStatus; |
| | | return s == 2 || s === "2"; |
| | | }; |
| | | |
| | | const onStockedQtyBlur = item => { |
| | | if (isFullyStocked(item)) return; |
| | | const raw = item.stockedQuantity; |
| | | const raw = item.operateQuantity; |
| | | if (raw === null || raw === undefined || String(raw).trim() === "") { |
| | | item.stockedQuantity = "0"; |
| | | item.operateQuantity = "0"; |
| | | return; |
| | | } |
| | | const n = Number(String(raw).trim()); |
| | | if (Number.isNaN(n)) { |
| | | item.stockedQuantity = defaultStockedQuantityFromRow(item); |
| | | item.operateQuantity = |
| | | type.value === QUALITY_TYPE.unqualified ? "0" : defaultStockedQuantityFromRow(item, "inbound"); |
| | | return; |
| | | } |
| | | item.stockedQuantity = String(Math.max(0, n)); |
| | | item.operateQuantity = String(Math.max(0, n)); |
| | | }; |
| | | |
| | | const formatCell = (item, row, idx) => { |
| | |
| | | return formatProductStockStatus(item.productStockStatus); |
| | | if (row.key === "heavyBox") return formatHeavyBox(item.heavyBox); |
| | | if (row.key === "remainingQuantity") { |
| | | const v = item.remainingQuantity; |
| | | return emptyDash(v); |
| | | } |
| | | if (row.key === "remainingShippedQuantity") { |
| | | const v = item.remainingShippedQuantity; |
| | | return emptyDash(v); |
| | | } |
| | | if (row.key === "shippedQuantity") { |
| | | const v = item.shippedQuantity; |
| | | return emptyDash(v); |
| | | } |
| | | if (row.key === "unqualifiedShippedQuantity") { |
| | | const v = |
| | | item.remainingQuantity ?? |
| | | item.remaining_quantity ?? |
| | | item.remainQuantity ?? |
| | | item.remain_quantity; |
| | | item.unqualifiedShippedQuantity ?? |
| | | item.unQualifiedShippedQuantity ?? |
| | | item.unqualifiedShippedQty ?? |
| | | item.unqualifiedOutboundQuantity; |
| | | return emptyDash(v); |
| | | } |
| | | if (row.key === "stockedQuantity") { |
| | | const v = item.stockedQuantity; |
| | | return emptyDash(v); |
| | | } |
| | | if (row.key === "unqualifiedStockedQuantity") { |
| | | const v = |
| | | item.unqualifiedStockedQuantity ?? |
| | | item.unQualifiedStockedQuantity ?? |
| | | item.unqualifiedStockedQty ?? |
| | | item.unqualifiedInboundQuantity; |
| | | return emptyDash(v); |
| | | } |
| | | if (row.key === "availableQuality") { |
| | |
| | | if (res.code === 200 && res.data && res.data.length > 0) { |
| | | recordList.value = res.data.map(row => ({ |
| | | ...row, |
| | | stockedQuantity: defaultStockedQuantityFromRow(row), |
| | | unqualifiedShippedQuantity: |
| | | row.unqualifiedShippedQuantity ?? |
| | | row.unQualifiedShippedQuantity ?? |
| | | row.unqualifiedShippedQty ?? |
| | | row.unqualifiedOutboundQuantity, |
| | | unqualifiedStockedQuantity: |
| | | row.unqualifiedStockedQuantity ?? |
| | | row.unQualifiedStockedQuantity ?? |
| | | row.unqualifiedStockedQty ?? |
| | | row.unqualifiedInboundQuantity, |
| | | operateQuantity: |
| | | type.value === QUALITY_TYPE.unqualified ? "0" : defaultStockedQuantityFromRow(row, "inbound"), |
| | | })); |
| | | expandedByIndex.value = {}; |
| | | showForm.value = true; |
| | |
| | | |
| | | </view> |
| | | |
| | | <view v-if="!isFullyStocked(item)" |
| | | <view |
| | | |
| | | class="stocked-qty-block"> |
| | | |
| | |
| | | |
| | | <up-input :key="'stocked-' + idx" |
| | | |
| | | v-model="item.stockedQuantity" |
| | | v-model="item.operateQuantity" |
| | | |
| | | type="number" |
| | | |
| | |
| | | |
| | | |
| | | |
| | | const { detailFieldRows, summaryFieldRows } = useScanOutFieldRows(contractKind); |
| | | const { detailFieldRows: rawDetailFieldRows, summaryFieldRows: rawSummaryFieldRows } = useScanOutFieldRows( |
| | | contractKind, |
| | | "outbound" |
| | | ); |
| | | const shouldShowOutboundQuantityField = key => { |
| | | if (type.value === QUALITY_TYPE.qualified) |
| | | return key !== "unqualifiedShippedQuantity" && key !== "unqualifiedStockedQuantity"; |
| | | if (type.value === QUALITY_TYPE.unqualified) return key !== "shippedQuantity" && key !== "remainingShippedQuantity"; |
| | | return true; |
| | | }; |
| | | const detailFieldRows = computed(() => |
| | | rawDetailFieldRows.value.filter(row => shouldShowOutboundQuantityField(row.key)) |
| | | ); |
| | | const summaryFieldRows = computed(() => |
| | | rawSummaryFieldRows.value.filter(row => shouldShowOutboundQuantityField(row.key)) |
| | | ); |
| | | |
| | | |
| | | |
| | |
| | | |
| | | |
| | | |
| | | const isFullyStocked = item => { |
| | | |
| | | const s = item?.productStockStatus; |
| | | |
| | | return s == 2 || s === "2"; |
| | | |
| | | }; |
| | | |
| | | |
| | | |
| | | const parseOptionalNumberLocal = raw => { |
| | | |
| | | if (raw === null || raw === undefined || raw === "") return null; |
| | |
| | | |
| | | const onStockedQtyBlur = item => { |
| | | |
| | | if (isFullyStocked(item)) return; |
| | | |
| | | const raw = item.stockedQuantity; |
| | | const raw = item.operateQuantity; |
| | | |
| | | if (raw === null || raw === undefined || String(raw).trim() === "") { |
| | | |
| | | item.stockedQuantity = "0"; |
| | | item.operateQuantity = "0"; |
| | | |
| | | return; |
| | | |
| | |
| | | const n = Number(String(raw).trim()); |
| | | |
| | | if (Number.isNaN(n)) { |
| | | |
| | | item.stockedQuantity = defaultStockedQuantityFromRow(item); |
| | | if (type.value === QUALITY_TYPE.unqualified) { |
| | | const unqualifiedInbound = parseOptionalNumber(item.unqualifiedStockedQuantity) ?? 0; |
| | | const unqualifiedOutbound = parseOptionalNumber(item.unqualifiedShippedQuantity) ?? 0; |
| | | item.operateQuantity = String(Math.max(0, unqualifiedInbound - unqualifiedOutbound)); |
| | | } else { |
| | | item.operateQuantity = defaultStockedQuantityFromRow(item, "outbound"); |
| | | } |
| | | |
| | | return; |
| | | |
| | | } |
| | | |
| | | item.stockedQuantity = String(Math.max(0, n)); |
| | | item.operateQuantity = String(Math.max(0, n)); |
| | | |
| | | }; |
| | | |
| | |
| | | |
| | | if (row.key === "remainingQuantity") { |
| | | |
| | | const v = |
| | | |
| | | item.remainingQuantity ?? |
| | | |
| | | item.remaining_quantity ?? |
| | | |
| | | item.remainQuantity ?? |
| | | |
| | | item.remain_quantity; |
| | | const v = item.remainingQuantity; |
| | | |
| | | return emptyDash(v); |
| | | |
| | | } |
| | | if (row.key === "remainingShippedQuantity") { |
| | | const v = item.remainingShippedQuantity; |
| | | return emptyDash(v); |
| | | } |
| | | if (row.key === "shippedQuantity") { |
| | | const v = item.shippedQuantity; |
| | | return emptyDash(v); |
| | | } |
| | | if (row.key === "unqualifiedShippedQuantity") { |
| | | const v = |
| | | item.unqualifiedShippedQuantity ?? |
| | | item.unQualifiedShippedQuantity ?? |
| | | item.unqualifiedShippedQty ?? |
| | | item.unqualifiedOutboundQuantity; |
| | | return emptyDash(v); |
| | | } |
| | | if (row.key === "stockedQuantity") { |
| | | const v = item.stockedQuantity; |
| | | return emptyDash(v); |
| | | } |
| | | if (row.key === "unqualifiedStockedQuantity") { |
| | | const v = item.unqualifiedStockedQuantity; |
| | | return emptyDash(v); |
| | | } |
| | | |
| | | if (row.key === "availableQuality") { |
| | |
| | | recordList.value = res.data.map(row => ({ |
| | | |
| | | ...row, |
| | | |
| | | stockedQuantity: defaultStockedQuantityFromRow(row), |
| | | unqualifiedShippedQuantity: |
| | | row.unqualifiedShippedQuantity ?? |
| | | row.unQualifiedShippedQuantity ?? |
| | | row.unqualifiedShippedQty ?? |
| | | row.unqualifiedOutboundQuantity, |
| | | unqualifiedStockedQuantity: |
| | | row.unqualifiedStockedQuantity ?? |
| | | row.unQualifiedStockedQuantity ?? |
| | | row.unqualifiedStockedQty ?? |
| | | row.unqualifiedInboundQuantity, |
| | | operateQuantity: |
| | | type.value === QUALITY_TYPE.unqualified |
| | | ? String( |
| | | Math.max( |
| | | 0, |
| | | (parseOptionalNumber( |
| | | row.unqualifiedStockedQuantity ?? |
| | | row.unQualifiedStockedQuantity ?? |
| | | row.unqualifiedStockedQty ?? |
| | | row.unqualifiedInboundQuantity |
| | | ) ?? 0) - |
| | | (parseOptionalNumber( |
| | | row.unqualifiedShippedQuantity ?? |
| | | row.unQualifiedShippedQuantity ?? |
| | | row.unqualifiedShippedQty ?? |
| | | row.unqualifiedOutboundQuantity |
| | | ) ?? 0) |
| | | ) |
| | | ) |
| | | : defaultStockedQuantityFromRow(row, "outbound"), |
| | | |
| | | })); |
| | | |
| | |
| | | import { computed, type Ref } from "vue"; |
| | | import { CONTRACT_KIND, type ContractKind } from "./scanOut.constants"; |
| | | |
| | | const outboundQuantityRows = [ |
| | | { label: "已出库数量", key: "shippedQuantity" }, |
| | | { label: "不合格出库数量", key: "unqualifiedShippedQuantity" }, |
| | | { label: "不合格入库数量", key: "unqualifiedStockedQuantity" }, |
| | | { label: "剩余出库数量", key: "remainingShippedQuantity" }, |
| | | ] as const; |
| | | |
| | | const inboundQuantityRows = [ |
| | | { label: "已入库数量", key: "stockedQuantity" }, |
| | | { label: "不合格入库数量", key: "unqualifiedStockedQuantity" }, |
| | | { label: "剩余入库数量", key: "remainingQuantity" }, |
| | | ] as const; |
| | | |
| | | export const detailFieldRowsSales = [ |
| | | { label: "楼层编号", key: "floorCode" }, |
| | | { label: "产品大类", key: "productCategory" }, |
| | |
| | | { label: "发货车牌", key: "shippingCarNumber" }, |
| | | { label: "发货日期", key: "shippingDate" }, |
| | | { label: "数量", key: "quantity" }, |
| | | { label: "剩余数量", key: "remainingQuantity" }, |
| | | { label: "税率(%)", key: "taxRate" }, |
| | | { label: "含税单价(元)", key: "taxInclusiveUnitPrice" }, |
| | | { label: "含税总价(元)", key: "taxInclusiveTotalPrice" }, |
| | |
| | | { label: "单位", key: "unit" }, |
| | | { label: "数量", key: "quantity" }, |
| | | { label: "可用数量", key: "availableQuality" }, |
| | | { label: "剩余数量", key: "remainingQuantity" }, |
| | | { label: "退货数量", key: "returnQuality" }, |
| | | { label: "税率(%)", key: "taxRate" }, |
| | | { label: "含税单价(元)", key: "taxInclusiveUnitPrice" }, |
| | |
| | | { label: "产品大类", key: "productCategory" }, |
| | | { label: "规格型号", key: "specificationModel" }, |
| | | { label: "数量", key: "quantity" }, |
| | | { label: "剩余数量", key: "remainingQuantity" }, |
| | | { label: "产品状态", key: "approveStatus" }, |
| | | { label: "入库状态", key: "productStockStatus" }, |
| | | ] as const; |
| | |
| | | { label: "单位", key: "unit" }, |
| | | { label: "数量", key: "quantity" }, |
| | | { label: "可用数量", key: "availableQuality" }, |
| | | { label: "剩余数量", key: "remainingQuantity" }, |
| | | ] as const; |
| | | |
| | | type FieldRow = { label: string; key: string }; |
| | | type ScanScene = "outbound" | "inbound"; |
| | | |
| | | export function useScanOutFieldRows(contractKindRef: Ref<ContractKind>) { |
| | | export function useScanOutFieldRows(contractKindRef: Ref<ContractKind>, scene: ScanScene = "outbound") { |
| | | const quantityRows = scene === "inbound" ? inboundQuantityRows : outboundQuantityRows; |
| | | const fieldRowsByContractKind = { |
| | | [CONTRACT_KIND.sales]: { |
| | | detail: detailFieldRowsSales, |
| | | summary: summaryFieldRowsSales, |
| | | detail: [...detailFieldRowsSales, ...quantityRows], |
| | | summary: [...summaryFieldRowsSales, ...quantityRows], |
| | | }, |
| | | [CONTRACT_KIND.purchase]: { |
| | | detail: detailFieldRowsPurchase, |
| | | summary: summaryFieldRowsPurchase, |
| | | detail: [...detailFieldRowsPurchase, ...quantityRows], |
| | | summary: [...summaryFieldRowsPurchase, ...quantityRows], |
| | | }, |
| | | } as const satisfies Record<ContractKind, { detail: readonly FieldRow[]; summary: readonly FieldRow[] }>; |
| | | } satisfies Record<ContractKind, { detail: readonly FieldRow[]; summary: readonly FieldRow[] }>; |
| | | |
| | | const currentContractFieldRows = computed(() => fieldRowsByContractKind[contractKindRef.value]); |
| | | |
| | |
| | | } |
| | | |
| | | export function parseRemainingQuantity(row: AnyRow): number | null { |
| | | const remRaw = |
| | | row?.remainingQuantity ?? |
| | | row?.remaining_quantity ?? |
| | | row?.remainQuantity ?? |
| | | row?.remain_quantity; |
| | | const remRaw = row?.remainingQuantity; |
| | | return parseOptionalNumber(remRaw); |
| | | } |
| | | |
| | | export function defaultStockedQuantityFromRow(row: AnyRow): string { |
| | | const rem = parseRemainingQuantity(row); |
| | | export function parseRemainingShippedQuantity(row: AnyRow): number | null { |
| | | const remRaw = row?.remainingShippedQuantity; |
| | | return parseOptionalNumber(remRaw); |
| | | } |
| | | |
| | | export function defaultStockedQuantityFromRow( |
| | | row: AnyRow, |
| | | scene: "inbound" | "outbound" = "inbound" |
| | | ): string { |
| | | const rem = |
| | | scene === "outbound" |
| | | ? parseRemainingShippedQuantity(row) ?? parseRemainingQuantity(row) |
| | | : parseRemainingQuantity(row) ?? parseRemainingShippedQuantity(row); |
| | | if (rem !== null) return String(Math.max(0, rem)); |
| | | |
| | | const avail = parseOptionalNumber(row?.availableQuality ?? row?.availableQuantity); |
| | |
| | | |
| | | export function buildSalesLedgerProductList(recordList: AnyRow[]): AnyRow[] { |
| | | return recordList.map((item: AnyRow) => { |
| | | const n = parseOptionalNumber(item.stockedQuantity); |
| | | const n = parseOptionalNumber(item.operateQuantity); |
| | | const qty = n !== null && !Number.isNaN(n) ? Math.max(0, n) : 0; |
| | | const { stockedQuantity: _sq, ...rest } = item; |
| | | const { operateQuantity: _oq, ...rest } = item; |
| | | return { ...rest, stockedQuantity: qty }; |
| | | }); |
| | | } |
| | |
| | | * @param content 消息内容 |
| | | */ |
| | | msgError(content: string): void { |
| | | const text = (content ?? '').toString() |
| | | // showToast 在多端经常会对 title 做单行/长度截断;长文案用 showModal 保证完整展示 |
| | | const shouldUseModal = text.length > 16 || text.includes('\n') |
| | | if (shouldUseModal) { |
| | | uni.showModal({ |
| | | title: '错误', |
| | | content: text, |
| | | showCancel: false, |
| | | confirmText: '知道了', |
| | | }) |
| | | return |
| | | } |
| | | uni.showToast({ |
| | | title: content, |
| | | icon: 'error' |
| | | title: text, |
| | | icon: 'none', |
| | | duration: 2500, |
| | | }) |
| | | }, |
| | | /** |