Browse Source

例行工作

master
zxc 3 years ago
parent
commit
b1c790affa
  1. 18
      epmet-module/open-data-worker/open-data-worker-client/src/main/java/com/epmet/opendata/dto/form/HistoryDataSyncFormDTO.java
  2. 3
      epmet-module/open-data-worker/open-data-worker-client/src/main/java/com/epmet/opendata/dto/form/UpsertPatrolRecordForm.java
  3. 25
      epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/controller/BaseGridDailyworkController.java
  4. 11
      epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/dao/BaseGridDailyworkDao.java
  5. 4
      epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/dao/GridstaffWorkInfoPingyinDao.java
  6. 19
      epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/entity/BaseGridDailyworkEntity.java
  7. 9
      epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/entity/GridstaffWorkInfoPingyinEntity.java
  8. 3
      epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/mq/listener/OpenDataPatrolChangeEventListener.java
  9. 17
      epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/service/BaseGridDailyworkService.java
  10. 4
      epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/service/GridstaffWorkInfoPingyinService.java
  11. 155
      epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/service/impl/BaseGridDailyworkServiceImpl.java
  12. 6
      epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/service/impl/GridstaffWorkInfoPingyinServiceImpl.java
  13. 43
      epmet-module/open-data-worker/open-data-worker-server/src/main/resources/mapper/BaseGridDailyworkDao.xml
  14. 3
      epmet-module/open-data-worker/open-data-worker-server/src/main/resources/mapper/GridstaffWorkInfoPingyinDao.xml

18
epmet-module/open-data-worker/open-data-worker-client/src/main/java/com/epmet/opendata/dto/form/HistoryDataSyncFormDTO.java

@ -0,0 +1,18 @@
package com.epmet.opendata.dto.form;
import lombok.Data;
import java.io.Serializable;
/**
* @Author zxc
* @DateTime 2022/7/22 13:23
* @DESC
*/
@Data
public class HistoryDataSyncFormDTO implements Serializable {
private static final long serialVersionUID = -58891187838165930L;
private String customerId;
}

3
epmet-module/open-data-worker/open-data-worker-client/src/main/java/com/epmet/opendata/dto/form/UpsertPatrolRecordForm.java

@ -22,8 +22,9 @@ public class UpsertPatrolRecordForm extends PageFormDTO {
/**
* 记录id
* 2022-07-22 注释校验的原因是兼容初始化当不传id时查询全部
*/
@NotEmpty(message = "id不能为空",groups = AddGroup.class)
// @NotEmpty(message = "id不能为空",groups = AddGroup.class)
private String id;
/**

25
epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/controller/BaseGridDailyworkController.java

@ -21,6 +21,7 @@ package com.epmet.opendata.controller;
import com.epmet.commons.tools.utils.Result;
import com.epmet.commons.tools.validator.ValidatorUtils;
import com.epmet.commons.tools.validator.group.DefaultGroup;
import com.epmet.opendata.dto.form.HistoryDataSyncFormDTO;
import com.epmet.opendata.dto.form.UpsertPatrolRecordForm;
import com.epmet.opendata.service.BaseGridDailyworkService;
import org.springframework.beans.factory.annotation.Autowired;
@ -54,5 +55,29 @@ public class BaseGridDailyworkController {
return new Result();
}
/**
* Desc: 2022-07-22 新表 例行工作同步
* @param formDTO
* @author zxc
* @date 2022/7/22 11:16
*/
@PostMapping("syncV2")
public Result getStaffBaseInfoV2(@RequestBody(required = false) UpsertPatrolRecordForm formDTO) {
ValidatorUtils.validateEntity(formDTO, DefaultGroup.class);
baseGridDailyworkService.insertBaseGridWorkRecordV2(formDTO);
return new Result();
}
/**
* Desc: 历史数据处理
* @param formDTO
* @author zxc
* @date 2022/7/22 13:24
*/
@PostMapping("historyDataSync")
public Result historyDataSync(@RequestBody HistoryDataSyncFormDTO formDTO){
baseGridDailyworkService.historyDataSync(formDTO);
return new Result();
}
}

11
epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/dao/BaseGridDailyworkDao.java

@ -21,6 +21,9 @@ package com.epmet.opendata.dao;
import com.epmet.commons.mybatis.dao.BaseDao;
import com.epmet.opendata.entity.BaseGridDailyworkEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 巡查例行工作
@ -31,4 +34,12 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BaseGridDailyworkDao extends BaseDao<BaseGridDailyworkEntity> {
/**
* Desc: 每次查询1000
* @param customerId
* @author zxc
* @date 2022/7/22 13:26
*/
List<BaseGridDailyworkEntity> getBaseGridDailyWork(@Param("customerId")String customerId,@Param("size")Integer size);
}

4
epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/dao/GridstaffWorkInfoPingyinDao.java

@ -12,5 +12,7 @@ import org.apache.ibatis.annotations.Mapper;
*/
@Mapper
public interface GridstaffWorkInfoPingyinDao extends BaseDao<GridstaffWorkInfoPingyinEntity> {
Integer delAllGridStaffWorkInfoPY();
}

19
epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/entity/BaseGridDailyworkEntity.java

@ -17,6 +17,7 @@
package com.epmet.opendata.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@ -152,4 +153,22 @@ public class BaseGridDailyworkEntity implements Serializable {
*/
private Date updateTime;
@TableField(exist = false)
private String qxBm;
/**
* 上报区县名称
*/
@TableField(exist = false)
private String qxMc;
@TableField(exist = false)
private String workType;
@TableField(exist = false)
private String happenTimeString;
@TableField(exist = false)
private Date recoredInsertTime;
}

9
epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/entity/GridstaffWorkInfoPingyinEntity.java

@ -1,8 +1,7 @@
package com.epmet.opendata.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.epmet.commons.mybatis.entity.BaseEpmetEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -46,6 +45,9 @@ public class GridstaffWorkInfoPingyinEntity {
*/
private String gridName;
@TableField(exist = false)
private String gridId;
/**
* 例行工作类型,应符合10.27中的例行工作类型
*/
@ -86,4 +88,7 @@ public class GridstaffWorkInfoPingyinEntity {
*/
private Date recoredInsertTime;
@TableField(exist = false)
private String happenTimeString;
}

3
epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/mq/listener/OpenDataPatrolChangeEventListener.java

@ -98,7 +98,8 @@ public class OpenDataPatrolChangeEventListener implements MessageListenerConcurr
aBoolean = true;
break;
case SystemMessageType.PATROL_ROUTINE_WORK_ADD:
aBoolean = SpringContextUtils.getBean(BaseGridDailyworkService.class).insertBaseGridWorkRecord(patrolRecordForm);
// aBoolean = SpringContextUtils.getBean(BaseGridDailyworkService.class).insertBaseGridWorkRecord(patrolRecordForm);
aBoolean = SpringContextUtils.getBean(BaseGridDailyworkService.class).insertBaseGridWorkRecordV2(patrolRecordForm);
break;
default:
log.error("错误的消息类型:{}", tags);

17
epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/service/BaseGridDailyworkService.java

@ -18,6 +18,7 @@
package com.epmet.opendata.service;
import com.epmet.commons.mybatis.service.BaseService;
import com.epmet.opendata.dto.form.HistoryDataSyncFormDTO;
import com.epmet.opendata.dto.form.UpsertPatrolRecordForm;
import com.epmet.opendata.entity.BaseGridDailyworkEntity;
@ -36,6 +37,14 @@ public interface BaseGridDailyworkService extends BaseService<BaseGridDailyworkE
*/
Boolean insertBaseGridWorkRecord(UpsertPatrolRecordForm patrolRecordForm);
/**
* Desc: 2022-07-22 新表 例行工作同步
* @param formDTO
* @author zxc
* @date 2022/7/22 11:16
*/
Boolean insertBaseGridWorkRecordV2(UpsertPatrolRecordForm formDTO);
/**
* desc重新加载数据
* @param customerId
@ -43,4 +52,12 @@ public interface BaseGridDailyworkService extends BaseService<BaseGridDailyworkE
*/
Boolean reloadPatrolData(String customerId);
/**
* Desc: 历史数据处理
* @param formDTO
* @author zxc
* @date 2022/7/22 13:24
*/
void historyDataSync(HistoryDataSyncFormDTO formDTO);
}

4
epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/service/GridstaffWorkInfoPingyinService.java

@ -3,8 +3,6 @@ package com.epmet.opendata.service;
import com.epmet.commons.mybatis.service.BaseService;
import com.epmet.opendata.entity.GridstaffWorkInfoPingyinEntity;
import java.util.Map;
/**
* 平阴区网格员例行工作信息
*
@ -13,4 +11,6 @@ import java.util.Map;
*/
public interface GridstaffWorkInfoPingyinService extends BaseService<GridstaffWorkInfoPingyinEntity> {
Integer delAllGridStaffWorkInfoPY();
}

155
epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/service/impl/BaseGridDailyworkServiceImpl.java

@ -1,20 +1,3 @@
/**
* Copyright 2018 人人开源 https://www.renren.io
* <p>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.epmet.opendata.service.impl;
import com.alibaba.fastjson.JSON;
@ -23,6 +6,10 @@ import com.epmet.commons.tools.constant.NumConstant;
import com.epmet.commons.tools.constant.StrConstant;
import com.epmet.commons.tools.exception.EpmetErrorCode;
import com.epmet.commons.tools.exception.EpmetException;
import com.epmet.commons.tools.redis.common.CustomerOrgRedis;
import com.epmet.commons.tools.redis.common.bean.GridInfoCache;
import com.epmet.commons.tools.utils.ConvertUtils;
import com.epmet.commons.tools.utils.DateUtils;
import com.epmet.commons.tools.utils.Result;
import com.epmet.commons.tools.validator.ValidatorUtils;
import com.epmet.commons.tools.validator.group.AddGroup;
@ -32,15 +19,18 @@ import com.epmet.feign.EpmetUserOpenFeignClient;
import com.epmet.opendata.dao.BaseGridDailyworkDao;
import com.epmet.opendata.dto.ExDeptDTO;
import com.epmet.opendata.dto.ExUserDTO;
import com.epmet.opendata.dto.form.HistoryDataSyncFormDTO;
import com.epmet.opendata.dto.form.UpsertPatrolRecordForm;
import com.epmet.opendata.entity.BaseGridDailyworkEntity;
import com.epmet.opendata.service.BaseGridDailyworkService;
import com.epmet.opendata.service.ExDeptService;
import com.epmet.opendata.service.ExUserService;
import com.epmet.opendata.entity.GridstaffWorkInfoPingyinEntity;
import com.epmet.opendata.service.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
@ -64,6 +54,12 @@ public class BaseGridDailyworkServiceImpl extends BaseServiceImpl<BaseGridDailyw
private ExDeptService exDeptService;
@Autowired
private ExUserService exUserService;
@Autowired
private GridstaffWorkInfoPingyinService pingYinService;
@Autowired
private GridstaffInfoPingyinService gridstaffInfoPingyinService;
@Autowired
private GridInfoPingyinService gridInfoPingyinService;
@Override
public Boolean insertBaseGridWorkRecord(UpsertPatrolRecordForm patrolRecordForm) {
@ -117,6 +113,7 @@ public class BaseGridDailyworkServiceImpl extends BaseServiceImpl<BaseGridDailyw
return null;
}
private Boolean insertRecordBatch(List<PatrolRoutineWorkResult> list, Map<String, ExDeptDTO> deptMap, Map<String, ExUserDTO> userMap){
List<BaseGridDailyworkEntity> insertList = new ArrayList<>();
list.forEach(o-> insertList.add(buildEntity(o, deptMap, userMap)));
@ -130,7 +127,6 @@ public class BaseGridDailyworkServiceImpl extends BaseServiceImpl<BaseGridDailyw
return true;
}
private BaseGridDailyworkEntity buildEntity(PatrolRoutineWorkResult record, Map<String, ExDeptDTO> deptMap, Map<String, ExUserDTO> userMap) {
BaseGridDailyworkEntity entity = new BaseGridDailyworkEntity();
entity.setCustomerId(record.getCustomerId());
@ -178,9 +174,122 @@ public class BaseGridDailyworkServiceImpl extends BaseServiceImpl<BaseGridDailyw
private PatrolQueryFormDTO buildParam(UpsertPatrolRecordForm patrolRecordForm) {
PatrolQueryFormDTO midPatrolFormDTO = new PatrolQueryFormDTO();
midPatrolFormDTO.setCustomerId(patrolRecordForm.getCustomerId());
midPatrolFormDTO.setId(patrolRecordForm.getId());
midPatrolFormDTO.setId(StringUtils.isBlank(patrolRecordForm.getId()) ? "" : patrolRecordForm.getId());
midPatrolFormDTO.setPageNo(patrolRecordForm.getPageNo());
midPatrolFormDTO.setPageSize(patrolRecordForm.getPageSize());
midPatrolFormDTO.setPageSize(NumConstant.ONE_THOUSAND);
return midPatrolFormDTO;
}
private GridstaffWorkInfoPingyinEntity buildNewEntity(PatrolRoutineWorkResult record) {
GridstaffWorkInfoPingyinEntity entity = ConvertUtils.sourceToTarget(record, GridstaffWorkInfoPingyinEntity.class);
entity.setQxBm("370124");
entity.setQxMc("平阴县");
entity.setHappenTime(DateUtils.parseDate(record.getHappenTime().concat(" 00:00:00"),DateUtils.DATE_PATTERN));
entity.setWorkResult(NumConstant.ONE == record.getIsNormal()? "Y":"N");
entity.setWorkType(record.getWorkTypeSecondCode());
entity.setRecoredInsertTime(record.getCreatedTime());
entity.setUpdateTime(record.getUpdatedTime());
entity.setId(record.getId()+"_PY");
entity.setIsDel(NumConstant.ZERO_STR);
return entity;
}
@Override
public Boolean insertBaseGridWorkRecordV2(UpsertPatrolRecordForm patrolRecordForm) {
log.info("insertBaseGridWorkRecordV2 param:{}", JSON.toJSONString(patrolRecordForm));
ValidatorUtils.validateEntity(patrolRecordForm, AddGroup.class);
// 构建传参
PatrolQueryFormDTO midPatrolFormDTO = buildParam(patrolRecordForm);
if (StringUtils.isBlank(patrolRecordForm.getId())){
Integer row;
do {
row = pingYinService.delAllGridStaffWorkInfoPY();
}while (row == NumConstant.ONE_THOUSAND);
}
List<PatrolRoutineWorkResult> data;
do {
Result<List<PatrolRoutineWorkResult>> record = epmetUserOpenFeignClient.getPatrolRoutineWorkListV2(midPatrolFormDTO);
if (record == null || !record.success()) {
log.error("获取例行工作记录V2失败,param:{}", JSON.toJSONString(midPatrolFormDTO));
throw new EpmetException(EpmetErrorCode.SERVER_ERROR.getCode());
}
data = record.getData();
if (CollectionUtils.isEmpty(data)) {
//数据已被删除了
//暂时设置error 用于排错
log.error("获取例行工作记录返回为空,param:{}", JSON.toJSONString(midPatrolFormDTO));
int effectRow = baseDao.deleteById(patrolRecordForm.getId());
log.warn("del effectRow:{}", effectRow);
throw new EpmetException(EpmetErrorCode.SERVER_ERROR.getCode());
}
data.forEach(d -> {
GridInfoCache gridInfo = CustomerOrgRedis.getGridInfo(d.getGridId());
if (null == gridInfo){
throw new EpmetException("未查询到网格信息:"+ d.getGridId());
}
d.setGridCode(gridInfo.getCode());
d.setGridName(gridInfo.getGridName());
});
insertNewRecordBatch(data);
}while (data.size() == NumConstant.ONE_THOUSAND);
return true;
}
/**
* Desc: 历史数据处理
* @param formDTO
* @author zxc
* @date 2022/7/22 13:24
*/
@Override
public void historyDataSync(HistoryDataSyncFormDTO formDTO) {
Integer pageSize = NumConstant.ONE_THOUSAND;
Integer pageNo = NumConstant.ONE;
List<BaseGridDailyworkEntity> baseGridDailyWork;
do {
baseGridDailyWork = baseDao.getBaseGridDailyWork(formDTO.getCustomerId(),(pageNo - NumConstant.ONE) * pageSize);
List<GridstaffWorkInfoPingyinEntity> newEntities = ConvertUtils.sourceToTarget(baseGridDailyWork, GridstaffWorkInfoPingyinEntity.class);
newEntities.forEach(n -> {
GridInfoCache gridInfo = CustomerOrgRedis.getGridInfo(n.getGridId());
if (null == gridInfo){
throw new EpmetException("未查询到网格信息:"+ n.getGridId());
}
n.setGridCode(gridInfo.getCode());
n.setHappenTime(DateUtils.parseDate(n.getHappenTimeString(),DateUtils.DATE_PATTERN));
n.setIsDel(NumConstant.ZERO_STR);
});
insertGridStaffWorkInfoPY(newEntities);
pageNo++;
}while (baseGridDailyWork.size() == NumConstant.ONE_THOUSAND);
}
/**
* Desc: 数据存入
* @param newEntities
* @author zxc
* @date 2022/7/22 14:17
*/
@Transactional(rollbackFor = Exception.class)
public void insertGridStaffWorkInfoPY(List<GridstaffWorkInfoPingyinEntity> newEntities){
if (!CollectionUtils.isEmpty(newEntities)){
List<List<GridstaffWorkInfoPingyinEntity>> partition = ListUtils.partition(newEntities, NumConstant.ONE_HUNDRED);
partition.forEach(p -> {
pingYinService.insertBatch(p);
});
}
}
private Boolean insertNewRecordBatch(List<PatrolRoutineWorkResult> list){
List<GridstaffWorkInfoPingyinEntity> insertList = new ArrayList<>();
list.forEach(o-> insertList.add(buildNewEntity(o)));
//insert
if (CollectionUtils.isEmpty(insertList)){
log.error("新构建要插入的数据为空,param:{}", JSON.toJSONString(list));
return false;
}
insertGridStaffWorkInfoPY(insertList);
// pingYinService.saveOrUpdateBatch(insertList, NumConstant.ONE_HUNDRED);
return true;
}
}

6
epmet-module/open-data-worker/open-data-worker-server/src/main/java/com/epmet/opendata/service/impl/GridstaffWorkInfoPingyinServiceImpl.java

@ -14,4 +14,10 @@ import org.springframework.stereotype.Service;
*/
@Service
public class GridstaffWorkInfoPingyinServiceImpl extends BaseServiceImpl<GridstaffWorkInfoPingyinDao, GridstaffWorkInfoPingyinEntity> implements GridstaffWorkInfoPingyinService {
@Override
public Integer delAllGridStaffWorkInfoPY() {
return baseDao.delAllGridStaffWorkInfoPY();
}
}

43
epmet-module/open-data-worker/open-data-worker-server/src/main/resources/mapper/BaseGridDailyworkDao.xml

@ -3,33 +3,22 @@
<mapper namespace="com.epmet.opendata.dao.BaseGridDailyworkDao">
<resultMap type="com.epmet.opendata.entity.BaseGridDailyworkEntity" id="patrolRoutineWorkMap">
<result property="id" column="ID"/>
<result property="customerId" column="CUSTOMER_ID"/>
<result property="gridId" column="GRID_ID"/>
<result property="gridName" column="GRID_NAME"/>
<result property="firstWorkType" column="FIRST_WORK_TYPE"/>
<result property="secondWorkType" column="SECOND_WORK_TYPE"/>
<result property="eventName" column="EVENT_NAME"/>
<result property="happenTime" column="HAPPEN_TIME"/>
<result property="workResult" column="WORK_RESULT"/>
<result property="workContent" column="WORK_CONTENT"/>
<result property="keyAreaType" column="KEY_AREA_TYPE"/>
<result property="regionScale" column="REGION_SCALE"/>
<result property="isKeyareaState" column="IS_KEYAREA_STATE"/>
<result property="isKeypeopleLocate" column="IS_KEY_PEOPLE_LOCATE"/>
<result property="keypeopleStatus" column="KEY_PEOPLE_STATUS"/>
<result property="happenPlace" column="HAPPEN_PLACE"/>
<result property="lng" column="LNG"/>
<result property="lat" column="LAT"/>
<result property="flag" column="FLAG"/>
<result property="delFlag" column="DEL_FLAG"/>
<result property="revision" column="REVISION"/>
<result property="createBy" column="CREATE_BY"/>
<result property="createTime" column="CREATE_TIME"/>
<result property="updateBy" column="UPDATE_BY"/>
<result property="updateTime" column="UPDATE_TIME"/>
</resultMap>
<select id="getBaseGridDailyWork" resultType="com.epmet.opendata.entity.BaseGridDailyworkEntity">
SELECT '370124' AS qxBm,
'平阴县' AS qxMc,
w.SECOND_WORK_TYPE AS workType,
w.GRID_ID,
w.GRID_NAME,
concat(w.HAPPEN_TIME,' 00:00:00') as happenTimeString,
w.WORK_RESULT,
w.WORK_CONTENT,
w.update_time,
w.create_time as recoredInsertTime
FROM base_grid_dailywork w
WHERE w.CUSTOMER_ID = #{customerId}
ORDER BY w.CREATE_TIME ASC
LIMIT #{size},1000
</select>
</mapper>

3
epmet-module/open-data-worker/open-data-worker-server/src/main/resources/mapper/GridstaffWorkInfoPingyinDao.xml

@ -18,6 +18,9 @@
<result property="isDel" column="is_del"/>
<result property="recoredInsertTime" column="recored_insert_time"/>
</resultMap>
<delete id="delAllGridStaffWorkInfoPY">
delete from gridstaff_work_info_pingyin LIMIT 1000
</delete>
</mapper>
Loading…
Cancel
Save