feat(intake): prefill create form from oa opening code
This commit is contained in:
parent
70594a287d
commit
6d89e1c2c3
@ -39,6 +39,7 @@ import { useSeriesComponentAssetSkuMerge } from "./workspaces/basic-info/useSeri
|
||||
import { useSeriesComponentDerivedSkuState } from "./workspaces/basic-info/useSeriesComponentDerivedSkuState";
|
||||
import { useSeriesComponentPreviewImageSync } from "./workspaces/basic-info/useSeriesComponentPreviewImageSync";
|
||||
import { useSkuImageUploadController } from "./workspaces/basic-info/useSkuImageUploadController";
|
||||
import { useOpeningCodeOaPrefill } from "./workspaces/basic-info/useOpeningCodeOaPrefill";
|
||||
import { buildFirstQualityWorkspaceRenderers } from "./workspaces/first-quality/firstQualityWorkspaceRenderers";
|
||||
import { usePatentLedgerEntryController } from "./workspaces/patent/usePatentLedgerEntryController";
|
||||
import { useRawMaterialProcessConfigEntryController } from "./workspaces/useRawMaterialProcessConfigEntryController";
|
||||
@ -2783,6 +2784,25 @@ export default function NonIntegratedDetailClient({
|
||||
setSpuCreateMode,
|
||||
setStockProductSpu,
|
||||
});
|
||||
const {
|
||||
handleOpeningCodeCommit,
|
||||
handleOpeningCodeInputChange,
|
||||
openingCodeLookupMessage,
|
||||
openingCodeLookupState,
|
||||
} = useOpeningCodeOaPrefill({
|
||||
enabled:
|
||||
mode === "create" &&
|
||||
!isReadOnly &&
|
||||
!isSeriesDevelopment &&
|
||||
!isSeriesComponentCreate &&
|
||||
!isStockProductCreate,
|
||||
formValuesRef,
|
||||
openingReferenceRef,
|
||||
refreshSpuEstimate,
|
||||
scheduleAutoSave,
|
||||
setFormValuesAndRef,
|
||||
setOpeningReferenceAndRef,
|
||||
});
|
||||
|
||||
const {
|
||||
applySkuSupplierName,
|
||||
@ -4817,6 +4837,8 @@ export default function NonIntegratedDetailClient({
|
||||
isSeriesDevelopment={isSeriesDevelopment}
|
||||
isStockProductCreate={isStockProductCreate}
|
||||
openingReference={openingReference}
|
||||
openingCodeLookupMessage={openingCodeLookupMessage}
|
||||
openingCodeLookupState={openingCodeLookupState}
|
||||
previewFileInputRef={previewFileInputRef}
|
||||
canUseExistingSpuCreateMode={
|
||||
canUseExistingSpuCreateMode
|
||||
@ -4836,6 +4858,8 @@ export default function NonIntegratedDetailClient({
|
||||
stockProductSpu={stockProductSpu}
|
||||
onAdaptableBrandsChange={updateAdaptableBrands}
|
||||
onExistingSpuSelection={handleExistingSpuSelection}
|
||||
onOpeningCodeCommit={handleOpeningCodeCommit}
|
||||
onOpeningCodeInputChange={handleOpeningCodeInputChange}
|
||||
onOpeningReferenceFieldChange={
|
||||
handleOpeningReferenceFieldChange
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ export function BasicField({
|
||||
searchableSelect = false,
|
||||
includeEmptyOption = false,
|
||||
clearable = false,
|
||||
onCommit,
|
||||
onChange,
|
||||
}: {
|
||||
label: string;
|
||||
@ -57,6 +58,7 @@ export function BasicField({
|
||||
searchableSelect?: boolean;
|
||||
includeEmptyOption?: boolean;
|
||||
clearable?: boolean;
|
||||
onCommit?: () => void;
|
||||
onChange: (value: string) => void;
|
||||
}) {
|
||||
if (type === "select") {
|
||||
@ -150,6 +152,7 @@ export function BasicField({
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
filterOptions={Boolean((options ?? []).length)}
|
||||
onCommit={onCommit}
|
||||
onChange={onChange}
|
||||
inputClassName={`h-9 w-full rounded-apple-lg border border-apple-separator px-3 text-sm text-apple-label-primary outline-none ${
|
||||
!readOnly && (options ?? []).length > 0 ? "pr-9" : ""
|
||||
|
||||
@ -69,6 +69,39 @@ export type NonIntegratedProductListParams = {
|
||||
stockDataScope?: string
|
||||
}
|
||||
|
||||
export type OaBoardProjectLookupItem = {
|
||||
title: string
|
||||
productName: string | null
|
||||
brand: string | null
|
||||
productCategory: string | null
|
||||
categoryName: string | null
|
||||
openingCode: string | null
|
||||
currentStageName: string | null
|
||||
currentApprovalStageName: string | null
|
||||
styleImages: Array<{
|
||||
url: string
|
||||
alt: string
|
||||
}>
|
||||
formSummary: {
|
||||
openingCode: string
|
||||
demandType: string
|
||||
category: string
|
||||
categoryName: string
|
||||
productPlanner: string
|
||||
operationBrandOwner: string
|
||||
spuOwner: string
|
||||
appearanceDesigner: string
|
||||
structureDesigner: string
|
||||
purchaseOwner: string
|
||||
merchandiserOwner: string
|
||||
competitorLink: string
|
||||
openingReason: string
|
||||
salesPriceBand: string
|
||||
platformPriceBand: string
|
||||
openingBrand: string
|
||||
}
|
||||
}
|
||||
|
||||
export type GenerateNonIntegratedAiWhiteBackgroundSessionPayload = {
|
||||
sessionId?: string
|
||||
styleKey: string
|
||||
@ -336,6 +369,28 @@ export async function listSharedNonIntegratedProductPage(
|
||||
}>(response)
|
||||
}
|
||||
|
||||
export async function listOaBoardProjects(params: {
|
||||
approvalNumber?: string
|
||||
keyword?: string
|
||||
} = {}) {
|
||||
const searchParams = new URLSearchParams()
|
||||
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (!value?.trim()) return
|
||||
searchParams.set(key, value.trim())
|
||||
})
|
||||
|
||||
const query = searchParams.toString()
|
||||
const response = await fetchWithTimeout(`/api/intake/oa-board${query ? `?${query}` : ''}`, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
})
|
||||
|
||||
return readApiData<{
|
||||
items: OaBoardProjectLookupItem[]
|
||||
}>(response)
|
||||
}
|
||||
|
||||
export async function listSharedNonIntegratedStockCategoryOptions() {
|
||||
const response = await fetchWithTimeout('/api/intake/non-integrated/stock-category-options', {
|
||||
method: 'GET',
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { ChangeEvent, RefObject } from "react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
import { AdaptableBrandField } from "../../components/BrandControls";
|
||||
import { BasicField } from "../../components/FormControls";
|
||||
@ -37,6 +38,8 @@ export function BasicInfoCreateSection({
|
||||
isSeriesDevelopment,
|
||||
isStockProductCreate,
|
||||
openingReference,
|
||||
openingCodeLookupMessage,
|
||||
openingCodeLookupState,
|
||||
previewFileInputRef,
|
||||
canUseExistingSpuCreateMode,
|
||||
createSpuDisplayLabel,
|
||||
@ -49,6 +52,8 @@ export function BasicInfoCreateSection({
|
||||
stockProductSpu,
|
||||
onAdaptableBrandsChange,
|
||||
onExistingSpuSelection,
|
||||
onOpeningCodeCommit,
|
||||
onOpeningCodeInputChange,
|
||||
onOpeningReferenceFieldChange,
|
||||
onPreviewImageChange,
|
||||
onPreviewProductImage,
|
||||
@ -72,6 +77,13 @@ export function BasicInfoCreateSection({
|
||||
isSeriesDevelopment: boolean;
|
||||
isStockProductCreate: boolean;
|
||||
openingReference: NonIntegratedOpeningReference;
|
||||
openingCodeLookupMessage?: string;
|
||||
openingCodeLookupState?:
|
||||
| "idle"
|
||||
| "loading"
|
||||
| "success"
|
||||
| "missing"
|
||||
| "error";
|
||||
previewFileInputRef: RefObject<HTMLInputElement | null>;
|
||||
canUseExistingSpuCreateMode: boolean;
|
||||
createSpuDisplayLabel: string;
|
||||
@ -84,6 +96,8 @@ export function BasicInfoCreateSection({
|
||||
stockProductSpu: string;
|
||||
onAdaptableBrandsChange: (value: string[]) => void;
|
||||
onExistingSpuSelection: (value: string) => void;
|
||||
onOpeningCodeCommit?: () => void;
|
||||
onOpeningCodeInputChange?: (value: string) => void;
|
||||
onOpeningReferenceFieldChange: (
|
||||
field: OpeningReferenceFieldKey,
|
||||
value: string,
|
||||
@ -98,6 +112,14 @@ export function BasicInfoCreateSection({
|
||||
}) {
|
||||
const handlePreview = () =>
|
||||
formValues.previewImage ? onPreviewProductImage() : undefined;
|
||||
const openingCodeLookupMessageClassName =
|
||||
openingCodeLookupState === "error"
|
||||
? "text-[#a32135]"
|
||||
: openingCodeLookupState === "success"
|
||||
? "text-[#156b3f]"
|
||||
: openingCodeLookupState === "missing"
|
||||
? "text-[#8a5a00]"
|
||||
: "text-apple-label-secondary";
|
||||
|
||||
return (
|
||||
<SectionCard title={developmentCreateTitle} compact>
|
||||
@ -183,16 +205,38 @@ export function BasicInfoCreateSection({
|
||||
readOnly={isReadOnly}
|
||||
/>
|
||||
</div>
|
||||
<BasicField
|
||||
label="开款编号"
|
||||
placeholder="请输入开款编号"
|
||||
value={openingReference.temporaryCode}
|
||||
readOnly={isReadOnly}
|
||||
compact
|
||||
onChange={(value) =>
|
||||
onOpeningReferenceFieldChange("temporaryCode", value)
|
||||
}
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
<BasicField
|
||||
label="开款编号"
|
||||
placeholder="请输入开款编号"
|
||||
value={openingReference.temporaryCode}
|
||||
readOnly={isReadOnly}
|
||||
compact
|
||||
onCommit={onOpeningCodeCommit}
|
||||
onChange={(value) => {
|
||||
onOpeningReferenceFieldChange("temporaryCode", value);
|
||||
onOpeningCodeInputChange?.(value);
|
||||
}}
|
||||
/>
|
||||
{openingCodeLookupState === "loading" ? (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-1.5 text-[11px] leading-4 text-apple-label-secondary">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
<span>正在从 OA 新品流程看板带入信息...</span>
|
||||
</div>
|
||||
<div className="h-1.5 overflow-hidden rounded-full bg-apple-bg-tertiary">
|
||||
<div className="h-full w-2/5 rounded-full bg-[#5b7c83] animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{openingCodeLookupState !== "loading" && openingCodeLookupMessage ? (
|
||||
<p
|
||||
className={`text-[11px] leading-4 ${openingCodeLookupMessageClassName}`}
|
||||
>
|
||||
{openingCodeLookupMessage}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<BasicField
|
||||
label={developmentNameLabel}
|
||||
required
|
||||
|
||||
@ -0,0 +1,150 @@
|
||||
import {
|
||||
NON_INTEGRATED_DEFAULT_BRAND,
|
||||
normalizeNonIntegratedBrand,
|
||||
normalizeNonIntegratedBrandList,
|
||||
type NonIntegratedOpeningReference,
|
||||
} from "../../nonIntegratedStorage";
|
||||
import type { OaBoardProjectLookupItem } from "../../core/nonIntegratedApi";
|
||||
import type { FormValues } from "./productFormValueHelpers";
|
||||
import { BRAND_OPTIONS } from "./basicInfoConfig";
|
||||
|
||||
type OaBoardImportPayload = {
|
||||
formValues: FormValues;
|
||||
openingReference: NonIntegratedOpeningReference;
|
||||
};
|
||||
|
||||
function pickFirstNonEmpty(...values: Array<string | null | undefined>) {
|
||||
for (const value of values) {
|
||||
const normalized = String(value ?? "").trim();
|
||||
if (normalized) {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function resolveImportedBrand(
|
||||
currentBrand: string,
|
||||
...brandCandidates: Array<string | null | undefined>
|
||||
) {
|
||||
const rawBrand = pickFirstNonEmpty(...brandCandidates);
|
||||
const normalizedBrand = normalizeNonIntegratedBrand(rawBrand);
|
||||
if (normalizedBrand && BRAND_OPTIONS.includes(normalizedBrand)) {
|
||||
return normalizedBrand;
|
||||
}
|
||||
|
||||
const fuzzyMatchedBrand = BRAND_OPTIONS.find((option) =>
|
||||
rawBrand ? option.includes(rawBrand) : false,
|
||||
);
|
||||
if (fuzzyMatchedBrand) {
|
||||
return fuzzyMatchedBrand;
|
||||
}
|
||||
|
||||
return currentBrand || NON_INTEGRATED_DEFAULT_BRAND;
|
||||
}
|
||||
|
||||
export function buildOaBoardOpeningCodeImportPayload(
|
||||
item: OaBoardProjectLookupItem,
|
||||
current: OaBoardImportPayload,
|
||||
): OaBoardImportPayload {
|
||||
const styleImage = item.styleImages[0];
|
||||
const normalizedBrand = resolveImportedBrand(
|
||||
current.formValues.brand,
|
||||
item.formSummary.openingBrand,
|
||||
item.brand,
|
||||
);
|
||||
const nextAdaptableBrands = normalizeNonIntegratedBrandList([
|
||||
normalizedBrand,
|
||||
...current.formValues.adaptableBrands,
|
||||
...current.openingReference.adaptableBrands,
|
||||
]).filter((brand) => BRAND_OPTIONS.includes(brand));
|
||||
const nextSalesCategory = pickFirstNonEmpty(
|
||||
item.formSummary.category,
|
||||
item.productCategory,
|
||||
);
|
||||
const nextProductCategory = pickFirstNonEmpty(
|
||||
item.formSummary.categoryName,
|
||||
item.categoryName,
|
||||
);
|
||||
const nextProductName = pickFirstNonEmpty(item.productName, item.title);
|
||||
const nextTargetPriceBand = pickFirstNonEmpty(
|
||||
item.formSummary.salesPriceBand,
|
||||
item.formSummary.platformPriceBand,
|
||||
);
|
||||
const nextPurchaseOwner = pickFirstNonEmpty(
|
||||
item.formSummary.purchaseOwner,
|
||||
item.formSummary.merchandiserOwner,
|
||||
);
|
||||
const nextCurrentStage = pickFirstNonEmpty(
|
||||
item.currentApprovalStageName,
|
||||
item.currentStageName,
|
||||
);
|
||||
|
||||
return {
|
||||
openingReference: {
|
||||
...current.openingReference,
|
||||
temporaryCode:
|
||||
pickFirstNonEmpty(item.formSummary.openingCode, item.openingCode) ||
|
||||
current.openingReference.temporaryCode,
|
||||
productImage: pickFirstNonEmpty(styleImage?.url) || current.openingReference.productImage,
|
||||
productImageName:
|
||||
pickFirstNonEmpty(styleImage?.alt) || current.openingReference.productImageName,
|
||||
productName: nextProductName || current.openingReference.productName,
|
||||
brand: normalizedBrand,
|
||||
adaptableBrands: nextAdaptableBrands,
|
||||
salesCategory: nextSalesCategory || current.openingReference.salesCategory,
|
||||
productCategory:
|
||||
nextProductCategory || current.openingReference.productCategory,
|
||||
designer:
|
||||
pickFirstNonEmpty(item.formSummary.appearanceDesigner) ||
|
||||
current.openingReference.designer,
|
||||
planningOwner:
|
||||
pickFirstNonEmpty(item.formSummary.productPlanner) ||
|
||||
current.openingReference.planningOwner,
|
||||
structureDesigner:
|
||||
pickFirstNonEmpty(item.formSummary.structureDesigner) ||
|
||||
current.openingReference.structureDesigner,
|
||||
currentStage: nextCurrentStage || current.openingReference.currentStage,
|
||||
competitorLink:
|
||||
pickFirstNonEmpty(item.formSummary.competitorLink) ||
|
||||
current.openingReference.competitorLink,
|
||||
targetPriceBand:
|
||||
nextTargetPriceBand || current.openingReference.targetPriceBand,
|
||||
urgent:
|
||||
pickFirstNonEmpty(item.formSummary.demandType) ||
|
||||
current.openingReference.urgent,
|
||||
reason:
|
||||
pickFirstNonEmpty(item.formSummary.openingReason) ||
|
||||
current.openingReference.reason,
|
||||
},
|
||||
formValues: {
|
||||
...current.formValues,
|
||||
previewImage: pickFirstNonEmpty(styleImage?.url) || current.formValues.previewImage,
|
||||
previewImageName:
|
||||
pickFirstNonEmpty(styleImage?.alt) || current.formValues.previewImageName,
|
||||
brand: normalizedBrand,
|
||||
adaptableBrands: nextAdaptableBrands,
|
||||
salesCategory: nextSalesCategory || current.formValues.salesCategory,
|
||||
productCategory:
|
||||
nextProductCategory || current.formValues.productCategory,
|
||||
designer:
|
||||
pickFirstNonEmpty(item.formSummary.appearanceDesigner) ||
|
||||
current.formValues.designer,
|
||||
productPlanning:
|
||||
pickFirstNonEmpty(item.formSummary.productPlanner) ||
|
||||
current.formValues.productPlanning,
|
||||
spuOwner:
|
||||
pickFirstNonEmpty(item.formSummary.spuOwner) || current.formValues.spuOwner,
|
||||
operator:
|
||||
pickFirstNonEmpty(item.formSummary.operationBrandOwner) ||
|
||||
current.formValues.operator,
|
||||
operationsOwner:
|
||||
pickFirstNonEmpty(item.formSummary.operationBrandOwner) ||
|
||||
current.formValues.operationsOwner,
|
||||
purchaseOwner: nextPurchaseOwner || current.formValues.purchaseOwner,
|
||||
purchaser: nextPurchaseOwner || current.formValues.purchaser,
|
||||
priceBand: nextTargetPriceBand || current.formValues.priceBand,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,182 @@
|
||||
import { useCallback, useRef, useState, type MutableRefObject } from "react";
|
||||
|
||||
import type { FormValues } from "./productFormValueHelpers";
|
||||
import type { NonIntegratedOpeningReference } from "../../nonIntegratedStorage";
|
||||
import {
|
||||
listOaBoardProjects,
|
||||
type OaBoardProjectLookupItem,
|
||||
} from "../../core/nonIntegratedApi";
|
||||
import { buildOaBoardOpeningCodeImportPayload } from "./oaBoardOpeningCodeImport";
|
||||
|
||||
type LookupState = "idle" | "loading" | "success" | "missing" | "error";
|
||||
|
||||
type UseOpeningCodeOaPrefillParams = {
|
||||
enabled: boolean;
|
||||
formValuesRef: MutableRefObject<FormValues>;
|
||||
openingReferenceRef: MutableRefObject<NonIntegratedOpeningReference>;
|
||||
refreshSpuEstimate: () => void;
|
||||
scheduleAutoSave: () => void;
|
||||
setFormValuesAndRef: (updater: (current: FormValues) => FormValues) => void;
|
||||
setOpeningReferenceAndRef: (
|
||||
updater: (
|
||||
current: NonIntegratedOpeningReference,
|
||||
) => NonIntegratedOpeningReference,
|
||||
) => void;
|
||||
};
|
||||
|
||||
function normalizeOpeningCode(value: string | null | undefined) {
|
||||
return String(value ?? "").trim().toUpperCase();
|
||||
}
|
||||
|
||||
function matchesOpeningCode(
|
||||
item: OaBoardProjectLookupItem,
|
||||
openingCode: string,
|
||||
) {
|
||||
return [
|
||||
item.formSummary.openingCode,
|
||||
item.openingCode,
|
||||
].some((value) => normalizeOpeningCode(value) === openingCode);
|
||||
}
|
||||
|
||||
function resolveMatchingOaBoardItem(
|
||||
items: OaBoardProjectLookupItem[],
|
||||
openingCode: string,
|
||||
) {
|
||||
const exactMatches = items.filter((item) => matchesOpeningCode(item, openingCode));
|
||||
|
||||
if (exactMatches.length === 1) {
|
||||
return exactMatches[0];
|
||||
}
|
||||
|
||||
if (exactMatches.length > 1) {
|
||||
throw new Error("这个开款编号在 OA 看板里查到了多条记录,请先人工确认。");
|
||||
}
|
||||
|
||||
return items.length === 1 ? items[0] : null;
|
||||
}
|
||||
|
||||
export function useOpeningCodeOaPrefill({
|
||||
enabled,
|
||||
formValuesRef,
|
||||
openingReferenceRef,
|
||||
refreshSpuEstimate,
|
||||
scheduleAutoSave,
|
||||
setFormValuesAndRef,
|
||||
setOpeningReferenceAndRef,
|
||||
}: UseOpeningCodeOaPrefillParams) {
|
||||
const [lookupState, setLookupState] = useState<LookupState>("idle");
|
||||
const [lookupMessage, setLookupMessage] = useState("");
|
||||
const lastImportedOpeningCodeRef = useRef("");
|
||||
const activeRequestIdRef = useRef(0);
|
||||
|
||||
const handleOpeningCodeInputChange = useCallback((value: string) => {
|
||||
const normalizedValue = normalizeOpeningCode(value);
|
||||
|
||||
if (!normalizedValue) {
|
||||
lastImportedOpeningCodeRef.current = "";
|
||||
setLookupState("idle");
|
||||
setLookupMessage("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizedValue !== lastImportedOpeningCodeRef.current) {
|
||||
setLookupState("idle");
|
||||
setLookupMessage("");
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleOpeningCodeCommit = useCallback(async () => {
|
||||
if (!enabled) return;
|
||||
|
||||
const openingCode = normalizeOpeningCode(
|
||||
openingReferenceRef.current.temporaryCode,
|
||||
);
|
||||
if (!openingCode) {
|
||||
lastImportedOpeningCodeRef.current = "";
|
||||
setLookupState("idle");
|
||||
setLookupMessage("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (openingCode === lastImportedOpeningCodeRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requestId = activeRequestIdRef.current + 1;
|
||||
activeRequestIdRef.current = requestId;
|
||||
setLookupState("loading");
|
||||
setLookupMessage("正在从 OA 新品流程看板带入信息...");
|
||||
|
||||
try {
|
||||
const { items } = await listOaBoardProjects({
|
||||
approvalNumber: openingCode,
|
||||
});
|
||||
|
||||
if (activeRequestIdRef.current !== requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
normalizeOpeningCode(openingReferenceRef.current.temporaryCode) !==
|
||||
openingCode
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matchedItem = resolveMatchingOaBoardItem(items, openingCode);
|
||||
if (!matchedItem) {
|
||||
lastImportedOpeningCodeRef.current = "";
|
||||
setLookupState("missing");
|
||||
setLookupMessage("未找到对应 OA 开款信息,先手动填写也可以。");
|
||||
return;
|
||||
}
|
||||
|
||||
const nextState = buildOaBoardOpeningCodeImportPayload(matchedItem, {
|
||||
formValues: formValuesRef.current,
|
||||
openingReference: openingReferenceRef.current,
|
||||
});
|
||||
const importedDisplayName =
|
||||
nextState.openingReference.productName.trim() || openingCode;
|
||||
|
||||
setFormValuesAndRef(() => nextState.formValues);
|
||||
setOpeningReferenceAndRef(() => nextState.openingReference);
|
||||
refreshSpuEstimate();
|
||||
scheduleAutoSave();
|
||||
|
||||
lastImportedOpeningCodeRef.current = openingCode;
|
||||
setLookupState("success");
|
||||
setLookupMessage(`已带入「${importedDisplayName}」的 OA 信息。`);
|
||||
} catch (error) {
|
||||
if (activeRequestIdRef.current !== requestId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
normalizeOpeningCode(openingReferenceRef.current.temporaryCode) !==
|
||||
openingCode
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLookupState("error");
|
||||
setLookupMessage(
|
||||
error instanceof Error ? error.message : "读取 OA 信息失败,请稍后重试。",
|
||||
);
|
||||
}
|
||||
}, [
|
||||
enabled,
|
||||
formValuesRef,
|
||||
openingReferenceRef,
|
||||
refreshSpuEstimate,
|
||||
scheduleAutoSave,
|
||||
setFormValuesAndRef,
|
||||
setOpeningReferenceAndRef,
|
||||
]);
|
||||
|
||||
return {
|
||||
handleOpeningCodeCommit,
|
||||
handleOpeningCodeInputChange,
|
||||
openingCodeLookupMessage: lookupMessage,
|
||||
openingCodeLookupState: lookupState,
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user