From 6b4cd85393474037ff2b2eb017d8534f29ce7e2a Mon Sep 17 00:00:00 2001 From: wangchao <1766193529@qq.com> Date: Wed, 20 Mar 2024 16:52:10 +0800 Subject: [PATCH] =?UTF-8?q?=E9=97=A8=E5=BA=97=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/enums/ErrorCodeConstants.java | 3 +- .../admin/carteen/CarteenController.java | 96 +++++++++++++++++++ .../admin/carteen/vo/CarteenPageReqVO.java | 46 +++++++++ .../admin/carteen/vo/CarteenRespVO.java | 56 +++++++++++ .../admin/carteen/vo/CarteenSaveReqVO.java | 46 +++++++++ .../dal/dataobject/carteen/CarteenDO.java | 63 ++++++++++++ .../dal/mysql/carteen/CarteenMapper.java | 34 +++++++ .../service/carteen/CarteenService.java | 55 +++++++++++ .../service/carteen/CarteenServiceImpl.java | 70 ++++++++++++++ 9 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/CarteenController.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenPageReqVO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenRespVO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenSaveReqVO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/carteen/CarteenDO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/carteen/CarteenMapper.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/carteen/CarteenService.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/carteen/CarteenServiceImpl.java diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index 2f41f67e..325f1ea3 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -170,5 +170,6 @@ public interface ErrorCodeConstants { // ========== 站内信发送 1-002-028-000 ========== ErrorCode NOTIFY_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_028_000, "模板参数({})缺失"); - + // ========== 门店管理 1-002-029-000 ========== + ErrorCode CARTEEN_NOT_EXISt = new ErrorCode(1_002_029_002, "当前门店不存在"); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/CarteenController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/CarteenController.java new file mode 100644 index 00000000..62560091 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/CarteenController.java @@ -0,0 +1,96 @@ +package cn.iocoder.yudao.module.system.controller.admin.carteen; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.system.controller.admin.carteen.vo.CarteenPageReqVO; +import cn.iocoder.yudao.module.system.controller.admin.carteen.vo.CarteenRespVO; +import cn.iocoder.yudao.module.system.controller.admin.carteen.vo.CarteenSaveReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.carteen.CarteenDO; +import cn.iocoder.yudao.module.system.service.carteen.CarteenService; +import org.springframework.web.bind.annotation.*; +import javax.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.security.access.prepost.PreAuthorize; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; + +import javax.validation.*; +import javax.servlet.http.*; +import java.io.IOException; +import java.util.List; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; + +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*; + + +@Tag(name = "管理后台 - 门店管理") +@RestController +@RequestMapping("/t/carteen") +@Validated +public class CarteenController { + + @Resource + private CarteenService carteenService; + + @PostMapping("/create") + @Operation(summary = "创建门店管理") + @PreAuthorize("@ss.hasPermission('t:carteen:create')") + public CommonResult createCarteen(@Valid @RequestBody CarteenSaveReqVO createReqVO) { + return success(carteenService.createCarteen(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新门店管理") + @PreAuthorize("@ss.hasPermission('t:carteen:update')") + public CommonResult updateCarteen(@Valid @RequestBody CarteenSaveReqVO updateReqVO) { + carteenService.updateCarteen(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除门店管理") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('t:carteen:delete')") + public CommonResult deleteCarteen(@RequestParam("id") Long id) { + carteenService.deleteCarteen(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得门店管理") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('t:carteen:query')") + public CommonResult getCarteen(@RequestParam("id") Long id) { + CarteenDO carteen = carteenService.getCarteen(id); + return success(BeanUtils.toBean(carteen, CarteenRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得门店管理分页") + @PreAuthorize("@ss.hasPermission('t:carteen:query')") + public CommonResult> getCarteenPage(@Valid CarteenPageReqVO pageReqVO) { + PageResult pageResult = carteenService.getCarteenPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, CarteenRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出门店管理 Excel") + @PreAuthorize("@ss.hasPermission('t:carteen:export')") + @OperateLog(type = EXPORT) + public void exportCarteenExcel(@Valid CarteenPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = carteenService.getCarteenPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "门店管理.xls", "数据", CarteenRespVO.class, + BeanUtils.toBean(list, CarteenRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenPageReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenPageReqVO.java new file mode 100644 index 00000000..19f9ea32 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenPageReqVO.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.system.controller.admin.carteen.vo; + +import lombok.*; +import java.util.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 门店管理分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class CarteenPageReqVO extends PageParam { + + @Schema(description = "门店名称", example = "芋艿") + private String storesName; + + @Schema(description = "门店状态,1已禁用0已启用", example = "1") + private Boolean status; + + @Schema(description = "多储位管理,1已禁用0已启用") + private Boolean multipleManage; + + @Schema(description = "是否启用公众号点餐,1不支持0支持") + private Boolean accountOrder; + + @Schema(description = "门店地址") + private String storeAddress; + + @Schema(description = "联系电话") + private String phone; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + + @Schema(description = "预留关联角色id", example = "27706") + private Integer roloId; + + @Schema(description = "编码") + private String serialNumber; + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenRespVO.java new file mode 100644 index 00000000..d4d0b11c --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenRespVO.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.system.controller.admin.carteen.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import java.util.*; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +import com.alibaba.excel.annotation.*; + +@Schema(description = "管理后台 - 门店管理 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CarteenRespVO { + + @Schema(description = "主键id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1824") + @ExcelProperty("主键id") + private Long id; + + @Schema(description = "门店名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @ExcelProperty("门店名称") + private String storesName; + + @Schema(description = "门店状态,1已禁用0已启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("门店状态,1已禁用0已启用") + private Boolean status; + + @Schema(description = "多储位管理,1已禁用0已启用", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("多储位管理,1已禁用0已启用") + private Boolean multipleManage; + + @Schema(description = "是否启用公众号点餐,1不支持0支持", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("是否启用公众号点餐,1不支持0支持") + private Boolean accountOrder; + + @Schema(description = "门店地址", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("门店地址") + private String storeAddress; + + @Schema(description = "联系电话", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("联系电话") + private String phone; + + @Schema(description = "创建时间") + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "预留关联角色id", example = "27706") + @ExcelProperty("预留关联角色id") + private Integer roloId; + + @Schema(description = "编码", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("编码") + private String serialNumber; + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenSaveReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenSaveReqVO.java new file mode 100644 index 00000000..abdebe45 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/carteen/vo/CarteenSaveReqVO.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.system.controller.admin.carteen.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import javax.validation.constraints.*; + +@Schema(description = "管理后台 - 门店管理新增/修改 Request VO") +@Data +public class CarteenSaveReqVO { + + @Schema(description = "主键id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1824") + private Long id; + + @Schema(description = "门店名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @NotEmpty(message = "门店名称不能为空") + private String storesName; + + @Schema(description = "门店状态,1已禁用0已启用", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "门店状态,1已禁用0已启用不能为空") + private Boolean status; + + @Schema(description = "多储位管理,1已禁用0已启用", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "多储位管理,1已禁用0已启用不能为空") + private Boolean multipleManage; + + @Schema(description = "是否启用公众号点餐,1不支持0支持", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "是否启用公众号点餐,1不支持0支持不能为空") + private Boolean accountOrder; + + @Schema(description = "门店地址", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "门店地址不能为空") + private String storeAddress; + + @Schema(description = "联系电话", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "联系电话不能为空") + private String phone; + + @Schema(description = "预留关联角色id", example = "27706") + private Integer roloId; + + @Schema(description = "编码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "编码不能为空") + private String serialNumber; + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/carteen/CarteenDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/carteen/CarteenDO.java new file mode 100644 index 00000000..0b551080 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/carteen/CarteenDO.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.system.dal.dataobject.carteen; + +import lombok.*; +import java.util.*; +import java.time.LocalDateTime; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.*; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; + +/** + * 门店管理 DO + * + * @author 开发账号 + */ +@TableName("t_carteen") +@KeySequence("t_carteen_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CarteenDO extends BaseDO { + + /** + * 主键id + */ + @TableId + private Long id; + /** + * 门店名称 + */ + private String storesName; + /** + * 门店状态,1已禁用0已启用 + */ + private Boolean status; + /** + * 多储位管理,1已禁用0已启用 + */ + private Boolean multipleManage; + /** + * 是否启用公众号点餐,1不支持0支持 + */ + private Boolean accountOrder; + /** + * 门店地址 + */ + private String storeAddress; + /** + * 联系电话 + */ + private String phone; + /** + * 预留关联角色id + */ + private Integer roloId; + /** + * 编码 + */ + private String serialNumber; + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/carteen/CarteenMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/carteen/CarteenMapper.java new file mode 100644 index 00000000..2f8dc3ce --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/carteen/CarteenMapper.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.system.dal.mysql.carteen; + +import java.util.*; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.module.system.controller.admin.carteen.vo.CarteenPageReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.carteen.CarteenDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 门店管理 Mapper + * + * @author 开发账号 + */ +@Mapper +public interface CarteenMapper extends BaseMapperX { + + default PageResult selectPage(CarteenPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(CarteenDO::getStoresName, reqVO.getStoresName()) + .eqIfPresent(CarteenDO::getStatus, reqVO.getStatus()) + .eqIfPresent(CarteenDO::getMultipleManage, reqVO.getMultipleManage()) + .eqIfPresent(CarteenDO::getAccountOrder, reqVO.getAccountOrder()) + .eqIfPresent(CarteenDO::getStoreAddress, reqVO.getStoreAddress()) + .eqIfPresent(CarteenDO::getPhone, reqVO.getPhone()) + .betweenIfPresent(CarteenDO::getCreateTime, reqVO.getCreateTime()) + .eqIfPresent(CarteenDO::getRoloId, reqVO.getRoloId()) + .eqIfPresent(CarteenDO::getSerialNumber, reqVO.getSerialNumber()) + .orderByDesc(CarteenDO::getId)); + } + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/carteen/CarteenService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/carteen/CarteenService.java new file mode 100644 index 00000000..e501644c --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/carteen/CarteenService.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.system.service.carteen; + +import javax.validation.*; + +import cn.iocoder.yudao.module.system.controller.admin.carteen.vo.CarteenPageReqVO; +import cn.iocoder.yudao.module.system.controller.admin.carteen.vo.CarteenSaveReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.carteen.CarteenDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; + +/** + * 门店管理 Service 接口 + * + * @author 开发账号 + */ +public interface CarteenService { + + /** + * 创建门店管理 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createCarteen(@Valid CarteenSaveReqVO createReqVO); + + /** + * 更新门店管理 + * + * @param updateReqVO 更新信息 + */ + void updateCarteen(@Valid CarteenSaveReqVO updateReqVO); + + /** + * 删除门店管理 + * + * @param id 编号 + */ + void deleteCarteen(Long id); + + /** + * 获得门店管理 + * + * @param id 编号 + * @return 门店管理 + */ + CarteenDO getCarteen(Long id); + + /** + * 获得门店管理分页 + * + * @param pageReqVO 分页查询 + * @return 门店管理分页 + */ + PageResult getCarteenPage(CarteenPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/carteen/CarteenServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/carteen/CarteenServiceImpl.java new file mode 100644 index 00000000..ca827360 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/carteen/CarteenServiceImpl.java @@ -0,0 +1,70 @@ +package cn.iocoder.yudao.module.system.service.carteen; + +import cn.iocoder.yudao.module.system.controller.admin.carteen.vo.CarteenPageReqVO; +import cn.iocoder.yudao.module.system.controller.admin.carteen.vo.CarteenSaveReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.carteen.CarteenDO; +import cn.iocoder.yudao.module.system.dal.mysql.carteen.CarteenMapper; +import cn.iocoder.yudao.module.system.enums.ErrorCodeConstants; +import org.springframework.stereotype.Service; +import javax.annotation.Resource; +import org.springframework.validation.annotation.Validated; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; + +/** + * 门店管理 Service 实现类 + * + * @author 开发账号 + */ +@Service +@Validated +public class CarteenServiceImpl implements CarteenService { + + @Resource + private CarteenMapper carteenMapper; + + @Override + public Long createCarteen(CarteenSaveReqVO createReqVO) { + // 插入 + CarteenDO carteen = BeanUtils.toBean(createReqVO, CarteenDO.class); + carteenMapper.insert(carteen); + // 返回 + return carteen.getId(); + } + + @Override + public void updateCarteen(CarteenSaveReqVO updateReqVO) { + // 校验存在 + validateCarteenExists(updateReqVO.getId()); + // 更新 + CarteenDO updateObj = BeanUtils.toBean(updateReqVO, CarteenDO.class); + carteenMapper.updateById(updateObj); + } + + @Override + public void deleteCarteen(Long id) { + // 校验存在 + validateCarteenExists(id); + // 删除 + carteenMapper.deleteById(id); + } + + private void validateCarteenExists(Long id) { + if (carteenMapper.selectById(id) == null) { + throw exception(ErrorCodeConstants.CARTEEN_NOT_EXISt); + } + } + + @Override + public CarteenDO getCarteen(Long id) { + return carteenMapper.selectById(id); + } + + @Override + public PageResult getCarteenPage(CarteenPageReqVO pageReqVO) { + return carteenMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file