From 806b3314cd671d4abb89d1420ef486db4c9f8c7f Mon Sep 17 00:00:00 2001 From: wangcl Date: Fri, 31 Dec 2021 11:33:39 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=9F=E4=BA=A7=E4=BB=BB=E5=8A=A1=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../busi/controller/BusiTaskController.java | 126 +++++++++++++ .../com/ruoyi/busi/domain/BusiSubTask.java | 109 +++++++++++ .../java/com/ruoyi/busi/domain/BusiTask.java | 158 ++++++++++++++++ .../com/ruoyi/busi/mapper/BusiTaskMapper.java | 87 +++++++++ .../ruoyi/busi/service/IBusiTaskService.java | 61 ++++++ .../service/impl/BusiTaskServiceImpl.java | 134 +++++++++++++ .../resources/mapper/busi/BusiTaskMapper.xml | 129 +++++++++++++ .../templates/busi/prisonLine/add.html | 14 +- .../resources/templates/busi/task/add.html | 176 ++++++++++++++++++ .../resources/templates/busi/task/edit.html | 161 ++++++++++++++++ .../resources/templates/busi/task/task.html | 125 +++++++++++++ 11 files changed, 1272 insertions(+), 8 deletions(-) create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiTaskController.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiSubTask.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiTask.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/busi/mapper/BusiTaskMapper.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiTaskService.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiTaskServiceImpl.java create mode 100644 ruoyi-admin/src/main/resources/mapper/busi/BusiTaskMapper.xml create mode 100644 ruoyi-admin/src/main/resources/templates/busi/task/add.html create mode 100644 ruoyi-admin/src/main/resources/templates/busi/task/edit.html create mode 100644 ruoyi-admin/src/main/resources/templates/busi/task/task.html diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiTaskController.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiTaskController.java new file mode 100644 index 000000000..34ca7b6fd --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/controller/BusiTaskController.java @@ -0,0 +1,126 @@ +package com.ruoyi.busi.controller; + +import java.util.List; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.busi.domain.BusiTask; +import com.ruoyi.busi.service.IBusiTaskService; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 生产任务Controller + * + * @author WangCL + * @date 2021-12-30 + */ +@Controller +@RequestMapping("/busi/task") +public class BusiTaskController extends BaseController +{ + private String prefix = "busi/task"; + + @Autowired + private IBusiTaskService busiTaskService; + + @RequiresPermissions("busi:task:view") + @GetMapping() + public String task() + { + return prefix + "/task"; + } + + /** + * 查询生产任务列表 + */ + @RequiresPermissions("busi:task:list") + @PostMapping("/list") + @ResponseBody + public TableDataInfo list(BusiTask busiTask) + { + startPage(); + List list = busiTaskService.selectBusiTaskList(busiTask); + return getDataTable(list); + } + + /** + * 导出生产任务列表 + */ + @RequiresPermissions("busi:task:export") + @Log(title = "生产任务", businessType = BusinessType.EXPORT) + @PostMapping("/export") + @ResponseBody + public AjaxResult export(BusiTask busiTask) + { + List list = busiTaskService.selectBusiTaskList(busiTask); + ExcelUtil util = new ExcelUtil(BusiTask.class); + return util.exportExcel(list, "生产任务数据"); + } + + /** + * 新增生产任务 + */ + @GetMapping("/add") + public String add() + { + return prefix + "/add"; + } + + /** + * 新增保存生产任务 + */ + @RequiresPermissions("busi:task:add") + @Log(title = "生产任务", businessType = BusinessType.INSERT) + @PostMapping("/add") + @ResponseBody + public AjaxResult addSave(BusiTask busiTask) + { + return toAjax(busiTaskService.insertBusiTask(busiTask)); + } + + /** + * 修改生产任务 + */ + @GetMapping("/edit/{id}") + public String edit(@PathVariable("id") String id, ModelMap mmap) + { + BusiTask busiTask = busiTaskService.selectBusiTaskById(id); + mmap.put("busiTask", busiTask); + return prefix + "/edit"; + } + + /** + * 修改保存生产任务 + */ + @RequiresPermissions("busi:task:edit") + @Log(title = "生产任务", businessType = BusinessType.UPDATE) + @PostMapping("/edit") + @ResponseBody + public AjaxResult editSave(BusiTask busiTask) + { + return toAjax(busiTaskService.updateBusiTask(busiTask)); + } + + /** + * 删除生产任务 + */ + @RequiresPermissions("busi:task:remove") + @Log(title = "生产任务", businessType = BusinessType.DELETE) + @PostMapping( "/remove") + @ResponseBody + public AjaxResult remove(String ids) + { + return toAjax(busiTaskService.deleteBusiTaskByIds(ids)); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiSubTask.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiSubTask.java new file mode 100644 index 000000000..80dfeb97d --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiSubTask.java @@ -0,0 +1,109 @@ +package com.ruoyi.busi.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 产品子任务对象 busi_sub_task + * + * @author WangCL + * @date 2021-12-30 + */ +public class BusiSubTask extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID主键 */ + private Long id; + + /** 任务ID */ + @Excel(name = "任务ID") + private String taskId; + + /** 产品需求ID */ + @Excel(name = "产品需求ID") + private String productRequireId; + + /** 目标数量 */ + @Excel(name = "目标数量") + private Long targetAmount; + + /** 完成数量 */ + @Excel(name = "完成数量") + private Long completedAmount; + + /** 完成状态 */ + @Excel(name = "完成状态") + private String status; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + public void setTaskId(String taskId) + { + this.taskId = taskId; + } + + public String getTaskId() + { + return taskId; + } + public void setProductRequireId(String productRequireId) + { + this.productRequireId = productRequireId; + } + + public String getProductRequireId() + { + return productRequireId; + } + public void setTargetAmount(Long targetAmount) + { + this.targetAmount = targetAmount; + } + + public Long getTargetAmount() + { + return targetAmount; + } + public void setCompletedAmount(Long completedAmount) + { + this.completedAmount = completedAmount; + } + + public Long getCompletedAmount() + { + return completedAmount; + } + public void setStatus(String status) + { + this.status = status; + } + + public String getStatus() + { + return status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("taskId", getTaskId()) + .append("productRequireId", getProductRequireId()) + .append("targetAmount", getTargetAmount()) + .append("completedAmount", getCompletedAmount()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("status", getStatus()) + .toString(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiTask.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiTask.java new file mode 100644 index 000000000..39180b8ba --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/domain/BusiTask.java @@ -0,0 +1,158 @@ +package com.ruoyi.busi.domain; + +import java.util.List; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.ruoyi.common.annotation.Excel; +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 生产任务对象 busi_task + * + * @author WangCL + * @date 2021-12-30 + */ +public class BusiTask extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID主键 */ + private String id; + + /** 订单ID */ + private String orderId; + + /** 订单名称 */ + @Excel(name = "订单名称") + private String orderName; + + /** 产线ID */ + private String prisonLineId; + + /** 产线名称 */ + @Excel(name = "产线名称") + private String lineName; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String taskName; + + /** 目标值 */ + @Excel(name = "目标值") + private Long targetAmount; + + /** 完成值 */ + @Excel(name = "完成值") + private Long completedAmount; + + /** 任务状态 */ + @Excel(name = "任务状态") + private String status; + + /** 产品子任务信息 */ + private List busiSubTaskList; + + public String getOrderName() { + return orderName; + } + + public void setOrderName(String orderName) { + this.orderName = orderName; + } + + public String getLineName() { + return lineName; + } + + public void setLineName(String lineName) { + this.lineName = lineName; + } + + public Long getTargetAmount() { + return targetAmount; + } + + public void setTargetAmount(Long targetAmount) { + this.targetAmount = targetAmount; + } + + public Long getCompletedAmount() { + return completedAmount; + } + + public void setCompletedAmount(Long completedAmount) { + this.completedAmount = completedAmount; + } + + public void setId(String id) + { + this.id = id; + } + + public String getId() + { + return id; + } + public void setOrderId(String orderId) + { + this.orderId = orderId; + } + + public String getOrderId() + { + return orderId; + } + public void setPrisonLineId(String prisonLineId) + { + this.prisonLineId = prisonLineId; + } + + public String getPrisonLineId() + { + return prisonLineId; + } + public void setTaskName(String taskName) + { + this.taskName = taskName; + } + + public String getTaskName() + { + return taskName; + } + public void setStatus(String status) + { + this.status = status; + } + + public String getStatus() + { + return status; + } + + public List getBusiSubTaskList() + { + return busiSubTaskList; + } + + public void setBusiSubTaskList(List busiSubTaskList) + { + this.busiSubTaskList = busiSubTaskList; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("orderId", getOrderId()) + .append("prisonLineId", getPrisonLineId()) + .append("taskName", getTaskName()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("status", getStatus()) + .append("busiSubTaskList", getBusiSubTaskList()) + .toString(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/mapper/BusiTaskMapper.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/mapper/BusiTaskMapper.java new file mode 100644 index 000000000..03240d6d6 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/mapper/BusiTaskMapper.java @@ -0,0 +1,87 @@ +package com.ruoyi.busi.mapper; + +import java.util.List; +import com.ruoyi.busi.domain.BusiTask; +import com.ruoyi.busi.domain.BusiSubTask; + +/** + * 生产任务Mapper接口 + * + * @author WangCL + * @date 2021-12-30 + */ +public interface BusiTaskMapper +{ + /** + * 查询生产任务 + * + * @param id 生产任务主键 + * @return 生产任务 + */ + public BusiTask selectBusiTaskById(String id); + + /** + * 查询生产任务列表 + * + * @param busiTask 生产任务 + * @return 生产任务集合 + */ + public List selectBusiTaskList(BusiTask busiTask); + + /** + * 新增生产任务 + * + * @param busiTask 生产任务 + * @return 结果 + */ + public int insertBusiTask(BusiTask busiTask); + + /** + * 修改生产任务 + * + * @param busiTask 生产任务 + * @return 结果 + */ + public int updateBusiTask(BusiTask busiTask); + + /** + * 删除生产任务 + * + * @param id 生产任务主键 + * @return 结果 + */ + public int deleteBusiTaskById(String id); + + /** + * 批量删除生产任务 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteBusiTaskByIds(String[] ids); + + /** + * 批量删除产品子任务 + * + * @param ids 需要删除的数据主键集合 + * @return 结果 + */ + public int deleteBusiSubTaskByTaskIds(String[] ids); + + /** + * 批量新增产品子任务 + * + * @param busiSubTaskList 产品子任务列表 + * @return 结果 + */ + public int batchBusiSubTask(List busiSubTaskList); + + + /** + * 通过生产任务主键删除产品子任务信息 + * + * @param id 生产任务ID + * @return 结果 + */ + public int deleteBusiSubTaskByTaskId(String id); +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiTaskService.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiTaskService.java new file mode 100644 index 000000000..43101f33a --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/IBusiTaskService.java @@ -0,0 +1,61 @@ +package com.ruoyi.busi.service; + +import java.util.List; +import com.ruoyi.busi.domain.BusiTask; + +/** + * 生产任务Service接口 + * + * @author WangCL + * @date 2021-12-30 + */ +public interface IBusiTaskService +{ + /** + * 查询生产任务 + * + * @param id 生产任务主键 + * @return 生产任务 + */ + public BusiTask selectBusiTaskById(String id); + + /** + * 查询生产任务列表 + * + * @param busiTask 生产任务 + * @return 生产任务集合 + */ + public List selectBusiTaskList(BusiTask busiTask); + + /** + * 新增生产任务 + * + * @param busiTask 生产任务 + * @return 结果 + */ + public int insertBusiTask(BusiTask busiTask); + + /** + * 修改生产任务 + * + * @param busiTask 生产任务 + * @return 结果 + */ + public int updateBusiTask(BusiTask busiTask); + + /** + * 批量删除生产任务 + * + * @param ids 需要删除的生产任务主键集合 + * @return 结果 + */ + public int deleteBusiTaskByIds(String ids); + + /** + * 删除生产任务信息 + * + * @param id 生产任务主键 + * @return 结果 + */ + public int deleteBusiTaskById(String id); +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiTaskServiceImpl.java b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiTaskServiceImpl.java new file mode 100644 index 000000000..4f6470ba1 --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/busi/service/impl/BusiTaskServiceImpl.java @@ -0,0 +1,134 @@ +package com.ruoyi.busi.service.impl; + +import java.util.List; +import com.ruoyi.common.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.ArrayList; +import com.ruoyi.common.utils.StringUtils; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.busi.domain.BusiSubTask; +import com.ruoyi.busi.mapper.BusiTaskMapper; +import com.ruoyi.busi.domain.BusiTask; +import com.ruoyi.busi.service.IBusiTaskService; +import com.ruoyi.common.core.text.Convert; + +/** + * 生产任务Service业务层处理 + * + * @author WangCL + * @date 2021-12-30 + */ +@Service +public class BusiTaskServiceImpl implements IBusiTaskService +{ + @Autowired + private BusiTaskMapper busiTaskMapper; + + /** + * 查询生产任务 + * + * @param id 生产任务主键 + * @return 生产任务 + */ + @Override + public BusiTask selectBusiTaskById(String id) + { + return busiTaskMapper.selectBusiTaskById(id); + } + + /** + * 查询生产任务列表 + * + * @param busiTask 生产任务 + * @return 生产任务 + */ + @Override + public List selectBusiTaskList(BusiTask busiTask) + { + return busiTaskMapper.selectBusiTaskList(busiTask); + } + + /** + * 新增生产任务 + * + * @param busiTask 生产任务 + * @return 结果 + */ + @Transactional + @Override + public int insertBusiTask(BusiTask busiTask) + { + busiTask.setCreateTime(DateUtils.getNowDate()); + int rows = busiTaskMapper.insertBusiTask(busiTask); + insertBusiSubTask(busiTask); + return rows; + } + + /** + * 修改生产任务 + * + * @param busiTask 生产任务 + * @return 结果 + */ + @Transactional + @Override + public int updateBusiTask(BusiTask busiTask) + { + busiTask.setUpdateTime(DateUtils.getNowDate()); + busiTaskMapper.deleteBusiSubTaskByTaskId(busiTask.getId()); + insertBusiSubTask(busiTask); + return busiTaskMapper.updateBusiTask(busiTask); + } + + /** + * 批量删除生产任务 + * + * @param ids 需要删除的生产任务主键 + * @return 结果 + */ + @Transactional + @Override + public int deleteBusiTaskByIds(String ids) + { + busiTaskMapper.deleteBusiSubTaskByTaskIds(Convert.toStrArray(ids)); + return busiTaskMapper.deleteBusiTaskByIds(Convert.toStrArray(ids)); + } + + /** + * 删除生产任务信息 + * + * @param id 生产任务主键 + * @return 结果 + */ + @Override + public int deleteBusiTaskById(String id) + { + busiTaskMapper.deleteBusiSubTaskByTaskId(id); + return busiTaskMapper.deleteBusiTaskById(id); + } + + /** + * 新增产品子任务信息 + * + * @param busiTask 生产任务对象 + */ + public void insertBusiSubTask(BusiTask busiTask) + { + List busiSubTaskList = busiTask.getBusiSubTaskList(); + String id = busiTask.getId(); + if (StringUtils.isNotNull(busiSubTaskList)) + { + List list = new ArrayList(); + for (BusiSubTask busiSubTask : busiSubTaskList) + { + busiSubTask.setTaskId(id); + list.add(busiSubTask); + } + if (list.size() > 0) + { + busiTaskMapper.batchBusiSubTask(list); + } + } + } +} diff --git a/ruoyi-admin/src/main/resources/mapper/busi/BusiTaskMapper.xml b/ruoyi-admin/src/main/resources/mapper/busi/BusiTaskMapper.xml new file mode 100644 index 000000000..daeb02116 --- /dev/null +++ b/ruoyi-admin/src/main/resources/mapper/busi/BusiTaskMapper.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + insert into busi_task + + order_id, + prison_line_id, + task_name, + create_by, + create_time, + update_by, + update_time, + status, + + + #{orderId}, + #{prisonLineId}, + #{taskName}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{status}, + + + + + update busi_task + + order_id = #{orderId}, + prison_line_id = #{prisonLineId}, + task_name = #{taskName}, + create_by = #{createBy}, + create_time = #{createTime}, + update_by = #{updateBy}, + update_time = #{updateTime}, + status = #{status}, + + where id = #{id} + + + + delete from busi_task where id = #{id} + + + + delete from busi_task where id in + + #{id} + + + + + delete from busi_sub_task where task_id in + + #{taskId} + + + + + delete from busi_sub_task where task_id = #{taskId} + + + + insert into busi_sub_task( id, task_id, product_require_id, target_amount, completed_amount, create_by, create_time, status) values + + ( #{item.id}, #{item.taskId}, #{item.productRequireId}, #{item.targetAmount}, #{item.completedAmount}, #{item.createBy}, #{item.createTime}, #{item.status}) + + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/busi/prisonLine/add.html b/ruoyi-admin/src/main/resources/templates/busi/prisonLine/add.html index fe03fbae2..72be33d8e 100644 --- a/ruoyi-admin/src/main/resources/templates/busi/prisonLine/add.html +++ b/ruoyi-admin/src/main/resources/templates/busi/prisonLine/add.html @@ -73,18 +73,16 @@ title: '监区产线选择', width: "380", url: prefix + "/selectPrisonLineTree/" + $("#treeId").val()+ "?JCOnly=yes", - callBack: doSubmit + callBack: function(index, layero){ + var body = $.modal.getChildFrame(index); + $("#treeId").val(body.find('#treeId').val()); + $("#treeName").val(body.find('#treeName').val()); + $.modal.close(index); + } }; $.modal.openOptions(options); } - function doSubmit(index, layero){ - var body = $.modal.getChildFrame(index); - $("#treeId").val(body.find('#treeId').val()); - $("#treeName").val(body.find('#treeName').val()); - $.modal.close(index); - } - $("input[name='classify']").on('ifChecked', function(event){ var menuType = $(event.target).val(); if (menuType == "J") { // 监区 diff --git a/ruoyi-admin/src/main/resources/templates/busi/task/add.html b/ruoyi-admin/src/main/resources/templates/busi/task/add.html new file mode 100644 index 000000000..fde2ab4c5 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/busi/task/add.html @@ -0,0 +1,176 @@ + + + + + + +
+
+

生产任务信息

+
+ +
+ + + +
+
+
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+

产品子任务信息

+
+
+ + +
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/busi/task/edit.html b/ruoyi-admin/src/main/resources/templates/busi/task/edit.html new file mode 100644 index 000000000..70806f87f --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/busi/task/edit.html @@ -0,0 +1,161 @@ + + + + + + +
+
+

生产任务信息

+ +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+

产品子任务信息

+
+
+ + +
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/ruoyi-admin/src/main/resources/templates/busi/task/task.html b/ruoyi-admin/src/main/resources/templates/busi/task/task.html new file mode 100644 index 000000000..e0a7ad697 --- /dev/null +++ b/ruoyi-admin/src/main/resources/templates/busi/task/task.html @@ -0,0 +1,125 @@ + + + + + + +
+
+
+
+
+
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • +  搜索 +  重置 +
  • +
+
+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file