智能体按 reply_code 读后台回复语;陆运整车报价单顶栏色与填表更稳,工单页展示同步收紧。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-10-07 11:41:09 +08:00
co-authored by Cursor
parent 177dd0b25f
commit 419b4a1bf7
35 changed files with 2439 additions and 268 deletions
@@ -44,12 +44,28 @@ public class InquiryAgentController {
@Autowired
private org.jeecg.modules.inquiry.tms.InquiryTmsCabinService cabinService;
@Autowired
private org.jeecg.modules.inquiry.service.IInquiryBotReplyService botReplyService;
@Value("${inquiry.agent.callback-token:${AGENT_CALLBACK_TOKEN:}}")
private String agentToken;
@Value("${inquiry.agent.biz-token:${BIZ_API_TOKEN:}}")
private String bizToken;
/**
* 智能体只读回复语。停用或不在列表里的编码由智能体回退默认句。
* 鉴权同其它 /inquiry/agent/** ,禁止后台菜单权限码。
*/
@ApiOperation("智能体-已启用回复语")
@GetMapping("/botReply/listEnabled")
public Result<java.util.List<java.util.Map<String, Object>>> botReplyListEnabled(HttpServletRequest http) {
if (!authed(http)) {
return Result.error(401, "unauthorized");
}
return Result.OK(botReplyService.listEnabledForAgent());
}
@ApiOperation("智能体-回写模板 AI 映射结果")
@PutMapping("/quoteTemplate/parseResult")
public Result<String> quoteTemplateParseResult(@RequestBody Map<String, Object> body,
@@ -21,7 +21,7 @@ import java.util.List;
/**
* 机器人回复语 API。
* 支持按分类拉取全部条目(后台九分类页)与分页查询。
* 调用编码 replyCode 给智能体日后按码取文案;本期只维护、不向智能体下发。
* 调用编码 replyCode 供智能体 GET /inquiry/agent/botReply/listEnabled 按码取文案。
*/
@Slf4j
@Api(tags = "询价-机器人回复语")
@@ -123,7 +123,7 @@ public class InquiryTicket implements Serializable {
private List<InquiryPriceLine> saleLines;
/**
* 利润点逐行。不入库。没调过价为空。最后一行是整单利润点。
* 利润点逐行。不入库。没调过价每项都是 0%。最后一行是整单利润点。
*/
@TableField(exist = false)
@ApiModelProperty("利润点逐行(展示)")
@@ -22,4 +22,10 @@ public interface IInquiryBotReplyService extends IService<InquiryBotReply> {
* 编辑正文/启停。已有调用编码不可被请求覆盖。
*/
void updateReply(InquiryBotReply body);
/**
* 智能体只读:已启用且已有调用编码的正文。
* 停用项不返回,智能体回退默认句。
*/
java.util.List<java.util.Map<String, Object>> listEnabledForAgent();
}
@@ -9,7 +9,13 @@ import org.jeecg.modules.inquiry.service.IInquiryBotReplyService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
@@ -90,6 +96,35 @@ public class InquiryBotReplyServiceImpl extends ServiceImpl<InquiryBotReplyMappe
body.setReplyCode(code);
}
@Override
public List<Map<String, Object>> listEnabledForAgent() {
QueryWrapper<InquiryBotReply> qw = new QueryWrapper<>();
qw.eq("enabled", 1);
qw.isNotNull("reply_code");
qw.ne("reply_code", "");
qw.orderByAsc("sort_no").orderByAsc("id");
Page<InquiryBotReply> page = new Page<>(1, 2000);
page.setSearchCount(false);
page.setMaxLimit(2000L);
List<InquiryBotReply> rows = page(page, qw).getRecords();
List<Map<String, Object>> out = new ArrayList<>();
if (rows == null) {
return out;
}
for (InquiryBotReply row : rows) {
if (row == null || StringUtils.isBlank(row.getReplyCode())) {
continue;
}
Map<String, Object> item = new LinkedHashMap<>();
item.put("replyCode", row.getReplyCode());
item.put("replyText", row.getReplyText());
item.put("triggerScene", row.getTriggerScene());
item.put("categoryCode", row.getCategoryCode());
out.add(item);
}
return out;
}
private void assertCodeUnique(String replyCode, String excludeId) {
if (StringUtils.isBlank(replyCode)) {
return;
@@ -686,7 +686,7 @@ public class InquiryDashboardServiceImpl implements IInquiryDashboardService {
return;
}
String username = StringUtils.trimToEmpty(login.getUsername());
String wecom = StringUtils.trimToEmpty(login.getWorkNo());
String wecom = dashboardWecomId(login);
qw.and(w -> {
boolean any = false;
if (StringUtils.isNotBlank(wecom)) {
@@ -734,4 +734,20 @@ public class InquiryDashboardServiceImpl implements IInquiryDashboardService {
return false;
}
}
/**
* 看板「只看自己」用的企微工号。
* 不用直接调 getWorkNo:测服打包时基座 LoginUser 可能还没有这个方法。
*/
private static String dashboardWecomId(LoginUser login) {
if (login == null) {
return "";
}
try {
Object v = login.getClass().getMethod("getWorkNo").invoke(login);
return v == null ? "" : StringUtils.trimToEmpty(String.valueOf(v));
} catch (ReflectiveOperationException ignored) {
return "";
}
}
}
@@ -19,7 +19,9 @@ import java.util.List;
* <p>
* 职责:从已保存的报价明细铺开展示。不改调价算法,不把各行金额加总冒充费用合计。
* 海运/陆运:成本是调价前生效价(手工同名盖 TMS,多出来的补上);销售是最新调价。
* 空运:成本整列空;销售是当前生效价;利润点只在调过价之后,对调价前销售价。
* 空运:成本整列空;销售是当前生效价。
* 未调价:各项利润点与费用合计都是 0%,禁止拿金额去填利润点列。
* 已调价:利润点对调价前同名行。
* 副作用:改 ticket 的三列逐行;手工盖价算出新的官方总价时,同时改成本价和利润点字符串。不写库。
* 调用:{@link InquiryTicketPriceSupport#apply} 末尾。列表 enrich 与保存报价后的 update 都走那里。
*/
@@ -126,7 +128,7 @@ public final class InquiryQuoteLineSupport {
profitTotal = StringUtils.defaultString(InquiryTicketPriceSupport.profitText(saleTotal, baseTotal));
ticket.setProfitRate(profitTotal);
} else if (!adjusted) {
ticket.setProfitRate("");
ticket.setProfitRate("0%");
}
if (air) {
@@ -134,13 +136,13 @@ public final class InquiryQuoteLineSupport {
ticket.setSaleLines(moneyLines(saleFees, saleTotal));
ticket.setProfitLines(adjusted && StringUtils.isNotBlank(profitTotal)
? profitAgainst(baselineFees, saleFees, profitTotal)
: Collections.<InquiryPriceLine>emptyList());
: zeroPercentLines(saleFees));
return;
}
if (!adjusted) {
ticket.setCostLines(moneyLines(costFees, costTotal));
ticket.setSaleLines(Collections.<InquiryPriceLine>emptyList());
ticket.setProfitLines(Collections.<InquiryPriceLine>emptyList());
ticket.setProfitLines(zeroPercentLines(costFees));
return;
}
List<Aligned> rows = align(costFees, saleFees);
@@ -216,6 +218,21 @@ public final class InquiryQuoteLineSupport {
return formatMoney(base.add(delta), totalCcy);
}
/**
* 未调价:每一项费用和费用合计都写 0%。
* 列表/详情都走这里,避免利润点列空白或误把金额填进去。
*/
private static List<InquiryPriceLine> zeroPercentLines(List<FeeRow> fees) {
List<InquiryPriceLine> lines = new ArrayList<InquiryPriceLine>();
if (fees != null) {
for (FeeRow fee : fees) {
lines.add(line(fee.name, "0%"));
}
}
lines.add(line(TOTAL_NAME, "0%"));
return lines;
}
private static List<InquiryPriceLine> moneyLines(List<FeeRow> fees, String totalText) {
List<InquiryPriceLine> lines = new ArrayList<InquiryPriceLine>();
if (fees != null) {
@@ -14,7 +14,8 @@ import java.util.regex.Pattern;
* 后台成本价 / 销售价 / 利润点:按业务线从报价记录重算。
* <p>
* 海运/陆运:TMS、产品报价是成本;销售调价后才有销售价,利润点=(销售-成本)/成本。
* 空运:成本价始终空;TMS/航线/调价都是销售侧;利润点只在销售调过价后算。
* 空运:成本价始终空;TMS/航线/调价都是销售侧。
* 未调价时利润点固定 0%(含各项),调过价才按差额算。
* 副作用:只改传入 ticket 的展示字段(三个总价字符串和三列逐行),不写库。调用方决定是否 updateById。
*/
public final class InquiryTicketPriceSupport {
@@ -63,7 +64,7 @@ public final class InquiryTicketPriceSupport {
ticket.setProfitRate(profitText(adjustPrice, baseline));
} else {
ticket.setSalePrice(StringUtils.isNotBlank(manualPrice) ? manualPrice : tmsPrice);
ticket.setProfitRate("");
ticket.setProfitRate("0%");
}
} else {
String cost = StringUtils.isNotBlank(manualPrice) ? manualPrice : tmsPrice;
@@ -73,7 +74,7 @@ public final class InquiryTicketPriceSupport {
ticket.setProfitRate(profitText(adjustPrice, cost));
} else {
ticket.setSalePrice("");
ticket.setProfitRate("");
ticket.setProfitRate("0%");
}
}
// 三个总价定完后,按同一批报价拆成逐行。有手工明细盖 TMS 时,这里会改写成本价和利润点。
@@ -689,7 +689,7 @@ public class InquiryTmsQueryService {
/**
* 把本次 TMS 报价写回工单主表,供后台列表/详情展示。
* 海运/陆运进成本价,空运进销售价;利润点必须等销售调价后才算。
* 海运/陆运进成本价,空运进销售价;未调价利润点为 0%,调过价才按差额算。
*/
static void applyQuoteToTicket(InquiryTicket ticket, Map<String, String> quote) {
if (ticket == null || quote == null) {
@@ -28,13 +28,16 @@ public class InquiryQuoteLineSupportTest {
+ "]}")));
assertEquals("CNY 1200.00", ticket.getCostPrice());
assertEquals("", ticket.getSalePrice());
assertEquals("", ticket.getProfitRate());
assertEquals("0%", ticket.getProfitRate());
assertLines(ticket.getCostLines(),
"运输费", "CNY 1000",
"卸货费", "CNY 200",
"费用合计", "CNY 1200.00");
assertTrue(ticket.getSaleLines() == null || ticket.getSaleLines().isEmpty());
assertTrue(ticket.getProfitLines() == null || ticket.getProfitLines().isEmpty());
assertLines(ticket.getProfitLines(),
"运输费", "0%",
"卸货费", "0%",
"费用合计", "0%");
}
@Test
@@ -113,7 +116,12 @@ public class InquiryQuoteLineSupportTest {
assertTrue(before.getCostLines() == null || before.getCostLines().isEmpty());
assertEquals("", before.getCostPrice());
assertEquals("CNY 2000.00", before.getSalePrice());
assertTrue(before.getProfitLines() == null || before.getProfitLines().isEmpty());
assertEquals("0%", before.getProfitRate());
assertLines(before.getProfitLines(),
"空运费", "0%",
"增点费", "0%",
"拆板费", "0%",
"费用合计", "0%");
assertLines(before.getSaleLines(),
"空运费", "CNY 1800",
"增点费", "CNY 100",
@@ -189,6 +197,11 @@ public class InquiryQuoteLineSupportTest {
"费用合计", "CNY 1320.00");
assertEquals("CNY 1320.00", ticket.getCostPrice());
assertTrue(ticket.getSaleLines() == null || ticket.getSaleLines().isEmpty());
assertLines(ticket.getProfitLines(),
"运输费", "0%",
"卸货费", "0%",
"配送费", "0%",
"费用合计", "0%");
}
private static void assertLines(List<InquiryPriceLine> lines, String... nameText) {
@@ -21,7 +21,7 @@ public class InquiryTicketPriceSupportTest {
InquiryTicketPriceSupport.apply(ticket, Collections.singletonList(quote("TMS", "TMS标准报价", "CNY 121.00")));
assertEquals("CNY 121.00", ticket.getCostPrice());
assertEquals("", ticket.getSalePrice());
assertEquals("", ticket.getProfitRate());
assertEquals("0%", ticket.getProfitRate());
}
@Test
@@ -53,7 +53,7 @@ public class InquiryTicketPriceSupportTest {
InquiryTicketPriceSupport.apply(ticket, Collections.singletonList(quote("TMS", "TMS标准报价", "USD 1280")));
assertEquals("", ticket.getCostPrice());
assertEquals("USD 1280", ticket.getSalePrice());
assertEquals("", ticket.getProfitRate());
assertEquals("0%", ticket.getProfitRate());
}
@Test
@@ -1543,6 +1543,9 @@ public class QuoteTemplateFillTest {
matrix.getCell(15).setCellStyle(matrixBlue);
sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(7, 8, 2, 2));
sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(7, 8, 9, 9));
org.apache.poi.ss.usermodel.Row sub = row(sheet, 8);
sub.createCell(3).setCellValue("最低收费");
sub.createCell(4).setCellValue("元/KG");
org.apache.poi.ss.usermodel.Font red = wb.createFont();
red.setColor(org.apache.poi.ss.usermodel.IndexedColors.RED.getIndex());
org.apache.poi.ss.usermodel.CellStyle redOnSky = wb.createCellStyle();
@@ -1553,6 +1556,14 @@ public class QuoteTemplateFillTest {
currencyHead.setCellStyle(redOnSky);
matrix.getCell(10).setCellStyle(redOnSky);
org.apache.poi.ss.usermodel.Row data = row(sheet, 9);
org.apache.poi.ss.usermodel.Font blackData = wb.createFont();
blackData.setFontName("宋体");
blackData.setFontHeightInPoints((short) 10);
blackData.setColor(org.apache.poi.ss.usermodel.IndexedColors.BLACK.getIndex());
org.apache.poi.ss.usermodel.CellStyle priceStyle = wb.createCellStyle();
priceStyle.setFont(blackData);
data.createCell(4).setCellValue(12);
data.getCell(4).setCellStyle(priceStyle);
org.apache.poi.ss.usermodel.Cell dest = data.createCell(2);
dest.setCellStyle(redOnSky);
org.apache.poi.ss.usermodel.Row delivery = row(sheet, 18);
@@ -1610,6 +1621,9 @@ public class QuoteTemplateFillTest {
assertEquals(org.apache.poi.ss.usermodel.VerticalAlignment.CENTER,
sheet.getRow(9).getCell(2).getCellStyle().getVerticalAlignment());
assertFalse(QuoteTemplateExcelSupport.fontIsRed(sheet.getRow(9).getCell(2)));
assertFalse(sheet.getRow(9).getCell(2).getCellStyle().getShrinkToFit());
assertEquals(QuoteTemplateExcelSupport.fontHeight(sheet.getRow(9).getCell(4)),
QuoteTemplateExcelSupport.fontHeight(sheet.getRow(9).getCell(2)));
assertEquals("始发地", read(sheet, 7, 1));
assertEquals("币种", read(sheet, 7, 9));
byte[] originFill = QuoteTemplateExcelSupport.readFillRgb(sheet.getRow(7).getCell(1));
@@ -1678,6 +1692,133 @@ public class QuoteTemplateFillTest {
}
}
/**
* 跨境整车:列头是「起始地」,城市必须写在下一行白格,不能盖掉列名。
*/
@Test
public void landCrossBorderFtlKeepsOriginHeader() throws Exception {
Path src = Files.createTempFile("quote-land-cb-ftl-src", ".xlsx");
Path out = Files.createTempFile("quote-land-cb-ftl-out", ".xlsx");
try (Workbook wb = new XSSFWorkbook()) {
Sheet sheet = wb.createSheet("跨境整车");
org.apache.poi.ss.usermodel.Row top = row(sheet, 6);
top.createCell(1).setCellValue("路线");
top.createCell(3).setCellValue("车型");
org.apache.poi.ss.usermodel.Row head = row(sheet, 7);
head.createCell(1).setCellValue("起始地");
head.createCell(2).setCellValue("目的地");
head.createCell(3).setCellValue("3T");
head.createCell(4).setCellValue("5T");
head.createCell(5).setCellValue("8T");
try (java.io.OutputStream os = Files.newOutputStream(src)) {
wb.write(os);
}
}
JSONObject mapping = new JSONObject(true);
mapping.put("sheet", "跨境整车");
JSONObject fields = new JSONObject(true);
fields.put("origin", "跨境整车!B8");
fields.put("destination", "跨境整车!C8");
mapping.put("fields", fields);
JSONObject facts = new JSONObject(true);
facts.put("origin", "郑州");
facts.put("destination", "河内");
QuoteTemplateExcelSupport.fillWorkbook(src, out, mapping, facts);
try (Workbook filled = new XSSFWorkbook(Files.newInputStream(out))) {
Sheet sheet = filled.getSheetAt(0);
assertEquals("起始地", read(sheet, 7, 1));
assertEquals("目的地", read(sheet, 7, 2));
assertEquals("郑州", read(sheet, 8, 1));
assertEquals("河内", read(sheet, 8, 2));
} finally {
Files.deleteIfExists(src);
Files.deleteIfExists(out);
}
}
/**
* 跨境整车国内段只写境内起点,境外段只写境外终点,不能两段都写成郑州→吉隆坡。
*/
@Test
public void landCrossBorderFtlSplitsDomesticAndOverseasOd() throws Exception {
Path src = Files.createTempFile("quote-land-cb-split-src", ".xlsx");
Path out = Files.createTempFile("quote-land-cb-split-out", ".xlsx");
try (Workbook wb = new XSSFWorkbook()) {
Sheet sheet = wb.createSheet("跨境整车");
row(sheet, 5).createCell(0).setCellValue("一、国内段报价");
org.apache.poi.ss.usermodel.Row head1 = row(sheet, 7);
head1.createCell(1).setCellValue("起始地");
head1.createCell(2).setCellValue("目的地");
head1.createCell(3).setCellValue("3T");
head1.createCell(4).setCellValue("5T");
head1.createCell(15).setCellValue("时效");
head1.createCell(16).setCellValue("币种");
head1.createCell(17).setCellValue("燃油附加费率");
head1.createCell(18).setCellValue("含税合计");
row(sheet, 24).createCell(0).setCellValue("二、境外段报价");
org.apache.poi.ss.usermodel.Row head2 = row(sheet, 26);
head2.createCell(1).setCellValue("起始地");
head2.createCell(2).setCellValue("目的地");
head2.createCell(3).setCellValue("1.25T");
head2.createCell(4).setCellValue("2.5T");
head2.createCell(15).setCellValue("时效");
head2.createCell(16).setCellValue("币种");
head2.createCell(17).setCellValue("燃油附加费率");
head2.createCell(18).setCellValue("含税合计");
try (java.io.OutputStream os = Files.newOutputStream(src)) {
wb.write(os);
}
}
JSONObject mapping = new JSONObject(true);
mapping.put("sheet", "跨境整车");
JSONArray regions = new JSONArray();
JSONObject inland = new JSONObject(true);
inland.put("title", "国内段报价");
inland.put("headerRow", 8);
inland.put("dataStartRow", 8);
JSONObject cols1 = new JSONObject(true);
cols1.put("origin", "B");
cols1.put("destination", "C");
inland.put("columns", cols1);
regions.add(inland);
JSONObject overseas = new JSONObject(true);
overseas.put("title", "境外段报价");
overseas.put("headerRow", 27);
overseas.put("dataStartRow", 27);
JSONObject cols2 = new JSONObject(true);
cols2.put("origin", "B");
cols2.put("destination", "C");
overseas.put("columns", cols2);
regions.add(overseas);
mapping.put("routeRegions", regions);
JSONObject facts = new JSONObject(true);
facts.put("origin", "郑州");
facts.put("destination", "吉隆坡");
facts.put("currency", "CNY");
facts.put("transit_days", "N+3");
QuoteTemplateExcelSupport.fillWorkbook(src, out, mapping, facts);
try (Workbook filled = new XSSFWorkbook(Files.newInputStream(out))) {
Sheet sheet = filled.getSheetAt(0);
assertEquals("起始地", read(sheet, 7, 1));
assertEquals("郑州", read(sheet, 8, 1));
assertEquals("", read(sheet, 8, 2));
assertEquals("起始地", read(sheet, 26, 1));
assertEquals("", read(sheet, 27, 1));
assertEquals("吉隆坡", read(sheet, 27, 2));
assertEquals("N+3", read(sheet, 8, 15));
assertEquals("CNY", read(sheet, 8, 16));
assertEquals("", read(sheet, 8, 17));
assertEquals("", read(sheet, 8, 18));
assertEquals("N+3", read(sheet, 27, 15));
assertEquals("CNY", read(sheet, 27, 16));
assertEquals("", read(sheet, 27, 17));
assertEquals("", read(sheet, 27, 18));
} finally {
Files.deleteIfExists(src);
Files.deleteIfExists(out);
}
}
/**
* 国内整车:4.2米/3T 这类车型费用也是标签右侧,不是海运费目表。
*/
@@ -1967,6 +2108,113 @@ public class QuoteTemplateFillTest {
}
}
/**
* 国内拼车:元/CBM、元/KG 必须跟「始发站」同一浅蓝,不能留成顶栏深蓝。
*/
@Test
public void landDomesticLtlUnitHeaderRowIsKept() throws Exception {
Path src = Files.createTempFile("quote-land-ltl-src", ".xlsx");
Path out = Files.createTempFile("quote-land-ltl-out", ".xlsx");
try (Workbook wb = new XSSFWorkbook()) {
Sheet sheet = wb.createSheet("国内拼车");
org.apache.poi.ss.usermodel.CellStyle sky = wb.createCellStyle();
sky.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.SKY_BLUE.getIndex());
sky.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
org.apache.poi.ss.usermodel.CellStyle navy = wb.createCellStyle();
navy.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.ROYAL_BLUE.getIndex());
navy.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
org.apache.poi.ss.usermodel.Row head = row(sheet, 7);
head.createCell(1).setCellValue("始发站");
head.getCell(1).setCellStyle(sky);
head.createCell(2).setCellValue("目的地");
head.getCell(2).setCellStyle(sky);
head.createCell(5).setCellValue("运输费");
head.getCell(5).setCellStyle(sky);
org.apache.poi.ss.usermodel.Row units = row(sheet, 8);
units.createCell(5).setCellValue("元/CBM");
units.getCell(5).setCellStyle(navy);
units.createCell(6).setCellValue("元/KG");
units.getCell(6).setCellStyle(navy);
row(sheet, 9);
boolean[] keep = QuoteTemplateExcelSupport.markLandHeaderRows(sheet);
assertTrue(keep[7]);
try (java.io.OutputStream os = Files.newOutputStream(src)) {
wb.write(os);
}
}
JSONObject facts = new JSONObject(true);
facts.put("origin", "上海");
facts.put("destination", "江苏南京市");
facts.put("price_per_cbm", "100");
QuoteTemplateExcelSupport.fillWorkbook(src, out, new JSONObject(), facts);
try (Workbook filled = new XSSFWorkbook(Files.newInputStream(out))) {
Sheet sheet = filled.getSheetAt(0);
assertEquals("元/CBM", read(sheet, 8, 5));
assertEquals("元/KG", read(sheet, 8, 6));
byte[] unitRgb = QuoteTemplateExcelSupport.readFillRgb(sheet.getRow(8).getCell(5));
byte[] headRgb = QuoteTemplateExcelSupport.readFillRgb(sheet.getRow(7).getCell(1));
assertTrue(unitRgb != null && unitRgb.length >= 3);
assertTrue(headRgb != null && headRgb.length >= 3);
assertArrayEquals(headRgb, unitRgb);
} finally {
Files.deleteIfExists(src);
Files.deleteIfExists(out);
}
}
/**
* 国内整车:车型格「4.2米/3T」是浅蓝表头,出单后不能变成业务员那种深蓝。
*/
@Test
public void landDomesticFtlVehicleHeaderFillSurvives() throws Exception {
Path src = Files.createTempFile("quote-land-ftl-src", ".xlsx");
Path out = Files.createTempFile("quote-land-ftl-out", ".xlsx");
try (Workbook wb = new XSSFWorkbook()) {
Sheet sheet = wb.createSheet("国内整车");
org.apache.poi.ss.usermodel.CellStyle sky = wb.createCellStyle();
sky.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.SKY_BLUE.getIndex());
sky.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
org.apache.poi.ss.usermodel.CellStyle navy = wb.createCellStyle();
navy.setFillForegroundColor(org.apache.poi.ss.usermodel.IndexedColors.ROYAL_BLUE.getIndex());
navy.setFillPattern(org.apache.poi.ss.usermodel.FillPatternType.SOLID_FOREGROUND);
org.apache.poi.ss.usermodel.Row head = row(sheet, 7);
head.createCell(1).setCellValue("始发站");
head.getCell(1).setCellStyle(sky);
head.createCell(2).setCellValue("目的地");
head.getCell(2).setCellStyle(sky);
head.createCell(3).setCellValue("车型/费用");
head.getCell(3).setCellStyle(sky);
head.createCell(8).setCellValue("时效");
head.getCell(8).setCellStyle(sky);
org.apache.poi.ss.usermodel.Row veh = row(sheet, 8);
veh.createCell(3).setCellValue("4.2米/3T");
veh.getCell(3).setCellStyle(navy);
veh.createCell(4).setCellValue("7.6米/8T");
veh.getCell(4).setCellStyle(navy);
row(sheet, 9);
try (java.io.OutputStream os = Files.newOutputStream(src)) {
wb.write(os);
}
}
JSONObject facts = new JSONObject(true);
facts.put("origin", "北京");
facts.put("destination", "上海");
QuoteTemplateExcelSupport.fillWorkbook(src, out, new JSONObject(), facts);
try (Workbook filled = new XSSFWorkbook(Files.newInputStream(out))) {
Sheet sheet = filled.getSheetAt(0);
assertEquals("4.2米/3T", read(sheet, 8, 3));
assertEquals("7.6米/8T", read(sheet, 8, 4));
byte[] vehRgb = QuoteTemplateExcelSupport.readFillRgb(sheet.getRow(8).getCell(3));
byte[] headRgb = QuoteTemplateExcelSupport.readFillRgb(sheet.getRow(7).getCell(1));
assertTrue(vehRgb != null && vehRgb.length >= 3);
assertTrue(headRgb != null && headRgb.length >= 3);
assertArrayEquals(headRgb, vehRgb);
} finally {
Files.deleteIfExists(src);
Files.deleteIfExists(out);
}
}
private static org.apache.poi.ss.usermodel.Row row(Sheet sheet, int r) {
org.apache.poi.ss.usermodel.Row exist = sheet.getRow(r);
return exist == null ? sheet.createRow(r) : exist;