典当行业档案管理软件从零搭建实战指南
一、系统核心需求与架构设计
典当行业的档案管理软件需要处理三类核心数据:当物档案(包含鉴定记录、照片、评估报告)、客户档案(身份信息、历史交易)、合同档案(当票、续当凭证、绝当处理文件)。系统必须满足《典当管理办法》的监管要求,确保数据不可篡改、长期可追溯。
1.1 技术栈选型
基于典型中小型典当行的需求(日均业务50-100笔),推荐以下轻量级方案:
- 后端:Python + Flask(轻量灵活,适合快速开发业务逻辑)
- 数据库:PostgreSQL 14+(数据可靠性强,支持JSON字段存储动态档案属性)
- 前端:Vue 3 + Element Plus(组件丰富,构建管理界面效率高)
- 文件存储:本地磁盘目录配合定期备份(初期成本最低,可直接对接后期OSS)
1.2 数据库表结构设计
创建核心表,以下是必须的字段:
```sql -- 客户档案表 CREATE TABLE customer ( id SERIAL PRIMARY KEY, id_number VARCHAR(20) UNIQUE NOT NULL, -- 身份证号 full_name VARCHAR(50) NOT NULL, phone VARCHAR(15), address TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, is_active BOOLEAN DEFAULT TRUE ); -- 当物档案表 CREATE TABLE pawn_item ( id VARCHAR(20) PRIMARY KEY, -- 格式:PAWN-YYYYMMDD-001 customer_id INTEGER REFERENCES customer(id), category VARCHAR(50) NOT NULL, -- 品类:贵金属/名表/数码产品等 description TEXT NOT NULL, -- 品名、规格、特征描述 appraisal_notes TEXT, -- 鉴定评语 appraisal_value DECIMAL(12,2) NOT NULL, -- 评估金额 status VARCHAR(20) DEFAULT '在当', -- 状态:在当/续当/绝当/取当 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 业务合同表(当票) CREATE TABLE pawn_ticket ( id VARCHAR(25) PRIMARY KEY, -- 当票号,按监管要求生成 item_id VARCHAR(20) REFERENCES pawn_item(id), loan_amount DECIMAL(12,2) NOT NULL, monthly_rate DECIMAL(5,4) NOT NULL, -- 月费率 start_date DATE NOT NULL, end_date DATE NOT NULL, storage_location VARCHAR(100), -- 实物存放位置 document_path TEXT NOT NULL, -- 当票扫描件存储路径 operator VARCHAR(50) NOT NULL -- 经办人 ); ```二、本地开发环境搭建
2.1 PostgreSQL安装与初始化
在Ubuntu 22.04系统上执行:
```bash sudo apt update sudo apt install postgresql postgresql-contrib sudo -u postgres psql -c "CREATE DATABASE pawn_archive;" sudo -u postgres psql -c "CREATE USER pawn_admin WITH PASSWORD 'YourSecurePassword123!';" sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE pawn_archive TO pawn_admin;" ```在Windows系统上,从官网下载安装包,安装时设置数据库名为pawn_archive,用户名密码同上。
2.2 后端服务搭建
创建项目目录并安装依赖:
```bash mkdir pawn-archive-backend && cd pawn-archive-backend python -m venv venv source venv/bin/activate Linux/Mac 或 venv\Scripts\activate Windows pip install flask flask-sqlalchemy psycopg2-binary python-dotenv ```创建应用主文件app.py:
```python from flask import Flask, request, jsonify from flask_sqlalchemy import SQLAlchemy from datetime import datetime import os app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://pawn_admin:YourSecurePassword123!@localhost:5432/pawn_archive' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['UPLOAD_FOLDER'] = '/var/www/pawn_uploads' Linux Windows示例:app.config['UPLOAD_FOLDER'] = 'D:\\pawn_uploads' db = SQLAlchemy(app) class Customer(db.Model): __tablename__ = 'customer' id = db.Column(db.Integer, primary_key=True) id_number = db.Column(db.String(20), unique=True, nullable=False) full_name = db.Column(db.String(50), nullable=False) phone = db.Column(db.String(15)) created_at = db.Column(db.DateTime, default=datetime.utcnow) @app.route('/api/customer', methods=['POST']) def create_customer(): data = request.json new_customer = Customer( id_number=data['id_number'], full_name=data['full_name'], phone=data.get('phone', '') ) db.session.add(new_customer) db.session.commit() return jsonify({'id': new_customer.id, 'message': '客户创建成功'}), 201 if __name__ == '__main__': with app.app_context(): db.create_all() 首次运行创建表 app.run(debug=True, port=5000) ```
运行测试:python app.py,访问http://localhost:5000看到404即表示服务启动成功。
三、核心功能模块实现
3.1 当物档案录入接口
在app.py中添加以下代码:
```python class PawnItem(db.Model): __tablename__ = 'pawn_item' id = db.Column(db.String(20), primary_key=True) customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), nullable=False) category = db.Column(db.String(50), nullable=False) description = db.Column(db.Text, nullable=False) appraisal_value = db.Column(db.Numeric(12,2), nullable=False) status = db.Column(db.String(20), default='在当') created_at = db.Column(db.DateTime, default=datetime.utcnow) def generate_item_id(): today = datetime.now().strftime('%Y%m%d') last_item = PawnItem.query.filter(PawnItem.id.like(f'PAWN-{today}-%')).order_by(PawnItem.id.desc()).first() if last_item: last_num = int(last_item.id.split('-')[-1]) new_num = f'{last_num + 1:03d}' else: new_num = '001' return f'PAWN-{today}-{new_num}' @app.route('/api/pawn_item', methods=['POST']) def create_pawn_item(): data = request.json 验证客户是否存在 customer = Customer.query.get(data['customer_id']) if not customer: return jsonify({'error': '客户不存在'}), 400 new_item = PawnItem( id=generate_item_id(), customer_id=data['customer_id'], category=data['category'], description=data['description'], appraisal_value=data['appraisal_value'] ) db.session.add(new_item) db.session.commit() return jsonify({'id': new_item.id, 'message': '当物档案创建成功'}), 201 ```3.2 档案文件上传与管理
创建文件上传目录并设置权限:
```bash sudo mkdir -p /var/www/pawn_uploads/items sudo chown -R www-data:www-data /var/www/pawn_uploads Linux Windows无需权限设置,但需确保IIS或应用有写入权限 ```添加上传接口:
```python import uuid from werkzeug.utils import secure_filename ALLOWED_EXTENSIONS = {'pdf', 'png', 'jpg', 'jpeg', 'gif'} def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/api/upload_item_doc', methods=['POST']) def upload_item_document(): if 'file' not in request.files: return jsonify({'error': '未选择文件'}), 400 file = request.files['file'] item_id = request.form.get('item_id') if file.filename == '': return jsonify({'error': '文件名不能为空'}), 400 if not allowed_file(file.filename): return jsonify({'error': '文件类型不支持,仅支持PDF、PNG、JPG、GIF'}), 400 if not item_id: return jsonify({'error': '必须提供当物ID'}), 400 生成唯一文件名 file_ext = file.filename.rsplit('.', 1)[1].lower() unique_filename = f"{uuid.uuid4().hex}.{file_ext}" save_path = os.path.join(app.config['UPLOAD_FOLDER'], 'items', unique_filename) 确保目录存在 os.makedirs(os.path.dirname(save_path), exist_ok=True) file.save(save_path) 将文件路径关联到数据库(需扩展PawnItem模型添加document_paths字段) return jsonify({ 'message': '上传成功', 'file_path': f'/uploads/items/{unique_filename}', 'original_name': file.filename }), 200 ```3.3 当票生成模块
安装报表生成库:pip install reportlab
创建ticket_generator.py:
```python from reportlab.lib.pagesizes import A4 from reportlab.pdfgen import canvas from reportlab.lib.units import mm from datetime import datetime def generate_pawn_ticket_pdf(ticket_data, output_path): c = canvas.Canvas(output_path, pagesize=A4) width, height = A4 标题 c.setFont("Helvetica-Bold", 16) c.drawString(50mm, height-50mm, "典当行当票") 当票编号 c.setFont("Helvetica", 12) c.drawString(50mm, height-65mm, f"当票号:{ticket_data['ticket_id']}") 客户信息 c.drawString(50mm, height-80mm, f"当户:{ticket_data['customer_name']}") c.drawString(50mm, height-90mm, f"证件号:{ticket_data['id_number']}") 当物信息 c.drawString(50mm, height-110mm, f"当物:{ticket_data['item_description']}") c.drawString(50mm, height-120mm, f"评估金额:¥{ticket_data['appraisal_value']:,.2f}") c.drawString(50mm, height-130mm, f"典当金额:¥{ticket_data['loan_amount']:,.2f}") 费率与期限 c.drawString(50mm, height-150mm, f"月费率:{ticket_data['monthly_rate']100:.2f}%") c.drawString(50mm, height-160mm, f"典当期限:{ticket_data['start_date']} 至 {ticket_data['end_date']}") 经办人 c.drawString(50mm, height-180mm, f"经办人:{ticket_data['operator']}") c.drawString(50mm, height-190mm, f"开具日期:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") 重要提示 c.setFont("Helvetica", 10) c.drawString(30mm, 30mm, "重要提示:请妥善保管本当票,赎当时需凭此票办理。逾期未赎当,当物将作绝当处理。") c.save() return output_path 使用示例 if __name__ == '__main__': sample_data = { 'ticket_id': '20231115001', 'customer_name': '张三', 'id_number': '310101199001011234', 'item_description': '劳力士手表,型号126334', 'appraisal_value': 80000.00, 'loan_amount': 50000.00, 'monthly_rate': 0.027, 'start_date': '2023-11-15', 'end_date': '2024-02-15', 'operator': '王经理' } generate_pawn_ticket_pdf(sample_data, 'pawn_ticket_sample.pdf') ```四、前端管理界面开发
4.1 客户档案管理页面
创建customer.html:
```html客户档案管理
4.2 当物档案录入页面
创建item_create.html,在表单中添加:
```html