|
|
@@ -25,6 +25,7 @@ import org.springframework.util.StringUtils;
|
|
|
|
|
|
import com.uas.search.console.service.InnerOrderSearchService;
|
|
|
import com.uas.search.console.support.IndexSearcherManager;
|
|
|
+import com.uas.search.console.util.OrderSearchUtils;
|
|
|
import com.uas.search.console.util.SearchConstants;
|
|
|
import com.uas.search.console.util.SearchUtils;
|
|
|
import com.uas.search.exception.SearchException;
|
|
|
@@ -34,6 +35,12 @@ import com.uas.search.model.PageParams;
|
|
|
import com.uas.search.model.SPage;
|
|
|
import com.uas.search.service.OrderSearchService;
|
|
|
|
|
|
+/**
|
|
|
+ * 单据搜索实现
|
|
|
+ *
|
|
|
+ * @author sunyj
|
|
|
+ * @since 2016年10月18日 下午5:59:12
|
|
|
+ */
|
|
|
@Service
|
|
|
public class OrderSearchServiceImpl implements OrderSearchService, InnerOrderSearchService {
|
|
|
/**
|
|
|
@@ -71,6 +78,27 @@ public class OrderSearchServiceImpl implements OrderSearchService, InnerOrderSea
|
|
|
|
|
|
@Override
|
|
|
public SPage<BaseOrder> searchOrderIds(String keyword, PageParams params) {
|
|
|
+ logger.info("main");
|
|
|
+ return searchMainTable(keyword, params, SearchConstants.ORDER_TABLE_NAME);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SPage<BaseOrder> searchOrderInvoiceIds(String keyword, PageParams params) {
|
|
|
+ return searchMainTable(keyword, params, SearchConstants.ORDER_INVOICE_TABLE_NAME);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SPage<BaseOrder> searchPurchaseIds(String keyword, PageParams params) {
|
|
|
+ // TODO Auto-generated method stub
|
|
|
+ return searchMainTable(keyword, params, SearchConstants.PURCHASE_TABLE_NAME);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SPage<BaseOrder> searchPurchaseInvoiceIds(String keyword, PageParams params) {
|
|
|
+ return searchMainTable(keyword, params, SearchConstants.PURCHASE_INVOICE_TABLE_NAME);
|
|
|
+ }
|
|
|
+
|
|
|
+ public SPage<BaseOrder> searchOrder2Ids(String keyword, PageParams params) {
|
|
|
if (SearchUtils.isKeywordInvalid(keyword)) {
|
|
|
throw new SearchException("搜索关键词无效:" + keyword);
|
|
|
}
|
|
|
@@ -132,19 +160,21 @@ public class OrderSearchServiceImpl implements OrderSearchService, InnerOrderSea
|
|
|
new Term(SearchConstants.ORDER_SELLERENUU_FIELD, filters.get("or_sellerenuu").toString())),
|
|
|
BooleanClause.Occur.MUST);
|
|
|
}
|
|
|
- if (!StringUtils.isEmpty(filters.get("or_mintime"))
|
|
|
- || !StringUtils.isEmpty(filters.get("or_maxtime"))) {
|
|
|
+ if (!StringUtils.isEmpty(filters.get(SearchConstants.MIN_TIME_KEY))
|
|
|
+ || !StringUtils.isEmpty(filters.get(SearchConstants.MAX_TIME_KEY))) {
|
|
|
// 最小日期初始化为2016.1.1
|
|
|
Calendar calendar = Calendar.getInstance();
|
|
|
calendar.set(2016, 0, 1);
|
|
|
// 最大日期初始化为现在
|
|
|
long minTime = calendar.getTimeInMillis();
|
|
|
long maxTime = new Date().getTime();
|
|
|
- if (!StringUtils.isEmpty(filters.get("or_mintime"))) {
|
|
|
- minTime = Long.parseLong(filters.get("or_mintime").toString());
|
|
|
+ if (!StringUtils.isEmpty(filters.get(SearchConstants.MIN_TIME_KEY))) {
|
|
|
+ minTime = Long.parseLong(filters.get(SearchConstants.MIN_TIME_KEY).toString());
|
|
|
+ logger.info("--" + minTime);
|
|
|
}
|
|
|
- if (!StringUtils.isEmpty(filters.get("or_maxtime"))) {
|
|
|
- maxTime = Long.parseLong(filters.get("or_maxtime").toString());
|
|
|
+ if (!StringUtils.isEmpty(filters.get(SearchConstants.MAX_TIME_KEY))) {
|
|
|
+ maxTime = Long.parseLong(filters.get(SearchConstants.MAX_TIME_KEY).toString());
|
|
|
+ logger.info("--" + maxTime);
|
|
|
}
|
|
|
booleanQuery.add(NumericRangeQuery.newLongRange(SearchConstants.ORDER_CREATETIME_FIELD, minTime,
|
|
|
maxTime, true, true), BooleanClause.Occur.MUST);
|
|
|
@@ -325,24 +355,6 @@ public class OrderSearchServiceImpl implements OrderSearchService, InnerOrderSea
|
|
|
return sPage;
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public SPage<BaseOrder> searchOrderInvoiceIds(String keyword, PageParams params) {
|
|
|
- // TODO Auto-generated method stub
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public SPage<BaseOrder> searchPurchaseIds(String keyword, PageParams params) {
|
|
|
- // TODO Auto-generated method stub
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public SPage<BaseOrder> searchPurchaseInvoiceIds(String keyword, PageParams params) {
|
|
|
- // TODO Auto-generated method stub
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 根据销售单id获取其明细
|
|
|
*
|
|
|
@@ -449,4 +461,329 @@ public class OrderSearchServiceImpl implements OrderSearchService, InnerOrderSea
|
|
|
return documents;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 搜索主表索引
|
|
|
+ *
|
|
|
+ * @param keyword
|
|
|
+ * 关键词
|
|
|
+ * @param params
|
|
|
+ * 其他信息,可能有分页信息,filter中可能有可以过滤的信息
|
|
|
+ * @param tableName
|
|
|
+ * 主表名
|
|
|
+ * @return 搜索结果
|
|
|
+ */
|
|
|
+ private SPage<BaseOrder> searchMainTable(String keyword, PageParams params, String tableName) {
|
|
|
+ if (SearchUtils.isKeywordInvalid(keyword)) {
|
|
|
+ throw new SearchException("搜索关键词无效:" + keyword);
|
|
|
+ }
|
|
|
+ IndexSearcher indexSearcher = getIndexSearcher();
|
|
|
+ List<String> keywordFields = OrderSearchUtils.getKeywordFields(tableName);
|
|
|
+ List<String> filterFields = OrderSearchUtils.getFilterFields(tableName);
|
|
|
+
|
|
|
+ SPage<BaseOrder> sPage = new SPage<>();
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+
|
|
|
+ // 关键词带空格,进行与操作
|
|
|
+ String[] strs = keyword.split(" ");
|
|
|
+ for (String str : strs) {
|
|
|
+ // keyword可能是哪些域
|
|
|
+ BooleanQuery booleanQuery2 = new BooleanQuery();
|
|
|
+ for (String keywordField : keywordFields) {
|
|
|
+ booleanQuery2.add(SearchUtils.getBooleanQuery(keywordField, str), BooleanClause.Occur.SHOULD);
|
|
|
+ }
|
|
|
+ booleanQuery.add(booleanQuery2, BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 分页信息
|
|
|
+ if (params == null) {
|
|
|
+ sPage.setPage(PAGE_INDEX);
|
|
|
+ sPage.setSize(PAGE_SIZE);
|
|
|
+ sPage.setFirst(true);
|
|
|
+ } else {
|
|
|
+ int page = params.getPage();
|
|
|
+ if (page > 0) {
|
|
|
+ sPage.setPage(page);
|
|
|
+ } else {
|
|
|
+ sPage.setPage(PAGE_INDEX);
|
|
|
+ sPage.setFirst(true);
|
|
|
+ }
|
|
|
+ int size = params.getSize();
|
|
|
+ if (size > 0) {
|
|
|
+ sPage.setSize(size);
|
|
|
+ } else {
|
|
|
+ sPage.setSize(PAGE_SIZE);
|
|
|
+ }
|
|
|
+
|
|
|
+ // filter中的域、日期范围
|
|
|
+ Map<String, Object> filters = params.getFilters();
|
|
|
+ if (!CollectionUtils.isEmpty(filters)) {
|
|
|
+ // filter中可能会有哪些域
|
|
|
+ for (String filterField : filterFields) {
|
|
|
+ if (!StringUtils.isEmpty(filters.get(filterField))) {
|
|
|
+ booleanQuery.add(new TermQuery(new Term(filterField, filters.get(filterField).toString())),
|
|
|
+ BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果最小时间和最大时间至少传了一个参数,说明需要对时间范围进行限定
|
|
|
+ if (!StringUtils.isEmpty(filters.get(SearchConstants.MIN_TIME_KEY))
|
|
|
+ || !StringUtils.isEmpty(filters.get(SearchConstants.MAX_TIME_KEY))) {
|
|
|
+ // 最小日期初始化为2016.1.1
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ calendar.set(2016, 0, 1);
|
|
|
+ // 最大日期初始化为现在
|
|
|
+ long minTime = calendar.getTimeInMillis();
|
|
|
+ long maxTime = new Date().getTime();
|
|
|
+ if (!StringUtils.isEmpty(filters.get(SearchConstants.MIN_TIME_KEY))) {
|
|
|
+ minTime = Long.parseLong(filters.get(SearchConstants.MIN_TIME_KEY).toString());
|
|
|
+ }
|
|
|
+ if (!StringUtils.isEmpty(filters.get(SearchConstants.MAX_TIME_KEY))) {
|
|
|
+ maxTime = Long.parseLong(filters.get(SearchConstants.MAX_TIME_KEY).toString());
|
|
|
+ }
|
|
|
+ booleanQuery.add(NumericRangeQuery.newLongRange(SearchConstants.ORDER_CREATETIME_FIELD, minTime,
|
|
|
+ maxTime, true, true), BooleanClause.Occur.MUST);
|
|
|
+ logger.info(minTime);
|
|
|
+ logger.info(maxTime);
|
|
|
+ logger.info(new Date(minTime));
|
|
|
+ logger.info(new Date(maxTime));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ logger.info(booleanQuery);
|
|
|
+
|
|
|
+ try {
|
|
|
+ TopDocs topDocs;
|
|
|
+ // 如果页码不为1
|
|
|
+ if (sPage.getPage() > 1) {
|
|
|
+ TopDocs previousTopDocs = indexSearcher.search(booleanQuery, (sPage.getPage() - 1) * sPage.getSize());
|
|
|
+ int totalHits = previousTopDocs.totalHits;
|
|
|
+ ScoreDoc[] previousScoreDocs = previousTopDocs.scoreDocs;
|
|
|
+ logger.info(previousScoreDocs == null || previousScoreDocs.length == 0);
|
|
|
+ // 如果主表中未匹配到结果,再搜索其明细表
|
|
|
+ if (totalHits == 0) {
|
|
|
+ return searchDetailTable(keyword, params, OrderSearchUtils.getDetailTableName(tableName));
|
|
|
+ } else {
|
|
|
+ if ((sPage.getPage() - 1) * sPage.getSize() >= totalHits) {
|
|
|
+ throw new SearchException("页码过大:元素总数量为" + totalHits);
|
|
|
+ }
|
|
|
+ topDocs = indexSearcher.searchAfter(previousScoreDocs[previousScoreDocs.length - 1], booleanQuery,
|
|
|
+ sPage.getSize());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sPage.setFirst(true);
|
|
|
+ topDocs = indexSearcher.search(booleanQuery, sPage.getSize());
|
|
|
+ }
|
|
|
+ // 如果主表中未匹配到结果,再搜索其明细表
|
|
|
+ int totalHits = topDocs.totalHits;
|
|
|
+ if (totalHits < 1) {
|
|
|
+ return searchDetailTable(keyword, params, OrderSearchUtils.getDetailTableName(tableName));
|
|
|
+ }
|
|
|
+
|
|
|
+ sPage.setTotalElement(totalHits);
|
|
|
+ int totalPage = (int) Math.ceil(totalHits / (1.0 * sPage.getSize()));
|
|
|
+ sPage.setTotalPage(totalPage);
|
|
|
+ if (totalPage == sPage.getPage()) {
|
|
|
+ sPage.setLast(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ ScoreDoc[] scoreDocs = topDocs.scoreDocs;
|
|
|
+ List<BaseOrder> baseOrders = new ArrayList<>();
|
|
|
+ for (ScoreDoc scoreDoc : scoreDocs) {
|
|
|
+ Document document = indexSearcher.doc(scoreDoc.doc);
|
|
|
+ Long id = Long.valueOf(document.get(OrderSearchUtils.getIdField(tableName)));
|
|
|
+ String code = document.get(OrderSearchUtils.getCodeField(tableName));
|
|
|
+ BaseOrder baseOrder = new BaseOrder(id, code);
|
|
|
+ baseOrder.setDetails(getDetails(id, OrderSearchUtils.getDetailTableName(tableName)));
|
|
|
+ baseOrders.add(baseOrder);
|
|
|
+ }
|
|
|
+ sPage.setContent(baseOrders);
|
|
|
+ } catch (NumberFormatException | IOException e) {
|
|
|
+ throw new SearchException(e).setDetailedMessage(e);
|
|
|
+ } finally {
|
|
|
+ searcherManager.release(indexSearcher);
|
|
|
+ }
|
|
|
+ return sPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 搜索明细表索引
|
|
|
+ *
|
|
|
+ * @param keyword
|
|
|
+ * 关键词
|
|
|
+ * @param params
|
|
|
+ * 其他信息,可能有分页信息,filter中可能有可以过滤的信息
|
|
|
+ * @param tableName
|
|
|
+ * 明细表名
|
|
|
+ * @return 搜索结果
|
|
|
+ */
|
|
|
+ private SPage<BaseOrder> searchDetailTable(String keyword, PageParams params, String tableName) {
|
|
|
+ if (SearchUtils.isKeywordInvalid(keyword)) {
|
|
|
+ throw new SearchException("搜索关键词无效:" + keyword);
|
|
|
+ }
|
|
|
+ IndexSearcher indexSearcher = getIndexSearcher();
|
|
|
+ List<String> keywordFields = OrderSearchUtils.getKeywordFields(tableName);
|
|
|
+
|
|
|
+ SPage<BaseOrder> sPage = new SPage<>();
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+
|
|
|
+ // 关键词带空格,进行与操作
|
|
|
+ String[] strs = keyword.split(" ");
|
|
|
+ for (String str : strs) {
|
|
|
+ // keyword可能是哪些域
|
|
|
+ BooleanQuery booleanQuery2 = new BooleanQuery();
|
|
|
+ for (String keywordField : keywordFields) {
|
|
|
+ booleanQuery2.add(SearchUtils.getBooleanQuery(keywordField, str), BooleanClause.Occur.SHOULD);
|
|
|
+ }
|
|
|
+ booleanQuery.add(booleanQuery2, BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 分页信息
|
|
|
+ if (params == null) {
|
|
|
+ sPage.setPage(PAGE_INDEX);
|
|
|
+ sPage.setSize(PAGE_SIZE);
|
|
|
+ sPage.setFirst(true);
|
|
|
+ } else {
|
|
|
+ int page = params.getPage();
|
|
|
+ if (page > 0) {
|
|
|
+ sPage.setPage(page);
|
|
|
+ } else {
|
|
|
+ sPage.setPage(PAGE_INDEX);
|
|
|
+ sPage.setFirst(true);
|
|
|
+ }
|
|
|
+ int size = params.getSize();
|
|
|
+ if (size > 0) {
|
|
|
+ sPage.setSize(size);
|
|
|
+ } else {
|
|
|
+ sPage.setSize(PAGE_SIZE);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ logger.info(booleanQuery);
|
|
|
+
|
|
|
+ try {
|
|
|
+ TopDocs topDocs = null;
|
|
|
+ // 如果页码不为1
|
|
|
+ if (sPage.getPage() > 1) {
|
|
|
+ TopDocs previousTopDocs = indexSearcher.search(booleanQuery, (sPage.getPage() - 1) * sPage.getSize());
|
|
|
+ int totalHits = previousTopDocs.totalHits;
|
|
|
+ ScoreDoc[] previousScoreDocs = previousTopDocs.scoreDocs;
|
|
|
+ logger.info(previousScoreDocs == null || previousScoreDocs.length == 0);
|
|
|
+ if (totalHits != 0) {
|
|
|
+ if ((sPage.getPage() - 1) * sPage.getSize() >= totalHits) {
|
|
|
+ throw new SearchException("页码过大:元素总数量为" + totalHits);
|
|
|
+ }
|
|
|
+ topDocs = indexSearcher.searchAfter(previousScoreDocs[previousScoreDocs.length - 1], booleanQuery,
|
|
|
+ sPage.getSize());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sPage.setFirst(true);
|
|
|
+ topDocs = indexSearcher.search(booleanQuery, sPage.getSize());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (topDocs == null || topDocs.totalHits < 1) {
|
|
|
+ sPage.setTotalElement(0);
|
|
|
+ sPage.setFirst(true);
|
|
|
+ sPage.setLast(true);
|
|
|
+ return sPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ sPage.setTotalElement(topDocs.totalHits);
|
|
|
+ int totalPage = (int) Math.ceil(topDocs.totalHits / (1.0 * sPage.getSize()));
|
|
|
+ sPage.setTotalPage(totalPage);
|
|
|
+ if (totalPage == sPage.getPage()) {
|
|
|
+ sPage.setLast(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ ScoreDoc[] scoreDocs = topDocs.scoreDocs;
|
|
|
+ Map<Long, List<BaseOrderDetail>> map = new HashMap<>();
|
|
|
+ for (ScoreDoc scoreDoc : scoreDocs) {
|
|
|
+ Document document = indexSearcher.doc(scoreDoc.doc);
|
|
|
+ Long orderId = Long.valueOf(document.get(OrderSearchUtils.getMainTableIdField(tableName)));
|
|
|
+ List<BaseOrderDetail> baseOrderDetails = map.get(orderId);
|
|
|
+ if (CollectionUtils.isEmpty(baseOrderDetails)) {
|
|
|
+ baseOrderDetails = new ArrayList<>();
|
|
|
+ }
|
|
|
+
|
|
|
+ Long id = Long.valueOf(document.get(OrderSearchUtils.getIdField(tableName)));
|
|
|
+ String code = document.get(OrderSearchUtils.getCodeField(tableName));
|
|
|
+ String detno = document.get(OrderSearchUtils.getDetnoField(tableName));
|
|
|
+ if (!StringUtils.isEmpty(detno)) {
|
|
|
+ baseOrderDetails.add(new BaseOrderDetail(id, Short.valueOf(detno), code));
|
|
|
+ } else {
|
|
|
+ baseOrderDetails.add(new BaseOrderDetail(id, null, code));
|
|
|
+ }
|
|
|
+ map.put(orderId, baseOrderDetails);
|
|
|
+ }
|
|
|
+ sPage.setContent(getBaseOrdersByDetails(map, OrderSearchUtils.getMainTableName(tableName)));
|
|
|
+ } catch (NumberFormatException | IOException e) {
|
|
|
+ throw new SearchException(e).setDetailedMessage(e);
|
|
|
+ } finally {
|
|
|
+ searcherManager.release(indexSearcher);
|
|
|
+ }
|
|
|
+ return sPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 明细表中根据主表单据id(外键)获取其明细信息
|
|
|
+ *
|
|
|
+ * @param id
|
|
|
+ * 单据id
|
|
|
+ * @param tableName
|
|
|
+ * 明细表名
|
|
|
+ * @return 对应的明细信息
|
|
|
+ */
|
|
|
+ private List<BaseOrderDetail> getDetails(Long id, String tableName) {
|
|
|
+ if (id == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ List<Document> documents = getDocuments(OrderSearchUtils.getMainTableIdField(tableName), id.toString());
|
|
|
+ List<BaseOrderDetail> baseOrderDetails = new ArrayList<>();
|
|
|
+ for (Document document : documents) {
|
|
|
+ Long detailId = Long.valueOf(document.get(OrderSearchUtils.getIdField(tableName)));
|
|
|
+ String code = document.get(OrderSearchUtils.getCodeField(tableName));
|
|
|
+ String detno = document.get(OrderSearchUtils.getDetnoField(tableName));
|
|
|
+ if (!StringUtils.isEmpty(detno)) {
|
|
|
+ baseOrderDetails.add(new BaseOrderDetail(detailId, Short.valueOf(detno), code));
|
|
|
+ } else {
|
|
|
+ baseOrderDetails.add(new BaseOrderDetail(detailId, null, code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return baseOrderDetails;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 主表中根据单据明细获取主表数据,汇总后返回
|
|
|
+ *
|
|
|
+ * @param map
|
|
|
+ * 明细数据,主表id(外键)为key,销售单明细数据为值
|
|
|
+ * @return 汇总后的数据
|
|
|
+ */
|
|
|
+ private List<BaseOrder> getBaseOrdersByDetails(Map<Long, List<BaseOrderDetail>> map, String tableName) {
|
|
|
+ List<BaseOrder> baseOrders = new ArrayList<>();
|
|
|
+ if (CollectionUtils.isEmpty(map)) {
|
|
|
+ return baseOrders;
|
|
|
+ }
|
|
|
+ Set<Long> keySet = map.keySet();
|
|
|
+ for (Long key : keySet) {
|
|
|
+ BaseOrder baseOrder = new BaseOrder();
|
|
|
+ List<BaseOrderDetail> baseOrderDetails = map.get(key);
|
|
|
+ baseOrder.setDetails(baseOrderDetails);
|
|
|
+ List<Document> documents = getDocuments(OrderSearchUtils.getIdField(tableName), Long.toString(key));
|
|
|
+ if (CollectionUtils.isEmpty(documents)) {
|
|
|
+ logger.error("明细表中外键" + key + "对应的主表索引不存在");
|
|
|
+ continue;
|
|
|
+ } else {
|
|
|
+ if (documents.size() > 1) {
|
|
|
+ logger.error("明细表中外键" + key + "对应的主表索引不唯一");
|
|
|
+ continue;
|
|
|
+ } else {
|
|
|
+ Document document = documents.get(0);
|
|
|
+ baseOrder.setId(key);
|
|
|
+ baseOrder.setCode(document.get(OrderSearchUtils.getCodeField(tableName)));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ baseOrders.add(baseOrder);
|
|
|
+ }
|
|
|
+ return baseOrders;
|
|
|
+ }
|
|
|
+
|
|
|
}
|