feat(contract): 使用合同编号代替订单号
合同编号格式: accountSequence-yyyyMMddHHmm 例如: 10001-202512251003 修改内容: - 数据库: 添加 contract_no 字段 - 后端: 聚合根、Repository、Service、PDF生成器支持 contractNo - 前端: 显示合同编号代替订单号 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9cf1bbbbd3
commit
0e93d2a343
|
|
@ -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 <noreply@anthropic.com>\nEOF\n\\)\")"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 || '',
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Buffer> {
|
||||
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}`);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic> json) {
|
||||
return ContractSigningTask(
|
||||
orderNo: json['orderNo'] ?? '',
|
||||
contractNo: json['contractNo'] ?? '',
|
||||
accountSequence: json['accountSequence'] ?? '',
|
||||
status: _parseStatus(json['status']),
|
||||
contractVersion: json['contractVersion'] ?? '',
|
||||
|
|
|
|||
|
|
@ -772,7 +772,7 @@ class _ContractSigningPageState extends ConsumerState<ContractSigningPage> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'订单: ${_task!.orderNo}',
|
||||
'合同编号: ${_task!.contractNo}',
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Color(0xFF666666),
|
||||
|
|
|
|||
Loading…
Reference in New Issue