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(frontend/mobile-app/lib/features/home/presentation/pages/home_shell_page.dart )",
|
||||||
"Bash(git branch:*)",
|
"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(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": [],
|
"deny": [],
|
||||||
"ask": []
|
"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)
|
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")
|
userId BigInt @map("user_id")
|
||||||
accountSequence String @map("account_sequence") @db.VarChar(20)
|
accountSequence String @map("account_sequence") @db.VarChar(20)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ export class ContractSigningController {
|
||||||
// 5. 生成带签名的 PDF
|
// 5. 生成带签名的 PDF
|
||||||
const signingDate = new Date().toISOString().split('T')[0];
|
const signingDate = new Date().toISOString().split('T')[0];
|
||||||
let pdfBuffer = await this.pdfGeneratorService.generateContractPdf({
|
let pdfBuffer = await this.pdfGeneratorService.generateContractPdf({
|
||||||
orderNo: task.orderNo,
|
contractNo: task.contractNo,
|
||||||
userRealName: task.userRealName || '未认证',
|
userRealName: task.userRealName || '未认证',
|
||||||
userIdCard: task.userIdCardNumber || '',
|
userIdCard: task.userIdCardNumber || '',
|
||||||
userPhone: task.userPhoneNumber || '',
|
userPhone: task.userPhoneNumber || '',
|
||||||
|
|
@ -317,7 +317,7 @@ export class ContractSigningController {
|
||||||
// 5. 生成带签名的 PDF
|
// 5. 生成带签名的 PDF
|
||||||
const signingDate = new Date().toISOString().split('T')[0];
|
const signingDate = new Date().toISOString().split('T')[0];
|
||||||
let pdfBuffer = await this.pdfGeneratorService.generateContractPdf({
|
let pdfBuffer = await this.pdfGeneratorService.generateContractPdf({
|
||||||
orderNo: task.orderNo,
|
contractNo: task.contractNo,
|
||||||
userRealName: task.userRealName || '未认证',
|
userRealName: task.userRealName || '未认证',
|
||||||
userIdCard: task.userIdCardNumber || '',
|
userIdCard: task.userIdCardNumber || '',
|
||||||
userPhone: task.userPhoneNumber || '',
|
userPhone: task.userPhoneNumber || '',
|
||||||
|
|
@ -413,7 +413,7 @@ export class ContractSigningController {
|
||||||
|
|
||||||
// 生成 PDF(使用 pdf-lib 直接操作 PDF 模板)
|
// 生成 PDF(使用 pdf-lib 直接操作 PDF 模板)
|
||||||
const pdfBuffer = await this.pdfGeneratorService.generateContractPdf({
|
const pdfBuffer = await this.pdfGeneratorService.generateContractPdf({
|
||||||
orderNo: task.orderNo,
|
contractNo: task.contractNo,
|
||||||
userRealName: task.userRealName || '未认证',
|
userRealName: task.userRealName || '未认证',
|
||||||
userIdCard: task.userIdCardNumber || '',
|
userIdCard: task.userIdCardNumber || '',
|
||||||
userPhone: task.userPhoneNumber || '',
|
userPhone: task.userPhoneNumber || '',
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ export interface CreateSigningTaskParams {
|
||||||
* 签署任务DTO
|
* 签署任务DTO
|
||||||
*/
|
*/
|
||||||
export interface ContractSigningTaskDto {
|
export interface ContractSigningTaskDto {
|
||||||
|
contractNo: string;
|
||||||
orderNo: string;
|
orderNo: string;
|
||||||
contractVersion: string;
|
contractVersion: string;
|
||||||
contractContent: string;
|
contractContent: string;
|
||||||
|
|
@ -310,6 +311,7 @@ export class ContractSigningService {
|
||||||
private toDto(task: ContractSigningTask): ContractSigningTaskDto {
|
private toDto(task: ContractSigningTask): ContractSigningTaskDto {
|
||||||
return {
|
return {
|
||||||
orderNo: task.orderNo,
|
orderNo: task.orderNo,
|
||||||
|
contractNo: task.contractNo,
|
||||||
contractVersion: task.contractVersion,
|
contractVersion: task.contractVersion,
|
||||||
contractContent: task.contractContent,
|
contractContent: task.contractContent,
|
||||||
status: task.status,
|
status: task.status,
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ export interface SignContractParams {
|
||||||
export class ContractSigningTask {
|
export class ContractSigningTask {
|
||||||
private _id?: bigint;
|
private _id?: bigint;
|
||||||
private _orderNo: string;
|
private _orderNo: string;
|
||||||
|
private _contractNo: string;
|
||||||
private _userId: bigint;
|
private _userId: bigint;
|
||||||
private _accountSequence: string;
|
private _accountSequence: string;
|
||||||
private _templateId: number;
|
private _templateId: number;
|
||||||
|
|
@ -108,6 +109,8 @@ export class ContractSigningTask {
|
||||||
static create(params: CreateContractSigningTaskParams): ContractSigningTask {
|
static create(params: CreateContractSigningTaskParams): ContractSigningTask {
|
||||||
const task = new ContractSigningTask();
|
const task = new ContractSigningTask();
|
||||||
task._orderNo = params.orderNo;
|
task._orderNo = params.orderNo;
|
||||||
|
// 生成合同编号: accountSequence-yyyyMMddHHmm
|
||||||
|
task._contractNo = ContractSigningTask.generateContractNo(params.accountSequence);
|
||||||
task._userId = params.userId;
|
task._userId = params.userId;
|
||||||
task._accountSequence = params.accountSequence;
|
task._accountSequence = params.accountSequence;
|
||||||
task._templateId = params.templateId;
|
task._templateId = params.templateId;
|
||||||
|
|
@ -133,6 +136,7 @@ export class ContractSigningTask {
|
||||||
static reconstitute(data: {
|
static reconstitute(data: {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
orderNo: string;
|
orderNo: string;
|
||||||
|
contractNo: string;
|
||||||
userId: bigint;
|
userId: bigint;
|
||||||
accountSequence: string;
|
accountSequence: string;
|
||||||
templateId: number;
|
templateId: number;
|
||||||
|
|
@ -166,6 +170,7 @@ export class ContractSigningTask {
|
||||||
const task = new ContractSigningTask();
|
const task = new ContractSigningTask();
|
||||||
task._id = data.id;
|
task._id = data.id;
|
||||||
task._orderNo = data.orderNo;
|
task._orderNo = data.orderNo;
|
||||||
|
task._contractNo = data.contractNo;
|
||||||
task._userId = data.userId;
|
task._userId = data.userId;
|
||||||
task._accountSequence = data.accountSequence;
|
task._accountSequence = data.accountSequence;
|
||||||
task._templateId = data.templateId;
|
task._templateId = data.templateId;
|
||||||
|
|
@ -210,6 +215,10 @@ export class ContractSigningTask {
|
||||||
return this._orderNo;
|
return this._orderNo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get contractNo(): string {
|
||||||
|
return this._contractNo;
|
||||||
|
}
|
||||||
|
|
||||||
get userId(): bigint {
|
get userId(): bigint {
|
||||||
return this._userId;
|
return this._userId;
|
||||||
}
|
}
|
||||||
|
|
@ -447,4 +456,22 @@ export class ContractSigningTask {
|
||||||
}
|
}
|
||||||
this._updatedAt = new Date();
|
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 生成数据
|
* 合同 PDF 生成数据
|
||||||
*/
|
*/
|
||||||
export interface ContractPdfData {
|
export interface ContractPdfData {
|
||||||
orderNo: string;
|
contractNo: string; // 合同编号: accountSequence-yyyyMMddHHmm
|
||||||
userRealName: string;
|
userRealName: string;
|
||||||
userIdCard: string;
|
userIdCard: string;
|
||||||
userPhone: string;
|
userPhone: string;
|
||||||
|
|
@ -57,7 +57,7 @@ export class PdfGeneratorService {
|
||||||
* @returns PDF Buffer
|
* @returns PDF Buffer
|
||||||
*/
|
*/
|
||||||
async generateContractPdf(data: ContractPdfData): Promise<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 {
|
try {
|
||||||
// 1. 加载 PDF 模板
|
// 1. 加载 PDF 模板
|
||||||
|
|
@ -81,9 +81,9 @@ export class PdfGeneratorService {
|
||||||
const fontSize = 12;
|
const fontSize = 12;
|
||||||
const textColor = rgb(0, 0, 0);
|
const textColor = rgb(0, 0, 0);
|
||||||
|
|
||||||
// 5. 填充第1页 - 协议编号
|
// 5. 填充第1页 - 协议编号(使用合同编号)
|
||||||
// "协议编号:" 后面的位置(根据 PDF 布局调整坐标)
|
// "协议编号:" 后面的位置(根据 PDF 布局调整坐标)
|
||||||
page1.drawText(data.orderNo, {
|
page1.drawText(data.contractNo, {
|
||||||
x: 390,
|
x: 390,
|
||||||
y: 95,
|
y: 95,
|
||||||
size: fontSize,
|
size: fontSize,
|
||||||
|
|
@ -156,11 +156,11 @@ export class PdfGeneratorService {
|
||||||
// 9. 保存 PDF
|
// 9. 保存 PDF
|
||||||
const pdfBytes = await pdfDoc.save();
|
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);
|
return Buffer.from(pdfBytes);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Failed to generate PDF for order ${data.orderNo}:`,
|
`Failed to generate PDF for contract ${data.contractNo}:`,
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
throw new Error(`PDF generation failed: ${error.message}`);
|
throw new Error(`PDF generation failed: ${error.message}`);
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ export class ContractSigningTaskRepositoryImpl implements IContractSigningTaskRe
|
||||||
const result = await this.prisma.contractSigningTask.create({
|
const result = await this.prisma.contractSigningTask.create({
|
||||||
data: {
|
data: {
|
||||||
orderNo: task.orderNo,
|
orderNo: task.orderNo,
|
||||||
|
contractNo: task.contractNo,
|
||||||
userId: task.userId,
|
userId: task.userId,
|
||||||
accountSequence: task.accountSequence,
|
accountSequence: task.accountSequence,
|
||||||
templateId: task.templateId,
|
templateId: task.templateId,
|
||||||
|
|
@ -188,6 +189,7 @@ export class ContractSigningTaskRepositoryImpl implements IContractSigningTaskRe
|
||||||
private mapToDomain(data: {
|
private mapToDomain(data: {
|
||||||
id: bigint;
|
id: bigint;
|
||||||
orderNo: string;
|
orderNo: string;
|
||||||
|
contractNo: string;
|
||||||
userId: bigint;
|
userId: bigint;
|
||||||
accountSequence: string;
|
accountSequence: string;
|
||||||
templateId: number;
|
templateId: number;
|
||||||
|
|
@ -230,6 +232,7 @@ export class ContractSigningTaskRepositoryImpl implements IContractSigningTaskRe
|
||||||
return ContractSigningTask.reconstitute({
|
return ContractSigningTask.reconstitute({
|
||||||
id: data.id,
|
id: data.id,
|
||||||
orderNo: data.orderNo,
|
orderNo: data.orderNo,
|
||||||
|
contractNo: data.contractNo,
|
||||||
userId: data.userId,
|
userId: data.userId,
|
||||||
accountSequence: data.accountSequence,
|
accountSequence: data.accountSequence,
|
||||||
templateId: data.templateId,
|
templateId: data.templateId,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ enum ContractSigningStatus {
|
||||||
/// 合同签署任务
|
/// 合同签署任务
|
||||||
class ContractSigningTask {
|
class ContractSigningTask {
|
||||||
final String orderNo;
|
final String orderNo;
|
||||||
|
final String contractNo;
|
||||||
final String accountSequence;
|
final String accountSequence;
|
||||||
final ContractSigningStatus status;
|
final ContractSigningStatus status;
|
||||||
final String contractVersion;
|
final String contractVersion;
|
||||||
|
|
@ -31,6 +32,7 @@ class ContractSigningTask {
|
||||||
|
|
||||||
ContractSigningTask({
|
ContractSigningTask({
|
||||||
required this.orderNo,
|
required this.orderNo,
|
||||||
|
required this.contractNo,
|
||||||
required this.accountSequence,
|
required this.accountSequence,
|
||||||
required this.status,
|
required this.status,
|
||||||
required this.contractVersion,
|
required this.contractVersion,
|
||||||
|
|
@ -50,6 +52,7 @@ class ContractSigningTask {
|
||||||
factory ContractSigningTask.fromJson(Map<String, dynamic> json) {
|
factory ContractSigningTask.fromJson(Map<String, dynamic> json) {
|
||||||
return ContractSigningTask(
|
return ContractSigningTask(
|
||||||
orderNo: json['orderNo'] ?? '',
|
orderNo: json['orderNo'] ?? '',
|
||||||
|
contractNo: json['contractNo'] ?? '',
|
||||||
accountSequence: json['accountSequence'] ?? '',
|
accountSequence: json['accountSequence'] ?? '',
|
||||||
status: _parseStatus(json['status']),
|
status: _parseStatus(json['status']),
|
||||||
contractVersion: json['contractVersion'] ?? '',
|
contractVersion: json['contractVersion'] ?? '',
|
||||||
|
|
|
||||||
|
|
@ -772,7 +772,7 @@ class _ContractSigningPageState extends ConsumerState<ContractSigningPage> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'订单: ${_task!.orderNo}',
|
'合同编号: ${_task!.contractNo}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Color(0xFF666666),
|
color: Color(0xFF666666),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue