diff --git a/.claude/settings.local.json b/.claude/settings.local.json index fa7c1c48..499e70da 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -404,7 +404,8 @@ "Bash(frontend/mobile-app/lib/features/home/presentation/pages/home_shell_page.dart )", "Bash(git branch:*)", "Bash(echo \"docker exec rwa-planting-service npx prisma db execute --stdin <<< \"\"SELECT template_id, version, title, is_active FROM contract_templates;\"\"\")", - "Bash(npm uninstall:*)" + "Bash(npm uninstall:*)", + "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(contract\\): 使用合同编号代替订单号\n\n合同编号格式: accountSequence-yyyyMMddHHmm\n例如: 10001-202512251003\n\n修改内容:\n- 数据库: 添加 contract_no 字段\n- 后端: 聚合根、Repository、Service、PDF生成器支持 contractNo\n- 前端: 显示合同编号代替订单号\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")" ], "deny": [], "ask": [] diff --git a/backend/services/planting-service/prisma/migrations/20241225100000_add_contract_no/migration.sql b/backend/services/planting-service/prisma/migrations/20241225100000_add_contract_no/migration.sql new file mode 100644 index 00000000..b466a10b --- /dev/null +++ b/backend/services/planting-service/prisma/migrations/20241225100000_add_contract_no/migration.sql @@ -0,0 +1,16 @@ +-- AlterTable: Add contract_no column to contract_signing_tasks +-- 合同编号格式: accountSequence-yyyyMMddHHmm (例如: 10001-202512251003) + +-- 1. 首先添加可空的 contract_no 列 +ALTER TABLE "contract_signing_tasks" ADD COLUMN "contract_no" VARCHAR(30); + +-- 2. 为现有记录生成合同编号(基于 account_sequence 和 created_at) +UPDATE "contract_signing_tasks" +SET "contract_no" = "account_sequence" || '-' || TO_CHAR("created_at", 'YYYYMMDDHH24MI') +WHERE "contract_no" IS NULL; + +-- 3. 设置为 NOT NULL +ALTER TABLE "contract_signing_tasks" ALTER COLUMN "contract_no" SET NOT NULL; + +-- 4. 添加唯一索引 +CREATE UNIQUE INDEX "contract_signing_tasks_contract_no_key" ON "contract_signing_tasks"("contract_no"); diff --git a/backend/services/planting-service/prisma/schema.prisma b/backend/services/planting-service/prisma/schema.prisma index fc34c0ec..23847f33 100644 --- a/backend/services/planting-service/prisma/schema.prisma +++ b/backend/services/planting-service/prisma/schema.prisma @@ -315,6 +315,7 @@ model ContractSigningTask { // 关联信息 orderNo String @unique @map("order_no") @db.VarChar(50) + contractNo String @unique @map("contract_no") @db.VarChar(30) // 合同编号: accountSequence-yyyyMMddHHmm userId BigInt @map("user_id") accountSequence String @map("account_sequence") @db.VarChar(20) diff --git a/backend/services/planting-service/src/api/controllers/contract-signing.controller.ts b/backend/services/planting-service/src/api/controllers/contract-signing.controller.ts index dd17f0ee..1945ecc1 100644 --- a/backend/services/planting-service/src/api/controllers/contract-signing.controller.ts +++ b/backend/services/planting-service/src/api/controllers/contract-signing.controller.ts @@ -227,7 +227,7 @@ export class ContractSigningController { // 5. 生成带签名的 PDF const signingDate = new Date().toISOString().split('T')[0]; let pdfBuffer = await this.pdfGeneratorService.generateContractPdf({ - orderNo: task.orderNo, + contractNo: task.contractNo, userRealName: task.userRealName || '未认证', userIdCard: task.userIdCardNumber || '', userPhone: task.userPhoneNumber || '', @@ -317,7 +317,7 @@ export class ContractSigningController { // 5. 生成带签名的 PDF const signingDate = new Date().toISOString().split('T')[0]; let pdfBuffer = await this.pdfGeneratorService.generateContractPdf({ - orderNo: task.orderNo, + contractNo: task.contractNo, userRealName: task.userRealName || '未认证', userIdCard: task.userIdCardNumber || '', userPhone: task.userPhoneNumber || '', @@ -413,7 +413,7 @@ export class ContractSigningController { // 生成 PDF(使用 pdf-lib 直接操作 PDF 模板) const pdfBuffer = await this.pdfGeneratorService.generateContractPdf({ - orderNo: task.orderNo, + contractNo: task.contractNo, userRealName: task.userRealName || '未认证', userIdCard: task.userIdCardNumber || '', userPhone: task.userPhoneNumber || '', diff --git a/backend/services/planting-service/src/application/services/contract-signing.service.ts b/backend/services/planting-service/src/application/services/contract-signing.service.ts index 20c7ee1e..e11435f6 100644 --- a/backend/services/planting-service/src/application/services/contract-signing.service.ts +++ b/backend/services/planting-service/src/application/services/contract-signing.service.ts @@ -35,6 +35,7 @@ export interface CreateSigningTaskParams { * 签署任务DTO */ export interface ContractSigningTaskDto { + contractNo: string; orderNo: string; contractVersion: string; contractContent: string; @@ -310,6 +311,7 @@ export class ContractSigningService { private toDto(task: ContractSigningTask): ContractSigningTaskDto { return { orderNo: task.orderNo, + contractNo: task.contractNo, contractVersion: task.contractVersion, contractContent: task.contractContent, status: task.status, diff --git a/backend/services/planting-service/src/domain/aggregates/contract-signing-task.aggregate.ts b/backend/services/planting-service/src/domain/aggregates/contract-signing-task.aggregate.ts index 1aa336d7..bf229049 100644 --- a/backend/services/planting-service/src/domain/aggregates/contract-signing-task.aggregate.ts +++ b/backend/services/planting-service/src/domain/aggregates/contract-signing-task.aggregate.ts @@ -63,6 +63,7 @@ export interface SignContractParams { export class ContractSigningTask { private _id?: bigint; private _orderNo: string; + private _contractNo: string; private _userId: bigint; private _accountSequence: string; private _templateId: number; @@ -108,6 +109,8 @@ export class ContractSigningTask { static create(params: CreateContractSigningTaskParams): ContractSigningTask { const task = new ContractSigningTask(); task._orderNo = params.orderNo; + // 生成合同编号: accountSequence-yyyyMMddHHmm + task._contractNo = ContractSigningTask.generateContractNo(params.accountSequence); task._userId = params.userId; task._accountSequence = params.accountSequence; task._templateId = params.templateId; @@ -133,6 +136,7 @@ export class ContractSigningTask { static reconstitute(data: { id: bigint; orderNo: string; + contractNo: string; userId: bigint; accountSequence: string; templateId: number; @@ -166,6 +170,7 @@ export class ContractSigningTask { const task = new ContractSigningTask(); task._id = data.id; task._orderNo = data.orderNo; + task._contractNo = data.contractNo; task._userId = data.userId; task._accountSequence = data.accountSequence; task._templateId = data.templateId; @@ -210,6 +215,10 @@ export class ContractSigningTask { return this._orderNo; } + get contractNo(): string { + return this._contractNo; + } + get userId(): bigint { return this._userId; } @@ -447,4 +456,22 @@ export class ContractSigningTask { } this._updatedAt = new Date(); } + + // ============================================ + // 私有辅助方法 + // ============================================ + + /** + * 生成合同编号 + * 格式: accountSequence-yyyyMMddHHmm + */ + private static generateContractNo(accountSequence: string): string { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hour = String(now.getHours()).padStart(2, '0'); + const minute = String(now.getMinutes()).padStart(2, '0'); + return `${accountSequence}-${year}${month}${day}${hour}${minute}`; + } } diff --git a/backend/services/planting-service/src/infrastructure/pdf/pdf-generator.service.ts b/backend/services/planting-service/src/infrastructure/pdf/pdf-generator.service.ts index f8ce067f..43d6d655 100644 --- a/backend/services/planting-service/src/infrastructure/pdf/pdf-generator.service.ts +++ b/backend/services/planting-service/src/infrastructure/pdf/pdf-generator.service.ts @@ -10,7 +10,7 @@ const fontkit = require('@pdf-lib/fontkit'); * 合同 PDF 生成数据 */ export interface ContractPdfData { - orderNo: string; + contractNo: string; // 合同编号: accountSequence-yyyyMMddHHmm userRealName: string; userIdCard: string; userPhone: string; @@ -57,7 +57,7 @@ export class PdfGeneratorService { * @returns PDF Buffer */ async generateContractPdf(data: ContractPdfData): Promise { - this.logger.log(`Generating PDF for order: ${data.orderNo}`); + this.logger.log(`Generating PDF for contract: ${data.contractNo}`); try { // 1. 加载 PDF 模板 @@ -81,9 +81,9 @@ export class PdfGeneratorService { const fontSize = 12; const textColor = rgb(0, 0, 0); - // 5. 填充第1页 - 协议编号 + // 5. 填充第1页 - 协议编号(使用合同编号) // "协议编号:" 后面的位置(根据 PDF 布局调整坐标) - page1.drawText(data.orderNo, { + page1.drawText(data.contractNo, { x: 390, y: 95, size: fontSize, @@ -156,11 +156,11 @@ export class PdfGeneratorService { // 9. 保存 PDF const pdfBytes = await pdfDoc.save(); - this.logger.log(`PDF generated successfully for order: ${data.orderNo}`); + this.logger.log(`PDF generated successfully for contract: ${data.contractNo}`); return Buffer.from(pdfBytes); } catch (error) { this.logger.error( - `Failed to generate PDF for order ${data.orderNo}:`, + `Failed to generate PDF for contract ${data.contractNo}:`, error, ); throw new Error(`PDF generation failed: ${error.message}`); diff --git a/backend/services/planting-service/src/infrastructure/persistence/repositories/contract-signing-task.repository.impl.ts b/backend/services/planting-service/src/infrastructure/persistence/repositories/contract-signing-task.repository.impl.ts index 095cd88b..61fae48d 100644 --- a/backend/services/planting-service/src/infrastructure/persistence/repositories/contract-signing-task.repository.impl.ts +++ b/backend/services/planting-service/src/infrastructure/persistence/repositories/contract-signing-task.repository.impl.ts @@ -59,6 +59,7 @@ export class ContractSigningTaskRepositoryImpl implements IContractSigningTaskRe const result = await this.prisma.contractSigningTask.create({ data: { orderNo: task.orderNo, + contractNo: task.contractNo, userId: task.userId, accountSequence: task.accountSequence, templateId: task.templateId, @@ -188,6 +189,7 @@ export class ContractSigningTaskRepositoryImpl implements IContractSigningTaskRe private mapToDomain(data: { id: bigint; orderNo: string; + contractNo: string; userId: bigint; accountSequence: string; templateId: number; @@ -230,6 +232,7 @@ export class ContractSigningTaskRepositoryImpl implements IContractSigningTaskRe return ContractSigningTask.reconstitute({ id: data.id, orderNo: data.orderNo, + contractNo: data.contractNo, userId: data.userId, accountSequence: data.accountSequence, templateId: data.templateId, diff --git a/frontend/mobile-app/lib/core/services/contract_signing_service.dart b/frontend/mobile-app/lib/core/services/contract_signing_service.dart index a3bc2a8a..14ba980a 100644 --- a/frontend/mobile-app/lib/core/services/contract_signing_service.dart +++ b/frontend/mobile-app/lib/core/services/contract_signing_service.dart @@ -14,6 +14,7 @@ enum ContractSigningStatus { /// 合同签署任务 class ContractSigningTask { final String orderNo; + final String contractNo; final String accountSequence; final ContractSigningStatus status; final String contractVersion; @@ -31,6 +32,7 @@ class ContractSigningTask { ContractSigningTask({ required this.orderNo, + required this.contractNo, required this.accountSequence, required this.status, required this.contractVersion, @@ -50,6 +52,7 @@ class ContractSigningTask { factory ContractSigningTask.fromJson(Map json) { return ContractSigningTask( orderNo: json['orderNo'] ?? '', + contractNo: json['contractNo'] ?? '', accountSequence: json['accountSequence'] ?? '', status: _parseStatus(json['status']), contractVersion: json['contractVersion'] ?? '', diff --git a/frontend/mobile-app/lib/features/contract_signing/presentation/pages/contract_signing_page.dart b/frontend/mobile-app/lib/features/contract_signing/presentation/pages/contract_signing_page.dart index 1ecfcae1..1d97cec3 100644 --- a/frontend/mobile-app/lib/features/contract_signing/presentation/pages/contract_signing_page.dart +++ b/frontend/mobile-app/lib/features/contract_signing/presentation/pages/contract_signing_page.dart @@ -772,7 +772,7 @@ class _ContractSigningPageState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '订单: ${_task!.orderNo}', + '合同编号: ${_task!.contractNo}', style: const TextStyle( fontSize: 12, color: Color(0xFF666666),