diff --git a/Dockerfile b/Dockerfile index 2ed1ff3..674d2fb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -100,7 +100,8 @@ RUN test -s "/app/public/brand/header-logo.png" && \ test -f "/app/prisma/migrations/20260914_expand_brand_knowledge_preview_slices/migration.sql" && \ test -f "/app/prisma/migrations/20260915_add_brand_knowledge_soft_delete/migration.sql" && \ test -f "/app/prisma/migrations/20260918_add_performance_conversion_sync/migration.sql" && \ - test -f "/app/prisma/migrations/20260923_add_certificate_report_metadata/migration.sql" + test -f "/app/prisma/migrations/20260923_add_certificate_report_metadata/migration.sql" && \ + test -f "/app/prisma/migrations/20260924_add_certificate_tested_on/migration.sql" # 这两个动态路由负责兜底访问本地上传文件;如果它们在构建上下文里丢失, # 线上音频/视频就会直接 404。这里提前失败,避免再把缺路由的镜像发出去。 @@ -234,6 +235,7 @@ COPY --from=builder --chown=root:root /app/prisma/migrations/20260914_expand_bra COPY --from=builder --chown=root:root /app/prisma/migrations/20260915_add_brand_knowledge_soft_delete ./prisma/migrations/20260915_add_brand_knowledge_soft_delete COPY --from=builder --chown=root:root /app/prisma/migrations/20260918_add_performance_conversion_sync ./prisma/migrations/20260918_add_performance_conversion_sync COPY --from=builder --chown=root:root /app/prisma/migrations/20260923_add_certificate_report_metadata ./prisma/migrations/20260923_add_certificate_report_metadata +COPY --from=builder --chown=root:root /app/prisma/migrations/20260924_add_certificate_tested_on ./prisma/migrations/20260924_add_certificate_tested_on COPY --from=builder --chown=nextjs:nodejs /app/lib ./lib COPY --from=builder --chown=nextjs:nodejs /app/scripts/public-traffic-rollup-from-nginx.py ./scripts/public-traffic-rollup-from-nginx.py COPY --from=builder --chown=nextjs:nodejs /app/scripts/collapse-performance-survey-template-versions.mjs ./scripts/collapse-performance-survey-template-versions.mjs diff --git a/app/api/materials/[id]/route.ts b/app/api/materials/[id]/route.ts index f02b086..f410fff 100644 --- a/app/api/materials/[id]/route.ts +++ b/app/api/materials/[id]/route.ts @@ -167,7 +167,7 @@ export async function GET( productModel: true, reportTitle: true, reportKind: true, - certificateExpiresOn: true, + certificateTestedOn: true, certificateNumber: true, certificateUsageInstructions: true, reportUsageRules: true, @@ -249,7 +249,7 @@ export async function GET( productModel: true, reportTitle: true, reportKind: true, - certificateExpiresOn: true, + certificateTestedOn: true, certificateNumber: true, certificateUsageInstructions: true, reportUsageRules: true, @@ -385,7 +385,7 @@ export async function GET( productModel: materialWithUser.productModel, reportTitle: materialWithUser.reportTitle || null, reportKind: materialWithUser.reportKind || null, - certificateExpiresOn: materialWithUser.certificateExpiresOn || null, + certificateTestedOn: materialWithUser.certificateTestedOn || null, certificateNumber: materialWithUser.certificateNumber || null, certificateUsageInstructions: materialWithUser.certificateUsageInstructions || null, reportUsageRules: materialWithUser.reportUsageRules || null, @@ -479,7 +479,7 @@ export async function GET( productModel: materialWithUser.productModel, reportTitle: materialWithUser.reportTitle || null, reportKind: materialWithUser.reportKind || null, - certificateExpiresOn: materialWithUser.certificateExpiresOn || null, + certificateTestedOn: materialWithUser.certificateTestedOn || null, certificateNumber: materialWithUser.certificateNumber || null, certificateUsageInstructions: materialWithUser.certificateUsageInstructions || null, reportUsageRules: materialWithUser.reportUsageRules || null, @@ -613,7 +613,7 @@ export async function PUT( ? { reportTitle: formData.get('reportTitle') as string | null } : {}), ...(formData.has('reportKind') ? { reportKind: formData.get('reportKind') as string | null } : {}), - ...(formData.has('certificateExpiresOn') ? { certificateExpiresOn: formData.get('certificateExpiresOn') as string | null } : {}), + ...(formData.has('certificateTestedOn') ? { certificateTestedOn: formData.get('certificateTestedOn') as string | null } : {}), ...(formData.has('certificateNumber') ? { certificateNumber: formData.get('certificateNumber') as string | null } : {}), ...(formData.has('certificateUsageInstructions') ? { certificateUsageInstructions: formData.get('certificateUsageInstructions') as string | null } : {}), ...(formData.has('reportUsageRules') ? { reportUsageRules: formData.get('reportUsageRules') as string | null } : {}), diff --git a/app/api/materials/share/route.ts b/app/api/materials/share/route.ts index ca8745e..8f86dd0 100644 --- a/app/api/materials/share/route.ts +++ b/app/api/materials/share/route.ts @@ -126,7 +126,7 @@ export async function GET(request: NextRequest) { productModel: true, reportTitle: true, reportKind: true, - certificateExpiresOn: true, + certificateTestedOn: true, certificateNumber: true, certificateUsageInstructions: true, reportUsageRules: true, @@ -362,7 +362,7 @@ export async function GET(request: NextRequest) { videoSourceUrl: canAccessSource ? material.videoSourceUrl : null, reportTitle: (material as any).reportTitle || null, reportKind: (material as any).reportKind || null, - certificateExpiresOn: (material as any).certificateExpiresOn || null, + certificateTestedOn: (material as any).certificateTestedOn || null, certificateNumber: (material as any).certificateNumber || null, certificateUsageInstructions: (material as any).certificateUsageInstructions || null, reportUsageRules: (material as any).reportUsageRules || null, diff --git a/app/api/materials/update/route.ts b/app/api/materials/update/route.ts index e54d616..87d80af 100644 --- a/app/api/materials/update/route.ts +++ b/app/api/materials/update/route.ts @@ -46,7 +46,7 @@ export async function PUT(request: NextRequest) { ? { reportTitle: formData.get('reportTitle') as string | null } : {}), ...(formData.has('reportKind') ? { reportKind: formData.get('reportKind') as string | null } : {}), - ...(formData.has('certificateExpiresOn') ? { certificateExpiresOn: formData.get('certificateExpiresOn') as string | null } : {}), + ...(formData.has('certificateTestedOn') ? { certificateTestedOn: formData.get('certificateTestedOn') as string | null } : {}), ...(formData.has('certificateNumber') ? { certificateNumber: formData.get('certificateNumber') as string | null } : {}), ...(formData.has('certificateUsageInstructions') ? { certificateUsageInstructions: formData.get('certificateUsageInstructions') as string | null } : {}), ...(formData.has('reportUsageRules') ? { reportUsageRules: formData.get('reportUsageRules') as string | null } : {}), diff --git a/app/api/materials/upload/certificate-report/init/route.test.ts b/app/api/materials/upload/certificate-report/init/route.test.ts index 343e6b1..e2f47af 100644 --- a/app/api/materials/upload/certificate-report/init/route.test.ts +++ b/app/api/materials/upload/certificate-report/init/route.test.ts @@ -20,7 +20,7 @@ const input = { reportKind: '原材料', productCategoryName: '板材', platformName: '品牌', - certificateExpiresOn: '2027-09-23', + certificateTestedOn: '2027-09-23', certificateNumber: 'CMA-2026-001', certificateUsageInstructions: '使用前核对送检主体', reportUsageRules: '完整说明\n第二行', @@ -48,7 +48,7 @@ describe('检测报告初始化', () => { expect(prisma.material.create).toHaveBeenCalledWith({ data: expect.objectContaining({ reportKind: '原材料', - certificateExpiresOn: new Date('2027-09-23T00:00:00.000Z'), + certificateTestedOn: new Date('2027-09-23T00:00:00.000Z'), certificateNumber: 'CMA-2026-001', certificateUsageInstructions: '使用前核对送检主体', reportUsageRules: '完整说明\n第二行', diff --git a/app/api/materials/upload/certificate-report/init/route.ts b/app/api/materials/upload/certificate-report/init/route.ts index d845611..f5b5c36 100644 --- a/app/api/materials/upload/certificate-report/init/route.ts +++ b/app/api/materials/upload/certificate-report/init/route.ts @@ -21,7 +21,7 @@ export async function POST(request: NextRequest) { reportKind: body.reportKind, productModel, reportTitle, - certificateExpiresOn: body.certificateExpiresOn, + certificateTestedOn: body.certificateTestedOn, certificateNumber: body.certificateNumber, certificateUsageInstructions: body.certificateUsageInstructions, reportUsageRules: body.reportUsageRules, diff --git a/app/edit/[id]/page.tsx b/app/edit/[id]/page.tsx index 3cae8fc..2b60632 100644 --- a/app/edit/[id]/page.tsx +++ b/app/edit/[id]/page.tsx @@ -7,7 +7,7 @@ import Header from '@/components/Header' import SearchableUserSelect from '@/components/SearchableUserSelect' import { useConfirmActionDialog } from '@/components/ConfirmActionDialog' import TaskReferenceFields from '@/components/upload/TaskReferenceFields' -import CertificateExpiryDatePicker from '@/components/upload/CertificateExpiryDatePicker' +import CertificateTestDatePicker from '@/components/upload/CertificateTestDatePicker' import { canManuallyReplacePreview, getSelectableMaterialTypeOptions, @@ -36,7 +36,7 @@ interface Material { productModel: string reportTitle?: string | null reportKind?: string | null - certificateExpiresOn?: string | Date | null + certificateTestedOn?: string | Date | null certificateNumber?: string | null certificateUsageInstructions?: string | null reportUsageRules?: string | null @@ -109,7 +109,7 @@ export default function EditMaterialPage({ params }: { params: { id: string } }) const [productModel, setProductModel] = useState('') const [reportTitle, setReportTitle] = useState('') const [reportKind, setReportKind] = useState('') - const [certificateExpiresOn, setCertificateExpiresOn] = useState('') + const [certificateTestedOn, setCertificateTestedOn] = useState('') const [certificateNumber, setCertificateNumber] = useState('') const [certificateUsageInstructions, setCertificateUsageInstructions] = useState('') const [reportUsageRules, setReportUsageRules] = useState('') @@ -161,7 +161,7 @@ export default function EditMaterialPage({ params }: { params: { id: string } }) productModel, reportTitle, reportKind, - certificateExpiresOn, + certificateTestedOn, certificateNumber, certificateUsageInstructions, reportUsageRules, @@ -189,7 +189,7 @@ export default function EditMaterialPage({ params }: { params: { id: string } }) renderer, reportTitle, reportKind, - certificateExpiresOn, + certificateTestedOn, certificateNumber, certificateUsageInstructions, reportUsageRules, @@ -304,7 +304,7 @@ export default function EditMaterialPage({ params }: { params: { id: string } }) setProductModel(data.productModel || '') setReportTitle(data.reportTitle || '') setReportKind(data.reportKind || '') - setCertificateExpiresOn(data.certificateExpiresOn ? String(data.certificateExpiresOn).slice(0, 10) : '') + setCertificateTestedOn(data.certificateTestedOn ? String(data.certificateTestedOn).slice(0, 10) : '') setCertificateNumber(data.certificateNumber || '') setCertificateUsageInstructions(data.certificateUsageInstructions || '') setReportUsageRules(data.reportUsageRules || (isCertificateReportMaterialType(data.materialType) ? DEFAULT_CERTIFICATE_REPORT_USAGE_RULES : '')) @@ -347,7 +347,7 @@ export default function EditMaterialPage({ params }: { params: { id: string } }) productModel: data.productModel || '', reportTitle: data.reportTitle || '', reportKind: data.reportKind || '', - certificateExpiresOn: data.certificateExpiresOn ? String(data.certificateExpiresOn).slice(0, 10) : '', + certificateTestedOn: data.certificateTestedOn ? String(data.certificateTestedOn).slice(0, 10) : '', certificateNumber: data.certificateNumber || '', certificateUsageInstructions: data.certificateUsageInstructions || '', reportUsageRules: data.reportUsageRules || (isCertificateReportMaterialType(data.materialType) ? DEFAULT_CERTIFICATE_REPORT_USAGE_RULES : ''), @@ -524,7 +524,7 @@ export default function EditMaterialPage({ params }: { params: { id: string } }) reportKind, productModel, reportTitle, - certificateExpiresOn, + certificateTestedOn, certificateNumber, certificateUsageInstructions, reportUsageRules, @@ -590,7 +590,7 @@ export default function EditMaterialPage({ params }: { params: { id: string } }) formData.append('reportTitle', reportTitle) if (isCertificateReport) { formData.append('reportKind', reportKind) - formData.append('certificateExpiresOn', certificateExpiresOn) + formData.append('certificateTestedOn', certificateTestedOn) formData.append('certificateNumber', certificateNumber) formData.append('certificateUsageInstructions', certificateUsageInstructions) formData.append('reportUsageRules', reportUsageRules) @@ -1064,9 +1064,9 @@ export default function EditMaterialPage({ params }: { params: { id: string } })
按证书标注日期选择,不自动推算有效期
+ +请按报告标注的检测日期选择
{material.platformName || '未填写'}
证书到期日
-{material.certificateExpiresOn ? String(material.certificateExpiresOn).slice(0, 10) : '待补充'}
+证书检测日期
+{material.certificateTestedOn ? String(material.certificateTestedOn).slice(0, 10) : '待补充'}
证书编号
diff --git a/components/upload/CertificateReportUploader.tsx b/components/upload/CertificateReportUploader.tsx index 75f31b0..a5970b4 100644 --- a/components/upload/CertificateReportUploader.tsx +++ b/components/upload/CertificateReportUploader.tsx @@ -9,7 +9,7 @@ import { detectFileKindFromName, validateCertificateReportMetadata, } from '@/lib/certificate-report' -import CertificateExpiryDatePicker from '@/components/upload/CertificateExpiryDatePicker' +import CertificateTestDatePicker from '@/components/upload/CertificateTestDatePicker' interface CategoryItem { id: string @@ -37,7 +37,7 @@ export default function CertificateReportUploader({ const [productModel, setProductModel] = useState('') const [reportTitle, setReportTitle] = useState('') const [reportKind, setReportKind] = useState请按证书标注日期选择,不自动推算有效期
+ +请按报告标注的检测日期选择