fix: band-level windowed refine_layout + programmatic map_fields to prevent 91.5% content loss

Root cause: LLM receiving full 34k-char JRXML would regenerate from scratch
instead of modifying coordinates in-place, shrinking output to ~3k chars.

Solution (programmatic node control, not prompt engineering):

- New agent/jrxml_windower.py: decompose JRXML into header (never sent to
  LLM) + individual bands. Split bands >4000 chars at element boundaries.
  Reassemble with element count validation (>10% change = rollback).

- Rewrite refine_layout: per-band windowed LLM processing (~2-4k chars
  each). LLM cannot "reimagine" the entire report.

- Rewrite map_fields: 100% programmatic regex $F{field_N} -> real name
  replacement. Zero LLM calls, zero content loss.

- _sanitize_field_name: non-ASCII chars escaped to _uXXXX_ format for
  valid JRXML identifiers.

- Tests: 48 new unit tests (windower 28 + map_fields 20). All passing.
  Full suite 385 tests, zero regressions.
This commit is contained in:
2026-05-24 08:55:38 +08:00
parent bb6cc6e241
commit bd5bfbac2d
80 changed files with 39463 additions and 108 deletions
+126 -4
View File
@@ -44,12 +44,15 @@ cd frontend && npm run dev
│ ├── api/client.ts SSE 客户端 + fetch 封装
│ ├── stores/chat.ts Pinia: 消息/流式/节点进度
│ ├── stores/session.ts Pinia: 会话管理
│ ├── stores/kb.ts Pinia: KB 状态管理(多租户知识库)
│ ├── components/
│ │ ├── Sidebar.vue 会话列表 + 下载 + 历史版本
│ │ ├── ChatMessages.vue 消息列表渲染
│ │ ├── ProcessSection.vue 过程折叠区(替代 StreamingMessage + NodeProgress
│ │ ├── UnifiedInput.vue 统一输入框(文本+文件拖拽/粘贴/芯片)
│ │ ── SummaryCard.vue 结果摘要卡片(含耗时)
│ │ ├── UnifiedInput.vue 统一输入框(文本+文件拖拽/粘贴/芯片,含 .jrxml
│ │ ── SummaryCard.vue 结果摘要卡片(含耗时)
│ │ ├── KbSelector.vue KB 下拉选择器(对话中切换知识库)
│ │ └── KbManager.vue KB 管理面板(创建/上传/构建/删除)
│ └── utils/format.ts 工具函数
▼ HTTP + SSE (Server-Sent Events)
@@ -87,9 +90,16 @@ validation_service/ (FastAPI, 端口 8001) — 不变
| `backend/annotation_detector.py` | 批注检测: 圈选(cv2 HoughCircles) + 箭头(HoughLinesP聚类) + OCR关联 + LLM格式化 | 中 |
| `backend/embeddings.py` | 嵌入模型工厂 (HuggingFace/OpenAI) | 低 |
| `backend/validation.py` | 验证服务 HTTP 客户端 | 低 |
| `backend/session.py` | 会话 JSON 文件 CRUD | 低 |
| `backend/session.py` | 会话 JSON 文件 CRUD(含 kb_id | 低 |
| `backend/kb_manager.py` | 用户+知识库 CRUD(多租户,原子 JSON 持久化) | 中 |
| `backend/kb_searcher.py` | 知识库隔离搜索 + 模板检索(per-KB ChromaDB | 中 |
| `backend/kb_parser.py` | KB 解析管道:文件解析→字段提取→chunk切割→向量嵌入 | 中 |
| `backend/field_matcher.py` | OCR↔KB 字段匹配:Embedding 粗筛 + LLM 精确确认 | 中 |
| `agent/datasource.py` | 数据源模式解析:$P{{xxx}} 参数 vs JDBC 直连 | 低 |
| `agent/jrxml_windower.py` | JRXML Band 级窗口化引擎:拆解/切分/重组/元素计数校验 | 中 |
| `validation_service/main.py` | FastAPI 验证服务 | 低 |
| `scripts/init_kb.py` | 知识库初始化/模型下载 | 低 |
| `scripts/init_kb.py` | 旧 RAG 知识库初始化/模型下载 | 低 |
| `scripts/init_default_kb.py` | 多租户默认 KB 初始化(默认用户 + 预置 KB) | 低 |
| `app.py` | ~~旧 Streamlit UI~~(已由 api_server.py + frontend/ 替代) | 废弃 |
## 关键约定
@@ -427,3 +437,115 @@ cd frontend && npx playwright test
### consult_answer 前端显示修复
- `api_server.py``agent_complete` SSE 事件新增 `consult_answer` 字段
## 更新 (v12 — 2026-05-23)
### 多租户知识库系统
**核心架构**:用户自行维护多套知识库,每套 KB 拥有独立的文件存储、JSON 元数据和 ChromaDB 向量集合。会话可绑定不同 KB,LLM 基于 KB 中的字段定义和 JRXML 模板生成报表。
**存储架构**
```
kb_data/
├── users.json # 用户注册表
└── {user_id}/
├── profile.json
└── {kb_id}/
├── meta.json # KB 元数据 + 字段定义 + 模板索引
├── raw/ # 原始上传文件
├── chunks.json # RAG chunks(含 JRXML 模板完整文本)
└── chroma/ # KB 专属 ChromaDB
```
**新增后端模块**
| 文件 | 职责 |
|------|------|
| `backend/kb_manager.py` | 用户+KB CRUDcreate_user/list_users/create_kb/list_kbs/get_kb/delete_kb/update_kb_meta/get_kb_raw_dir/get_kb_chroma_path。原子 JSON 写入(tempfile + os.replace |
| `backend/kb_parser.py` | KB 解析管道:`parse_jrxml_fields()` XML 提取参数/字段/查询 → `process_file_for_kb()` 处理多种格式(jrxml/zip/tar/pdf/docx/xlsx/md`chunk_file_results()` 切割 → `build_kb_from_files()` 全管线(parse→chunk→embed→update meta |
| `backend/kb_searcher.py` | `KBChromaSearcher` 类:per-KB ChromaDB 懒连接。`search()` 语义搜索、`search_templates()` 仅搜索 JRXML 模板 chunk、`add_chunks()` 向量写入。全局 searcher 缓存 `_searchers: dict` |
| `backend/field_matcher.py` | OCR↔KB 字段匹配:1) Embedding 粗筛(余弦相似度 top-3)2) LLM 精确确认。返回 `{"工单号": "billNo", ...}` 映射 |
| `agent/datasource.py` | 数据源模式:`resolve_datasource_mode()` 检测用户意图 → `"parameter"`(默认 $P{xxx})或 `"jdbc"`(SQL 直连)。未配置 DB 时生成反问消息 |
| `scripts/init_default_kb.py` | 默认 KB 初始化:创建默认用户 → 解析 `rag/jrxml_source/` 下的 17 个 JRXML + 16 个 MD → chunk + embed → ChromaDB |
**新增 API 端点**api_server.py):
```
POST /api/users # 创建用户
GET /api/users # 用户列表
GET /api/users/{user_id} # 用户详情
DELETE /api/users/{user_id} # 删除用户
GET /api/users/{user_id}/kbs # KB 列表
POST /api/users/{user_id}/kbs # 创建 KB
GET /api/kbs/{kb_id} # KB 详情
DELETE /api/kbs/{kb_id} # 删除 KB
POST /api/kbs/{kb_id}/upload # 上传文件到 KB
POST /api/kbs/{kb_id}/build # 构建 KBchunk→embed
GET /api/kbs/{kb_id}/status # KB 状态
GET /api/kbs/{kb_id}/fields # KB 字段+模板列表
GET /api/kbs/{kb_id}/search?q=&type= # KB 语义搜索
PUT /api/sessions/{session_id}/kb # 绑定会话-KB
GET /api/sessions/{session_id}/kb # 获取会话绑定的 KB
```
**三条模板获取路径**
1. **管理页预上传**:用户上传文件到 KB → 解析管道 → chunks + ChromaDB → 对话选择 KB → retrieve 节点从 KB 检索
2. **对话框即时上传**:用户拖入 `.jrxml``_parse_jrxml_file()` → 注入 `agent_state["uploaded_template_jrxml"]` → 生成节点直接使用该模板
3. **口头引用模板**:用户说"根据标准结算单模板" → `_detect_template_intent()` 正则匹配 → `retrieve()` 在 KB 中搜索模板 → 注入 `kb_template_jrxml`
**模板上下文注入**:所有 6 个生成节点(generate/generate_skeleton/refine_layout/map_fields/modify_jrxml/correct_jrxml)通过 `_build_template_context(state)` 获取模板上下文,优先级:聊天上传 > KB 检索 > KB 字段定义。6 个 prompt 模板全部新增 `{template_context}` 占位符。
**前端新增**
| 文件 | 职责 |
|------|------|
| `stores/kb.ts` | Pinia store:用户列表、KB 列表、当前选择、字段/模板缓存、CRUD 操作、会话绑定 |
| `components/KbSelector.vue` | 对话顶部 KB 下拉选择器 + 管理按钮 |
| `components/KbManager.vue` | 模态面板:创建 KB、上传文件(支持 .jrxml/.md/.xlsx/.docx/.pdf/.csv/.zip 等)、构建、删除 |
**API Server 增强**
- `_process_files()` 检测 `.jrxml` 文件 → 提取参数/字段/查询/页面尺寸 → 注入 `uploaded_template_jrxml` + `uploaded_template_params`
- `agent/state.py` 新增 10 个字段:`kb_id`, `kb_fields`, `kb_field_mapping`, `uploaded_template_jrxml`, `uploaded_template_params`, `kb_template_jrxml`, `kb_template_name`, `datasource_mode`, `db_config`
**字段匹配管线**`_match_ocr_to_kb` → 尚未集成到节点):OCR 提取中文字段名 → `match_ocr_to_kb()` 两阶段匹配 → 结果为 `{"工单号": "billNo"}``format_field_mapping_context()` 注入 prompt → LLM 使用 `$P{billNo}` 而非 `$P{工单号}`
## 更新 (v13 — 2026-05-24)
### 3 阶段管道内容丢失修复 — Band 级窗口化 + 程序化字段映射
**问题**`generate_skeleton` 生成 34k 字符骨架 JRXML → `refine_layout` 将完整 34k 发给 LLM → LLM 重新生成简化版(~3k 字符,丢失 91.5%)。`map_fields` 同样存在字段映射时内容丢失问题。
**根因**:LLM 看到完整 JRXML 时倾向于"重新生成"而非"在原基础上修改坐标/字段名"。提示词调控无法可靠解决。
**修复方案**(按用户要求 — 程序化节点控制,不靠 LLM 提示词):
#### `refine_layout`Band 级窗口化 LLM 精调
新增 `agent/jrxml_windower.py` — JRXML 拆解/切分/重组引擎:
| 函数 | 用途 |
|------|------|
| `decompose_jrxml()` | ET 安全解析 → 分离 headerfield 声明/queryString 等,不发给 LLM+ 所有 band |
| `split_band_into_windows()` | 超过 4000 字符的 band 在元素闭合标签处切分为多个窗口 |
| `reassemble_band_windows()` | 合并同一 band 的多个窗口结果 |
| `reassemble_jrxml()` | header + 所有修改后 band + footer → 完整 JRXML |
| `count_elements()` | 正则计数 textField/staticText/field(兼容命名空间前缀) |
| `validate_element_count()` | 校验元素数变化,>10% 回退到前一版本 |
**LLM 每次只看到 ~2-4k 字符片段**,无法"重写整个报表"。header 部分完全不发给 LLM,原样保留。
#### `map_fields`:完全程序化替换(零 LLM 调用)
`_programmatic_map_fields()` — 纯正则替换 `$F{field_N}` → OCR 真实字段名,100% 确定性。
`_sanitize_field_name()` — 非 ASCII 字符(中文/日文)转义为 `_uXXXX_` Unicode 码点格式,确保 JRXML 合法。
#### 新增测试
| 文件 | 用例数 | 覆盖 |
|------|--------|------|
| `tests/test_jrxml_windower.py` | 28 | 拆解/往返重组/窗口切分/元素计数/命名空间/多 section 多 band |
| `tests/test_programmatic_map_fields.py` | 20 | 字段声明替换/引用替换/中文转义/坐标保留/部分映射/空字段跳过 |
完整测试套件(385 项)无回归。
+115
View File
@@ -0,0 +1,115 @@
"""数据源模式解析模块。
默认使用 $P{xxx} 参数模式;用户可选择 JDBC 直连模式。
"""
import json
import os
import re
from typing import Optional
from dotenv import load_dotenv
from agent.state import AgentState
load_dotenv()
def resolve_datasource_mode(state: AgentState) -> str:
"""返回数据源模式: "parameter""jdbc"
优先读取 state 中已设定的模式,否则根据用户输入检测。
"""
existing = state.get("datasource_mode", "")
if existing in ("parameter", "jdbc"):
return existing
user_input = state.get("user_input", "")
if _detect_jdbc_intent(user_input):
return "jdbc"
return "parameter"
def _detect_jdbc_intent(user_input: str) -> bool:
"""检测用户是否想要 JDBC 直连数据库模式。"""
patterns = [
r"(直连|直连数据库|数据库直连)",
r"(从|在)(数据库|DB|MySQL|PostgreSQL|Oracle|SQL Server)\w*",
r"(jdbc|JDBC)",
r"(连接|连)(数据库|DB)",
r"(查询|select|SELECT)\s",
]
for pat in patterns:
if re.search(pat, user_input):
return True
return False
def _sanitize_url(url: str) -> str:
"""剥离 JDBC URL 中的 user:password@ 片段,防止泄露到 LLM prompt。"""
return re.sub(r"://[^@]*@", "://***:***@", url)
def build_datasource_context(mode: str, kb_fields: list, db_config: Optional[dict] = None) -> str:
"""构建数据源上下文字符串,注入生成 prompt。"""
if mode == "jdbc":
if not db_config or not db_config.get("url"):
return (
"[数据源模式: JDBC]\n"
"⚠ 用户想要 JDBC 直连模式,但尚未配置数据库连接信息。\n"
"请先生成带 $P{xxx} 参数占位符的 JRXML,并提醒用户配置 JDBC 连接。"
)
safe_url = _sanitize_url(db_config.get("url", ""))
return (
"[数据源模式: JDBC]\n"
f"连接URL: {safe_url}\n"
f"驱动: {db_config.get('driver', '')}\n"
"请使用 <queryString><![CDATA[...]]></queryString> 中的 SQL 查询。"
)
# parameter mode
if kb_fields:
field_list = "\n".join(
f"| {f['name']} | {f.get('description', '')} | {f.get('type', 'java.lang.String')} |"
for f in kb_fields
)
return (
"[数据源模式: 参数]\n"
"使用 $P{xxx} 参数模式,以下为可用参数:\n"
f"| 参数名 | 含义 | 类型 |\n|---|---|---|\n{field_list}"
)
return "[数据源模式: 参数]\n使用 $P{xxx} 参数模式生成 JRXML。"
def configure_jdbc(state: AgentState, url: str = "", driver: str = "",
username: str = "", password: str = "") -> dict:
"""配置 JDBC 连接并返回更新字段。
注意:db_config 会被存入 AgentState 并持久化到会话文件。
生产环境应使用外部密钥管理服务,避免明文存储密码。
"""
return {
"datasource_mode": "jdbc",
"db_config": {
"url": url,
"driver": driver or "com.mysql.cj.jdbc.Driver",
"username": username,
"password": password,
},
}
def ask_db_config(state: AgentState) -> Optional[str]:
"""如果用户选了 JDBC 模式但未配置 DB 连接,返回反问消息。"""
mode = resolve_datasource_mode(state)
if mode == "jdbc":
db_config = state.get("db_config", {})
if not db_config or not db_config.get("url"):
return (
"您选择了数据库直连模式,请提供以下信息:\n"
"1. JDBC URL(如 jdbc:mysql://localhost:3306/dbname\n"
"2. 数据库用户名\n"
"3. 数据库密码\n"
"4. 驱动类名(可选,默认 com.mysql.cj.jdbc.Driver"
)
return None
+377
View File
@@ -0,0 +1,377 @@
"""JRXML 窗口化拆解与重组工具。
用于 3 阶段生成管道的 refine_layout 和 map_fields 节点:
- 将大段 JRXML 按 band 拆解为独立窗口
- 每个窗口独立发送给 LLM 进行坐标精调
- 重组所有窗口 + 校验元素完整性
调用者: agent/nodes.py (refine_layout, map_fields)
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import Optional
import defusedxml.ElementTree as ET
from backend.logger import get_logger
_windower_log = get_logger("jrxml.windower")
# 需要按 section 拆解的 band 容器标签
_SECTION_TAGS = {
"title", "pageHeader", "columnHeader", "detail", "columnFooter",
"pageFooter", "lastPageFooter", "summary", "noData", "background",
}
# 不发给 LLM 的 header 元素(原样保留)
_HEADER_TAGS = {
"property", "propertyExpression", "import", "template", "reportFont",
"style", "subDataset", "scriptlet", "parameter", "queryString",
"field", "sortField", "variable", "filterExpression", "group",
}
@dataclass
class BandInfo:
"""单个 band 的拆解信息。"""
section_name: str # 所属 section 名,如 "title", "detail"
band_index: int # 在该 section 中的序号(0-based
band_xml: str # 完整 <band ...>...</band> 原始 XML
element_count: int # textField + staticText 数量
char_length: int # 字符数
@property
def label(self) -> str:
"""用于日志和 prompt 的标识。"""
if self.band_index > 0:
return f"{self.section_name}_band{self.band_index}"
return self.section_name
@dataclass
class JRXMLParts:
"""JRXML 拆解结果。"""
declaration: str # <?xml version="1.0"?>(如有)
root_open: str # <jasperReport ...>
header_xml: str # fields/params/queryString 等(不发给 LLM
bands: list[BandInfo] # 按出现顺序
footer: str # </jasperReport>
@property
def band_count(self) -> int:
return len(self.bands)
@property
def total_elements(self) -> int:
return sum(b.element_count for b in self.bands)
# ── 拆解 ──────────────────────────────────────────────────────────
def decompose_jrxml(jrxml: str) -> Optional[JRXMLParts]:
"""将 JRXML 字符串拆解为 header + bands + footer 三部分。
使用 defusedxml.ElementTree 进行安全解析。
返回 None 表示解析失败。
"""
try:
root = ET.fromstring(jrxml)
except ET.ParseError as e:
_windower_log.error("JRXML 解析失败: %s", e)
return None
tag = _local_tag(root.tag)
if tag != "jasperReport":
_windower_log.error("根元素不是 jasperReport: %s", tag)
return None
# 提取 XML 声明
declaration = ""
if jrxml.strip().startswith("<?xml"):
decl_end = jrxml.find("?>")
if decl_end != -1:
declaration = jrxml[:decl_end + 2]
# 提取根元素属性来重建 root_open
root_open = _build_root_open(jrxml, root)
# 分离 header 子元素和 section 子元素
header_children = []
section_children = [] # (section_tag, child_elem)
for child in root:
child_tag = _local_tag(child.tag)
if child_tag in _HEADER_TAGS:
header_children.append(child)
elif child_tag in _SECTION_TAGS:
section_children.append((child_tag, child))
# 构建 header_xml:序列化所有 header 子元素
header_parts = []
for child in header_children:
header_parts.append(_elem_to_string(child))
header_xml = "\n".join(header_parts)
# 提取 bands:每个 section 内可能有多个 <band>
bands = []
for sec_tag, sec_elem in section_children:
for bi, band_elem in enumerate(sec_elem):
band_local = _local_tag(band_elem.tag)
if band_local != "band":
continue
band_xml = _elem_to_string(band_elem)
ec = _count_elements_in_text(band_xml)
bands.append(BandInfo(
section_name=sec_tag,
band_index=bi,
band_xml=band_xml,
element_count=ec,
char_length=len(band_xml),
))
# 提取 footer</jasperReport> 闭合标签
footer = _extract_footer(jrxml)
parts = JRXMLParts(
declaration=declaration,
root_open=root_open,
header_xml=header_xml,
bands=bands,
footer=footer,
)
_windower_log.info(
"JRXML 拆解完成: %d bands, %d 个元素, header %d 字符",
len(bands), parts.total_elements, len(header_xml),
)
return parts
# ── 窗口切分 ──────────────────────────────────────────────────────
# 安全的元素边界:在这些闭合标签后切分
_SAFE_SPLIT_CLOSING = re.compile(
r"</(?:[\w:]+:)?(?:textField|staticText|line|rectangle|ellipse|image|"
r"frame|subreport|elementGroup|break|componentElement)>\s*"
)
def split_band_into_windows(band: BandInfo, max_chars: int = 4000) -> list[str]:
"""将一个 band 的 XML 在元素边界处切分为多个窗口。
每个窗口是合法的 XML 片段(完整的 <band>...</band>),
大小不超过 max_chars。
"""
if band.char_length <= max_chars:
return [band.band_xml]
inner = _extract_band_inner(band.band_xml)
if not inner:
return [band.band_xml]
segments = _split_at_boundaries(inner, _SAFE_SPLIT_CLOSING)
if len(segments) <= 1:
return [band.band_xml]
windows = _greedy_aggregate(segments, band.band_xml, max_chars)
return windows
# ── 重组 ──────────────────────────────────────────────────────────
def reassemble_band_windows(modified_windows: list[str]) -> str:
"""将多个窗口的修改结果重新合并为一个 band XML。
策略:取第一个窗口的开头(band 标签)和最后一个窗口的结尾(/band 标签),
中间拼接所有窗口内部的元素内容。
"""
if len(modified_windows) == 1:
return modified_windows[0]
first = modified_windows[0]
band_open_end = first.find(">")
if band_open_end == -1:
return "\n".join(modified_windows)
band_open = first[:band_open_end + 1]
last = modified_windows[-1]
band_close = _extract_band_close(last)
inner_parts = []
for win in modified_windows:
inner = _extract_band_inner(win)
if inner:
inner_parts.append(inner)
return band_open + "\n" + "\n".join(inner_parts) + "\n" + band_close
def reassemble_jrxml(parts: JRXMLParts, modified_bands: dict[str, str]) -> str:
"""将修改后的 bands 与 header/footer 重新组装为完整 JRXML。
modified_bands 的 key 格式为 "{section_name}_band{index}""{section_name}"index=0 时)。
"""
result = []
if parts.declaration:
result.append(parts.declaration)
result.append(parts.root_open)
if parts.header_xml.strip():
result.append(parts.header_xml)
current_section = None
for band in parts.bands:
if band.section_name != current_section:
if current_section is not None:
result.append(f"</{current_section}>")
current_section = band.section_name
result.append(f"<{current_section}>")
modified = modified_bands.get(band.label, band.band_xml)
result.append(modified)
if current_section is not None:
result.append(f"</{current_section}>")
result.append(parts.footer)
return "\n".join(result)
# ── 元素计数与校验 ────────────────────────────────────────────────
_ELEMENT_RE = re.compile(r"<(?:[\w:]+:)?(textField|staticText|field)\b", re.IGNORECASE)
def count_elements(jrxml: str) -> int:
"""正则计数 JRXML 中的 textField + staticText + field 声明。"""
return len(_ELEMENT_RE.findall(jrxml))
def validate_element_count(original: str, modified: str, stage: str) -> dict:
"""校验修改前后的元素数变化。
返回:
{"ok": bool, "original": int, "modified": int, "change_pct": float}
变化 > 10% 时 ok=False,调用方应回退。
"""
orig = count_elements(original)
mod = count_elements(modified)
if orig == 0:
return {"ok": True, "original": 0, "modified": mod, "change_pct": 0}
change = abs(mod - orig) / orig
ok = change <= 0.10
if not ok:
_windower_log.error(
"%s 元素数变化过大: %d%d (%.1f%%)",
stage, orig, mod, change * 100,
)
elif change > 0.05:
_windower_log.warning(
"%s 元素数有差异: %d%d (%.1f%%)",
stage, orig, mod, change * 100,
)
return {"ok": ok, "original": orig, "modified": mod, "change_pct": round(change, 4)}
# ── 内部工具函数 ──────────────────────────────────────────────────
def _local_tag(tag: str) -> str:
"""去除 XML 命名空间前缀。"""
return tag.split("}")[-1] if "}" in tag else tag
def _elem_to_string(elem: ET.Element) -> str:
"""将 ElementTree 元素序列化为字符串(使用 defusedxml 的 tostring)。"""
raw = ET.tostring(elem, encoding="unicode")
return raw.strip()
def _build_root_open(jrxml: str, root: ET.Element) -> str:
"""从原始文本重建 <jasperReport ...> 开头标签。"""
m = re.search(r"<jasperReport\b[^>]*>", jrxml, re.IGNORECASE)
if m:
return m.group(0)
attrs = []
for k, v in root.attrib.items():
attrs.append(f'{k}="{v}"')
return "<jasperReport " + " ".join(attrs) + ">"
def _extract_footer(jrxml: str) -> str:
"""提取 </jasperReport> 闭合标签。"""
m = re.search(r"</(?:[\w:]+:)?jasperReport>\s*$", jrxml, re.IGNORECASE)
if m:
return m.group(0).rstrip()
return "</jasperReport>"
_BAND_CLOSE_RE = re.compile(r"</(?:[\w:]+:)?band>\s*$", re.IGNORECASE)
def _extract_band_close(band_xml: str) -> str:
"""提取 band 的闭合标签(兼容命名空间前缀),如 '</ns0:band>''</band>'"""
m = _BAND_CLOSE_RE.search(band_xml)
return m.group(0).rstrip() if m else "</band>"
def _extract_band_inner(band_xml: str) -> str:
"""提取 <band ...> 和 </ns0:band> 之间的内容(兼容命名空间前缀)。"""
tag_end = band_xml.find(">")
if tag_end == -1:
return ""
close_m = _BAND_CLOSE_RE.search(band_xml)
if not close_m:
return band_xml[tag_end + 1:].strip()
return band_xml[tag_end + 1:close_m.start()].strip()
def _split_at_boundaries(text: str, boundary_re: re.Pattern) -> list[str]:
"""在正则匹配的闭合标签处切分文本。
返回切分后的片段列表(分隔符附加到前一个片段末尾)。
"""
segments = []
last_end = 0
for m in boundary_re.finditer(text):
end = m.end()
segments.append(text[last_end:end])
last_end = end
if last_end < len(text):
segments.append(text[last_end:])
elif not segments:
segments.append(text)
return segments
def _greedy_aggregate(segments: list[str], band_xml: str, max_chars: int) -> list[str]:
"""贪心聚合:将片段组合成不超过 max_chars 的窗口。
每个窗口包上 <band ...> 和 </band> 标签。
"""
tag_end = band_xml.find(">")
band_open = band_xml[:tag_end + 1] if tag_end != -1 else "<band>"
band_close = _extract_band_close(band_xml)
overhead = len(band_open) + len(band_close) + 1 # +1 for \n
windows = []
current = []
current_len = overhead
for seg in segments:
seg_len = len(seg)
if current and current_len + seg_len > max_chars:
windows.append(band_open + "\n" + "".join(current) + "\n" + band_close)
current = [seg]
current_len = overhead + seg_len
else:
current.append(seg)
current_len += seg_len
if current:
windows.append(band_open + "\n" + "".join(current) + "\n" + band_close)
return windows
def _count_elements_in_text(xml_text: str) -> int:
"""统计 XML 文本中的 textField + staticText 数量。"""
return len(_ELEMENT_RE.findall(xml_text))
+307 -75
View File
@@ -418,7 +418,8 @@ def load_session_node(state: AgentState) -> Dict:
state["session_name"] = data.get("session_name", "")
state["created_at"] = data.get("created_at", "")
except Exception:
pass
_node_log.warning("会话加载失败,使用空状态",
extra={"session_id": state.get("session_id", "")})
return state
@@ -454,7 +455,8 @@ def save_session_node(state: AgentState) -> Dict:
state["session_name"] = session_name
state["updated_at"] = persistable["updated_at"]
except Exception:
pass
_node_log.exception("会话保存失败",
extra={"session_id": state.get("session_id", "")})
return state
@@ -493,6 +495,90 @@ def _format_row_coordinates(row: dict) -> dict:
return {"y_center": row.get("y_center", 0), "columns": cols}
def _build_sampled_text(ocr_rows: list) -> str:
"""从 OCR 行数据构建采样坐标 JSON 字符串。"""
sampled = {}
if isinstance(ocr_rows, list) and len(ocr_rows) >= 1:
sampled["header_row"] = _format_row_coordinates(ocr_rows[0])
if len(ocr_rows) > 1:
sampled["first_data_row"] = _format_row_coordinates(ocr_rows[1])
if len(ocr_rows) > 2:
sampled["last_row"] = _format_row_coordinates(ocr_rows[-1])
return json.dumps(sampled, ensure_ascii=False, indent=2)
def _extract_band_height(band_xml: str) -> int:
"""从 <band height="N"> 中提取高度值。"""
m = re.search(r'<band\b[^>]*\sheight\s*=\s*"(\d+)"', band_xml)
return int(m.group(1)) if m else 0
def _extract_xml_fragment(text: str) -> str:
"""从 LLM 响应中提取 XML 片段(去除 markdown 代码块和解释文本)。"""
text = text.strip()
# 尝试提取 markdown 代码块内的内容
m = re.search(r"```(?:xml)?\s*([\s\S]*?)```", text, re.IGNORECASE)
if m:
content = m.group(1).strip()
if content:
return content
# 尝试找到 <band ...>...</band> 片段
m = re.search(r"(<band\b[\s\S]*?</band>)", text, re.IGNORECASE)
if m:
return m.group(1).strip()
return text
def _programmatic_map_fields(jrxml: str, ocr_fields: list[dict]) -> str:
"""程序化字段映射:将 $F{{field_N}} 替换为 OCR 提取的真实字段名。
纯正则替换,不调 LLM。100% 确定性,零内容丢失。
"""
result = jrxml
for i, f in enumerate(ocr_fields):
placeholder = f"field_{i+1}"
raw_name = f.get("field_name", "")
if not raw_name:
continue
real_name = _sanitize_field_name(raw_name)
if real_name == placeholder:
continue
# 替换 field 声明: <field name="field_1" → <field name="customer_name"
result = re.sub(
rf'(<field\b[^>]*\bname\s*=\s*"){re.escape(placeholder)}(")',
rf'\g<1>{real_name}\g<2>', result,
)
# 替换所有引用: $F{{field_1}} → $F{{customer_name}}
result = result.replace(f'$F{{{placeholder}}}', f'$F{{{real_name}}}')
return result
def _sanitize_field_name(name: str) -> str:
"""将 OCR 字段名净化为合法的 JRXML field name(仅 ASCII 字母/数字/下划线)。
非 ASCII 字符会被替换为其 Unicode 码点,确保唯一且合法。
"""
result = []
for ch in name:
if ch.isascii() and (ch.isalnum() or ch == '_'):
result.append(ch)
elif ch.isascii():
result.append('_')
else:
# 非 ASCII 转 _uXXXX_ 格式,保留可追溯性
cp = ord(ch)
result.append(f'_u{cp:04X}_')
cleaned = ''.join(result)
cleaned = cleaned.strip('_')
if not cleaned:
return "unnamed_field"
if cleaned[0].isdigit():
cleaned = 'f_' + cleaned
# 压缩连续下划线
cleaned = re.sub(r'_{2,}', '_', cleaned)
return cleaned.lower()
def _format_ocr_context(state: AgentState) -> str:
"""将 OCR 提取结果格式化为 LLM 可用的上下文文本。"""
ocr_result = state.get("ocr_extraction_result")
@@ -619,27 +705,116 @@ def _log_ocr_layers(state: AgentState) -> None:
@log_node("retrieve")
def retrieve(state: AgentState) -> Dict:
"""在 ChromaDB + 错误知识库中搜索相关的 JRXML 模板和组件。"""
"""在 ChromaDB + 错误知识库中搜索相关的 JRXML 模板和组件。
支持按 KB 隔离搜索 + 模板意图检测。
"""
try:
from backend.rag_adapter import search_chunks
from backend.error_kb import search_error_cases
user_input = state.get("user_input", "")
context = search_chunks(user_input, k=5)
kb_id = state.get("kb_id", "")
context = search_chunks(user_input, k=5, kb_id=kb_id)
# 如果有最近错误,同时搜索错误知识库
# 错误知识库
error_msg = state.get("error_msg", "")
if error_msg:
error_context = search_error_cases(error_msg, k=2)
if error_context:
context = f"{context}\n\n[历史错误修正案例]\n{error_context}"
# 模板意图检测:用户是否提到了模板名?
template_keywords = _detect_template_intent(user_input)
if template_keywords and kb_id:
try:
from backend.kb_searcher import search_templates_in_kb
templates = search_templates_in_kb(kb_id, template_keywords, k=1)
if templates:
tmpl = templates[0]
state["kb_template_jrxml"] = tmpl.get("content", "")
state["kb_template_name"] = (
tmpl.get("metadata", {}).get("report_name", "")
)
context += (
f"\n\n[匹配到模板: {state['kb_template_name']}]\n"
f"{state['kb_template_jrxml']}"
)
except Exception:
_node_log.warning("模板检索失败", extra={"kb_id": kb_id})
state["retrieved_context"] = context
except Exception:
_node_log.exception("RAG 检索失败", extra={"user_input": user_input[:80]})
state["retrieved_context"] = ""
return state
def _detect_template_intent(user_input: str) -> str:
"""检测用户输入中是否包含模板引用意图,返回提取的搜索关键词。"""
import re
patterns = [
r"根据(.+?)模板",
r"基于(.+?)模板",
r"参照(.+?)模板",
r"用(.+?)模板",
r"使用(.+?)模板",
r"(.+单)模板",
]
for pat in patterns:
m = re.search(pat, user_input)
if m:
kw = m.group(1).strip()
if len(kw) >= 2:
return kw
return ""
def _build_template_context(state: dict) -> str:
"""构建模板上下文,用于注入生成 prompt。
优先级:对话上传 > KB 检索 > KB 字段定义。
"""
parts = []
# 对话中上传的 JRXML 模板
uploaded = state.get("uploaded_template_jrxml", "")
if uploaded:
params = state.get("uploaded_template_params", [])
param_str = "\n".join(
f" - {p['name']} ({p.get('type', 'String')})" for p in params
) if params else "(参数列表未解析)"
parts.append(
"[对话上传的模板]\n"
f"以下为用户上传的 JRXML 模板,请基于此模板进行修改:\n"
f"模板参数:\n{param_str}\n"
f"```xml\n{uploaded}\n```"
)
# KB 检索到的模板
kb_tmpl = state.get("kb_template_jrxml", "")
kb_name = state.get("kb_template_name", "")
if kb_tmpl and not uploaded:
parts.append(
f"[知识库模板: {kb_name}]\n"
f"以下为从知识库检索到的 JRXML 模板,请作为结构参考:\n"
f"```xml\n{kb_tmpl}\n```"
)
# KB 字段定义
kb_fields = state.get("kb_fields", [])
if kb_fields:
field_table = "\n".join(
f"| {f['name']} | {f.get('description', '')} | {f.get('type', 'String')} |"
for f in kb_fields
)
parts.append(
"[可用数据字段]\n"
"生成 JRXML 时请使用以下字段作为 $P{{xxx}} 参数:\n"
f"| 字段名 | 含义 | 类型 |\n|---|---|---|\n{field_table}"
)
return "\n\n".join(parts)
@log_node("generate")
def generate(state: AgentState) -> Dict:
"""根据用户需求和检索到的上下文生成初始 JRXML。"""
@@ -656,6 +831,7 @@ def generate(state: AgentState) -> Dict:
prompt = load_prompt("initial_generation").format(
context=state.get("retrieved_context", ""),
user_request=user_request,
template_context=_build_template_context(state),
)
full = []
for chunk in llm.stream(prompt):
@@ -683,6 +859,7 @@ def generate_skeleton(state: AgentState) -> Dict:
layout_schema=schema_text,
context=state.get("retrieved_context", ""),
user_request=user_request,
template_context=_build_template_context(state),
)
prev_jrxml = state.get("current_jrxml", "")
full_text = _generate_with_continuation(llm, prompt, writer, "generate_skeleton")
@@ -700,92 +877,145 @@ def generate_skeleton(state: AgentState) -> Dict:
@log_node("refine_layout")
def refine_layout(state: AgentState) -> Dict:
"""阶段二:使用采样坐标(表头 + 首行数据 + 最后一行)精确调整元素位置。"""
"""阶段二:Band 级窗口化精调 — 拆解骨架 JRXML 为独立 band,逐窗口 LLM 调整坐标。
流程:
1. decompose_jrxml() 拆解为 header + bands + footer
2. 每个 band 作为一个(或多个,若 >4000 字符)窗口发给 LLM
3. LLM 只修改该窗口内 reportElement 的 x/y/width/height
4. reassemble_jrxml() 重组 + validate_element_count() 校验
"""
from langgraph.config import get_stream_writer
from agent.jrxml_windower import (
decompose_jrxml, split_band_into_windows, reassemble_band_windows,
reassemble_jrxml, validate_element_count,
)
writer = get_stream_writer()
llm = get_llm(caller="refine_layout")
ocr_rows = state.get("ocr_elements", [])
sampled = {}
if isinstance(ocr_rows, list) and len(ocr_rows) >= 1:
sampled["header_row"] = _format_row_coordinates(ocr_rows[0])
if len(ocr_rows) > 1:
sampled["first_data_row"] = _format_row_coordinates(ocr_rows[1])
if len(ocr_rows) > 2:
sampled["last_row"] = _format_row_coordinates(ocr_rows[-1])
sampled_text = json.dumps(sampled, ensure_ascii=False, indent=2)
prompt = load_prompt("refine_layout").format(
current_jrxml=state.get("current_jrxml", ""),
sampled_coordinates=sampled_text,
)
prev_jrxml = state.get("current_jrxml", "")
full_text = _generate_with_continuation(llm, prompt, writer, "refine_layout")
if not full_text.strip():
_node_log.error("refine_layout LLM 返回空响应,保留前一版本")
if not prev_jrxml.strip():
_node_log.warning("refine_layout 无输入 JRXML,跳过")
return state
jrxml = _extract_jrxml(full_text)
if len(jrxml.strip()) < 200:
_node_log.warning(f"refine_layout 输出过短({len(jrxml)} 字符),回退到前一版本")
jrxml = prev_jrxml
state["current_jrxml"] = jrxml
state["conversation_history"].append({"role": "assistant", "content": jrxml})
# 拆解 JRXML
parts = decompose_jrxml(prev_jrxml)
if parts is None or parts.band_count == 0:
_node_log.warning("refine_layout 拆解失败或无 band,跳过")
return state
# 构建采样坐标
ocr_rows = state.get("ocr_elements", [])
sampled_text = _build_sampled_text(ocr_rows)
template_ctx = _build_template_context(state)
# 逐 band 窗口化精调
modified_bands: dict[str, str] = {}
total_windows = 0
for band in parts.bands:
if band.element_count == 0:
modified_bands[band.label] = band.band_xml
continue
windows = split_band_into_windows(band, max_chars=4000)
total_windows += len(windows)
band_results: list[str] = []
for wi, win_xml in enumerate(windows):
prompt = load_prompt("refine_layout").format(
band_name=band.section_name,
band_index=band.band_index,
band_height=_extract_band_height(win_xml),
window_index=wi + 1,
total_windows=len(windows),
xml_fragment=win_xml,
sampled_coordinates=sampled_text,
template_context=template_ctx,
)
try:
response = llm.invoke(prompt)
content = response.content if hasattr(response, "content") else str(response)
fragment = _extract_xml_fragment(content)
if fragment:
band_results.append(fragment)
writer({"type": "stream", "node": "refine_layout",
"text": f"[{band.label} 窗口 {wi+1}/{len(windows)} 完成] "})
else:
_node_log.warning("refine_layout 窗口 %s/%d 返回空,使用原文",
band.label, wi + 1)
band_results.append(win_xml)
except Exception as e:
_node_log.warning("refine_layout 窗口 %s/%d LLM 失败: %s,使用原文",
band.label, wi + 1, e)
band_results.append(win_xml)
if len(band_results) == 1:
modified_bands[band.label] = band_results[0]
else:
modified_bands[band.label] = reassemble_band_windows(band_results)
# 重组并校验
result = reassemble_jrxml(parts, modified_bands)
validation = validate_element_count(prev_jrxml, result, "refine_layout")
_node_log.info(
"refine_layout 窗口化完成: %d bands, %d 窗口, 元素 %d%d (%.1f%%)",
parts.band_count, total_windows,
validation["original"], validation["modified"],
validation["change_pct"] * 100,
)
if not validation["ok"]:
_node_log.error("refine_layout 元素丢失过多,回退到骨架版本")
return state
state["current_jrxml"] = result
state["conversation_history"].append({"role": "assistant", "content": result})
return state
@log_node("map_fields")
def map_fields(state: AgentState) -> Dict:
"""阶段三:将占位字段名替换为 OCR 提取的真实字段名。"""
from langgraph.config import get_stream_writer
"""阶段三:程序化字段映射 — 用正则将 $F{field_N} 替换为 OCR 字段名,不调 LLM。
writer = get_stream_writer()
llm = get_llm(caller="map_fields")
仅当 OCR 字段名包含中文等需要语义解释时才回退到 LLM。
"""
from agent.jrxml_windower import validate_element_count
ocr_result = state.get("ocr_extraction_result", {})
fields_text = ""
if isinstance(ocr_result, dict) and ocr_result.get("fields"):
field_descs = []
for f in ocr_result["fields"]:
fname = f.get("field_name", "")
fval = f.get("field_value", "")
if fname:
field_descs.append(f" - {fname}: {fval}")
if field_descs:
fields_text = "提取的字段:\n" + "\n".join(field_descs)
if not fields_text:
elements = ocr_result.get("elements", []) if isinstance(ocr_result, dict) else []
if elements:
texts = [e.get("text", "") for e in elements if e.get("text")]
fields_text = "OCR 文本内容:\n" + "\n".join(f" - {t}" for t in texts[:50])
prompt = load_prompt("field_mapping").format(
current_jrxml=state.get("current_jrxml", ""),
ocr_fields=fields_text,
)
prev_jrxml = state.get("current_jrxml", "")
full_text = _generate_with_continuation(llm, prompt, writer, "map_fields")
# 空响应重试:有时 LLM 第一轮不输出,换个方式再试一次
if not full_text.strip():
_node_log.warning("map_fields 第一轮返回空响应,尝试简化 prompt 重试")
retry_prompt = (
"请将以下 JRXML 中的占位字段名 $F{field_1}, $F{field_2}, ... 替换为 OCR 提取的真实字段名。\n"
"规则:根据列顺序映射——$F{field_1} 对应第1列,$F{field_2} 对应第2列,以此类推。\n"
"同时更新 <field name=\"...\"> 声明和所有 $F{...} 引用。\n"
"只输出完整 JRXML,不要解释。\n\n"
f"OCR 字段:\n{fields_text}\n\n"
f"JRXML\n{prev_jrxml}"
)
full_text = _generate_with_continuation(llm, retry_prompt, writer, "map_fields")
if not full_text.strip():
_node_log.error("map_fields LLM 重试后仍返回空响应,保留占位字段版本")
if not prev_jrxml.strip():
_node_log.warning("map_fields 无输入 JRXML,跳过")
return state
jrxml = _extract_jrxml(full_text)
if len(jrxml.strip()) < 200:
_node_log.warning(f"map_fields 输出过短({len(jrxml)} 字符),回退到前一版本")
jrxml = prev_jrxml
state["current_jrxml"] = jrxml
state["conversation_history"].append({"role": "assistant", "content": jrxml})
# 提取 OCR 字段列表
ocr_result = state.get("ocr_extraction_result", {})
ocr_fields: list[dict] = []
if isinstance(ocr_result, dict) and ocr_result.get("fields"):
ocr_fields = ocr_result["fields"]
if not ocr_fields:
_node_log.info("map_fields 无 OCR 字段,保留占位字段名")
state["conversation_history"].append({"role": "assistant", "content": prev_jrxml})
return state
# 程序化替换(主路径)
result = _programmatic_map_fields(prev_jrxml, ocr_fields)
# 校验
validation = validate_element_count(prev_jrxml, result, "map_fields")
_node_log.info(
"map_fields 程序化完成: %d 个字段, 元素 %d%d (%.1f%%)",
len(ocr_fields),
validation["original"], validation["modified"],
validation["change_pct"] * 100,
)
if not validation["ok"]:
_node_log.error("map_fields 元素丢失过多,回退到前一版本")
return state
state["current_jrxml"] = result
state["conversation_history"].append({"role": "assistant", "content": result})
return state
@@ -810,6 +1040,7 @@ def modify_jrxml(state: AgentState) -> Dict:
conversation_history=conv_text,
modification_request=state.get("user_modification_request", ""),
ocr_context=_format_ocr_context(state),
template_context=_build_template_context(state),
)
prev_jrxml = state.get("current_jrxml", "")
full_text = _generate_with_continuation(llm, prompt, writer, "modify_jrxml")
@@ -1181,6 +1412,7 @@ def correct_jrxml(state: AgentState) -> Dict:
ocr_context=ocr_context,
layout_schema_text=layout_text,
fidelity_context=fidelity_text,
template_context=_build_template_context(state),
)
# 保存修正前状态(供 validate 判断是否写入错误知识库)
state["last_error_case"] = {
+11
View File
@@ -51,3 +51,14 @@ class AgentState(TypedDict, total=False):
# 需求9:分层精确生成
layout_schema: dict # extract_layout_schema() 输出,列+区域结构
ocr_elements: list # OCR 原始行数据(用于阶段二坐标采样)
# 需求10:多租户知识库
kb_id: str # 当前会话绑定的知识库 ID
kb_fields: list # KB 提取的字段定义 [{name, description, type, required}]
kb_field_mapping: dict # OCR 字段 → KB 字段映射 {"工单号": "billNo", ...}
uploaded_template_jrxml: str # 对话中上传的 JRXML 模板原文
uploaded_template_params: list # 解析出的参数 [{name, type}]
kb_template_jrxml: str # 从 KB 检索到的模板 JRXML
kb_template_name: str # 检索到的模板名称
datasource_mode: str # "parameter" 或 "jdbc"
db_config: dict # JDBC 连接配置
+292 -8
View File
@@ -30,7 +30,7 @@ from pathlib import Path
from typing import Optional
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException, UploadFile, File
from fastapi import FastAPI, HTTPException, UploadFile, File, Form, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, FileResponse
@@ -50,6 +50,13 @@ from backend.session import (
)
from backend.file_parser import parse_file
from backend.layout_analyzer import analyze_layout, extract_layout_schema
from backend.kb_manager import (
create_user, list_users, get_user, delete_user,
create_kb, list_kbs, get_kb, update_kb_meta, delete_kb,
get_kb_raw_dir,
)
from backend.kb_parser import parse_jrxml_fields, build_kb_from_files
from backend.kb_searcher import search_kb, search_templates_in_kb
# ─────────────────────────────────────────────
# 常量(从 app.py 迁移)
@@ -97,6 +104,7 @@ SKIP_NODES = {"load_session", "process_input", "manage_context",
_api_log = get_logger("api")
UPLOADS_DIR = Path(os.getenv("UPLOADS_DIR", "./uploads"))
MAX_UPLOAD_SIZE = 50 * 1024 * 1024 # 50 MB
def _check_session_id(session_id: str) -> None:
"""校验 session_id 合法性(防路径穿越),非法时抛出 HTTPException(400)。"""
@@ -380,6 +388,218 @@ async def remove_session(session_id: str):
return {"status": "deleted", "session_id": session_id}
# ─────────────────────────────────────────────
# 用户管理
# ─────────────────────────────────────────────
@app.post("/api/users")
async def create_new_user(payload: dict):
name = payload.get("name", "").strip()
if not name:
raise HTTPException(status_code=400, detail="用户名不能为空")
try:
user = create_user(name)
return user
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/users")
async def list_all_users():
return {"users": list_users()}
@app.get("/api/users/{user_id}")
async def get_user_info(user_id: str):
user = get_user(user_id)
if user is None:
raise HTTPException(status_code=404, detail="用户不存在")
return user
@app.delete("/api/users/{user_id}")
async def remove_user(user_id: str):
ok = delete_user(user_id)
if not ok:
raise HTTPException(status_code=404, detail="用户不存在")
return {"status": "deleted", "user_id": user_id}
# ─────────────────────────────────────────────
# 知识库 CRUD
# ─────────────────────────────────────────────
@app.get("/api/users/{user_id}/kbs")
async def list_user_kbs(user_id: str):
return {"kbs": list_kbs(user_id)}
@app.post("/api/users/{user_id}/kbs")
async def create_user_kb(user_id: str, payload: dict):
name = payload.get("name", "").strip()
description = payload.get("description", "")
if not name:
raise HTTPException(status_code=400, detail="知识库名称不能为空")
try:
kb = create_kb(user_id, name, description)
return kb
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/kbs/{kb_id}")
async def get_kb_info(kb_id: str):
kb = get_kb(kb_id)
if kb is None:
raise HTTPException(status_code=404, detail="知识库不存在")
return kb
@app.delete("/api/kbs/{kb_id}")
async def remove_kb(kb_id: str):
ok = delete_kb(kb_id)
if not ok:
raise HTTPException(status_code=404, detail="知识库不存在")
return {"status": "deleted", "kb_id": kb_id}
# ─────────────────────────────────────────────
# 知识库文件上传
# ─────────────────────────────────────────────
@app.post("/api/kbs/{kb_id}/upload")
async def upload_to_kb(kb_id: str, file: UploadFile = File(...)):
kb = get_kb(kb_id)
if kb is None:
raise HTTPException(status_code=404, detail="知识库不存在")
raw_dir = get_kb_raw_dir(kb_id)
if raw_dir is None:
raise HTTPException(status_code=500, detail="知识库存储目录不可用")
raw_dir.mkdir(parents=True, exist_ok=True)
safe_name = Path(file.filename or "upload").name
dest = raw_dir / safe_name
content = await file.read()
if len(content) > MAX_UPLOAD_SIZE:
raise HTTPException(status_code=413, detail="文件大小超过 50MB 上限")
dest.write_bytes(content)
from backend.kb_parser import process_file_for_kb
result = process_file_for_kb(kb_id, str(dest), source_name=safe_name)
_api_log.info("KB文件上传", extra={
"kb_id": kb_id, "file": safe_name, "type": result.get("type"),
})
return {
"filename": safe_name,
"type": result.get("type", ""),
"error": result.get("error"),
}
@app.post("/api/kbs/{kb_id}/build")
async def build_kb(kb_id: str):
"""构建知识库:对已上传的文件执行 chunk → embed 管线。"""
from backend.kb_parser import build_kb_from_files as build_fn
raw_dir = get_kb_raw_dir(kb_id)
if raw_dir is None or not raw_dir.exists():
raise HTTPException(status_code=404, detail="知识库无已上传文件")
files = [str(p) for p in raw_dir.iterdir() if p.is_file()]
if not files:
raise HTTPException(status_code=400, detail="知识库无文件,请先上传")
result = build_fn(kb_id, files)
return result
@app.get("/api/kbs/{kb_id}/status")
async def kb_status(kb_id: str):
kb = get_kb(kb_id)
if kb is None:
raise HTTPException(status_code=404, detail="知识库不存在")
return {
"kb_id": kb_id,
"name": kb.get("name", ""),
"field_count": len(kb.get("fields", [])),
"template_count": len(kb.get("templates", [])),
"file_count": kb.get("file_count", 0),
"chunk_count": kb.get("chunk_count", 0),
"parse_status": kb.get("parse_status", "empty"),
"created_at": kb.get("created_at", ""),
}
@app.get("/api/kbs/{kb_id}/fields")
async def kb_fields(kb_id: str):
kb = get_kb(kb_id)
if kb is None:
raise HTTPException(status_code=404, detail="知识库不存在")
return {"fields": kb.get("fields", []), "templates": kb.get("templates", [])}
@app.get("/api/kbs/{kb_id}/search")
async def kb_search(kb_id: str, q: str = "", type: str = ""):
if not q:
raise HTTPException(status_code=400, detail="查询参数 q 不能为空")
if type == "template":
results = search_templates_in_kb(kb_id, q, k=5)
else:
ctx = search_kb(kb_id, q, k=5)
return {"query": q, "context": ctx}
return {"query": q, "results": results}
# ─────────────────────────────────────────────
# 会话-知识库绑定
# ─────────────────────────────────────────────
@app.put("/api/sessions/{session_id}/kb")
async def bind_session_kb(session_id: str, payload: dict):
_check_session_id(session_id)
kb_id = payload.get("kb_id", "").strip()
data = load_session(session_id)
if data is None:
raise HTTPException(status_code=404, detail="会话不存在")
agent_state = data.get("agent_state", {})
if kb_id:
kb = get_kb(kb_id)
if kb is None:
raise HTTPException(status_code=404, detail="知识库不存在")
agent_state["kb_id"] = kb_id
agent_state["kb_fields"] = kb.get("fields", [])
else:
agent_state.pop("kb_id", None)
agent_state.pop("kb_fields", None)
save_session(session_id, agent_state)
return {"session_id": session_id, "kb_id": kb_id or None}
@app.get("/api/sessions/{session_id}/kb")
async def get_session_kb(session_id: str):
_check_session_id(session_id)
data = load_session(session_id)
if data is None:
raise HTTPException(status_code=404, detail="会话不存在")
agent_state = data.get("agent_state", {})
kb_id = agent_state.get("kb_id", "")
result = {"kb_id": kb_id, "kb_fields": agent_state.get("kb_fields", [])}
if kb_id:
kb = get_kb(kb_id)
if kb:
result["kb_name"] = kb.get("name", "")
result["templates"] = kb.get("templates", [])
return result
# ─────────────────────────────────────────────
# 文件上传
# ─────────────────────────────────────────────
@@ -396,6 +616,9 @@ async def upload_file(file: UploadFile = File(...), session_id: str = ""):
dest = _ensure_upload_dir(session_id) / f"{file_id}_{safe_name}"
content = await file.read()
if len(content) > MAX_UPLOAD_SIZE:
raise HTTPException(status_code=413, detail="文件大小超过 50MB 上限")
dest.write_bytes(content)
content_type = file.content_type or mimetypes.guess_type(safe_name)[0] or "application/octet-stream"
@@ -423,20 +646,47 @@ async def upload_file(file: UploadFile = File(...), session_id: str = ""):
# 文件处理辅助
# ─────────────────────────────────────────────
def _process_files(file_ids: list[str], session_id: str) -> dict:
"""处理上传的文件:解析 → 布局分析 → 提取 schema 文本
def _parse_jrxml_file(file_path: str) -> dict:
"""解析上传的 JRXML 文件,提取模板参数和字段
Returns:
{full_prompt_prefix, uploaded_paths, layout_schema, ocr_text}
{jrxml_text, parameters: [{name, type}], fields: [{name, type}],
query: str, report_name: str, page_width: str, page_height: str}
"""
jrxml_info = parse_jrxml_fields(file_path)
try:
raw_xml = Path(file_path).read_text(encoding="utf-8")
except Exception:
raw_xml = ""
return {
"jrxml_text": raw_xml,
"parameters": jrxml_info.get("parameters", []),
"fields": jrxml_info.get("fields", []),
"query": jrxml_info.get("query", ""),
"report_name": jrxml_info.get("report_name", ""),
"page_width": jrxml_info.get("page_width", ""),
"page_height": jrxml_info.get("page_height", ""),
"error": jrxml_info.get("error"),
}
def _process_files(file_ids: list[str], session_id: str) -> dict:
"""处理上传的文件:解析 → 布局分析 → 提取 schema 文本。
JRXML 文件额外解析为模板上下文注入 agent_state。
Returns:
{full_prompt_prefix, uploaded_paths, layout_schema, ocr_text,
jrxml_template: dict | None}
"""
if not file_ids:
return {"full_prompt_prefix": "", "uploaded_paths": [],
"layout_schema": {}, "ocr_text": ""}
"layout_schema": {}, "ocr_text": "", "jrxml_template": None}
parts = []
uploaded_paths = []
layout_schema = {}
ocr_text = ""
jrxml_template = None
for fid in file_ids:
info = _file_registry.get(fid)
@@ -446,8 +696,33 @@ def _process_files(file_ids: list[str], session_id: str) -> dict:
file_path = info["path"]
uploaded_paths.append(file_path)
suffix = Path(info["filename"]).suffix.lower()
parsed = parse_file(file_path, Path(info["filename"]).suffix)
# JRXML 文件 → 解析为模板
if suffix == ".jrxml":
jrxml_template = _parse_jrxml_file(file_path)
if jrxml_template.get("error"):
parts.append(f"[JRXML 模板: {info['filename']}]\n解析失败: {jrxml_template['error']}")
else:
params = jrxml_template["parameters"]
fields = jrxml_template["fields"]
param_desc = "\n".join(
f" - {p['name']} ({p.get('type', 'String')})" for p in params
) if params else " (无参数)"
field_desc = "\n".join(
f" - {f['name']} ({f.get('type', 'String')})" for f in fields
) if fields else " (无字段)"
parts.append(
f"[上传的 JRXML 模板: {jrxml_template['report_name'] or info['filename']}]\n"
f"页面尺寸: {jrxml_template['page_width']}x{jrxml_template['page_height']}\n"
f"参数列表:\n{param_desc}\n"
f"字段列表:\n{field_desc}\n"
f"SQL查询: {jrxml_template['query'] or '(无)'}\n"
f"--- XML 内容 ---\n{jrxml_template['jrxml_text']}"
)
continue
parsed = parse_file(file_path, suffix)
if parsed.get("error"):
parts.append(f"[文件: {info['filename']}]\n解析失败: {parsed['error']}")
continue
@@ -490,6 +765,7 @@ def _process_files(file_ids: list[str], session_id: str) -> dict:
"uploaded_paths": uploaded_paths,
"layout_schema": layout_schema,
"ocr_text": ocr_text,
"jrxml_template": jrxml_template,
}
@@ -543,6 +819,12 @@ async def chat(session_id: str, payload: dict):
if file_result.get("uploaded_paths"):
agent_state["uploaded_file_path"] = file_result["uploaded_paths"][0]
# ── 注入 JRXML 模板(对话中上传的模板)──
jrxml_tmpl = file_result.get("jrxml_template")
if jrxml_tmpl and not jrxml_tmpl.get("error"):
agent_state["uploaded_template_jrxml"] = jrxml_tmpl["jrxml_text"]
agent_state["uploaded_template_params"] = jrxml_tmpl["parameters"]
# ── 设置本轮输入 ──
if agent_state.get("current_jrxml"):
agent_state["user_modification_request"] = full_prompt
@@ -591,7 +873,7 @@ async def chat(session_id: str, payload: dict):
# ─────────────────────────────────────────────
@app.get("/api/sessions/{session_id}/download/latest")
async def download_latest(session_id: str):
async def download_latest(session_id: str, background_tasks: BackgroundTasks):
"""下载最新 JRXML 文件。"""
_check_session_id(session_id)
data = load_session(session_id)
@@ -607,6 +889,7 @@ async def download_latest(session_id: str):
encoding="utf-8")
tmp.write(jrxml)
tmp.close()
background_tasks.add_task(os.unlink, tmp.name)
return FileResponse(
tmp.name,
@@ -616,7 +899,7 @@ async def download_latest(session_id: str):
@app.get("/api/sessions/{session_id}/download/{version}")
async def download_version(session_id: str, version: int):
async def download_version(session_id: str, version: int, background_tasks: BackgroundTasks):
"""下载指定版本的 JRXML 文件。"""
_check_session_id(session_id)
data = load_session(session_id)
@@ -635,6 +918,7 @@ async def download_version(session_id: str, version: int):
encoding="utf-8")
tmp.write(jrxml)
tmp.close()
background_tasks.add_task(os.unlink, tmp.name)
return FileResponse(
tmp.name,
+136
View File
@@ -0,0 +1,136 @@
"""OCR 字段 → KB 字段匹配模块。
两阶段匹配:
1. Embedding 粗筛(相似度 top-3
2. LLM 精确确认
返回映射: {"工单号": "billNo", "客户名称": "customerName", ...}
"""
import json
import os
from typing import Optional
from dotenv import load_dotenv
from backend.logger import get_logger
load_dotenv()
_match_log = get_logger("field_matcher")
def _embed(text: str) -> list:
"""获取文本的向量嵌入。"""
from backend.rag_adapter import _get_searcher
searcher = _get_searcher()
if searcher._model is None:
_ = searcher.model
emb = searcher.model.encode(text, normalize_embeddings=True, show_progress_bar=False)
return emb.tolist()
def _cosine_similarity(a: list, b: list) -> float:
"""余弦相似度(假设向量已归一化,点积即相似度)。"""
return sum(x * y for x, y in zip(a, b))
def match_ocr_to_kb(ocr_fields: list[str], kb_fields: list[dict],
llm=None) -> dict[str, str]:
"""将 OCR 提取的字段名匹配到 KB 字段定义。
Args:
ocr_fields: OCR 提取的中文字段名列表
kb_fields: KB 字段定义 [{"name": "billNo", "description": "工单号", ...}]
llm: 可选的 LLM 实例,用于精确确认
Returns:
{"工单号": "billNo", "客户": "customerName", ...}
"""
if not ocr_fields or not kb_fields:
return {}
result = {}
# 阶段 1: Embedding 粗筛
try:
ocr_embs = {f: _embed(f) for f in ocr_fields}
kb_embs = {f["name"]: _embed(f.get("description", f["name"])) for f in kb_fields}
except Exception as e:
_match_log.warning("Embedding 匹配失败,回退到 LLM: %s", e)
return _match_via_llm(ocr_fields, kb_fields, llm)
candidates = {}
for ocr_name, ocr_emb in ocr_embs.items():
scored = []
for kb_name, kb_emb in kb_embs.items():
sim = _cosine_similarity(ocr_emb, kb_emb)
scored.append((kb_name, sim))
scored.sort(key=lambda x: x[1], reverse=True)
candidates[ocr_name] = scored[:3]
# 阶段 2: LLM 精确确认
if llm:
confirmed = _match_via_llm(ocr_fields, kb_fields, llm, candidates)
result.update(confirmed)
else:
for ocr_name, cands in candidates.items():
if cands and cands[0][1] > 0.5:
result[ocr_name] = cands[0][0]
return result
def _match_via_llm(ocr_fields: list[str], kb_fields: list[dict],
llm, candidates: Optional[dict] = None) -> dict[str, str]:
"""使用 LLM 精确确认字段映射。"""
kb_desc = "\n".join(
f"- {f['name']}: {f.get('description', '')} ({f.get('type', 'java.lang.String')})"
for f in kb_fields
)
candidates_hint = ""
if candidates:
cand_lines = []
for ocr_name, cands in candidates.items():
cand_str = ", ".join(f"{n}({s:.2f})" for n, s in cands)
cand_lines.append(f" {ocr_name} -> 候选: {cand_str}")
candidates_hint = (
"向量相似度候选(仅供参考,请根据语义确认):\n"
+ "\n".join(cand_lines)
)
prompt = (
"请将以下 OCR 识别的字段名匹配到知识库定义的字段。\n\n"
f"OCR 字段: {json.dumps(ocr_fields, ensure_ascii=False)}\n\n"
f"知识库字段:\n{kb_desc}\n\n"
f"{candidates_hint}\n\n"
"请以 JSON 对象格式输出映射关系,键为 OCR 字段名,值为 KB 字段名:\n"
'{"工单号": "billNo", "客户名称": "customerName"}'
)
try:
response = llm.invoke(prompt)
content = response.content if hasattr(response, "content") else str(response)
start = content.find("{")
end = content.rfind("}") + 1
if start >= 0 and end > start:
return json.loads(content[start:end])
except Exception as e:
_match_log.warning("LLM 字段匹配失败: %s", e)
return {}
def format_field_mapping_context(mapping: dict[str, str]) -> str:
"""将字段映射格式化为 prompt 上下文字符串。"""
if not mapping:
return ""
lines = ["[字段映射 — OCR -> KB]",
"请在 JRXML 中使用以下参数名:",
"| OCR 字段 | JRXML 参数 |",
"|---|---|"]
for ocr_name, kb_name in mapping.items():
lines.append(f"| {ocr_name} | $P{{{kb_name}}} |")
return "\n".join(lines)
+227
View File
@@ -0,0 +1,227 @@
"""多租户知识库管理模块。
用户 + 知识库 CRUD,持久化到 kb_data/ 目录。
每个 KB 拥有独立的 JSON 元数据文件和文件存储目录。
"""
import json
import os
import re
import uuid
import tempfile
import shutil
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
from dotenv import load_dotenv
from backend.logger import get_logger
load_dotenv()
_kb_log = get_logger("kb_manager")
KB_DATA_DIR = Path(os.getenv("KB_DATA_DIR", "./kb_data"))
_USERS_FILE = KB_DATA_DIR / "users.json"
_VALID_ID_RE = re.compile(r'^[a-fA-F0-9]{12,}$')
def _validate_id(id_str: str, label: str = "id") -> None:
if not _VALID_ID_RE.match(id_str):
raise ValueError(f"Invalid {label}: {id_str!r}")
def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
def _ensure_dir(path: Path) -> None:
path.mkdir(parents=True, exist_ok=True)
def _read_json(fp: Path) -> dict:
with open(fp, "r", encoding="utf-8") as f:
return json.load(f)
def _write_json_atomic(fp: Path, data: dict) -> None:
_ensure_dir(fp.parent)
tmp = tempfile.NamedTemporaryFile(
mode="w", suffix=".json", delete=False,
dir=fp.parent, encoding="utf-8",
)
try:
json.dump(data, tmp, ensure_ascii=False, indent=2)
tmp.flush()
os.fsync(tmp.fileno())
tmp.close()
os.replace(tmp.name, str(fp))
except Exception:
tmp.close()
Path(tmp.name).unlink(missing_ok=True)
raise
# ── User CRUD ──────────────────────────────────────────────────────────────
def _load_users() -> list[dict]:
_ensure_dir(KB_DATA_DIR)
if _USERS_FILE.exists():
return _read_json(_USERS_FILE)
return []
def _save_users(users: list[dict]) -> None:
_write_json_atomic(_USERS_FILE, users)
def create_user(name: str, user_id: Optional[str] = None) -> dict:
uid = user_id or uuid.uuid4().hex
users = _load_users()
if any(u["user_id"] == uid for u in users):
raise ValueError(f"User {uid} already exists")
user = {"user_id": uid, "name": name, "created_at": _now_iso()}
users.append(user)
_save_users(users)
_ensure_dir(KB_DATA_DIR / uid)
_write_json_atomic(KB_DATA_DIR / uid / "profile.json", user)
_kb_log.info("创建用户", extra={"user_id": uid, "user_name": name})
return user
def list_users() -> list[dict]:
return _load_users()
def get_user(user_id: str) -> Optional[dict]:
_validate_id(user_id, "user_id")
for u in _load_users():
if u["user_id"] == user_id:
return u
return None
def delete_user(user_id: str) -> bool:
_validate_id(user_id, "user_id")
users = _load_users()
filtered = [u for u in users if u["user_id"] != user_id]
if len(filtered) == len(users):
return False
_save_users(filtered)
user_dir = KB_DATA_DIR / user_id
if user_dir.exists():
shutil.rmtree(user_dir)
_kb_log.info("删除用户", extra={"user_id": user_id})
return True
# ── KB CRUD ────────────────────────────────────────────────────────────────
def _kb_dir(kb_id: str) -> Optional[Path]:
_validate_id(kb_id, "kb_id")
for user_dir in KB_DATA_DIR.iterdir():
if user_dir.is_dir() and not user_dir.name.startswith("."):
candidate = user_dir / kb_id
if candidate.is_dir():
return candidate
return None
def _ensure_user_dir(user_id: str) -> Path:
_validate_id(user_id, "user_id")
d = KB_DATA_DIR / user_id
_ensure_dir(d)
return d
def create_kb(user_id: str, name: str, description: str = "",
kb_id: Optional[str] = None) -> dict:
user_dir = _ensure_user_dir(user_id)
kid = kb_id or uuid.uuid4().hex
kb_dir = user_dir / kid
_ensure_dir(kb_dir)
_ensure_dir(kb_dir / "raw")
now = _now_iso()
meta = {
"kb_id": kid, "user_id": user_id, "name": name,
"description": description, "created_at": now, "updated_at": now,
"fields": [], "templates": [], "file_count": 0,
"chunk_count": 0, "parse_status": "empty",
}
_write_json_atomic(kb_dir / "meta.json", meta)
_kb_log.info("创建知识库", extra={"kb_id": kid, "user_id": user_id, "kb_name": name})
return meta
def list_kbs(user_id: str) -> list[dict]:
user_dir = _ensure_user_dir(user_id)
kbs = []
for kb_dir in sorted(user_dir.iterdir(), key=os.path.getmtime, reverse=True):
if kb_dir.is_dir() and not kb_dir.name.startswith("."):
meta_path = kb_dir / "meta.json"
if meta_path.exists():
meta = _read_json(meta_path)
kbs.append({
"kb_id": meta.get("kb_id", kb_dir.name),
"name": meta.get("name", kb_dir.name),
"description": meta.get("description", ""),
"created_at": meta.get("created_at", ""),
"updated_at": meta.get("updated_at", ""),
"field_count": len(meta.get("fields", [])),
"template_count": len(meta.get("templates", [])),
"file_count": meta.get("file_count", 0),
"chunk_count": meta.get("chunk_count", 0),
"parse_status": meta.get("parse_status", "empty"),
})
return kbs
def get_kb(kb_id: str) -> Optional[dict]:
_validate_id(kb_id, "kb_id")
kb_dir = _kb_dir(kb_id)
if kb_dir is None:
return None
meta_path = kb_dir / "meta.json"
return _read_json(meta_path) if meta_path.exists() else None
def update_kb_meta(kb_id: str, updates: dict) -> Optional[dict]:
kb_dir = _kb_dir(kb_id)
if kb_dir is None:
return None
meta_path = kb_dir / "meta.json"
meta = _read_json(meta_path)
meta.update(updates)
meta["updated_at"] = _now_iso()
_write_json_atomic(meta_path, meta)
return meta
def delete_kb(kb_id: str) -> bool:
kb_dir = _kb_dir(kb_id)
if kb_dir is None:
return False
shutil.rmtree(kb_dir)
_kb_log.info("删除知识库", extra={"kb_id": kb_id})
return True
def get_kb_raw_dir(kb_id: str) -> Optional[Path]:
kb_dir = _kb_dir(kb_id)
return kb_dir / "raw" if kb_dir else None
def get_kb_chunks_path(kb_id: str) -> Optional[Path]:
kb_dir = _kb_dir(kb_id)
return kb_dir / "chunks.json" if kb_dir else None
def get_kb_chroma_path(kb_id: str) -> Optional[Path]:
kb_dir = _kb_dir(kb_id)
if kb_dir is None:
return None
chroma_dir = kb_dir / "chroma"
_ensure_dir(chroma_dir)
return chroma_dir
+336
View File
@@ -0,0 +1,336 @@
"""KB 解析管道 — 文件提取→字段解析→chunk 切割→向量嵌入。
调用者: api_server.py (upload endpoint), scripts/init_default_kb.py
"""
import os
import json
import shutil
import zipfile
import tarfile
import tempfile
import defusedxml.ElementTree as ET
from pathlib import Path
from typing import Optional
from dotenv import load_dotenv
from backend.logger import get_logger
from backend.file_parser import parse_file
load_dotenv()
_kb_parse_log = get_logger("kb_parser")
def _find_tag(elem, tag):
for el in elem.iter():
local = el.tag.split("}")[-1] if "}" in el.tag else el.tag
if local == tag:
return el
return None
def _find_all_tags(elem, tag):
results = []
for el in elem.iter():
local = el.tag.split("}")[-1] if "}" in el.tag else el.tag
if local == tag:
results.append(el)
return results
def parse_jrxml_fields(jrxml_path: str) -> dict:
"""解析 JRXML 文件,提取参数和字段定义。"""
try:
tree = ET.parse(jrxml_path)
root = tree.getroot()
except ET.ParseError as e:
return {"error": f"JRXML 解析失败: {e}", "parameters": [], "fields": [],
"report_name": ""}
report_name = root.attrib.get("name", "")
page_width = root.attrib.get("pageWidth", "")
page_height = root.attrib.get("pageHeight", "")
parameters = []
for p in _find_all_tags(root, "parameter"):
params = {"name": p.attrib.get("name", ""),
"type": p.attrib.get("class", "java.lang.String"),
"description": ""}
desc = _find_tag(p, "parameterDescription")
if desc is not None and desc.text:
params["description"] = desc.text.strip()
parameters.append(params)
fields = []
for f in _find_all_tags(root, "field"):
fields.append({"name": f.attrib.get("name", ""),
"type": f.attrib.get("class", "java.lang.String"),
"description": ""})
query_text = ""
query = _find_tag(root, "queryString")
if query is not None and query.text:
query_text = query.text.strip()
return {"report_name": report_name, "page_width": page_width,
"page_height": page_height, "parameters": parameters,
"fields": fields, "query": query_text, "error": None}
def _extract_archive(file_path: str, dest_dir: str) -> list[str]:
extracted = []
resolved_dest = os.path.realpath(dest_dir)
if zipfile.is_zipfile(file_path):
with zipfile.ZipFile(file_path, "r") as zf:
for member in zf.namelist():
member_path = os.path.realpath(os.path.join(dest_dir, member))
if not member_path.startswith(resolved_dest + os.sep):
continue
zf.extract(member, dest_dir)
if not member.endswith("/"):
extracted.append(member_path)
elif tarfile.is_tarfile(file_path):
with tarfile.open(file_path, "r:*") as tf:
for member in tf.getmembers():
member_path = os.path.realpath(os.path.join(dest_dir, member.name))
if not member_path.startswith(resolved_dest + os.sep):
continue
tf.extract(member, dest_dir)
if not member.name.endswith("/"):
extracted.append(member_path)
return extracted
def process_file_for_kb(kb_id: str, file_path: str,
source_name: str = "") -> dict:
from backend.kb_manager import get_kb_raw_dir
raw_dir = get_kb_raw_dir(kb_id)
if raw_dir is None:
return {"error": "KB 不存在"}
fname = source_name or os.path.basename(file_path)
dest = raw_dir / fname
shutil.copy2(file_path, dest)
suffix = Path(fname).suffix.lower()
if suffix == ".jrxml":
jrxml_info = parse_jrxml_fields(file_path)
text = f"[JRXML 模板: {jrxml_info['report_name']}]\n"
text += f"页面: {jrxml_info['page_width']}x{jrxml_info['page_height']}\n"
text += "参数:\n" + "\n".join(
f" {p['name']} ({p['type']})" for p in jrxml_info["parameters"])
text += "\n字段:\n" + "\n".join(
f" {f['name']} ({f['type']})" for f in jrxml_info["fields"])
if jrxml_info["query"]:
text += f"\n查询:\n{jrxml_info['query']}"
try:
raw_xml = Path(file_path).read_text(encoding="utf-8")
except Exception:
raw_xml = ""
return {"filename": fname, "type": "jrxml", "text": text,
"raw_xml": raw_xml, "jrxml_info": jrxml_info, "error": None}
if suffix in (".zip", ".tar", ".gz", ".tgz"):
tmpdir = tempfile.mkdtemp(prefix="kb_extract_")
try:
extracted = _extract_archive(file_path, tmpdir)
sub_results = []
for ep in extracted:
sub = process_file_for_kb(
kb_id, ep, source_name=os.path.basename(ep))
sub_results.append(sub)
return {"filename": fname, "type": "archive", "text": "",
"archive_contents": sub_results, "error": None}
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
parse_result = parse_file(str(dest))
return {"filename": fname, "type": suffix.lstrip("."),
"text": parse_result.get("text", ""),
"error": parse_result.get("error")}
def chunk_file_results(results: list[dict], kb_name: str = "") -> list[dict]:
chunks = []
chunk_idx = 0
for r in results:
if r.get("type") == "archive":
for sub in r.get("archive_contents", []):
chunks.extend(chunk_file_results([sub], kb_name))
continue
fname = r.get("filename", "")
ftype = r.get("type", "")
text = r.get("text", "")
if not text.strip():
continue
if ftype == "jrxml" and r.get("raw_xml"):
jinfo = r.get("jrxml_info", {})
report_name = jinfo.get("report_name", "")
chunks.append({
"id": f"chunk_{chunk_idx}",
"content": f"[JRXML 模板: {report_name}]\n{r['text']}\n\n"
f"<xml>\n{r['raw_xml']}\n</xml>",
"metadata": {"chunk_type": "jrxml_template",
"source_file": fname,
"report_name": report_name,
"kb_name": kb_name,
"param_count": len(jinfo.get("parameters", [])),
"field_count": len(jinfo.get("fields", []))},
})
chunk_idx += 1
continue
paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
for para in paragraphs:
if len(para) < 10:
continue
chunk_type = "md_section" if ftype in ("md", "") else f"{ftype}_text"
chunks.append({
"id": f"chunk_{chunk_idx}",
"content": para,
"metadata": {"chunk_type": chunk_type,
"source_file": fname, "kb_name": kb_name},
})
chunk_idx += 1
return chunks
def extract_fields_with_llm(text: str, llm=None) -> list[dict]:
if llm is None:
return _extract_fields_from_table(text)
prompt = (
"请分析以下接口文档内容,提取所有字段定义。\n"
"对每个字段,输出: 字段名, 含义, 类型, 是否必需。\n"
"以 JSON 数组格式输出,每个元素为 {\"name\": \"...\", "
"\"description\": \"...\", \"type\": \"...\", \"required\": false}。\n\n"
f"{text}"
)
try:
response = llm.invoke(prompt)
content = response.content if hasattr(response, "content") else str(response)
start = content.find("[")
end = content.rfind("]") + 1
if start >= 0 and end > start:
return json.loads(content[start:end])
except Exception as e:
_kb_parse_log.warning("LLM 字段提取失败,使用表格回退: %s", e)
return _extract_fields_from_table(text)
def _extract_fields_from_table(text: str) -> list[dict]:
fields = []
lines = text.split("\n")
header_found = False
for line in lines:
line = line.strip()
if not line.startswith("|"):
continue
cells = [c.strip() for c in line.split("|")[1:-1]]
if not cells:
continue
if not header_found:
if any(h in str(c) for c in cells
for h in ["字段", "名称", "含义", "说明", "类型"]):
header_found = True
continue
if all(c.replace("-", "").replace(":", "").replace(" ", "") == ""
for c in cells):
continue
if len(cells) >= 2:
name = cells[0].replace("**", "").replace("L ", "").replace("\\", "").strip()
if not name or name in ("", "---"):
continue
field = {"name": name, "description": "", "type": "java.lang.String",
"required": False}
if len(cells) >= 2 and cells[1]:
field["description"] = cells[1].replace("<br/>", " ").strip()
if len(cells) >= 3 and cells[2]:
field["required"] = cells[2].strip() in ("", "Y", "y", "yes", "Yes", "必填")
if len(cells) >= 4 and cells[3]:
field["type"] = cells[3].strip()
fields.append(field)
return fields
def build_kb_from_files(kb_id: str, file_paths: list[str],
llm=None) -> dict:
from backend.kb_manager import update_kb_meta, get_kb_chunks_path
from backend.kb_searcher import get_kb_searcher
all_results = []
errors = []
for fp in file_paths:
try:
r = process_file_for_kb(kb_id, fp)
all_results.append(r)
if r.get("error"):
errors.append({"file": os.path.basename(fp), "error": r["error"]})
except Exception as e:
errors.append({"file": os.path.basename(fp), "error": str(e)})
chunks = chunk_file_results(all_results)
chunks_path = get_kb_chunks_path(kb_id)
if chunks_path:
chunks_path.parent.mkdir(parents=True, exist_ok=True)
with open(chunks_path, "w", encoding="utf-8") as f:
json.dump(chunks, f, ensure_ascii=False, indent=2)
searcher = get_kb_searcher(kb_id)
if searcher and chunks:
try:
searcher.add_chunks(chunks)
except Exception as e:
errors.append({"file": "embedding", "error": str(e)})
all_fields = []
template_names = []
for r in all_results:
_collect_from_result(r, all_fields, template_names)
for r in all_results:
if r.get("type") in ("archive", "jrxml"):
continue
text = r.get("text", "")
if text.strip():
for ef in extract_fields_with_llm(text, llm):
if not any(f["name"] == ef["name"] for f in all_fields):
all_fields.append(ef)
update_kb_meta(kb_id, {
"fields": all_fields, "templates": template_names,
"file_count": len(file_paths), "chunk_count": len(chunks),
"parse_status": "ready" if not errors else "partial",
})
_kb_parse_log.info("KB 构建完成", extra={
"kb_id": kb_id, "fields": len(all_fields),
"templates": len(template_names), "chunks": len(chunks),
})
return {"status": "ready" if not errors else "partial",
"field_count": len(all_fields), "chunk_count": len(chunks),
"template_count": len(template_names), "errors": errors}
def _collect_from_result(r: dict, all_fields: list, template_names: list) -> None:
jinfo = r.get("jrxml_info")
if jinfo and jinfo.get("report_name"):
template_names.append({"name": jinfo["report_name"],
"file": r.get("filename", "")})
for p in jinfo.get("parameters", []):
field = {"name": p["name"], "description": p.get("description", ""),
"type": p.get("type", "java.lang.String"), "required": False}
if not any(f["name"] == field["name"] for f in all_fields):
all_fields.append(field)
for f in jinfo.get("fields", []):
field = {"name": f["name"], "description": f.get("description", ""),
"type": f.get("type", "java.lang.String"), "required": False}
if not any(fi["name"] == field["name"] for fi in all_fields):
all_fields.append(field)
+170
View File
@@ -0,0 +1,170 @@
"""KB 隔离的 ChromaDB 语义搜索适配器。
每个知识库拥有独立的 ChromaDB collection。
调用者: backend/rag_adapter.py, agent/nodes.py, api_server.py
"""
import os
import logging
from pathlib import Path
from typing import Optional
from dotenv import load_dotenv
load_dotenv()
logger = logging.getLogger(__name__)
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
def _resolve(path: str) -> Path:
p = Path(path)
return p if p.is_absolute() else _PROJECT_ROOT / p
class KBChromaSearcher:
"""连接指定 KB 的 ChromaDB,提供语义搜索。"""
def __init__(self, chroma_path: str, collection_name: str = "kb_chunks",
model_name: Optional[str] = None, use_gpu: Optional[bool] = None,
use_fp16: Optional[bool] = None):
self.chroma_path = str(_resolve(chroma_path))
self.collection_name = collection_name
model_path = model_name or os.getenv(
"RAG_EMBED_MODEL", "./rag/models/paraphrase-multilingual-MiniLM-L12-v2")
resolved = _resolve(model_path)
self.model_name = str(resolved) if resolved.exists() else model_path
self.use_gpu = (use_gpu if use_gpu is not None
else os.getenv("RAG_USE_GPU", "true").lower() in ("true", "1"))
self.use_fp16 = (use_fp16 if use_fp16 is not None
else os.getenv("RAG_USE_FP16", "true").lower() in ("true", "1"))
self._model = None
self._client = None
self._collection = None
@property
def model(self):
if self._model is None:
import torch
from sentence_transformers import SentenceTransformer
device = "cuda" if (self.use_gpu and torch.cuda.is_available()) else "cpu"
logger.info("加载嵌入模型: %s (device=%s)", self.model_name, device)
model = SentenceTransformer(self.model_name, device=device)
if device == "cuda" and self.use_fp16:
model = model.half()
self._model = model
return self._model
@property
def client(self):
if self._client is None:
import chromadb
self._client = chromadb.PersistentClient(path=self.chroma_path)
return self._client
@property
def collection(self):
if self._collection is None:
try:
self._collection = self.client.get_collection(self.collection_name)
except Exception:
self._collection = self.client.create_collection(
self.collection_name, metadata={"hnsw:space": "cosine"})
return self._collection
def is_ready(self) -> bool:
try:
self.client.get_collection(self.collection_name)
return True
except Exception:
return False
def search(self, query: str, k: int = 5, threshold: Optional[float] = None) -> list[dict]:
if not self.is_ready():
return []
query_embedding = self.model.encode(
query, normalize_embeddings=True, show_progress_bar=False)
results = self.collection.query(
query_embeddings=[query_embedding.tolist()],
n_results=k, include=["documents", "metadatas", "distances"])
output = []
if not results["ids"] or not results["ids"][0]:
return output
for i, doc_id in enumerate(results["ids"][0]):
dist = results["distances"][0][i]
if threshold is not None and dist > threshold:
continue
output.append({
"id": doc_id,
"content": results["documents"][0][i],
"metadata": results["metadatas"][0][i] or {},
"distance": dist,
})
return output
def search_templates(self, query: str, k: int = 3) -> list[dict]:
results = self.search(query, k=k * 2)
templates = []
for r in results:
meta = r.get("metadata", {})
chunk_type = meta.get("chunk_type", "")
if "jrxml" in chunk_type.lower() or meta.get("report_name"):
templates.append(r)
if len(templates) >= k:
break
return templates
def search_as_context(self, query: str, k: int = 5) -> str:
results = self.search(query, k=k)
if not results:
return ""
parts = []
for r in results:
meta = r.get("metadata", {})
header = f"[类型:{meta.get('chunk_type', 'N/A')}]"
if meta.get("report_name"):
header += f" [报表:{meta['report_name']}]"
parts.append(f"{header}\n{r['content']}")
return "\n\n---\n\n".join(parts)
def add_chunks(self, chunks: list[dict]) -> None:
if not chunks:
return
ids = [c["id"] for c in chunks]
docs = [c["content"] for c in chunks]
metas = [c.get("metadata", {}) for c in chunks]
embeddings = self.model.encode(
docs, normalize_embeddings=True, show_progress_bar=True)
self.collection.upsert(
ids=ids, documents=docs, metadatas=metas,
embeddings=embeddings.tolist())
_searchers: dict = {}
def get_kb_searcher(kb_id: str) -> Optional[KBChromaSearcher]:
from backend.kb_manager import get_kb_chroma_path
if kb_id in _searchers:
return _searchers[kb_id]
chroma_path = get_kb_chroma_path(kb_id)
if chroma_path is None:
return None
searcher = KBChromaSearcher(str(chroma_path))
_searchers[kb_id] = searcher
return searcher
def search_kb(kb_id: str, query: str, k: int = 5) -> str:
searcher = get_kb_searcher(kb_id)
if searcher is None:
return ""
return searcher.search_as_context(query, k=k)
def search_templates_in_kb(kb_id: str, query: str, k: int = 3) -> list[dict]:
searcher = get_kb_searcher(kb_id)
if searcher is None:
return []
return searcher.search_templates(query, k=k)
+8 -2
View File
@@ -150,6 +150,12 @@ def _get_searcher() -> RAGSearcher:
return _searcher
def search_chunks(query: str, k: int = 5) -> str:
"""搜索 JRXML 知识库并返回拼接后的上下文文本(便捷函数)。"""
def search_chunks(query: str, k: int = 5, kb_id: str = "") -> str:
"""搜索知识库并返回拼接后的上下文文本
若指定 kb_id,使用该 KB 专属 ChromaDB;否则使用全局默认库。
"""
if kb_id:
from backend.kb_searcher import search_kb
return search_kb(kb_id, query, k=k)
return _get_searcher().search_as_context(query, k=k)
+1
View File
@@ -56,6 +56,7 @@ def create_session(name: str = "", agent_state: Optional[dict] = None,
"session_name": name or f"新建报表 {now[:10]}",
"created_at": now,
"updated_at": now,
"kb_id": agent_state.get("kb_id", "") if agent_state else "",
"agent_state": agent_state,
}
with open(_session_path(sid), "w", encoding="utf-8") as f:
+50 -3
View File
@@ -1,5 +1,52 @@
# Vue 3 + TypeScript + Vite
# JRXML Agent 前端
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Vue 3 + TypeScript + Vite + Pinia — JRXML 报表生成代理的 Web UI。
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
## 技术栈
- **Vue 3** (Composition API + `<script setup>`)
- **TypeScript** 6.x
- **Vite** 8.x
- **Pinia** 3.x (状态管理)
- **SSE** (Server-Sent Events) 流式响应
## 组件结构
```
src/
├── api/client.ts SSE 客户端 + fetch 封装
├── stores/
│ ├── chat.ts Pinia: 消息/流式/节点进度/文件
│ ├── session.ts Pinia: 会话 CRUD
│ └── kb.ts Pinia: 多租户知识库管理
├── components/
│ ├── Sidebar.vue 会话列表 + 下载 + 历史版本
│ ├── ChatMessages.vue 消息列表渲染
│ ├── ProcessSection.vue 处理过程折叠区
│ ├── UnifiedInput.vue 统一输入框(文本+文件拖拽/粘贴)
│ ├── SummaryCard.vue 结果摘要卡片
│ ├── KbSelector.vue KB 下拉选择器
│ └── KbManager.vue KB 管理面板(创建/上传/构建/删除)
└── utils/format.ts 工具函数
```
## 开发
```bash
npm install
npm run dev # 启动开发服务器 (localhost:5173)
npm run build # 生产构建
npx playwright test # E2E 测试
```
## SSE 事件流
前端通过 `api.chat()` 发起 POST 请求,后端返回 `text/event-stream`
| 事件 | 说明 |
|------|------|
| `node_start` | 节点开始执行(含 node/label/step_index |
| `node_complete` | 节点执行完成(含 detail) |
| `stream_token` | LLM 逐字输出 |
| `agent_complete` | 全图执行完成(含 intent/status/jrxml_length/error 等) |
| `agent_error` | 执行异常 |
+13
View File
@@ -8,9 +8,19 @@ import ChatMessages from './components/ChatMessages.vue'
import ProcessSection from './components/ProcessSection.vue'
import SummaryCard from './components/SummaryCard.vue'
import UnifiedInput from './components/UnifiedInput.vue'
import KbSelector from './components/KbSelector.vue'
import KbManager from './components/KbManager.vue'
import { useKbStore } from './stores/kb'
const chat = useChatStore()
const session = useSessionStore()
const kb = useKbStore()
function handleKbChange(kbId: string) {
if (session.currentId) {
kb.bindKbToSession(session.currentId, kbId)
}
}
const chatContainer = ref<HTMLElement | null>(null)
@@ -128,6 +138,7 @@ async function handleSend(text: string, files: File[]) {
<Sidebar @quickAction="(text) => handleSend(text, [])" />
<main class="main-area">
<KbSelector @change="handleKbChange" />
<div class="chat-container" ref="chatContainer">
<ChatMessages />
<ProcessSection />
@@ -139,6 +150,8 @@ async function handleSend(text: string, files: File[]) {
@send="handleSend"
/>
</main>
<KbManager />
</div>
</template>
+160
View File
@@ -0,0 +1,160 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useKbStore } from '../stores/kb'
const kb = useKbStore()
const newKbName = ref('')
const newKbDesc = ref('')
const creating = ref(false)
const uploading = ref<string | null>(null)
const building = ref<string | null>(null)
const fileInput = ref<HTMLInputElement | null>(null)
onMounted(() => { kb.init() })
async function handleCreate() {
if (!newKbName.value.trim()) return
creating.value = true
await kb.createKb(newKbName.value.trim(), newKbDesc.value.trim())
newKbName.value = ''
newKbDesc.value = ''
creating.value = false
}
async function handleDelete(kbId: string) {
if (confirm('确定删除此知识库?所有文件和数据将被永久删除。')) {
await kb.deleteKb(kbId)
}
}
function triggerUpload(kbId: string) {
uploading.value = kbId
fileInput.value?.click()
}
async function handleFileSelect(e: Event, kbId: string) {
const input = e.target as HTMLInputElement
if (input.files && input.files.length > 0) {
for (const f of input.files) {
await kb.uploadFileToKb(kbId, f)
}
await kb.buildKb(kbId)
await kb.refreshKbs()
}
input.value = ''
uploading.value = null
}
async function handleBuild(kbId: string) {
building.value = kbId
await kb.buildKb(kbId)
building.value = null
}
</script>
<template>
<Teleport to="body">
<div v-if="kb.showManager" class="kb-manager-overlay" @click.self="kb.showManager = false">
<div class="kb-manager">
<h3>知识库管理</h3>
<div class="create-form">
<input v-model="newKbName" class="kb-input" placeholder="知识库名称" :disabled="creating" />
<input v-model="newKbDesc" class="kb-input" placeholder="描述(可选)" :disabled="creating" />
<button class="kb-btn primary" :disabled="creating || !newKbName.trim()" @click="handleCreate">
{{ creating ? '创建中...' : '创建' }}
</button>
</div>
<div v-if="kb.loading" class="empty">加载中...</div>
<div v-else-if="kb.kbs.length === 0" class="empty">暂无知识库</div>
<div v-for="k in kb.kbs" :key="k.kb_id" class="kb-card">
<div class="kb-card-header">
<strong>{{ k.name }}</strong>
<span class="kb-status" :class="k.parse_status">
{{ k.parse_status === 'ready' ? '就绪' : k.parse_status === 'partial' ? '部分' : '空' }}
</span>
</div>
<div class="kb-meta">
{{ k.field_count }}字段 &middot; {{ k.template_count }}模板 &middot;
{{ k.file_count }}文件 &middot; {{ k.chunk_count }}
</div>
<div class="kb-actions">
<button class="kb-btn" @click="triggerUpload(k.kb_id)" :disabled="uploading === k.kb_id">
{{ uploading === k.kb_id ? '上传中...' : '上传文件' }}
</button>
<button class="kb-btn" @click="handleBuild(k.kb_id)" :disabled="building === k.kb_id || k.file_count === 0">
{{ building === k.kb_id ? '构建中...' : '构建' }}
</button>
<button class="kb-btn danger" @click="handleDelete(k.kb_id)">删除</button>
</div>
</div>
<button class="kb-btn close-btn" @click="kb.showManager = false">关闭</button>
<input ref="fileInput" type="file" multiple
accept=".jrxml,.md,.xlsx,.xls,.docx,.doc,.pdf,.csv,.txt,.json,.zip,.tar,.gz"
style="display:none"
@change="(e: Event) => { const kbId = uploading; if (kbId) handleFileSelect(e, kbId); }" />
</div>
</div>
</Teleport>
</template>
<style scoped>
.kb-manager-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.6);
z-index: 100;
display: flex; align-items: center; justify-content: center;
}
.kb-manager {
background: #1e1e2e;
border: 1px solid #45475a;
border-radius: 12px;
padding: 24px;
width: 540px;
max-height: 80vh;
overflow-y: auto;
color: #cdd6f4;
}
h3 { margin: 0 0 16px; font-size: 18px; }
.create-form { display: flex; gap: 8px; margin-bottom: 16px; }
.kb-input {
flex: 1;
background: #181825;
border: 1px solid #45475a;
border-radius: 6px;
color: #cdd6f4;
padding: 6px 10px;
font-size: 13px;
outline: none;
}
.kb-input:focus { border-color: #cba6f7; }
.kb-btn {
background: #313244; border: none; border-radius: 6px;
color: #cdd6f4; padding: 6px 12px; font-size: 12px;
cursor: pointer; white-space: nowrap;
}
.kb-btn:hover:not(:disabled) { background: #45475a; }
.kb-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.kb-btn.primary { background: #cba6f7; color: #1e1e2e; }
.kb-btn.primary:hover:not(:disabled) { background: #b4befe; }
.kb-btn.danger { color: #f38ba8; }
.kb-btn.danger:hover:not(:disabled) { background: #f38ba8; color: #1e1e2e; }
.empty { text-align: center; color: #6c7086; padding: 24px 0; }
.kb-card {
background: #181825; border: 1px solid #313244;
border-radius: 8px; padding: 12px; margin-bottom: 8px;
}
.kb-card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; }
.kb-status { font-size: 11px; padding: 1px 6px; border-radius: 4px; }
.kb-status.ready { background: #a6e3a1; color: #1e1e2e; }
.kb-status.partial { background: #fab387; color: #1e1e2e; }
.kb-status.empty { background: #45475a; color: #a6adc8; }
.kb-meta { font-size: 11px; color: #6c7086; margin-bottom: 8px; }
.kb-actions { display: flex; gap: 6px; }
.close-btn { display: block; margin: 16px auto 0; }
</style>
+108
View File
@@ -0,0 +1,108 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { useKbStore } from '../stores/kb'
const kb = useKbStore()
const emit = defineEmits<{
change: [kbId: string]
}>()
function handleChange(e: Event) {
const kbId = (e.target as HTMLSelectElement).value
kb.selectKb(kbId)
if (kbId) {
kb.fetchKbFields(kbId)
}
emit('change', kbId)
}
onMounted(() => {
kb.init()
})
</script>
<template>
<div class="kb-selector">
<label class="kb-label">知识库</label>
<select
class="kb-select"
:value="kb.currentKbId"
@change="handleChange"
>
<option value="">-- 不使用知识库 --</option>
<option
v-for="k in kb.kbs"
:key="k.kb_id"
:value="k.kb_id"
>
{{ k.name }} ({{ k.field_count }}字段, {{ k.template_count }}模板)
</option>
</select>
<button class="kb-manage-btn" @click="kb.showManager = !kb.showManager" title="管理知识库">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/>
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
</button>
<span v-if="kb.currentKbName" class="kb-badge">当前: {{ kb.currentKbName }}</span>
</div>
</template>
<style scoped>
.kb-selector {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: #181825;
border-bottom: 1px solid #313244;
}
.kb-label {
font-size: 12px;
color: #6c7086;
white-space: nowrap;
}
.kb-select {
flex: 1;
background: #1e1e2e;
border: 1px solid #45475a;
border-radius: 6px;
color: #cdd6f4;
padding: 4px 8px;
font-size: 13px;
outline: none;
cursor: pointer;
}
.kb-select:focus {
border-color: #cba6f7;
}
.kb-manage-btn {
background: #313244;
border: none;
color: #a6adc8;
border-radius: 6px;
padding: 4px 6px;
cursor: pointer;
display: flex;
align-items: center;
}
.kb-manage-btn:hover {
background: #45475a;
color: #cdd6f4;
}
.kb-badge {
font-size: 11px;
color: #a6e3a1;
background: #1e1e2e;
padding: 2px 8px;
border-radius: 10px;
white-space: nowrap;
}
</style>
+1 -1
View File
@@ -140,7 +140,7 @@ function handlePaste(e: ClipboardEvent) {
ref="fileInputRef"
type="file"
multiple
accept="image/*,.pdf,.docx,.doc,.xlsx,.xls,.txt,.csv"
accept="image/*,.pdf,.docx,.doc,.xlsx,.xls,.txt,.csv,.jrxml"
style="display:none"
@change="handleFileSelect"
/>
+152
View File
@@ -0,0 +1,152 @@
/** Pinia store — multi-tenant KB management. */
import { ref } from 'vue'
import { defineStore } from 'pinia'
export interface KbSummary {
kb_id: string
name: string
description: string
created_at: string
updated_at: string
field_count: number
template_count: number
file_count: number
chunk_count: number
parse_status: string
}
export interface UserInfo {
user_id: string
name: string
created_at: string
}
export interface KbTemplate {
name: string
file: string
}
export interface KbField {
name: string
description: string
type: string
required: boolean
}
export const useKbStore = defineStore('kb', () => {
const users = ref<UserInfo[]>([])
const currentUserId = ref('')
const kbs = ref<KbSummary[]>([])
const currentKbId = ref('')
const currentKbName = ref('')
const currentKbFields = ref<KbField[]>([])
const currentKbTemplates = ref<KbTemplate[]>([])
const loading = ref(false)
const showManager = ref(false)
function setKbs(list: KbSummary[]) { kbs.value = list }
function selectKb(kbId: string) {
currentKbId.value = kbId
const kb = kbs.value.find(k => k.kb_id === kbId)
if (kb) currentKbName.value = kb.name
}
async function refreshUsers() {
try {
const r = await fetch('/api/users')
const data = await r.json()
users.value = data.users || []
if (users.value.length > 0 && !currentUserId.value) {
currentUserId.value = users.value[0].user_id
}
} catch (e) { console.error('获取用户列表失败:', e) }
}
async function refreshKbs(userId?: string) {
const uid = userId || currentUserId.value
if (!uid) return
loading.value = true
try {
const r = await fetch(`/api/users/${uid}/kbs`)
const data = await r.json()
kbs.value = data.kbs || []
} catch (e) { console.error('获取知识库列表失败:', e) }
finally { loading.value = false }
}
async function createKb(name: string, description = ''): Promise<KbSummary | null> {
if (!currentUserId.value) return null
try {
const r = await fetch(`/api/users/${currentUserId.value}/kbs`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, description }),
})
if (!r.ok) throw new Error('创建失败')
const kb = await r.json()
await refreshKbs()
return kb
} catch (e) { console.error('创建知识库失败:', e); return null }
}
async function deleteKb(kbId: string): Promise<boolean> {
try {
const r = await fetch(`/api/kbs/${kbId}`, { method: 'DELETE' })
if (!r.ok) throw new Error('删除失败')
if (currentKbId.value === kbId) { currentKbId.value = ''; currentKbName.value = '' }
await refreshKbs()
return true
} catch (e) { console.error('删除知识库失败:', e); return false }
}
async function uploadFileToKb(kbId: string, file: File): Promise<boolean> {
try {
const form = new FormData()
form.append('file', file)
const r = await fetch(`/api/kbs/${kbId}/upload`, { method: 'POST', body: form })
return r.ok
} catch (e) { console.error('KB文件上传失败:', e); return false }
}
async function buildKb(kbId: string): Promise<boolean> {
try {
const r = await fetch(`/api/kbs/${kbId}/build`, { method: 'POST' })
if (!r.ok) throw new Error('构建失败')
await refreshKbs()
return true
} catch (e) { console.error('KB构建失败:', e); return false }
}
async function fetchKbFields(kbId: string) {
try {
const r = await fetch(`/api/kbs/${kbId}/fields`)
const data = await r.json()
currentKbFields.value = data.fields || []
currentKbTemplates.value = data.templates || []
} catch (e) { console.error('获取KB字段失败:', e) }
}
async function bindKbToSession(sessionId: string, kbId: string) {
try {
await fetch(`/api/sessions/${sessionId}/kb`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ kb_id: kbId }),
})
} catch (e) { console.error('绑定KB失败:', e) }
}
async function init() {
await refreshUsers()
if (currentUserId.value) await refreshKbs()
}
return {
users, currentUserId, kbs, currentKbId, currentKbName,
currentKbFields, currentKbTemplates, loading, showManager,
setKbs, selectKb, refreshUsers, refreshKbs, createKb, deleteKb,
uploadFileToKb, buildKb, fetchKbFields, bindKbToSession, init,
}
})
+152
View File
@@ -166,3 +166,155 @@ test.describe("Input UX", () => {
await expect(page.locator(".send-btn")).toBeEnabled();
});
});
// ── KB (Knowledge Base) API mocks ───────────────────────────────
function mockKbApi(page: any) {
mockApi(page);
page.route("**/api/users", (route: any) => {
if (route.request().method() === "POST") {
return route.fulfill({
json: { user_id: "u_e2e_test_001", name: "E2E用户", created_at: "2026-05-23T00:00:00Z" },
});
}
return route.fulfill({
json: { users: [{ user_id: "u_e2e_test_001", name: "E2E用户", created_at: "2026-05-23T00:00:00Z" }] },
});
});
page.route("**/api/users/*/kbs", (route: any) => {
if (route.request().method() === "POST") {
return route.fulfill({
json: {
kb_id: "kb_e2e_001", user_id: "u_e2e_test_001",
name: "E2E测试库", description: "",
created_at: "2026-05-23T00:00:00Z", updated_at: "2026-05-23T00:00:00Z",
fields: [], templates: [], file_count: 0, chunk_count: 0, parse_status: "empty",
},
});
}
return route.fulfill({
json: {
kbs: [{
kb_id: "kb_e2e_001", name: "E2E测试库", description: "",
created_at: "2026-05-23T00:00:00Z", updated_at: "2026-05-23T00:00:00Z",
field_count: 10, template_count: 3, file_count: 2, chunk_count: 50, parse_status: "ready",
}],
},
});
});
page.route("**/api/kbs/*/status", (route: any) =>
route.fulfill({ json: { parse_status: "ready", file_count: 2, chunk_count: 50 } })
);
page.route("**/api/kbs/*/fields", (route: any) =>
route.fulfill({ json: {
fields: [{ name: "billNo", description: "工单号", type: "String" }],
templates: [{ name: "结算单", file: "结算单.jrxml" }],
}})
);
page.route("**/api/kbs/*", (route: any) => {
if (route.request().method() === "DELETE") {
return route.fulfill({ json: { status: "deleted" } });
}
return route.fulfill({
json: {
kb_id: "kb_e2e_001", user_id: "u_e2e_test_001",
name: "E2E测试库", description: "",
fields: [], templates: [],
file_count: 2, chunk_count: 50, parse_status: "ready",
},
});
});
page.route("**/api/kbs/*/upload", (route: any) =>
route.fulfill({ json: { filename: "test.jrxml", type: "jrxml", error: null } })
);
page.route("**/api/sessions/*/kb", (route: any) => {
if (route.request().method() === "PUT") {
return route.fulfill({ json: { kb_id: "kb_e2e_001", kb_name: "E2E测试库" } });
}
return route.fulfill({ json: { kb_id: "kb_e2e_001", kb_name: "E2E测试库" } });
});
}
// ── KB feature tests ────────────────────────────────────────────
test.describe("KB selector", () => {
test("KB selector renders in chat interface", async ({ page }) => {
await mockKbApi(page);
await page.goto("/");
await expect(page.locator(".kb-selector")).toBeVisible({ timeout: 5000 });
await expect(page.locator(".kb-label")).toContainText("知识库");
await expect(page.locator(".kb-select")).toBeVisible();
await expect(page.locator(".kb-manage-btn")).toBeVisible();
});
test("can select a KB from dropdown", async ({ page }) => {
await mockKbApi(page);
await page.goto("/");
const select = page.locator(".kb-select");
await expect(select).toBeVisible({ timeout: 5000 });
await select.selectOption({ label: "E2E测试库 (10字段, 3模板)" });
await expect(page.locator(".kb-badge")).toContainText("E2E测试库");
});
});
test.describe("KB manager", () => {
test("opens KB manager overlay", async ({ page }) => {
await mockKbApi(page);
await page.goto("/");
await page.locator(".kb-manage-btn").click();
await expect(page.locator(".kb-manager")).toBeVisible({ timeout: 3000 });
await expect(page.locator(".kb-manager h3")).toContainText("知识库管理");
});
test("can close KB manager", async ({ page }) => {
await mockKbApi(page);
await page.goto("/");
await page.locator(".kb-manage-btn").click();
await expect(page.locator(".kb-manager")).toBeVisible({ timeout: 3000 });
await page.locator(".close-btn").click();
await expect(page.locator(".kb-manager")).not.toBeVisible({ timeout: 3000 });
});
test("create form has name input and create button", async ({ page }) => {
await mockKbApi(page);
await page.goto("/");
await page.locator(".kb-manage-btn").click();
await expect(page.locator(".kb-manager")).toBeVisible({ timeout: 3000 });
await expect(page.locator('.kb-manager .create-form .kb-input').first()).toBeVisible();
await expect(page.locator('.kb-manager .create-form button.primary')).toBeVisible();
});
test("KB cards show name, status, and actions", async ({ page }) => {
await mockKbApi(page);
await page.goto("/");
await page.locator(".kb-manage-btn").click();
await expect(page.locator(".kb-manager")).toBeVisible({ timeout: 3000 });
await expect(page.locator(".kb-card")).toBeVisible({ timeout: 3000 });
await expect(page.locator(".kb-card strong")).toContainText("E2E测试库");
await expect(page.locator(".kb-status.ready")).toContainText("就绪");
await expect(page.locator(".kb-actions button")).toHaveCount(3);
});
});
test.describe("JRXML upload in chat", () => {
test("file input accepts .jrxml extension", async ({ page }) => {
await mockKbApi(page);
await page.goto("/");
const input = page.locator('.unified-input input[type="file"]');
await expect(input).toHaveAttribute("accept", /\.jrxml/);
});
});
@@ -0,0 +1,13 @@
{
"kb_id": "49b972ec9e424f04aec34899c978f087",
"user_id": "2db10c2ebbf6434aab28035026e196c3",
"name": "smoke_kb",
"description": "",
"created_at": "2026-05-23T12:21:32.409028+00:00",
"updated_at": "2026-05-23T12:21:32.409028+00:00",
"fields": [],
"templates": [],
"file_count": 0,
"chunk_count": 0,
"parse_status": "empty"
}
@@ -0,0 +1,5 @@
{
"user_id": "2db10c2ebbf6434aab28035026e196c3",
"name": "SmokeTest",
"created_at": "2026-05-23T12:21:32.399217+00:00"
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,67 @@
# 保险单接口文档
# 保险单接口文档
打印平台模版分类:保险单
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| companyTitle | 打印title | | String |
| customerName | 客户姓名 | | String |
| carNo | 车牌号 | | String |
| vin | vin码 | | String |
| carModel | 车型 | | String |
| cellPhone | 手机号 | | String |
| insureDate | 起保日期 | | String yyyy-MM-dd |
| receivable | 应收金额 | | BigDecimal |
| preferentialAmount | 优惠金额 | | BigDecimal |
| receiveAmount | 实收金额 | | BigDecimal |
| oweAmount | 未收金额 | | BigDecimal |
| commissionAmountTotal | 手续费 | | BigDecimal |
| companyRefundAmount | 保险公司返点 | | BigDecimal |
| customerRefundAmount | 客户返点 | | BigDecimal |
| insuranceCompanyName | 承保公司 | | String |
| memo | 备注 | | String |
| employeeName | 服务顾问 | | String |
| contacts | 保险公司联系人 | | String |
| contactMobile | 保险公司联系人手机号 | | String |
| channelName | 来店途径 | | String |
| startDate | 开始日期 | | String yyyy-MM-dd |
| endDate | 结束日期 | | String yyyy-MM-dd |
| renewal | 是否续保 0否/1是 | | Integer |
| tsInsuranceDetailList | 保险单明细 | | List<TsInsuranceDetailPrintVo> |
| | | | |
| <br/><br/> | | | |
TsInsuranceDetailPrintVo
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| policyNo | 保单号 | | String |
| insuranceType | 保险类型(0交强险/1商业险) | | Integer |
| name | 险种名称 | | String |
| amount | 保额 | | BigDecimal |
| receivable | 应收金额(元) | | BigDecimal |
| discount | 折扣 | | BigDecimal |
| concessionary | 优惠金额(元) | | BigDecimal |
| commissionRate | 手续费率 | | BigDecimal |
| commissionAmount | 手续费 | | BigDecimal |
| paid | 实收金额(元) | | BigDecimal |
| memo | 备注 | | String |
| companyRefundAmount | 保险公司返点 | | BigDecimal |
| customerRefundAmount | 客户返点 | | BigDecimal |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| <br/><br/> | | | |
> 更新: 2023-09-20 11:32:29 原文: <https://xcz.yuque.com/ombipo/rpc7ms/fpzmr5qph5mloy1x>
@@ -0,0 +1,420 @@
# 出/入库单据打印
# 现状梳理
| **场景** | | 入口 | 打印效果 | **底层模版** | 接口 |
| --- | --- | --- | --- | --- | --- |
| 库存 | 出入库单据-出库单 | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739783099448-84de3a8e-2473-4d88-a83d-638122d695ac.png) | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739782451805-24599a5e-df6e-4d6b-bbfb-cbbc1816614f.png) | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739782672560-9e9927d9-bdbd-4ad1-9199-f6588fc11e9d.png) | REST/stock/stockInAndOutBill/stockOutPrint?idStock=XXX&isNew=true<br>底层接口:com.f6car.stock.service.impl.print.PrintServiceImpl#getStockOutPrintUrl<br>日志关键字:+"出库单打印参数:" (有 apollo 开关 log.stockInout.print.switch 默认true |
| | 领料出库(工单领料)-出库单 | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739782990039-cc493301-06f6-48f0-bd0d-76487b30d04e.png)![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739783038197-1924e595-433a-48b7-8ea2-291492a7caae.png) | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739783054922-543a7d3c-f0eb-4814-874d-34bb82aed7cd.png) |
| | 出入库单据-入库单 | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/f5893832-29ca-4164-9071-78371d724dbd.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/09773831-4ef6-4c46-b04a-0b00ea6376b7.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/f4b0a5b6-b259-4b41-91ec-67ed895c9c61.png) | REST/stock/stockInAndOutBill/stockInPrint?idStock=XXX&isNew=true<br>底层接口:com.f6car.stock.service.impl.print.PrintServiceImpl#getStockInPrintUrl<br>日志关键字:+"入库单打印参数:" (有 apollo 开关 log.stockInout.print.switch 默认true |
| | 领料出库(工单领料)-退料单 | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/b3aaf9d0-5231-4dd2-bbb4-a3f02afbfb89.png)<br>![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/afe3844b-a253-4493-ad83-f1a6371aaa51.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/17875029-3462-449d-9f24-3aad72d3737b.png) |
| | 手工出入库-出库单 | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739783184569-1df2391a-c404-48d3-be4e-b39f3f50e9ef.png) | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739783196368-a62151d6-2ff3-48ce-ae99-2742f296e1be.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/de1c2e35-c7db-43e1-b03d-9a617b876df3.png) | REST/stock/manual/print?pkId=XXX&billType=0&isNew=true type=0 表示入库单 type=1 表示出库单)<br>底层接口:com.f6car.stock.service.print.PrintService#getManualStorageStockInPrintUrl<br>日志关键字:+"手工出入库单据打印入参是:" |
| | 手工出入库-入库单 | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/21383c0d-4fbd-490f-95a4-b55a9c1aa473.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/4e269988-1029-48cd-97e5-c71e3ec6b275.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/5e6f982e-0369-48eb-a09b-3d247608db0f.png) |
| | 领料详情-打印领料单 | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/9c2c7fdb-360f-43e0-b07f-4ee0f8a43510.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/974ef939-6d36-433d-a3fe-594cb6cb270c.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/f4baaf53-7915-4b45-9ee5-aff164922a00.png) | REST/stock/maintain/print?idSourceBill=XXX&hasPreview=true<br>底层接口:com.f6car.stock.service.impl.print.PrintServiceImpl#getMaintainPrintUrl<br>日志关键字:+"领料单打印参数:" (有 apollo 开关 log.stock.maintain.print.switch 默认false |
# 出入库单据-出库单 && 领料出库-出库单打印模版参数说明
出库单据定制类需求模版分类(newStockOutMaintainCustomPrint--20250925新增
打印模版参数
HashMap<String, Object> resultMap
| **字段** | **说明** | 备注 |
| --- | --- | --- |
| title | 门店名称+ "出库单" | |
| billNo | 出库单号 | |
| sourceBillNo | 来源单号 | |
| showSourceBillNo | 显示来源单号 boolean | |
| billStatus | 单据状态(制单、完成) | |
| inOutDate | 出库日期 | |
| showInOutDate | 是否显示出库日期(boolean | |
| objectName | 出入库对象 | |
| objectNameGD | 出入库对象工单<br/>工单出库单:客户姓名+车牌号整体+车辆VIN码+车辆品牌车系车型全称 (拼接后取前 80 个字符)<br/>非工单出库单:"" | |
| creatorName | 制单人 | |
| billDate | 制单日期 yyyy-MM-dd | |
| sumNumber | 材料总数量 | |
| sumAmount | 总金额 <br>\--脱敏场景显示 _\*_\*_\*\*_ | |
| chineseAmount | 总金额(中文大写)<br>\--脱敏场景显示 _\*_\*\*\* | |
| nowDateTime | 打印当前时间 yyyy-MM-dd HH:mm | |
| idOwnOrg | 门店ID | |
| remark | 备注信息 | |
| showRemark | 是否显示备注(boolean<br>\-- 备注不为空时是 true | |
| isGdType | 是否是工单类型单据出库(boolean<br>\-- 工单出库单是 true | |
| saName | 服务顾问姓名<br>\-- 工单出库单场景 | |
| printCount | 打印次数 | |
| showCustomCode | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739785277433-3b96ef23-7a4c-4df1-bf12-49cd4547de9e.png)配置出入库打印参数-是否显示材料编码(boolean) | |
| showBusinessLabel | 配置出入库打印参数-是否显示材料业务分类(boolean) | |
| showApplyModel | 配置出入库打印参数-是否显示材料适用车型(boolean) | |
| showStorageName | 配置出入库打印参数-是否显示出库仓库(boolean) | |
| showDefSeat | 配置出入库打印参数-是否显示出库货位(boolean) | |
| showChineseAmount | 配置出入库打印参数-是否显示大写金额(boolean) | |
| showChineseSubtotal | 配置出入库打印参数-是否显示大写行合计(boolean) | |
| columnCount | 显示几列 | |
| batchPrintConfig | 配置出入库打印参数-查询批次成本展示设置 0:都不展示,1:总成本(将批次成本合并),2和3:展示批次成本 | |
| memo | 车主描述 | \--20250925 新增 |
| partInfoDetailMapList | | |
| sortNumber | 序号<br>**通用模版追加一行合计行,显示 合计** | |
| partShowName | 材料组合名称 | |
| partName | 材料名称 | |
| partBrand | 材料品牌 | \-- 2025.02.27 新增 |
| supplierCode | 零件号 | |
| labelName | 业务分类 | |
| applyModel | 适用车型 | |
| customCode | 材料编码 | |
| storageName | 仓库名称 | |
| defSeat | 货位 | |
| number | 数量<br>**\-- 通用模版追加一行合计行,显示 材料出库总数** | |
| unit | 单位 | |
| price | 单价<br>\--脱敏场景显示 _\*_\*\*\* | |
| subtotal | 金额<br>\--脱敏场景显示 _\*_\*\*\* | |
| employeeName | 出库人 | |
| salesEmployeeNameList | 材料行销售人员 | \--20250925 新增 |
| orderBatchList<br>\--List<Map<String, String>> | 材料行成本相关 | |
| orderNo | 批次号<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示 ""<br>**\-- 通用模版追加一行合计,追加行,显示 ""** | |
| count | 批次出库几个<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示 ""<br>**\-- 通用模版追加一行合计,追加行,显示总数** | |
| price | 批次单位成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 "" | |
| priceNoTax | 批次单位除税成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 "" | 2025.08.14 新增 |
| totalPrice | 批次总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本 | |
| totalPriceNoTax | 批次除税总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价除税总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本 | 2025.08.14 新增 |
样列:
![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739844284185-900277fc-1fd5-4da3-a36f-224e8fcc9994.png)
```plaintext
{
"saName":"xuetting",
"objectNameGD":"18551638685【苏ABF358】 LFV2A21K6A3092399 大众 速腾 1.4T 双离合变速器(DSG) 2011 速腾",
"creatorName":"cltest",
"showStorageName":true,
"remark":"",
"title":"流程配置ES出库单",
"showSourceBillNo":true,
"sumAmount":"860.0",
"showChineseAmount":true,
"billStatus":"完成",
"sumNumber":"1.0",
"isGdType":true,
"printCount":"3",
"billNo":"CKD20250116001",
"showDefSeat":true,
"idOwnOrg":"15870306745529549109",
"showApplyModel":true,
"memo":"",
"partInfoDetailMapList":[
{
"employeeName":"cltest",
"sortNumber":"1",
"partShowName":" 3M 燃油宝1号 PN6868 3M (PN6868)",
"defSeat":"A",
"orderBatchList":[
{
"orderNo":"20211213000001",
"totalPrice":"0.0",
"price":"0.0",
"count":"1.0"
}
],
"supplierCode":"PN6868",
"partName":"3M 燃油宝1号 PN6868",
"applyModel":"",
"customCode":"CL0000015",
"storageName":"主仓库",
"number":"1.0",
"unit":"瓶",
"price":"860.0",
"subtotal":"860.0",
"labelName":"保养",
"salesEmployeeNameList": "B2C一店新员工,B2C一店采购员"
},
{
"number":"1.0",
"subtotal":"860.0",
"sortNumber":"合计",
"orderBatchList":[
{
"orderNo":"",
"totalPrice":"0.0",
"price":"",
"count":"1.0"
}
]
}],
"sourceBillNo":"WXD20250103001",
"billDate":"2025-01-16",
"chineseAmount":"捌佰陆拾元整",
"columnCount":"5",
"showCustomCode":true,
"showBusinessLabel":true,
"batchPrintConfig":"3",
"nowDateTime":"2025-02-18 10:04",
"showInOutDate":true,
"showChineseSubtotal":true,
"objectName":"18551638685【苏ABF358】大众 速腾",
"inOutDate":"2025-01-16",
"showRemark":false
}
```
# 出入库单据-入库单 && 退料入库-入库单打印模版参数说明
入库单据定制类需求模版分类(newStockInMaintainCustomPrint--20250925新增
打印模版参数
HashMap<String, Object> resultMap
| **字段** | **说明** | 备注 |
| --- | --- | --- |
| title | 门店名称+ "入库单" | |
| billNo | 入库单号 | |
| sourceBillNo | 来源单号 | |
| showSourceBillNo | 显示来源单号 boolean<br>默认:true | |
| billStatus | 单据状态(制单、完成) | |
| inOutDate | 入库日期<br>\-- yyyy-MM-dd | |
| showInOutDate | 是否显示入库日期(boolean<br>\-制单是 false<br>\-完成是 true | |
| objectName | 出入库对象 | |
| creatorName | 制单人 | |
| billDate | 制单日期 yyyy-MM-dd | |
| sumNumber | 材料总数量 | |
| sumAmount | 总金额 <br>\--脱敏场景显示 _\*_\*_\*\*_ | |
| noTaxSumAmount | 除税总金额 <br>\--脱敏场景显示 _\*_\*_\*\*_ | |
| chineseAmount | 总金额(中文大写)<br>\--脱敏场景显示 _\*_\*\*\* | |
| chineseNoTaxSumAmount | 除税总金额(中文大写)<br>\--脱敏场景显示 _\*_\*\*\* | |
| nowDateTime | 打印当前时间 yyyy-MM-dd HH:mm | |
| idOwnOrg | 门店ID | |
| remark | 备注信息 | |
| showRemark | 是否显示备注(boolean<br>\-- 备注不为空时是 true | |
| isGdType | 是否是工单类型单据出库(boolean<br>\-- 工单出库单是 true | |
| saName | 服务顾问姓名<br>\-- 工单出库单场景 | |
| printCount | 打印次数 | |
| showCustomCode | 配置出入库打印参数-是否显示材料编码(boolean) | |
| showBusinessLabel | 配置出入库打印参数-是否显示材料业务分类(boolean) | |
| showApplyModel | 配置出入库打印参数-是否显示材料适用车型(boolean) | |
| showStorageName | 配置出入库打印参数-是否显示出库仓库(boolean) | |
| showDefSeat | 配置出入库打印参数-是否显示出库货位(boolean) | |
| showChineseAmount | 配置出入库打印参数-是否显示大写金额(boolean) | |
| showChineseSubtotal | 配置出入库打印参数-是否显示大写行合计(boolean) | |
| sumSubtotal | 入库总金额<br>\-- 脱敏场景显示 \*\*\*\*<br>\-- 工单退-才有 | |
| chineseSubtotal | 大写入库总金额<br>\-- 脱敏场景显示 \*\*\*\*<br>\-- 工单退-才有 | |
| showReturnIn | 显示退料入库一行<br>\-- 工单退-true | |
| showSign | 【仓管签字】显示的位置<br>工单退-2;其它场景1 | |
| stockInType | 退料入库<br>\-- 工单退-才有 | |
| columnCount | 显示几列 | |
| memo | 车主描述 | \--20250925 新增 |
| partInfoDetailMapList | | |
| sortNumber | 序号 | |
| partShowName | 材料组合名称 | |
| partName | 材料名称 | |
| partBrand | 材料品牌 | |
| labelName | 业务分类 | |
| applyModel | 适用车型 | |
| customCode | 材料编码 | |
| storageName | 仓库名称 | |
| defSeat | 货位 | |
| number | 数量<br>**\-- 通用模版追加一行合计行,显示 材料出库总数** | |
| unit | 单位 | |
| price | 单价<br>\--脱敏场景显示 _\*_\*\*\* | |
| noTaxPrice | 除税单价<br>\--脱敏场景显示 _\*_\*\*\* | |
| subtotal | 金额<br>\--脱敏场景显示 _\*_\*\*\* | |
| noTaxSubtotal | 除税金额<br>\--脱敏场景显示 _\*_\*\*\* | |
| employeeName | 入库人 | |
| salesEmployeeNameList | 材料行销售人员 | \--20250925 新增 |
# 手工出入库-出/入库单
打印模版参数
HashMap<String, Object> resultMap
| **字段** | **说明** | **备注** |
| --- | --- | --- |
| title | 出库单:门店名称+ "出库单"<br>入库单:门店名称+ "入库单" | |
| billNo | 出库单号/入库单号 | |
| sourceBillNo | 来源单号 空 | |
| showSourceBillNo | 显示来源单号 booleanfalse | |
| billStatus | 单据状态(制单、完成) | |
| inOutDate | 出库日期 (yyyy-MM-dd) | |
| showInOutDate | 是否显示出库日期(boolean | |
| objectName | 出入库对象 | |
| objectNameGD | 出入库对象 | |
| creatorName | 制单人 | |
| billDate | 制单日期 yyyy-MM-dd | |
| sumNumber | 材料总数量 | |
| sumAmount | 总金额 <br>\--脱敏场景显示 _\*_\*\*\* | |
| chineseAmount | 总金额(中文大写)<br>\--脱敏场景显示 _\*_\*\*\* | |
| nowDateTime | 打印当前时间 yyyy-MM-dd HH:mm | |
| idOwnOrg | 门店ID | |
| remark | 备注信息 | |
| showRemark | 是否显示备注(boolean<br/>-- 备注不为空时是 true | |
| printCount | 打印次数 | |
| showCustomCode | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739785277433-3b96ef23-7a4c-4df1-bf12-49cd4547de9e.png)<br/>配置出入库打印参数-是否显示材料编码(boolean) | |
| showBusinessLabel | 配置出入库打印参数-是否显示材料业务分类(boolean) | |
| showApplyModel | 配置出入库打印参数-是否显示材料适用车型(boolean) | |
| showStorageName | 配置出入库打印参数-是否显示出库仓库(boolean) | |
| showDefSeat | 配置出入库打印参数-是否显示出库货位(boolean) | |
| showChineseAmount | 配置出入库打印参数-是否显示大写金额(boolean) | |
| showChineseSubtotal | 配置出入库打印参数-是否显示大写行合计(boolean) | |
| columnCount | 显示几列 | |
| batchPrintConfig | 配置出入库打印参数-查询批次成本展示设置 0:都不展示,1:总成本(将批次成本合并),2和3:展示批次成本 | |
| partInfoDetailMapList | | |
| sortNumber | 序号<br>**\-- 通用模版追加一行合计行,显示 合计** | |
| partShowName | 材料组合名称 (材料名称 规格型号 材料品牌 零件号) | |
| partName | 材料名称 | |
| partBrand | 材料品牌 -- 2025.02.27 新增 | |
| supplierCode | 零件号 | |
| labelName | 业务分类 | |
| applyModel | 适用车型 | |
| customCode | 材料编码 | |
| storageName | 仓库名称 | |
| defSeat | 货位 | |
| number | 数量<br>**\-- 通用模版追加一行合计行,显示 材料出库总数** | |
| unit | 单位 | |
| price | 单价<br>\--脱敏场景显示 _\*_\*\*\* | |
| subtotal | 金额<br>\--脱敏场景显示 _\*_\*\*\* | |
| employeeName | 出库人 | |
| taxRate | 税率 | |
| orderBatchList<br>\--List<Map<String, String>> | 材料行成本相关 | |
| orderNo | 批次号<br>\- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示 ""<br>**\-- 通用模版追加一行合计,追加行,显示 ""** | |
| count | 批次出库几个<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示出库/入库个数<br>**\-- 通用模版追加一行合计,追加行,显示总数** | |
| price | 批次单位成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 ""\*\* | |
| priceNoTax | 批次除税单位成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 ""\*\* | 2025.08.14 追加 |
| totalPrice | 批次总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本\*\*\*\* | |
| totalPriceNoTax | 批次除税总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本\*\*\*\* | 2025.08.14 追加 |
样列:
![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739842418604-54075443-f0a5-465f-ba7b-487a96b7728e.png)
日志关键字:+"手工出入库单据打印入参是"
```plaintext
{
"objectNameGD":"",
"creatorName":"王◇龙",
"showStorageName":true,
"remark":"",
"title":"ISC总店出库单",
"showSourceBillNo":false,
"sumAmount":"410.0",
"showChineseAmount":true,
"billStatus":"完成",
"sumNumber":"2.0",
"printCount":"5",
"billNo":"SGC20240515001",
"showDefSeat":true,
"idOwnOrg":"4060685614490690260",
"showApplyModel":true,
"partInfoDetailMapList":[
{
"employeeName":"XN",
"sortNumber":"1",
"partShowName":" 材料09080112 123456 米其林 (1240)",
"defSeat":"A-14-02",
"orderBatchList":[
{
"orderNo":"20210909000128",
"totalPrice":"400.0",
"price":"400.0",
"count":"1.0"
}
],
"supplierCode":"1240",
"partName":"材料09080112",
"applyModel":"大众 途安",
"customCode":"CL090800112",
"storageName":"主仓库",
"number":"1.0",
"unit":"条",
"price":"400.0",
"subtotal":"400.0",
"labelName":"轮胎"
},
{
"employeeName":"XN",
"sortNumber":"2",
"partShowName":" fnst=>1 AC德科 (FNST>=1)",
"defSeat":"",
"orderBatchList":[
{
"orderNo":"20201104000002",
"totalPrice":"4.0",
"price":"4.0",
"count":"1.0"
}
],
"supplierCode":"FNST>=1",
"partName":"fnst=>1",
"applyModel":"江淮瑞风S52....",
"customCode":"fnst>=1",
"storageName":"总2仓",
"number":"1.0",
"unit":"个",
"price":"10.0",
"subtotal":"10.0",
"labelName":"保养"
},
{
"number":"2.0",
"subtotal":"410.0",
"sortNumber":"合计",
"orderBatchList":[
{
"orderNo":"",
"totalPrice":"404.0",
"price":"",
"count":"2.0"
}
]
}
],
"sourceBillNo":"",
"billDate":"2024-05-15",
"chineseAmount":"肆佰壹拾元整",
"columnCount":"5",
"showCustomCode":true,
"showBusinessLabel":true,
"batchPrintConfig":"3",
"nowDateTime":"2025-02-18 09:33",
"showInOutDate":true,
"showChineseSubtotal":true,
"objectName":"",
"inOutDate":"2024-05-15",
"showRemark":false
}
```
# 领料详情-打印领料单
打印模版参数
HashMap<String, Object> resultMap
| **字段** | **说明** | **备注** |
| --- | --- | --- |
| idOwnOrg | 门店ID | |
| title | 门店名称+ "领料单" | |
| billNo | 工单号 | |
| nowDateTime | 打印当前时间 yyyy-MM-dd HH:mm | |
| employeeName | 服务顾问 | |
| carModel | 车型 | |
| carNoWhole | 车牌号 | |
| memo | 车主描述 | 2025.08.14 新增 |
| printTimes | 打印次数 | |
| columnCount | 显示几列 | |
| batchPrintConfig | 配置出入库打印参数-查询批次成本展示设置 0:都不展示,1:总成本(将批次成本合并),2和3:展示批次成本 | |
| stuffDetailVOList | | |
| index | 序号<br>**\-- 通用模版追加一行合计行,显示 合计** | |
| partName | 材料组合名称 (材料名称 规格型号 材料品牌 零件号) | |
| unit | 单位 | |
| defSeatList | 货位 | |
| salesEmployeeNameList | 销售人员<br>List<String> | 8.14新增 |
| orderBatchList<br>\--List<Map<String, String>> | 材料行成本相关 | |
| orderNo | 批次号<br>\- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示 ""<br>**\-- 通用模版追加一行合计,追加行,显示 ""** | |
| count | 批次出库几个<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示出库/入库个数<br>**\-- 通用模版追加一行合计,追加行,显示总数** | |
| price | 批次单位成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 ""\*\* | |
| priceNoTax | 批次除税单位成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 ""\*\* | 2025.08.14 追加 |
| totalPrice | 批次总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本\*\*\*\* | |
| totalPriceNoTax | 批次除税总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本\*\*\*\* | 2025.08.14 追加 |
@@ -0,0 +1,682 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.3.1.final using JasperReports Library version 6.3.1 -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="Blank_A4" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="3fa22123-efc4-4f3f-a186-6a8f692d17e6">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<subDataset name="List1" uuid="7366c5be-288c-41c7-b295-b8d023ec81ae">
<queryString>
<![CDATA[]]>
</queryString>
<field name="index" class="java.lang.String"/>
<field name="serviceName" class="java.lang.String"/>
<field name="cooperationServiceName" class="java.lang.String"/>
<field name="cooperationOrgName" class="java.lang.String"/>
<field name="auditStatus" class="java.lang.String"/>
<field name="cooperationCost" class="java.math.BigDecimal"/>
</subDataset>
<parameter name="title" class="java.lang.String"/>
<parameter name="billNo" class="java.lang.String"/>
<parameter name="creatorName" class="java.lang.String"/>
<parameter name="printTime" class="java.lang.String"/>
<parameter name="naEmployee" class="java.lang.String"/>
<parameter name="billDate" class="java.lang.String"/>
<parameter name="deliveryTime" class="java.lang.String"/>
<parameter name="naCustomer" class="java.lang.String"/>
<parameter name="carModel" class="java.lang.String"/>
<parameter name="cellPhone" class="java.lang.String"/>
<parameter name="carNoWhole" class="java.lang.String"/>
<parameter name="vin" class="java.lang.String"/>
<parameter name="serviceDetailVOList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="amountAll" class="java.math.BigDecimal"/>
<queryString>
<![CDATA[]]>
</queryString>
<detail>
<band height="115" splitType="Stretch">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="555" height="40" uuid="7c3a0deb-dcb0-406e-ba9c-9f279e1518b0">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="16"/>
</textElement>
<textFieldExpression><![CDATA[$P{title}!=null?$P{title}+"协作项目确认单":""]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="240" y="60" width="120" height="18" uuid="6849ccdb-a3a0-4d32-a556-7a8fd5a19cca">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{creatorName}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="420" y="60" width="135" height="18" uuid="b347e5b3-390c-44c3-b746-481aa6dc808e">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{printTime}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="60" y="60" width="120" height="18" uuid="92646184-6d70-4b6e-9f0e-23b4a7b15fb5">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{billNo}]]></textFieldExpression>
</textField>
<staticText>
<reportElement key="" x="360" y="60" width="50" height="18" isRemoveLineWhenBlank="true" uuid="86735eee-0435-4238-9b93-c08d86ed318f">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[打印时间:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="60" width="60" height="18" uuid="fef0343d-31f4-4b1c-a521-1a1b736aa485">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[工单号:]]></text>
</staticText>
<staticText>
<reportElement x="180" y="60" width="60" height="18" uuid="627d90fe-c235-45d8-9f4a-92f8d7ec0de7">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[制单人:]]></text>
</staticText>
<line>
<reportElement x="0" y="78" width="555" height="1" uuid="329fa736-2c18-4d9d-8fab-f54846315633">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
<staticText>
<reportElement x="180" y="96" width="60" height="18" uuid="ef411f00-dff2-400c-9e82-bbc2450553c0">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.4" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[车型]]></text>
</staticText>
<staticText>
<reportElement x="0" y="78" width="60" height="18" uuid="74602da5-9195-47dd-9416-9cb229aeccad">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[服务顾问]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="240" y="96" width="315" height="18" uuid="985f2bdc-723d-477e-9786-263a1d3ce58c">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.4"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{carModel}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="240" y="78" width="120" height="18" uuid="49312902-8cda-4fe3-a92f-d6ecbf9cb893">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{billDate}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="180" y="78" width="60" height="18" uuid="65483940-e0c7-4713-9e9a-6a5a7294d248">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[进厂时间]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="60" y="78" width="120" height="18" uuid="e9c8328d-22ea-4e66-a23f-5015ffbd8248">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{naEmployee}]]></textFieldExpression>
</textField>
<line>
<reportElement x="0" y="96" width="555" height="1" uuid="f6716829-054d-4fed-a12c-2f4d227724c7">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Dotted"/>
</graphicElement>
</line>
<line>
<reportElement x="0" y="114" width="555" height="1" uuid="404f9e68-011f-4c55-88f6-f1231a22908b">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Dotted"/>
</graphicElement>
</line>
<line>
<reportElement x="0" y="113" width="555" height="1" uuid="09d822fb-3735-4773-b954-9c83f3eefdef">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="420" y="78" width="135" height="18" uuid="09e44308-af9c-4bf2-b88d-b6c213fa2c43">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{vin}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="360" y="78" width="60" height="18" uuid="180c43d9-ea5c-4074-9b9e-b1ffaa905250">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[VIN码]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="60" y="96" width="120" height="18" uuid="85a6bfa2-352f-4f7e-b690-625639120c96">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{carNoWhole}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="96" width="60" height="18" uuid="fb4b2bbf-ccd0-4201-9e4b-d98ad63364ca">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[车牌号]]></text>
</staticText>
</band>
<band height="56">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<componentElement>
<reportElement x="0" y="20" width="555" height="36" uuid="dab34898-b275-4c88-8129-eefa0454fc8a">
<property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
<property name="com.jaspersoft.studio.table.style.table_header" value="Table 2_TH"/>
<property name="com.jaspersoft.studio.table.style.column_header" value="Table 2_CH"/>
<property name="com.jaspersoft.studio.table.style.detail" value="Table 2_TD"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd">
<datasetRun subDataset="List1" uuid="c97f7286-0f00-496d-af86-5689f8ef21e5">
<dataSourceExpression><![CDATA[$P{serviceDetailVOList}]]></dataSourceExpression>
</datasetRun>
<jr:column width="20" uuid="2a312910-46b6-42d0-a6f9-5b10690854b7">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column1"/>
<jr:columnHeader height="18" rowSpan="1">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.4" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
</jr:columnHeader>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="20" height="18" uuid="c20bd145-2589-4e34-acd8-c52ecb01f627"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$V{COLUMN_COUNT}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="150" uuid="e296f490-d79c-4dc7-b064-4df5beea7f6a">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column2"/>
<jr:columnHeader height="18" rowSpan="1">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.4" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<staticText>
<reportElement x="0" y="0" width="150" height="18" uuid="2cf4018d-7073-47cd-ba69-6239dea208d5"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[项目名称]]></text>
</staticText>
</jr:columnHeader>
<jr:detailCell height="18">
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="150" height="18" uuid="6d5dabce-2317-4d34-b286-b035b0319c86"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$F{serviceName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="125" uuid="901500f5-9ff9-42de-985e-5d31c114a41c">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column3"/>
<jr:columnHeader height="18" rowSpan="1">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.4" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<staticText>
<reportElement x="0" y="0" width="125" height="18" uuid="fda635b8-de40-4302-a86e-27d94d5977cf"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[协作项目名称]]></text>
</staticText>
</jr:columnHeader>
<jr:detailCell height="18">
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="125" height="18" uuid="776c62d8-14e6-4bd5-9548-ecc3f933440a"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$F{cooperationServiceName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="120" uuid="cf002016-6b32-41b9-ad57-b813114477bf">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column4"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:columnHeader height="18" rowSpan="1">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.4" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<staticText>
<reportElement x="0" y="0" width="120" height="18" uuid="04d9aca5-e6c0-4688-b7f4-93dcf012c025"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="0"/>
</textElement>
<text><![CDATA[协作门店]]></text>
</staticText>
</jr:columnHeader>
<jr:detailCell height="18">
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="120" height="18" uuid="b7b12dc9-c060-47b9-8252-ad264faed596"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$F{cooperationOrgName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="70" uuid="275bd3e2-9eb4-4274-8693-06306f525ed9">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column5"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:columnHeader height="18" rowSpan="1">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.4" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<staticText>
<reportElement x="0" y="0" width="70" height="18" uuid="a8222aea-c4e9-47c4-9c88-4043e14b06bc"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="0" rightIndent="0"/>
</textElement>
<text><![CDATA[审核状态]]></text>
</staticText>
</jr:columnHeader>
<jr:detailCell height="18">
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="70" height="18" uuid="afca6b83-a1d0-4752-8fdb-b977d5c29aad"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$F{auditStatus}.equals("0")?"未审核":"已审核"]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="70" uuid="efa6775a-c855-4f28-9c4b-f2f940ca48ed">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column6"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:columnHeader height="18" rowSpan="1">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<box>
<pen lineWidth="0.4"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.4" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<staticText>
<reportElement x="0" y="0" width="70" height="18" uuid="bc6f3a7c-ae6f-48a8-abea-958935d8ee5c"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="0" rightIndent="0"/>
</textElement>
<text><![CDATA[协作成本]]></text>
</staticText>
</jr:columnHeader>
<jr:detailCell height="18">
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="70" height="18" uuid="e473f232-b64b-4d97-a953-f8a62d80e2f0"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$F{cooperationCost}.setScale( 2, BigDecimal.ROUND_DOWN )]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
</jr:table>
</componentElement>
</band>
<band height="19">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="20" y="1" width="60" height="18" uuid="fc27cb3d-0e5d-4143-b83d-daf432fa56c8">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[小计]]></text>
</staticText>
<line>
<reportElement x="0" y="0" width="555" height="1" uuid="98b923f9-1e14-4cc5-b508-12141cefb96e">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="485" y="1" width="70" height="18" uuid="bac7389a-d2c8-4df5-9e2b-bd0fba391750">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{amountAll}.setScale( 2, BigDecimal.ROUND_DOWN )]]></textFieldExpression>
</textField>
</band>
<band height="50">
<staticText>
<reportElement x="185" y="25" width="70" height="18" uuid="c9a9b702-c7eb-4be8-b07e-365064dee61f">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph firstLineIndent="5"/>
</textElement>
<text><![CDATA[技师签名:]]></text>
</staticText>
<staticText>
<reportElement x="370" y="25" width="70" height="18" uuid="a35c01ab-c3e4-46ca-a47f-e24e82171c0e">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph firstLineIndent="5"/>
</textElement>
<text><![CDATA[审核人签名:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="25" width="70" height="18" uuid="fa883b7a-2acb-44e9-a646-e9e7c5f2aef8">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph firstLineIndent="5"/>
</textElement>
<text><![CDATA[顾问签名:]]></text>
</staticText>
</band>
</detail>
</jasperReport>
@@ -0,0 +1,32 @@
# 协作单接口文档
# 协作单接口文档
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| title | 打印抬头(门店简称) | | String |
| billNo | 工单号 | | String |
| creatorName | 制单人 | | String |
| printTime | 打印时间 | | String |
| naEmployee | 服务顾问 | | String |
| billDate | 进厂日期 | | String |
| deliveryTime | 交车时间(出厂时间) | | String |
| naCustomer | 车主姓名 | | String |
| cellPhone | 车主电话 | | String |
| carModel | 车型 | | String |
| carNoWhole | 车牌号 | | String |
| vin | 车辆VIN码 | | String |
| amountAll | 小计 | | BigDecimal |
| serviceDetailVOList | 协作项目集合 | | List<CooperationServicePrintAttribute> |
## CooperationServicePrintAttribute
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| serviceName | 工单项目名称 | | String |
| cooperationServiceName | 协作项目名称 | | String |
| cooperationOrgName | 协作门店 | | String |
| auditStatus | 审核状态 | | String |
| cooperationCost | 协作成本 | | BigDecimal |
> 更新: 2023-08-28 16:06:07 原文: <https://xcz.yuque.com/ombipo/rpc7ms/awq306g9g8fg78or>
@@ -0,0 +1,72 @@
# 增项单接口文档
# 增项单接口文档
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| title | 打印抬头(门店名称+"新增项目确认单" | | String |
| sourceBillNo | 关联工单号 | | String |
| printTime | 打印时间 | | String |
| naEmployee | 服务顾问 | | String |
| arrivalTime | 进厂日期 | | String |
| deliveryTime | 交车时间 | | String |
| naCustomer | 车主姓名 | | String |
| cellPhone | 车主电话 | | String |
| repairPerson | 送修人 | | String |
| repairPersonContact | 送修人联系方式 | | String |
| carModel | 车型 | | String |
| carNoWhole | 车牌号 | | String |
| carColor | 车身颜色 | | String |
| engineCode | 发动机号 | | String |
| vin | 车辆VIN码 | | String |
| mileage | 进厂里程 | | String |
| oilCapacity | 进厂油量 | | String |
| merchantAddress | 商家联系地址 | | String |
| merchantTel | 商家联系方式(固定电话) | | String |
| merchantPhone | 商家联系方式(手机) | | String |
| workHourPriceSubtotal | 工时费(小计) | | String |
| amountSubtotal | 材料费(小计) | | String |
| attachedServiceVoList | 增项服务项目集合 | | List<ServicePrintAttribute> |
| attachedStuffVoList | 增项配件材料集合 | | List<PartPrintAttribute> |
**ServicePrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| serviceName | 工单项目名称 | | String |
| labelName | 业务分类名称 | | String |
| customCode | 项目编码 | | String |
| price | 工时单价 | | Double |
| workHour | 工时 | | Double |
| number | 项目数量 | | Integer |
| discount | 折扣 | | Double |
| discountedSubtotal | 折后金额 | | Double |
| subtotal | 金额 | | Double |
| serviceMemo | 项目备注 | | String |
| isMember | 当前项目是否使用会员 | | Integer |
| nameMember | 会员项目的来源名(如套餐代码) | | String |
| empNameStr | 服务项目明细对应修理工名称组装字符串 | | String |
If you get gainsplease give a like
**PartPrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| partName | 材料名称 | | String |
| labelName | 业务分类名称 | | String |
| partBrand | 配件品牌 | | String |
| customCode | 材料编码 | | String |
| price | 单价 | | Double |
| number | 材料数量 | | Integer |
| unit | 单位 | | String |
| discount | 折扣 | | Double |
| discountedSubtotal | 折后金额 | | Double |
| subtotal | 金额 | | Double |
| partMemo | 材料备注 | | String |
| isMember | 当前项目是否使用会员 | | Integer |
| nameMember | 会员项目的来源名(如套餐代码) | | String |
| employeeName | 员工名称 | | String |
| empNameStr | 材料明细对应修理工名称组装字符串 | | String |
> 更新: 2023-09-05 10:34:22 原文: <https://xcz.yuque.com/ombipo/rpc7ms/gpobrxn8mn5lzthk>
@@ -0,0 +1,53 @@
# 委托单接口文档
# 委托单接口文档
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| printOrgName | 打印抬头 | | String |
| orgName | 维修厂名称 | | String |
| billNo | 工单号 | | String |
| naEmployee | 服务顾问 | | String |
| employeePhone | 服务顾问手机号 | | String |
| naCustomer | 单位名称(客户姓名) | | String |
| carNoWhole | 车牌号整体 = carPrefix + carNo | | String |
| cellPhone | 联系电话(客户) | | String |
| repairPerson | 送修人 | | String |
| repairPersonContact | 送修人联系方式 | | String |
| billDate | 进厂日期 | | String |
| deliveryTime | 交车时间(出厂时间) | | String |
| mileage | 出厂里程(进厂里程) | | BigDecimal |
| oilCapacity | 当前油量 | | String |
| vin | 车辆VIN码 | | String |
| carModelShort | 车型简称 | | String |
| signaturePhotoUrl | 签名图片 | | String |
| orgContactNumber | 联系电话(维修厂) | | String |
| orgDetailAddress | 联系地址(维修厂) | | String |
| orgContactMobile | 联系电话(维修厂) | | String |
| printContentEntrust | 委托单免责条款 | | String |
| serviceSubtotalVip | 服务项目明细小计(会员项目) | | BigDecimal |
| stuffSubtotalVip | 材料收入小计(会员项目) | | BigDecimal |
| serviceSubtotalAll | 工时费小计 | | BigDecimal |
| stuffSubtotalAll | 材料费小计 | | BigDecimal |
| serviceList | 工单对应项目集合 | | List<ServicePrintAttribute> |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
## ServicePrintAttribute
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| serviceName | 项目名称 | | String |
| price | 工时单价 | | BigInteger |
| workHour | 工时 | | BigInteger |
| subtotal | 金额 | | BigInteger |
| serviceMemo | 附加信息备注 | | String |
| isMember | 当前项目是否使用会员 | | Integer |
| empNameStr | 服务项目明细对应修理工名称组装字符串 | | String |
| | | | |
| | | | |
> 更新: 2022-11-30 15:56:54 原文: <https://xcz.yuque.com/ombipo/rpc7ms/eppwl9lml80qq2bi>
@@ -0,0 +1,121 @@
# 定金单打印接口文档
# 定金单打印接口文档
# 接口出参
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| title | 标题(门店名称) | String |
| abbreviation | 门店简称 | String |
| billNO | 定金单号 | String |
| printTime | 打印时间 | String |
| customerName | 客户姓名 | String |
| cellPhone | 手机号码 | String |
| carNo | 适用车辆(顿号、隔开) | String |
| orgName | 适用门店(顿号、隔开) | String |
| balanceStatus | 结算状态 | String |
| receivedAmount | 已收金额 | Double |
| amount | 收款交易金额 | Double |
| amountAll | 定金单收款小计 | Double |
| preRefundBalance | 定金单退款前余额 | Double |
| advancesReceivedBalance | 定金单收款后剩余面额 | Double |
| memo | 定金备注 | String |
| settlePerson | 结算人 | String |
| employeeName | 收款人 | String |
| businessDate | 收款时间 | String |
| billDate | 交易时间 | String |
| transactionDate | 交易时间 | String |
| detailAddress | 联系地址 | String |
| contactMobile | 联系方式(手机+固定电话) | String |
| naServicePerson | 服务顾问 | String |
| gatheringList | 收款方式 | List |
| L paymentType | 支付方式 | String |
| L amount | 金额 | BigDecimal |
| relationServices | 适用项目列表 | List |
| L infoId | 项目id | BigInteger |
| L infoName | 项目名称 | String |
| L labelName | 业务分类 | String |
| relationParts | 适用材料列表 | List |
| L infoId | 材料id | BigInteger |
| L infoName | 材料名称 | String |
| L labelName | 业务分类 | String |
| relationCars | 适用车辆列表 | List |
| L idCar | 车辆信息id | BigInteger |
| L carNo | 车牌号 | String |
| L vin | vin码 | String |
# 范例
```plaintext
收款收款{
"data": {
"preRefundBalance": 20000,
"memo": "我是备注。",
"title": "演示主店",
"carNo": "藏AVB2131",
"naServicePerson": "唐铭远",
"contactMobile": "15051779785",
"employeeName": "刘思杰",
"amount": 10000,
"orgName": "演示主店测试、第一分店",
"advancesReceivedBalance": 10000,
"amountAll": 10000,
"balanceStatus": "7100",
"billDate": "2024-07-11 16:27:49",
"businessDate": "2024-07-24 14:49:01",
"receivedAmount": 10000,
"abbreviation": "演示主店测试",
"transactionDate": "2024-07-11 16:27:49",
"relationServices": [
{
"infoName": "龙膜全车贴膜(不含撕膜)",
"infoId": "10545055918005551735",
"infoType": 1,
"id": "239",
"labelName": "其他",
"idSubscription": "11159"
}
],
"customerName": "牛洋",
"gatheringList": [
{
"amount": 4000,
"paymentType": "支付宝"
},
{
"amount": 3000,
"paymentType": "现金"
},
{
"amount": 3000,
"paymentType": "挂账"
}
],
"detailAddress": "西藏自治区那曲市班戈县西藏自治区那曲市班戈县北拉镇邮政所",
"billNO": "DJD20240711001",
"cellPhone": "17625046227",
"printTime": "2024-07-11 17:28:08",
"relationParts": [
{
"infoName": "奔腾CI-4 15W40 4*4L 胜牌 (706650)",
"infoId": "10545055918005692400",
"infoType": 2,
"id": "240",
"labelName": "业务分类测试",
"idSubscription": "11159"
}
],
"relationCars": [
{
"carNo": "AV616E",
"vin": "LSVAA49J132047371",
"idCar": 15809106713748983890
}
],
},
"storeId": 4060685614487994527,
"tempId": 41
}
```
> 更新: 2024-07-25 20:54:41 原文: <https://xcz.yuque.com/ombipo/rpc7ms/io3q0kkop242geg6>
@@ -0,0 +1,942 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.3.1.final using JasperReports Library version 6.3.1 -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="dingjin" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="ecb495fd-dade-4203-bf0d-1beb50748cbb">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<subDataset name="Bean" uuid="c4b2280f-9d97-43a6-af41-df8e4c3837f1">
<queryString>
<![CDATA[]]>
</queryString>
<field name="index" class="java.lang.String"/>
<field name="paymentType" class="java.lang.String"/>
<field name="amount" class="java.math.BigDecimal"/>
</subDataset>
<subDataset name="SubscriptionInfoRelationBean" uuid="63a5d0a7-7ac9-4e14-b3f3-d03138dc8910">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<queryString>
<![CDATA[]]>
</queryString>
<field name="infoName" class="java.lang.String"/>
<field name="labelName" class="java.lang.String"/>
</subDataset>
<parameter name="title" class="java.lang.String"/>
<parameter name="billNO" class="java.lang.String"/>
<parameter name="printTime" class="java.lang.String"/>
<parameter name="customerName" class="java.lang.String"/>
<parameter name="cellPhone" class="java.lang.String"/>
<parameter name="carNo" class="java.lang.String"/>
<parameter name="orgName" class="java.lang.String"/>
<parameter name="amountAll" class="java.math.BigDecimal"/>
<parameter name="memo" class="java.lang.String"/>
<parameter name="settlePerson" class="java.lang.String"/>
<parameter name="employeeName" class="java.lang.String"/>
<parameter name="billDate" class="java.lang.String"/>
<parameter name="detailAddress" class="java.lang.String"/>
<parameter name="contactMobile" class="java.lang.String"/>
<parameter name="gatheringList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="transactionDate" class="java.lang.String"/>
<parameter name="naServicePerson" class="java.lang.String"/>
<parameter name="relationServices" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="relationParts" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="balanceStatus" class="java.lang.String">
<parameterDescription><![CDATA[]]></parameterDescription>
</parameter>
<queryString>
<![CDATA[]]>
</queryString>
<detail>
<band height="103" splitType="Stretch">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="1" width="555" height="30" uuid="6b893528-b46e-4a7d-b95d-62163a92812c">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="16"/>
</textElement>
<textFieldExpression><![CDATA[$P{title}!=null?$P{title} + "定金收款单":""]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="40" width="50" height="18" uuid="df2d5fff-f94e-48ce-8327-d04b0ed8a8eb">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[定金单号:]]></text>
</staticText>
<staticText>
<reportElement x="405" y="40" width="50" height="18" uuid="6100aced-372a-4449-91cf-1b49465ddce1">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[打印时间:]]></text>
</staticText>
<line>
<reportElement x="0" y="68" width="555" height="1" uuid="6820699a-64e5-4110-aab9-059fcfa04956">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Solid"/>
</graphicElement>
</line>
<staticText>
<reportElement x="0" y="68" width="50" height="15" uuid="2d6a570f-5cff-411e-a42d-5f3833a49674">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[客户姓名]]></text>
</staticText>
<staticText>
<reportElement x="150" y="68" width="50" height="15" uuid="e25f93dc-e5d5-4f43-bd78-20bbd337df95">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[手机号码]]></text>
</staticText>
<textField isBlankWhenNull="true">
<reportElement x="50" y="68" width="100" height="15" uuid="ae838d08-7ea2-4a9f-9bcb-ad039eea0139">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{customerName}]]></textFieldExpression>
</textField>
<textField isBlankWhenNull="true">
<reportElement x="200" y="68" width="100" height="15" uuid="f1280a32-7f86-45b7-b15e-e16b88db6f69">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{cellPhone}]]></textFieldExpression>
</textField>
<textField isBlankWhenNull="true">
<reportElement x="50" y="40" width="100" height="18" uuid="f86834e1-12c3-4f52-ab27-921980da0a86">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{billNO}]]></textFieldExpression>
</textField>
<textField isBlankWhenNull="true">
<reportElement x="455" y="40" width="100" height="18" uuid="f01ca83f-cc02-4c16-8edf-1ef24a479592">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{printTime}]]></textFieldExpression>
</textField>
<textField isBlankWhenNull="true">
<reportElement x="300" y="69" width="150" height="15" uuid="28fd8ca7-cd61-45c9-a5de-ecf081f3623c">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{naServicePerson} != null ? "服务顾问 " + $P{naServicePerson} : null]]></textFieldExpression>
</textField>
</band>
<band height="15">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="0" width="505" height="15" isPrintWhenDetailOverflows="true" uuid="bc87056d-ec39-4fa8-bc1b-cf2475f0b712">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<box bottomPadding="1"/>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{carNo}!=null?$P{carNo}:""]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="0" width="50" height="15" uuid="be24a675-674a-4e69-904f-462adddebdf2">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[限定车辆]]></text>
</staticText>
<line>
<reportElement x="0" y="0" width="555" height="1" uuid="e0b7e65f-b9cd-4f1d-9896-f7b35076c20f">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
</band>
<band height="15">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="0" width="50" height="15" uuid="c951994c-2dc6-4a82-835e-9c132409edd0">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[适用门店]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="0" width="505" height="15" isPrintWhenDetailOverflows="true" uuid="6450a724-3257-45c0-b5ac-d970e2608683">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<box bottomPadding="1"/>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{orgName}!=null?$P{orgName}:""]]></textFieldExpression>
</textField>
<line>
<reportElement x="0" y="0" width="555" height="1" uuid="95190ceb-a559-4b6b-99ed-4d92568db2ce">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
</band>
<band height="15">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isBlankWhenNull="true">
<reportElement x="50" y="0" width="505" height="15" uuid="73423a33-419c-424a-bf28-1f278c95f6de">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{amountAll}.setScale( 2, BigDecimal.ROUND_DOWN )]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="0" width="50" height="15" uuid="bbfe6557-435b-4a06-89e7-68f5fa5f866e">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[定金金额]]></text>
</staticText>
<line>
<reportElement x="0" y="0" width="555" height="1" uuid="34772527-56ed-496e-83e8-638d8f87a7d4">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
</band>
<band height="18">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement stretchType="ContainerBottom" x="0" y="0" width="50" height="15" isRemoveLineWhenBlank="true" uuid="31b57e69-4dea-4593-84c4-5f6e5d7c5cad">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[定金备注]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement stretchType="ContainerBottom" x="50" y="0" width="505" height="15" isRemoveLineWhenBlank="true" isPrintWhenDetailOverflows="true" uuid="d8c6dc34-aedb-46ab-ab17-1b75d37cee18">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{memo}]]></textFieldExpression>
</textField>
<line>
<reportElement x="0" y="0" width="555" height="1" uuid="024b26b4-5ca0-4d1a-a713-2b1a04e4facb">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
</band>
<band height="6">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{relationServices} != null]]></printWhenExpression>
<line>
<reportElement x="0" y="5" width="555" height="1" uuid="3286a86a-5ca6-42c8-8302-9412dd51255b">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Solid"/>
</graphicElement>
</line>
<line>
<reportElement x="0" y="2" width="555" height="1" uuid="6e4f8003-b171-43ba-b3d1-1e863feaa14f">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
</band>
<band height="62">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{relationServices} != null]]></printWhenExpression>
<staticText>
<reportElement x="0" y="0" width="555" height="18" uuid="186a3a0f-9415-48af-8fc5-f0b91f01c8dd">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[限定项目条件]]></text>
</staticText>
<line>
<reportElement x="0" y="18" width="555" height="1" uuid="784988f1-bab7-4893-b7b0-45b8c7d36113">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
<staticText>
<reportElement x="10" y="21" width="40" height="18" uuid="3581da6d-7736-4324-8d00-46a8101328b8">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[序号]]></text>
</staticText>
<staticText>
<reportElement x="60" y="21" width="260" height="18" uuid="729944a0-c591-4d3c-bd82-10852a27ddb5">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[项目名称]]></text>
</staticText>
<staticText>
<reportElement x="340" y="21" width="215" height="18" uuid="91d16827-3716-49bf-9589-fef01692e051">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[业务分类]]></text>
</staticText>
<componentElement>
<reportElement x="10" y="43" width="530" height="15" uuid="70e14cdb-d3db-4143-a95b-06c79be594e9">
<property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
<property name="com.jaspersoft.studio.table.style.table_header" value="Table_TH"/>
<property name="com.jaspersoft.studio.table.style.column_header" value="Table_CH"/>
<property name="com.jaspersoft.studio.table.style.detail" value="Table_TD"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="net.sf.jasperreports.export.headertoolbar.table.name" value=""/>
</reportElement>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd">
<datasetRun subDataset="SubscriptionInfoRelationBean" uuid="65597dd8-2b18-4b8f-af59-9a14a8c656f9">
<dataSourceExpression><![CDATA[$P{relationServices}]]></dataSourceExpression>
</datasetRun>
<jr:column width="20" uuid="9f5cb566-9a81-471e-83b7-eed0ae9a15cc">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column1"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="15">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="20" height="15" uuid="5b2a2317-f511-4c5b-8146-7422aa7bb656"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$V{REPORT_COUNT}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="299" uuid="50b00464-f90b-4b48-8dc9-3d06ac503250">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column2"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="15">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement stretchType="RelativeToBandHeight" x="0" y="0" width="299" height="15" uuid="4d201729-3ee9-42c2-b659-49966309bb6a">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph lineSpacing="Single" leftIndent="33"/>
</textElement>
<textFieldExpression><![CDATA[$F{infoName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="211" uuid="9589926e-9fb0-432d-a202-7701e2654ee5">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column3"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="15">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="211" height="15" uuid="41413b43-582e-4a5e-9025-bbe735caf9f7">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph rightIndent="170"/>
</textElement>
<textFieldExpression><![CDATA[$F{labelName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
</jr:table>
</componentElement>
</band>
<band height="6">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{relationParts} != null]]></printWhenExpression>
<line>
<reportElement x="0" y="4" width="555" height="1" uuid="2f6ac67a-48f3-4ead-a04d-dc0a5ee9ec77">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="59">
<printWhenExpression><![CDATA[$P{relationParts} != null]]></printWhenExpression>
<staticText>
<reportElement x="0" y="0" width="555" height="18" uuid="0cf72aee-23b8-46aa-a29f-cf3f3b5f60fa">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[限定材料条件]]></text>
</staticText>
<line>
<reportElement x="0" y="18" width="555" height="1" uuid="55b86547-d6ca-42e0-854e-2e08893a2b5f">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
<staticText>
<reportElement x="10" y="20" width="40" height="18" uuid="91997de4-2512-47f0-ab50-a9611a851818">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[序号]]></text>
</staticText>
<staticText>
<reportElement x="60" y="20" width="260" height="18" uuid="821e871f-4d45-4c6e-a153-bdb11222edfd">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[材料名称]]></text>
</staticText>
<staticText>
<reportElement x="340" y="20" width="215" height="18" uuid="cb6c5a3e-3605-4451-b6e6-57bbd3b20aaa">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[业务分类]]></text>
</staticText>
<componentElement>
<reportElement x="10" y="40" width="530" height="15" uuid="d9989d91-ae8e-494b-99ae-e466e8480b93">
<property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
<property name="com.jaspersoft.studio.table.style.table_header" value="Table_TH"/>
<property name="com.jaspersoft.studio.table.style.column_header" value="Table_CH"/>
<property name="com.jaspersoft.studio.table.style.detail" value="Table_TD"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd">
<datasetRun subDataset="SubscriptionInfoRelationBean" uuid="2250db78-4f45-464b-afac-5c1fbe1c526a">
<dataSourceExpression><![CDATA[$P{relationParts}]]></dataSourceExpression>
</datasetRun>
<jr:column width="20" uuid="a3a9ba5c-d15d-45a0-9529-905dd13d1bb3">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column1"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="15">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="20" height="15" uuid="80a4d1e9-c964-49e0-9165-e83d7f53c955"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$V{REPORT_COUNT}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="299" uuid="5b5b73ee-6e45-440c-abcc-c47295a8f445">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column2"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="15">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement stretchType="RelativeToBandHeight" x="0" y="0" width="299" height="15" uuid="f344b148-d0c4-4f7e-9b21-1a350e385f04">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph lineSpacing="Single" leftIndent="33"/>
</textElement>
<textFieldExpression><![CDATA[$F{infoName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="211" uuid="bc6a0624-08f4-4831-b786-d96ecbad358f">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column3"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="15">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="211" height="15" uuid="43282bdc-4735-4173-b9be-1e34a4c7ecf1">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph rightIndent="170"/>
</textElement>
<textFieldExpression><![CDATA[$F{labelName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
</jr:table>
</componentElement>
</band>
<band height="6">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<line>
<reportElement x="0" y="4" width="555" height="1" uuid="6020511d-2f28-4dc2-80f1-9176d218b3be">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="19">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA["7100".equals($P{balanceStatus})]]></printWhenExpression>
<staticText>
<reportElement x="20" y="0" width="50" height="18" uuid="3581da6d-7736-4324-8d00-46a8101328b8">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[收款方式]]></text>
</staticText>
<line>
<reportElement x="0" y="18" width="555" height="1" uuid="784988f1-bab7-4893-b7b0-45b8c7d36113">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Solid"/>
</graphicElement>
</line>
<staticText>
<reportElement x="455" y="0" width="75" height="18" uuid="e5017d0f-3946-4dd0-8cdd-cc01396607c0">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
<paragraph leftIndent="0" rightIndent="3"/>
</textElement>
<text><![CDATA[金额]]></text>
</staticText>
</band>
<band height="15">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA["7100".equals($P{balanceStatus})]]></printWhenExpression>
<componentElement>
<reportElement x="0" y="0" width="530" height="15" uuid="e508f855-fa78-4d64-a6e4-a8a8a46aca77">
<property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
<property name="com.jaspersoft.studio.table.style.table_header" value="Table_TH"/>
<property name="com.jaspersoft.studio.table.style.column_header" value="Table_CH"/>
<property name="com.jaspersoft.studio.table.style.detail" value="Table_TD"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd">
<datasetRun subDataset="Bean" uuid="b65bc3e9-f219-4cb4-9e37-06389e44b27a">
<dataSourceExpression><![CDATA[$P{gatheringList}]]></dataSourceExpression>
</datasetRun>
<jr:column width="20" uuid="ee067303-224b-4228-9945-59b00d0f5cab">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column1"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="15">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="20" height="15" uuid="69411085-ae93-4494-a5be-5f1c08c669e6"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$V{REPORT_COUNT}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="435" uuid="c73b03e1-b451-4ba7-aa66-506770b0d16b">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column2"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="15">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="435" height="15" uuid="513d0744-5c1a-4cbc-9ab0-a494b491c01f"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$F{paymentType}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="75" uuid="e1f4c79b-33e8-46e3-8d01-2405584f90dc">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column3"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="15">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="75" height="15" uuid="41c011f0-5a98-40a5-96c5-27cad152cd7b"/>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph rightIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$F{amount}.setScale( 2, BigDecimal.ROUND_DOWN )]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
</jr:table>
</componentElement>
</band>
<band height="15">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA["7100".equals($P{balanceStatus})]]></printWhenExpression>
<line>
<reportElement x="0" y="0" width="555" height="1" uuid="125ba998-bf5b-433a-9954-6043f153ff42">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Solid"/>
</graphicElement>
</line>
<staticText>
<reportElement x="20" y="0" width="50" height="15" uuid="f6849ec7-9e15-47e5-b71e-dcd53f0b84e0">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[小计]]></text>
</staticText>
<textField isBlankWhenNull="true">
<reportElement x="455" y="0" width="75" height="15" uuid="6fc715c5-3a85-4a39-8b51-ff75731aba06">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="0" rightIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{amountAll}.setScale( 2, BigDecimal.ROUND_DOWN )]]></textFieldExpression>
</textField>
</band>
<band height="25">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA["7100".equals($P{balanceStatus})]]></printWhenExpression>
<staticText>
<reportElement x="0" y="10" width="50" height="15" uuid="97da61eb-2f1d-46a4-8ed4-c3172d371408">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[结算人:]]></text>
</staticText>
<staticText>
<reportElement x="200" y="10" width="50" height="15" uuid="319ada1b-ff86-4b3c-82d6-15bc9981b1d9">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[收款人:]]></text>
</staticText>
<staticText>
<reportElement x="405" y="10" width="50" height="15" uuid="01e213a0-948f-4a51-9b39-f3da64729c60">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[收款时间:]]></text>
</staticText>
<textField isBlankWhenNull="true">
<reportElement x="50" y="10" width="100" height="15" uuid="92b95e36-c1a5-4311-94db-f615b29958f1">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{employeeName}]]></textFieldExpression>
</textField>
<textField isBlankWhenNull="true">
<reportElement x="250" y="10" width="100" height="15" uuid="389f723a-1618-48f7-8e25-890a4902b19b">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{employeeName}]]></textFieldExpression>
</textField>
<textField isBlankWhenNull="true">
<reportElement x="455" y="10" width="100" height="15" uuid="d8abcebc-b5b3-4c86-89b2-b45c218f353e">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{billDate}]]></textFieldExpression>
</textField>
</band>
<band height="109">
<staticText>
<reportElement x="0" y="25" width="50" height="15" uuid="55ff22be-c6bb-4bbb-bb41-8cefc91675e0">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[办理人签名]]></text>
</staticText>
<staticText>
<reportElement x="300" y="25" width="50" height="15" uuid="ff44db31-a42f-477e-9e52-378b94c34217">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[客户签名]]></text>
</staticText>
<staticText>
<reportElement x="0" y="55" width="50" height="15" uuid="9afa1f35-7bf2-4b9b-aa4d-9989db266bd5">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[联系地址:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="70" width="50" height="15" uuid="a439d4d9-7268-40cb-8f44-41c75c328179">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[联系方式:]]></text>
</staticText>
<textField isBlankWhenNull="true">
<reportElement x="50" y="55" width="505" height="15" uuid="0fd29c3e-c04b-4552-85b0-55b2720d57df">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{detailAddress}]]></textFieldExpression>
</textField>
<textField isBlankWhenNull="true">
<reportElement x="50" y="70" width="505" height="15" uuid="421732bc-d6d4-4f97-8480-565eaa109ffc">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{contactMobile}.split(" ")[1].length() == 5 ? $P{contactMobile}.split(" ")[0] : $P{contactMobile}]]></textFieldExpression>
</textField>
</band>
</detail>
</jasperReport>
@@ -0,0 +1,641 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.3.1.final using JasperReports Library version 6.3.1 -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="Blank_A4" pageWidth="223" pageHeight="842" whenNoDataType="NoPages" columnWidth="223" leftMargin="0" rightMargin="0" topMargin="0" bottomMargin="0" uuid="5195ed21-e8c1-4daf-ab55-5f5dc9c07b0a">
<property name="com.jaspersoft.studio.unit." value="pixel"/>
<property name="com.jaspersoft.studio.unit.topMargin" value="mm"/>
<property name="com.jaspersoft.studio.unit.bottomMargin" value="mm"/>
<property name="com.jaspersoft.studio.unit.leftMargin" value="mm"/>
<property name="com.jaspersoft.studio.unit.rightMargin" value="mm"/>
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<property name="com.jaspersoft.studio.unit.pageWidth" value="mm"/>
<property name="ireport.zoom" value="1.0"/>
<property name="ireport.x" value="0"/>
<property name="ireport.y" value="0"/>
<subDataset name="Dataset1" uuid="7e6cf9c7-d927-4f84-a0f6-e84863998e11">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<queryString>
<![CDATA[]]>
</queryString>
<field name="paymentType" class="java.lang.String"/>
<field name="amount" class="java.math.BigDecimal"/>
</subDataset>
<subDataset name="SubscriptionInfoRelationBean" uuid="63b98521-dd7d-419f-bae4-f6a776630dd5">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<queryString>
<![CDATA[]]>
</queryString>
<field name="infoName" class="java.lang.String"/>
<field name="labelName" class="java.lang.String"/>
</subDataset>
<parameter name="title" class="java.lang.String"/>
<parameter name="abbreviation" class="java.lang.String"/>
<parameter name="billNO" class="java.lang.String"/>
<parameter name="printTime" class="java.lang.String"/>
<parameter name="customerName" class="java.lang.String"/>
<parameter name="cellPhone" class="java.lang.String"/>
<parameter name="carNo" class="java.lang.String"/>
<parameter name="orgName" class="java.lang.String"/>
<parameter name="amountAll" class="java.math.BigDecimal"/>
<parameter name="memo" class="java.lang.String"/>
<parameter name="settlePerson" class="java.lang.String"/>
<parameter name="employeeName" class="java.lang.String"/>
<parameter name="billDate" class="java.lang.String"/>
<parameter name="detailAddress" class="java.lang.String"/>
<parameter name="contactMobile" class="java.lang.String"/>
<parameter name="gatheringList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="transactionDate" class="java.lang.String"/>
<parameter name="naServicePerson" class="java.lang.String"/>
<parameter name="relationServices" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="relationParts" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="balanceStatus" class="java.lang.String"/>
<queryString>
<![CDATA[]]>
</queryString>
<detail>
<band height="20" splitType="Stretch">
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="222" height="20" isPrintWhenDetailOverflows="true" uuid="fdd7c75d-7f0c-42a3-afa1-9927522a4dd1">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{title}]]></textFieldExpression>
</textField>
</band>
<band height="22">
<staticText>
<reportElement x="0" y="1" width="222" height="15" uuid="2f71d4ac-e294-4763-a1ad-8e24fcb09055">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体"/>
</textElement>
<text><![CDATA[定金单]]></text>
</staticText>
<line>
<reportElement x="15" y="16" width="195" height="1" uuid="1fac1b83-9a99-4059-94f8-705f03630c06">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
</band>
<band height="12">
<printWhenExpression><![CDATA["7100".equals($P{balanceStatus})]]></printWhenExpression>
<staticText>
<reportElement x="0" y="0" width="44" height="12" uuid="8210a1bf-fb5f-465d-8e13-15ee265db665">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[收款时间]]></text>
</staticText>
<textField isBlankWhenNull="true">
<reportElement x="44" y="0" width="177" height="12" uuid="e79b3ef6-3727-46d0-8de1-035b71f358f4">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="15"/>
</textElement>
<textFieldExpression><![CDATA[$P{transactionDate}]]></textFieldExpression>
</textField>
</band>
<band height="12">
<staticText>
<reportElement x="0" y="0" width="44" height="12" uuid="924e0a29-acb7-4430-98fd-0dd08bf26d01">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[单号]]></text>
</staticText>
<textField isBlankWhenNull="true">
<reportElement x="44" y="0" width="177" height="12" uuid="35315081-9a78-4f78-8df5-5ad786dd56b9">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="15"/>
</textElement>
<textFieldExpression><![CDATA[$P{billNO}]]></textFieldExpression>
</textField>
</band>
<band height="12">
<printWhenExpression><![CDATA["7100".equals($P{balanceStatus})]]></printWhenExpression>
<staticText>
<reportElement x="0" y="0" width="44" height="12" uuid="af0870ec-d979-4c13-8743-c7a0c3621815">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[收款人]]></text>
</staticText>
<textField isBlankWhenNull="true">
<reportElement x="44" y="0" width="177" height="12" uuid="3b66f5f5-f6d9-4d05-90e7-af9561373bb6">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="15"/>
</textElement>
<textFieldExpression><![CDATA[$P{employeeName}]]></textFieldExpression>
</textField>
</band>
<band height="12">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{naServicePerson}!=null]]></printWhenExpression>
<staticText>
<reportElement x="0" y="0" width="44" height="12" uuid="ccdb57d5-cdaf-4189-930f-fc1165c936ee">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[服务顾问]]></text>
</staticText>
<textField isBlankWhenNull="true">
<reportElement x="44" y="-1" width="177" height="12" uuid="62146321-e5d6-4c36-b91c-db8c0a5653de">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体"/>
<paragraph leftIndent="15"/>
</textElement>
<textFieldExpression><![CDATA[$P{naServicePerson}]]></textFieldExpression>
</textField>
</band>
<band height="24">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="-1" width="44" height="12" uuid="53391abc-4be2-40a8-8c2d-a770e6c13f18">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[车主姓名]]></text>
</staticText>
<staticText>
<reportElement x="0" y="11" width="44" height="12" uuid="e3b541a2-001d-4a3c-b67d-c687f923f78e">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[车主电话]]></text>
</staticText>
<textField isBlankWhenNull="true">
<reportElement x="44" y="0" width="177" height="12" uuid="7f7dfe97-543c-4dae-8af6-d7113d7a438e">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="15"/>
</textElement>
<textFieldExpression><![CDATA[$P{customerName}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="44" y="11" width="177" height="12" isPrintWhenDetailOverflows="true" uuid="b8c5fc03-800c-4e05-bfe1-e47ec4dfbcb8">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="15"/>
</textElement>
<textFieldExpression><![CDATA[$P{cellPhone}]]></textFieldExpression>
</textField>
</band>
<band height="12">
<staticText>
<reportElement x="0" y="0" width="44" height="12" uuid="92f7d079-701e-433b-80f4-54b2bffec66c">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[适用门店]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="44" y="0" width="177" height="12" isPrintWhenDetailOverflows="true" uuid="0e37d554-d28f-405d-8f0a-ea70b390ee9d">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="15"/>
</textElement>
<textFieldExpression><![CDATA[$P{orgName}!=null?$P{orgName}:""]]></textFieldExpression>
</textField>
</band>
<band height="12">
<staticText>
<reportElement x="0" y="0" width="44" height="12" uuid="a58d2826-ecce-4305-a937-880861b60320">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[限定车辆]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="44" y="0" width="177" height="12" isPrintWhenDetailOverflows="true" uuid="fb93e076-c62e-40f1-ad63-412181861bbb">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="15"/>
</textElement>
<textFieldExpression><![CDATA[$P{carNo}!=null?$P{carNo}:""]]></textFieldExpression>
</textField>
</band>
<band height="12">
<staticText>
<reportElement x="0" y="0" width="33" height="12" uuid="3776a7e6-1f34-4e6e-b685-28ea1beb953f">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[备注:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="33" y="0" width="188" height="12" isPrintWhenDetailOverflows="true" uuid="49649536-e37f-4a5c-8fbf-35e71314c888"/>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="5"/>
</textElement>
<textFieldExpression><![CDATA[$P{memo}]]></textFieldExpression>
</textField>
</band>
<band height="6">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{relationServices} != null]]></printWhenExpression>
<line>
<reportElement x="5" y="4" width="202" height="1" uuid="d13f3282-835d-4a3f-bfaf-de1bc0f02c6f">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="42">
<printWhenExpression><![CDATA[$P{relationServices} != null]]></printWhenExpression>
<staticText>
<reportElement x="5" y="6" width="64" height="12" uuid="b46dad1a-e4e2-4cbb-8078-8fadef5c2421">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[限定项目条件]]></text>
</staticText>
<componentElement>
<reportElement x="12" y="21" width="200" height="12" uuid="7544bbec-c542-4d41-ae3c-a434b41bfc67">
<property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
<property name="com.jaspersoft.studio.table.style.table_header" value="Table_TH"/>
<property name="com.jaspersoft.studio.table.style.column_header" value="Table_CH"/>
<property name="com.jaspersoft.studio.table.style.detail" value="Table_TD"/>
<property name="net.sf.jasperreports.export.headertoolbar.table.name" value=""/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd" whenNoDataType="Blank">
<datasetRun subDataset="SubscriptionInfoRelationBean" uuid="0c7af790-dee9-440a-9406-7ca725234db3">
<dataSourceExpression><![CDATA[$P{relationServices}]]></dataSourceExpression>
</datasetRun>
<jr:column width="130" uuid="854655c8-8e29-4621-8d50-301a091b089c">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column1"/>
<jr:detailCell height="12">
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement stretchType="RelativeToBandHeight" x="0" y="0" width="130" height="12" uuid="17589590-fe21-4b4b-ac94-6c89446150af"/>
<textElement>
<font fontName="黑体"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$F{infoName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="70" uuid="4a3d6479-0f51-4e1b-8499-f5e408b62814">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column2"/>
<jr:detailCell height="12">
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="70" height="12" uuid="948b7821-5590-4c82-a0ba-540f0c14ea99"/>
<textElement verticalAlignment="Middle">
<font fontName="黑体"/>
<paragraph leftIndent="23"/>
</textElement>
<textFieldExpression><![CDATA[$F{labelName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
</jr:table>
</componentElement>
</band>
<band height="10">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{relationParts} != null]]></printWhenExpression>
<line>
<reportElement x="5" y="4" width="202" height="1" uuid="8e4022d7-288e-46d5-9e8b-322f809389b5">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="42">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{relationParts} != null]]></printWhenExpression>
<staticText>
<reportElement x="5" y="6" width="64" height="12" uuid="3eb5fc85-6d1a-4e66-a6bb-90ffab0419bf">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[限定材料条件]]></text>
</staticText>
<componentElement>
<reportElement x="12" y="21" width="200" height="12" uuid="0e8c9514-c83d-46bb-a398-df11b50450b3">
<property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
<property name="com.jaspersoft.studio.table.style.table_header" value="Table_TH"/>
<property name="com.jaspersoft.studio.table.style.column_header" value="Table_CH"/>
<property name="com.jaspersoft.studio.table.style.detail" value="Table_TD"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd" whenNoDataType="Blank">
<datasetRun subDataset="SubscriptionInfoRelationBean" uuid="a7ec5475-0753-496e-ba0c-821200ca3b09">
<dataSourceExpression><![CDATA[$P{relationParts}]]></dataSourceExpression>
</datasetRun>
<jr:column width="130" uuid="521fa5b3-9477-4345-a4f1-22b89cd526b4">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column1"/>
<jr:detailCell height="12">
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement stretchType="RelativeToBandHeight" x="0" y="0" width="130" height="12" uuid="00aa7b2c-66f5-4b2a-86ef-e353042a169e"/>
<textElement>
<font fontName="黑体"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$F{infoName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="70" uuid="7d0f5ffc-76f9-4f63-9218-1af7cb5a5fbd">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column2"/>
<jr:detailCell height="12">
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="70" height="12" uuid="006c2c0f-2b11-4379-964b-6b7769307ee7"/>
<textElement verticalAlignment="Middle">
<font fontName="黑体"/>
<paragraph leftIndent="23"/>
</textElement>
<textFieldExpression><![CDATA[$F{labelName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
</jr:table>
</componentElement>
</band>
<band height="6">
<line>
<reportElement x="5" y="4" width="202" height="1" uuid="394e187e-5449-424f-807c-bb0c62e2d18d">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="29">
<textField isBlankWhenNull="true">
<reportElement x="130" y="5" width="70" height="12" uuid="3d050094-7d49-4e3f-bca4-47d99321ed82">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{amountAll}.setScale( 2, BigDecimal.ROUND_DOWN )]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="5" width="130" height="12" uuid="4e9ec6ff-a558-48bb-a529-e94936a53b12">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[金额]]></text>
</staticText>
<line>
<reportElement x="15" y="23" width="195" height="1" uuid="267b9040-1d0d-4050-8906-04ebcdfba7b0">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
</band>
<band height="18">
<printWhenExpression><![CDATA["7100".equals($P{balanceStatus})]]></printWhenExpression>
<componentElement>
<reportElement x="0" y="5" width="200" height="12" uuid="9ba8d55a-8e43-4aee-af93-ffdb8f2ec5ce">
<property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
<property name="com.jaspersoft.studio.table.style.table_header" value="Table_TH"/>
<property name="com.jaspersoft.studio.table.style.column_header" value="Table_CH"/>
<property name="com.jaspersoft.studio.table.style.detail" value="Table_TD"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd" whenNoDataType="Blank">
<datasetRun subDataset="Dataset1" uuid="a4a70f4b-c3cb-4482-95ff-149fb66ea7f6">
<dataSourceExpression><![CDATA[$P{gatheringList}]]></dataSourceExpression>
</datasetRun>
<jr:column width="130" uuid="3b60f38e-2a7c-43b7-b976-071e57aeadf1">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column1"/>
<jr:detailCell height="12">
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="130" height="12" uuid="faf9cca2-d266-463d-91ee-cc7cc3575f24"/>
<textElement>
<font fontName="黑体"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$F{paymentType}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="70" uuid="56829dd0-7286-4bbf-825a-48d016b353bd">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column2"/>
<jr:detailCell height="12">
<textField isBlankWhenNull="true">
<reportElement x="0" y="0" width="70" height="12" uuid="f8450d4c-7ad6-4237-ba69-56862a2d9a6d"/>
<textElement>
<font fontName="黑体"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$F{amount}.setScale( 2, BigDecimal.ROUND_DOWN )]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
</jr:table>
</componentElement>
</band>
<band height="44">
<line>
<reportElement x="15" y="5" width="195" height="1" uuid="425e895d-8b95-436e-bd82-55a6c4d4cbf8">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
<staticText>
<reportElement x="0" y="11" width="44" height="12" uuid="c3e05a21-6ba4-4708-991a-825a994a1b2f">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[客户签字]]></text>
</staticText>
<line>
<reportElement x="15" y="28" width="195" height="1" uuid="b6bb8778-b57c-4fda-9e09-1a7eac4ca4e7">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
<staticText>
<reportElement x="0" y="32" width="222" height="12" uuid="8a83c68d-8555-4889-a063-2f78dce9af4a">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[请妥善保管购物凭证,谢谢惠顾!]]></text>
</staticText>
</band>
<band height="12">
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="53" y="0" width="169" height="12" isPrintWhenDetailOverflows="true" uuid="429e5e34-e7cc-4b2e-a7ec-f9bdc72268ce">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<textFieldExpression><![CDATA[$P{detailAddress}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="0" width="53" height="12" uuid="91cf76df-ccf1-417b-8ad1-73b7e1be2306"/>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[门店地址:]]></text>
</staticText>
</band>
<band height="24">
<textField isBlankWhenNull="true">
<reportElement x="53" y="12" width="169" height="12" uuid="bee0af12-b474-4e3a-8218-141663bc62f2">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<textFieldExpression><![CDATA[$P{printTime}]]></textFieldExpression>
</textField>
<textField isBlankWhenNull="true">
<reportElement x="33" y="0" width="189" height="12" uuid="0f0d0893-f0ea-4221-b89f-b7a8ab6461be">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<textFieldExpression><![CDATA[$P{contactMobile}.split(" ")[1].length() == 5 ? $P{contactMobile}.split(" ")[0] : $P{contactMobile}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="0" width="33" height="12" uuid="d007cd43-b97e-4837-badf-79a6f4fad415">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[电话:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="12" width="53" height="12" uuid="3bd289b6-ad7a-4e24-b8b0-cbf579a979f2">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[打印时间:]]></text>
</staticText>
</band>
</detail>
</jasperReport>
@@ -0,0 +1,510 @@
# 工单结算单接口文档
# 打印单最新接口参数
#### maintain接口
**接口:/print/dispatchPrint/genUrl**
**方法:post**
支持场景:
各类结算单
不支持:上海结算单
**入参:**
```plaintext
{
"pkId": "14581820313319918809",
"rowCode": "costSettlePrint",
"rowId": "12"
}
```
```plaintext
{
"code": 200,
"data": {
"url": "http://s1.f6yc.com/printserver/test/printFile/201912/191213154046701.pdf"
},
"message": "SUCCESS"
}
```
#### erp接口
**/print/getPrintPDFPath.do**
templateId=56&templateType=newSettlePrint&idSourceBill=10546443563897503197
**rest  get**
**支持各种新版打印类型**
```plaintext
{
"code": 200,
"data": "http://s1.f6yc.com/printserver/test/printFile/201912/191213154046701.pdf",
"message": "SUCCESS"
}
```
#### jasper取参对照
```plaintext
{
"data": {
"cellPhone": "15421562365", //联系电话
"naCustomer": "0322新", //单位名称
"repairPerson": "", //送修人
"carOwnerName":"", // 车辆所有人
"accountNumber": "", //账号
"billNo": "GD20190517001", //工单号
"carNoWhole": "苏1542", //车牌号
""
"carColor":"" //车身颜色
"orgDetailAddress": "江苏省盐城市阜宁县豆豆",//联系地址
"vin": "11111111111111111", //车辆VIN码
"naEmployee": "员工1(旧1)", //服务顾问
"billDate": "2019-05-17 11:49",//进厂日期
"businessTypeName": "维修", //维修类别
"deliveryTime": "2019-05-17 12:49",//交车时间(出厂时间)
"email": "126544@qq.com", //组织邮件
"maintainType": "GD", //工单类型
"billStatus":"6300", //单据状态
"orgContactMobile": "15315256232", //联系电话
"memo": "", //工单备注
"orgMemo":"", // 门店备注
"carMemo":"", // 车辆备注
"printCount": "1", //打印次数
"orgContactNumber": "", //联系电话-承修方信息
"carSeriesName": "商用车", //车系名称
"carBrandName": "商用车", //品牌名称
"balanceStatus": "7000", //结算状态
"printTime": "2019-07-26 11:43:48",//打印时间
"firstSettlementTime":"2019-07-26 11:43:48",//结算时间(第一次收款时间)
"orgName": "新公司测试", //单位名称-承修方信息
"abbreviation": "门店简称", //门店简称
"engineNumber": "123", //发动机号
"transmissionNo": "123", //变速箱号
"creationtime": "2019-05-17 11:50:46.0",//创建时间
"creatorName": "员工1(旧1)", //创建人名称
"employeePhone":"18734033191", //服务顾问手机号
"paymentTypeDetails":"记账", //支付方式(记账公司)汇总
"bankAccount": "", //开户银行
"naInsurer":"", //理赔公司名称
"insurancepolicyNo":"", //理赔单理赔保险单号
"mergePackageContent": "1", //套餐合并标识
"totalStuffNum": 1.0, //材料数量合计
"selfTotalStuffNum": 0.0, //自带材料数量合计
"serviceNum": "1", //维修项目小计
"spreadRate": 0.0, //进销差价率
"managementCost": 0.0, //进销差价合计
"amountAll": 160.0, //应收总计
"serviceDisCountSubTotal": 100.0, //项目折后金额合计
"stuffSubtotalAll": 60.0, //材料费小计
"serviceSubtotalAll": 100.0, //工时费小计
"receiptAmount": 160.0, //实收金额
"chineseAmount": "壹佰陆拾元整", //实收金额(大写)
"oweAmount": 160.0, //未收金额
"remainAmount":1.0, //结算金额tsf
"receivedAmount":1.0, //收款金额tsf
"settleOweAmout":1.0, //结算单中用的待付金额(未收)
"settleOweAmoutChinese":"壹", //待付金额大写
"settleReceivedAmout":1.0, //实付金额
"settleReceivedAmoutChinese":1.0, //实付金额大写
"totalWorkHour": 1.0, //项目工时合计
"serviceFavourableTotal": 52.0, //项目优惠金额合计
"serviceFavourableCommonTotal": 52.0,//普通项目优惠金额合计
"stuffSubtotalAll": 532.0, //材料费小计
"partFavourableTotal": 102.0, //材料优惠金额合计
"partFavourableCommonTotal": 92.0, //普通材料优惠金额合计
"stuffDisCountTotal": 60.0, //材料折后金额合计
"extraCostTotal": 0.0, //附加费小计
"allOtherCost": 0.0, //附加费合计应收
"packageFavourable": 0.0, //套餐优惠
"czkExpense": 0.0, //储值卡消费金额
"vipExpense": 0.0, //会员卡消费金额
"czkExpenseFavourable": 0.0, //储值卡优惠金额
"czkDiscountFavourable": 0.0, //储值卡办卡优惠金额
"vipExpenseFavourable": 0.0, //计次卡/套餐卡优惠金额
"partinfoDiscountFavourable": 0.0, //材料折扣优惠
"partinfoFavourable": 0.0, //材料项目(非会员项目)客户等级优惠
"couponFavourable": 0.0, //优惠券优惠
"pointFavourable": 0.0, //积分优惠
"discountFavourable": 0.0, //结算时设置的结清优惠
"gatheringFavourable": 0.0, //收银时设置的收银优惠
"customerLevelFavourable": 0.0, //客户级别优惠金额
"serviceFavourable": 0.0, //服务项目(非会员项目)客户等级优惠
"disCountAll": 0.0, //总优惠合计
"disCountAllBak":0.0, //总优惠合计bak
"printContent": "", //免责条款
"printContentEntrust":"", //委托单免责条款
"mainCostList": [ //维修结算费用集合
{
"subtotal": 60.0, //价格
"sortNumber": "1", //序号
"costName": "材料费" //名称
},
{
"subtotal": 100.0,
"sortNumber": "2",
"costName": "工时费"
},
{
"subtotal": 160.0,
"sortNumber": "3",
"costName": "合计"
}
],
"partList": [ //工单对应配件材料集合
{
"unit": "个", //单位
"isBring": 0, //是否自带,1表示自带,0表示非自带
"discountedSubtotal": 60.0, //折后金额
"price": 60.0, //价格
"partName": "分全", //材料名称
"number": 1.0, //数量
"subtotal": 60.0, //金额
"taxRateOutput": 0.13, //销项税率
"singleFavourable":0.0, //优惠金额
"partBrand":"", //配件品牌
"spec":"", //规格型号
"supplierCode":"", //供应商编码(零件号)
"customCode":"", //材料编码
"isMember":0, //是否是会员卡材料 1是
"discount":0.5, //折扣
"partMemo":"", //备注
"employeeName":"", //员工名称(维修技师)
"cargoSpace":"", //货位
"outStockEmployeeName":"", //领料人
"sortNumber": "1" //序号
}
],
"serviceList": [ //工单对应项目集合
{
"discountedSubtotal": 100.0,//折后金额
"price": 100.0, //工时单价
"workHour": 1.0, //工时
"subtotal": 100.0, //金额
"taxRateOutput": 0.13, //销项税率
"sortNumber": "1", //序号
"discount":1, //折扣
"singleFavourable":0.0, //优惠金额
"isMember":1, //是否是会员卡项目 1是 0否
"empNameStr":"", //修理工
"unusedNumber":1, //会员卡项目-未使用次数
"number":2, //会员卡项目-总次数
"infiniteFlag":1, //是否无限,0:否,1:是
"serviceName": "0322新" //项目名称
}
],
"cardList": [ //会员卡列表
{
"favourable": 20, //优惠
"amount": 2019799, //余额
"consumeAmount":10, //本次消费金额
"memberCardNo": "123232rg",//卡号
"name": "测试洗车卡项目2" //卡名称
}
],
"czkList": [ //储值卡列表
{
"favourable": 20, //优惠
"amount": 2019799, //余额
"consumeAmount":10, //本次消费金额
"memberCardNo": "123232rg",//卡号
"name": "测试洗车卡项目2" //卡名称
}
],
"extraPrintVo": { //附加项目
"processItemName": "加工", //加工条目名称
"checkCustomName": "检测费", //检测费名称
"diagnosisCustomName": "诊断费", //诊断费名称
"diagnosisItemName": "维修诊断", //维修诊断项目
"diagnosisMemo": "", //诊断费备注
"diagnosisCost": 0.0, //诊断费
"commissionCustomName": "代办费", //代办费名称
"managementCustomName": "管理费", //管理费名称
"commissionMemo": "", //代办费备注
"processMemo": "", //加工费
"commissionCost": 0.0, //代办费
"checkCost": 0.0, //检测费
"managementCost": 0.0, //管理费
"processCustomName": "加工费", //加工费名称
"processCost": 0.0, //加工费
"managementMemo": "", //管理费备注
"checkMemo": "" //检测费备注
}
},
"storeId": 25965086392720693,
"tempId": 123
}
```
#### 参数说明(完整)
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| \------基础信息 | | |
| **billNo** | String | _工单号_ |
| **maintainType** | String | _工单类型_ |
| **balanceStatus** | String | _结算状态_<br/>_"7000" -- 未结算(即存在待付金额)_<br/>_"7100" -- 已结算_<br/>_"7200"  -- 部分结算_ |
| **creatorName** | String | _创建人名称_ |
| **naEmployee** | String | _服务顾问_ |
| **spreadRate** | Double | _进销差价率_ |
| carCategoryName | String | _客户车辆分类名称_ |
| **creationtime** | String | _创建时间_ |
| **memo** | String | _工单备注_ |
| **printCount** | String | _打印次数_ |
| **printTime** | String | _打印时间_ |
| **deliveryTime** | String | _交车时间(出厂时间),收款取收款时间,未收款完工的取完工时间,未完工的取预计交车时间_ |
| **mergePackageContent** | String | _套餐合并标识_ |
| **printContent** | String | _免责条款_ |
| **engineNumber** | String | _发动机号_ |
| **transmissionNo** | String | 变速箱号 |
| **printMaintainGuaZi** | String | _guazi标识_ |
| **amountAll** | Double | _应收总计_ |
| **disCountAll** | Double | _总优惠合计_ |
| **oweAmount** | Double | _未收金额_ |
| **vipExpense** | Double | _会员卡消费金额_ |
| **vipExpenseFavourable** | Double | _计次卡/套餐卡优惠金额_ |
| **czkExpense** | Double | _储值卡消费金额_ |
| **czkExpenseFavourable** | Double | _储值卡优惠金额_ |
| **czkSettleFavourable** | Double | _储值卡结算优惠金额(仅未收款返回)_ |
| **accountAmount** | Double | 记账金额 |
| **totalOweAmount** | Double | _未付金额_ |
| **serviceFavourable** | Double | _服务项目(非会员项目)客户等级优惠_ |
| **partinfoFavourable** | Double | _材料项目(非会员项目)客户等级优惠_ |
| **partinfoDiscountFavourable** | Double | _材料折扣优惠_ |
| **pointFavourable** | Double | _积分优惠_ |
| **packageFavourable** | Double | _套餐优惠_ |
| **discountFavourable** | Double | _结算时设置的结清优惠_ |
| **gatheringFavourable** | Double | _收银时设置的收银优惠_ |
| **couponFavourable** | Double | _优惠券优惠_ |
| **customerLevelFavourable** | Double | _客户级别优惠金额_ |
| **customerLevelName** | String | _客户级别_ |
| **customerDetailAddress** | String | 客户详细地址 |
| **channelName** | String | 来店途径名称 |
| **receiptMemo** | String | 收银备注 |
| **customerSourceName** | String | _客户来源名称_ |
| **carSourceName** | String | _车辆来源名称_ |
| \-----托修方信息 | | |
| **naCustomer** | String | _单位名称/托修方_ |
| **repairPerson** | String | _送修人_ |
| **carNoWhole** | String | _车牌号整体_ |
| **carBrandName** | String | _品牌名称_ |
| **carSeriesName** | String | _车系名称_ |
| carMemo | String | 车辆备注 |
| **businessTypeName** | String | _维修类别_ |
| **vin** | String | _车辆VIN码_ |
| carFuelTypeNameOriginal | carFuelTypeNameOriginal | 燃料类型 |
| **billDate** | String | _进厂日期_ |
| **mileage** | java.math.BigDecimal | _出厂里程_ |
| **contractNumber** | | _合同编号(空)_ |
| **certificateNumber** | | _合格证号(空)_ |
| **cellPhone** | String | _联系电话_ |
| **email** | String | _组织邮件_ |
| \------承修方信息 | | |
| **orgName** | String | _单位名称_ |
| **abbreviation** | String | 门店简称 |
| **orgContactNumber** | String | _联系电话_ |
| **orgDetailAddress** | String | _联系地址_ |
| **orgContactMobile** | String | _联系电话_ |
| **bankAccount** | String | _开户银行_ |
| **accountNumber** | String | _账号_ |
| **businessLicenseCode** | String | 营业执照编码 |
| \------项目信息 | | |
| serviceList | array | 项目条目 |
| #### orderNumber | String | 序号(验证可用) |
| sortNumber | String | _序号_ |
| customCode | String | 项目编码 |
| serviceName | String | _项目名称_ |
| **labelName** | String | _业务分类名称_ |
| **nameMember** | String | _会员项目的来源名_ |
| **labelName** | String | 业务分类 |
| price | Double | _工时单价_ |
| workHour | Double | _工时_ |
| subtotal | Double | _金额_ |
| ```plaintext<br> taxRateOutput <br>``` | BigDecimal | _销项税率_ |
| ```plaintext<br> singleFavourable <br>``` | Double | 优惠金额 |
| discountedSubtotal | Double | _折后金额_ |
| **serviceMemo** | String | _单据服务项目备注_ |
| discount | Double | 折扣 |
| unusedNumber | Integer | 会员卡项目-未使用次数 |
| number | Integer | 会员卡项目-总次数 |
| ```plaintext<br>favourableVoList <br>``` | List<FavourableDetailPrintVo> | 优惠明细 |
| ```plaintext<br>discountType <br>``` | Integer | 优惠类型(编码) |
| ```plaintext<br>discountTypeName <br>``` | String | 优惠类型名称 |
| amount | Double | 优惠金额 |
| ```plaintext<br>sourceId <br>``` | String | 优惠项目的主键,如:如果优惠项是优惠券,那么该字段为优惠券的id |
| **memo** | String | 项目说明 |
| **qualityCheckEmployeeName** | String | 质检人姓名 |
| **qualityCheckEmployeeCode** | String | 质检人工号 |
| **cooperationMemo** | String | 协作备注 |
| **totalWorkHour** | Double | _项目工时合计_ |
| **serviceSubtotalAll** | Double | _工时费小计_ |
| **serviceNum** | Double | _维修项目小计_ |
| **serviceDisCountSubTotal** | Double | _项目折后金额合计_ |
| \-------材料信息 | | |
| partList | array | 材料条目 |
| #### orderNumber | String | 序号(验证可用) |
| sortNumber | String | _序号_ |
| customCode | String | 材料编码 |
| partName | String | _材料名称_ |
| **partBrand** | String | _配件品牌_ |
| spec | String | 规格型号 |
| standard | String | 规格型号(旧) |
| **partShowName** | String | _配件名称规格型号品牌_ |
| **supplierCode** | String | _供应商编码(零件号)_ |
| unit | String | _单位_ |
| number | Double | _数量_ |
| **nameMember** | String | _会员项目的来源名_ |
| price | Double | _价格_ |
| cost | Double | _材料成本_ |
| subtotal | Double | _退货金额_ |
| taxRateOutput | BigDecimal | _销项税率_ |
| singleFavourable | Double | 优惠金额 |
| discountedSubtotal | Double | _折后金额_ |
| **partMemo** | String | _单据服务材料备注_ |
| discount | Double | 折扣 |
| **applyModel** | String | 适用车型 |
| ```plaintext<br>cargoSpace <br>``` | String | 材料货位 |
| **defSeats** | List<String> | 材料货位列表 |
| ```plaintext<br>favourableVoList <br>``` | List<FavourableDetailPrintVo> | 优惠明细 |
| ```plaintext<br>discountType <br>``` | Integer | 优惠类型(编码) |
| ```plaintext<br>discountTypeName <br>``` | String | 优惠类型名称 |
| ```plaintext<br>amount <br>``` | Double | 优惠金额 |
| ```plaintext<br>sourceId <br>``` | String | 优惠项目的主键,如:如果优惠项是优惠券,那么该字段为优惠券的id |
| \-------自带材料(新版维修/贴膜)信息 | | |
| **bringPartList** | array | 自带材料条目 |
| **partShowName** | String | 自带材料名称(文本) |
| **photoList** | List<String> | 自带图片路径(url |
| empNameStr | String | 技师 |
| outStockEmployeeName | String | 领料人 |
| **isBring** | String | _是否自带_ |
| **selfPartList** | array | _工单对应配件自带材料集合(内容同上面材料)_ |
| **totalStuffNum** | String | _材料数量合计_ |
| **selfTotalStuffNum** | String | _自带材料数量合计_ |
| **stuffSubtotalAll** | Double | _材料费小计_ |
| **partFavourableTotal** | Double | _材料优惠金额合计_ |
| **partFavourableCommonTotal** | Double | _普通材料优惠金额合计_ |
| **stuffDisCountTotal** | Double | _材料折后金额合计_ |
| **managementCost** | Double | _进销差价合计_ |
| \------附加费用信息 | | |
| **extraChargeList** | | |
| **sortNumber** | String | _序号_ |
| **extraName** | String | _附加费名称_ |
| **subtotal** | Double | _金额_ |
| **memo** | String | _备注_ |
| \-----维修结算费用集合(江苏结算单) | | |
| **mainCostList** | array | |
| **sortNumber** | String | _序号_ |
| **costName** | String | _名称_ |
| **memo** | String | _备注_ |
| **subtotal** | Double | _金额_ |
| \----_其他结算费用集合(江苏结算单)_ | | |
| **otherCostList** | array | 内容同上 维修结算费用集合 |
| **extraCostTotal** | Double | _附加费小计_ |
| **allOtherCost** | Double | _附加费合计应收_ |
| **favourableExtraCost** | Double | _附加费优惠金额_ |
| **receiptAmount** | Double | _实收金额(已收金额-储值卡金额,如未收款,则还加入了欠款金额+客户等级优惠-积分优惠-结清优惠)_ |
| **receiptAmountChinese** | String | _实收金额大写(逻辑同上)_ |
| **amountReal** | Double | 工单收款后真正的实收 |
| **chineseAmount** | String | _实收金额(大写)(逻辑同上)_ |
| **payItemTogetherChinese** | String | _付款方式总额大写_ |
| **payItemTogetherExcludeAccountAmountChinese** | String | 付款总额(排除记账金额)大写 |
| **settleOweAmout** | Double | 结算单中用的待付金额(未收),使用后台逻辑算好 |
| **settleOweAmoutChinese** | String | 待付金额大写 |
| \-----结算付款方式及优惠保存信息 | array | |
| **settlementPayItemList** | | |
| **payWay** | String | _付款方式_ |
| **payAmount** | Double | _付款金额_ |
| **chinesePayAmount** | String | _大写付款方式_ |
| **accountAgreementName** | String | _记账客户名称_ |
| \-----付款方式信息 | array | |
| **payItemList** | | |
| **payWay** | String | _付款方式_ |
| **payAmount** | Double | _付款金额_ |
| **chinesePayAmount** | String | _大写付款方式_ |
| \-----附加项目(江苏结算单) | | |
| **extraPrintVo** | obj | |
| **commissionCustomName** | String | _代办费自定义名称_ |
| **commissionCost** | Double | _代办费成本_ |
| **commissionMemo** | String | _代办费备注_ |
| **diagnosisCustomName** | String | _诊断费自定义名称_ |
| **diagnosisCost** | Double | _诊断费成本_ |
| **diagnosisItemName** | String | _诊断详细名称_ |
| **diagnosisMemo** | String | _诊断费备注_ |
| **checkCustomName** | String | _检查费自定义名称_ |
| **checkCost** | Double | _检查费成本_ |
| **checkMemo** | String | _检查费备注_ |
| **processCustomName** | String | _加工费自定义名称_ |
| **processCost** | Double | _加工费成本_ |
| **processMemo** | String | _加工费备注_ |
| **processItemName** | String | _加工详细名称_ |
| **managementCustomName** | String | _管理费自定义名称_ |
| **managementCost** | Double | _管理费成本_ |
| **managementMemo** | String | _管理费备注_ |
| \-----二期新增字段 | | |
| **oilCapacity** | String | 油量 |
| **nextMileage** | Double | _下次保养里程(工单数据源,目前维保、洗车单读取)_ |
| **nextMaintainDate** | String | _下次保养日期(工单数据源,目前维保、洗车单读取)_ |
| **nextMileageRemind** | Double | _下次服务里程(服务提醒数据源,目前维修、贴膜单读取)_ |
| **nextMaintainDateRemind** | Long | _下次服务时间(服务提醒数据源,目前维修、贴膜单读取)_ |
| **repairPersonContact** | String | _送修人联系方式_ |
| **memberCardNo** | String | _会员号_ |
| **points** | String | _积分_ |
| **czkList** | array | 储值卡列表 |
| **name** | String | 名称 |
| **memberCardNo** | String | 卡号 |
| ```plaintext<br> **cardOwner** <br>``` | String | 持卡人 |
| **amount** | Double | 金额 |
| **cardList** | array | 套餐卡列表 |
| **name** | String | 名称 |
| **memberCardNo** | String | 卡号 |
| ```plaintext<br> **cardOwner** <br>``` | String | 持卡人 |
| **amount** | Double | 金额 |
| **combineServiceAndPartList** | array | 项目材料组合列表 |
| **servicePrintVo** | 参见serviceList | |
| **partPrintVo** | 参见partList | |
# 案例记录:
#### 1.结算前 待付 结算后实付(跟进结算状态判断,7100 为已结算)
```plaintext
$P{balanceStatus}.equals("7100")?($P{amountAll}.subtract($P{vipExpense}).subtract($P{czkExpense}).subtract($P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable})).setScale( 2, BigDecimal.ROUND_HALF_EVEN ).toString()+($P{payItemTogether}==null?"":"("+$P{payItemTogether}+")")):($P{amountAll}.subtract($P{vipExpense}).subtract($P{czkExpense}).subtract($P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable})).setScale( 2, BigDecimal.ROUND_HALF_EVEN ))
```
* 对应的汉字
```plaintext
$P{balanceStatus}.equals("7100")?$P{payItemTogetherChinese}:$P{settleOweAmoutChinese}
```
# 工具类jar包附件下载:
[请至钉钉文档查看附件《print-core-1.0.7.jar》](https://alidocs.dingtalk.com/i/nodes/vy20BglGWOexYpophlEGoZvGJA7depqY?iframeQuery=anchorId%3DX02mjljl3qzo6fk6o7712b)
### 数字金额转中文方法调用示例:
**数字金额**$P{amount}==null?BigDecimal.ZERO:$P{amount}
**转中文****com.f6car.printserver.core.CharacterUtil.chinese(**$P{amount}==null?BigDecimal.ZERO:$P{amount})
### 日期时间戳转日期示例:
**日期格式选择:**java.lang.Long
**日期时间戳**$P{nextMaintainDateRemind} **转为目标格式**$P{nextMaintainDateRemind} == null ? "" : new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date($P{nextMaintainDateRemind}))
其中,"yyyy-MM-dd HH:mm:ss" 根据实际需求指定,比如到日则选择 "yyyy-MM-dd"
### 洗车单
洗车单小票不支持定制,定制洗车单的模板名称必须包含“洗车单”三个字  ,否则无法显示对应模板
> 更新: 2025-02-24 17:08:50 原文: <https://xcz.yuque.com/ombipo/rpc7ms/ro5fs1>
@@ -0,0 +1,262 @@
# 打印单定制介绍
# 打印单定制介绍
| **最常用的基础操作** |
| --- |
| [1、新增静态文本](#jwTdk) |
| [2、新增动态字段(例如想要展示结算单中的工单号)](#fKwpz) |
| [3、列表中增减字段(例如结算单中的项目列表和材料列表)](#IUyOs) |
| [4、新增边框和样式设置](#RKqIf) |
| [6、保存+输出](#M31IF) |
| [7、打印单后台配置](#gr6xz) |
## 常用链接地址:
###### 打印单后台地址
[http://print.f6yc.com/print-server/ui/index.html#/template/classification](http://print.f6yc.com/print-server/ui/index.html#/template/classification)
###### 打印单模板样式
[https://xcz.yuque.com/ombipo/rpc7ms/fbd6ay?singleDoc#](https://xcz.yuque.com/ombipo/rpc7ms/fbd6ay?singleDoc#) 《打印单各类模板样式》
###### 打印单参数表
[https://xcz.yuque.com/ombipo/rpc7ms/ro5fs1?singleDoc#](https://xcz.yuque.com/ombipo/rpc7ms/ro5fs1?singleDoc#) 《打印单最新接口参数》
###### 打印单工具简易开发教程(附带案例)
[《打印单定制简易开发教程》](https://alidocs.dingtalk.com/i/nodes/dQPGYqjpJYgZGbvbCdEKGDGZWakx1Z5N?utm_scene=team_space)
## 工具下载
jdk1.8 使用 jaspersoft6.8版本
[请至钉钉文档查看附件《Jaspersoft Studio-6.8.0.zip》](https://alidocs.dingtalk.com/i/nodes/20eMKjyp81R0ndXdsdYe4BaDWxAZB1Gv?corpId=&iframeQuery=anchorId%3DX02mgkgykvfbfbiqcbc8b4)
WIN
[请至钉钉文档查看附件《Jaspersoft Studio-6.3.1.final.rar》](https://alidocs.dingtalk.com/i/nodes/20eMKjyp81R0ndXdsdYe4BaDWxAZB1Gv?corpId=&iframeQuery=anchorId%3DX02mki20wvdslzwftp0ao)
MAC
[请至钉钉文档查看附件《TIBCOJaspersoftStudio-6.3.1.final-mac-x86\_64.zip》](https://alidocs.dingtalk.com/i/nodes/20eMKjyp81R0ndXdsdYe4BaDWxAZB1Gv?corpId=&iframeQuery=anchorId%3DX02mki26xyvtjfkmi00ki)
## 打印单模板修改流程
#### 1、下载需要的模板
###### 通过模板名称,直接到模板管理中通过模板名称查询并下载
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715248827418-8d2597e8-0ded-4cb8-9825-202074613dcc.png)
#### 2、打开编辑工具 TIBCO Jaspersoft Studio
###### 打开文件夹,双击Jaspersoft Studio.exe 运行工具
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715304945610-6a9086af-c9f5-4b88-8c6b-1ef6bc11e8a0.png)
###### 点击File-->Open File-->选择下载的模板文件
###### ![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715305042360-e716b275-c696-4231-b0c8-99c0566fe7c5.png)进入编辑模板的页面
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715305302215-a5f09700-28cc-4e80-a78c-e919b831e857.png)
#### 3、常见编辑操作
##### 1.新增静态文本
###### 新增组件到模板中
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715305970916-23aa4144-a215-4656-8281-0c3385aba707.png)
###### 双击组件编辑显示文本
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306012526-86e4bc9d-73dc-4120-abbf-fbedcf99c47f.png)
###### 调整组件大小和位置参数
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306133042-c7c17a7c-138c-4b4c-b587-11782185e910.png)
##### 2.新增动态字段(例如想要展示结算单中的工单号)
###### 在参数表中搜索想要的参数名称和类型:名称是:billNo  类型是文本信息=java.lang.String
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306460522-232a3499-d104-470d-be1a-0b1fb0957331.png)
###### 拖拽一个 Text Field 组件到模板中
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306292127-d6629324-8ef8-4ed8-a35e-a77d543bac2d.png)
###### 双击组件写入公式固定写法$P{参数名称}
###### ![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306617239-b7ce26f3-c520-4266-83c2-8b18196e2ea9.png)
###### 遇到提示:The current expression is not valid. Please verify it!;表明这个参数在模板中没有预先创建,需要手动创建参数信息
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306763081-4495ab86-c432-455f-b06b-7b15fb0b8a3e.png)
###### 模板中预设参数信息:Outline-->Parameters(右键单击)-->Create Parameter
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306875194-8d8a509a-23a8-4563-b6b7-ba6c673151e1.png)
###### 编辑参数信息:Name(填写参数名称);Class(数字就选择:java.math.BigDecimal   文本就选择java.lang.String
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715307033769-86f5efa1-d6e2-4d9a-a322-849079503f21.png)
##### 3.列表中增减字段(例如结算单中的项目列表和材料列表)
###### 例如查找材料名称,可以发现参数名是partName,是在一个名字叫partList 的列表里面的,在材料信息的列表中能使用到的参数就只有partList下的这个参数,其他参数无法在列表中直接使用(例如工单号在材料列表中展示不了)
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715307395294-6a1cf006-260b-4f9e-950f-b0f34fa9c3c9.png)
###### 双击需要编辑的列表,进入列表编辑页面
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715308008053-5abeeebb-84ad-4fb3-994b-e0a360ea96d8.png)
###### 编辑方式与新增动态字段相同,但是固定写法从$P{参数名称} 改为 $F{参数名称}
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715308039626-e11f409f-014a-4146-a578-38d2cab4d1e0.png)
###### 提示The current expression is not valid. Please verify it!  参数没有预设时,在列表编辑页面中新增,逻辑与上面相同![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715313291292-8fe511dd-64f4-4ee6-971e-f8d1f0a7dfb0.png)
##### 4.新增边框和样式设置
###### 选择需要编辑的组件,选择Boeders 进行编辑
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715308327002-0c996de2-b92b-4477-9f5d-5e68a9be188b.png)
#### 4、常见语法介绍
###### 文本拼接参数:$P{参数名称}+"特定文本内容"  —— 例如打印单标题:$P{printOrgName}+"结算单"
###### 小数保留2位小数或多位:$P{参数名称}.setScale( 保留几位小数, BigDecimal.ROUND\_DOWN ) ——例如折后金额小计,保留2位小数:$P{stuffSubtotalAll}.setScale( 2, BigDecimal.ROUND\_DOWN )
###### 字符串截取:$P{参数名称}.substring(起始位置,截取长度)——例如进厂日期,保留前10位:$P{billDate}.substring(0,10)
###### 三元运算-IF判断:(关系表达式) ? 表达式1 : 表达式2 ——例如打印单标题:($P{printOrgName}==null?$P{orgName}:($P{printOrgName}.isEmpty()?$P{orgName}:$P{printOrgName}))+"结算单"
###### 常见运算:
是否相等:”==“  或者 $P{参数名称}.equals("文本内容")
加:$P{参数名称1}.add($P{参数名称2})
减:$P{参数名称1}.subtract($P{参数名称2})
乘:$P{参数名称1}.multiply($P{参数名称2})              $P{参数名称1}.multiply(new BigDecimal(1.13))
除:$P{参数名称1}.divide($P{参数名称2}, 2, BigDecimal.ROUND_HALF_UP)
#### 4.1、高级语法介绍
###### jar包导入:例如金额转大写,研发通过编写一个jar工具包实现特定功能,下面是导入jar包步骤
![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/oJGq75kaYJrZ2lAK/img/882b929c-310e-4525-a463-15def4e3fac3.png)
![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/oJGq75kaYJrZ2lAK/img/3839a386-06ab-442a-b0e3-997afd7e0a94.png)
![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/oJGq75kaYJrZ2lAK/img/76ef341c-a67b-4f30-816d-73497eb8d703.png)
启用成功后按照研发语法实现具体功能,比如![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/oJGq75kaYJrZ2lAK/img/0d61f87d-719a-4af6-bc34-c06a2fabf5b8.png)
###### 从list中取特定的值写入外层表格中:该公式使用jdk1.8语法,jaspersoft6.8可用
下面表达式的意思是,从支付方式列表(payItemList) 中找到
支付方式(payWay) 
等于“记账”的
第一个支付金额(payAmount
```java
$P{payItemList}.getData().stream()
.filter(map -> "记账".equals(map.get("payWay")))
.map(map -> {
Object amt = map.get("payAmount");
return amt == null ? BigDecimal.ZERO : new BigDecimal(amt.toString());
})
.findFirst()
.orElse(BigDecimal.ZERO)
```
#### 5、格式预览
###### 工具中只能预览模板的样式,涉及到参数判断的需要将模板上传到门店后在F6系统工单中打印预览
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311314096-6d38326f-8c42-4e22-b8c4-59f23f1ad075.png)
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311442290-12854551-e373-40ea-b19a-c0d9fe59393b.png)
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311456159-163f97ae-fb32-46b3-8548-42fd39bc66e4.png)
#### 6、保存+输出
###### 保存
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311668513-2cf8626b-3d4f-4a8d-8a0e-71b2e1300189.png)
###### 选择.jrxml的文件,右键选择Compile Report 进行编译
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311723006-c971ca0b-90d1-4568-90f4-78861c437d62.png)
###### 选择.jasper的文件,右键选择Export Files to...
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311841522-716ed408-0069-449d-a460-4a483c724ca5.png)
###### 另存到桌面
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311906058-9ae58e93-f4d9-487d-96fa-e87450747f8c.png)
#### 7、打印单后台配置
###### 新增/编辑模板
注意:不要随便删除模板,删除一定要再三确认清楚,避免出现误操作的情况(删除不可恢复)  预计5.16号后 对删除的功能二次确认进行优化。
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715312153740-d570281c-2e63-432a-885f-2d4d703a810d.png)
###### 模板名称命名规范
* **简单调整**\*\***\*\*模板名称:基础表+特殊修改需求**
**模板编码:修改人姓名首字母英文大写+模板分类+日期**
**模板备注:模板各修改点**
![image](https://ddoc.f6yc.com/yuque/0/2024/png/245629/1715334288403-7a693418-2995-4707-a36c-87ee83a0e7f5.png)
* **定制调整**\*\***\*\*模板名称:门店名称+定制**
**模板编码:修改人姓名首字母英文大写+模板分类+日期**
**模板备注:模板各修改点**
![image](https://ddoc.f6yc.com/yuque/0/2024/png/245629/1715334472806-b911e666-83b7-4512-bdc0-d08996377303.png)
###### 给指定门店配置打印单
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715312294997-84f80cc6-8058-4dbc-988d-3681bd2ece7e.png)
#### 8、常见打印分类及对应的通用模板
| 常见打印单分类 | 对应系统上的打印模块 | 通用模板名称 | 模板编码 |
| --- | --- | --- | --- |
| 新结算单打印 | 维保单 | F6标准结算单(壹) | newSettleFirst |
| 结算单-新 | 除维保单的其他单据(维修单、贴膜单......) | 无 | 无 |
| 附表-新 | 新附表 | 无 | 无 |
| 销售单 | 销售单 | 销售单(日期版) | xiaoshodanriqiban |
| 洗车单 | 洗车单 | 洗车单 | wash01 |
| 报价单打印 | 报价单 | 报价单打印 | quotationPrint |
| 新库存入库单打印 | 入库单 | 新库存入库单打印 | 9001 |
| 新库存出库单打印 | 出库单 | 新库存出库单打印 | 9002 |
| | | | |
| **注:采购单和采购退货单走打印平台定制,先向赵亚妮提供门店编码、门店名称,开通后再上传配置门店生效** | | | |
**注意:结算单要在收款后页面打印,请确保模板名称中包含“结算单”三个字**
> 更新: 2025-05-16 13:54:24 原文: <https://xcz.yuque.com/ombipo/obbigo/kg2qc1sbszwk48bf>
@@ -0,0 +1,60 @@
# 报价单接口参数
# 报价单接口参数
# 参数说明
## 主单信息
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| partDetailVoList | 材料列表 | | List<QuotationServiceDetailPrintVo> |
| serviceDetailVoList | 项目列表 | | List<QuotationPartDetailPrintVo> |
| amountChinese | 商品金额中文大写 | | |
| realAmountWithoutCardChinese | 待付金额中文大写 | | |
## QuotationPartDetailPrintVo
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| pkId | 无意义主键 | 是 | BigInteger |
| idOwnOrg | 门店id | 是 | BigInteger |
| idQuotation | 报价单id | 是 | BigInteger |
| idPart | 材料id | 否 | BigInteger |
| partName | 材料名称(非组合,对应材料名称字段) | 是 | String |
| partShowName | 材料在界面上显示的名称(组合供应商编码等信息) | 是 | String |
| idMdmPart | 云材料ID | 否 | String |
| labelId | 业务分类id | 否 | BigInteger |
| labelName | 荣誉的业务分类名称 | 否 | String |
| number | 数量 | 是 | BigDecimal |
| price | 单价 | 是 | BigDecimal |
| subtotal | 总价 | 是 | BigDecimal |
| idEmployee | 服务员工id | 否 | String |
| employeeName | 服务员工姓名 | 否 | String |
| isMember | 套餐2,套餐卡1,普通0 | 是 | byte |
| memo | 备注 | 否 | String |
| discount | 折扣 | 是 | BigDecimal |
| realSubtotal | 折后总计 | 是 | BigDecimal |
| stockNumber | 门店库存数量 | 否 | BigDecimal |
| groupId | 公司id | 是 | BigInteger |
| unit | 单位 | 否 | String |
| spec | 规格型号 | 否 | String |
| brand | 品牌名称 | 否 | String |
| brandId | 品牌id | 否 | String |
| supplierCode | 零件号 | 否 | String |
## QuotationServiceDetailPrintVo
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| pkId | 无意义主键 | 是 | BigInteger |
| customCode | 项目编码 | 否 | String |
| serviceName | 项目名称 | 是 | String |
| workHour | 工时 | 否 | BigDecimal |
| price | 单价 | 是 | BigDecimal |
| subtotal | 工时费 | 是 | BigDecimal |
| discount | 折扣 | 是 | BigDecimal |
| realSubtotal | 折后金额 | 是 | BigDecimal |
| memo | 备注 | 否 | String |
> 更新: 2025-05-26 14:19:50 原文: <https://xcz.yuque.com/ombipo/rpc7ms/gcgxuuzvmyhs4eoe>
@@ -0,0 +1,675 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.3.1.final using JasperReports Library version 6.3.1 -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="jiesuandan" pageWidth="595" pageHeight="842" columnWidth="575" leftMargin="15" rightMargin="5" topMargin="10" bottomMargin="10" uuid="8fdb09f3-da43-46f9-a6cb-2b26a2247961">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<property name="com.jaspersoft.studio.unit." value="pixel"/>
<property name="com.jaspersoft.studio.unit.pageHeight" value="pixel"/>
<property name="com.jaspersoft.studio.unit.pageWidth" value="pixel"/>
<property name="com.jaspersoft.studio.unit.topMargin" value="pixel"/>
<property name="com.jaspersoft.studio.unit.bottomMargin" value="pixel"/>
<property name="com.jaspersoft.studio.unit.leftMargin" value="pixel"/>
<property name="com.jaspersoft.studio.unit.rightMargin" value="pixel"/>
<property name="com.jaspersoft.studio.unit.columnWidth" value="pixel"/>
<property name="com.jaspersoft.studio.unit.columnSpacing" value="pixel"/>
<style name="Table_TH" mode="Opaque" backcolor="#FFFFFF">
<box>
<pen lineWidth="0.5" lineColor="#000000"/>
<topPen lineWidth="0.5" lineColor="#000000"/>
<leftPen lineWidth="0.5" lineColor="#000000"/>
<bottomPen lineWidth="0.5" lineColor="#000000"/>
<rightPen lineWidth="0.5" lineColor="#000000"/>
</box>
</style>
<style name="Table_CH" mode="Opaque" backcolor="#FFFFFF">
<box>
<pen lineWidth="0.5" lineColor="#000000"/>
<topPen lineWidth="0.5" lineColor="#000000"/>
<leftPen lineWidth="0.5" lineColor="#000000"/>
<bottomPen lineWidth="0.5" lineColor="#000000"/>
<rightPen lineWidth="0.5" lineColor="#000000"/>
</box>
</style>
<style name="Table_TD" mode="Opaque" backcolor="#FFFFFF">
<box>
<pen lineWidth="0.5" lineColor="#000000"/>
<topPen lineWidth="0.5" lineColor="#000000"/>
<leftPen lineWidth="0.5" lineColor="#000000"/>
<bottomPen lineWidth="0.5" lineColor="#000000"/>
<rightPen lineWidth="0.5" lineColor="#000000"/>
</box>
</style>
<subDataset name="Dataset1" uuid="22e86b94-acb8-45ed-960f-04558f91ad82">
<queryString>
<![CDATA[]]>
</queryString>
<field name="row1" class="java.lang.String"/>
<field name="row2" class="java.lang.String"/>
<field name="row3" class="java.lang.String">
<fieldDescription><![CDATA[]]></fieldDescription>
</field>
<field name="row4" class="java.lang.String"/>
<field name="row5" class="java.lang.String"/>
<field name="row6" class="java.lang.String"/>
<field name="row7" class="java.lang.String"/>
<field name="row8" class="java.lang.String"/>
<field name="row9" class="java.lang.String"/>
<field name="row10" class="java.lang.String"/>
<field name="row11" class="java.lang.String"/>
<field name="row12" class="java.lang.String"/>
<field name="row13" class="java.lang.String"/>
<field name="row14" class="java.lang.String"/>
</subDataset>
<parameter name="orgName" class="java.lang.String"/>
<parameter name="gatheringTime" class="java.lang.String"/>
<parameter name="title" class="java.lang.String"/>
<parameter name="printTime" class="java.lang.String"/>
<parameter name="rowList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="column1" class="java.lang.String"/>
<parameter name="column2" class="java.lang.String"/>
<parameter name="column3" class="java.lang.String"/>
<parameter name="column4" class="java.lang.String"/>
<parameter name="column5" class="java.lang.String"/>
<parameter name="column6" class="java.lang.String"/>
<parameter name="column7" class="java.lang.String"/>
<parameter name="column8" class="java.lang.String"/>
<parameter name="column9" class="java.lang.String"/>
<parameter name="column10" class="java.lang.String"/>
<parameter name="column11" class="java.lang.String"/>
<parameter name="column12" class="java.lang.String"/>
<parameter name="column13" class="java.lang.String"/>
<queryString>
<![CDATA[]]>
</queryString>
<detail>
<band height="92">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="-10" width="575" height="84" uuid="7ddf9306-7451-4ea4-bd57-cee86f8e13c9">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="16"/>
</textElement>
<textFieldExpression><![CDATA[$P{title}]]></textFieldExpression>
</textField>
<staticText>
<reportElement positionType="Float" mode="Opaque" x="1" y="74" width="48" height="18" backcolor="#FFFFFF" uuid="5da51596-6830-44c5-98e4-691007be1a4e">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[所属门店:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="49" y="74" width="526" height="18" uuid="954bf3e6-7bf3-4deb-becf-217784bdc82a">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{orgName}]]></textFieldExpression>
</textField>
</band>
<band height="25">
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="260" y="0" width="315" height="18" uuid="36caab98-d260-4527-b87e-568ac54f61eb">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="0" rightIndent="5"/>
</textElement>
<textFieldExpression><![CDATA["收款时间:"+$P{gatheringTime}]]></textFieldExpression>
</textField>
</band>
<band height="36">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="46" y="0" width="44" height="36" backcolor="#D4D4D4" uuid="b7ced69b-d8a8-45d7-b05b-3a3b83324d93">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{column2}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column2}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="90" y="0" width="44" height="36" backcolor="#D4D4D4" uuid="c53a49f2-2d34-455e-b62d-b249f1fced35">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{column3}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column3}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="134" y="0" width="44" height="36" backcolor="#D4D4D4" uuid="3385087d-7234-411d-8d79-738cac0295fd">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{column4}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column4}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="266" y="0" width="44" height="36" backcolor="#D4D4D4" uuid="ccde0db9-971e-4912-9249-27361902f1b9">
<printWhenExpression><![CDATA[$P{column7}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column7}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="310" y="0" width="44" height="36" backcolor="#D4D4D4" uuid="0ae42faa-c6a8-47d6-9620-48ec03fd35d0">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{column8}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column8}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="178" y="0" width="44" height="36" backcolor="#D4D4D4" uuid="62d27b1c-bebc-4a97-8fa0-2a6a497ab35e">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{column5}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column5}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="222" y="0" width="44" height="36" backcolor="#D4D4D4" uuid="e459b7e6-0136-4c4e-ac76-6a4894311481">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{column6}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column6}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="442" y="0" width="44" height="36" backcolor="#D4D4D4" uuid="dbb18e36-2d02-4970-9440-c2d8634e5c39">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{column11}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column11}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="398" y="0" width="44" height="36" backcolor="#D4D4D4" uuid="49242b96-56f8-4c62-85f6-87a0342c936d">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{column10}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column10}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="486" y="0" width="44" height="36" backcolor="#D4D4D4" uuid="900e6d7d-dc55-4f19-8c11-3ad4429f66ac">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{column12}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column12}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="354" y="0" width="44" height="36" backcolor="#D4D4D4" uuid="e0da6daf-273a-4d28-b333-7c91e68a3ee6">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{column9}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column9}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="530" y="0" width="45" height="36" backcolor="#D4D4D4" uuid="ad9f6d64-65f9-4dcd-8dbc-37e5899c528a">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{column13}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$P{column13}]]></textFieldExpression>
</textField>
<staticText>
<reportElement mode="Opaque" x="1" y="0" width="45" height="36" backcolor="#D4D4D4" uuid="162abd58-679f-4c66-a256-458805e89193">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<text><![CDATA[]]></text>
</staticText>
<staticText>
<reportElement x="16" y="0" width="30" height="18" uuid="652d5210-41df-4c62-a4ba-97f2e42b65ac">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<text><![CDATA[内容]]></text>
</staticText>
<staticText>
<reportElement x="1" y="18" width="30" height="18" uuid="6e239a7c-f268-4ba1-b26d-f59cea6bb5ff">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<text><![CDATA[方式]]></text>
</staticText>
<line>
<reportElement mode="Transparent" x="1" y="0" width="45" height="36" backcolor="#D9D9D9" uuid="b02cf093-98f9-4cb6-8bc2-d6611a303b4e"/>
<graphicElement>
<pen lineWidth="0.5"/>
</graphicElement>
</line>
</band>
<band height="19">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<componentElement>
<reportElement isPrintRepeatedValues="false" x="1" y="0" width="574" height="18" uuid="23ae66f5-6e0a-4c24-9fa5-2237d31a1aea">
<property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
<property name="com.jaspersoft.studio.table.style.table_header" value="Table 1_TH"/>
<property name="com.jaspersoft.studio.table.style.column_header" value="Table 1_CH"/>
<property name="com.jaspersoft.studio.table.style.detail" value="Table 1_TD"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd">
<datasetRun subDataset="Dataset1" uuid="f221c952-cad7-4dff-9cf7-ca32d2ddef3b">
<dataSourceExpression><![CDATA[$P{rowList}]]></dataSourceExpression>
</datasetRun>
<jr:column width="45" uuid="9627a917-c5b0-4b80-b21f-449e4a1af32a">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column1"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="0" y="0" width="45" height="18" backcolor="#D4D4D4" uuid="05c9f6b2-819e-415f-8505-e8be2889cab5">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$F{row1}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA[$F{row1}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="44" uuid="cd30f1df-f54b-42a6-bf80-fcecb57824dc">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column2"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="44" height="18" uuid="fe6de99c-5be1-4d46-9282-c4d77f126dc9">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<printWhenExpression><![CDATA[$F{row2}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row2}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="44" uuid="badfb61b-4c3d-443e-9d96-ce2f8871c148">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column3"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="44" height="18" uuid="bbc0c8d1-ff73-4d70-bfd9-a3f514bf894b">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$F{row3}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row3}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="44" uuid="e54a7626-9237-4d3e-bb62-d48c41475cfd">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column4"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="44" height="18" uuid="8b267259-7609-4612-b6f2-417498e8409f">
<printWhenExpression><![CDATA[$F{row4}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row4}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="44" uuid="1b3cbfb0-6d88-4b14-9215-cedc2a5b9c5e">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column5"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="44" height="18" uuid="7f6b00bd-a8ce-42ca-94e3-0f9202796b7d">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$F{row5}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row5}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="44" uuid="867931a4-9fef-46d7-85df-5d77f61bc7a9">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column6"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="44" height="18" uuid="e38c4d71-5e05-43c6-9d85-cbbdc4b2859b">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$F{row6}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row6}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="44" uuid="807004cb-f00a-4852-bc11-2a5ad1ae65b5">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column7"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="44" height="18" uuid="5cb940bc-d863-486d-a8e2-730002019abb">
<printWhenExpression><![CDATA[$F{row7}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row7}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="44" uuid="baa16adc-a1b3-4e93-a6ee-5f5b705fd72d">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column8"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="44" height="18" uuid="f66b1d3d-3144-4bea-b57a-571187c122f3">
<printWhenExpression><![CDATA[$F{row8}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row8}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="44" uuid="bee2d403-9d56-4d85-8687-2c4101cc6d06">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column9"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="44" height="18" uuid="9e46c6f4-841b-467d-97f1-0542179f7cc6">
<printWhenExpression><![CDATA[$F{row9}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row9}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="44" uuid="fd8e7505-d366-4fde-96ac-1df719f7272e">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column10"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="44" height="18" uuid="f0783afb-cce2-47d8-be32-55ab498515a6">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$F{row10}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row10}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="44" uuid="53cbd27d-048c-4200-8dd5-b21671c68e4b">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column11"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="44" height="18" uuid="cce29502-097d-4763-b5c0-330093e14041">
<printWhenExpression><![CDATA[$F{row11}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row11}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="44" uuid="9f2c3f4f-bc90-430d-b865-795def749d2d">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column12"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<box>
<pen lineWidth="0.4"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="44" height="18" uuid="29c0a84c-f4d0-4e4b-8d19-3d13a024552b">
<printWhenExpression><![CDATA[$F{row12}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row12}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="45" uuid="3def2e39-4c08-458e-8c36-df8eac735c65">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column13"/>
<jr:detailCell height="18">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<box>
<pen lineWidth="0.4"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement mode="Opaque" x="0" y="0" width="45" height="18" backcolor="#FFFFFF" uuid="573d45d1-bf4e-43e1-8282-49508130a3c0">
<printWhenExpression><![CDATA[$F{row13}!=null]]></printWhenExpression>
</reportElement>
<box>
<pen lineWidth="0.4"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="8"/>
</textElement>
<textFieldExpression><![CDATA[$F{row13}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
</jr:table>
</componentElement>
</band>
<band height="75">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="454" y="10" width="52" height="18" uuid="c1af5cd0-e851-4fd6-a16b-f461a743d336">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[出纳签字:]]></text>
</staticText>
<staticText>
<reportElement x="2" y="50" width="48" height="18" uuid="1cf7fa11-8f01-4764-a9a8-d981c9f32ca6">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[打印时间:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="50" width="124" height="18" uuid="b2daacc3-cb7c-443a-946b-be951bf080d5">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{printTime}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="454" y="50" width="52" height="18" uuid="3c4cbf80-712b-4e51-a861-1b1643642612">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[收银签字:]]></text>
</staticText>
</band>
</detail>
</jasperReport>
@@ -0,0 +1,243 @@
# 新版附表打印接口文档
# 新版附表打印接口文档
# 接口出参
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| billNo | 附表单号 | String |
| fromBillNo | 附表源工单号 | String |
| fromMaintainType | 来源单据类型 | String |
| billDate | 进厂日期 | String |
| creatorName | 创建人名称 | String |
| creationtime | 创建时间 | String |
| naEmployee | 服务顾问 | String |
| employeePhone | 服务顾问手机号 | String |
| businessTypeName | 业务类型 | String |
| nextMaintainDate | 下次保养日期 | String |
| oilCapacity | 当前油量 | String |
| mileage | 出厂里程(进厂里程) | Double |
| nextMileage | 下次保养里程 | Double |
| vin | 车辆VIN码 | String |
| carNoWhole | 车牌号 | String |
| carModel | 品牌车型全称 | String |
| carModelShort | 车型简称 | String |
| carColor | 车身颜色 | String |
| carCategoryName | 车辆分类名称 | String |
| carBrandName | 车辆品牌名称 | String |
| carSeriesName | 车系名称 | String |
| engineNumber | 发动机号 | String |
| transmissionNo | 变速箱号 | String |
| registerDate | 车辆注册日期 | String |
| cardDate | 车辆发证日期 | String |
| carNatureOfUseName | 车辆使用性质 | String |
| carFuelTypeName | 车辆燃料(能源)类型 | String |
| carSourceName | 车辆来源 | String |
| carOwnerName | 车辆所有人姓名 | String |
| naCustomer | 客户姓名 | String |
| customerSourceName | 客户来源名称 | String |
| customerDetailAddress | 客户详细地址 | String |
| cellPhone | 联系电话(客户) | String |
| memberCardNo | 会员卡号 | String |
| points | 客户积分 | String |
| customerLevelName | 客户等级名称 | String |
| repairPerson | 送修人 | String |
| repairPersonContact | 送修人联系方式 | String |
| memo | 备注 | String |
| completeDate | 完工日期 | String |
| firstGatheringTime | 初次收款时间 | String |
| estimatedDeliveryTime | 预计交车时间 | String |
| deliveryTime | 交车时间 | String |
| printContent | 免责条款 | String |
| printContentJs | 免责条款江苏 | printContentJs |
| storeLogo | 门店logo | String |
| orgName | 门店名称 | String |
| orgMemo | 门店备注 | String |
| orgContacts | 联系人(维修厂) | String |
| orgContactNumber | 联系电话(维修厂) | String |
| orgDetailAddress | 联系地址(维修厂) | String |
| orgContactMobile | 联系电话(维修厂) | String |
| fax | 传真 | String |
| email | 组织邮件 | String |
| bankAccount | 开户银行 | String |
| accountNumber | 账号 | String |
| businessLicenseCode | 企业执照号 | String |
| channelName | 来店途径名称 | String |
| printOrgName | 打印抬头(需读取配置) | String |
| amountAll | 应收总计(合计金额) | Double |
| amountAllChinese | 应收总计(合计金额)中文大写 | String |
| disCountAll | 总优惠合计(附表:项目优惠+材料优惠+收银优惠) | Double |
| disCountAllBak | 项目优惠+材料优惠+收银优惠,等同于disCountAll | Double |
| amountReal | 实收金额 | Double |
| chineseAmount | 实收金额(中文大写) | Double |
| oweAmount | 未收金额 | Double |
| vipExpense | 套餐卡消费金额(附表为0 | Double |
| vipExpenseFavourable | 套餐卡优惠金额(附表为0 | Double |
| czkExpense | 储值卡消费金额(附表为0 | Double |
| czkExpenseFavourable | 储值卡优惠金额(附表为0 | Double |
| remainAmount | 结算金额tsf<br/>附表:应收-项目优惠-材料优惠 | Double |
| receivedAmount | 收款金额tsf<br/>附表:等同于已收金额 | Double |
| serviceList | 项目集合 | List |
| └─ sortNumber | 序号 | String |
| └─ orderNumber | 序号(全部) | String |
| └─ name | 名称 | String |
| └─ serviceName | 项目名称 | String |
| └─ labelName | 业务分类名称 | String |
| └─ price | 工时单价 | Double |
| └─ workHour | 工时 | Double |
| └─ subtotal | 金额 | Double |
| └─ singleFavourable | 优惠金额 | Double |
| └─ discountedSubtotal | 折后金额 | Double |
| └─ serviceMemo | 附加信息备注 | String |
| └─ discount | 折扣 | Double |
| └─ empNameStr | 服务项目明细对应修理工名称组装字符串 | String |
| └─ infiniteFlag | 是否无限,0:否,1:是 | Integer |
| └─ customCode | 自定义编码 | String |
| totalWorkHour | 项目工时合计 | Double |
| totalWorkHourVip | VIP项目工时合计(附表为0 | Double |
| serviceSubtotalAll | 工时费小计 | Double |
| serviceSubtotalVip | 服务项目明细小计(会员项目,附表为0) | Double |
| serviceNum | 维修项目小计 | String |
| serviceFavourable | 服务项目(非会员项目)客户等级优惠(附表为0) | Double |
| serviceDiscountFavourable | 项目优惠 | Double |
| serviceFavourableTotal | 项目优惠金额合计 | Double |
| serviceFavourableCommonTotal | 普通项目优惠金额合计 | Double |
| serviceDisCountSubTotal | 项目折后金额合计 | Double |
| partList | 工单对应配件材料集合 | List |
| └─ sortNumber | 序号 | String |
| └─ orderNumber | 序号(全部) | String |
| └─ name | 名称 | String |
| └─ partName | 材料名称 | String |
| └─ partShowName | 材料名称(全) | String |
| └─ partBrand | 配件品牌 | String |
| └─ standard | 配件名称规格型号品牌 | String |
| └─ spec | 规格型号 | String |
| └─ supplierCode | 供应商编码 | String |
| └─ unit | 单位 | String |
| └─ number | 数量 | Double |
| └─ price | 价格(单价) | Double |
| └─ subtotal | 金额(材料金额) | Double |
| └─ discount | 折扣 | Double |
| └─ singleFavourable | 优惠金额 | Double |
| └─ discountedSubtotal | 折后金额 | Double |
| └─ partMemo | 备注 | String |
| └─ customCode | 自定义编码 | String |
| └─ employeeName | 员工名称 | String |
| └─ outStockEmployeeName | 领料人 | String |
| └─ empNameStr | 明细对应修理工名称组装字符串 | String |
| └─ labelName | 业务分类名称 | String |
| └─ idPart | 配件材料pk | BigInteger |
| └─ idInfo | 本地材料id(长码) | String |
| └─ applyModel | 适用车型 | String |
| stuffNum | 材料数目合计 | String |
| totalStuffNum | 材料数量合计 | String |
| totalStuffNumVip | Vip材料数量合计(附表为0 | String |
| stuffSubtotalAll | 材料费小计 | Double |
| stuffSubtotalVip | 材料收入小计(会员项目,附表为0) | Double |
| partinfoFavourable | 材料项目(非会员项目)客户等级优惠(附表为0) | Double |
| partinfoDiscountFavourable | 材料折扣优惠 | Double |
| partFavourableTotal | 材料优惠金额合计 | Double |
| partFavourableCommonTotal | 普通材料优惠金额合计 | Double |
| stuffDisCountTotal | 材料折后金额合计 | Double |
| pointFavourable | 积分优惠(附表为0 | Double |
| packageFavourable | 套餐优惠(附表为0 | Double |
| discountFavourable | 结清优惠(附表为0 | Double |
| gatheringFavourable | 收银优惠 | Double |
| couponFavourable | 优惠券优惠(附表为0 | Double |
| czkDiscountFavourable | 储值卡折扣优惠(附表为0 | Double |
| customerLevelFavourable | 客户级别优惠金额(附表为0 | Double |
| extraChargeList | 附加费用集合 | List |
| └─ sortNumber | 序号 | String |
| └─ extraName | 附加费名称 | String |
| └─ subtotal | 金额 | Double |
| └─ memo | 备注 | String |
| extraCostTotal | 附加费小计 | Double |
| extraNumber | 附加费数量小计 | String |
| managementCost | 管理费 | Double |
| extraPrintVo | 附加项目 | ExtraPrintAttribute |
| └─ commissionCustomName | 代办费自定义名称 | String |
| └─ commissionCost | 代办费金额 | Double |
| └─ commissionMemo | 代办费备注 | String |
| └─ diagnosisCustomName | 诊断费自定义名称 | String |
| └─ diagnosisCost | 诊断费金额 | Double |
| └─ diagnosisItemName | 诊断详细名称 | String |
| └─ diagnosisMemo | 诊断费备注 | String |
| └─ checkCustomName | 检查费自定义名称 | String |
| └─ checkItemName | 诊断详细名称 | String |
| └─ checkCost | 检查费金额 | Double |
| └─ checkMemo | 检查费备注 | String |
| └─ processCustomName | 加工费自定义名称 | String |
| └─ processCost | 加工费金额 | Double |
| └─ processMemo | 加工费备注 | String |
| └─ processItemName | 加工详细名称 | String |
| └─ managementCustomName | 管理费自定义名称 | String |
| └─ managementCost | 管理费金额 | Double |
| └─ managementMemo | 管理费备注 | String |
| └─ fuelName | 加油费 | String |
| └─ fuelAmount | 加油费金额 | Double |
| └─ trailName | 拖车费 | String |
| └─ trailAmount | 拖车费金额 | Double |
| allOtherCost | 附加费合计应收 | Double |
| receiptAmount | 收据金额(附表:应收-项目优惠-材料优惠-收银优惠) | Double |
| receiptAmountChinese | 收据金额中文大写 | String |
| payItemList | 付款方式集合 | List |
| └─ payWay | 付款方式 | String |
| └─ payAmount | 付款金额 | Double |
| └─ chinesePayAmount | 付款金额中文大写 | String |
| payItemTogether | 付款方式拼接 | String |
| payItemTogetherChinese | 付款方式总额中文大写 | String |
| paymentTypeDetails | 支付方式汇总 | String |
| settleOweAmout | 结算单中用的待付金额(未收) | Double |
| settleOweAmoutChinese | 结算单中用的待付金额大写(未收) | String |
| settleReceivedAmout | 结算单中用的实付金额(实收) | Double |
| settleReceivedAmoutChinese | 结算单中用的实付金额大写(未收) | String |
| realPayAmountChinese | 客户实付大写(应收-所有优惠) | String |
| naInsurer | 理赔公司名称 | String |
| insurancepolicyNo | 理赔单理赔保险单号 | String |
| insuranceCompany | 保险公司名称 | String |
| contactName | 联系人姓名 | String |
| contactCellphone | 联系人电话 | String |
| insuranceNo | 商业险单号 | String |
| insuranceNoTCI | 交强险单号 | String |
| insuranceExpiryDate | 商业险到期日 | String |
| insuranceExpiryDateTCI | 交强险到期日 | String |
| printEmployeeName | 打印人姓名 | String |
| printTime | 打印时间 | String |
| printCount | 打印次数 | String |
# 备注1
1. ++新版附表没有“状态”字段,所有模板中切勿使用 billStatus【单据状态】、balanceStatus【结算状态】来进行判断输出;采用直接取值方式填值++
1. ++没有了balanceStatus,采用收款方式列表payItemList判空的方式来验证是否有收款信息++
1. ++收银金额可写作:++$P{payItemList}.getRecordCount()==0?$P{receivedAmount}:$P{receivedAmount}.setScale( 2, BigDecimal.ROUND\_HALF\_EVEN ).toString()+"("+$P{paymentTypeDetails}+")"
2. ++待付金额可写作:$P{oweAmount}++
3. ++实付金额可写作:$P{amountReal}++
4. ++合计金额可写作:++$P{payItemList}.getRecordCount()==0?($P{settleOweAmout}).setScale( 2, BigDecimal.ROUND_HALF_EVEN ):($P{settleReceivedAmout}).setScale( 2, BigDecimal.ROUND_HALF_EVEN )
5. ++大写可写作:++$P{payItemList}.getRecordCount()==0?$P{settleOweAmoutChinese}:$P{chineseAmount}
2. ++新版附表没有“单据类型”字段(原附表的maintainType为"GDFB"、"LPDFB"两种附表类型),所有模板中切勿使用 maintainType【单据类型】字段作为判断条件。根据情况可以取 fromMaintainType【来源单据类型】进行判断。如:++
1. ++是否展示理赔公司、理赔单等理赔相关信息栏,老附表判断逻辑为 maintainType.equals('LPDFB')。可更换为 fromMaintainType.equals('LPD')++
# 备注2
1. 出参标注颜色为 **绿 色** 的字段,为 ++**附表本身内容**++(如车牌号、VIN码等) 或 ++**无法变更内容**++(如门店信息等)
1. 客户在附表页面直接变更信息,会直观反映在打印内容中。
2. 出参标注颜色为 **橙 色** 的字段,为 ++**通过客户ID、车辆ID、项目ID、材料ID**++ 等,++**反查**++ 基础数据获得内容,
1. 若客户通过 ++**附表页面选择组件方式**++ 修改附表信息,因ID发生变化,该部分打印内容会随之变化,变更信息将 ++**会体现在打印内容中**++。
2. 若客户通过 ++**手动填写文本内容方式**++ 修改附表信息,因ID未发生变化,该部分打印内容不会变化,变更信息将 ++**不会体现在打印内容中**++,若客户不满意结果,请客户直接将 ++**基础数据进行变更**++ 后,通过页面组件选择后再进行打印。
> 更新: 2024-01-11 12:11:34 原文: <https://xcz.yuque.com/ombipo/rpc7ms/nmoz9mzqf2q8micw>
@@ -0,0 +1,61 @@
# 材料标签接口参数
# 材料标签接口参数
打印平台模版分类:材料价格通用标签打印
材料标签支持一次打多张,与labelList集合里的元素个数相匹配:
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| labelList | 材料标签列表 | | List<Label> |
| <br/><br/> | | | |
Label:
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| name | 材料名称 | | String |
| supplierCode | 零件号 | | String |
| customCode | 材料编码 | | String |
| sellPrice | 销售价格 | | BigDecimal |
| date | 打印日期 | | String yyyy/MM/dd |
| <br/><br/> | | | |
# 采购库存材料标签打印
### 打印平台模版分类:
不带供应商信息:采购库存通用标签打印-新
带供应商信息:采购库存通用标签打印-包含供应商-新
![image](https://ddoc.f6yc.com/yuque/0/2023/png/227465/1694662514424-52b54134-ad88-4e10-b5ad-c3565633f157.png)
### 打印数据体
材料标签支持一次打多张,于labelList集合里的元素个数相匹配:
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| labelList | 材料名称 | | List<Object> |
Object:
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| partName | 材料名称 | | String |
| partBrand | 材料品牌 | | String |
| customCode | 材料编码 | | String |
| partShowName | 组合名称(材料品牌,名称,规格型号组合) | | String |
| supplierCode | 材料供应商编码 | | String |
| spec | 材料规格型号 | | String |
| unit | 材料单位 | | String |
| barCode | 材料编码 | | String |
| billDate | 单据日期 | | String yyyy-MM-dd |
| supplierName | 供应商名称 | | String |
| storageName | 材料仓库名称 | | String |
| defSeat | 材料货位 | | String |
| date | 打印的当前日期 | | String yyyy/MM/dd |
> 更新: 2023-09-14 14:27:45 原文: <https://xcz.yuque.com/ombipo/rpc7ms/mvq9tlfxeg2k6v9y>
@@ -0,0 +1,100 @@
# 检测单接口参数
# 检测单接口参数
# 参数说明
## 主单信息
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| carCheckPackageName | 模板名称 | | String |
| title | 标题 | | String |
| billNo | 检测单号 | | String |
| printTime | 打印时间 | | String |
| naEmployee | 服务顾问 | | String |
| billDate | 进厂时间 | | String |
| deliveryTime | 预计交车 | | String |
| naCustomer | 车主 | | String |
| carModel | 车型 | | String |
| cellPhone | 车主电话 | | String |
| carNoWhole | 车牌号 | | String |
| vin | vin码 | | String |
| repairPerson | 送修人 | | String |
| repairPersonContact | 送修人联系方式 | | String |
| mileage | 进厂里程 | | String |
| oilCapacity | 进厂油量 | | String |
| nextMileage | 下次保养里程 | | String |
| nextMaintainDate | 下次保养日期 | | String |
| customerMemo | 车主描述 | | String |
| merchantAddress | 联系地址 | | String |
| merchantPhone | 联系方式(手机 + 固定电话) | | String |
| qrCode | 二维码 | | String |
| qrCodeToB | 新版检测单B端二维码 | | String |
| qrCodeToC | 新版检测单C端二维码 | | String |
| icon | 车辆环视图 | | String |
| maintainBillNo | 结算单号 | | String |
| showComputerCheckInfo | 是否展示电脑检测 | | Boolean |
| computerCheckInfoList | 电脑检测 | | List<ComputerPrintItem> |
| personalCheckInfoList | 包含正常和异常人工检测项 | | List<PersonalPrintItem> |
| sortedPersonalCheckInfoList | 包含正常和异常人工检测项,问题项目排序靠前 | | List<PersonalPrintItem> |
| optionPersonalCheckInfoList | 异常人工检测项 | | List<PersonalPrintItem> |
| normalPersonalCheckInfoList | 正常人工检测项 | | List<PersonalPrintItem> |
| iconMemo | 环视图备注 | | String |
| iconResult | 环视图结论 | | String |
| warningLightResult | 警示灯结论 | | String |
| warningLightMemo | 警示灯备注 | | String |
| showWarningLightItem | 是否展示警示灯 | | Boolean |
| warningLightItemList | 警示灯 | | List<WarningLightItem> |
| warningLightBrightItemUrls | 警示灯列表字符串 | | String |
| employeeName | 服务技师 | | String |
| | | | |
## ComputerPrintItem
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| index | 序号 | String |
| errorCode | 故障码 | String |
| itemName | 检测项目 | String |
| optionNameS | 建议处理(Y | String |
| optionNameE | 择期处理(Y | String |
| optionNameU | 急需处理(Y | String |
| memo | 备注 | String |
| | | |
| | | |
| | | |
| | | |
| | | |
## PersonalPrintItem(检测小类)
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| index | 序号 | BigInteger |
| itemComponent | 检测部件(小类名称) | String |
| memo | 备注 | String |
| childList | 子项目列表 | List<PrintTinyItem> |
## PrintTinyItem(检测项目)
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| itemName | 检测项目 | String |
| itemResults | 检测结果 | String |
| optionNameS | 建议处理 Y | String |
| optionNameE | 择期处理 Y | String |
| optionNameU | 急需处理 Y | String |
| memo | 备注 | String |
## ComputerPrintItem
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| warningIconCode | 警示灯 | String |
| | | |
| | | |
| | | |
| | | |
> 更新: 2025-02-25 13:55:59 原文: <https://xcz.yuque.com/ombipo/rpc7ms/zayna4s335straki>
@@ -0,0 +1,842 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.3.1.final using JasperReports Library version 6.3.1 -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="carWash" pageWidth="136" pageHeight="842" columnWidth="136" leftMargin="0" rightMargin="0" topMargin="0" bottomMargin="10" uuid="8fdb09f3-da43-46f9-a6cb-2b26a2247961">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<property name="com.jaspersoft.studio.unit." value="mm"/>
<property name="com.jaspersoft.studio.unit.pageHeight" value="pixel"/>
<style name="Table_TH" mode="Opaque" backcolor="#FFFFFF">
<box>
<pen lineWidth="0.5" lineColor="#000000"/>
<topPen lineWidth="0.5" lineColor="#000000"/>
<leftPen lineWidth="0.5" lineColor="#000000"/>
<bottomPen lineWidth="0.5" lineColor="#000000"/>
<rightPen lineWidth="0.5" lineColor="#000000"/>
</box>
</style>
<style name="Table_CH" mode="Opaque" backcolor="#FFFFFF">
<box>
<pen lineWidth="0.5" lineColor="#000000"/>
<topPen lineWidth="0.5" lineColor="#000000"/>
<leftPen lineWidth="0.5" lineColor="#000000"/>
<bottomPen lineWidth="0.5" lineColor="#000000"/>
<rightPen lineWidth="0.5" lineColor="#000000"/>
</box>
</style>
<style name="Table_TD" mode="Opaque" backcolor="#FFFFFF">
<box>
<pen lineWidth="0.5" lineColor="#000000"/>
<topPen lineWidth="0.5" lineColor="#000000"/>
<leftPen lineWidth="0.5" lineColor="#000000"/>
<bottomPen lineWidth="0.5" lineColor="#000000"/>
<rightPen lineWidth="0.5" lineColor="#000000"/>
</box>
</style>
<subDataset name="Dataset1" uuid="22e86b94-acb8-45ed-960f-04558f91ad82">
<queryString>
<![CDATA[]]>
</queryString>
<field name="serviceName" class="java.lang.String"/>
<field name="workHour" class="java.math.BigDecimal"/>
<field name="leftCount" class="java.math.BigDecimal"/>
<field name="unusedNumber" class="java.lang.Integer"/>
<field name="number" class="java.lang.Integer"/>
<field name="subtotal" class="java.math.BigDecimal"/>
<field name="empNameStr" class="java.lang.String"/>
</subDataset>
<subDataset name="Dataset1sub" uuid="22e86b94-acb8-45ed-960f-04558f91ad82">
<queryString>
<![CDATA[]]>
</queryString>
<field name="serviceName" class="java.lang.String"/>
<field name="workHour" class="java.math.BigDecimal"/>
<field name="leftCount" class="java.math.BigDecimal"/>
<field name="unusedNumber" class="java.lang.Integer"/>
<field name="number" class="java.lang.Integer"/>
<field name="subtotal" class="java.math.BigDecimal"/>
</subDataset>
<subDataset name="Dataset2" uuid="b9fe50e4-5070-473e-9238-1aa624bd7ae5">
<queryString>
<![CDATA[]]>
</queryString>
<field name="customCode" class="java.lang.String"/>
<field name="partName" class="java.lang.String"/>
<field name="unit" class="java.lang.String"/>
<field name="number" class="java.math.BigDecimal"/>
<field name="price" class="java.math.BigDecimal"/>
<field name="subtotal" class="java.math.BigDecimal"/>
<field name="partBrand" class="java.lang.String"/>
<field name="standard" class="java.lang.String"/>
<field name="supplierCode" class="java.lang.String"/>
<field name="discountedSubtotal" class="java.math.BigDecimal"/>
<field name="partMemo" class="java.lang.String"/>
<field name="isBring" class="java.lang.String"/>
<field name="singleFavourable" class="java.math.BigDecimal"/>
<field name="isMember" class="java.lang.Integer"/>
<field name="discount" class="java.math.BigDecimal"/>
<field name="spec" class="java.lang.String"/>
<sortField name="isMember" order="Descending"/>
</subDataset>
<subDataset name="DatasetPay" uuid="6f96d8c9-fa56-4586-9b6d-846e9a678fc3">
<queryString>
<![CDATA[]]>
</queryString>
<field name="payWay" class="java.lang.String"/>
<field name="payAmount" class="java.math.BigDecimal"/>
<field name="chinesePayAmount" class="java.lang.String"/>
</subDataset>
<subDataset name="DatasetExtra" uuid="6d2afb78-c6ff-45a0-90a2-6e65d25da398">
<queryString>
<![CDATA[]]>
</queryString>
<field name="extraName" class="java.lang.String"/>
<field name="subtotal" class="java.math.BigDecimal"/>
<field name="memo" class="java.lang.String"/>
</subDataset>
<subDataset name="Dataset_sub" uuid="6a79ccac-0c3d-4ada-a547-6d9f5ef3a7c9">
<queryString>
<![CDATA[]]>
</queryString>
<field name="serviceName" class="java.lang.String"/>
<field name="workHour" class="java.math.BigDecimal"/>
<field name="leftCount" class="java.math.BigDecimal"/>
<field name="unusedNumber" class="java.lang.Integer"/>
<field name="number" class="java.lang.Integer"/>
<field name="subtotal" class="java.math.BigDecimal"/>
</subDataset>
<subDataset name="CopyOfDataset_1" uuid="fad36331-4b02-4d0a-99c9-d3608c7abffb">
<queryString>
<![CDATA[]]>
</queryString>
<field name="serviceName" class="java.lang.String"/>
<field name="workHour" class="java.math.BigDecimal"/>
<field name="leftCount" class="java.math.BigDecimal"/>
<field name="unusedNumber" class="java.lang.Integer"/>
<field name="number" class="java.lang.Integer"/>
<field name="subtotal" class="java.math.BigDecimal"/>
</subDataset>
<parameter name="orgName" class="java.lang.String"/>
<parameter name="detailAddress" class="java.lang.String"/>
<parameter name="contactNumber" class="java.lang.String"/>
<parameter name="fax" class="java.lang.String"/>
<parameter name="bankAccount" class="java.lang.String"/>
<parameter name="email" class="java.lang.String"/>
<parameter name="accountNumber" class="java.lang.String"/>
<parameter name="naCustomer" class="java.lang.String"/>
<parameter name="contactName" class="java.lang.String"/>
<parameter name="contactCellphone" class="java.lang.String"/>
<parameter name="billNo" class="java.lang.String"/>
<parameter name="billDate" class="java.lang.String"/>
<parameter name="mileage" class="java.math.BigDecimal"/>
<parameter name="carNoWhole" class="java.lang.String"/>
<parameter name="carModelShort" class="java.lang.String"/>
<parameter name="deliveryTime" class="java.lang.String"/>
<parameter name="serviceSubtotalAll" class="java.math.BigDecimal"/>
<parameter name="stuffSubtotalAll" class="java.math.BigDecimal"/>
<parameter name="extraCostTotal" class="java.math.BigDecimal"/>
<parameter name="amountAll" class="java.math.BigDecimal"/>
<parameter name="totalWorkHour" class="java.math.BigDecimal"/>
<parameter name="numberCount" class="java.lang.String"/>
<parameter name="serviceList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="partList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="businessTypeName" class="java.lang.String"/>
<parameter name="creatorName" class="java.lang.String"/>
<parameter name="carModel" class="java.lang.String"/>
<parameter name="vin" class="java.lang.String"/>
<parameter name="printCount" class="java.lang.String"/>
<parameter name="serviceNum" class="java.lang.String"/>
<parameter name="serviceDisCountSubTotal" class="java.math.BigDecimal"/>
<parameter name="totalStuffNum" class="java.lang.String"/>
<parameter name="couponFavourable" class="java.math.BigDecimal"/>
<parameter name="pointFavourable" class="java.math.BigDecimal"/>
<parameter name="partinfoDiscountFavourable" class="java.math.BigDecimal"/>
<parameter name="naEmployee" class="java.lang.String"/>
<parameter name="memo" class="java.lang.String"/>
<parameter name="repairPerson" class="java.lang.String"/>
<parameter name="cellPhone" class="java.lang.String"/>
<parameter name="carBrandName" class="java.lang.String"/>
<parameter name="orgContactMobile" class="java.lang.String"/>
<parameter name="orgDetailAddress" class="java.lang.String"/>
<parameter name="selfPartList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="payItemList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="extraChargeList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="stuffDisCountTotal" class="java.math.BigDecimal"/>
<parameter name="selfTotalStuffNum" class="java.lang.String"/>
<parameter name="czkExpense" class="java.math.BigDecimal"/>
<parameter name="disCountAll" class="java.math.BigDecimal"/>
<parameter name="packageFavourable" class="java.math.BigDecimal"/>
<parameter name="serviceFavourable" class="java.math.BigDecimal"/>
<parameter name="partinfoFavourable" class="java.math.BigDecimal"/>
<parameter name="gatheringFavourable" class="java.math.BigDecimal"/>
<parameter name="discountFavourable" class="java.math.BigDecimal"/>
<parameter name="oweAmount" class="java.math.BigDecimal"/>
<parameter name="amountReal" class="java.math.BigDecimal"/>
<parameter name="printTime" class="java.lang.String"/>
<parameter name="creationtime" class="java.lang.String"/>
<parameter name="extraNumber" class="java.lang.String"/>
<parameter name="carSeriesName" class="java.lang.String"/>
<parameter name="qRCodeStr" class="java.lang.String"/>
<parameter name="vipExpense" class="java.math.BigDecimal"/>
<parameter name="storeLogo" class="java.lang.String"/>
<parameter name="partFavourableTotal" class="java.math.BigDecimal"/>
<parameter name="serviceFavourableTotal" class="java.math.BigDecimal"/>
<parameter name="partFavourableCommonTotal" class="java.math.BigDecimal"/>
<parameter name="serviceFavourableCommonTotal" class="java.math.BigDecimal"/>
<parameter name="serviceSubtotalVip" class="java.math.BigDecimal"/>
<parameter name="stuffSubtotalVip" class="java.math.BigDecimal"/>
<parameter name="nextMileage" class="java.math.BigDecimal"/>
<parameter name="printContent" class="java.lang.String"/>
<parameter name="repairPersonContact" class="java.lang.String"/>
<parameter name="payItemTogether" class="java.lang.String"/>
<parameter name="payItemTogetherChinese" class="java.lang.String"/>
<parameter name="printOrgName" class="java.lang.String"/>
<parameter name="orgContactNumber" class="java.lang.String"/>
<parameter name="balanceStatus" class="java.lang.String"/>
<parameter name="receiptAmount" class="java.math.BigDecimal"/>
<parameter name="stubPrintFlag" class="java.lang.String"/>
<parameter name="stubServiceList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource">
<parameterDescription><![CDATA[]]></parameterDescription>
</parameter>
<parameter name="czkDetailInfo" class="java.lang.String"/>
<queryString>
<![CDATA[]]>
</queryString>
<variable name="tempServiceList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource">
<variableExpression><![CDATA[$P{serviceList}]]></variableExpression>
</variable>
<variable name="tempServiceList2" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource">
<variableExpression><![CDATA[$P{serviceList}]]></variableExpression>
</variable>
<detail>
<band height="43">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="1" width="136" height="40" uuid="749214fe-d497-4af5-809c-60db5b97deee">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="11"/>
</textElement>
<textFieldExpression><![CDATA[($P{printOrgName}==null?$P{orgName}:($P{printOrgName}.isEmpty()?$P{orgName}:$P{printOrgName}))+"洗车单"]]></textFieldExpression>
</textField>
<line>
<reportElement positionType="Float" x="0" y="42" width="136" height="1" uuid="9902b948-f3bc-4318-9162-169d3a18d40e">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="11">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="0" width="40" height="11" uuid="a9ea9a61-8b35-4714-abd6-c1b3f01daf4c">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[打印时间:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="32" y="0" width="104" height="11" uuid="3e42cdc1-f715-455d-965b-a3862546efe3">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{printTime}.substring(0,16)]]></textFieldExpression>
</textField>
</band>
<band height="11">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="0" width="40" height="11" uuid="d8179c10-e152-47cf-98ed-6d25100423b1">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[洗车单号:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="32" y="0" width="104" height="11" uuid="73392fa1-f678-4f97-b702-97d591431a76">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{billNo}]]></textFieldExpression>
</textField>
</band>
<band height="11">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="32" y="0" width="104" height="11" uuid="4b8e8259-abcb-4b55-8932-645ab3c168db">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{naEmployee}]]></textFieldExpression>
</textField>
<staticText>
<reportElement key="" x="0" y="0" width="40" height="11" uuid="aa005a7f-11f1-4362-94dd-7d4b737f85fb">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[服务顾问:]]></text>
</staticText>
</band>
<band height="11">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="24" y="0" width="112" height="11" uuid="4c9e2e05-12f0-4c10-a4a7-cfac0dedc4c7">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{carNoWhole}]]></textFieldExpression>
</textField>
<staticText>
<reportElement key="" x="0" y="0" width="40" height="11" uuid="506d2cfc-d948-4a43-971c-e2ae1f4f4a6e">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[车牌号:]]></text>
</staticText>
</band>
<band height="11">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="18" y="0" width="118" height="11" uuid="7c4f4788-dd00-484c-8762-c907f94aba06">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{naCustomer}.substring(0, 1) + "(*^_^*)" + $P{naCustomer}.substring($P{naCustomer}.length() > 10 ? 10 : $P{naCustomer}.length(), $P{naCustomer}.length())]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="0" width="29" height="11" uuid="1e0990e3-d082-4c2a-b861-ea88ae864819">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[客户:]]></text>
</staticText>
</band>
<band height="12">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="32" y="0" width="104" height="11" uuid="aa23dce0-7f6f-4324-8368-9040c5460fce">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{cellPhone}.length() <= 3 ? $P{cellPhone}: $P{cellPhone}.substring(0, 3) + "(*^_^*)" + $P{cellPhone}.substring($P{cellPhone}.length() > 7 ? 7 : $P{cellPhone}.length(), $P{cellPhone}.length())]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="0" width="40" height="11" uuid="17c41cd7-40bc-417a-b81f-31cb88c34931">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[客户电话:]]></text>
</staticText>
<line>
<reportElement positionType="Float" x="0" y="11" width="136" height="1" uuid="83b4e0cf-0321-463b-93ab-cf578aecc422">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="17">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{serviceList}!=null]]></printWhenExpression>
<staticText>
<reportElement positionType="Float" x="0" y="0" width="46" height="16" uuid="3b60f428-f1be-46b4-8844-000b66d37cb3">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[项目]]></text>
</staticText>
<staticText>
<reportElement positionType="Float" x="46" y="0" width="38" height="16" uuid="3dce837c-7930-438f-88cf-d0ca69283abf">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[工时费(元)]]></text>
</staticText>
<staticText>
<reportElement positionType="Float" x="84" y="0" width="52" height="16" uuid="4f6f1957-c5d3-45d4-9f9e-59f03d002151">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[卡剩余次数]]></text>
</staticText>
<line>
<reportElement positionType="Float" x="0" y="16" width="136" height="1" uuid="45924492-61d2-4ec2-9dae-9edf6181e7a4">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Dashed"/>
</graphicElement>
</line>
</band>
<band height="11">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{serviceList}!=null]]></printWhenExpression>
<componentElement>
<reportElement isPrintRepeatedValues="false" x="2" y="0" width="136" height="11" uuid="ecd9cd2e-ad62-4a61-a8a7-6f894e8b881b">
<property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
<property name="com.jaspersoft.studio.table.style.table_header" value="Table 1_TH"/>
<property name="com.jaspersoft.studio.table.style.column_header" value="Table 1_CH"/>
<property name="com.jaspersoft.studio.table.style.detail" value="Table 1_TD"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd">
<datasetRun subDataset="Dataset1" uuid="12ce7a05-4dbc-42aa-ab97-ca225ebd48e1">
<dataSourceExpression><![CDATA[$P{serviceList}]]></dataSourceExpression>
</datasetRun>
<jr:column width="46" uuid="4a339d5c-d93e-4d9b-a7f5-478cb3bc4c66">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column1"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="11">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement key="" x="0" y="0" width="46" height="11" uuid="d1b9c27f-da5c-43d5-b245-b2ce10285445">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
</textElement>
<textFieldExpression><![CDATA[$F{serviceName}.indexOf("卡消费") > 0 ? $F{serviceName}.replaceAll("卡消费",$F{empNameStr}): $F{serviceName}+"("+$F{empNameStr}+")"]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="38" uuid="36d33a43-8dc0-421c-89b2-6c17b4c992c2">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column2"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<jr:detailCell height="11">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement key="" x="0" y="0" width="38" height="11" uuid="b0a21e7b-9ef2-453a-8b0b-f81e4a8d02ae">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
</textElement>
<textFieldExpression><![CDATA[$F{subtotal}.setScale( 2, BigDecimal.ROUND_HALF_EVEN )]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="52" uuid="922b68cd-f23e-4d67-9717-74621fa31ed2">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column3"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<jr:detailCell height="11">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement key="" x="0" y="0" width="52" height="11" uuid="683ff861-1457-4841-8904-a5b8030d6a63">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
</textElement>
<textFieldExpression><![CDATA[($F{unusedNumber}==null?"":$F{unusedNumber}+"/")+($F{number}==null?"":$F{number})]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
</jr:table>
</componentElement>
</band>
<band height="20">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="8" width="40" height="11" uuid="26dc81d8-29cc-4024-b7b4-d25a9ed72773">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[合计金额:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="37" y="8" width="99" height="11" uuid="2872a1d3-dafc-430d-9cfd-fef01cdcc46a">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{amountAll}.subtract($P{vipExpense}).setScale( 2, BigDecimal.ROUND_HALF_EVEN )]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="33" y="9" width="7" height="11" uuid="f8d751a0-c2f4-45c9-bd4d-168f7250ab40">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="微软雅黑" size="7"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<line>
<reportElement positionType="Float" x="0" y="5" width="136" height="1" uuid="aa43bd00-5440-4c07-a3d5-bbbf892646bb">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="11">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable}).setScale( 2, BigDecimal.ROUND_HALF_EVEN ).compareTo(BigDecimal.ZERO) != 0]]></printWhenExpression>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="37" y="-1" width="99" height="11" uuid="d4b00569-8830-4a81-98b9-b02516e9e433">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable}).setScale( 2, BigDecimal.ROUND_HALF_EVEN )]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="33" y="0" width="7" height="11" uuid="7d3b3e0c-8646-455a-ac17-10c3e46db01f">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="微软雅黑" size="7"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<staticText>
<reportElement x="0" y="-1" width="40" height="11" uuid="0eb1e983-dbb7-4e21-b8ee-db6f492cdfab">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[优惠金额:]]></text>
</staticText>
</band>
<band height="22">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="37" y="10" width="99" height="11" uuid="00e5ff0d-525e-4d27-ab0d-5a988300256b">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{balanceStatus}.equals("7000")?($P{amountAll}.subtract($P{vipExpense}).subtract($P{czkExpense}).subtract($P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable})).setScale( 2, BigDecimal.ROUND_HALF_EVEN )):($P{oweAmount}.setScale( 2, BigDecimal.ROUND_HALF_EVEN ))]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="10" width="40" height="11" uuid="e5619fe5-5014-44c7-be70-428e7fa89a99">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[待付金额:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="-1" width="40" height="11" uuid="b3f329b3-6432-412c-ac54-3c3f1fdbe034">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[实付金额:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="37" y="-1" width="99" height="11" uuid="8bbae461-eac5-4e83-9f39-4a27a55bed68">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{balanceStatus}.equals("7000")?"0.00":($P{amountAll}.subtract($P{vipExpense}).subtract($P{oweAmount}).subtract($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).subtract($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).subtract($P{czkExpense}).
subtract($P{serviceFavourable}).subtract($P{partinfoFavourable}).subtract($P{discountFavourable}).subtract($P{couponFavourable}).subtract($P{pointFavourable}).subtract($P{gatheringFavourable}).setScale( 2, BigDecimal.ROUND_HALF_EVEN ))]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="33" y="0" width="7" height="11" uuid="d257edd4-940d-4804-a256-5e23b7fc781e">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="微软雅黑" size="7"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<staticText>
<reportElement x="33" y="11" width="7" height="11" uuid="162688e4-1a42-4f5c-b406-e5c7c780a696">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="微软雅黑" size="7"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
</band>
<band height="11">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{czkExpense}.compareTo(BigDecimal.ZERO) != 0]]></printWhenExpression>
<staticText>
<reportElement x="40" y="-1" width="7" height="11" uuid="389acfa8-bc97-47ca-b7de-61e45f6392f8">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<printWhenExpression><![CDATA[$P{czkExpense}.compareTo(BigDecimal.ZERO) != 0]]></printWhenExpression>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="微软雅黑" size="7"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<staticText>
<reportElement x="0" y="0" width="50" height="11" uuid="ef352217-3888-4dee-9291-2e1814474afa">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{czkExpense}.compareTo(BigDecimal.ZERO) != 0]]></printWhenExpression>
</reportElement>
<textElement verticalAlignment="Top">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[储值卡消费:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement positionType="Float" x="43" y="0" width="37" height="11" uuid="9ed79fa8-64a1-401d-b20e-2bd537aa60ce">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{czkExpense}.compareTo(BigDecimal.ZERO) != 0]]></printWhenExpression>
</reportElement>
<textElement verticalAlignment="Top">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{czkExpense}.setScale( 2, BigDecimal.ROUND_HALF_EVEN )]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement positionType="Float" x="80" y="0" width="56" height="11" uuid="9ed79fa8-64a1-401d-b20e-2bd537aa60ce">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{czkExpense}.compareTo(BigDecimal.ZERO) != 0]]></printWhenExpression>
</reportElement>
<textElement verticalAlignment="Top">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA["("+($P{czkDetailInfo}==null?"":$P{czkDetailInfo}) + ")"]]></textFieldExpression>
</textField>
</band>
<band height="15">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="0" width="40" height="11" uuid="107cd24a-9c65-454a-857b-d4343360c17c">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[收银金额:]]></text>
</staticText>
<staticText>
<reportElement x="33" y="1" width="7" height="11" uuid="d3e37020-1703-4943-88cc-f6a27b30051d">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="微软雅黑" size="7"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement positionType="Float" x="37" y="2" width="99" height="11" uuid="8cac6572-8613-4439-ac11-8a09c05356a6">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{balanceStatus}.equals("7000")?"0.00":($P{amountAll}.subtract($P{vipExpense}).subtract($P{oweAmount}).subtract($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).subtract($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).subtract($P{czkExpense}).
subtract($P{serviceFavourable}).subtract($P{partinfoFavourable}).subtract($P{discountFavourable}).subtract($P{couponFavourable}).subtract($P{pointFavourable}).subtract($P{gatheringFavourable}).setScale( 2, BigDecimal.ROUND_HALF_EVEN ).toString()+"("+$P{payItemTogether}+")")]]></textFieldExpression>
</textField>
</band>
<band height="26">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="2" width="29" height="11" uuid="fc491228-a3a9-47ab-b354-af9f4c4e0f3e">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Top">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[备注:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement positionType="Float" x="20" y="2" width="116" height="11" uuid="b7b78327-0448-424d-970e-bada89ec5d60">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Top">
<font fontName="黑体" size="7"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{memo}]]></textFieldExpression>
</textField>
<line>
<reportElement x="1" y="0" width="136" height="1" uuid="1d0bcb53-619a-484c-a904-4e88f609d733">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
</detail>
</jasperReport>
@@ -0,0 +1,433 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.3.1.final using JasperReports Library version 6.3.1 -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="Blank_A4" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="3fa22123-efc4-4f3f-a186-6a8f692d17e6">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<subDataset name="List1" uuid="7366c5be-288c-41c7-b295-b8d023ec81ae">
<queryString>
<![CDATA[]]>
</queryString>
<field name="index" class="java.lang.String"/>
<field name="serviceName" class="java.lang.String"/>
<field name="cooperationServiceName" class="java.lang.String"/>
<field name="cooperationOrgName" class="java.lang.String"/>
<field name="auditStatus" class="java.lang.String"/>
<field name="cooperationCost" class="java.math.BigDecimal"/>
</subDataset>
<parameter name="billNo" class="java.lang.String"/>
<parameter name="tradingOrgName" class="java.lang.String"/>
<parameter name="tradingDate" class="java.lang.String"/>
<parameter name="customerName" class="java.lang.String"/>
<parameter name="carNo" class="java.lang.String"/>
<parameter name="employeeName" class="java.lang.String"/>
<parameter name="cardName" class="java.lang.String"/>
<parameter name="memberCardNo" class="java.lang.String"/>
<parameter name="cardEndDate" class="java.lang.String"/>
<parameter name="tradingAmount" class="java.lang.String"/>
<parameter name="amount" class="java.lang.String"/>
<parameter name="remark" class="java.lang.String"/>
<parameter name="orgOfPrinting" class="java.lang.String"/>
<parameter name="dateOfPrinting" class="java.lang.String"/>
<queryString>
<![CDATA[]]>
</queryString>
<detail>
<band height="103" splitType="Stretch">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="10" width="555" height="40" uuid="7c3a0deb-dcb0-406e-ba9c-9f279e1518b0">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="16" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA["卡交易单"]]></textFieldExpression>
</textField>
<staticText>
<reportElement key="" x="387" y="58" width="50" height="18" isRemoveLineWhenBlank="true" uuid="86735eee-0435-4238-9b93-c08d86ed318f">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[交易日期:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="58" width="50" height="18" uuid="fef0343d-31f4-4b1c-a521-1a1b736aa485">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[单号:]]></text>
</staticText>
<staticText>
<reportElement x="200" y="58" width="50" height="18" uuid="627d90fe-c235-45d8-9f4a-92f8d7ec0de7">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[交易门店:]]></text>
</staticText>
<staticText>
<reportElement x="387" y="80" width="50" height="18" uuid="7af4c3ab-ae72-4f6b-8b40-7b3129bd7eff">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[服务顾问:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="80" width="50" height="18" uuid="74602da5-9195-47dd-9416-9cb229aeccad">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[客户姓名:]]></text>
</staticText>
<staticText>
<reportElement x="200" y="80" width="50" height="18" uuid="65483940-e0c7-4713-9e9a-6a5a7294d248">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[用卡车辆:]]></text>
</staticText>
<line>
<reportElement x="0" y="78" width="555" height="1" uuid="f6716829-054d-4fed-a12c-2f4d227724c7">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Dotted"/>
</graphicElement>
</line>
<line>
<reportElement x="0" y="100" width="555" height="1" uuid="9c9a29fc-1a65-4869-8d03-3242ff1fe667">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="80" width="140" height="18" uuid="01160695-dd7d-4038-8476-f20a7f997e62"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{customerName}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="58" width="140" height="18" uuid="d8bc8e14-2a41-4471-8be9-dae7dee48c2b"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{billNo}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="250" y="58" width="130" height="18" uuid="f258bb2c-70ce-4b49-a305-a4c0db4671b2"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{tradingOrgName}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="250" y="80" width="130" height="18" uuid="8ffee383-6250-4697-8381-b3017b0d67ad"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{carNo}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="437" y="58" width="118" height="18" uuid="7daab9f8-bc40-45fc-90b2-510a19fe0578"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{tradingDate}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="437" y="80" width="118" height="18" uuid="5cd6ce11-c901-42cb-b84c-fc7b3a0de3b3"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{employeeName}]]></textFieldExpression>
</textField>
</band>
<band height="78">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="51" width="50" height="18" uuid="2cf35160-a982-4dcc-881b-c53adcf3761a">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[剩余金额:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="2" width="50" height="18" uuid="6b4cf2c5-cb81-43b6-82c9-ee238923a378">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="false"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[卡名称:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="25" width="50" height="18" uuid="782a08a7-c605-4a2f-83b7-4e3ad8ee3c4c">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[消费金额:]]></text>
</staticText>
<line>
<reportElement x="0" y="47" width="555" height="1" uuid="8699bef5-cebd-4ebc-bb34-3697568375f7">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Dotted"/>
</graphicElement>
</line>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="51" width="140" height="18" uuid="4ff3b629-b9e5-49c5-ad89-ce856a556839">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{amount}]]></textFieldExpression>
</textField>
<line>
<reportElement x="0" y="71" width="555" height="1" uuid="98b923f9-1e14-4cc5-b508-12141cefb96e">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="25" width="140" height="18" uuid="7ce3fef6-73c6-49e9-a676-64e402603d93"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{tradingAmount}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="200" y="2" width="50" height="18" uuid="04029be9-7783-4583-9f54-0b15047b2e76">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[卡号:]]></text>
</staticText>
<staticText>
<reportElement x="388" y="2" width="50" height="18" uuid="471ece50-ac4b-4d63-ba15-e82448944610">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[卡有效期:]]></text>
</staticText>
<line>
<reportElement x="0" y="22" width="555" height="1" uuid="7672b0ab-ea26-468c-a2f5-7188f85f733c">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="2" width="140" height="18" uuid="88cb0ab6-fc29-4d51-b688-0ab2bdb0b2f9"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{cardName}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="250" y="2" width="130" height="18" uuid="90dfc3ce-5cf5-42d0-8e6f-670b49922088"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{memberCardNo}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="438" y="2" width="117" height="18" uuid="48056694-c19b-4dc3-a0d7-ef3e32ea4cb4"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{cardEndDate}]]></textFieldExpression>
</textField>
</band>
<band height="92">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="17" width="50" height="18" uuid="fc27cb3d-0e5d-4143-b83d-daf432fa56c8">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="false"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[打印门店:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="17" width="140" height="18" uuid="1a73fa76-4113-4dcd-803c-da26e934f441"/>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{orgOfPrinting}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="200" y="17" width="50" height="18" uuid="786b4cf3-7636-455c-bf05-115ff8ca71b7">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Right" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="false"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[打印日期:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="250" y="17" width="130" height="18" uuid="61b50ddd-d880-403c-855f-3ae2f0403592"/>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{dateOfPrinting}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="-4" width="504" height="18" uuid="d9928920-600f-4030-976c-bdf598467704"/>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{remark}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="-4" width="50" height="18" uuid="03491a27-e480-40c3-aa80-d95b2bed3d95">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="false"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[交易备注:]]></text>
</staticText>
</band>
</detail>
</jasperReport>
@@ -0,0 +1,215 @@
# 结算单(指定内容)接口文档
# 结算单(指定内容)接口文档
# 接口
**接口: /blazer/maintenance/pr****int/file/dispatchCondition**
**方法: post**
**支持场景:打印 维保单/洗车单/维修单/贴膜单/理赔单/零售单,支持指定(项目/材料/附加费)打印**
**支持打印模块(**PrintModuleEnum**):****工时费、材料费、附加费、其他费用、服务费用**
**入参:**
```plaintext
{
"rowId": "10329",
"rowCode": "partialSettlementStatement",
"pkId": "16126085167868551253",
"serviceList": [
{
"pkId": 16126085168250232896,
"module": 1
},
{
"pkId": 16155904656688554043,
"module": 4
}
],
"partList": [
{
"pkId": 11004336,
"module": 2
},
{
"pkId": 11004811,
"module": 5
}
],
"extraList": [
{
"type": 4,
"module": 3
},
{
"type": 6,
"module": 4
}
]
}
```
**出参:**
```plaintext
{
"preScanUrl": "https://f.f6yc.com/printserver/pdfprint.html?url=https://f.f6yc.com/print-server/test/2024-10/default/49be06dc15444e2793a21fc8c16d3b5c.pdf?Expires=1729152323&OSSAccessKeyId=LTAI4Fcf2C1U99o3e3UQ2bHV&Signature=L86HWRF2ilyXU4BbzKhwXad%2BwXg%3D",
"url": "https://f.f6yc.com/print-server/test/2024-10/default/49be06dc15444e2793a21fc8c16d3b5c.pdf?Expires=1729152323&OSSAccessKeyId=LTAI4Fcf2C1U99o3e3UQ2bHV&Signature=L86HWRF2ilyXU4BbzKhwXad%2BwXg%3D"
}
```
# jasper取参说明
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| billNo | 工单号 | 是 | String |
| maintainType | 工单类型 | 是 | String |
| businessTypeName | 业务类别 | 是 | String |
| balanceStatus | 结算状态 | 是 | String |
| billStatus | 单据状态 | 是 | String |
| creatorName | 创建人名称 | 是 | String |
| naEmployee | 服务顾问 | 否 | String |
| employeePhone | 服务顾问手机号 | 否 | String |
| naInsurer | 理赔公司名称 | 否 | String |
| insurancepolicyNo | 理赔保险单号 | 否 | Double |
| serviceSubtotalVip | 套餐卡项目工时费小计 | 是 | Double |
| serviceFavourableCommonTotal | 普通项目优惠金额合计 | 是 | Double |
| totalStuffNum | 材料数量合计 | 是 | String |
| stuffSubtotalVip | 套餐卡项目材料费小计 | 是 | Double |
| stuffSubtotalAll | 材料费小计 | 是 | Double |
| partFavourableCommonTotal | 普通材料优惠金额合计 | 是 | Double |
| extraNumber | 附加费数量小计 | 是 | String |
| extraCostTotal | 附加费小计 | 是 | Double |
| favourableTotalList | 优惠明细小计 | 否 | List<FavourableDetailPrintAttribute> |
| naCustomer | 客户姓名 | 是 | String |
| cellPhone | 客户联系电话 | 是 | String |
| repairPerson | 送修人 | 否 | String |
| repairPersonContact | 送修人联系方式 | 否 | String |
| carNoWhole <br/> | 车牌号 | 是 | String |
| vin<br/> | 车辆VIN码 | 否 | String |
| carBrandName<br/> | 品牌名称 | 否 | String |
| carSeriesName<br/> | 车系名称 | 否 | String |
| carModelShort<br/> | 车型简称 | 否 | String |
| carModel<br/> | 车型 | 否 | String |
| transmissionNo | 变速箱号 | 否 | String |
| carFuelTypeName | 燃料类型 | 否 | String |
| carColor<br/> | 车身颜色 | 否 | String |
| billDate<br/> | 进厂日期 | 是 | String |
| estimatedDeliveryTime<br/> | 预计交车时间 | 是 | String |
| deliveryTime<br/> | 交车时间(出厂时间) | 是 | String |
| mileage<br/> | 进厂里程 | 是 | Double |
| nextMaintainDateRemind | 下次服务时间(服务提醒数据源) | 否 | Date |
| nextMileageRemind | 下次服务里程(服务提醒数据源) | 否 | Double |
| serviceList | 项目列表 | 否 | List<PartialServicePrintAttribute> |
| partList | 材料列表 | 否 | List<PartialPartPrintAttribute> |
| extraChargeList | 附加费列表 | 否 | List<ExtraChargePrintAttribute> |
| extendedModuleList | 扩展模块列表 | 否 | List<ExtendedModulePrintAttribute> |
| memo<br/> | 车主备注 | 否 | String |
| signaturePhotoUrl<br/> | 签名图片 | 否 | String |
| orgName<br/> | 维修厂名称 | 是 | String |
| orgContacts<br/> | 维修厂联系人 | 是 | String |
| orgDetailAddress<br/> | 维修厂地址 | 是 | String |
| orgContactMobile<br/> | 联系电话(维修厂) | 否 | String |
| storeLogo<br/> | logo | 否 | String |
| printOrgName<br/> | 打印抬头 | 否 | String |
| printContent<br/> | 免责条款 | 是 | String |
| printCount<br/> | 打印次数 | 是 | String |
| printTime<br/> | 打印时间 | 是 | String |
**PartialServicePrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| serviceName | 项目名称 | 是 | String |
| labelName | 业务分类名称 | 是 | String |
| isMember | 当前项目是否使用会员 | 是 | String |
| idService | 服务项目id | 是 | BigInteger |
| idInfo | 本地项目id | 是 | String |
| price | 工时单价 | 是 | Double |
| workHour | 工时 | 是 | Double |
| subtotal | 金额 | 是 | Double |
| singleFavourable | 优惠金额 | 是 | Double |
| discountedSubtotal | 折后金额 | 是 | Double |
| serviceMemo | 附加信息备注 | 否 | String |
| discount | 折扣 | 是 | Double |
| empNameStr | 服务项目明细对应修理工名称组装字符串 | 否 | String |
| favourableVoList | 优惠明细 | 否 | List<FavourableDetailPrintAttribute> |
**PartialPartPrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| partName | 材料名称 | 是 | String |
| partShowName | 材料名称(全) | 是 | String |
| partBrand | 配件品牌 | 是 | String |
| unit | 单位 | 是 | String |
| number | 数量 | 是 | Double |
| isMember | 当前项目是否使用会员 | 是 | Integer |
| price | 价格(单价) | 是 | Double |
| subtotal | 金额(材料金额) | 是 | Double |
| discount | 折扣 | 是 | Double |
| singleFavourable | 优惠金额 | 是 | Double |
| discountedSubtotal<br/> | 折后金额 | 是 | Double |
| partMemo<br/> | 备注 | 否 | String |
| isBring<br/> | 是否自带 | 是 | String |
| empNameStr<br/> | 明细对应修理工名称组装字符串 | 否 | String |
| outStockEmployeeName<br/> | 领料人 | 否 | String |
| labelName<br/> | 业务分类名称 | 是 | String |
| idPart<br/> | 配件材料pk | 是 | BigInteger |
| idInfo<br/> | 本地材料id | 是 | String |
| favourableVoList | 优惠明细 | 否 | List<FavourableDetailPrintAttribute> |
**ExtraChargePrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| extraName | 附加费名称 | 是 | String |
| subtotal | 金额 | 是 | Double |
| memo | 备注 | 否 | String |
**FavourableDetailPrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| discountType | 优惠类型 | 是 | Integer |
| discountTypeName | 优惠类型名称 | 是 | Double |
| amount | 优惠金额 | 是 | Double |
**ExtendedModulePrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| module | 打印模块,PrintModuleEnum.code | 是 | Integer |
| name | 项目/材料/附加费名称 | 是 | String |
| number | 项目对应工时,材料对应数量,附加费为“-” | 是 | Double |
| price | 项目对应工时单价,材料对应材料单价,,附加费为“-” | 是 | Double |
| subtotal | 项目对应工时费,材料对应材料费,附加费对应为金额 | 是 | Double |
| discount | 项目对应折扣,材料对应折扣,附加费为“1.00” | 是 | Double |
| discountedSubtotal | 项目对应折后金额,材料对应材料费折后金额,,附加费对应为金额 | 是 | Double |
| favourableVoList | 优惠明细 | 否 | List<FavourableDetailPrintAttribute> |
**PrintModuleEnum 打印模块枚举**
| **key** | **code** | **name** |
| --- | --- | --- |
| MAN\_HOUR\_COST | 1 | 工时费模块 |
| MATERIAL\_COST | 2 | 材料费模块 |
| EXTRA\_COST | 3 | 附加费模块 |
| OTHER\_COST | 4 | 其他费用模块 |
| SERVICE\_COST | 5 | 服务费用模块 |
# 工具类jar包附件下载
[附件: print-core-1.0.7.jar](./attachments/JUhN4OhWjosFCdDg/print-core-1.0.7.jar)
### 数字金额转中文方法调用示例:
**数字金额**$P{amount}==null?BigDecimal.ZERO:$P{amount} **转中文**com.f6car.printserver.core.CharacterUtil.chinese($P{amount}==null?BigDecimal.ZERO:$P{amount})
> 更新: 2024-10-17 15:07:46 原文: <https://xcz.yuque.com/ombipo/rpc7ms/qz7dm6e8b06r7t70>
@@ -0,0 +1,64 @@
# 调拨单打印
# 采购单模版打印说明
* 前端调用接口
/stock/allot/print?idAllot=
* 打印使用模版
模版编码=allotInPrint
![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/4EZlweZ0zW4mZqxA/img/43819a24-8744-4fc0-96c6-0bba66f2e0b3.png)
* 打印模版参数
HashMap<String, Object> resultMap
| 字段 | 说明 | 备注 |
| --- | --- | --- |
| printFlag | 均价门店 false<br>批次门店 true | |
| showPrice | 参配-打印调拨价格及金额<br>勾选 true<br>未勾选 false | |
| orgName | 打印抬头<br>调入门店打印:XXX调入单<br>调出门店打印:XXX调出单 | |
| billNo | 调拨单号<br>调入门店打印:DRDXXX<br>调出门店打印:DCDXXX | |
| idOwnOrg | 门店ID<br>调入门店打印:调入门店ID<br>调出门店打印:调出门店ID | |
| naOrgIn | 调入门店名称 | |
| naOrgOut | 调出门店名称 | |
| detailAddress | 供应商详细地址 | |
| statusName | 调拨单状态(制单/待发货/待收货/已完成) | |
| billDate | 单据日期(yyyy-MM-dd | |
| creatorName | 创建单名称 | |
| nowDateTime | 打印时间(yyyy-MM-dd HH:mm | |
| remark | 备注信息 | |
| showRemark | 是否显示备注<br>备注为空 false<br>备注不为空 true | |
| sumNumber | 总数量 | |
| sumAmount | 总金额<br>价格脱敏时显示 \*\*\*\* | |
| printCount | 打印次数 | |
| allotDetailVoList | 调拨单明细行 | |
| sortNumber | 序号 | |
| partShowName | 材料组合名称 | |
| customCode | 材料自定义编码 | |
| partName | 材料名称 | 20250731追加 |
| partBrand | 材料品牌 | 20250731追加 |
| supplierCode | 材料零件号 | 20250731追加 |
| standard | 材料规格型号 | 20250731追加 |
| carNo | 车牌 | 20250731追加 |
| defSeat | 货位<br>调入门店打印:调入门店货位<br>调出门店打印:调出门店货位 | |
| unit | 单位 | |
| price | 单价<br>价格脱敏时显示 \*\*\*\* | |
| numCus | 均价门店<br>  调拨个数<br>批次门店&制单&未选择批次<br>   调拨个数<br>批次门店<br>   选择或使用的批次个数 | |
| amount | 均价门店<br>  调拨明细行金额<br>批次门店&制单&未选择批次<br>   调拨明细行金额<br>批次门店<br>   选择或使用的批次个数\*调拨单单价<br>备注:价格脱敏时显示 \*\*\*\* | |
| orderNo | 批次号 | |
| productDate | 批次生成日期 | |
| 明细合计行 | | |
| sortNumber | 合计行 | |
| partShowName | "" | |
| unit | "" | |
| numCus | 总数量 | |
| amount | 总金额<br>价格脱敏时显示 \*\*\*\* | |
| defSeat | "" | |
| stockNumber | "" | |
@@ -0,0 +1,452 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.3.1.final using JasperReports Library version 6.3.1 -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="Blank_A4" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="3fa22123-efc4-4f3f-a186-6a8f692d17e6">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<subDataset name="List1" uuid="7366c5be-288c-41c7-b295-b8d023ec81ae">
<queryString>
<![CDATA[]]>
</queryString>
<field name="index" class="java.lang.String"/>
<field name="serviceName" class="java.lang.String"/>
<field name="cooperationServiceName" class="java.lang.String"/>
<field name="cooperationOrgName" class="java.lang.String"/>
<field name="auditStatus" class="java.lang.String"/>
<field name="cooperationCost" class="java.math.BigDecimal"/>
</subDataset>
<parameter name="tradingStoreName" class="java.lang.String"/>
<parameter name="tradingTime" class="java.lang.String"/>
<parameter name="idSource" class="java.lang.String"/>
<parameter name="cardName" class="java.lang.String"/>
<parameter name="memberNo" class="java.lang.String"/>
<parameter name="noCar" class="java.lang.String"/>
<parameter name="customerName" class="java.lang.String"/>
<parameter name="paymentTypeAndAmount" class="java.lang.String"/>
<parameter name="orgOfPrinting" class="java.lang.String"/>
<parameter name="dateOfPrinting" class="java.lang.String"/>
<parameter name="amount" class="java.lang.String">
<defaultValueExpression><![CDATA[$P{amount}]]></defaultValueExpression>
</parameter>
<queryString>
<![CDATA[]]>
</queryString>
<detail>
<band height="97" splitType="Stretch">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="10" width="555" height="40" uuid="7c3a0deb-dcb0-406e-ba9c-9f279e1518b0">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="16" isBold="true"/>
</textElement>
<textFieldExpression><![CDATA["会员卡交易单"]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="210" y="60" width="178" height="18" uuid="6849ccdb-a3a0-4d32-a556-7a8fd5a19cca">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{tradingTime}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="437" y="60" width="118" height="18" uuid="b347e5b3-390c-44c3-b746-481aa6dc808e">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{idSource}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="60" width="90" height="18" uuid="92646184-6d70-4b6e-9f0e-23b4a7b15fb5">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{tradingStoreName}]]></textFieldExpression>
</textField>
<staticText>
<reportElement key="" x="389" y="60" width="48" height="18" isRemoveLineWhenBlank="true" uuid="86735eee-0435-4238-9b93-c08d86ed318f">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[交易单号]]></text>
</staticText>
<staticText>
<reportElement x="0" y="60" width="50" height="18" uuid="fef0343d-31f4-4b1c-a521-1a1b736aa485">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[交易门店]]></text>
</staticText>
<staticText>
<reportElement x="140" y="60" width="70" height="18" uuid="627d90fe-c235-45d8-9f4a-92f8d7ec0de7">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[交易日期]]></text>
</staticText>
<line>
<reportElement x="0" y="60" width="555" height="1" uuid="329fa736-2c18-4d9d-8fab-f54846315633">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
<staticText>
<reportElement x="389" y="78" width="48" height="18" uuid="7af4c3ab-ae72-4f6b-8b40-7b3129bd7eff">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[车牌号]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="437" y="78" width="118" height="18" uuid="8971461a-8c2e-4c4e-8c3f-9bd177df4b7e">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{noCar}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="78" width="50" height="18" uuid="74602da5-9195-47dd-9416-9cb229aeccad">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[卡名称]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="185" y="78" width="94" height="18" uuid="49312902-8cda-4fe3-a92f-d6ecbf9cb893">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{memberNo}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="140" y="78" width="44" height="18" uuid="65483940-e0c7-4713-9e9a-6a5a7294d248">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[卡号]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="78" width="90" height="18" uuid="e9c8328d-22ea-4e66-a23f-5015ffbd8248">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{cardName}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="279" y="78" width="40" height="18" uuid="19b4e87c-4a50-42ef-bfd0-7ca0536d5526">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[持卡人]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="319" y="78" width="70" height="18" uuid="86c056e0-0ed9-4024-b868-4133a06c5bab">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{customerName}]]></textFieldExpression>
</textField>
<line>
<reportElement x="0" y="78" width="555" height="1" uuid="f6716829-054d-4fed-a12c-2f4d227724c7">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Dotted"/>
</graphicElement>
</line>
<line>
<reportElement x="0" y="96" width="555" height="1" uuid="9c9a29fc-1a65-4869-8d03-3242ff1fe667">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="78">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="36" width="50" height="18" uuid="2cf35160-a982-4dcc-881b-c53adcf3761a">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.5" lineStyle="Solid"/>
<topPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<leftPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<bottomPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
<rightPen lineWidth="0.0" lineStyle="Solid" lineColor="#000000"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[退款方式:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="0" width="120" height="18" uuid="6b4cf2c5-cb81-43b6-82c9-ee238923a378">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="10" isBold="true"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[退卡金额(元)]]></text>
</staticText>
<staticText>
<reportElement x="0" y="17" width="50" height="18" uuid="782a08a7-c605-4a2f-83b7-4e3ad8ee3c4c">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[退款金额]]></text>
</staticText>
<line>
<reportElement x="0" y="35" width="555" height="1" uuid="8699bef5-cebd-4ebc-bb34-3697568375f7">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Dotted"/>
</graphicElement>
</line>
<line>
<reportElement stretchType="RelativeToTallestObject" x="0" y="18" width="555" height="1" uuid="18287ba4-b04b-4846-9122-3679f693685c">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Dotted"/>
</graphicElement>
</line>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="37" width="504" height="16" uuid="4ff3b629-b9e5-49c5-ad89-ce856a556839">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{paymentTypeAndAmount}]]></textFieldExpression>
</textField>
<line>
<reportElement x="0" y="54" width="555" height="1" uuid="98b923f9-1e14-4cc5-b508-12141cefb96e">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.4" lineStyle="Solid"/>
</graphicElement>
</line>
<staticText>
<reportElement x="0" y="55" width="50" height="18" uuid="fc27cb3d-0e5d-4143-b83d-daf432fa56c8">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="false"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[打印门店]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="55" width="110" height="18" uuid="1a73fa76-4113-4dcd-803c-da26e934f441"/>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{orgOfPrinting}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="160" y="55" width="80" height="18" uuid="786b4cf3-7636-455c-bf05-115ff8ca71b7">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="9" isBold="false"/>
<paragraph leftIndent="5"/>
</textElement>
<text><![CDATA[打印日期]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="240" y="55" width="314" height="18" uuid="61b50ddd-d880-403c-855f-3ae2f0403592"/>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{dateOfPrinting}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="50" y="18" width="504" height="16" uuid="7ce3fef6-73c6-49e9-a676-64e402603d93"/>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$P{amount}]]></textFieldExpression>
</textField>
</band>
<band height="39">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</band>
</detail>
</jasperReport>
@@ -0,0 +1,3 @@
# 采购单/采购退模版打印
[《采购单/采购退打印》](https://alidocs.dingtalk.com/i/nodes/7NkDwLng8ZMajY9YijOwlz4bJKMEvZBY)
@@ -0,0 +1,922 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.3.1.final using JasperReports Library version 6.3.1 -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="Blank_A4" pageWidth="222" pageHeight="352" whenNoDataType="NoPages" columnWidth="202" leftMargin="10" rightMargin="10" topMargin="0" bottomMargin="10" isIgnorePagination="true" uuid="5195ed21-e8c1-4daf-ab55-5f5dc9c07b0a">
<property name="com.jaspersoft.studio.unit." value="pixel"/>
<property name="com.jaspersoft.studio.unit.topMargin" value="mm"/>
<property name="com.jaspersoft.studio.unit.bottomMargin" value="mm"/>
<property name="com.jaspersoft.studio.unit.leftMargin" value="mm"/>
<property name="com.jaspersoft.studio.unit.rightMargin" value="mm"/>
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<property name="ireport.zoom" value="1.0"/>
<property name="ireport.x" value="0"/>
<property name="ireport.y" value="0"/>
<property name="com.jaspersoft.studio.unit.pageWidth" value="mm"/>
<property name="com.jaspersoft.studio.unit.pageHeight" value="pixel"/>
<property name="com.jaspersoft.studio.unit.columnWidth" value="pixel"/>
<property name="com.jaspersoft.studio.unit.columnSpacing" value="pixel"/>
<subDataset name="Dataset1" uuid="7e6cf9c7-d927-4f84-a0f6-e84863998e11">
<property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/>
<queryString>
<![CDATA[]]>
</queryString>
<field name="subtotal" class="java.math.BigDecimal"/>
<field name="serviceName" class="java.lang.String"/>
<field name="unusedNumber" class="java.lang.Integer"/>
<field name="number" class="java.lang.Integer"/>
<field name="isMember" class="java.lang.Integer"/>
</subDataset>
<subDataset name=" Dataset2" uuid="7dae8b10-73a8-43eb-a81d-296b60197ab3">
<field name="partName" class="java.lang.String"/>
<field name="number" class="java.math.BigDecimal"/>
<field name="subtotal" class="java.math.BigDecimal"/>
<field name="isMember" class="java.lang.Integer"/>
</subDataset>
<parameter name="title" class="java.lang.String"/>
<parameter name="abbreviation" class="java.lang.String"/>
<parameter name="billNo" class="java.lang.String">
<parameterDescription><![CDATA[]]></parameterDescription>
</parameter>
<parameter name="printTime" class="java.lang.String"/>
<parameter name="customerName" class="java.lang.String"/>
<parameter name="cellPhone" class="java.lang.String"/>
<parameter name="carNo" class="java.lang.String"/>
<parameter name="orgName" class="java.lang.String"/>
<parameter name="amountAll" class="java.math.BigDecimal"/>
<parameter name="memo" class="java.lang.String"/>
<parameter name="settlePerson" class="java.lang.String"/>
<parameter name="employeeName" class="java.lang.String"/>
<parameter name="billDate" class="java.lang.String"/>
<parameter name="detailAddress" class="java.lang.String"/>
<parameter name="contactMobile" class="java.lang.String"/>
<parameter name="partList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="transactionDate" class="java.lang.String"/>
<parameter name="naEmployee" class="java.lang.String"/>
<parameter name="mileage" class="java.math.BigDecimal"/>
<parameter name="deliveryTime" class="java.lang.String"/>
<parameter name="vin" class="java.lang.String"/>
<parameter name="carNoWhole" class="java.lang.String"/>
<parameter name="naCustomer" class="java.lang.String"/>
<parameter name="carModelShort" class="java.lang.String"/>
<parameter name="vipExpense" class="java.math.BigDecimal"/>
<parameter name="serviceFavourable" class="java.math.BigDecimal"/>
<parameter name="partFavourableCommonTotal" class="java.math.BigDecimal"/>
<parameter name="oweAmount" class="java.math.BigDecimal"/>
<parameter name="serviceFavourableCommonTotal" class="java.math.BigDecimal"/>
<parameter name="czkExpense" class="java.math.BigDecimal"/>
<parameter name="partinfoFavourable" class="java.math.BigDecimal"/>
<parameter name="discountFavourable" class="java.math.BigDecimal"/>
<parameter name="pointFavourable" class="java.math.BigDecimal"/>
<parameter name="couponFavourable" class="java.math.BigDecimal"/>
<parameter name="balanceStatus" class="java.lang.String"/>
<parameter name="gatheringFavourable" class="java.math.BigDecimal"/>
<parameter name="payItemTogether" class="java.lang.String"/>
<parameter name="orgContactMobile" class="java.lang.String"/>
<parameter name="orgDetailAddress" class="java.lang.String"/>
<parameter name="printOrgName" class="java.lang.String"/>
<parameter name="serviceList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="firstSettlementTime" class="java.lang.String"/>
<parameter name="extraCostTotal" class="java.math.BigDecimal">
<parameterDescription><![CDATA[]]></parameterDescription>
</parameter>
<parameter name="extraChargeList" class="net.sf.jasperreports.engine.data.JRMapCollectionDataSource"/>
<parameter name="orgContactNumber" class="java.lang.String"/>
<parameter name="czkDetailInfo" class="java.lang.String"/>
<parameter name="creatorName" class="java.lang.String"/>
<parameter name="totalStuffNum" class="java.lang.String"/>
<parameter name="stuffSubtotalAll" class="java.math.BigDecimal"/>
<parameter name="stuffSubtotalVip" class="java.math.BigDecimal"/>
<queryString>
<![CDATA[]]>
</queryString>
<detail>
<band height="20" splitType="Stretch">
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="202" height="20" isPrintWhenDetailOverflows="true" uuid="fdd7c75d-7f0c-42a3-afa1-9927522a4dd1"/>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="黑体" size="16" isBold="false"/>
</textElement>
<textFieldExpression><![CDATA[($P{printOrgName}==null?$P{orgName}:($P{printOrgName}.isEmpty()?$P{orgName}:$P{printOrgName}))+"销售单"]]></textFieldExpression>
</textField>
</band>
<band height="20">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="4" width="51" height="16" uuid="8210a1bf-fb5f-465d-8e13-15ee265db665">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[工单号:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="51" y="4" width="151" height="16" uuid="e79b3ef6-3727-46d0-8de1-035b71f358f4">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<textFieldExpression><![CDATA[$P{billNo}]]></textFieldExpression>
</textField>
</band>
<band height="16">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="2" width="51" height="12" uuid="c34510ca-1706-47a6-bd9f-f78a472136a9">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Top">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[销售人员:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="51" y="2" width="150" height="12" uuid="13df7d48-2be4-4a9f-848b-6a1fa1573a2e">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<textFieldExpression><![CDATA[$P{naEmployee}]]></textFieldExpression>
</textField>
</band>
<band height="16">
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="31" y="2" width="60" height="12" uuid="62146321-e5d6-4c36-b91c-db8c0a5653de">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体"/>
<paragraph leftIndent="0"/>
</textElement>
<textFieldExpression><![CDATA[$P{naCustomer}.substring(0, 1) + "(*^_^*)" + $P{naCustomer}.substring($P{naCustomer}.length() > 10 ? 10 : $P{naCustomer}.length(), $P{naCustomer}.length())]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="91" y="2" width="110" height="12" uuid="8c918e6d-69a1-4dc3-9336-f54cc900e659">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{cellPhone}.length() <= 3 ? $P{cellPhone}: $P{cellPhone}.substring(0, 3) + "(*^_^*)" + $P{cellPhone}.substring($P{cellPhone}.length() > 7 ? 7 : $P{cellPhone}.length(), $P{cellPhone}.length())]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="2" width="31" height="12" uuid="16d36e31-361b-4fee-a18e-a4ad7991e6c6">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[客户:]]></text>
</staticText>
</band>
<band height="14">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="2" width="31" height="12" uuid="53391abc-4be2-40a8-8c2d-a770e6c13f18">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[车牌:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="31" y="2" width="171" height="12" uuid="de1db383-eb89-4a11-acd1-ec590d10da96">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{carNoWhole}]]></textFieldExpression>
</textField>
</band>
<band height="4">
<line>
<reportElement x="3" y="3" width="196" height="1" uuid="fcbb9490-ed14-4cb0-8117-6d17915d2403">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.3" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="12">
<printWhenExpression><![CDATA[$P{partList}!=null]]></printWhenExpression>
<staticText>
<reportElement x="0" y="0" width="95" height="12" uuid="c3e05a21-6ba4-4708-991a-825a994a1b2f">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[材料]]></text>
</staticText>
<staticText>
<reportElement x="95" y="0" width="53" height="12" uuid="07662de2-32dd-4e19-9f70-9b1f2927b7ab">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[数量]]></text>
</staticText>
<staticText>
<reportElement x="148" y="0" width="54" height="12" uuid="9c43fe3b-01e3-4683-935a-993973d42461">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[材料费(元)]]></text>
</staticText>
</band>
<band height="12">
<printWhenExpression><![CDATA[$P{partList}!=null]]></printWhenExpression>
<componentElement>
<reportElement x="0" y="0" width="202" height="12" uuid="d0a60b3d-9ead-4191-b6c1-b76521bf47f6">
<property name="com.jaspersoft.studio.layout" value="com.jaspersoft.studio.editor.layout.VerticalRowLayout"/>
<property name="com.jaspersoft.studio.table.style.table_header" value="Table_TH"/>
<property name="com.jaspersoft.studio.table.style.column_header" value="Table_CH"/>
<property name="com.jaspersoft.studio.table.style.detail" value="Table_TD"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<jr:table xmlns:jr="http://jasperreports.sourceforge.net/jasperreports/components" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports/components http://jasperreports.sourceforge.net/xsd/components.xsd" whenNoDataType="Blank">
<datasetRun subDataset=" Dataset2" uuid="2d38cb48-be8c-41f9-a529-866d4b65253f">
<dataSourceExpression><![CDATA[$P{partList}]]></dataSourceExpression>
</datasetRun>
<jr:column width="95" uuid="13512459-4d71-4edc-b745-751e555e284e">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column1"/>
<jr:detailCell height="12">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="95" height="12" uuid="8d7ef19e-5295-4cfa-ba8e-dfac9dc5029c"/>
<box topPadding="2" bottomPadding="2"/>
<textElement>
<font fontName="黑体"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$F{partName}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="53" uuid="eb394fd2-532e-4da9-8058-785d644277ac">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column2"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="12">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="53" height="12" uuid="31f7ac04-d923-44d2-85ed-9c2d5f22d3b3"/>
<box topPadding="2" bottomPadding="2"/>
<textElement textAlignment="Left">
<font fontName="黑体"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$F{number}]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
<jr:column width="54" uuid="02e06895-4360-460c-ad3a-d4036bde9323">
<property name="com.jaspersoft.studio.components.table.model.column.name" value="Column3"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<jr:detailCell height="12">
<property name="com.jaspersoft.studio.unit.width" value="px"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="54" height="12" uuid="544a881f-1055-4089-a49d-e1c979d0b452"/>
<box topPadding="2" bottomPadding="2"/>
<textElement textAlignment="Left">
<font fontName="黑体"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$F{isMember} == 1 ? "0.00" :$F{subtotal}.setScale( 2, BigDecimal.ROUND_HALF_EVEN )]]></textFieldExpression>
</textField>
</jr:detailCell>
</jr:column>
</jr:table>
</componentElement>
</band>
<band height="4">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{extraChargeList}!=null && $P{partList}!=null]]></printWhenExpression>
<line>
<reportElement x="0" y="3" width="196" height="1" uuid="e343ed32-d402-484e-9550-a3f92a5f1d95">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Dashed"/>
</graphicElement>
</line>
</band>
<band height="12">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<printWhenExpression><![CDATA[$P{extraChargeList}!=null]]></printWhenExpression>
<staticText>
<reportElement x="0" y="0" width="51" height="12" uuid="ab72a883-2de5-4b77-974f-86ce04a85321">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.0"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="10"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[数量小计:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="51" y="0" width="98" height="12" uuid="fd7e12a8-bb87-4181-b961-362f5011d2c7">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.0"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="10"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{totalStuffNum}]]></textFieldExpression>
</textField>
</band>
<band height="36">
<staticText>
<reportElement x="62" y="0" width="5" height="12" isRemoveLineWhenBlank="true" uuid="4b3ef70a-6f98-4270-996d-71c65a31b4b7">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.0"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="微软雅黑" size="10"/>
<paragraph spacingBefore="2"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="67" y="0" width="82" height="12" uuid="c234c01c-7802-4177-9cf1-ddb4b3a64380">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.0"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="10"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{stuffSubtotalAll}.subtract($P{stuffSubtotalVip}).setScale( 2, BigDecimal.ROUND_HALF_EVEN )]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="0" width="62" height="12" uuid="0ee54c87-b5c2-43bf-a362-9c79fbd020b2">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.0"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="10"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[材料费小计:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="56" y="12" width="93" height="12" uuid="651ea460-07ba-4514-b29e-2f07cea1c7b5">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.0"/>
</box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="黑体" size="10"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{partFavourableCommonTotal}.setScale( 2, BigDecimal.ROUND_HALF_EVEN )]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="51" y="12" width="5" height="12" uuid="ec51a4b4-84d6-4896-9b51-1bcb63db905f">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.0"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="微软雅黑" size="10"/>
<paragraph spacingBefore="2"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<staticText>
<reportElement x="0" y="12" width="51" height="12" uuid="3b7564d7-fd10-45ae-b35d-8b9e7996b37b">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.0"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="10"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[优惠小计:]]></text>
</staticText>
<staticText>
<reportElement x="70" y="24" width="5" height="12" uuid="df93cf01-d9b9-4e5a-9b54-ecb6d1500ffc">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.0"/>
</box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font fontName="微软雅黑" size="10"/>
<paragraph spacingBefore="2"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="75" y="24" width="74" height="12" uuid="695cf981-09b0-4b5b-8634-6a6e80812934">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.0"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="10"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{stuffSubtotalAll}.subtract($P{stuffSubtotalVip}).subtract($P{partFavourableCommonTotal}).setScale( 2, BigDecimal.ROUND_HALF_EVEN )]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="24" width="70" height="12" uuid="8745ff2b-c3e7-4dfe-8c42-3b62f1d198ca">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<box>
<pen lineWidth="0.0"/>
</box>
<textElement verticalAlignment="Middle">
<font fontName="黑体" size="10"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[折后金额小计:]]></text>
</staticText>
</band>
<band height="6">
<printWhenExpression><![CDATA[$P{partList}!=null || $P{serviceList}!=null || $P{extraChargeList}!=null]]></printWhenExpression>
<line>
<reportElement x="0" y="5" width="196" height="1" uuid="92031c32-47e1-4778-9696-acfd36cea5c1">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="17">
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="56" y="4" width="45" height="12" isPrintWhenDetailOverflows="true" uuid="429e5e34-e7cc-4b2e-a7ec-f9bdc72268ce">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{amountAll}.subtract($P{vipExpense}).setScale( 2, BigDecimal.ROUND_HALF_EVEN )]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="0" y="4" width="51" height="12" uuid="91cf76df-ccf1-417b-8ad1-73b7e1be2306">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[合计金额:]]></text>
</staticText>
<staticText>
<reportElement key="" x="101" y="4" width="51" height="12" uuid="0dc06605-4e07-4c3c-855e-22f496455a6a">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<printWhenExpression><![CDATA[$P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable}).setScale( 2, BigDecimal.ROUND_HALF_EVEN ).compareTo(BigDecimal.ZERO) != 0]]></printWhenExpression>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[优惠金额:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="157" y="4" width="45" height="12" isPrintWhenDetailOverflows="true" uuid="a7ee1bfa-c68a-441d-8cde-368dd1642208">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<printWhenExpression><![CDATA[$P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable}).setScale( 2, BigDecimal.ROUND_HALF_EVEN ).compareTo(BigDecimal.ZERO) != 0]]></printWhenExpression>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable}).setScale( 2, BigDecimal.ROUND_HALF_EVEN )]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="152" y="5" width="5" height="12" uuid="5334ca45-b9a7-4bda-949c-8532255cbf73">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<printWhenExpression><![CDATA[$P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable}).setScale( 2, BigDecimal.ROUND_HALF_EVEN ).compareTo(BigDecimal.ZERO) != 0]]></printWhenExpression>
</reportElement>
<textElement verticalAlignment="Middle">
<font isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<staticText>
<reportElement x="51" y="5" width="5" height="12" uuid="93dba0ec-ff63-4524-ad00-7677de202fb0">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font isBold="false" isUnderline="false"/>
<paragraph leftIndent="0"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
</band>
<band height="13">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="0" width="51" height="12" uuid="ad0a3fc9-c823-44f3-90bb-ec4358472ece">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[实付金额:]]></text>
</staticText>
<staticText>
<reportElement key="" x="51" y="1" width="5" height="12" uuid="48a4a2fc-5e83-43b5-9faf-593ced47fba0">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="56" y="0" width="45" height="12" isPrintWhenDetailOverflows="true" uuid="d71691b9-8665-4b03-ba8b-e3dc6081afd2">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{balanceStatus}.equals("7000")?"0.00":($P{amountAll}.subtract($P{vipExpense}).subtract($P{oweAmount}).subtract($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).subtract($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).
subtract($P{serviceFavourable}).subtract($P{partinfoFavourable}).subtract($P{discountFavourable}).subtract($P{couponFavourable}).subtract($P{pointFavourable}).subtract($P{gatheringFavourable}).setScale( 2, BigDecimal.ROUND_HALF_EVEN ))]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="101" y="0" width="51" height="12" uuid="60a16c6f-0ae8-412f-94ea-bcf27f9ae0a3">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[待付金额:]]></text>
</staticText>
<staticText>
<reportElement x="152" y="1" width="5" height="12" uuid="18cf2b7d-cf81-477b-8ef7-1ec2b9a3cb2b">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="157" y="0" width="45" height="12" isPrintWhenDetailOverflows="true" uuid="ce222378-cff4-47d0-9658-edb83a64f33e">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{balanceStatus}.equals("7000")?($P{amountAll}.subtract($P{vipExpense}).subtract($P{czkExpense}).subtract($P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable})).setScale( 2, BigDecimal.ROUND_HALF_EVEN )):($P{oweAmount}.setScale( 2, BigDecimal.ROUND_HALF_EVEN ))]]></textFieldExpression>
</textField>
</band>
<band height="13">
<printWhenExpression><![CDATA[$P{czkExpense}.compareTo(BigDecimal.ZERO) != 0]]></printWhenExpression>
<staticText>
<reportElement x="0" y="0" width="61" height="12" uuid="e3d118e6-8e4c-4562-a92f-b02171737fa4">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[储值卡消费:]]></text>
</staticText>
<staticText>
<reportElement x="61" y="1" width="5" height="12" uuid="dc416d41-3d4e-4ec2-8df2-8fd963d34b2f">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="SansSerif" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="67" y="0" width="45" height="12" isPrintWhenDetailOverflows="true" uuid="93fc5f6c-5c7c-4e75-9054-783e6dbbfb0d">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{czkExpense}.setScale( 2, BigDecimal.ROUND_HALF_EVEN )]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="112" y="0" width="90" height="12" isPrintWhenDetailOverflows="true" uuid="7b247b30-a5cc-4d9a-84e2-24128a6b4775">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" size="9" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{czkDetailInfo}==null?"":"(" + $P{czkDetailInfo} + ")"]]></textFieldExpression>
</textField>
</band>
<band height="17">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="0" width="51" height="12" uuid="2f9041fb-4bfa-43a0-9642-e429c87c8326">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[收银金额:]]></text>
</staticText>
<staticText>
<reportElement x="51" y="1" width="5" height="12" uuid="9f6a7ec2-4243-454c-a1b6-2adc655dd86a">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="SansSerif" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<text><![CDATA[¥]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="57" y="0" width="55" height="12" isPrintWhenDetailOverflows="true" uuid="507cb11e-dfc7-477d-ae35-49de03f644f1">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{balanceStatus}.equals("7000")?"0.00":($P{amountAll}.subtract($P{vipExpense}).subtract($P{oweAmount}).subtract($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).subtract($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).subtract($P{czkExpense}).
subtract($P{serviceFavourable}).subtract($P{partinfoFavourable}).subtract($P{discountFavourable}).subtract($P{couponFavourable}).subtract($P{pointFavourable}).subtract($P{gatheringFavourable}).setScale( 2, BigDecimal.ROUND_HALF_EVEN ).toString())]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="112" y="0" width="90" height="12" isPrintWhenDetailOverflows="true" uuid="1d396dda-a308-4062-addd-bf1e6d367ef0">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" size="9" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{balanceStatus}.equals("7000")? "" : $P{payItemTogether} == null ? "" : "("+$P{payItemTogether}+")"]]></textFieldExpression>
</textField>
</band>
<band height="1">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<line>
<reportElement x="0" y="0" width="196" height="1" uuid="232d5e4a-f361-483f-a3d5-b5ebe29dc22e">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.5" lineStyle="Solid"/>
</graphicElement>
</line>
</band>
<band height="47">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<staticText>
<reportElement x="0" y="4" width="53" height="12" uuid="1d22bc87-0fc0-4f8f-8c98-62adcc943822">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[客户签名:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="30" width="31" height="12" uuid="caf074dd-5c62-4224-a0d8-ef4380188ecd">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[备注:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="31" y="30" width="81" height="12" uuid="049afdd2-76f6-4be7-86e9-b213c9fbf31a">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<textFieldExpression><![CDATA[$P{memo}]]></textFieldExpression>
</textField>
<staticText>
<reportElement x="112" y="30" width="31" height="12" uuid="bac0ce57-79c7-4cc7-842e-5f15284486a1">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[日期:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="143" y="30" width="59" height="12" isPrintWhenDetailOverflows="true" uuid="a8131af0-d94a-4b50-93f8-c187293efb33">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Top">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<textFieldExpression><![CDATA[$P{deliveryTime}.isEmpty()?"":($P{deliveryTime}.substring( 0, 10))]]></textFieldExpression>
</textField>
</band>
<band height="1">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<line>
<reportElement x="0" y="0" width="202" height="1" uuid="c936fb70-4d8c-405d-ab85-2e8916a715c1">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
</reportElement>
<graphicElement>
<pen lineWidth="0.3"/>
</graphicElement>
</line>
</band>
<band height="20">
<staticText>
<reportElement x="0" y="4" width="51" height="16" uuid="d47fdd5c-eb46-4023-8e59-19de6fe9f43a">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[门店名称:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="51" y="6" width="151" height="12" uuid="519ca386-a8c3-47ee-bbe8-6046d7a8512b">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<textFieldExpression><![CDATA[$P{orgName}]]></textFieldExpression>
</textField>
</band>
<band height="16">
<staticText>
<reportElement x="0" y="0" width="51" height="16" uuid="477d09a8-4cd9-4a13-88eb-65a6f70e13a6">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[联系电话:]]></text>
</staticText>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="51" y="0" width="151" height="16" uuid="5fe5a641-383e-488e-b59b-604282cdb856">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<textFieldExpression><![CDATA[$P{orgContactMobile}==null?"":$P{orgContactMobile}]]></textFieldExpression>
</textField>
</band>
<band height="16">
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
<textField isStretchWithOverflow="true" isBlankWhenNull="true">
<reportElement x="0" y="0" width="202" height="16" uuid="8d8ad542-4e85-4c3c-93b1-dcf7fcb38720">
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
<property name="com.jaspersoft.studio.unit.height" value="pixel"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<textFieldExpression><![CDATA[$P{orgDetailAddress}]]></textFieldExpression>
</textField>
</band>
<band height="12">
<staticText>
<reportElement x="62" y="0" width="51" height="12" uuid="fca6fa0d-2eaf-42f4-986c-62724d5e6e17">
<property name="com.jaspersoft.studio.unit.width" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="3"/>
</textElement>
<text><![CDATA[打印时间:]]></text>
</staticText>
<textField isBlankWhenNull="true">
<reportElement x="113" y="0" width="89" height="12" uuid="a987d0cf-8310-4b16-b278-5eeadb07c893">
<property name="com.jaspersoft.studio.unit.y" value="pixel"/>
<property name="com.jaspersoft.studio.unit.x" value="pixel"/>
</reportElement>
<textElement>
<font fontName="黑体" isBold="false"/>
<paragraph leftIndent="0"/>
</textElement>
<textFieldExpression><![CDATA[$P{printTime}.substring( 0, 16)]]></textFieldExpression>
</textField>
</band>
</detail>
</jasperReport>
@@ -0,0 +1,13 @@
{
"kb_id": "db5e3844c382439dba91c29f4f29eeca",
"user_id": "d198ae3b32cd49f09736c4290dd1223a",
"name": "测试csv",
"description": "csv",
"created_at": "2026-05-23T15:31:44.119922+00:00",
"updated_at": "2026-05-23T15:31:44.119922+00:00",
"fields": [],
"templates": [],
"file_count": 0,
"chunk_count": 0,
"parse_status": "empty"
}
@@ -0,0 +1,5 @@
{
"user_id": "d198ae3b32cd49f09736c4290dd1223a",
"name": "默认用户",
"created_at": "2026-05-23T12:19:43.785929+00:00"
}
+12
View File
@@ -0,0 +1,12 @@
[
{
"user_id": "d198ae3b32cd49f09736c4290dd1223a",
"name": "默认用户",
"created_at": "2026-05-23T12:19:43.785929+00:00"
},
{
"user_id": "2db10c2ebbf6434aab28035026e196c3",
"name": "SmokeTest",
"created_at": "2026-05-23T12:21:32.399217+00:00"
}
]
+2
View File
@@ -25,4 +25,6 @@
{fidelity_context}
{template_context}
立即生成修正后的 JRXML
+8 -1
View File
@@ -1,16 +1,23 @@
你是一位资深 JasperReports 工程师。当前有一个 JRXML 使用占位字段名($F{{field_1}}, $F{{field_2}}, ...),需要替换为从 OCR 提取的真实字段名。
**你必须基于已有的 JRXML 进行修改,而不是重新生成。保留所有现有的元素,只替换字段名。**
关键规则:
- 只输出完整修改后的 JRXML 代码,不要解释,不要 markdown 标记。
- **保留所有现有的 field、staticText、textField、band、reportElement 元素,一个都不能少。**
- **不要删除、简化、合并或重写任何元素。不要改动任何坐标值(x, y, width, height)。**
- 将每个 $F{{field_N}} 占位符替换为 OCR 提取结果中对应的真实字段名。
- 替换规则:根据列的顺序映射——$F{{field_1}} 对应第 1 列的 OCR 字段名,$F{{field_2}} 对应第 2 列,以此类推。
- 同时更新 <field name="..."> 声明和所有 $F{{...}} 表达式中的引用。
- 同时更新 field name="..." 声明和所有 $F{{...}} 表达式中的引用。
- 如果 OCR 提取的字段数少于占位字段数,保留多余的占位字段。
- 不要修改 band 结构、元素位置或大小。
- 确保 JRXML 兼容 JasperReports 7.0.6。
- **输出的 JRXML 字符数应与输入的 JRXML 大致相同(允许 ±15% 偏差),因为只替换字段名,不增删修改任何元素。**
当前 JRXML(含占位字段):
{current_jrxml}
{template_context}
OCR 提取的结构化字段:
{ocr_fields}
+2
View File
@@ -11,5 +11,7 @@ JRXML 必须兼容 JasperReports 7.0.6 schema。
参考模板和组件:
{context}
{template_context}
用户需求:
{user_request}
+2
View File
@@ -10,6 +10,8 @@
{ocr_context}
{template_context}
当前 JRXML
{current_jrxml}
+19 -13
View File
@@ -1,17 +1,23 @@
你是一位资深 JasperReports 工程师。当前有一个骨架 JRXML,需要根据精确的像素坐标调整每个元素的位置
你是一位 JRXML 坐标调整器。你的唯一任务是修改 <reportElement> 标签内的 x, y, width, height 属性值
关键规则:
- 只输出完整修改后的 JRXML 代码,不要解释,不要 markdown 标记。
- 根据提供的采样坐标,精确调整每个 textField/staticText 的 x, y, width, height。
- 表头行的坐标直接使用采样坐标中 header_row 对应列的 x, y, width, height。
- 数据行:根据 first_data_row 的坐标模式,向下插值生成剩余数据行(每行 y 递增行高)。
- 标题行(如有)和表尾行:保持其在骨架中的 y 位置大致不变,但调整 x 和 width 与列的采样坐标对齐。
- 不要修改字段名(保持 $F{{field_N}} 占位名不变)。
- 不要修改 band 结构。
- 确保 JRXML 兼容 JasperReports 7.0.6。
**这是 Band "{band_name}"(高度 {band_height}px)的第 {window_index}/{total_windows} 个窗口。你只看到该 band 的一个片段,不要尝试生成完整报表。**
当前骨架 JRXML
{current_jrxml}
严格规则
- 只修改 x, y, width, height。不改任何其他内容。
- 不添加、删除、重命名任何元素。
- 不修改文本内容(CDATA)、表达式(textFieldExpression)、样式属性。
- 只输出修改后的 XML 片段,不要解释,不要 markdown,不要代码块标记。
- 输出的字符数应与输入片段大致相同。
采样坐标(表头行 + 第一行数据行,像素位置)
坐标调整规则
- 表头行:直接使用 header_row 对应列的 x, y, width, height
- 数据行:根据 first_data_row 的坐标模式,向下插值(每行 y 递增行高)
- 标题行和表尾行:保持 y 位置大致不变,但调整 x 和 width 与列的采样坐标对齐
{template_context}
采样坐标参考:
{sampled_coordinates}
请调整以下片段的坐标:
{xml_fragment}
+2
View File
@@ -15,5 +15,7 @@
参考模板和组件:
{context}
{template_context}
用户需求:
{user_request}
+134
View File
@@ -0,0 +1,134 @@
"""初始化默认用户和预置知识库。
解析 rag/jrxml_source/ 下的全部 JRXML 模板 + 接口文档
创建默认用户 "默认用户" 和知识库 "F6-汽车维修打印默认知识库"
执行完整的 parse -> chunk -> embed 管线
用法:
python scripts/init_default_kb.py
python scripts/init_default_kb.py --force # 强制重建
"""
import os
import sys
import argparse
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from dotenv import load_dotenv
load_dotenv()
from backend.logger import get_logger
_log = get_logger("init_kb")
DEFAULT_USER_NAME = "默认用户"
DEFAULT_KB_NAME = "F6-汽车维修打印默认知识库"
DEFAULT_KB_DESC = "预置的汽车维修打印单模板与接口文档知识库"
def find_source_files(source_dir: Path) -> list[str]:
"""收集源目录下的所有 .jrxml 和 .md 文件。"""
files = []
if not source_dir.exists():
_log.warning("源目录不存在: %s", source_dir)
return files
for pattern in ("*.jrxml", "*.md", "**/*.jrxml", "**/*.md"):
for fp in source_dir.glob(pattern):
if fp.is_file():
files.append(str(fp))
return list(dict.fromkeys(files))
def init_default_kb(force: bool = False) -> dict:
"""初始化默认 KB,返回结果摘要。"""
from backend.kb_manager import create_user, list_users, create_kb, list_kbs
from backend.kb_parser import build_kb_from_files
# 1. 查找或创建默认用户
users = list_users()
default_user = None
for u in users:
if u.get("name") == DEFAULT_USER_NAME:
default_user = u
break
if default_user is None:
default_user = create_user(DEFAULT_USER_NAME)
_log.info("创建默认用户: %s", default_user["user_id"])
else:
_log.info("默认用户已存在: %s", default_user["user_id"])
user_id = default_user["user_id"]
# 2. 查找或创建默认 KB
user_kbs = list_kbs(user_id)
default_kb = None
for kb in user_kbs:
if kb.get("name") == DEFAULT_KB_NAME:
default_kb = kb
break
if default_kb is not None and force:
from backend.kb_manager import delete_kb
delete_kb(default_kb["kb_id"])
default_kb = None
_log.info("强制重建: 已删除旧 KB")
if default_kb is None:
default_kb = create_kb(user_id, DEFAULT_KB_NAME, DEFAULT_KB_DESC)
_log.info("创建默认知识库: %s", default_kb["kb_id"])
elif default_kb.get("parse_status") == "ready":
_log.info("默认知识库已就绪: chunks=%s", default_kb.get("chunk_count", 0))
return {"status": "already_ready", "kb_id": default_kb["kb_id"],
"user_id": user_id, "chunk_count": default_kb.get("chunk_count", 0)}
kb_id = default_kb["kb_id"]
# 3. 收集源文件
source_dir = PROJECT_ROOT / "rag" / "jrxml_source"
files = find_source_files(source_dir)
if not files:
_log.warning("未找到源文件,跳过构建")
return {"status": "no_files", "kb_id": kb_id, "user_id": user_id}
_log.info("找到 %d 个源文件", len(files))
# 4. 构建 KB
result = build_kb_from_files(kb_id, files)
_log.info("KB 构建完成: %s", result)
return {**result, "kb_id": kb_id, "user_id": user_id, "file_count": len(files)}
def main():
parser = argparse.ArgumentParser(description="初始化默认知识库")
parser.add_argument("--force", action="store_true", help="强制重建(删除已有 KB")
args = parser.parse_args()
print("=" * 60)
print("JRXML Agent - 默认知识库初始化")
print("=" * 60)
result = init_default_kb(force=args.force)
print(f"\n用户: {DEFAULT_USER_NAME}")
print(f"知识库: {DEFAULT_KB_NAME}")
print(f"状态: {result.get('status', 'unknown')}")
print(f"字段数: {result.get('field_count', 0)}")
print(f"模板数: {result.get('template_count', 0)}")
print(f"Chunk数: {result.get('chunk_count', 0)}")
if result.get("errors"):
print(f"错误: {len(result['errors'])}")
for e in result["errors"]:
print(f" - {e.get('file', '')}: {e.get('error', '')}")
print("\n初始化完成!")
if __name__ == "__main__":
main()
+2 -1
View File
@@ -104,9 +104,10 @@ def main():
subprocess.run(["npm", "install"], cwd=str(FRONTEND_DIR), check=True)
subprocess.Popen(
["npm", "run", "dev"],
"npm run dev",
cwd=str(FRONTEND_DIR),
creationflags=subprocess.CREATE_NO_WINDOW,
shell=True,
)
if not wait_port(5173):
print("[FAIL] Frontend did not start in time.")
+246
View File
@@ -265,3 +265,249 @@ class TestBoundaries:
json={"text": large_text, "file_ids": []},
) as resp:
assert resp.status_code == 200
# ── 用户 CRUD API ───────────────────────────────────────────────
class TestUserAPI:
@pytest.fixture(autouse=True)
def temp_kb_data(self, monkeypatch, tmp_path):
kb_data = tmp_path / "kb_data"
monkeypatch.setattr("backend.kb_manager.KB_DATA_DIR", kb_data)
monkeypatch.setattr("backend.kb_manager._USERS_FILE", kb_data / "users.json")
yield kb_data
def test_create_user(self, client):
resp = client.post("/api/users", json={"name": "测试用户"})
assert resp.status_code == 200
data = resp.json()
assert data["name"] == "测试用户"
assert len(data["user_id"]) >= 12
def test_create_user_empty_name_rejected(self, client):
resp = client.post("/api/users", json={"name": ""})
assert resp.status_code == 400
def test_list_users(self, client):
client.post("/api/users", json={"name": "A"})
client.post("/api/users", json={"name": "B"})
resp = client.get("/api/users")
assert resp.status_code == 200
assert len(resp.json()["users"]) == 2
def test_get_user(self, client):
uid = client.post("/api/users", json={"name": "张三"}).json()["user_id"]
resp = client.get(f"/api/users/{uid}")
assert resp.status_code == 200
assert resp.json()["name"] == "张三"
def test_get_user_not_found(self, client):
resp = client.get("/api/users/deadbeef1234567890abcd")
assert resp.status_code == 404
def test_delete_user(self, client):
uid = client.post("/api/users", json={"name": "待删除"}).json()["user_id"]
resp = client.delete(f"/api/users/{uid}")
assert resp.status_code == 200
assert resp.json()["status"] == "deleted"
assert client.get(f"/api/users/{uid}").status_code == 404
def test_delete_nonexistent_user(self, client):
resp = client.delete("/api/users/deadbeef1234567890abcd")
assert resp.status_code == 404
# ── 知识库 CRUD API ─────────────────────────────────────────────
class TestKbAPI:
@pytest.fixture(autouse=True)
def setup_kb(self, monkeypatch, tmp_path):
kb_data = tmp_path / "kb_data"
monkeypatch.setattr("backend.kb_manager.KB_DATA_DIR", kb_data)
monkeypatch.setattr("backend.kb_manager._USERS_FILE", kb_data / "users.json")
# 使用 raw TestClient 来创建前置用户
from fastapi.testclient import TestClient as TC
tc = TC(app)
resp = tc.post("/api/users", json={"name": "KB测试用户"})
self.uid = resp.json()["user_id"]
def test_create_kb(self, client):
resp = client.post(f"/api/users/{self.uid}/kbs", json={"name": "测试库", "description": "描述"})
assert resp.status_code == 200
data = resp.json()
assert data["name"] == "测试库"
assert data["parse_status"] == "empty"
def test_create_kb_empty_name_rejected(self, client):
resp = client.post(f"/api/users/{self.uid}/kbs", json={"name": ""})
assert resp.status_code == 400
def test_list_kbs(self, client):
client.post(f"/api/users/{self.uid}/kbs", json={"name": "KB1"})
client.post(f"/api/users/{self.uid}/kbs", json={"name": "KB2"})
resp = client.get(f"/api/users/{self.uid}/kbs")
assert resp.status_code == 200
assert len(resp.json()["kbs"]) == 2
def test_get_kb(self, client):
kid = client.post(f"/api/users/{self.uid}/kbs", json={"name": "查询库"}).json()["kb_id"]
resp = client.get(f"/api/kbs/{kid}")
assert resp.status_code == 200
assert resp.json()["name"] == "查询库"
def test_get_kb_not_found(self, client):
resp = client.get("/api/kbs/deadbeef1234567890abcd")
assert resp.status_code == 404
def test_delete_kb(self, client):
kid = client.post(f"/api/users/{self.uid}/kbs", json={"name": "待删库"}).json()["kb_id"]
resp = client.delete(f"/api/kbs/{kid}")
assert resp.status_code == 200
assert resp.json()["status"] == "deleted"
def test_kb_status(self, client):
kid = client.post(f"/api/users/{self.uid}/kbs", json={"name": "状态库"}).json()["kb_id"]
resp = client.get(f"/api/kbs/{kid}/status")
assert resp.status_code == 200
assert resp.json()["parse_status"] == "empty"
assert resp.json()["file_count"] == 0
def test_kb_fields(self, client):
kid = client.post(f"/api/users/{self.uid}/kbs", json={"name": "字段库"}).json()["kb_id"]
resp = client.get(f"/api/kbs/{kid}/fields")
assert resp.status_code == 200
assert resp.json()["fields"] == []
assert resp.json()["templates"] == []
# ── KB 文件上传 & 构建 API ──────────────────────────────────────
class TestKbUploadBuild:
@pytest.fixture(autouse=True)
def setup_up(self, monkeypatch, tmp_path):
kb_data = tmp_path / "kb_data"
kb_data.mkdir(parents=True, exist_ok=True)
monkeypatch.setattr("backend.kb_manager.KB_DATA_DIR", kb_data)
monkeypatch.setattr("backend.kb_manager._USERS_FILE", kb_data / "users.json")
# Mock process_file_for_kb to avoid SameFileError (API already writes to raw_dir)
monkeypatch.setattr(
"backend.kb_parser.process_file_for_kb",
lambda kb_id, file_path, source_name="": {
"filename": source_name, "type": "txt", "error": None})
from fastapi.testclient import TestClient as TC
tc = TC(app)
resp = tc.post("/api/users", json={"name": "上传测试用户"})
self.uid = resp.json()["user_id"]
def test_upload_to_kb(self, client):
kid = client.post(f"/api/users/{self.uid}/kbs", json={"name": "上传库"}).json()["kb_id"]
resp = client.post(
f"/api/kbs/{kid}/upload",
files={"file": ("readme.md", io.BytesIO(b"# test"), "text/markdown")},
)
assert resp.status_code == 200
assert resp.json()["filename"] == "readme.md"
def test_upload_to_nonexistent_kb(self, client):
resp = client.post(
"/api/kbs/deadbeef1234567890abcd/upload",
files={"file": ("x.txt", io.BytesIO(b"x"), "text/plain")},
)
assert resp.status_code == 404
def test_build_empty_kb_fails(self, client):
kid = client.post(f"/api/users/{self.uid}/kbs", json={"name": "空库"}).json()["kb_id"]
resp = client.post(f"/api/kbs/{kid}/build")
assert resp.status_code == 400
def test_search_kb_empty_query_rejected(self, client):
kid = client.post(f"/api/users/{self.uid}/kbs", json={"name": "搜索库"}).json()["kb_id"]
resp = client.get(f"/api/kbs/{kid}/search")
assert resp.status_code == 400
# ── 会话-KB 绑定 API ────────────────────────────────────────────
class TestSessionKbBinding:
@pytest.fixture(autouse=True)
def setup_bind(self, monkeypatch, tmp_path):
kb_data = tmp_path / "kb_data"
kb_data.mkdir(parents=True, exist_ok=True)
sessions_dir = tmp_path / "sessions"
monkeypatch.setattr("backend.kb_manager.KB_DATA_DIR", kb_data)
monkeypatch.setattr("backend.kb_manager._USERS_FILE", kb_data / "users.json")
monkeypatch.setattr("backend.session.SESSIONS_DIR", sessions_dir)
monkeypatch.setattr("api_server.UPLOADS_DIR", tmp_path / "uploads")
def test_bind_kb_to_session(self, client):
uid = client.post("/api/users", json={"name": "绑定用户"}).json()["user_id"]
kid = client.post(f"/api/users/{uid}/kbs", json={"name": "绑定库"}).json()["kb_id"]
sid = client.post("/api/sessions").json()["session_id"]
resp = client.put(f"/api/sessions/{sid}/kb", json={"kb_id": kid})
assert resp.status_code == 200
assert resp.json()["kb_id"] == kid
def test_get_session_kb(self, client):
uid = client.post("/api/users", json={"name": "查询用户"}).json()["user_id"]
kid = client.post(f"/api/users/{uid}/kbs", json={"name": "查询KB"}).json()["kb_id"]
sid = client.post("/api/sessions").json()["session_id"]
client.put(f"/api/sessions/{sid}/kb", json={"kb_id": kid})
resp = client.get(f"/api/sessions/{sid}/kb")
assert resp.status_code == 200
assert resp.json()["kb_id"] == kid
assert resp.json()["kb_name"] == "查询KB"
def test_unbind_kb(self, client):
sid = client.post("/api/sessions").json()["session_id"]
resp = client.put(f"/api/sessions/{sid}/kb", json={"kb_id": ""})
assert resp.status_code == 200
assert resp.json()["kb_id"] is None
def test_bind_nonexistent_kb(self, client):
sid = client.post("/api/sessions").json()["session_id"]
resp = client.put(f"/api/sessions/{sid}/kb", json={"kb_id": "deadbeef1234567890abcd"})
assert resp.status_code == 404
def test_bind_to_nonexistent_session(self, client):
resp = client.put("/api/sessions/deadbeef1234567890abcd/kb", json={"kb_id": ""})
assert resp.status_code == 404
# ── 用户-KB 端到端流程 ──────────────────────────────────────────
class TestUserKbE2E:
@pytest.fixture(autouse=True)
def setup_e2e(self, monkeypatch, tmp_path):
kb_data = tmp_path / "kb_data"
kb_data.mkdir(parents=True, exist_ok=True)
sessions_dir = tmp_path / "sessions"
monkeypatch.setattr("backend.kb_manager.KB_DATA_DIR", kb_data)
monkeypatch.setattr("backend.kb_manager._USERS_FILE", kb_data / "users.json")
monkeypatch.setattr("backend.session.SESSIONS_DIR", sessions_dir)
monkeypatch.setattr("api_server.UPLOADS_DIR", tmp_path / "uploads")
# Mock process_file_for_kb to avoid SameFileError
monkeypatch.setattr(
"backend.kb_parser.process_file_for_kb",
lambda kb_id, file_path, source_name="": {
"filename": source_name, "type": "txt", "error": None})
def test_full_flow(self, client):
# 1. 创建用户
uid = client.post("/api/users", json={"name": "E2E用户"}).json()["user_id"]
# 2. 创建 KB
kid = client.post(f"/api/users/{uid}/kbs", json={"name": "E2E库"}).json()["kb_id"]
# 3. 上传文件
resp = client.post(
f"/api/kbs/{kid}/upload",
files={"file": ("readme.md", io.BytesIO(b"# E2E test"), "text/markdown")},
)
assert resp.status_code == 200
# 4. 创建会话
sid = client.post("/api/sessions").json()["session_id"]
# 5. 绑定 KB 到会话
bind = client.put(f"/api/sessions/{sid}/kb", json={"kb_id": kid})
assert bind.status_code == 200
assert bind.json()["kb_id"] == kid
# 6. 查询会话 KB
info = client.get(f"/api/sessions/{sid}/kb")
assert info.json()["kb_name"] == "E2E库"
+161
View File
@@ -0,0 +1,161 @@
"""datasource.py 测试 — 数据源模式解析, JDBC 检测, 上下文构建。"""
import sys
from pathlib import Path
from unittest.mock import MagicMock
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent))
from agent.datasource import (
resolve_datasource_mode, _detect_jdbc_intent,
build_datasource_context, configure_jdbc, ask_db_config,
)
def _make_state(**overrides):
s = {
"user_input": "",
"conversation_history": [],
"current_jrxml": "",
"status": "",
"error_msg": "",
"natural_explanation": "",
"retry_count": 0,
"user_modification_request": "",
"final_jrxml": "",
"stage": "",
"retrieved_context": "",
**overrides,
}
return s
# ── JDBC 意图检测 ───────────────────────────────────────────────
class TestDetectJdbcIntent:
def test_direct_connect_keywords(self):
assert _detect_jdbc_intent("我想从数据库直连查询") is True
assert _detect_jdbc_intent("直连数据库获取数据") is True
def test_db_name_mentions(self):
assert _detect_jdbc_intent("从MySQL数据库查询用户表") is True
assert _detect_jdbc_intent("在PostgreSQL中执行查询") is True
assert _detect_jdbc_intent("从Oracle读取数据") is True
def test_jdbc_explicit_mention(self):
assert _detect_jdbc_intent("使用JDBC连接") is True
def test_sql_keywords(self):
assert _detect_jdbc_intent("SELECT * FROM users") is True
assert _detect_jdbc_intent("从数据库查询用户表") is True
assert _detect_jdbc_intent("先查询 数据库") is True
def test_normal_request_is_not_jdbc(self):
assert _detect_jdbc_intent("生成一个员工报表") is False
assert _detect_jdbc_intent("修改标题为XX公司") is False
def test_empty_input(self):
assert _detect_jdbc_intent("") is False
# ── 模式解析 ────────────────────────────────────────────────────
class TestResolveDatasourceMode:
def test_defaults_to_parameter_mode(self):
state = _make_state(user_input="生成报表")
assert resolve_datasource_mode(state) == "parameter"
def test_detects_jdbc_from_input(self):
state = _make_state(user_input="从数据库直连查询")
assert resolve_datasource_mode(state) == "jdbc"
def test_respects_existing_mode_in_state(self):
state = _make_state(datasource_mode="jdbc", user_input="生成报表")
assert resolve_datasource_mode(state) == "jdbc"
def test_existing_parameter_overrides_jdbc_input(self):
state = _make_state(datasource_mode="parameter", user_input="从数据库直连")
assert resolve_datasource_mode(state) == "parameter"
def test_ignores_invalid_mode_in_state(self):
state = _make_state(datasource_mode="unknown", user_input="从数据库直连")
assert resolve_datasource_mode(state) == "jdbc"
# ── 上下文构建 ──────────────────────────────────────────────────
class TestBuildDatasourceContext:
def test_parameter_mode_with_fields(self):
fields = [
{"name": "billNo", "description": "工单号", "type": "java.lang.String"},
{"name": "amount", "description": "金额", "type": "java.math.BigDecimal"},
]
ctx = build_datasource_context("parameter", fields)
assert "[数据源模式: 参数]" in ctx
assert "$P{xxx}" in ctx
assert "billNo" in ctx
assert "amount" in ctx
def test_parameter_mode_without_fields(self):
ctx = build_datasource_context("parameter", [])
assert "[数据源模式: 参数]" in ctx
assert "$P{xxx}" in ctx
def test_jdbc_mode_with_config(self):
db_config = {"url": "jdbc:mysql://localhost:3306/mydb",
"driver": "com.mysql.cj.jdbc.Driver"}
ctx = build_datasource_context("jdbc", [], db_config)
assert "[数据源模式: JDBC]" in ctx
assert "jdbc:mysql://" in ctx
assert "CDATA" in ctx
def test_jdbc_mode_without_config_shows_warning(self):
ctx = build_datasource_context("jdbc", [])
assert "尚未配置数据库连接" in ctx
assert "P{xxx}" in ctx
# ── JDBC 配置 ───────────────────────────────────────────────────
class TestConfigureJdbc:
def test_configure_returns_update_dict(self):
state = _make_state()
update = configure_jdbc(
state, url="jdbc:mysql://localhost/db",
driver="com.mysql.cj.jdbc.Driver",
username="root", password="pass")
assert update["datasource_mode"] == "jdbc"
assert update["db_config"]["url"] == "jdbc:mysql://localhost/db"
assert update["db_config"]["username"] == "root"
def test_default_driver_is_mysql(self):
update = configure_jdbc(_make_state(), url="jdbc:postgresql://localhost/db")
assert "mysql" in update["db_config"]["driver"]
# ── ask_db_config ───────────────────────────────────────────────
class TestAskDbConfig:
def test_returns_none_for_parameter_mode(self):
state = _make_state(datasource_mode="parameter")
assert ask_db_config(state) is None
def test_returns_none_when_jdbc_configured(self):
state = _make_state(datasource_mode="jdbc",
db_config={"url": "jdbc:mysql://localhost/db"})
assert ask_db_config(state) is None
def test_returns_prompt_when_jdbc_missing_config(self):
state = _make_state(datasource_mode="jdbc")
msg = ask_db_config(state)
assert msg is not None
assert "JDBC URL" in msg
assert "用户名" in msg
assert "密码" in msg
def test_returns_none_when_db_config_empty(self):
state = _make_state(datasource_mode="jdbc", db_config={})
msg = ask_db_config(state)
assert msg is not None
+157
View File
@@ -0,0 +1,157 @@
"""field_matcher.py 测试 — OCR 字段 → KB 字段匹配, embedding + LLM。"""
import sys
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent))
from backend.field_matcher import (
_cosine_similarity, match_ocr_to_kb, _match_via_llm,
format_field_mapping_context,
)
# ── 余弦相似度 ──────────────────────────────────────────────────
class TestCosineSimilarity:
def test_identical_vectors(self):
assert _cosine_similarity([1, 0, 0], [1, 0, 0]) == 1.0
def test_orthogonal_vectors(self):
assert _cosine_similarity([1, 0, 0], [0, 1, 0]) == 0.0
def test_opposite_vectors(self):
assert _cosine_similarity([1, 0], [-1, 0]) == -1.0
def test_normalized_vectors_range(self):
sim = _cosine_similarity([0.6, 0.8], [0.8, 0.6])
assert -1.0 <= sim <= 1.0
# ── LLM 匹配 ────────────────────────────────────────────────────
class TestMatchViaLlm:
def test_returns_json_mapping(self):
mock_llm = MagicMock()
mock_response = MagicMock()
mock_response.content = '{"工单号": "billNo", "客户": "customerName"}'
mock_llm.invoke.return_value = mock_response
kb_fields = [
{"name": "billNo", "description": "工单号", "type": "String"},
{"name": "customerName", "description": "客户名称", "type": "String"},
]
result = _match_via_llm(["工单号", "客户"], kb_fields, mock_llm)
assert result == {"工单号": "billNo", "客户": "customerName"}
def test_includes_candidates_hint_when_provided(self):
mock_llm = MagicMock()
mock_response = MagicMock()
mock_response.content = '{"工单号": "billNo"}'
mock_llm.invoke.return_value = mock_response
candidates = {"工单号": [("billNo", 0.85), ("orderId", 0.62)]}
result = _match_via_llm(
["工单号"],
[{"name": "billNo", "description": "工单号", "type": "String"}],
mock_llm, candidates=candidates)
call_args = mock_llm.invoke.call_args[0][0]
assert "候选" in call_args
assert "billNo" in call_args
def test_llm_error_returns_empty_dict(self):
mock_llm = MagicMock()
mock_llm.invoke.side_effect = RuntimeError("LLM crash")
result = _match_via_llm(["x"], [{"name": "y", "description": "", "type": "String"}], mock_llm)
assert result == {}
def test_llm_returns_invalid_json_returns_empty(self):
mock_llm = MagicMock()
mock_response = MagicMock()
mock_response.content = "not json at all"
mock_llm.invoke.return_value = mock_response
result = _match_via_llm(["x"], [{"name": "y", "description": "", "type": "String"}], mock_llm)
assert result == {}
# ── 完整匹配流程 ────────────────────────────────────────────────
class TestMatchOcrToKb:
@pytest.fixture(autouse=True)
def mock_embed(self):
with patch("backend.field_matcher._embed") as mock_embed:
def _fake_embed(text):
if "billNo" in text or "工单" in text:
return [1.0, 0.0, 0.0]
if "customerName" in text or "客户" in text:
return [0.0, 1.0, 0.0]
if "amount" in text or "金额" in text:
return [0.0, 0.0, 1.0]
return [0.0, 0.0, 0.0]
mock_embed.side_effect = _fake_embed
yield mock_embed
def test_matches_without_llm(self):
kb_fields = [
{"name": "billNo", "description": "工单号", "type": "String"},
{"name": "customerName", "description": "客户名称", "type": "String"},
{"name": "amount", "description": "金额", "type": "BigDecimal"},
]
mapping = match_ocr_to_kb(
["工单号", "客户名称", "金额"], kb_fields, llm=None)
assert mapping["工单号"] == "billNo"
assert mapping["客户名称"] == "customerName"
assert mapping["金额"] == "amount"
def test_empty_inputs_return_empty(self):
assert match_ocr_to_kb([], [], llm=None) == {}
assert match_ocr_to_kb(["x"], [], llm=None) == {}
assert match_ocr_to_kb([], [{"name": "y", "description": "", "type": "String"}], llm=None) == {}
def test_low_similarity_not_matched(self):
kb_fields = [{"name": "far", "description": "不相关字段", "type": "String"}]
mapping = match_ocr_to_kb(["无关"], kb_fields, llm=None)
assert "无关" not in mapping or mapping == {}
def test_uses_llm_when_provided(self):
mock_llm = MagicMock()
mock_response = MagicMock()
mock_response.content = '{"工单号": "billNo", "客户名称": "customerName"}'
mock_llm.invoke.return_value = mock_response
kb_fields = [
{"name": "billNo", "description": "工单号", "type": "String"},
{"name": "customerName", "description": "客户", "type": "String"},
]
mapping = match_ocr_to_kb(["工单号", "客户名称"], kb_fields, llm=mock_llm)
assert mapping["工单号"] == "billNo"
def test_embedding_failure_falls_back_to_llm(self):
mock_llm = MagicMock()
mock_response = MagicMock()
mock_response.content = '{"工单号": "billNo"}'
mock_llm.invoke.return_value = mock_response
with patch("backend.field_matcher._embed", side_effect=RuntimeError("model error")):
kb_fields = [{"name": "billNo", "description": "工单号", "type": "String"}]
mapping = match_ocr_to_kb(["工单号"], kb_fields, llm=mock_llm)
assert mapping["工单号"] == "billNo"
# ── 格式化上下文 ────────────────────────────────────────────────
class TestFormatFieldMappingContext:
def test_formats_mapping_as_table(self):
ctx = format_field_mapping_context({"工单号": "billNo", "客户": "customerName"})
assert "[字段映射" in ctx
assert "$P{billNo}" in ctx
assert "$P{customerName}" in ctx
assert "工单号" in ctx
assert "客户" in ctx
def test_empty_mapping_returns_empty_string(self):
assert format_field_mapping_context({}) == ""
def test_single_entry(self):
ctx = format_field_mapping_context({"发票号码": "invoiceNo"})
assert "$P{invoiceNo}" in ctx
+325
View File
@@ -0,0 +1,325 @@
"""JRXML 窗口化模块单元测试。
测试 decompose split reassemble 往返链路
以及元素计数和校验逻辑
"""
from __future__ import annotations
import pytest
from agent.jrxml_windower import (
decompose_jrxml, reassemble_jrxml,
split_band_into_windows, reassemble_band_windows,
count_elements, validate_element_count,
BandInfo,
)
# ── 最小 JRXML 测试夹具 ──────────────────────────────────────────────
MINIMAL_JRXML = """<?xml version="1.0" encoding="UTF-8"?>
<jasperReport name="test" pageWidth="595" pageHeight="842" columnCount="3">
<property name="test.prop" value="1"/>
<field name="name" class="java.lang.String"/>
<field name="amount" class="java.math.BigDecimal"/>
<queryString><![CDATA[SELECT * FROM t]]></queryString>
<title>
<band height="50">
<staticText>
<reportElement x="0" y="0" width="100" height="20"/>
<text><![CDATA[Title]]></text>
</staticText>
<textField>
<reportElement x="200" y="0" width="80" height="20"/>
<textFieldExpression><![CDATA[$F{name}]]></textFieldExpression>
</textField>
</band>
</title>
<columnHeader>
<band height="30">
<staticText>
<reportElement x="0" y="0" width="100" height="30"/>
<text><![CDATA[Header]]></text>
</staticText>
</band>
</columnHeader>
<detail>
<band height="40">
<textField>
<reportElement x="0" y="0" width="100" height="20"/>
<textFieldExpression><![CDATA[$F{name}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="200" y="0" width="80" height="20"/>
<textFieldExpression><![CDATA[$F{amount}]]></textFieldExpression>
</textField>
</band>
</detail>
<pageFooter>
<band height="30">
<textField>
<reportElement x="0" y="0" width="100" height="20"/>
<textFieldExpression><![CDATA["Page " + $V{PAGE_NUMBER}]]></textFieldExpression>
</textField>
</band>
</pageFooter>
</jasperReport>"""
# ── Decompose 测试 ────────────────────────────────────────────────────
class TestDecompose:
def test_parses_minimal_jrxml(self):
parts = decompose_jrxml(MINIMAL_JRXML)
assert parts is not None
assert parts.band_count == 4 # title, columnHeader, detail, pageFooter
assert parts.total_elements == 6 # 2 + 1 + 2 + 1
def test_declaration_preserved(self):
parts = decompose_jrxml(MINIMAL_JRXML)
assert '<?xml' in parts.declaration
def test_root_open_has_jasperreport(self):
parts = decompose_jrxml(MINIMAL_JRXML)
assert 'jasperReport' in parts.root_open
def test_header_children_separated(self):
parts = decompose_jrxml(MINIMAL_JRXML)
assert 'field name="name"' in parts.header_xml
assert 'field name="amount"' in parts.header_xml
assert 'queryString' in parts.header_xml
assert 'property name' in parts.header_xml
def test_band_labels(self):
parts = decompose_jrxml(MINIMAL_JRXML)
labels = [b.label for b in parts.bands]
assert labels == ["title", "columnHeader", "detail", "pageFooter"]
def test_footer_closes_jasperreport(self):
parts = decompose_jrxml(MINIMAL_JRXML)
assert 'jasperReport' in parts.footer
assert parts.footer.strip().endswith('>')
def test_returns_none_for_non_jrxml(self):
parts = decompose_jrxml("<html><body></body></html>")
assert parts is None
def test_returns_none_for_malformed_xml(self):
parts = decompose_jrxml("not xml at all <<<")
assert parts is None
# ── Roundtrip 测试 ────────────────────────────────────────────────────
class TestRoundtrip:
def test_decompose_reassemble_element_count_unchanged(self):
parts = decompose_jrxml(MINIMAL_JRXML)
band_map = {b.label: b.band_xml for b in parts.bands}
result = reassemble_jrxml(parts, band_map)
orig = count_elements(MINIMAL_JRXML)
reassembled = count_elements(result)
assert orig == reassembled, f"Elements: {orig} -> {reassembled}"
def test_roundtrip_preserves_text_content(self):
parts = decompose_jrxml(MINIMAL_JRXML)
band_map = {b.label: b.band_xml for b in parts.bands}
result = reassemble_jrxml(parts, band_map)
assert 'Title' in result
assert 'Header' in result
assert '$F{name}' in result
assert '$F{amount}' in result
def test_empty_bands_preserved(self):
"""空 band(无元素)在 roundtrip 中不丢失。"""
jrxml = """<?xml version="1.0" encoding="UTF-8"?>
<jasperReport name="t" pageWidth="595" pageHeight="842">
<queryString><![CDATA[]]></queryString>
<background>
<band height="10"/>
</background>
<title>
<band height="50">
<staticText>
<reportElement x="0" y="0" width="100" height="20"/>
<text><![CDATA[T]]></text>
</staticText>
</band>
</title>
</jasperReport>"""
parts = decompose_jrxml(jrxml)
assert parts.band_count == 2
band_map = {b.label: b.band_xml for b in parts.bands}
result = reassemble_jrxml(parts, band_map)
assert count_elements(jrxml) == count_elements(result)
# ── Window Split 测试 ─────────────────────────────────────────────────
class TestWindowSplit:
def test_small_band_not_split(self):
"""小 band 不会被切分。"""
band = BandInfo(
section_name="title", band_index=0,
band_xml='<band height="50"><staticText><reportElement x="0" y="0" width="1" height="1"/><text><![CDATA[X]]></text></staticText></band>',
element_count=1, char_length=150,
)
windows = split_band_into_windows(band, max_chars=4000)
assert len(windows) == 1
def test_large_band_split_at_element_boundaries(self):
"""超过字符阈值的 band 在元素边界切分。"""
inner = "<staticText><reportElement x=\"0\" y=\"0\" width=\"100\" height=\"20\"/><text><![CDATA[A]]></text></staticText>\n" * 80
band_xml = f'<band height="50">{inner}</band>'
band = BandInfo(
section_name="detail", band_index=0,
band_xml=band_xml,
element_count=80, char_length=len(band_xml),
)
windows = split_band_into_windows(band, max_chars=4000)
assert len(windows) > 1, f"Expected multiple windows, got {len(windows)}"
def test_split_preserves_element_count(self):
"""切分后重组元素数不变。"""
inner = "<staticText><reportElement x=\"0\" y=\"0\" width=\"100\" height=\"20\"/><text><![CDATA[A]]></text></staticText>\n" * 80
band_xml = f'<band height="50">{inner}</band>'
band = BandInfo(
section_name="detail", band_index=0,
band_xml=band_xml,
element_count=80, char_length=len(band_xml),
)
windows = split_band_into_windows(band, max_chars=4000)
reassembled = reassemble_band_windows(windows)
assert count_elements(band_xml) == count_elements(reassembled)
def test_no_empty_windows(self):
"""所有窗口非空。"""
inner = "<staticText><reportElement x=\"0\" y=\"0\" width=\"100\" height=\"20\"/><text><![CDATA[A]]></text></staticText>\n" * 80
band_xml = f'<band height="50">{inner}</band>'
band = BandInfo(
section_name="detail", band_index=0,
band_xml=band_xml,
element_count=80, char_length=len(band_xml),
)
windows = split_band_into_windows(band, max_chars=4000)
for i, w in enumerate(windows):
assert len(w.strip()) > 0, f"Window {i} is empty"
assert '<band' in w, f"Window {i} missing <band>"
def test_namespaced_band_split(self):
"""命名空间前缀的 band 也能正确切分。"""
inner = "<ns0:staticText><ns0:reportElement x=\"0\" y=\"0\" width=\"100\" height=\"20\"/><ns0:text><![CDATA[A]]></ns0:text></ns0:staticText>\n" * 80
band_xml = f'<ns0:band xmlns:ns0="http://jasperreports.sourceforge.net/jasperreports" height="50">{inner}</ns0:band>'
band = BandInfo(
section_name="detail", band_index=0,
band_xml=band_xml,
element_count=80, char_length=len(band_xml),
)
windows = split_band_into_windows(band, max_chars=4000)
assert len(windows) > 1, f"Expected multiple, got {len(windows)}"
for w in windows:
assert '</ns0:band>' in w or w.startswith('<ns0:band')
# ── Element Count 测试 ────────────────────────────────────────────────
class TestElementCount:
def test_counts_textfield_statictext(self):
xml = '<textField/><staticText/>'
assert count_elements(xml) == 2
def test_counts_field_declarations(self):
xml = '<field name="a" class="java.lang.String"/>'
assert count_elements(xml) == 1
def test_counts_namespaced_elements(self):
xml = '<ns0:textField/><ns0:staticText/><ns0:field name="x"/>'
assert count_elements(xml) == 3
def test_minimal_jrxml_count(self):
assert count_elements(MINIMAL_JRXML) == 8
def test_empty_string_zero(self):
assert count_elements("") == 0
# ── Validate 测试 ─────────────────────────────────────────────────────
class TestValidateElementCount:
def test_no_change_ok(self):
r = validate_element_count(MINIMAL_JRXML, MINIMAL_JRXML, "test")
assert r["ok"] is True
assert r["change_pct"] == 0
def test_small_change_ok(self):
"""< 5% 变化静默通过。"""
xml2 = MINIMAL_JRXML.replace('<staticText>', '<staticText><!-- comment -->')
r = validate_element_count(MINIMAL_JRXML, xml2, "test")
# 0% change since comments don't count as elements
assert r["ok"] is True
def test_large_change_not_ok(self):
"""> 10% 变化返回 ok=False。"""
short = MINIMAL_JRXML[:500] # 大幅截断
r = validate_element_count(MINIMAL_JRXML, short, "test")
if r["original"] > 0 and r["change_pct"] > 0.10:
assert r["ok"] is False
def test_zero_original_always_ok(self):
r = validate_element_count("", MINIMAL_JRXML, "test")
assert r["ok"] is True
# ── 多 section 多 band 测试 ──────────────────────────────────────────
MULTI_BAND_JRXML = """<?xml version="1.0" encoding="UTF-8"?>
<jasperReport name="multi" pageWidth="595" pageHeight="842">
<field name="f1" class="java.lang.String"/>
<queryString><![CDATA[SELECT 1]]></queryString>
<detail>
<band height="30">
<textField>
<reportElement x="0" y="0" width="100" height="20"/>
<textFieldExpression><![CDATA[$F{f1}]]></textFieldExpression>
</textField>
</band>
<band height="20">
<staticText>
<reportElement x="0" y="0" width="100" height="15"/>
<text><![CDATA[Sub]]></text>
</staticText>
</band>
</detail>
<summary>
<band height="40">
<textField>
<reportElement x="0" y="0" width="200" height="30"/>
<textFieldExpression><![CDATA["Total"]]></textFieldExpression>
</textField>
</band>
</summary>
</jasperReport>"""
class TestMultiBand:
def test_multiple_bands_same_section(self):
"""同一 section 内的多个 band 分别处理。"""
parts = decompose_jrxml(MULTI_BAND_JRXML)
assert parts.band_count == 3 # detail_band0, detail_band1, summary
labels = [b.label for b in parts.bands]
assert labels == ["detail", "detail_band1", "summary"]
def test_multi_band_roundtrip(self):
parts = decompose_jrxml(MULTI_BAND_JRXML)
band_map = {b.label: b.band_xml for b in parts.bands}
result = reassemble_jrxml(parts, band_map)
assert count_elements(MULTI_BAND_JRXML) == count_elements(result)
def test_reassemble_opens_closes_sections(self):
parts = decompose_jrxml(MULTI_BAND_JRXML)
band_map = {b.label: b.band_xml for b in parts.bands}
result = reassemble_jrxml(parts, band_map)
assert result.count('<detail>') == 1
assert result.count('</detail>') == 1
assert result.count('<summary>') == 1
assert result.count('</summary>') == 1
+265
View File
@@ -0,0 +1,265 @@
"""kb_manager.py 测试 — 用户 + KB CRUD, 原子写入, ID 验证。"""
import json
import os
import sys
import tempfile
from pathlib import Path
from unittest.mock import patch
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent))
from backend.kb_manager import (
_validate_id, _now_iso, _ensure_dir, _read_json, _write_json_atomic,
_load_users, _save_users,
create_user, list_users, get_user, delete_user,
create_kb, list_kbs, get_kb, update_kb_meta, delete_kb,
get_kb_raw_dir, get_kb_chunks_path, get_kb_chroma_path,
KB_DATA_DIR, _USERS_FILE,
)
@pytest.fixture
def temp_kb_data(monkeypatch):
with tempfile.TemporaryDirectory(prefix="test_kb_") as tmpdir:
monkeypatch.setattr("backend.kb_manager.KB_DATA_DIR", Path(tmpdir))
monkeypatch.setattr("backend.kb_manager._USERS_FILE", Path(tmpdir) / "users.json")
yield Path(tmpdir)
@pytest.fixture
def user(temp_kb_data):
return create_user("测试用户")
@pytest.fixture
def kb(temp_kb_data, user):
return create_kb(user["user_id"], "测试知识库", "测试描述")
# ── ID 验证 ─────────────────────────────────────────────────────
class TestIDValidation:
def test_valid_hex_id_passes(self):
_validate_id("aabbccddeeff0011223344", "test_id")
def test_short_id_raises(self):
with pytest.raises(ValueError, match="Invalid"):
_validate_id("abc", "test_id")
def test_non_hex_id_raises(self):
with pytest.raises(ValueError, match="Invalid"):
_validate_id("not_valid!!!", "test_id")
def test_empty_id_raises(self):
with pytest.raises(ValueError, match="Invalid"):
_validate_id("", "test_id")
# ── 原子写入 ────────────────────────────────────────────────────
class TestAtomicWrite:
def test_write_json_atomic_creates_file(self, temp_kb_data):
fp = temp_kb_data / "test.json"
_write_json_atomic(fp, {"key": "value"})
assert fp.exists()
assert json.loads(fp.read_text(encoding="utf-8")) == {"key": "value"}
def test_write_json_atomic_overwrites(self, temp_kb_data):
fp = temp_kb_data / "test.json"
_write_json_atomic(fp, {"a": 1})
_write_json_atomic(fp, {"b": 2})
assert json.loads(fp.read_text(encoding="utf-8")) == {"b": 2}
def test_write_json_atomic_creates_parent_dir(self, temp_kb_data):
fp = temp_kb_data / "deep" / "nested" / "test.json"
_write_json_atomic(fp, {"ok": True})
assert fp.exists()
def test_write_json_atomic_no_partial_file_on_error(self, temp_kb_data):
fp = temp_kb_data / "fail.json"
with patch("json.dump", side_effect=RuntimeError("boom")):
with pytest.raises(RuntimeError):
_write_json_atomic(fp, {"x": 1})
assert not fp.exists()
tmps = list(temp_kb_data.glob("*.json*"))
assert len(tmps) == 0 or all(not f.name.endswith(".tmp") for f in tmps)
# ── 用户 CRUD ───────────────────────────────────────────────────
class TestUserCRUD:
def test_create_user_returns_dict(self, temp_kb_data):
u = create_user("张三")
assert u["name"] == "张三"
assert len(u["user_id"]) >= 12
assert "created_at" in u
def test_create_user_persists_to_disk(self, temp_kb_data):
u = create_user("李四")
loaded = list_users()
assert any(x["user_id"] == u["user_id"] for x in loaded)
def test_create_user_with_custom_id(self, temp_kb_data):
uid = "abcdef1234567890abcdef"
u = create_user("王五", user_id=uid)
assert u["user_id"] == uid
def test_create_duplicate_user_id_raises(self, temp_kb_data):
uid = "deadbeef1234567890abcd"
create_user("用户1", user_id=uid)
with pytest.raises(ValueError, match="already exists"):
create_user("用户2", user_id=uid)
def test_list_users_empty(self, temp_kb_data):
assert list_users() == []
def test_list_users_returns_all(self, temp_kb_data):
create_user("A")
create_user("B")
assert len(list_users()) == 2
def test_get_user_found(self, user):
u = get_user(user["user_id"])
assert u is not None
assert u["name"] == user["name"]
def test_get_user_not_found(self, temp_kb_data):
assert get_user("deadbeef1234567890abcd") is None
def test_get_user_invalid_id_raises(self, temp_kb_data):
with pytest.raises(ValueError):
get_user("invalid")
def test_delete_user_returns_true(self, user):
assert delete_user(user["user_id"]) is True
def test_delete_user_removes_from_list(self, user):
delete_user(user["user_id"])
assert get_user(user["user_id"]) is None
def test_delete_user_removes_dir(self, temp_kb_data, user):
user_dir = temp_kb_data / user["user_id"]
assert user_dir.exists()
delete_user(user["user_id"])
assert not user_dir.exists()
def test_delete_user_not_found_returns_false(self, temp_kb_data):
assert delete_user("deadbeef1234567890abcd") is False
def test_delete_user_invalid_id_raises(self, temp_kb_data):
with pytest.raises(ValueError):
delete_user("bad_id")
# ── KB CRUD ─────────────────────────────────────────────────────
class TestKbCRUD:
def test_create_kb_returns_meta(self, kb):
assert kb["name"] == "测试知识库"
assert len(kb["kb_id"]) >= 12
assert kb["parse_status"] == "empty"
assert kb["file_count"] == 0
def test_create_kb_creates_dir_structure(self, temp_kb_data, user, kb):
kb_dir = temp_kb_data / user["user_id"] / kb["kb_id"]
assert kb_dir.is_dir()
assert (kb_dir / "raw").is_dir()
assert (kb_dir / "meta.json").exists()
def test_create_kb_with_custom_id(self, user):
kid = "cafebabe1234567890feed"
kb = create_kb(user["user_id"], "自定义ID库", kb_id=kid)
assert kb["kb_id"] == kid
def test_list_kbs_empty(self, user):
assert list_kbs(user["user_id"]) == []
def test_list_kbs_returns_all(self, user):
create_kb(user["user_id"], "B库")
create_kb(user["user_id"], "A库")
assert len(list_kbs(user["user_id"])) == 2
def test_list_kbs_summary_format(self, user, kb):
kbs = list_kbs(user["user_id"])
s = kbs[0]
for key in ("kb_id", "name", "field_count", "template_count", "parse_status"):
assert key in s
def test_get_kb_found(self, kb):
k = get_kb(kb["kb_id"])
assert k is not None
assert k["name"] == kb["name"]
def test_get_kb_not_found(self, temp_kb_data):
assert get_kb("deadbeef1234567890abcd") is None
def test_get_kb_invalid_id_raises(self, temp_kb_data):
with pytest.raises(ValueError):
get_kb("bad")
def test_update_kb_meta_changes_fields(self, kb):
updated = update_kb_meta(kb["kb_id"], {"parse_status": "ready", "file_count": 5})
assert updated is not None
assert updated["parse_status"] == "ready"
assert updated["file_count"] == 5
assert "updated_at" in updated
def test_update_kb_meta_not_found(self, temp_kb_data):
assert update_kb_meta("deadbeef1234567890abcd", {"x": 1}) is None
def test_delete_kb_returns_true(self, kb):
assert delete_kb(kb["kb_id"]) is True
def test_delete_kb_removes_dir(self, temp_kb_data, user, kb):
kb_dir = temp_kb_data / user["user_id"] / kb["kb_id"]
assert kb_dir.exists()
delete_kb(kb["kb_id"])
assert not kb_dir.exists()
def test_delete_kb_not_found_returns_false(self, temp_kb_data):
assert delete_kb("deadbeef1234567890abcd") is False
# ── 工具函数 ────────────────────────────────────────────────────
class TestHelpers:
def test_get_kb_raw_dir(self, kb):
d = get_kb_raw_dir(kb["kb_id"])
assert d is not None
assert d.name == "raw"
def test_get_kb_raw_dir_not_found(self, temp_kb_data):
assert get_kb_raw_dir("deadbeef1234567890abcd") is None
def test_get_kb_chunks_path(self, kb):
p = get_kb_chunks_path(kb["kb_id"])
assert p is not None
assert p.name == "chunks.json"
def test_get_kb_chroma_path_creates_dir(self, kb):
p = get_kb_chroma_path(kb["kb_id"])
assert p is not None
assert p.name == "chroma"
assert p.exists()
def test_user_can_own_multiple_kbs(self, user):
create_kb(user["user_id"], "KB1")
create_kb(user["user_id"], "KB2")
create_kb(user["user_id"], "KB3")
assert len(list_kbs(user["user_id"])) == 3
def test_different_users_have_isolated_kbs(self, temp_kb_data):
u1 = create_user("用户A")
u2 = create_user("用户B")
create_kb(u1["user_id"], "A的库")
create_kb(u2["user_id"], "B的库")
assert len(list_kbs(u1["user_id"])) == 1
assert len(list_kbs(u2["user_id"])) == 1
def test_delete_user_cascades_to_kbs(self, temp_kb_data, user):
create_kb(user["user_id"], "要被删除的库")
delete_user(user["user_id"])
assert not (temp_kb_data / user["user_id"]).exists()
+311
View File
@@ -0,0 +1,311 @@
"""kb_parser.py 测试 — JRXML 解析, 文件处理, 分块, 字段提取。"""
import json
import os
import sys
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent))
from backend.kb_parser import (
parse_jrxml_fields, _extract_archive, process_file_for_kb,
chunk_file_results, extract_fields_with_llm, _extract_fields_from_table,
_find_tag, _find_all_tags, _collect_from_result, build_kb_from_files,
)
SAMPLE_JRXML = """<?xml version="1.0" encoding="UTF-8"?>
<jasperReport name="TestReport" pageWidth="595" pageHeight="842"
xmlns="http://jasperreports.sourceforge.net/jasperreports">
<parameter name="billNo" class="java.lang.String">
<parameterDescription>工单号</parameterDescription>
</parameter>
<parameter name="customerName" class="java.lang.String"/>
<field name="amount" class="java.math.BigDecimal"/>
<field name="createDate" class="java.sql.Date"/>
<queryString><![CDATA[SELECT * FROM orders WHERE bill_no = $P{billNo}]]></queryString>
</jasperReport>"""
SAMPLE_JRXML_NO_NS = """<?xml version="1.0" encoding="UTF-8"?>
<jasperReport name="SimpleReport" pageWidth="800" pageHeight="600">
<parameter name="title" class="java.lang.String"/>
<field name="name" class="java.lang.String"/>
</jasperReport>"""
INVALID_XML = """<?xml version="1.0"?>
<jasperReport>
<parameter name="broken"
</jasperReport>"""
@pytest.fixture
def jrxml_file():
with tempfile.NamedTemporaryFile(mode="w", suffix=".jrxml",
delete=False, encoding="utf-8") as f:
f.write(SAMPLE_JRXML)
path = f.name
yield path
os.unlink(path)
@pytest.fixture
def temp_kb(monkeypatch):
with tempfile.TemporaryDirectory(prefix="test_kb_parser_") as tmpdir:
kb_data = Path(tmpdir)
user_dir = kb_data / "default"
kb_dir = user_dir / "abcdef1234567890abcd"
raw_dir = kb_dir / "raw"
raw_dir.mkdir(parents=True)
monkeypatch.setattr(
"backend.kb_manager.get_kb_raw_dir",
lambda kb_id: raw_dir if kb_id == "abcdef1234567890abcd" else None)
monkeypatch.setattr(
"backend.kb_manager.get_kb_chunks_path",
lambda kb_id: kb_dir / "chunks.json" if kb_id == "abcdef1234567890abcd" else None)
monkeypatch.setattr(
"backend.kb_manager.update_kb_meta",
lambda kb_id, updates: None)
yield {"kb_id": "abcdef1234567890abcd", "kb_dir": kb_dir, "raw_dir": raw_dir,
"data_dir": kb_data}
# ── JRXML 解析 ──────────────────────────────────────────────────
class TestParseJrxmlFields:
def test_parses_parameters(self, jrxml_file):
result = parse_jrxml_fields(jrxml_file)
assert result["error"] is None
assert len(result["parameters"]) == 2
assert result["parameters"][0]["name"] == "billNo"
assert result["parameters"][0]["type"] == "java.lang.String"
assert result["parameters"][0]["description"] == "工单号"
def test_parses_fields(self, jrxml_file):
result = parse_jrxml_fields(jrxml_file)
assert len(result["fields"]) == 2
field_names = [f["name"] for f in result["fields"]]
assert "amount" in field_names
assert "createDate" in field_names
def test_parses_query(self, jrxml_file):
result = parse_jrxml_fields(jrxml_file)
assert "SELECT * FROM orders" in result["query"]
def test_parses_report_metadata(self, jrxml_file):
result = parse_jrxml_fields(jrxml_file)
assert result["report_name"] == "TestReport"
assert result["page_width"] == "595"
assert result["page_height"] == "842"
def test_parses_jrxml_without_namespace(self, tmp_path):
fp = tmp_path / "simple.jrxml"
fp.write_text(SAMPLE_JRXML_NO_NS, encoding="utf-8")
result = parse_jrxml_fields(str(fp))
assert result["report_name"] == "SimpleReport"
assert len(result["parameters"]) == 1
def test_invalid_xml_returns_error(self, tmp_path):
fp = tmp_path / "bad.jrxml"
fp.write_text(INVALID_XML, encoding="utf-8")
result = parse_jrxml_fields(str(fp))
assert result["error"] is not None
assert "解析失败" in result["error"]
def test_empty_jrxml_has_no_fields(self, tmp_path):
fp = tmp_path / "empty.jrxml"
fp.write_text(
'<?xml version="1.0"?>'
'<jasperReport name="Empty"/>',
encoding="utf-8")
result = parse_jrxml_fields(str(fp))
assert result["parameters"] == []
assert result["fields"] == []
def test_nonexistent_file_raises(self):
with pytest.raises(FileNotFoundError):
parse_jrxml_fields("/nonexistent/path.jrxml")
# ── 表格字段提取 ────────────────────────────────────────────────
class TestExtractFieldsFromTable:
def test_extracts_from_markdown_table(self):
text = """| 字段名 | 含义 | 必填 | 类型 |
|--------|------|------|------|
| billNo | 工单号 | | String |
| amount | 金额 | | BigDecimal |"""
fields = _extract_fields_from_table(text)
assert len(fields) == 2
assert fields[0]["name"] == "billNo"
assert fields[0]["description"] == "工单号"
assert fields[0]["required"] is True
assert fields[1]["name"] == "amount"
def test_skips_separator_rows(self):
text = """| 字段 | 说明 |
|------|------|
|------|------|
| name | 名称 |"""
fields = _extract_fields_from_table(text)
assert len(fields) == 1
assert fields[0]["name"] == "name"
def test_returns_empty_for_plain_text(self):
fields = _extract_fields_from_table("这是一段普通文本,没有表格。")
assert fields == []
def test_cells_with_bold_markers_stripped(self):
text = """| 名称 | 含义 |
|------|------|
| **billNo** | 单号 |"""
fields = _extract_fields_from_table(text)
assert fields[0]["name"] == "billNo"
# ── LLM 字段提取 ────────────────────────────────────────────────
class TestExtractFieldsWithLlm:
def test_falls_back_to_table_when_no_llm(self):
text = "| 字段 | 说明 |\n|------|------|\n| code | 编码 |"
fields = extract_fields_with_llm(text, llm=None)
assert len(fields) >= 1
assert any(f["name"] == "code" for f in fields)
def test_uses_llm_when_provided(self):
mock_llm = MagicMock()
mock_response = MagicMock()
mock_response.content = '[{"name": "id", "description": "ID", "type": "Long", "required": true}]'
mock_llm.invoke.return_value = mock_response
fields = extract_fields_with_llm("some text", llm=mock_llm)
assert len(fields) == 1
assert fields[0]["name"] == "id"
def test_llm_failure_falls_back_to_table(self):
mock_llm = MagicMock()
mock_llm.invoke.side_effect = RuntimeError("LLM down")
text = "| 字段 | 说明 |\n|------|------|\n| code | 编码 |"
fields = extract_fields_with_llm(text, llm=mock_llm)
assert any(f["name"] == "code" for f in fields)
# ── 文件处理 ────────────────────────────────────────────────────
class TestProcessFileForKb:
def test_process_jrxml_copies_and_parses(self, jrxml_file, temp_kb):
result = process_file_for_kb(temp_kb["kb_id"], jrxml_file)
assert result["type"] == "jrxml"
assert result["jrxml_info"]["report_name"] == "TestReport"
assert result["error"] is None
copied = list(temp_kb["raw_dir"].glob("*.jrxml"))
assert len(copied) == 1
def test_process_nonexistent_kb_returns_error(self, jrxml_file):
result = process_file_for_kb("deadbeef1234567890abcd", jrxml_file)
assert result["error"] is not None
def test_process_text_file(self, tmp_path, temp_kb):
fp = tmp_path / "readme.md"
fp.write_text("# 标题\n\n这是一段内容。\n\n另一段内容。", encoding="utf-8")
with patch("backend.kb_parser.parse_file") as mock_parse:
mock_parse.return_value = {"text": "parsed content", "error": None}
result = process_file_for_kb(temp_kb["kb_id"], str(fp))
assert result["filename"] is not None
assert result["error"] is None
# ── 分块 ────────────────────────────────────────────────────────
class TestChunkFileResults:
def test_jrxml_result_produces_template_chunk(self, jrxml_file):
info = parse_jrxml_fields(jrxml_file)
raw = Path(jrxml_file).read_text(encoding="utf-8")
results = [{
"filename": "test.jrxml", "type": "jrxml",
"text": "text content", "raw_xml": raw,
"jrxml_info": info, "error": None,
}]
chunks = chunk_file_results(results, kb_name="测试库")
assert len(chunks) >= 1
tmpl = [c for c in chunks if c["metadata"]["chunk_type"] == "jrxml_template"]
assert len(tmpl) == 1
assert tmpl[0]["metadata"]["report_name"] == "TestReport"
assert "TestReport" in tmpl[0]["content"]
def test_archive_result_recurses(self):
results = [{
"filename": "bundle.zip", "type": "archive", "text": "",
"archive_contents": [
{"filename": "inner.jrxml", "type": "jrxml",
"text": "inner text", "raw_xml": "<xml/>",
"jrxml_info": {"report_name": "Inner", "parameters": [], "fields": []},
"error": None},
], "error": None,
}]
chunks = chunk_file_results(results)
assert any(c["metadata"]["report_name"] == "Inner" for c in chunks)
def test_empty_text_skipped(self):
results = [{"filename": "empty.md", "type": "md", "text": "", "error": None}]
assert chunk_file_results(results) == []
def test_short_paragraphs_skipped(self):
results = [{"filename": "short.txt", "type": "txt", "text": "hi", "error": None}]
assert chunk_file_results(results) == []
def test_text_split_into_paragraphs(self):
long_para = "A" * 50
results = [
{"filename": "doc.txt", "type": "txt",
"text": f"{long_para}\n\n{long_para}\n\n{long_para}", "error": None},
]
chunks = chunk_file_results(results)
assert len(chunks) == 3
# ── _collect_from_result ────────────────────────────────────────
class TestCollectFromResult:
def test_collects_jrxml_parameters_as_fields(self):
fields = []
templates = []
_collect_from_result({
"jrxml_info": {
"report_name": "R1",
"parameters": [{"name": "p1", "type": "String", "description": "参数1"}],
"fields": [],
},
"filename": "r1.jrxml",
}, fields, templates)
assert len(templates) == 1
assert any(f["name"] == "p1" for f in fields)
def test_collects_jrxml_fields(self):
fields = []
templates = []
_collect_from_result({
"jrxml_info": {
"report_name": "R2",
"parameters": [],
"fields": [{"name": "f1", "type": "Double", "description": ""}],
},
"filename": "r2.jrxml",
}, fields, templates)
assert any(f["name"] == "f1" for f in fields)
def test_skips_non_jrxml(self):
fields = []
templates = []
_collect_from_result({"type": "csv", "filename": "data.csv"}, fields, templates)
assert templates == []
assert fields == []
def test_deduplicates_fields(self):
fields = []
templates = []
info = {"report_name": "R", "parameters": [{"name": "dup", "type": "String", "description": ""}], "fields": []}
_collect_from_result({"jrxml_info": info, "filename": "a.jrxml"}, fields, templates)
_collect_from_result({"jrxml_info": info, "filename": "b.jrxml"}, fields, templates)
assert sum(1 for f in fields if f["name"] == "dup") == 1
+214
View File
@@ -0,0 +1,214 @@
"""kb_searcher.py 测试 — KBChromaSearcher 创建, 搜索, 模板检索。"""
import sys
import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent))
from backend.kb_searcher import (
KBChromaSearcher, get_kb_searcher, search_kb, search_templates_in_kb,
)
@pytest.fixture
def mock_chromadb(monkeypatch):
mock_client = MagicMock()
mock_collection = MagicMock()
mock_client.get_collection.return_value = mock_collection
mock_client.create_collection.return_value = mock_collection
monkeypatch.setattr(
"chromadb.PersistentClient",
lambda path: mock_client)
mock_st = MagicMock()
mock_st_model = MagicMock()
mock_st_model.encode.return_value = MagicMock()
mock_st_model.encode.return_value.tolist.return_value = [0.1, 0.2, 0.3]
mock_st.return_value = mock_st_model
monkeypatch.setattr("sentence_transformers.SentenceTransformer", mock_st)
yield {"client": mock_client, "collection": mock_collection,
"st_model": mock_st_model, "st": mock_st}
@pytest.fixture
def searcher(mock_chromadb):
with tempfile.TemporaryDirectory(prefix="test_chroma_") as tmpdir:
s = KBChromaSearcher(chroma_path=tmpdir, collection_name="test_kb")
s._model = mock_chromadb["st_model"]
s._client = mock_chromadb["client"]
s._collection = mock_chromadb["collection"]
yield s
# ── 创建 & 就绪检查 ─────────────────────────────────────────────
class TestKBChromaSearcherInit:
def test_creates_with_defaults(self, mock_chromadb):
with tempfile.TemporaryDirectory(prefix="test_chroma_") as tmpdir:
s = KBChromaSearcher(chroma_path=tmpdir)
assert s.collection_name == "kb_chunks"
assert s.chroma_path == str(tmpdir)
def test_custom_collection_name(self, mock_chromadb):
with tempfile.TemporaryDirectory(prefix="test_chroma_") as tmpdir:
s = KBChromaSearcher(chroma_path=tmpdir, collection_name="custom")
assert s.collection_name == "custom"
def test_model_lazy_loaded(self, mock_chromadb):
with tempfile.TemporaryDirectory(prefix="test_chroma_") as tmpdir:
s = KBChromaSearcher(chroma_path=tmpdir)
assert s._model is None
def test_is_ready_true(self, searcher):
assert searcher.is_ready() is True
def test_is_ready_false(self, searcher, mock_chromadb):
mock_chromadb["client"].get_collection.side_effect = Exception("no collection")
assert searcher.is_ready() is False
# ── 搜索 ────────────────────────────────────────────────────────
class TestSearch:
def test_search_returns_empty_when_not_ready(self, searcher, mock_chromadb):
mock_chromadb["client"].get_collection.side_effect = Exception("no collection")
results = searcher.search("test query")
assert results == []
def test_search_calls_collection_query(self, searcher, mock_chromadb):
mock_chromadb["collection"].query.return_value = {
"ids": [["chunk_0", "chunk_1"]],
"documents": [["doc1", "doc2"]],
"metadatas": [[{"chunk_type": "md"}, {"chunk_type": "txt"}]],
"distances": [[0.1, 0.3]],
}
results = searcher.search("query", k=5)
assert len(results) == 2
assert results[0]["id"] == "chunk_0"
assert results[0]["content"] == "doc1"
assert results[0]["metadata"]["chunk_type"] == "md"
assert results[0]["distance"] == 0.1
def test_search_respects_threshold(self, searcher, mock_chromadb):
mock_chromadb["collection"].query.return_value = {
"ids": [["chunk_0", "chunk_1"]],
"documents": [["doc1", "doc2"]],
"metadatas": [[{}, {}]],
"distances": [[0.2, 0.8]],
}
results = searcher.search("query", threshold=0.5)
assert len(results) == 1
assert results[0]["id"] == "chunk_0"
def test_search_empty_results(self, searcher, mock_chromadb):
mock_chromadb["collection"].query.return_value = {
"ids": [[]], "documents": [[]], "metadatas": [[]], "distances": [[]],
}
assert searcher.search("query") == []
# ── 模板搜索 ────────────────────────────────────────────────────
class TestSearchTemplates:
def test_filters_jrxml_chunks(self, searcher, mock_chromadb):
mock_chromadb["collection"].query.return_value = {
"ids": [["c0", "c1", "c2"]],
"documents": [["t1", "t2", "t3"]],
"metadatas": [[
{"chunk_type": "jrxml_template", "report_name": "R1"},
{"chunk_type": "md_section"},
{"chunk_type": "jrxml_template", "report_name": "R2"},
]],
"distances": [[0.1, 0.2, 0.3]],
}
templates = searcher.search_templates("query", k=3)
assert len(templates) >= 1
for t in templates:
meta = t["metadata"]
assert "jrxml" in meta.get("chunk_type", "").lower() or meta.get("report_name")
def test_no_jrxml_chunks_returns_empty(self, searcher, mock_chromadb):
mock_chromadb["collection"].query.return_value = {
"ids": [["c0"]],
"documents": [["text"]],
"metadatas": [[{"chunk_type": "md_section"}]],
"distances": [[0.1]],
}
templates = searcher.search_templates("query")
assert templates == []
# ── search_as_context ───────────────────────────────────────────
class TestSearchAsContext:
def test_returns_formatted_context(self, searcher, mock_chromadb):
mock_chromadb["collection"].query.return_value = {
"ids": [["c0", "c1"]],
"documents": [["内容1", "内容2"]],
"metadatas": [[
{"chunk_type": "md", "report_name": "R1"},
{"chunk_type": "txt"},
]],
"distances": [[0.1, 0.2]],
}
ctx = searcher.search_as_context("q", k=2)
assert "内容1" in ctx
assert "内容2" in ctx
assert "类型" in ctx
assert "报表" in ctx
assert "---" in ctx
def test_empty_returns_empty_string(self, searcher, mock_chromadb):
mock_chromadb["collection"].query.return_value = {
"ids": [[]], "documents": [[]], "metadatas": [[]], "distances": [[]],
}
assert searcher.search_as_context("q") == ""
# ── add_chunks ──────────────────────────────────────────────────
class TestAddChunks:
def test_add_chunks_calls_upsert(self, searcher, mock_chromadb):
chunks = [{"id": "c0", "content": "test content", "metadata": {"chunk_type": "md"}}]
searcher.add_chunks(chunks)
mock_chromadb["collection"].upsert.assert_called_once()
kwargs = mock_chromadb["collection"].upsert.call_args[1]
assert kwargs["ids"] == ["c0"]
assert kwargs["documents"] == ["test content"]
def test_empty_chunks_noop(self, searcher, mock_chromadb):
searcher.add_chunks([])
mock_chromadb["collection"].upsert.assert_not_called()
# ── 工厂函数 ────────────────────────────────────────────────────
class TestGetKbSearcher:
def test_returns_cached_instance(self, monkeypatch, mock_chromadb):
with tempfile.TemporaryDirectory(prefix="test_chroma_") as tmpdir:
monkeypatch.setattr(
"backend.kb_manager.get_kb_chroma_path",
lambda kb_id: Path(tmpdir) if kb_id == "abcdef1234567890abcd" else None)
s1 = get_kb_searcher("abcdef1234567890abcd")
s2 = get_kb_searcher("abcdef1234567890abcd")
assert s1 is s2
def test_returns_none_for_invalid_kb(self, monkeypatch):
monkeypatch.setattr(
"backend.kb_manager.get_kb_chroma_path", lambda kb_id: None)
assert get_kb_searcher("deadbeef1234567890abcd") is None
class TestSearchKbFunction:
def test_returns_empty_for_invalid_kb(self, monkeypatch):
monkeypatch.setattr(
"backend.kb_manager.get_kb_chroma_path", lambda kb_id: None)
assert search_kb("deadbeef1234567890abcd", "query") == ""
def test_returns_empty_for_invalid_template_search(self, monkeypatch):
monkeypatch.setattr(
"backend.kb_manager.get_kb_chroma_path", lambda kb_id: None)
assert search_templates_in_kb("deadbeef1234567890abcd", "query") == []
+200
View File
@@ -0,0 +1,200 @@
"""程序化字段映射单元测试。
测试 _programmatic_map_fields _sanitize_field_name
的确定性替换行为以及 validate_element_count 校验
"""
from __future__ import annotations
import pytest
from agent.nodes import _programmatic_map_fields, _sanitize_field_name
from agent.jrxml_windower import count_elements, validate_element_count
# ── 最小 JRXML 模板(含占位字段)────────────────────────────────────
JRXML_WITH_PLACEHOLDERS = """<?xml version="1.0" encoding="UTF-8"?>
<jasperReport name="test" pageWidth="595" pageHeight="842">
<field name="field_1" class="java.lang.String"/>
<field name="field_2" class="java.math.BigDecimal"/>
<field name="field_3" class="java.lang.String"/>
<queryString><![CDATA[SELECT * FROM t]]></queryString>
<title>
<band height="50">
<staticText>
<reportElement x="0" y="0" width="100" height="20"/>
<text><![CDATA[$F{field_1}]]></text>
</staticText>
<textField>
<reportElement x="100" y="0" width="80" height="20"/>
<textFieldExpression><![CDATA[$F{field_2}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="200" y="0" width="80" height="20"/>
<textFieldExpression><![CDATA[$F{field_3}]]></textFieldExpression>
</textField>
</band>
</title>
<detail>
<band height="30">
<textField>
<reportElement x="0" y="0" width="100" height="20"/>
<textFieldExpression><![CDATA[$F{field_1} + " " + $F{field_2}]]></textFieldExpression>
</textField>
</band>
</detail>
</jasperReport>"""
# ── _sanitize_field_name 测试 ────────────────────────────────────────
class TestSanitizeFieldName:
def test_ascii_name_passes_through(self):
assert _sanitize_field_name("customer_name") == "customer_name"
def test_uppercase_lowered(self):
assert _sanitize_field_name("CustomerName") == "customername"
def test_spaces_replaced(self):
assert _sanitize_field_name("customer name") == "customer_name"
def test_chinese_characters_escaped(self):
result = _sanitize_field_name("发票代码")
assert "发票" not in result
assert "u53d1_" in result
assert "u7968_" in result
def test_mixed_ascii_chinese(self):
result = _sanitize_field_name("发票_code")
assert "_code" in result
assert "u53d1_" in result
def test_empty_returns_unnamed(self):
assert _sanitize_field_name("") == "unnamed_field"
def test_all_special_chars_returns_unnamed(self):
assert _sanitize_field_name("!!!") == "unnamed_field"
def test_leading_digit_prefixed(self):
result = _sanitize_field_name("123abc")
assert result == "f_123abc"
def test_consecutive_underscores_collapsed(self):
result = _sanitize_field_name("a__b___c")
assert result == "a_b_c"
def test_japanese_characters_escaped(self):
result = _sanitize_field_name("請求書")
assert "請求" not in result
# ── _programmatic_map_fields 测试 ────────────────────────────────────
class TestProgrammaticMapFields:
def test_replaces_field_declarations(self):
ocr = [
{"field_name": "customer_name"},
{"field_name": "total_amount"},
{"field_name": "invoice_date"},
]
result = _programmatic_map_fields(JRXML_WITH_PLACEHOLDERS, ocr)
assert 'field name="customer_name"' in result
assert 'field name="total_amount"' in result
assert 'field name="invoice_date"' in result
assert 'field name="field_1"' not in result
def test_replaces_field_references(self):
ocr = [
{"field_name": "customer_name"},
{"field_name": "total_amount"},
{"field_name": "invoice_date"},
]
result = _programmatic_map_fields(JRXML_WITH_PLACEHOLDERS, ocr)
assert "$F{field_1}" not in result
assert "$F{customer_name}" in result
assert "$F{total_amount}" in result
assert "$F{invoice_date}" in result
def test_preserves_element_count(self):
ocr = [
{"field_name": "customer_name"},
{"field_name": "total_amount"},
{"field_name": "invoice_date"},
]
result = _programmatic_map_fields(JRXML_WITH_PLACEHOLDERS, ocr)
orig = count_elements(JRXML_WITH_PLACEHOLDERS)
mod = count_elements(result)
assert orig == mod, f"Elements: {orig} -> {mod}"
def test_preserves_coordinates(self):
ocr = [
{"field_name": "customer_name"},
{"field_name": "total_amount"},
{"field_name": "invoice_date"},
]
result = _programmatic_map_fields(JRXML_WITH_PLACEHOLDERS, ocr)
assert 'x="0"' in result
assert 'x="100"' in result
assert 'x="200"' in result
assert 'y="0"' in result
assert 'width="100"' in result
assert 'height="20"' in result
def test_partial_fields_preserved(self):
"""当 OCR 字段少于占位字段时,多余占位字段保留。"""
ocr = [
{"field_name": "customer_name"},
{"field_name": "total_amount"},
]
result = _programmatic_map_fields(JRXML_WITH_PLACEHOLDERS, ocr)
assert 'field name="field_3"' in result
assert "$F{field_3}" in result
def test_empty_field_name_skipped(self):
"""空 field_name 的 OCR 字段不触发替换。"""
ocr = [
{"field_name": ""},
{"field_name": "total_amount"},
{"field_name": ""},
]
result = _programmatic_map_fields(JRXML_WITH_PLACEHOLDERS, ocr)
assert '$F{field_1}' in result
assert '$F{total_amount}' in result
assert '$F{field_3}' in result
def test_no_ocr_fields_no_change(self):
result = _programmatic_map_fields(JRXML_WITH_PLACEHOLDERS, [])
assert result == JRXML_WITH_PLACEHOLDERS
def test_chinese_field_names_sanitized(self):
ocr = [
{"field_name": "发票代码"},
{"field_name": "发票号码"},
{"field_name": "金额"},
]
result = _programmatic_map_fields(JRXML_WITH_PLACEHOLDERS, ocr)
assert "发票代码" not in result
def test_validate_element_count_passes(self):
ocr = [
{"field_name": "customer_name"},
{"field_name": "total_amount"},
{"field_name": "invoice_date"},
]
result = _programmatic_map_fields(JRXML_WITH_PLACEHOLDERS, ocr)
validation = validate_element_count(
JRXML_WITH_PLACEHOLDERS, result, "map_fields"
)
assert validation["ok"] is True
assert validation["modified"] == validation["original"]
def test_expression_with_multiple_fields(self):
"""包含多个 $F{} 的表达式正确替换。"""
ocr = [
{"field_name": "unit_price"},
{"field_name": "quantity"},
]
result = _programmatic_map_fields(JRXML_WITH_PLACEHOLDERS, ocr)
assert '$F{unit_price}' in result
assert '$F{quantity}' in result
assert '$F{field_1}' not in result
assert '$F{field_2}' not in result