Merge remote-tracking branch 'origin/codex/xuxiake-mainline' into codex/wuyongjie-ai-render-texture-resign

This commit is contained in:
WuYongJie 2026-06-18 11:08:41 +08:00
commit e3217c6bc1
7 changed files with 127 additions and 25 deletions

View File

@ -7679,6 +7679,15 @@ describe("NonIntegratedDetailClient", () => {
executeStandard: "GB/T3324-2024",
},
],
sampleMeasurementRows: [
{
skuId: "sku-pricing-1",
lengthCm: "",
widthCm: "",
heightCm: "",
weightKg: "",
},
],
}),
);
@ -7724,11 +7733,34 @@ describe("NonIntegratedDetailClient", () => {
]);
});
expect(screen.getByRole("button", { name: "确认推送" })).toBeEnabled();
expect(screen.getByRole("button", { name: "批量确认推送" })).toBeEnabled();
expect(screen.getByRole("button", { name: "确认推送" })).toBeDisabled();
expect(screen.getByRole("button", { name: "批量确认推送" })).toBeDisabled();
expect(screen.queryByText("已录入")).not.toBeInTheDocument();
expect(screen.queryByText("已推送")).not.toBeInTheDocument();
await actor.click(screen.getByTestId("document-workspace-trigger-样包测量"));
await actor.type(
screen.getByTestId("sample-measurement-lengthCm-sku-pricing-1"),
"120",
);
await actor.type(
screen.getByTestId("sample-measurement-widthCm-sku-pricing-1"),
"60",
);
await actor.type(
screen.getByTestId("sample-measurement-heightCm-sku-pricing-1"),
"75",
);
await actor.type(
screen.getByTestId("sample-measurement-weightKg-sku-pricing-1"),
"18.5",
);
await actor.click(screen.getByTestId("document-workspace-trigger-产品核价"));
expect(screen.getByRole("button", { name: "确认推送" })).toBeEnabled();
expect(screen.getByRole("button", { name: "批量确认推送" })).toBeEnabled();
const savedProduct = getNonIntegratedProductById("DNZ0001-product-pricing");
expect(savedProduct?.documentWorkspaceConfirmations?.产品核价).not.toBe(true);
expect(savedProduct?.documentWorkspaceApprovedAt?.产品核价).toBeUndefined();

View File

@ -1452,6 +1452,7 @@ export default function NonIntegratedDetailClient({
displayedProductPricingRowsReadyForPush,
displayedSampleMeasurementRowsConfirmedByPricing,
displayedSampleMeasurementRowsComplete,
isDisplayedProductPricingRowReadyForPush,
isSampleMeasurementSkuLocked,
planningLayoutHasContent,
populatedFinishedSampleShipmentRowCount,
@ -4126,6 +4127,7 @@ export default function NonIntegratedDetailClient({
isReadOnly,
isNodeLocked: isProductPricingNodeLocked,
canUsePushControls: canUseProductPricingPushControls,
isRowReadyForPush: isDisplayedProductPricingRowReadyForPush,
onUpdateRow: updateProductPricingRow,
onConfirmPushRows: handleConfirmProductPricingPushRows,
},

View File

@ -17,8 +17,8 @@ import {
import { isProductFactoryRowComplete } from "./productFactoryHelpers";
import {
isProductLimitRowComplete,
isProductPricingRowComplete,
isProductPricingRowReadyForJstPush,
isProductPricingRowReadyForPush,
isProductPricingRowSystemEntered,
type PlanningLayoutSkuRow,
type ProductLimitDisplayRow,
@ -143,6 +143,16 @@ export function useNonIntegratedWorkflowCompletion({
displayedProductPricingRows.every((row) =>
isProductPricingRowReadyForJstPush(row, row),
);
const displayedSampleMeasurementCompletionBySkuId = useMemo(
() =>
new Map(
displayedProductSampleMeasurementRows.map((row) => [
row.skuId,
isSampleMeasurementRowComplete(row),
]),
),
[displayedProductSampleMeasurementRows],
);
const allProductPricingRowsSystemEntered =
productPricingRows.length > 0 &&
productPricingRows.every(isProductPricingRowSystemEntered);
@ -153,12 +163,31 @@ export function useNonIntegratedWorkflowCompletion({
displayedProductPricingRows.every(isProductPricingRowSystemEntered);
const displayedProductPricingRowsReadyForPush =
displayedProductPricingRows.length > 0 &&
displayedProductPricingRowsComplete &&
displayedProductPricingRows.some(
displayedProductPricingRows.some((row) =>
isProductPricingRowReadyForPush({
pricingRow: row,
sampleMeasurementComplete:
displayedSampleMeasurementCompletionBySkuId.get(row.skuId) ?? false,
}),
) &&
displayedProductPricingRows.every(
(row) =>
isProductPricingRowReadyForJstPush(row, row) &&
!isProductPricingRowSystemEntered(row),
isProductPricingRowSystemEntered(row) ||
isProductPricingRowReadyForPush({
pricingRow: row,
sampleMeasurementComplete:
displayedSampleMeasurementCompletionBySkuId.get(row.skuId) ?? false,
}),
);
const isDisplayedProductPricingRowReadyForPush = useCallback(
(row: ProductPricingDisplayRow) =>
isProductPricingRowReadyForPush({
pricingRow: row,
sampleMeasurementComplete:
displayedSampleMeasurementCompletionBySkuId.get(row.skuId) ?? false,
}),
[displayedSampleMeasurementCompletionBySkuId],
);
const productPricingSystemEnteredSkuIdSet = useMemo(
() =>
new Set(
@ -213,6 +242,7 @@ export function useNonIntegratedWorkflowCompletion({
displayedProductPricingRowsReadyForPush,
displayedSampleMeasurementRowsConfirmedByPricing,
displayedSampleMeasurementRowsComplete,
isDisplayedProductPricingRowReadyForPush,
isSampleMeasurementSkuLocked,
planningLayoutHasContent,
populatedFinishedSampleShipmentRowCount,

View File

@ -6,7 +6,6 @@ import {
type NonIntegratedPricingRow,
} from "../nonIntegratedStorage";
import {
isProductPricingRowReadyForJstPush,
isProductPricingRowSystemEntered,
PRODUCT_PRICING_JST_PUSH_ENABLED,
type ProductPricingDisplayRow,
@ -21,6 +20,7 @@ type ProductPricingWorkspaceProps = {
isReadOnly: boolean;
isNodeLocked: boolean;
canUsePushControls: boolean;
isRowReadyForPush: (row: ProductPricingDisplayRow) => boolean;
onUpdateRow: (
skuId: string,
key: ProductPricingFieldKey,
@ -93,6 +93,7 @@ export function ProductPricingWorkspace({
isReadOnly,
isNodeLocked,
canUsePushControls,
isRowReadyForPush,
onUpdateRow,
onConfirmPushRows,
}: ProductPricingWorkspaceProps) {
@ -153,8 +154,7 @@ export function ProductPricingWorkspace({
<tbody>
{displayedRows.map((row) => {
const isSystemEntered = isProductPricingRowSystemEntered(row);
const pricingReadyForFuturePush =
isProductPricingRowReadyForJstPush(row, row);
const pricingReadyForFuturePush = isRowReadyForPush(row);
const isModularParentRow =
isDraftGroupModularParentSkuItem(row);
const virtualInventoryStatusMeta =
@ -251,14 +251,15 @@ export function ProductPricingWorkspace({
title={
isSystemEntered
? "当前 SKU 已录入系统,后续以聚水潭回传状态为准"
: PRODUCT_PRICING_JST_PUSH_ENABLED &&
pricingReadyForFuturePush
? "确认推送当前 SKU 到聚水潭"
: !PRODUCT_PRICING_JST_PUSH_ENABLED
? "聚水潭接口未接入,暂不可确认推送"
: pricingReadyForFuturePush
? "聚水潭接口未接入,暂不可确认推送"
? "确认推送当前 SKU 到聚水潭"
: isModularParentRow
? "当前模块化父商品将直接录入聚水潭组合装"
: "请先填写成本价"
: row.costPrice.trim().length > 0
? "请先填写当前 SKU 的样包测量数据"
: "请先填写成本价"
}
className="inline-flex h-7 min-w-[52px] items-center justify-center rounded-full bg-[#1f8bff] px-3 text-xs font-apple-medium text-white shadow-sm transition hover:bg-[#1478df] disabled:cursor-not-allowed disabled:bg-apple-bg-tertiary disabled:text-apple-label-tertiary disabled:shadow-none"
>

View File

@ -38,6 +38,8 @@ export function ProductPricingHeaderActions({
? "聚水潭接口未接入,暂不可批量确认推送"
: !rowsComplete
? "当前批次成本价未填写完成"
: !rowsReadyForPush
? "当前批次样包测量未填写完成"
: "批量确认推送当前批次 SKU";
return (

View File

@ -178,7 +178,9 @@ export function normalizePricingRowsForPayload(
);
}
export function isProductPricingRowComplete(row: NonIntegratedPricingRow) {
export function isProductPricingRowComplete(
row: Pick<NonIntegratedPricingRow, "costPrice">,
) {
return row.costPrice.trim().length > 0;
}
@ -205,6 +207,32 @@ export function isProductPricingRowSystemEntered(
);
}
export function isProductPricingRowReadyForPush({
pricingRow,
sampleMeasurementComplete,
}: {
pricingRow: Pick<
ProductPricingDisplayRow,
| "costPrice"
| "systemEnteredAt"
| "source"
| "skuCode"
| "barcode"
| "componentDetails"
>;
sampleMeasurementComplete: boolean;
}) {
if (isProductPricingRowSystemEntered(pricingRow)) {
return false;
}
if (isDraftGroupModularParentSkuItem(pricingRow)) {
return true;
}
return sampleMeasurementComplete && isProductPricingRowComplete(pricingRow);
}
export function createProductLimitRowFromSku(
sku: NonIntegratedSkuItem,
spu: string,

View File

@ -30,6 +30,7 @@ import {
} from "../first-quality/firstQualityHelpers";
import {
normalizeFinishedSampleShipmentShipped,
isSampleMeasurementRowComplete,
updateFinishedSampleShipmentRowsForSku,
updateSampleMeasurementRowsForSku,
} from "../sample-workflow/sampleWorkflowHelpers";
@ -43,9 +44,7 @@ import {
createPlanningLayoutClipboardValue,
getPlanningLayoutDraftKey,
getPlanningLayoutFieldTextValue,
isProductPricingRowReadyForJstPush,
isProductPricingRowComplete,
isProductPricingRowSystemEntered,
isProductPricingRowReadyForPush,
parsePlanningLayoutListInput,
PRODUCT_PRICING_JST_PUSH_ENABLED,
removePlanningLayoutListValueFromRow,
@ -188,16 +187,24 @@ export function useProductWorkflowRowController({
) {
if (!canUseProductPricingPushControls) return;
if (isReadOnly || isProductPricingNodeLocked) return;
const sampleMeasurementCompletionBySkuId = new Map(
productSampleMeasurementRows.map((row) => [
row.skuId,
isSampleMeasurementRowComplete(row),
]),
);
const readyRows = rows.filter(
(row) =>
isProductPricingRowReadyForJstPush(
row,
workflowSkuItems.find((item) => item.id === row.skuId),
) &&
!isProductPricingRowSystemEntered(row),
isProductPricingRowReadyForPush({
pricingRow: row,
sampleMeasurementComplete:
sampleMeasurementCompletionBySkuId.get(row.skuId) ?? false,
}),
);
if (readyRows.length === 0) {
setSaveMessage("请先填写要推送 SKU 的成本价,已录入的 SKU 不会重复推送");
setSaveMessage(
"请先填写要推送 SKU 的样包测量数据和成本价,已录入的 SKU 不会重复推送",
);
setSaveMessageType("error");
return;
}