Date: 2026-07-08 Branch: fapiao Status: Planning
Copy the Alipay industry invoice platform (https://fxkp.alipay.com/industry-invoice-platform/) as a new module in the payment platform. 8 pages with full CRUD, import/export, and mock APIs. Multi-tenant + enterprise + admin data scoping on all tables.
All tables use PaymentEnterpriseBaseEntity (inherits PaymentTenantBaseEntity → PaymentBaseEntity), providing: id (snowflake), status, description, created_time, updated_time, tenant_id, enterprise_id.
Flyway migration file: V1.2__create_pay_invoice_tables.sql
pay_invoice_order — 交易及发票订单| Column | Type | Constraint | Notes |
|---|---|---|---|
| id | BIGINT | PK (snowflake) | From PaymentEnterpriseBaseEntity |
| status | VARCHAR(10) | DEFAULT '0' | From PaymentEnterpriseBaseEntity |
| description | TEXT | From PaymentEnterpriseBaseEntity | |
| created_time | TIMESTAMP | NOT NULL DEFAULT NOW() | From PaymentEnterpriseBaseEntity |
| updated_time | TIMESTAMP | NOT NULL DEFAULT NOW() | From PaymentEnterpriseBaseEntity |
| tenant_id | BIGINT | From PaymentTenantBaseEntity | |
| enterprise_id | VARCHAR(64) | From PaymentEnterpriseBaseEntity | |
| order_no | VARCHAR(64) | NOT NULL | 订单编号 |
| alipay_trade_no | VARCHAR(64) | 支付宝交易号 | |
| payment_time | TIMESTAMP | 支付时间 | |
| natural_person_name | VARCHAR(64) | 自然人姓名 | |
| collection_account_type | VARCHAR(32) | 收款账户类型: ALIPAY / BANK_CARD | |
| tax_inclusive_amount | DECIMAL(18,2) | 含税金额 | |
| trade_status | VARCHAR(32) | NOT NULL DEFAULT 'PENDING_ASSOCIATE' | 交易状态 |
| invoice_no | VARCHAR(64) | 发票号码 | |
| invoice_pretax_amount | DECIMAL(18,2) | 发票税前金额 | |
| invoice_tax_amount | DECIMAL(18,2) | 发票税额 | |
| red_invoice_no | VARCHAR(64) | 红票号码 | |
| clerk_name | VARCHAR(64) | 营业员姓名 | |
| attachment_url | TEXT | 附件URL JSON array |
Indexes:
idx_invoice_order_enterprise ON (enterprise_id)idx_invoice_order_order_no ON (order_no)idx_invoice_order_trade_no ON (alipay_trade_no)idx_invoice_order_trade_status ON (trade_status)idx_invoice_order_created_time ON (created_time)pay_invoice_supplier — 供应商| Column | Type | Constraint | Notes |
|---|---|---|---|
| id | BIGINT | PK (snowflake) | |
| status, description, created_time, updated_time, tenant_id, enterprise_id | From base entities | ||
| supplier_name | VARCHAR(128) | NOT NULL | 供应商名称 |
| account_type | VARCHAR(32) | NOT NULL | ALIPAY_PHONE / ALIPAY_EMAIL |
| collection_account | VARCHAR(128) | NOT NULL | 收款账户 |
| phone | VARCHAR(20) | 联系电话 | |
| confirm_status | VARCHAR(32) | NOT NULL DEFAULT 'PENDING' | CONFIRMED / PENDING / REJECTED |
Indexes:
idx_invoice_supplier_enterprise ON (enterprise_id)idx_invoice_supplier_name ON (supplier_name)idx_invoice_supplier_confirm_status ON (confirm_status)pay_invoice_employee — 员工管理| Column | Type | Constraint | Notes |
|---|---|---|---|
| id | BIGINT | PK (snowflake) | |
| status, description, created_time, updated_time, tenant_id, enterprise_id | From base entities | ||
| employee_name | VARCHAR(64) | NOT NULL | 员工姓名 |
| phone | VARCHAR(20) | NOT NULL | 手机号 |
| id_number | VARCHAR(20) | NOT NULL | 身份证号 |
| role | VARCHAR(32) | NOT NULL | CLERK / INVOICER / SUPER_ADMIN |
| allow_miniapp_supplier | BOOLEAN | DEFAULT FALSE | 允许小程序选供应商 |
Indexes:
idx_invoice_employee_enterprise ON (enterprise_id)idx_invoice_employee_role ON (role)pay_invoice_product — 常用商品| Column | Type | Constraint | Notes |
|---|---|---|---|
| id | BIGINT | PK (snowflake) | |
| status, description, created_time, updated_time, tenant_id, enterprise_id | From base entities | ||
| product_name | VARCHAR(128) | NOT NULL | 商品名称 |
| category | VARCHAR(32) | NOT NULL | 报废产品分类 |
| unit | VARCHAR(32) | 单位 | |
| spec | VARCHAR(128) | 规格 |
Indexes:
idx_invoice_product_enterprise ON (enterprise_id)idx_invoice_product_category ON (category)pay_invoice_company — 企业信息| Column | Type | Constraint | Notes |
|---|---|---|---|
| id | BIGINT | PK (snowflake) | |
| status, description, created_time, updated_time, tenant_id, enterprise_id | From base entities | ||
| company_name | VARCHAR(128) | 企业名称 | |
| tax_id | VARCHAR(64) | 税号 | |
| bank_name | VARCHAR(128) | 开户银行 | |
| bank_account | VARCHAR(64) | 银行账号 | |
| address | VARCHAR(256) | 地址 | |
| phone | VARCHAR(20) | 电话 | |
| tax_region | VARCHAR(64) | 纳税地区 | |
| issuer | VARCHAR(64) | 开票人 | |
| contact_name | VARCHAR(64) | 业务联系人姓名 | |
| contact_phone | VARCHAR(20) | 业务联系人电话 | |
| monthly_quota | DECIMAL(18,2) | DEFAULT 10000000 | 月度授信额度 |
| available_quota | DECIMAL(18,2) | 可用额度 | |
| downloaded_count | INTEGER | DEFAULT 0 | 已下载张数 |
| used_count | INTEGER | DEFAULT 0 | 已使用张数 |
| tax_calculation_method | VARCHAR(32) | DEFAULT 'SIMPLE' | 计税方式 |
| default_invoice_type | VARCHAR(32) | DEFAULT 'NORMAL' | 默认票种 |
| default_tax_rate | VARCHAR(16) | DEFAULT '1%' | 默认税率 |
| audit_after_payment | BOOLEAN | DEFAULT TRUE | 货品审核后付款 |
| invite_as_supplier | BOOLEAN | DEFAULT FALSE | 邀请为供应商 |
| receipt_plaintext | BOOLEAN | DEFAULT FALSE | 电子回单明文 |
| employee_quota_management | BOOLEAN | DEFAULT FALSE | 员工额度管理 |
Index:
idx_invoice_company_enterprise ON (enterprise_id) UNIQUE (one record per enterprise)pay_invoice_task — 任务中心| Column | Type | Constraint | Notes |
|---|---|---|---|
| id | BIGINT | PK (snowflake) | |
| status, description, created_time, updated_time, tenant_id, enterprise_id | From base entities | ||
| task_type | VARCHAR(32) | NOT NULL | 任务类型 |
| task_status | VARCHAR(32) | NOT NULL DEFAULT 'PENDING' | PENDING / PROCESSING / SUCCESS / FAILED |
| start_time | TIMESTAMP | 任务开始时间 | |
| completion_time | TIMESTAMP | 完成时间 | |
| product_name | VARCHAR(128) | 商品名称 | |
| file_url | VARCHAR(512) | 导出文件URL | |
| total_count | INTEGER | DEFAULT 0 | 总记录数 |
| success_count | INTEGER | DEFAULT 0 | 成功数 |
| fail_count | INTEGER | DEFAULT 0 | 失败数 |
| error_message | TEXT | 错误信息 |
Indexes:
idx_invoice_task_enterprise ON (enterprise_id)idx_invoice_task_type ON (task_type)idx_invoice_task_status ON (task_status)pay_invoice_tax_payment — 缴税记录| Column | Type | Constraint | Notes |
|---|---|---|---|
| id | BIGINT | PK (snowflake) | |
| status, description, created_time, updated_time, tenant_id, enterprise_id | From base entities | ||
| payment_mode | VARCHAR(32) | NOT NULL | INDIVIDUAL / ENTERPRISE_PROXY |
| tax_amount | DECIMAL(18,2) | 缴税金额 | |
| tax_period | VARCHAR(32) | 纳税期间 | |
| payment_time | TIMESTAMP | 缴税时间 | |
| payment_status | VARCHAR(32) | DEFAULT 'UNPAID' | UNPAID / PAID |
| enterprise_proxy_enabled | BOOLEAN | DEFAULT FALSE | 是否开通企业代缴 |
Indexes:
idx_invoice_tax_enterprise ON (enterprise_id)idx_invoice_tax_status ON (payment_status)module/payment/invoice/
├── InvoiceModuleConfig.java (optional: module-level @Configuration if needed)
├── order/ # 1. 交易及发票管理
│ ├── controller/
│ │ └── OrderController.java
│ ├── dto/
│ │ ├── OrderQueryDTO.java
│ │ ├── OrderVO.java
│ │ ├── OrderBatchImportDTO.java
│ │ ├── OrderBatchCancelDTO.java
│ │ └── InvoiceDetailVO.java
│ ├── entity/
│ │ └── OrderEntity.java
│ ├── enums/
│ │ └── OrderEnums.java (TradeStatus, CollectionAccountType)
│ ├── mapper/
│ │ └── OrderMapper.java
│ └── service/
│ ├── OrderService.java (list, detail, batchImport, batchCancel, export)
│ └── impl/
│ └── OrderServiceImpl.java (optional — if following existing pattern)
├── account/ # 2. 转账账户管理
│ ├── controller/
│ │ └── InvoiceAccountController.java
│ ├── dto/
│ │ └── AccountBalanceVO.java
│ ├── entity/
│ │ └── InvoiceAccountEntity.java
│ ├── mapper/
│ │ └── InvoiceAccountMapper.java
│ └── service/
│ └── InvoiceAccountService.java
├── tax/ # 3. 缴税管理
│ ├── controller/
│ │ └── TaxPaymentController.java
│ ├── dto/
│ │ ├── TaxPaymentQueryDTO.java
│ │ └── TaxPaymentVO.java
│ ├── entity/
│ │ └── TaxPaymentEntity.java
│ ├── enums/
│ │ └── TaxEnums.java (PaymentMode, PaymentStatus)
│ ├── mapper/
│ │ └── TaxPaymentMapper.java
│ └── service/
│ └── TaxPaymentService.java
├── employee/ # 4. 员工管理
│ ├── controller/
│ │ └── InvoiceEmployeeController.java
│ ├── dto/
│ │ ├── EmployeeSaveDTO.java
│ │ └── EmployeeVO.java
│ ├── entity/
│ │ └── InvoiceEmployeeEntity.java
│ ├── enums/
│ │ └── InvoiceEmployeeEnums.java (EmployeeRole)
│ ├── mapper/
│ │ └── InvoiceEmployeeMapper.java
│ └── service/
│ └── InvoiceEmployeeService.java
├── product/ # 5. 常用商品管理
│ ├── controller/
│ │ └── ProductController.java
│ ├── dto/
│ │ ├── ProductSaveDTO.java
│ │ └── ProductVO.java
│ ├── entity/
│ │ └── ProductEntity.java
│ ├── enums/
│ │ └── ProductEnums.java (ProductCategory)
│ ├── mapper/
│ │ └── ProductMapper.java
│ └── service/
│ └── ProductService.java
├── company/ # 6. 企业信息
│ ├── controller/
│ │ └── CompanyController.java
│ ├── dto/
│ │ ├── CompanyVO.java
│ │ ├── ContactUpdateDTO.java
│ │ └── InvoiceInfoUpdateDTO.java
│ ├── entity/
│ │ └── CompanyEntity.java
│ ├── enums/
│ │ └── CompanyEnums.java (TaxCalcMethod, DefaultInvoiceType)
│ ├── mapper/
│ │ └── CompanyMapper.java
│ └── service/
│ └── CompanyService.java
├── supplier/ # 7. 供应商管理
│ ├── controller/
│ │ └── SupplierController.java
│ ├── dto/
│ │ ├── SupplierSaveDTO.java
│ │ ├── SupplierQueryDTO.java
│ │ └── SupplierVO.java
│ ├── entity/
│ │ └── SupplierEntity.java
│ ├── enums/
│ │ └── SupplierEnums.java (AccountType, ConfirmStatus)
│ ├── mapper/
│ │ └── SupplierMapper.java
│ └── service/
│ └── SupplierService.java
└── task/ # 8. 任务中心
├── controller/
│ └── TaskController.java
├── dto/
│ ├── TaskQueryDTO.java
│ └── TaskVO.java
├── entity/
│ └── TaskEntity.java
├── enums/
│ └── TaskEnums.java (TaskType, TaskStatus)
├── mapper/
│ └── TaskMapper.java
├── scheduler/
│ └── InvoiceTaskScheduler.java (poll export/import tasks, execute asynchronously)
└── service/
├── TaskService.java
└── InvoiceMockDataService.java (singleton mock data factory for all modules)
Key design decisions:
@RestController with @RequestMapping("/payment/invoice/<submodule>")@Service @RequiredArgsConstructor using LambdaQueryWrapper for queries@Mapper extends BaseMapper<Entity>PaymentEnterpriseBaseEntity (auto-tenant + auto-enterprise)InvoiceMockDataService generates realistic mock data using Faker-like patterns for all modulesPageResult.of(pageNo, pageSize, total, items)GET /payment/invoice/order/list — paginated list with filters
GET /payment/invoice/order/{id} — order detail
GET /payment/invoice/order/invoice/{id} — invoice detail (红票/蓝票)
POST /payment/invoice/order/batch-import — batch import (multipart .xlsx)
POST /payment/invoice/order/batch-cancel — batch cancel by order IDs
GET /payment/invoice/order/export/orders — export orders as .xlsx
GET /payment/invoice/order/export/invoices — export invoices as .xlsx
GET /payment/invoice/order/template — download import template
GET /payment/invoice/account/balance — get account balance + pending amount
GET /payment/invoice/account/recharge-info — get recharge bank account info
POST /payment/invoice/account/recharge — (mock) recharge
GET /payment/invoice/tax/config — get tax payment config (mode, proxy enabled)
POST /payment/invoice/tax/mode — set payment mode (individual / enterprise)
POST /payment/invoice/tax/enable-proxy — enable enterprise proxy payment
GET /payment/invoice/tax/records — tax payment history
GET /payment/invoice/employee/list — employee list
POST /payment/invoice/employee — add employee
PUT /payment/invoice/employee/{id} — update employee
DELETE /payment/invoice/employee/{id} — delete employee
GET /payment/invoice/product/list — product list (filter by category)
POST /payment/invoice/product — add product
PUT /payment/invoice/product/{id} — update product
DELETE /payment/invoice/product/{id} — delete product
GET /payment/invoice/product/categories — category tree
GET /payment/invoice/company/info — get company info (auto-create if missing)
POST /payment/invoice/company/sync-tax — mock sync tax info
POST /payment/invoice/company/verify — mock company verification
PUT /payment/invoice/company/contact — update contact info
PUT /payment/invoice/company/invoice-info — update invoice settings
PUT /payment/invoice/company/trade-rules — update trade rule toggles
GET /payment/invoice/supplier/list — paginated list with search filters
POST /payment/invoice/supplier — add supplier
PUT /payment/invoice/supplier/{id} — update supplier
DELETE /payment/invoice/supplier/{id} — delete supplier
POST /payment/invoice/supplier/batch-import — batch import (.xlsx)
GET /payment/invoice/supplier/template — download import template
GET /payment/invoice/task/list — paginated task list
GET /payment/invoice/task/{id} — task detail
GET /payment/invoice/task/{id}/download — download export file
InvoiceMockDataService will be injected into each service constructor. It generates:
Each service method checks if real data exists; if not, falls back to mock data.
src/views/module_payment/invoice/
├── order/
│ ├── index.vue # main page with search + table
│ ├── components/
│ │ ├── SearchForm.vue # search filters
│ │ ├── OrderTable.vue # data table with actions
│ │ ├── OrderDetailDialog.vue # order detail modal
│ │ ├── InvoiceDetailDialog.vue # invoice detail (红票/蓝票) modal
│ │ └── BatchImportDialog.vue # batch import with template download + upload
│ └── index.ts # (if needed for local types)
├── account/
│ ├── index.vue # balance display + recharge section
│ └── components/
│ ├── BalanceCard.vue # account balance + pending amount
│ └── RechargeGuide.vue # collapsible "how to recharge" section
├── payTaxes/
│ ├── index.vue # tax payment mode selection
│ └── components/
│ ├── IndividualPayment.vue # 个人缴税 view
│ └── EnterpriseProxy.vue # 企业代缴 view with "立即开通" flow
├── employeeManagement/
│ ├── index.vue # employee list + add/edit dialogs
│ └── components/
│ ├── EmployeeTable.vue # table with masked fields
│ ├── AddEmployeeDialog.vue # role selector + dynamic form
│ └── EditEmployeeDialog.vue # role readonly, edit form
├── goods/
│ ├── index.vue # left category tree + right product table
│ └── components/
│ ├── CategoryTree.vue # 9-category scrapped product tree
│ └── ProductTable.vue # product list by selected category
├── company/
│ ├── index.vue # enterprise info dashboard
│ └── components/
│ ├── BasicInfoSection.vue # 8 read-only fields
│ ├── ContactSection.vue # editable contact
│ ├── CreditLimitSection.vue # 授信额度 4 fields
│ ├── InvoiceInfoSection.vue # editable invoice settings
│ ├── TradeRuleSection.vue # 4 toggle switches
│ └── ServiceProviderSection.vue # 服务商授权管理 link
├── suppliers/
│ ├── index.vue # supplier list with search + batch import
│ └── components/
│ ├── SupplierTable.vue # table with confirm_status filter
│ ├── AddSupplierDialog.vue # add/edit with save+next
│ └── BatchImportSupplier.vue # batch import dialog
└── tasks/
├── index.vue # task center with search
└── components/
├── TaskTable.vue # task list
└── TaskDetailDrawer.vue # task detail side panel
API modules:
src/api/module_payment/invoice/
├── order.ts
├── account.ts
├── tax.ts
├── employee.ts
├── product.ts
├── company.ts
├── supplier.ts
└── task.ts
Insert SQL in a migration or seed file. Menu entries reference the dynamic component paths that the frontend transformRoutes resolves via import.meta.glob("../../views/**/**.vue").
Industry Invoice Platform (parent menu)
├── type: 1 (directory), route_path: /invoice-platform, route_name: InvoicePlatform
│ component_path: (empty, Layout handles it), icon: "invoice", order: 500
├── 交易及发票管理 (type: 2 menu)
│ parent_id: <parent>, route_path: /invoice-platform/order
│ route_name: InvoiceOrder, component_path: module_payment/invoice/order/index
│ permission: module_payment:invoice:order:list
├── 转账账户管理
│ parent_id: <parent>, route_path: /invoice-platform/account
│ route_name: InvoiceAccount, component_path: module_payment/invoice/account/index
│ permission: module_payment:invoice:account:view
├── 缴税管理
│ parent_id: <parent>, route_path: /invoice-platform/payTaxes
│ route_name: InvoicePayTaxes, component_path: module_payment/invoice/payTaxes/index
│ permission: module_payment:invoice:tax:view
├── 员工管理
│ parent_id: <parent>, route_path: /invoice-platform/employee
│ route_name: InvoiceEmployee, component_path: module_payment/invoice/employeeManagement/index
│ permission: module_payment:invoice:employee:list
├── 常用商品管理
│ parent_id: <parent>, route_path: /invoice-platform/goods
│ route_name: InvoiceGoods, component_path: module_payment/invoice/goods/index
│ permission: module_payment:invoice:product:list
├── 企业信息
│ parent_id: <parent>, route_path: /invoice-platform/company
│ route_name: InvoiceCompany, component_path: module_payment/invoice/company/index
│ permission: module_payment:invoice:company:view
├── 供应商管理
│ parent_id: <parent>, route_path: /invoice-platform/suppliers
│ route_name: InvoiceSuppliers, component_path: module_payment/invoice/suppliers/index
│ permission: module_payment:invoice:supplier:list
└── 任务中心
parent_id: <parent>, route_path: /invoice-platform/tasks
route_name: InvoiceTasks, component_path: module_payment/invoice/tasks/index
permission: module_payment:invoice:task:list
Important: The component_path values must match the file structure module_payment/invoice/<folder>/index.vue, because the permission store's transformRoutes resolves these with modules[../../views/${normalizedRoute.component}.vue].
V1.2__create_pay_invoice_tables.sql with all 7 tables + indexesPaymentEnterpriseBaseEntityInvoiceMockDataService with mock data generationCompanyService — single-record per enterprise, auto-create on first accessOrderService — list, detail, batch cancel, exportSupplierService — list, CRUD, batch importInvoiceEmployeeService — CRUDProductService — CRUD + categoriesInvoiceAccountService — balance queryTaxPaymentService — config + historyTaskService — list, detail@PreAuthorize annotations matching menu permissionssrc/api/module_payment/invoice/ with all 8 API modulesImplement pages in order of complexity:
sys_menu recordsAll tables have tenant_id + enterprise_id columns. TenantInnerInterceptor auto-injects tenant_id on INSERT and auto-filters on SELECT. The enterprise_id must be set manually in the service layer when creating records (typically from the request context or current enterprise selection).
Mock data handling: InvoiceMockDataService should accept tenantId and enterpriseId parameters so mock data is scoped correctly. Each enterprise sees only their own records.
GET /template returns a pre-formatted .xlsx)POST /batch-import multipart)pay_invoice_task for trackingGET /export/orders or GET /export/invoicesExcelUtil, saves to temp storagepay_invoice_task with task_status = 'SUCCESS'A @Scheduled job that polls pay_invoice_task for PENDING tasks and processes them asynchronously. This handles:
The frontend already has an enterprise.store.ts for enterprise selection. All API calls should pass enterprise_id as a parameter (the backend's PaymentEnterpriseBaseEntity handles filtering). The current enterprise is available via useEnterpriseStore().
Each service method should follow this pattern:
// In production, real API calls would go here
// For now, return mock data
return invoiceMockDataService.getOrders(tenantId, enterpriseId, queryDTO);
This makes future migration to real APIs straightforward — just replace the mock service call with a real API client call.
Configure Spring multipart max size to 3MB for batch import (matches the Alipay constraint):
spring.servlet.multipart.max-file-size: 3MB
spring.servlet.multipart.max-request-size: 3MB
But use a controller-level check to allow larger uploads elsewhere.
| Layer | Files |
|---|---|
| SQL Migration | 1 file |
| Entities | 7 files |
| Enums | 7 files |
| Mappers | 8 files |
| DTOs | ~15 files |
| Services | 9 files (8 services + 1 mock service) |
| Controllers | 8 files |
| Scheduler | 1 file |
| Layer | Files |
|---|---|
| API modules | 8 files |
| View pages | 8 files |
| Components | ~12 files |
| Risk | Mitigation |
|---|---|
| Dynamic route resolution fails for new view paths | Verify component_path in sys_menu matches actual file path relative to src/views/ |
| Tenant interceptor not working for invoice tables | Tables use tenant_id column like existing pay_* tables — automatically picked up |
enterprise_id is String not Long (existing convention) |
Use String enterpriseId consistently; InvoiceMockDataService uses String parameter |
| Red/Blue invoice display not well-defined | Mock it: red invoice = negative amounts + red color; blue = normal |
| Large batch import ~3MB causing timeout | Use task scheduler for async processing; immediate response with task ID |
| Phase | Effort (hours) |
|---|---|
| Phase 1: DB + Entities | 3-4 |
| Phase 2: Mock + Core Services | 6-8 |
| Phase 3: Controllers + DTOs | 4-5 |
| Phase 4: Frontend API Layer | 2-3 |
| Phase 5: Simple Pages | 8-10 |
| Phase 6: Complex Pages | 8-10 |
| Phase 7: Menu + Integration | 2-4 |
| Total | 33-44 hours |