Przeglądaj źródła

프로젝트 첫 업로드

Hoon 4 lat temu
commit
a920c6ca1d
100 zmienionych plików z 22207 dodań i 0 usunięć
  1. 23 0
      .gitignore
  2. 19 0
      .gitlab-ci.yml
  3. 38 0
      .vscode/launch.json
  4. 11 0
      .vscode/settings.json
  5. 42 0
      .vscode/tasks.json
  6. 162 0
      Controllers/AccidentCodeTypeController.cs
  7. 261 0
      Controllers/AccidentController.cs
  8. 131 0
      Controllers/AlarmLogController.cs
  9. 126 0
      Controllers/AlarmMonitoringPointHistoryController.cs
  10. 170 0
      Controllers/AlarmPointController.cs
  11. 212 0
      Controllers/AnnouncementController.cs
  12. 144 0
      Controllers/App/AccidentControllers.cs
  13. 119 0
      Controllers/App/AlarmLogController.cs
  14. 119 0
      Controllers/App/AlarmMonitoringPointHistoryController.cs
  15. 162 0
      Controllers/App/AnnouncementController.cs
  16. 134 0
      Controllers/App/ComplaintsController.cs
  17. 173 0
      Controllers/App/ContractManagementController.cs
  18. 481 0
      Controllers/App/DailyReportController.cs
  19. 287 0
      Controllers/App/EquipmentController.cs
  20. 245 0
      Controllers/App/EquipmentHistoryController.cs
  21. 183 0
      Controllers/App/FacilityController.cs
  22. 165 0
      Controllers/App/FacilityTimeController.cs
  23. 153 0
      Controllers/App/FacilityWorkHistoryController.cs
  24. 92 0
      Controllers/App/FloorController.cs
  25. 260 0
      Controllers/App/FormulaControllers.cs
  26. 160 0
      Controllers/App/GuidelineController.cs
  27. 27 0
      Controllers/App/HomeController.cs
  28. 126 0
      Controllers/App/ManualController.cs
  29. 131 0
      Controllers/App/ManualHistoryController.cs
  30. 309 0
      Controllers/App/ManualMeterController.cs
  31. 255 0
      Controllers/App/MaterialController.cs
  32. 197 0
      Controllers/App/MaterialDailyStockController.cs
  33. 196 0
      Controllers/App/MaterialMonthlyStockController.cs
  34. 173 0
      Controllers/App/MobileShortcutController.cs
  35. 756 0
      Controllers/App/MonthlyReportController.cs
  36. 158 0
      Controllers/App/PartnerController.cs
  37. 149 0
      Controllers/App/ReportHistoryController.cs
  38. 126 0
      Controllers/App/SiteController.cs
  39. 155 0
      Controllers/App/TenantController.cs
  40. 255 0
      Controllers/App/UserController.cs
  41. 282 0
      Controllers/App/WorkHistoryController.cs
  42. 209 0
      Controllers/App/WorkOrderController.cs
  43. 355 0
      Controllers/App/WorkRequestController.cs
  44. 479 0
      Controllers/App/WorkResultController.cs
  45. 390 0
      Controllers/App/WorkScheduleController.cs
  46. 178 0
      Controllers/BuildingController.cs
  47. 191 0
      Controllers/BusinessFieldController.cs
  48. 193 0
      Controllers/CertificateStatusController.cs
  49. 224 0
      Controllers/CompanyController.cs
  50. 194 0
      Controllers/CompanyTypeController.cs
  51. 162 0
      Controllers/ComplaintCodeTypeController.cs
  52. 245 0
      Controllers/ComplaintController.cs
  53. 295 0
      Controllers/ConstructionPartnerController.cs
  54. 201 0
      Controllers/ContractClassController.cs
  55. 311 0
      Controllers/ContractManagementController.cs
  56. 201 0
      Controllers/ContractMethodController.cs
  57. 201 0
      Controllers/ContractTypeController.cs
  58. 77 0
      Controllers/ControlController.cs
  59. 537 0
      Controllers/DailyReportController.cs
  60. 1653 0
      Controllers/DashboardController.cs
  61. 214 0
      Controllers/DepartmentController.cs
  62. 162 0
      Controllers/DrawingCodeGroupController.cs
  63. 162 0
      Controllers/DrawingCodeTypeController.cs
  64. 199 0
      Controllers/DrawingController.cs
  65. 175 0
      Controllers/DrawingHistoryController.cs
  66. 400 0
      Controllers/EnergyDayAnalysisController.cs
  67. 430 0
      Controllers/EnergyUsageController.cs
  68. 131 0
      Controllers/EntityController.cs
  69. 164 0
      Controllers/EquipmentCodeTypeController.cs
  70. 379 0
      Controllers/EquipmentController.cs
  71. 343 0
      Controllers/EquipmentHistoryController.cs
  72. 278 0
      Controllers/EquipmentRentInfoController.cs
  73. 75 0
      Controllers/ErrorController.cs
  74. 362 0
      Controllers/FacilityCodeClassController.cs
  75. 646 0
      Controllers/FacilityController.cs
  76. 277 0
      Controllers/FacilityStateController.cs
  77. 177 0
      Controllers/FacilityTimeController.cs
  78. 67 0
      Controllers/FileDownloadController.cs
  79. 165 0
      Controllers/FloorCodeController.cs
  80. 276 0
      Controllers/FloorController.cs
  81. 247 0
      Controllers/FormulaControllers.cs
  82. 214 0
      Controllers/GuidelineController.cs
  83. 261 0
      Controllers/HolidayController.cs
  84. 92 0
      Controllers/HolidayCustomController.cs
  85. 93 0
      Controllers/HolidayWeekendController.cs
  86. 44 0
      Controllers/HomeController.cs
  87. 162 0
      Controllers/Investigation1CodeGroupController.cs
  88. 162 0
      Controllers/Investigation1CodeTypeController.cs
  89. 193 0
      Controllers/Investigation1Controller.cs
  90. 178 0
      Controllers/Investigation1HistoryController.cs
  91. 162 0
      Controllers/Investigation2CodeGroupController.cs
  92. 162 0
      Controllers/Investigation2CodeTypeController.cs
  93. 193 0
      Controllers/Investigation2Controller.cs
  94. 178 0
      Controllers/Investigation2HistoryController.cs
  95. 162 0
      Controllers/Investigation3CodeGroupController.cs
  96. 162 0
      Controllers/Investigation3CodeTypeController.cs
  97. 193 0
      Controllers/Investigation3Controller.cs
  98. 178 0
      Controllers/Investigation3HistoryController.cs
  99. 193 0
      Controllers/LayoutController.cs
  100. 233 0
      Controllers/LegalCheckPartnerController.cs

+ 23 - 0
.gitignore

@@ -0,0 +1,23 @@
+# dependencies
+
+# testing
+
+# log files
+/Logs
+
+# production
+/bin
+/bin/**
+/obj
+/obj/**
+
+/wwwroot/files/**
+
+# misc
+/Migrations
+/*.db
+*.sql
+/publish
+
+appsettings.*.json
+appsettings.json

+ 19 - 0
.gitlab-ci.yml

@@ -0,0 +1,19 @@
+image: mcr.microsoft.com/dotnet/core/sdk:3.1
+
+stages:
+    - build
+    - test
+
+variables:
+    test: "Example.Test"
+
+build:
+    stage: build
+    script:
+        - export PATH="$PATH:/root/.dotnet/tools"
+        - "dotnet build"
+
+test:
+    stage: test
+    script: 
+        - "dotnet test"

+ 38 - 0
.vscode/launch.json

@@ -0,0 +1,38 @@
+{
+  // Use IntelliSense to find out which attributes exist for C# debugging
+  // Use hover for the description of the existing attributes
+  // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": ".NET Core Launch (web)",
+      "type": "coreclr",
+      "request": "launch",
+      "preLaunchTask": "build",
+      // If you have changed target frameworks, make sure to update the program path.
+      "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/FMSAdmin.dll",
+      "args": [],
+      "cwd": "${workspaceFolder}",
+      "stopAtEntry": false,
+      // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
+      /*
+            "serverReadyAction": {
+                "action": "openExternally",
+                "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)"                
+            },
+            */
+      "env": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      },
+      "sourceFileMap": {
+        "/Views": "${workspaceFolder}/Views"
+      }
+    },
+    {
+      "name": ".NET Core Attach",
+      "type": "coreclr",
+      "request": "attach",
+      "processId": "${command:pickProcess}"
+    }
+  ]
+}

+ 11 - 0
.vscode/settings.json

@@ -0,0 +1,11 @@
+{
+  "editor.formatOnSave": true,
+  "editor.tabSize": 4,
+  "editor.codeLens": true,
+  "git.enableSmartCommit": false,
+  "rest-client.environmentVariables": {
+    "$shared": {
+      "baseUrl": "http://localhost:9090"
+    }
+  }
+}

+ 42 - 0
.vscode/tasks.json

@@ -0,0 +1,42 @@
+{
+  "version": "2.0.0",
+  "tasks": [
+    {
+      "label": "build",
+      "command": "dotnet",
+      "type": "process",
+      "args": [
+        "build",
+        "${workspaceFolder}/FMSAdmin.csproj",
+        "/property:GenerateFullPaths=true",
+        "/consoleloggerparameters:NoSummary"
+      ],
+      "problemMatcher": "$msCompile"
+    },
+    {
+      "label": "publish",
+      "command": "dotnet",
+      "type": "process",
+      "args": [
+        "publish",
+        "${workspaceFolder}/FMSAdmin.csproj",
+        "/property:GenerateFullPaths=true",
+        "/consoleloggerparameters:NoSummary"
+      ],
+      "problemMatcher": "$msCompile"
+    },
+    {
+      "label": "watch",
+      "command": "dotnet",
+      "type": "process",
+      "args": [
+        "watch",
+        "run",
+        "${workspaceFolder}/FMSAdmin.csproj",
+        "/property:GenerateFullPaths=true",
+        "/consoleloggerparameters:NoSummary"
+      ],
+      "problemMatcher": "$msCompile"
+    }
+  ]
+}

+ 162 - 0
Controllers/AccidentCodeTypeController.cs

@@ -0,0 +1,162 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class AccidentCodeTypeController : Controller {
+        private readonly ILogger<AccidentCodeTypeController> _logger;
+        private readonly FMSContext _context;
+        private AccidentCodeTypeService _service;
+
+        public AccidentCodeTypeController(
+            ILogger<AccidentCodeTypeController> logger,
+            FMSContext context,
+            AccidentCodeTypeService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsAccidentCodeType> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsAccidentCodeType> query) {
+            var list = query.Select(x => new {
+                x.AccidentTypeId,
+                x.Name,
+                IsUse = (x.IsUse.GetValueOrDefault() ? "Y" : "N")
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.AccidentTypeId,
+                x.Name,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] FmsAccidentCodeType data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.AccidentTypeId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsAccidentCodeType data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 261 - 0
Controllers/AccidentController.cs

@@ -0,0 +1,261 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class AccidentController : Controller {
+        private readonly ILogger<AccidentController> _logger;
+        private readonly FMSContext _context;
+        private AccidentService _service;
+        private readonly StorageHelper _storage;
+
+        public AccidentController(
+            ILogger<AccidentController> logger,
+            FMSContext context,
+            AccidentService service,
+             StorageHelper storage
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsAccident> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            var sortQuery = query.OrderBy(x => x.CmSite.SortOrderNo);
+            sortQuery = sortQuery.ThenByDescending(x => x.StartDate);
+
+            sortQuery = sortQuery.ThenSort(req.sort);
+
+            return sortQuery;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsAccident> query) {
+            var list = query.Select(x => new {
+                x.AccidentId,
+                x.AccidentTypeId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                AccidentTypeName = x.FmsAccidentCodeType.Name,
+                x.Name,
+                DepartmentName = x.CmDepartment.Name,
+                StartDate = x.StartDate != null ? x.StartDate.Value.ToString("yyyy/MM/dd") : null,
+                EndDate = x.EndDate != null ? x.EndDate.Value.ToString("yyyy/MM/dd") : null,
+                ClosingDate = x.ClosingDate != null ? x.ClosingDate.Value.ToString("yyyy/MM/dd") : null
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            _logger.LogError("#############");
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.AccidentId,
+                x.AccidentTypeId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.Name,
+                x.AccidentLocation,
+                FmsAccidentCodeType = new {
+                    x.FmsAccidentCodeType.Name
+                },
+                x.StartDate,
+                x.EndDate,
+                x.ClosingDate,
+                CmCompany = new {
+                    x.CmCompany.Name
+                },
+                CmDepartment = new {
+                    x.CmDepartment.Name
+                },
+                x.CompanyId,
+                x.DepartmentId,
+                x.Reason,
+                x.Action,
+                x.Action2,
+                x.Damage,
+                x.Measurement,
+                CmFile1 = _storage.ParseFile(x.CmFile1),
+                CmFile2 = _storage.ParseFile(x.CmFile2),
+                CmFile3 = _storage.ParseFile(x.CmFile3),
+                CmFile4 = _storage.ParseFile(x.CmFile4),
+                x.Firstwitness,
+                x.VictimAge,
+                x.VictimName,
+                x.VictimRelation,
+                x.IsInsurance,
+                x.InsuranceContent
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] FmsAccident data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            _logger.LogInformation("siteId:" + siteId);
+            if (id != data.AccidentId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsAccident data) {
+            _logger.LogInformation("Create");
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+            return Ok();
+        }
+
+        /// <summary>
+        /// 보고서
+        /// </summary>
+        [AllowAnonymous]
+        [HttpGet("Report/{siteId}/{id}")]
+        public IActionResult Report(int siteId, int id) {
+
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.AccidentId,
+                x.AccidentTypeId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.Name,
+                x.AccidentLocation,
+                AccidentCodeTypeName = x.FmsAccidentCodeType.Name,
+                x.StartDate,
+                x.EndDate,
+                ClosingDate = x.ClosingDate.Value.ToString("yyyy/MM/dd"),
+                CompanyName = x.CmCompany.Name,
+                DepartmentName = x.CmDepartment.Name,
+                x.CompanyId,
+                x.DepartmentId,
+                x.Reason,
+                x.Action,
+                x.Action2,
+                x.Damage,
+                x.Measurement,
+                CmFileUrl1 = _storage.ParseFileUrl(x.CmFile1),
+                CmFileUrl2 = _storage.ParseFileUrl(x.CmFile2),
+                x.Firstwitness,
+                x.VictimAge,
+                x.VictimName,
+                x.VictimRelation,
+                IsInsurance = x.IsInsurance == true ? "네" : "아니오",
+                x.InsuranceContent
+            });
+
+            return Ok(list.ToList());
+        }
+
+    }
+}

+ 131 - 0
Controllers/AlarmLogController.cs

@@ -0,0 +1,131 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class AlarmLogController : Controller {
+        private readonly ILogger<AlarmLogController> _logger;
+        private readonly FMSContext _context;
+        private readonly AlarmPointService _service;
+
+        public AlarmLogController(
+            ILogger<AlarmLogController> logger,
+            FMSContext context,
+            AlarmPointService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string facilityCode = "") {
+            var query = _FilterAndSort(req);
+
+            if (!facilityCode.Equals("")) {
+                query = query.Where(x => x.FacilityCode == Util.ToInt(facilityCode));
+            }
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<FmsAlarmLog> query) {
+            var list = query.Select(x => new {
+                CreatedDateTime = x.CreatedDateTime.ToString("yyyy/MM/dd H:mm"),
+                x.SiteId,
+                SiteName = x.Site.Name,
+                x.FacilityTypeId,
+                FacilityTypeName = x.AlarmPoint.BemsMonitoringPoint.FacilityType.Name,
+                x.FacilityCode,
+                FacilityName = x.AlarmPoint.BemsMonitoringPoint.CmFacility.Name,
+                x.PropertyId,
+                MonitoringPointName = x.AlarmPoint.BemsMonitoringPoint.Name,
+                x.CurrentValue,
+
+            });
+
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsAlarmLog> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAlarmLog();
+
+            query = query.Filter(req.conditions);
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            var sortQuery = query.OrderByDescending(x => x.CreatedDateTime);
+            sortQuery = sortQuery.ThenBy(x => x.Site.SortOrderNo);
+            sortQuery = sortQuery.ThenSort(req.sort);
+            return sortQuery;
+        }
+    }
+
+}
+
+

+ 126 - 0
Controllers/AlarmMonitoringPointHistoryController.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class AlarmMonitoringPointHistoryController : Controller {
+        private readonly ILogger<AlarmMonitoringPointHistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly AlarmPointService _service;
+
+        public AlarmMonitoringPointHistoryController(
+            ILogger<AlarmMonitoringPointHistoryController> logger,
+            FMSContext context,
+            AlarmPointService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<BemsMonitoringPointHistory15min> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                CreatedDateTime = x.CreatedDateTime.ToString("yyyy/MM/dd H:mm"),
+                SiteName = x.Site.Name,
+                x.FacilityTypeId,
+                FacilityTypeName = x.FacilityType.Name,
+                x.FacilityCode,
+                FacilityName = x.BemsMonitoringPoint.CmFacility.Name,
+                x.PropertyId,
+                MonitoringPointName = x.BemsMonitoringPoint.Name,
+                x.CurrentValue,
+
+            });
+
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<BemsMonitoringPointHistory15min> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetPointHistoryList();
+
+            query = query.Filter(req.conditions);
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+
+}
+

+ 170 - 0
Controllers/AlarmPointController.cs

@@ -0,0 +1,170 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class AlarmPointController : Controller {
+        private readonly ILogger<AlarmPointController> _logger;
+        private readonly FMSContext _context;
+        private AlarmPointService _service;
+
+        public AlarmPointController(
+            ILogger<AlarmPointController> logger,
+            FMSContext context,
+            AlarmPointService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsAlarmPoint> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsAlarmPoint> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = x.Site.Name,
+                x.FacilityTypeId,
+                FacilityTypeName = x.FacilityType.Name,
+                x.FacilityCode,
+                FacilityName = x.Facility.Name,
+                PointName = x.BemsMonitoringPoint.Name,
+                x.PropertyId,
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{facilityCode}/{propertyId}")]
+        public IActionResult Get(int siteId, int facilityCode, int propertyId) {
+            var data = _service.Get(siteId, facilityCode, propertyId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.SiteId,
+                CmSite = new {
+                    x.Site.Name,
+                },
+                x.FacilityCode,
+                Facility = new {
+                    x.Facility.Name,
+                    TypeName = x.Facility.BemsFacilityType.Name
+                },
+                BemsMonitoringPoint = new {
+                    x.BemsMonitoringPoint.Name,
+                },
+                ServiceType = new {
+                    x.BemsMonitoringPoint.ServiceType.Name
+                },
+                x.PropertyId,
+                x.FacilityTypeId,
+                FuelType = new {
+                    x.BemsMonitoringPoint.FuelType.Name
+                }
+            });
+            return Ok(list.First());
+        }
+
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsAlarmPoint[] data) {
+            _logger.LogInformation("Create");
+            // _logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{facilityCode}/{propertyId}")]
+        public IActionResult Delete(int siteId, int facilityCode, int propertyId) {
+            _service.Delete(siteId, facilityCode, propertyId);
+            return Ok();
+        }
+
+    }
+}

+ 212 - 0
Controllers/AnnouncementController.cs

@@ -0,0 +1,212 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class AnnouncementController : Controller {
+        private readonly ILogger<AnnouncementController> _logger;
+        private readonly FMSContext _context;
+        private readonly AnnouncementService _service;
+        private readonly StorageHelper _storage;
+
+        public AnnouncementController(
+            ILogger<AnnouncementController> logger,
+            FMSContext context,
+            AnnouncementService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}/{isCount}")]
+        public IActionResult Get(int siteId, int id, int isCount) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("공지사항 정보를 찾을 수 없습니다.");
+            }
+
+            //조회수 증가
+            if (isCount == 1) {
+                _service.AddCount(id, siteId);
+            }
+
+            var item = data.Select(x => new {
+                x.AnnouncementId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.BusinessFieldId,
+                CmBusinessField = new {
+                    x.CmBusinessField.Name,
+                },
+                x.Title,
+                x.Contents,
+                RegisterUserId = x.RegisterUserId.Trim(),
+                x.FileId,
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.AddDate,
+                x.UpdateDate,
+                x.IsUse,
+                x.ReadCount,
+                IsSysop = x.CmUser.IsSysop,
+                IsAllView = Util.ToBool(x.IsAllView),
+                IsAllViewName = (Util.ToBool(x.IsAllView)) ? "본사지침" : "",
+            });
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] CmAnnouncement data) {
+            if (id != data.AnnouncementId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmAnnouncement data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<CmAnnouncement> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.AnnouncementId,
+                x.BusinessFieldId,
+                BusinessFieldName = x.CmBusinessField.Name,
+                IsAllView = x.IsAllView.GetValueOrDefault() ? "Y" : "N",
+                x.Title,
+                x.RegisterUserId,
+                AddDate = x.AddDate.ToString("yyyy-MM-dd"),
+                UpdateDate = x.UpdateDate.ToString("yyyy-MM-dd"),
+                x.IsUse,
+                IsSysop = x.CmUser.IsSysop,
+                FilePath = _storage.getFilePath(x.CmFile),
+                FileName = x.CmFile.Name
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmAnnouncement> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId) || x.IsAllView == true);
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+
+            // 업무분야 필드가 있는경우만 추가
+            if (req.businessFieldId != null) {
+                query = query.Where(x => x.SiteId == req.siteId);
+                query = query.Where(x => x.BusinessFieldId == req.businessFieldId || x.CmBusinessField.Name == "공통");
+            }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+    }
+}

+ 144 - 0
Controllers/App/AccidentControllers.cs

@@ -0,0 +1,144 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class AccidentController : Controller {
+        private readonly ILogger<AccidentController> _logger;
+        private readonly FMSContext _context;
+        private readonly AccidentService _service;
+        private readonly StorageHelper _storage;
+
+        public AccidentController(
+            ILogger<AccidentController> logger,
+            FMSContext context,
+            AccidentService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.AccidentId,
+                AccidentTypeName = x.FmsAccidentCodeType.Name,
+                x.Name,
+                Dates = new {
+                    StartDate = x.StartDate == null ? null : ((DateTime)x.StartDate).ToString("yyyy.MM.dd"),
+                    EndDate = x.EndDate == null ? null : ((DateTime)x.EndDate).ToString("yyyy.MM.dd"),
+                    ClosingDate = x.ClosingDate == null ? null : x.ClosingDate.Value.ToString("yyyy.MM.dd")
+                },
+                x.AccidentTypeId,
+                x.DepartmentId,
+                DepartmentName = x.CmDepartment.Name,
+                x.AccidentLocation
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string accidentId) {
+            var query = _service.GetAll().Where(x => x.AccidentId == Util.ToInt(accidentId)).Where(x => x.SiteId == Util.ToInt(siteId));
+            var item = query.Select(x => new {
+                x.SiteId, //사이트ID
+                x.AccidentId, //AccidentId
+                x.AccidentTypeId, //사고유형ID
+                AccidentTypeName = x.FmsAccidentCodeType.Name,
+                x.Name, //사고명
+                x.AccidentLocation, //사고위치
+                x.StartDate,//사고발생일
+                x.EndDate,//조치완료일
+                x.ClosingDate,
+                x.CompanyId, //회사
+                CompanyName = x.CmCompany.Name,
+                x.DepartmentId, //부서
+                DepartmentName = x.CmDepartment.Name,
+                x.Reason, //사고원인
+                x.Action, //조치내용
+                x.Action2,
+                x.Damage, //피해내용
+                x.Measurement, //향후대책
+                x.Firstwitness,
+                x.VictimName,
+                x.VictimRelation,
+                x.VictimAge,
+                x.IsInsurance,
+                x.InsuranceContent,
+                CmFile1 = _storage.ParseFile(x.CmFile1), //첨부파일
+                CmFile2 = _storage.ParseFile(x.CmFile2), //첨부파일
+                CmFile3 = _storage.ParseFile(x.CmFile3), //첨부파일
+                CmFile4 = _storage.ParseFile(x.CmFile4) //첨부파일
+            }).FirstOrDefault();
+
+            return Ok(item);
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsAccident> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] FmsAccident data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpDelete("{siteId}/{accidentId}")]
+        public IActionResult Delete(int siteId, int accidentId) {
+            _service.Delete(siteId, accidentId);
+            return Ok();
+        }
+
+    }
+}

+ 119 - 0
Controllers/App/AlarmLogController.cs

@@ -0,0 +1,119 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class AlarmLogController : Controller {
+        private readonly ILogger<AlarmLogController> _logger;
+        private readonly FMSContext _context;
+        private readonly AlarmPointService _service;
+
+        public AlarmLogController(
+            ILogger<AlarmLogController> logger,
+            FMSContext context,
+            AlarmPointService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery]PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery]PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<FmsAlarmLog> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = x.Site.Name,
+                x.FacilityTypeId,
+                FacilityTypeName = x.AlarmPoint.BemsMonitoringPoint.FacilityType.Name,
+                x.FacilityCode,
+                FacilityName = x.AlarmPoint.BemsMonitoringPoint.CmFacility.Name,
+                x.PropertyId,
+                MonitoringPointName = x.AlarmPoint.BemsMonitoringPoint.Name,
+                x.CurrentValue,
+                x.CreatedDateTime,
+            });
+
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsAlarmLog> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAlarmLog(req.siteId.Value);
+
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+
+}
+

+ 119 - 0
Controllers/App/AlarmMonitoringPointHistoryController.cs

@@ -0,0 +1,119 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class AlarmMonitoringPointHistoryController : Controller {
+        private readonly ILogger<AlarmMonitoringPointHistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly AlarmPointService _service;
+
+        public AlarmMonitoringPointHistoryController(
+            ILogger<AlarmMonitoringPointHistoryController> logger,
+            FMSContext context,
+            AlarmPointService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery]PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery]PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<BemsMonitoringPointHistory15min> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = x.Site.Name,
+                x.FacilityTypeId,
+                FacilityTypeName = x.FacilityType.Name,
+                x.FacilityCode,
+                FacilityName = x.BemsMonitoringPoint.CmFacility.Name,
+                x.PropertyId,
+                MonitoringPointName = x.BemsMonitoringPoint.Name,
+                x.CurrentValue,
+                x.CreatedDateTime,
+            });
+
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<BemsMonitoringPointHistory15min> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetPointHistoryList(req.siteId.Value);
+
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+
+}
+

+ 162 - 0
Controllers/App/AnnouncementController.cs

@@ -0,0 +1,162 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class AnnouncementController : Controller {
+        private readonly ILogger<AnnouncementController> _logger;
+        private readonly FMSContext _context;
+        private readonly AnnouncementService _service;
+        private readonly SiteService _siteService;
+        private readonly StorageHelper _storage;
+
+        public AnnouncementController(
+            ILogger<AnnouncementController> logger,
+            FMSContext context,
+            AnnouncementService service,
+            SiteService siteService,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _siteService = siteService;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            _logger.LogInformation("req" + req.Dump());
+            var user = _context.CmUser.FirstOrDefault(x => x.UserId == User.Identity.Name);
+            var query = _FilterAndSort(req);
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId) || x.IsAllView == true);
+            }
+            //query = query.Where(x => x.AddDate < DateTime.Now.AddDays(1).Date && x.UpdateDate >= DateTime.Now.Date);
+            if (businessAuth && user != null && user.BusinessFieldId != null) {
+                var businessCommon = _context.CmBusinessField.FirstOrDefault(x => x.Name == "공통");
+                query = query.Where(x => (new int[] { businessCommon.BusinessFieldId, user.BusinessFieldId.Value }).Contains(x.BusinessFieldId) || x.IsAllView == true);
+            }
+
+            var list = query.Select(x => new {
+                x.SiteId,
+                Name = Util.ToBool(x.CmUser.IsSysop) ? "운영자" : (Util.ToBool(x.IsAllView) ? "본사지침" : x.CmSite.Name),
+                x.Title,
+                x.AnnouncementId,
+                // CmSite = new {
+                //     Name = x.CmSite.Name// _siteService.Get(x.SiteId).FirstOrDefault().Name
+                // },
+                CmUser = new {
+                    Name = x.CmUser.Name
+                },
+                CmBusinessField = new {
+                    x.CmBusinessField.Name,
+                },
+                Date = new {
+                    AddDate = x.AddDate.ToString("yyyy.MM.dd")
+                },
+                IsAllView = Util.ToBool(x.IsAllView),
+                IsSysop = x.CmUser.IsSysop,
+                x.ReadCount,
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string annId) {
+            var query = _service.GetAll()
+                .Where(x => x.AnnouncementId == Util.ToInt(annId));
+
+            //조회수 증가
+            var announcement = query.FirstOrDefault();
+            if (announcement != null && User.Identity.Name != announcement.RegisterUserId) {
+                _service.AddCount(Util.ToInt(annId));
+            }
+
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.Title,
+                x.Contents,
+                x.CmUser.Name,
+                x.AddDate,
+                x.UpdateDate,
+                x.BusinessFieldId,
+                BusinessFieldName = x.CmBusinessField.Name,
+                x.RegisterUserId,
+                CmFile = _storage.ParseFile(x.CmFile),
+                CmBusinessField = new {
+                    BusinessFieldId = x.CmBusinessField.BusinessFieldId,
+                    Name = x.CmBusinessField.Name
+                },
+                CmUser = new {
+                    UserId = x.CmUser.UserId
+                },
+                x.AnnouncementId,
+                x.IsUse,
+                IsAllView = Util.ToBool(x.IsAllView),
+                x.ReadCount,
+            }).FirstOrDefault();
+
+            return Ok(item);
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmAnnouncement> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] CmAnnouncement data) {
+
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpDelete("{siteId}/{annId}")]
+        public IActionResult Delete(int siteId, int annId) {
+            _service.Delete(siteId, annId);
+            return Ok();
+        }
+
+    }
+}

+ 134 - 0
Controllers/App/ComplaintsController.cs

@@ -0,0 +1,134 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class ComplaintsController : Controller {
+        private readonly ILogger<ComplaintsController> _logger;
+        private readonly FMSContext _context;
+        private readonly ComplaintsService _service;
+        private readonly StorageHelper _storage;
+
+        public ComplaintsController(
+            ILogger<ComplaintsController> logger,
+            FMSContext context,
+            ComplaintsService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.ComplaintId,
+                ComplaintTypeName = x.FmsComplaintCodeType.Name,
+                x.Name,
+                Dates = new {
+                    StartDate = x.StartDate == null ? null : ((DateTime)x.StartDate).ToString("yyyy.MM.dd"),
+                    EndDate = x.EndDate == null ? null : ((DateTime)x.EndDate).ToString("yyyy.MM.dd"),
+                },
+                x.ComplaintTypeId,
+                x.DepartmentId,
+                DepartmentName = x.CmDepartment.Name,
+                x.ComplaintLocation
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string complaintId) {
+            var query = _service.GetAll().Where(x => x.ComplaintId == Util.ToInt(complaintId)).Where(x => x.SiteId == Util.ToInt(siteId));
+            var item = query.Select(x => new {
+                x.SiteId, //사이트ID
+                x.ComplaintId, //ComplaintId
+                x.ComplaintTypeId, //사고유형ID
+                ComplaintTypeName = x.FmsComplaintCodeType.Name,
+                x.Name, //사고명
+                x.ComplaintLocation, //사고위치
+                x.StartDate,//사고발생일
+                x.EndDate,//조치완료일
+                x.CompanyId, //회사
+                CompanyName = x.CmCompany.Name,
+                x.DepartmentId, //부서
+                DepartmentName = x.CmDepartment.Name,
+                x.Reason, //사고원인
+                x.Action, //조치내용
+                x.Damage, //피해내용
+                x.Measurement, //향후대책
+                CmFile1 = _storage.ParseFile(x.CmFile1), //첨부파일
+                CmFile2 = _storage.ParseFile(x.CmFile2), //첨부파일
+                CmFile3 = _storage.ParseFile(x.CmFile3) //첨부파일
+            }).FirstOrDefault();
+
+            return Ok(item);
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsComplaints> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] FmsComplaints data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpDelete("{siteId}/{complaintId}")]
+        public IActionResult Delete(int siteId, int complaintId) {
+            _service.Delete(siteId, complaintId);
+            return Ok();
+        }
+
+    }
+}

+ 173 - 0
Controllers/App/ContractManagementController.cs

@@ -0,0 +1,173 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class ContractManagementController : Controller {
+        private readonly ILogger<ContractManagementController> _logger;
+        private readonly FMSContext _context;
+        private readonly ContractManagementService _service;
+        private readonly StorageHelper _storage;
+
+        public ContractManagementController(
+            ILogger<ContractManagementController> logger,
+            FMSContext context,
+            ContractManagementService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] int siteId, [FromQuery] int contractId) {
+            var data = _service.Gets(contractId, siteId);
+            if (data.Count() == 0) {
+                return NotFound("정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.PartnerId,
+                PartnerName = x.CmPartner.Name,
+                x.PartnerTypeId,
+                PartnerTypeName = x.PartnerType.Name,
+                x.SiteId,
+                x.Name,
+                x.ContractDate,
+                x.StartDate,
+                x.EndDate,
+                x.OwnerShipName,
+                x.OwnerShipPhoneNo,
+                x.CommitmentMan,
+                x.ContractClassId,
+                ContractClassName = x.FmsContractClass.Name,
+                x.PaymentTypeId,
+                PaymentTypeName = x.FmsPaymentType.Name,
+                x.ContractTypeId,
+                ContractTypeName = x.FmsContractType.Name,
+                x.ContractMethodId,
+                ContractMethodName = x.FmsContractMethod.Name,
+                x.Comment,
+                x.ContractAmountText,
+                x.ContractExeGuaranteeRate,
+                x.DefectGuaranteeRate,
+                x.DefectGuaranteePeriodText,
+                CmFile = _storage.ParseFile(x.CmFile)
+                // x.RepresentativeName,
+                // x.SalesName,
+                // x.PhoneNo,
+                // x.SalesPhoneNo,
+                // x.FaxNo,
+                // x.RegistrationNo,
+                // x.HomepageUri,
+                // x.Address1,
+                // x.Adderss2,
+                // x.AddressZip1,
+                // x.AddressZip2,
+                // x.Note,
+                // x.IsUse,
+            }).FirstOrDefault();
+            return Ok(item);
+        }
+
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] FmsContract data) {
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<FmsContract> query) {
+            var list = query.Select(x => new {
+                x.PartnerId,
+                x.PartnerTypeId,
+                PartnerTypeName = x.PartnerType.Name,
+                PartnerName = x.CmPartner.Name,
+                x.SiteId,
+                x.Name,
+                x.ContractId,
+                Dates = new {
+                    StartDate = x.StartDate == null ? null : ((DateTime)x.StartDate).ToString("yyyy.MM.dd"),
+                    EndDate = x.EndDate == null ? null : ((DateTime)x.EndDate).ToString("yyyy.MM.dd"),
+                },
+                x.ContractAmountText
+            });
+            return list;
+        }
+
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsContract> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+    }
+}

+ 481 - 0
Controllers/App/DailyReportController.cs

@@ -0,0 +1,481 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Text;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class DailyReportController : Controller {
+        private readonly ILogger<DailyReportController> _logger;
+        private readonly FMSContext _context;
+        private readonly DailyReportService _service;
+        private readonly FMSAdmin.Services.SiteWeatherInfoService _siteWeatherInfoService;
+        private readonly StorageHelper _storage;
+
+        public enum WorkTypeId {
+            PeriodicInspection = 1,
+            ForensicExamination = 2,
+            CivilComplaint = 3,
+            SiAlarm = 4,
+            Occasional = 5,
+            Construct = 6,
+            ForensicEducation = 7,
+        }
+
+        public enum WorkProgressId {
+            WorkStatusPending = 3,
+            WorkStatusInProgress = 4,
+            WorkStatusComplete = 5,
+            WorkStatusApproved = 6,
+            WorkStatusDeferred = 7,
+        }
+
+        public enum AdjustmentTypeId {
+            Regular = 0,
+            Loss = 1,
+            Return = 2
+        }
+
+        public DailyReportController(
+            ILogger<DailyReportController> logger,
+            FMSContext context,
+            DailyReportService service,
+            FMSAdmin.Services.SiteWeatherInfoService siteWeatherInfoService,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _siteWeatherInfoService = siteWeatherInfoService;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            var query = _FilterAndSort(req);
+            query = BusinessFieldHelper.Apply<FmsDailyReport>(_context, User, businessAuth, query);
+            query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.AddDate,
+                x.BusinessFieldId,
+                x.Name,
+                x.Contents,
+                x.RegisterUserId,
+                RegisterUserName = _context.CmUser.FirstOrDefault(user => user.UserId == x.RegisterUserId).Name,
+                x.ApproveDate,
+                x.RegisterDate,
+                x.ApproverUserId,
+                ApproverUserName = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId).Name,
+            });
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] DateTime addDate, [FromQuery] string businessFieldId) {
+            var query = _service.GetAll();
+
+            var item = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.AddDate == addDate.Date
+                && x.BusinessFieldId == Util.ToInt(businessFieldId)
+            ).Select(x => new {
+                x.SiteId,
+                x.AddDate,
+                x.BusinessFieldId,
+                x.Name,
+                x.Contents,
+                x.Datas,
+                x.RegisterUserId,
+                x.RegisterDate,
+                x.ApproveDate,
+                x.ApproverUserId,
+                ApproverUserName = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId).Name,
+                x.ApproverUserId2,
+                ApproverUserName2 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId2).Name,
+                x.ApproverUserId3,
+                ApproverUserName3 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId3).Name,
+                x.ApproverUserId4,
+                ApproverUserName4 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId4).Name,
+                x.ApproverUserId5,
+                ApproverUserName5 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId5).Name,
+                CmFile = _storage.ParseFile(x.CmFile),
+            }).FirstOrDefault();
+
+            return Ok(item);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult GetCurrentInfo([FromQuery] string siteId, [FromQuery] DateTime addDate, [FromQuery] string businessFieldId) {
+
+            var today = addDate.Date;
+            var yesterday = addDate.Date.AddDays(-1);
+            var tomorrow = addDate.Date.AddDays(1);
+            var currentMonthFirstDay = new DateTime(addDate.Year, addDate.Month, 1);
+            var prevMonthFirstDay = new DateTime(addDate.Year, addDate.Month - 1, 1);
+
+            var site = _context.CmSite.FirstOrDefault(x => x.SiteId == Util.ToInt(siteId));
+            var sido = _siteWeatherInfoService.ReplaceSido(site.ApiSido ?? "");
+            var sigun = site.ApiSigun ?? "";
+            var weatherDateTime = new DateTime(addDate.Year, addDate.Month, addDate.Day, 10, 0, 0);
+            SiteWeatherInfo weather = null;
+            if (!string.IsNullOrEmpty(sido) && !string.IsNullOrEmpty(sigun)) {
+                weather = _context.SiteWeatherInfo.Where(
+                    x => x.Sido == sido
+                    && x.Sigun == sigun
+                    && x.BaseTime >= weatherDateTime
+                    && x.BaseTime < weatherDateTime.Date.AddDays(1)
+                ).OrderBy(x => x.BaseTime).FirstOrDefault();
+            }
+
+            /*
+            var workStatus = new Dictionary<string, int> {
+                ["WorkStatusTypePeriodicInspection"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.PeriodicInspection, addDate),
+                ["WorkStatusTypeForensicExamination"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.ForensicExamination, addDate),
+                ["WorkStatusTypeForensicEducation"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.ForensicEducation, addDate),
+                ["WorkStatusTypeOccasional"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.Occasional, addDate),
+                ["WorkStatusTypeConstruct"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.Construct, addDate),
+                ["WorkStatusTypeEtc"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.SiAlarm, addDate)
+                    + _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.CivilComplaint, addDate),
+
+                ["WorkStatusPending"] = _service.GetWorkStatusByWorkProgressId(Util.ToInt(siteId), (int)WorkProgressId.WorkStatusPending, addDate),
+                ["WorkStatusInProgress"] = _service.GetWorkStatusByWorkProgressId(Util.ToInt(siteId), (int)WorkProgressId.WorkStatusInProgress, addDate),
+                ["WorkStatusDeferred"] = _service.GetWorkStatusByWorkProgressId(Util.ToInt(siteId), (int)WorkProgressId.WorkStatusDeferred, addDate),
+                ["WorkStatusComplete"] = _service.GetFmsWorkResultComplete(Util.ToInt(siteId), (int)WorkProgressId.WorkStatusComplete, addDate),
+                ["WorkStatusApproved"] = _service.GetFmsWorkResultApproval(Util.ToInt(siteId), (int)WorkProgressId.WorkStatusApproved, addDate),
+            };
+            workStatus["WorkStatusTypeTotal"] = workStatus["WorkStatusTypeOccasional"]
+                + workStatus["WorkStatusTypePeriodicInspection"]
+                + workStatus["WorkStatusTypeForensicExamination"]
+                + workStatus["WorkStatusTypeEtc"];
+            workStatus["WorkStatusTotal"] = workStatus["WorkStatusPending"]
+                + workStatus["WorkStatusInProgress"]
+                + workStatus["WorkStatusDeferred"]
+                + workStatus["WorkStatusComplete"]
+                + workStatus["WorkStatusApproved"];
+            */
+
+            _logger.LogInformation("========================== 에너지! =======================");
+            var energies = _context.BemsFormula.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.FormulaId == 1
+                && (new int[] { 100, 101, 102, 104, 105, 106 }).Contains(x.FacilityTypeId)
+            ).Select(x => new {
+                x.FacilityTypeId,
+                FacilityTypeName = _context.BemsFacilityType.FirstOrDefault(ft => ft.FacilityTypeId == x.FacilityTypeId).Name,
+                x.FacilityCode,
+                List = x.BemsFormulaParameter.Select(p => new {
+                    Parameter = new {
+                        p.ParameterFacilityCode,
+                        p.ParameterPropertyId,
+                        p.FormulaId,
+                        p.ParameterId,
+                    },
+                    MonitoringPoint = _context.BemsMonitoringPoint.Where(
+                        mp => mp.SiteId == p.SiteId
+                        && mp.FacilityCode == p.ParameterFacilityCode
+                        && mp.PropertyId == p.ParameterPropertyId
+                    ).Select(mp => new {
+                        mp.FuelTypeId,
+                        mp.Name,
+                        // 당일(15minRaw 에서 해당 일자의 마지막 검침값)
+                        Today = mp.BemsMonitoringPointHistory15minRawData.Where(
+                            raw => raw.CreatedDateTime >= today && raw.CreatedDateTime < tomorrow
+                        ).OrderByDescending(raw => raw.CreatedDateTime).FirstOrDefault().CurrentValue,
+
+                        // 전일(15minRaw 에서 해당 일자 전날의 마지막 검침값)
+                        DayBefore = mp.BemsMonitoringPointHistory15minRawData.Where(
+                            raw => raw.CreatedDateTime >= yesterday && raw.CreatedDateTime < today
+                        ).OrderByDescending(raw => raw.CreatedDateTime).FirstOrDefault().CurrentValue,
+
+                        // 당일 
+                        DailyUsage = mp.BemsMonitoringPointHistoryDaily.Where(
+                            usage => usage.CreatedDateTime.Date == today.Date
+                        ).FirstOrDefault().DailyValue,
+
+                        //당월
+                        MonthUsage = mp.BemsMonitoringPointHistoryDaily.Where(
+                            usage => usage.CreatedDateTime.Date >= currentMonthFirstDay.Date && usage.CreatedDateTime < tomorrow
+                        ).Sum(usage => usage.DailyValue)
+                    }).FirstOrDefault()
+                }).Where(p => !p.MonitoringPoint.Name.Contains("과거")).ToList()
+            }).ToList();
+
+            _logger.LogInformation("========================== 작업내역 =======================");
+            var works = _context.CmBusinessField.Where(
+                x => x.IsUse == true
+            ).OrderBy(x => x.BusinessFieldId).Select(x => new {
+                x.BusinessFieldId,
+                BusinessFieldName = x.Name,
+                /* 기존 Bems 소스 그대로 가져 넣음... where 절의 and 와 or 가 난해함... 의도한건지 정확하지 않음. */
+                List = (from req in _context.FmsWorkRequest
+                        let res = from res in _context.FmsWorkResult
+                                  where
+                                  res.SiteId == Util.ToInt(siteId) && ((res.IsConfirmed == false && (res.StartDate <= addDate.Date)) ||
+                                  (res.ConfirmedDate.Value.Year == addDate.Year &&
+                                  res.ConfirmedDate.Value.Month == addDate.Month &&
+                                  res.ConfirmedDate.Value.Day == addDate.Day))
+                                  select res.WorkRequestId
+                        where
+                        req.SiteId == Util.ToInt(siteId) && (
+                        (res.Contains(req.WorkRequestId) && req.BusinessFieldId == x.BusinessFieldId)
+                        || (req.WorkProgressId <= 5 && req.WorkProgressId >= 4 && req.BusinessFieldId == x.BusinessFieldId)
+                        && (req.StartWorkDate <= addDate.Date))
+                        orderby req.RequestDate, req.WorkProgressId ascending
+                        select new {
+                            WorkRequestId = req.WorkRequestId,
+                            BusinessFieldId = req.BusinessFieldId,
+                            WorkProgressId = req.WorkProgressId,
+                            WorkProgressName = req.WorkProgress.Name,
+                            WorkTypeId = req.WorkTypeId,
+                            WorkTypeName = req.WorkType.Name,
+                            RequestDate = req.RequestDate,
+                            Title = req.Title,
+                            WorkUsers = req.WorkUsers,
+                            WorkPartnerId = req.WorkPartnerId,
+                            WorkPartnerName = req.WorkPartner.Name,
+                            req.FmsWorkResult.StartDate,
+                            req.FmsWorkResult.EndDate,
+                            CheckItems = _context.FmsWorkResultCheckItem.Where(
+                                check => check.SiteId == req.SiteId
+                                && check.WorkRequestId == req.WorkRequestId
+                            ).OrderBy(check => check.CheckItemId).Select(check => new {
+                                check.CheckDate,
+                                check.TextResult,
+                            }).ToList()
+                        }).ToList()
+            }).ToList();
+
+            _logger.LogInformation("========================== 작업현황 =======================");
+            /*
+            var workCounts = _context.CmBusinessField.Where(
+                x => x.IsUse == true
+            ).OrderBy(x => x.BusinessFieldId).Select(x => new {
+                x.BusinessFieldId,
+                BusinessFieldName = x.Name,
+                RequestCount = _context.FmsWorkRequest.Where(
+                    req => req.SiteId == Util.ToInt(siteId)
+                    && req.StartWorkDate.Date == addDate.Date
+                    && req.BusinessFieldId == x.BusinessFieldId
+                ).Count(),
+                ApprovalCount = _context.FmsWorkResult.Where(
+                    res => res.SiteId == Util.ToInt(siteId)
+                    && res.FmsWorkRequest.StartWorkDate.Date == addDate.Date
+                    && res.FmsWorkRequest.WorkProgressId == 6
+                    && res.FmsWorkRequest.BusinessFieldId == x.BusinessFieldId
+                ).Count()
+            }).ToList();
+            */
+            var workCounts = works.Select(x => new {
+                x.BusinessFieldId,
+                x.BusinessFieldName,
+                RequestCount = x.List.Count,
+                ApprovalCount = x.List.Where(x => x.WorkProgressId == 6).Count()
+            }).ToList();
+
+            var materials = _context.CmBusinessField.Where(
+                x => x.IsUse == true
+            ).OrderBy(x => x.BusinessFieldId).Select(x => new {
+                x.BusinessFieldId,
+                BusinessFieldName = x.Name,
+                List = _context.FmsMaterialRelease.Where(
+                    release => release.SiteId == Util.ToInt(siteId)
+                    && release.ReleaseDate.Date == addDate.Date
+                    && release.BusinessFieldId == x.BusinessFieldId
+                    && (release.AdjustmentTypeId == 0 || release.AdjustmentTypeId == 1)
+                ).OrderBy(release => release.ReleaseDate).Select(release => new {
+                    ReleaseTypeName = release.ReleaseType.Name,
+                    FirstClassId = release.FmsMaterial.FirstClassId,
+                    FirstClassName = release.FmsMaterial.FmsMaterialCodeClass.Name,
+                    SecondClassId = release.FmsMaterial.SecondClassId,
+                    SecondClassName = release.FmsMaterial.FmsMaterialCodeClass1.Name,
+                    ThirdClassId = release.FmsMaterial.ThirdClassId,
+                    ThirdClassName = release.FmsMaterial.FmsMaterialCodeClass2.Name,
+                    MaterialName = release.FmsMaterial.Name,
+                    Standard = release.FmsMaterial.Standard,
+                    WorkName = release.FmsWorkResult.FmsWorkOrder.Title,
+                    Unit = release.FmsMaterial.Unit,
+                    MaterialCount = (int)release.MaterialCount,
+                    WorkerName = release.FmsWorkResult.CmUser.Name
+                }).ToList()
+            }).ToList();
+
+            var accidents = _context.FmsAccident.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.StartDate >= today
+                && x.StartDate < tomorrow
+            ).OrderBy(x => x.AccidentId).Select(x => new {
+                x.AccidentTypeId,
+                AccidentTypeName = x.FmsAccidentCodeType.Name,
+                x.AccidentId,
+                x.Name,
+                x.DepartmentId,
+                DepartmentName = x.CmDepartment.Name,
+                x.StartDate,
+                x.EndDate,
+            }).ToList();
+
+            var complaints = _context.FmsComplaints.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.StartDate >= today
+                && x.StartDate < tomorrow
+            ).OrderBy(x => x.ComplaintId).Select(x => new {
+                x.ComplaintTypeId,
+                ComplaintTypeName = x.FmsComplaintCodeType.Name,
+                x.ComplaintId,
+                x.Name,
+                x.DepartmentId,
+                DepartmentName = x.CmDepartment.Name,
+                x.StartDate,
+                x.EndDate,
+            }).ToList();
+
+            var alarms = _context.FmsAlarmLog.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.CreatedDateTime >= today
+                && x.CreatedDateTime < tomorrow
+                && x.CurrentValue != 0
+            ).OrderBy(x => x.CreatedDateTime).Select(x => new {
+                x.FacilityTypeId,
+                FacilityTypeName = x.AlarmPoint.FacilityType.Name,
+                x.FacilityCode,
+                x.PropertyId,
+                MonitoringPointName = x.AlarmPoint.BemsMonitoringPoint.Name,
+                FacilityName = x.AlarmPoint.Facility.Name,
+                CreatedDateTime = x.CreatedDateTime.ToString("yyyy-MM-dd HH:mm"),
+                x.CurrentValue,
+            }).ToList();
+
+            return Ok(new {
+                Weather = weather,
+                Energies = energies,
+                Works = works,
+                WorkCounts = workCounts,
+                Materials = materials,
+                Accidents = accidents,
+                Complaints = complaints,
+                Alarms = alarms,
+            });
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] FmsDailyReport data, [FromQuery] bool isCreate) {
+            _logger.LogInformation($"Save isCreate({isCreate})");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data, isCreate);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpPost("[action]")]
+        public IActionResult Approve([FromQuery] string siteId, [FromQuery] string approverUserId, [FromQuery] DateTime addDate, [FromQuery] string businessFieldId) {
+            _service.Approve(Util.ToInt(siteId), approverUserId, addDate, Util.ToInt(businessFieldId));
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsDailyReport> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        [HttpDelete("[action]")]
+        public IActionResult Delete([FromQuery] string siteId, [FromQuery] DateTime addDate, [FromQuery] string businessFieldId) {
+            _service.Delete(Util.ToInt(siteId), addDate, Util.ToInt(businessFieldId));
+            return Ok();
+        }
+
+        [AllowAnonymous]
+        [HttpGet("[action]")]
+        public string GetJson([FromQuery] string siteId, [FromQuery] DateTime addDate) {
+            var query = _service.GetAll();
+
+            var item = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.AddDate.Date == addDate.Date
+            ).Select(x => new {
+                CmSite = new {
+                    x.Site.Name
+                },
+                AddDate = x.AddDate.ToString("yyyy-MM-dd"),
+                x.Name,
+                x.Contents,
+                x.Datas,
+                x.ApproveDate,
+                x.ApproveDate2,
+                x.ApproveDate3,
+                x.ApproverUserId,
+                x.ApproverUserId2,
+                x.ApproverUserId3
+            }).FirstOrDefault();
+            string approve = "";
+            if (item != null) {
+                approve += "[" + getApproverInfo(item.ApproverUserId, item.ApproveDate);
+                approve += getApproverInfo(item.ApproverUserId2, item.ApproveDate2);
+                approve += getApproverInfo(item.ApproverUserId3, item.ApproveDate3);
+                approve += "]";
+            }
+
+            string json = "";
+            if (item != null)
+                json = $@"
+                    {{ 
+                        SiteNm : ""{item.CmSite.Name}"",
+                        Datas: {item.Datas},
+                        Approver : {approve}
+                    }}";
+            return json;
+        }
+
+        public string getApproverInfo(string id, DateTime? date) {
+            if (!Util.IsNullOrEmpty(id)) {
+                var userInfo = _context.CmUser.Where(x => x.UserId == id).Select(x => new {
+                    GroupNm = x.CmUserGroup.Name,
+                    Date = date.Value.ToString("yyyy-MM-dd"),
+                    image = x.CmFile1 != null ? _storage.GetFullUrl(x.CmFile1.Path) + _storage.getFilePath(x.CmFile1) : ""
+                }).SingleOrDefault();
+                return JsonConvert.SerializeObject(userInfo) + ",";
+            } else return "";
+        }
+    }
+}

+ 287 - 0
Controllers/App/EquipmentController.cs

@@ -0,0 +1,287 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class EquipmentController : Controller {
+        private readonly ILogger<EquipmentController> _logger;
+        private readonly FMSContext _context;
+        private readonly EquipmentService _service;
+        private readonly StorageHelper _storage;
+
+        public EquipmentController(
+            ILogger<EquipmentController> logger,
+            FMSContext context,
+            EquipmentService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 엑셀파일 임포트
+        /// </summary>
+        /// <param name="file"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult ImportExcel(IFormFile file) {
+            IList<Hashtable> list = new List<Hashtable>();
+            using (ExcelPackage excelPackage = new ExcelPackage(file.OpenReadStream())) {
+                var worksheet = excelPackage.Workbook.Worksheets[0];
+                for (int i = worksheet.Dimension.Start.Row; i <= worksheet.Dimension.End.Row; i++) {
+
+                    var name = worksheet.Cells[i, 1].Value;
+                    var rfid = worksheet.Cells[i, 2].Value;
+                    var code = worksheet.Cells[i, 3].Value;
+                    list.Add(new Hashtable {
+                        {"Name", name}
+                    });
+                }
+            }
+            //_service.ImportExcel(list);
+            //return BadRequest(ex.Message);
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{id}/{historyId}")]
+        public IActionResult Get(int siteId, int id, int historyId) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("공기구정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.EquipmentId,
+                x.EquipmentTypeId,
+                FmsEquipmentCodeType = new {
+                    x.FmsEquipmentCodeType.Name
+                },
+                BusinessFieldId = x.FmsMaterialWarehouse.BusinessFieldId,
+                BusinessFieldName = x.FmsMaterialWarehouse.CmBusinessField.Name,
+                x.WarehouseId,
+                FmsMaterialWarehouse = new {
+                    x.FmsMaterialWarehouse.Name
+                },
+                x.SiteId,
+                CmSite = new {
+                    x.Site.Name,
+                },
+                ImageFile = _storage.ParseFile(x.ImageFile),
+                x.Name,
+                x.Standard,
+                x.Unit,
+                x.SupplierName,
+                x.SupplierPhoneNo,
+                TotalStockCount = x.FmsEquipmentHistory.Where(x => x.EquipmentHistoryId == historyId).Select(x => x.TotalStockCount).FirstOrDefault(),
+                CurrentStockCount = x.FmsEquipmentHistory.Where(x => x.EquipmentHistoryId == historyId).Select(x => x.CurrentStockCount).FirstOrDefault(),
+            });
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("자재정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.EquipmentId,
+                x.EquipmentTypeId,
+                EquipmentTypeName = x.FmsEquipmentCodeType.Name,
+                x.WarehouseId,
+                WarehouseName = x.FmsMaterialWarehouse.Name,
+                BusinessFieldId = x.FmsMaterialWarehouse.BusinessFieldId,
+                BusinessFieldName = x.FmsMaterialWarehouse.CmBusinessField.Name,
+                x.SiteId,
+                Site = new {
+                    x.Site.Name,
+                },
+                ImageFile = _storage.ParseFile(x.ImageFile),
+                x.Name,
+                x.Standard,
+                x.Unit,
+                x.SupplierName,
+                x.SupplierPhoneNo,
+            });
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] FmsEquipment data) {
+            if (id != data.EquipmentId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+            ModelState.Remove("RegisterUserId");
+            ModelState.RemoveStartOf("ImageFile");
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsEquipment data) {
+            ModelState.Remove("RegisterUserId");
+            ModelState.RemoveStartOf("ImageFile");
+            data.RegisterUserId = User.Identity.Name;
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<FmsEquipment> query) {
+            var list = query.Select(x => new {
+                x.EquipmentId,
+                x.SiteId,
+                SiteName = x.Site.Name,
+                x.EquipmentTypeId,
+                EquipmentTypeName = x.FmsEquipmentCodeType.Name,
+                x.Name,
+                StockCount = x.FmsEquipmentHistory.OrderByDescending(x => x.EquipmentHistoryId).Select(x => x.CurrentStockCount).FirstOrDefault() ?? 0,
+                x.AddDate,
+                x.Standard,
+                x.Unit,
+                x.SupplierName,
+                x.SupplierPhoneNo,
+                x.WarehouseId,
+                WarehouseName = x.FmsMaterialWarehouse.Name,
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsEquipment> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 245 - 0
Controllers/App/EquipmentHistoryController.cs

@@ -0,0 +1,245 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class EquipmentHistoryController : Controller {
+        private readonly ILogger<EquipmentHistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly EquipmentService _service;
+        private readonly EquipmentHistoryService _historyService;
+        private readonly StorageHelper _storage;
+
+        public EquipmentHistoryController(
+            ILogger<EquipmentHistoryController> logger,
+            FMSContext context,
+            EquipmentService service,
+            EquipmentHistoryService historyService,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _historyService = historyService;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 공기구 등록 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult AddStockList([FromQuery] PagingRequest req) {
+            return List(req, 1);
+        }
+
+        /// <summary>
+        /// 공기구 대여 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult RentList([FromQuery] PagingRequest req) {
+            return List(req, 2);
+        }
+
+        /// <summary>
+        /// 공기구 반납/손실 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult ReturnList([FromQuery] PagingRequest req) {
+            return List(req, 3);
+        }
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, int? stateType) {
+            var query = _FilterAndSort(req, stateType);
+
+            if (stateType != null) {
+                if (stateType == 2) { // 대여 
+                    query = query.Where(x => x.EquipmentStateTypeId != 1 && x.IsReturned == false);
+                } else if (stateType == 3) {
+                    query = query.Where(x => x.EquipmentStateTypeId == 3 || x.EquipmentStateTypeId == 4 || x.EquipmentStateTypeId == 5);
+                } else {
+                    query = query.Where(x => x.EquipmentStateTypeId == stateType);
+                }
+            } else {
+                if (!_context.SiteConfig.Single().UseEquipmentRent) {
+                    query = query.Where(x => x.EquipmentStateTypeId != 2);
+                }
+            }
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _historyService.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("공기구정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.SiteId,
+                x.EquipmentId,
+                x.EquipmentHistoryId,
+                x.EquipmentRentId,
+                Site = new {
+                    x.CmSite.Name,
+                },
+                EquipmentStateType = new {
+                    x.EquipmentStateType.Name
+                },
+                RentInfo = new {
+                    Title = x.FmsEquipmentRentInfo.Title,
+                    RentUserName = x.FmsEquipmentRentInfo.RentUserName,
+                    RentDate = x.FmsEquipmentRentInfo.RentDate,
+                    ReturnDueDate = x.FmsEquipmentRentInfo.ReturnDueDate,
+                    ReturnFixDate = x.FmsEquipmentRentInfo.ReturnFixDate
+                },
+                EquipmentInfo = new {
+                    x.FmsEquipment.Name,
+                    Standard = x.FmsEquipment.Standard,
+                },
+                EquipmentName = x.FmsEquipment.Name,
+                x.RentCount,
+                x.CurrentRentCount,
+                x.LossCount,
+                x.ReturnCount,
+                x.ReturnReason,
+                x.IsReturnApproval,
+                x.ReturnApprovalDate,
+                ReturnApprovalUser = x.CmUser.Name,
+                CmFile = _storage.ParseFile(x.CmFile),
+            });
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsEquipmentHistory data) {
+            if (ModelState.IsValid) {
+                _historyService.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            //_service.DeleteHistory(id, siteId);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<FmsEquipmentHistory> query) {
+            var list = query.Select(x => new {
+                x.EquipmentHistoryId,
+                x.EquipmentId,
+                x.EquipmentStateTypeId,
+                EquipmentStateTypeName = x.EquipmentStateType.Name,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.FmsEquipment.EquipmentTypeId,
+                EquipmentTypeName = x.FmsEquipment.FmsEquipmentCodeType.Name,
+                x.FmsEquipment.Name,
+                x.FmsEquipment.Standard,
+                x.FmsEquipment.Unit,
+                x.FmsEquipment.WarehouseId,
+                WarehouseName = x.FmsEquipment.FmsMaterialWarehouse.Name,
+                x.TotalStockCount,
+                x.CurrentRentCount,
+                x.CurrentStockCount,
+                x.StoredCount,
+                x.RentCount,
+                x.ReturnCount,
+                x.LossCount,
+                x.ReturnReason,
+                x.AddDate,
+                x.EquipmentRentId,
+                x.FmsEquipmentRentInfo.RentDate,
+                x.FmsEquipmentRentInfo.ReturnDueDate,
+                x.FmsEquipmentRentInfo.ReturnFixDate,
+                x.FmsEquipment.SupplierName,
+                x.FmsEquipment.SupplierPhoneNo,
+                RentUserName = x.FmsEquipmentRentInfo.RentUserName,
+                x.FmsEquipmentRentInfo.RentUserId,
+                ReturnApprovalUserName = x.ReturnApprovalUserId != null ? x.CmUser.Name : "",
+                x.ReturnApprovalDate,
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsEquipmentHistory> _FilterAndSort(PagingRequest req, int? stateType) {
+            var query = _service.GetHistory();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+
+            if (Util.IsNullOrEmpty(stateType)) {
+                // 고정정렬 (사옥 준공순)
+                var sortQuery = query.OrderBy(x => x.CmSite.SortOrderNo);
+                // 추가 정렬
+                sortQuery = sortQuery.ThenSort(req.sort);
+                return sortQuery;
+            } else {
+                // 기본 Entity 정렬
+                query = query.Sort(req.sort);
+                return query;
+            }
+        }
+
+    }
+}

+ 183 - 0
Controllers/App/FacilityController.cs

@@ -0,0 +1,183 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class FacilityController : Controller {
+        private readonly ILogger<FacilityController> _logger;
+        private readonly FMSContext _context;
+        private readonly FacilityService _service;
+        private readonly StorageHelper _storage;
+
+        public FacilityController(
+            ILogger<FacilityController> logger,
+            FMSContext context,
+            FacilityService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.FacilityCode,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                CmBuilding = new {
+                    x.CmBuilding.BuildingId,
+                    x.CmBuilding.Name,
+                },
+                CmFloor = new {
+                    x.CmFloor.FloorId,
+                    x.CmFloor.Name,
+                },
+                CmZone = new {
+                    x.CmZone.ZoneId,
+                    x.CmZone.Name,
+                },
+                x.FirstClassId,
+                FirstClass = new {
+                    x.FmsFacilityCodeClass.Name,
+                },
+                x.SecondClassId,
+                SecondClass = new {
+                    x.FmsFacilityCodeClass1.Name,
+                },
+                x.ThirdClassId,
+                ThirdClass = new {
+                    x.FmsFacilityCodeClass2.Name,
+                },
+                x.Name,
+                x.Manufacturer,
+                x.FacilityCapacity,
+                x.IsUse,
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string facilityCode) {
+            _logger.LogInformation("siteId : " + siteId + ", facilityCode : " + facilityCode);
+            var query = _service.GetAll().Where(x => x.SiteId == Util.ToInt(siteId)
+                                            && x.FacilityCode == Util.ToInt(facilityCode));
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.FileId,
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.FacilityCode,
+                x.FacilityTypeId,
+                x.FirstClassId,
+                x.SecondClassId,
+                x.ThirdClassId,
+                x.DeviceType,
+                x.Name,
+                x.Nickname,
+                x.FacilityUsage,
+                x.Manufacturer,
+                x.ManufactureDate,
+                x.IsUse,
+                x.FacilityCapacity,
+                x.Rfid,
+                x.RatedPowerConsumption,
+                x.RatedCop,
+                x.ManufactureSerial,
+                x.ManufactureModel,
+                x.Supplier,
+                x.SupplierPhoneNo,
+                x.BuildingId,
+                x.FloorId,
+                x.ZoneId,
+                x.InstallDate,
+                x.OperationStartDate,
+                x.MainContactorUserId,
+                MainContactor = new {
+                    x.MainContactor.Name,
+                },
+                x.SubContactorUserId,
+                SubContactor = new {
+                    x.SubContactor.Name,
+                },
+                x.FacilityCost,
+                x.FacilityCount,
+                x.FuelTypeId,
+                x.ContractType,
+                ContractTypeName = _context.FmsContractType.FirstOrDefault(c => c.ContractTypeId == x.ContractType).Name,
+                x.Note,
+                x.Multiple,
+            }).FirstOrDefault();
+
+            return Ok(item);
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] CmFacility data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpDelete("{siteId}/{facilityCode}")]
+        public IActionResult Delete(int siteId, int facilityCode) {
+            _service.Delete(siteId, facilityCode);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmFacility> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            query = query.Where(x => !(new int[] { 99, 100, 101, 102, 103 }).Contains(x.FacilityTypeId));
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 165 - 0
Controllers/App/FacilityTimeController.cs

@@ -0,0 +1,165 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class FacilityTimeController : Controller {
+        private readonly ILogger<FacilityTimeController> _logger;
+        private readonly FMSContext _context;
+        private readonly FacilityTimeService _service;
+        private readonly StorageHelper _storage;
+
+        public FacilityTimeController(
+            ILogger<FacilityTimeController> logger,
+            FMSContext context,
+            FacilityTimeService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery]PagingRequest req, [FromQuery] string siteId, [FromQuery] string facilityTypeId, [FromQuery] string propertyId, [FromQuery] string sDate, [FromQuery] string eDate) {
+            DateTime startDate = DateTime.Parse(sDate);
+            DateTime endDate = DateTime.Parse(eDate);
+
+            var query = _FilterAndSort(req);
+
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            if (!string.IsNullOrEmpty(facilityTypeId)) {
+                query = query.Where(x => x.FacilityTypeId == Util.ToInt(facilityTypeId));
+            }
+            if (!string.IsNullOrEmpty(propertyId)) {
+                query = query.Where(x => x.PropertyId == Util.ToInt(propertyId));
+            }
+            query = query.Where(x => x.CreatedDateTime >= startDate)
+                         .Where(x => x.CreatedDateTime <= endDate);
+
+            var list = query.GroupBy(x => new { x.FacilityCode, x.CmFacility.Name })
+                             .Select(x => new { FacilityCode = x.Key.FacilityCode, Name = x.Key.Name, Value = x.Sum(v => v.DailyValue) }).ToList();
+            if (list.Count() > 0) {
+                return Ok(new {
+                    total = list.Count(),
+                    page = 1,
+                    limit = 0,
+                    list = list,
+                    maxValue = list.Select(x => x.Value).Max()
+                });
+            } else {
+                IList emptyArr = new List<string>();
+                return Ok(new {
+                    total = 0,
+                    page = 1,
+                    limit = 0,
+                    list = emptyArr,
+                    maxValue = 0
+                });
+            }
+            // var paging = PagingList.Pagination(list, req.page, req.limit);
+            // return Ok(paging);
+        }
+        [HttpGet("[action]")]
+        public IActionResult Detail([FromQuery]PagingRequest req, [FromQuery] string siteId, [FromQuery] string facilityTypeId, [FromQuery] string propertyId, [FromQuery] string facilityCode, [FromQuery] string sDate, [FromQuery] string eDate) {
+            DateTime startDate = DateTime.Parse(sDate);
+            DateTime endDate = DateTime.Parse(eDate);
+
+            var query = _FilterAndSort(req);
+
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            if (!string.IsNullOrEmpty(facilityTypeId)) {
+                query = query.Where(x => x.FacilityTypeId == Util.ToInt(facilityTypeId));
+            }
+            if (!string.IsNullOrEmpty(propertyId)) {
+                query = query.Where(x => x.PropertyId == Util.ToInt(propertyId));
+            }
+            if (!string.IsNullOrEmpty(facilityCode)) {
+                query = query.Where(x => x.FacilityCode == Util.ToInt(facilityCode));
+            }
+
+            query = query.Where(x => x.CreatedDateTime >= startDate)
+                         .Where(x => x.CreatedDateTime <= endDate);
+
+            var list = query.Select(x => new {
+                Date = new {
+                    Date = x.CreatedDateTime.ToString("yyyy.MM.dd")
+                },
+                Facility = new {
+                    Name = x.CmFacility.Name
+                },
+                Value = x.DailyValue,
+            }).ToList();
+            string aa = list.Select(x => x.Facility.Name).FirstOrDefault();
+            if (list.Count() > 0) {
+                return Ok(new {
+                    total = list.Count(),
+                    page = 1,
+                    limit = 0,
+                    name = list.Select(x => x.Facility.Name).FirstOrDefault(),
+                    maxValue = list.Select(x => x.Value).Max(),
+                    list = list
+
+
+                });
+            } else {
+                IList emptyArr = new List<string>();
+                return Ok(new {
+                    total = 0,
+                    page = 1,
+                    limit = 0,
+                    name = "",
+                    maxValue = 0,
+                    list = emptyArr
+                });
+            }
+            // var paging = PagingList.Pagination(list, req.page, req.limit);
+            // return Ok(paging);
+        }
+
+
+
+
+
+        // 검색 & 정렬 공통
+        private IQueryable<BemsMonitoringPointHistoryDaily> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 153 - 0
Controllers/App/FacilityWorkHistoryController.cs

@@ -0,0 +1,153 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class FacilityWorkHistoryController : Controller {
+        private readonly ILogger<FacilityWorkHistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly WorkOrderService _service;
+        private readonly StorageHelper _storage;
+
+        public FacilityWorkHistoryController(
+            ILogger<FacilityWorkHistoryController> logger,
+            FMSContext context,
+            WorkOrderService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        [HttpGet]
+        public IActionResult List([FromQuery]PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            var query = _FilterAndSort(req);
+
+            query = BusinessFieldHelper.Apply<FmsWorkOrderToFacility>(_context, User, businessAuth, query, "FmsWorkOrder.FmsWorkRequest");
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.FmsWorkOrder.FmsWorkRequest.WorkProgressId == 6
+            );
+
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.FacilityCode,
+                x.CmFacility.FirstClassId,
+                FirstClassName = x.CmFacility.FmsFacilityCodeClass.Name,
+                x.CmFacility.SecondClassId,
+                SecondClassName = x.CmFacility.FmsFacilityCodeClass1.Name,
+                x.CmFacility.ThirdClassId,
+                ThirdClassName = x.CmFacility.FmsFacilityCodeClass2.Name,
+                x.CmFacility.Name,
+                x.CmFacility.BuildingId,
+                x.CmFacility.FloorId,
+                FacilityTypeName = x.CmFacility.BemsFacilityType.Name,
+                x.FmsWorkOrder.FmsWorkRequest.WorkTypeId,
+                x.FmsWorkOrder.FmsWorkRequest.BusinessFieldId,
+                x.FmsWorkOrder.Title,
+                x.FmsWorkOrder.OrderDate,
+                x.FmsWorkOrder.StartWorkDate,
+                x.FmsWorkOrder.FmsWorkResult.StartDate,
+                x.FmsWorkOrder.FmsWorkResult.EndDate,
+                x.FmsWorkOrder.FmsWorkResult.ConfirmedUserId,
+                x.FmsWorkOrder.FmsWorkResult.ConfirmedDate,
+                x.FmsWorkOrder.FmsWorkRequest.WorkProgressId,
+                RequestStartWorkDate = x.FmsWorkOrder.FmsWorkRequest.StartWorkDate,
+                BusinessFieldName = x.FmsWorkOrder.FmsWorkRequest.CmBusinessField.Name,
+                OrderUserName = x.FmsWorkOrder.CmUser.Name,
+                WorkTypeName = x.FmsWorkOrder.FmsWorkRequest.WorkType.Name,
+                OrderBusinessFieldName = x.FmsWorkOrder.CmBusinessField.Name,
+                WorkProgressName = x.FmsWorkOrder.FmsWorkRequest.WorkProgress.Name,
+                ConfirmedUserName = x.FmsWorkOrder.FmsWorkResult.CmUser.Name,
+                WorkerUserName = x.FmsWorkOrder.FmsWorkResult.CmUserNavigation.Name,
+                x.FmsWorkOrder.Content
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string workRequestId, [FromQuery] string facilityCode) {
+            var query = _context.FmsWorkOrderToFacility as IQueryable<FmsWorkOrderToFacility>;
+
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.FacilityCode == Util.ToInt(facilityCode)
+                && x.WorkRequestId == Util.ToInt(workRequestId)
+            );
+
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.FacilityCode,
+                x.CmFacility.FirstClassId,
+                FirstClassName = x.CmFacility.FmsFacilityCodeClass.Name,
+                x.CmFacility.SecondClassId,
+                SecondClassName = x.CmFacility.FmsFacilityCodeClass1.Name,
+                x.CmFacility.ThirdClassId,
+                ThirdClassName = x.CmFacility.FmsFacilityCodeClass2.Name,
+                x.CmFacility.Name,
+                x.CmFacility.BuildingId,
+                x.CmFacility.FloorId,
+                FacilityTypeName = x.CmFacility.BemsFacilityType.Name,
+                x.FmsWorkOrder.FmsWorkRequest.WorkTypeId,
+                x.FmsWorkOrder.FmsWorkRequest.BusinessFieldId,
+                x.FmsWorkOrder.Title,
+                x.FmsWorkOrder.OrderDate,
+                x.FmsWorkOrder.StartWorkDate,
+                x.FmsWorkOrder.FmsWorkResult.StartDate,
+                x.FmsWorkOrder.FmsWorkResult.EndDate,
+                x.FmsWorkOrder.FmsWorkResult.ConfirmedUserId,
+                x.FmsWorkOrder.FmsWorkResult.ConfirmedDate,
+                x.FmsWorkOrder.FmsWorkRequest.WorkProgressId,
+                RequestStartWorkDate = x.FmsWorkOrder.FmsWorkRequest.StartWorkDate,
+                BusinessFieldName = x.FmsWorkOrder.FmsWorkRequest.CmBusinessField.Name,
+                OrderUserName = x.FmsWorkOrder.CmUser.Name,
+                WorkTypeName = x.FmsWorkOrder.FmsWorkRequest.WorkType.Name,
+                OrderBusinessFieldName = x.FmsWorkOrder.CmBusinessField.Name,
+                WorkProgressName = x.FmsWorkOrder.FmsWorkRequest.WorkProgress.Name,
+                ConfirmedUserName = x.FmsWorkOrder.FmsWorkResult.CmUser.Name,
+                WorkerUserName = x.FmsWorkOrder.FmsWorkResult.CmUserNavigation.Name,
+                x.FmsWorkOrder.Content,
+            }).FirstOrDefault();
+
+            return Ok(item);
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsWorkOrderToFacility> _FilterAndSort(PagingRequest req) {
+            var query = _context.FmsWorkOrderToFacility as IQueryable<FmsWorkOrderToFacility>;
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 92 - 0
Controllers/App/FloorController.cs

@@ -0,0 +1,92 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class FloorController : Controller {
+        private readonly ILogger<FloorController> _logger;
+        private readonly FMSContext _context;
+        private readonly FloorService _service;
+
+        public FloorController(
+            ILogger<FloorController> logger,
+            FMSContext context,
+            FloorService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery]PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.BuildingId,
+                x.FloorId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                CmBuilding = new {
+                    x.CmBuilding.Name,
+                },
+                x.Name,
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string buildingId, [FromQuery] string floorId) {
+            var floor = _service.GetAll().Where(x => x.SiteId == Util.ToInt(siteId)
+                && x.BuildingId == Util.ToInt(buildingId)
+                && x.FloorId == Util.ToInt(floorId)).FirstOrDefault();
+
+            return Ok(new {
+                floor.SiteId,
+                floor.BuildingId,
+                BuildingName = floor.CmBuilding?.Name ?? "",
+                floor.FloorId,
+                floor.Name,
+            });
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmFloor> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 260 - 0
Controllers/App/FormulaControllers.cs

@@ -0,0 +1,260 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Text.RegularExpressions;
+using System.Collections;
+using System.Collections.Generic;
+using FMSAdmin.Models.Formula;
+using FMSAdmin.Helpers.Formula;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class FormulaController : Controller {
+
+        private static Regex regexParameter = new Regex(@"\b[A-Z]\b", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
+        private static Regex regexFunction = new Regex(@"\$([\w]+).+\$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
+        private static FormulaTableManager tableManager;
+        private readonly ILogger<FormulaController> _logger;
+        private readonly FormulaService _service;
+        private readonly FMSContext _context;
+        public FormulaController(
+            ILogger<FormulaController> logger,
+            FormulaService service,
+            FMSContext context
+            ) {
+            _logger = logger;
+            _service = service;
+            _context = context;
+            tableManager = new FormulaTableManager(context);
+        }
+
+        /// <summary>
+        // 성능분석-챠트에 사용되는듯
+        ///</summary>
+        [HttpGet("[action]")]
+        public IActionResult Calculation([FromQuery] int siteId, [FromQuery] int facilityTypeId, [FromQuery] int formulaId, [FromQuery] int facilityCode, [FromQuery] int timeIntervalType, [FromQuery] string sDate, [FromQuery] string eDate) {
+            DateTime startDate = DateTime.Parse(sDate);
+            DateTime endDate = DateTime.Parse(eDate);
+
+            List<CalculationResult> list = new List<CalculationResult>();
+
+            BemsFormula formula = GetFormula(Util.ToInt(siteId), Util.ToInt(facilityTypeId), Util.ToInt(facilityCode), Util.ToInt(formulaId));
+            if (formula == null) return Ok(new { });
+
+            TimeInterval timeInterval = (TimeInterval)timeIntervalType;
+
+            ArrayList parameters = new ArrayList();
+
+            MathCalculator calculator = new MathCalculator();
+
+            if (false == calculator.Compile(formula.Formula)) {
+                throw new Exception("Failed to compile formula: " + formula.Formula);
+            }
+            var match = regexParameter.Match(formula.Formula);
+            if (match.Success) {
+                var queryParameters = _context.BemsFormulaParameter.Where(x =>
+                    x.SiteId == siteId &&
+                    x.FacilityTypeId == facilityTypeId &&
+                    (facilityTypeId >= 100 || x.FacilityCode == facilityCode) &&
+                    x.FormulaId == formulaId);
+
+                if (queryParameters == null || queryParameters.Any() == false) {
+                    throw new Exception(string.Format("Not Found Formula Parameters: {0},{1},{2},{3}", siteId, facilityTypeId, facilityCode, formulaId));
+                }
+                List<MathParameterValue> parametersForArray = new List<MathParameterValue>();
+                var test = queryParameters.ToList();
+                foreach (var p in queryParameters.ToList()) {
+                    char parameter = p.ParameterId[0];
+                    var pValue = new MathParameterValue {
+                        Variable = (MathCalculator.Parameter)(parameter - 'A'),
+                        Value = 0,
+                    };
+                    parametersForArray.Add(pValue);
+
+                    var values = CreateCalculationValues(startDate, endDate, timeInterval);
+
+                    var pointvalues = _service.GetPointValues(siteId, p.ParameterFacilityCode,
+                        p.ParameterPropertyId, timeInterval, startDate, endDate);
+
+                    if (pointvalues == null)
+                        continue;
+
+
+                    CalculationValue pp = null;
+                    CalculationValue v = null;
+                    for (var i = 0; i < values.Length; i++) {
+                        v = values[i];
+                        for (var j = 0; j < pointvalues.Length; j++) {
+                            pp = pointvalues[j];
+                            if (pp.DateTime == v.DateTime) {
+                                v.Value += pp.Value;
+                            }
+                        }
+                    }
+
+                    parameters.Add(new CalculationParameter {
+                        ParameterValue = pValue,
+                        Values = values
+                    });
+                }
+                calculator.SetParameter(parametersForArray.ToArray());
+            }
+            {
+                var functions = calculator.GetFunctions();
+                if (functions.Any()) {
+                    foreach (var f in functions) {
+                        var tableValues = tableManager.GetTableValues(f.Function.FunctionName);
+                        if (tableValues == null) {
+                            throw new Exception("Not Found Function.");
+                        }
+
+                        f.Function.FunctionDelegate = (x) => {
+                            var index = Array.BinarySearch(tableValues, new FormulaTableManager.TableComparerValue { XValue = (double)x });
+                            if (index < 0) {
+                                index = ~index;
+                                if (index == 0) {
+                                    return Math.Round((decimal)tableValues[0].YValue, 2);
+                                } else if (index == tableValues.Length) {
+                                    return Math.Round((decimal)tableValues[tableValues.Length - 1].YValue, 2);
+                                }
+                            }
+                            return Math.Round((decimal)tableValues[index].YValue, 2);
+                        };
+                    }
+                }
+            }
+            DateTime dateTime = DateTime.Now;
+            if (parameters.Count > 0) {
+                var ps = parameters.Cast<CalculationParameter>().ToArray();
+                ParameterBox parameterBox = new ParameterBox(ps);
+
+                while (parameterBox.Sync()) {
+                    var value = calculator.Calculate();
+                    if (value != null) {
+                        list.Add(new CalculationResult {
+                            DateTime = parameterBox.DateTime,
+                            ShortDateTime = timeInterval == TimeInterval.Day ? parameterBox.DateTime.ToString("yyyy.MM.dd") : parameterBox.DateTime.ToString("yyyy.MM"),
+                            Value = Math.Round((double)value, 2)
+                            //Value = (double)value
+                        });
+                    }
+                    if (parameterBox.NextStage() == false) {
+                        break;
+                    }
+                }
+            } else {
+                IList emptyArr = new List<CalculationResult>();
+                return Ok(new {
+                    total = 0,
+                    page = 1,
+                    limit = 0,
+                    list = emptyArr,
+                    maxValue = 0
+                });
+            }
+
+            //list.Select(x=>x.Value).Max();
+            return Ok(new {
+                total = list.Count(),
+                page = 1,
+                limit = 0,
+                list = list,
+                maxValue = list.Select(x => x.Value).Max()
+            });
+
+        }
+
+        public BemsFormula GetFormula(int siteId, int facilityTypeId, int facilityCode, int formulaId) {
+            var queryFormula = from f in _context.BemsFormula
+                               where f.SiteId == siteId && f.FacilityTypeId == facilityTypeId &&
+                                   (facilityTypeId >= 100 || f.FacilityCode == facilityCode) && f.FormulaId == formulaId
+                               select f;
+            if (queryFormula == null || queryFormula.Any() == false) {
+                return null;
+            }
+            return queryFormula.Single();
+        }
+        private CalculationValue[] CreateCalculationValues(DateTime startDate, DateTime endDate, TimeInterval timeIntervalType) {
+            DateTime datetime = startDate;
+            int count = GetCountInTimeRange(startDate, endDate, timeIntervalType);
+            CalculationValue[] values = new CalculationValue[count];
+
+            for (var i = 0; i < values.Length; i++) {
+                values[i] = new CalculationValue {
+                    DateTime = datetime,
+                    Value = 0
+                };
+                NextTimeFromTimeInterval(ref datetime, timeIntervalType);
+            }
+            return values;
+        }
+        private int GetCountInTimeRange(DateTime startDate, DateTime endDate, TimeInterval timeIntervalType) {
+            TimeSpan ts = endDate - startDate;
+            switch (timeIntervalType) {
+                case TimeInterval.QuarterMin:
+                    return (int)ts.TotalMinutes / 15 + 1;
+                case TimeInterval.Hour:
+                    return (int)ts.TotalHours + 1;
+                case TimeInterval.Day:
+                    return (int)ts.TotalDays + 1;
+                case TimeInterval.Month: {
+                        int count = 0;
+                        DateTime date = startDate;
+                        //while (date.Year <= endDate.Year && date.Month <= endDate.Month)
+                        while (date <= endDate) {
+                            count++;
+                            date = date.AddMonths(1);
+                        }
+                        return count;
+                    }
+                default: // TimeInterval.Year:
+                    {
+                        int count = 0;
+                        DateTime date = startDate;
+                        while (date.Year <= endDate.Year) {
+                            count++;
+                            date = date.AddYears(1);
+                        }
+                        return count;
+                    }
+            }
+
+        }
+        private void NextTimeFromTimeInterval(ref DateTime datetime, TimeInterval timeIntervalType) {
+            switch (timeIntervalType) {
+                case TimeInterval.QuarterMin:
+                    datetime = datetime.AddMinutes(15);
+                    break;
+                case TimeInterval.Hour:
+                    datetime = datetime.AddHours(1);
+                    break;
+                case TimeInterval.Day:
+                    datetime = datetime.AddDays(1);
+                    break;
+                case TimeInterval.Month:
+                    datetime = datetime.AddMonths(1);
+                    break;
+                default: // TimeInterval.Year:
+                    datetime = datetime.AddYears(1);
+                    break;
+            }
+        }
+    }
+
+}

+ 160 - 0
Controllers/App/GuidelineController.cs

@@ -0,0 +1,160 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using LinqKit;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class GuidelineController : Controller {
+        private readonly ILogger<GuidelineController> _logger;
+        private readonly FMSContext _context;
+        private readonly GuidelineService _service;
+        private readonly SiteService _siteService;
+        private readonly StorageHelper _storage;
+
+        public GuidelineController(
+            ILogger<GuidelineController> logger,
+            FMSContext context,
+            GuidelineService service,
+            SiteService siteService,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _siteService = siteService;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            var user = _context.CmUser.FirstOrDefault(x => x.UserId == User.Identity.Name);
+            var query = _FilterAndSort(req);
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId) || x.IsAllView == true);
+            }
+            //query = query.Where(x => x.AddDate < DateTime.Now.AddDays(1).Date && x.UpdateDate >= DateTime.Now.Date);
+            if (businessAuth && user != null && user.BusinessFieldId != null) {
+                var businessCommon = _context.CmBusinessField.FirstOrDefault(x => x.Name == "공통");
+                query = query.Where(x => (new int[] { businessCommon.BusinessFieldId, user.BusinessFieldId.Value }).Contains(x.BusinessFieldId) || x.IsAllView == true);
+            }
+
+            var list = query.Select(x => new {
+                x.SiteId,
+                Name = Util.ToBool(x.CmUser.IsSysop) ? "운영자" : (Util.ToBool(x.IsAllView) ? "본사지침" : x.CmSite.Name),
+                x.Title,
+                x.GuidelineId,
+                // CmSite = new {
+                //     Name = x.CmSite.Name// _siteService.Get(x.SiteId).FirstOrDefault().Name
+                // },
+                CmUser = new {
+                    Name = x.CmUser.Name
+                },
+                CmBusinessField = new {
+                    x.CmBusinessField.Name,
+                },
+                Date = new {
+                    AddDate = x.AddDate.ToString("yyyy.MM.dd")
+                },
+                IsAllView = Util.ToBool(x.IsAllView),
+                IsSysop = x.CmUser.IsSysop,
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string guidelineId) {
+            var query = _service.GetAll()
+                .Where(x => x.GuidelineId == Util.ToInt(guidelineId));
+
+            // 조회수 증가
+            var guideline = query.FirstOrDefault();
+            if (guideline != null && User.Identity.Name != guideline.RegisterUserId) {
+                _service.AddCount(Util.ToInt(guidelineId));
+            }
+
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.Title,
+                x.Contents,
+                x.CmUser.Name,
+                x.AddDate,
+                x.UpdateDate,
+                x.BusinessFieldId,
+                x.RegisterUserId,
+                CmFile = _storage.ParseFile(x.CmFile),
+                CmBusinessField = new {
+                    BusinessFieldId = x.CmBusinessField.BusinessFieldId,
+                    Name = x.CmBusinessField.Name
+                },
+                CmUser = new {
+                    UserId = x.CmUser.UserId
+                },
+                x.GuidelineId,
+                x.ReadCount,
+                x.IsUse,
+                IsAllView = Util.ToBool(x.IsAllView)
+            }).FirstOrDefault();
+
+            return Ok(item);
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmGuideline> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] CmGuideline data) {
+
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpDelete("{siteId}/{guidelineId}")]
+        public IActionResult Delete(int siteId, int guidelineId) {
+            _service.Delete(siteId, guidelineId);
+            return Ok();
+        }
+
+    }
+}

+ 27 - 0
Controllers/App/HomeController.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using FMSAdmin.Models;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authorization;
+
+namespace FMSApp.Controllers {
+    public class HomeController : Controller {
+        private readonly ILogger<HomeController> _logger;
+
+        public HomeController(ILogger<HomeController> logger) {
+            _logger = logger;
+        }
+
+        public IActionResult Index() {
+            return Content("api server");
+        }
+
+    }
+}

+ 126 - 0
Controllers/App/ManualController.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class ManualController : Controller {
+        private readonly ILogger<ManualController> _logger;
+        private readonly FMSContext _context;
+        private readonly ManualService _service;
+        private readonly StorageHelper _storage;
+
+        public ManualController(
+            ILogger<ManualController> logger,
+            FMSContext context,
+            ManualService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.ManualId,
+                x.ManualNo,
+                x.ManualTypeId,    // 매뉴얼분류
+                FmsManualType = new { //매뉴얼분류명
+                    x.FmsManualType.Name
+                },
+                x.ManualGroupId,
+                FmsManualGroup = new {
+                    x.FmsManualGroup.Name
+                },
+                x.Name,
+                x.Description,
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string ManualId) {
+            var query = _service.GetAll()
+                .Where(x => x.SiteId == Util.ToInt(siteId) && x.ManualId == Util.ToInt(ManualId));
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.ManualId,
+                x.ManualNo,
+                x.ManualTypeId,
+                ManualTypeName = x.FmsManualType.Name,
+                x.ManualGroupId,
+                ManualGroupName = x.FmsManualGroup.Name,
+                x.Name,
+                x.Description,
+            }).FirstOrDefault();
+            return Ok(item);
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] FmsManual data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                data.CreateUserId = User.Identity.Name;
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpDelete("{siteId}/{manualId}")]
+        public IActionResult Delete(int siteId, int manualId) {
+            _service.Delete(siteId, manualId);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsManual> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 131 - 0
Controllers/App/ManualHistoryController.cs

@@ -0,0 +1,131 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class ManualHistoryController : Controller {
+        private readonly ILogger<ManualHistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly ManualHistoryService _service;
+        private readonly StorageHelper _storage;
+
+        public ManualHistoryController(
+            ILogger<ManualHistoryController> logger,
+            FMSContext context,
+            ManualHistoryService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] string ManualId) {
+            var query = _FilterAndSort(req);
+            query = query.Where(x => x.SiteId == Util.ToInt(siteId) && x.ManualId == Util.ToInt(ManualId));
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.ManualId,
+                x.HistoryId,
+                x.RevisionNo,
+                x.FileId,
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.Description,
+                x.UpdatedDate,
+                x.UpdatedUserId,
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string ManualId, [FromQuery] string historyId, [FromQuery] string userId, [FromQuery] string isCreate) {
+            if (Util.ToBool(isCreate)) {
+                var query = _service.GetAll()
+                    .Where(x => x.SiteId == Util.ToInt(siteId) && x.ManualId == Util.ToInt(ManualId)).OrderByDescending(x => x.HistoryId);
+                var item = query.Select(x => new {
+                    x.SiteId,
+                    RevisionNo = Util.ToInt(x.RevisionNo) + 1
+                }).FirstOrDefault();
+                return Ok(item);
+            } else {
+                var query = _service.GetAll()
+                    .Where(x => x.SiteId == Util.ToInt(siteId) && x.ManualId == Util.ToInt(ManualId) && x.HistoryId == Util.ToInt(historyId));
+                var item = query.Select(x => new {
+                    x.SiteId,
+                    x.ManualId,
+                    x.HistoryId,
+                    x.RevisionNo,
+                    CmFile = _storage.ParseFile(x.CmFile),
+                    x.Description,
+                    x.UpdatedDate,
+                    x.UpdatedUserId,
+                    CmUser = new {
+                        Name = x.CmUser.Name
+                    }
+                }).FirstOrDefault();
+                return Ok(item);
+            }
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] FmsManualHistory data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpDelete("{siteId}/{manualId}/{historyId}")]
+        public IActionResult Delete(int siteId, int manualId, int historyId) {
+            _service.Delete(siteId, manualId, historyId);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsManualHistory> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 309 - 0
Controllers/App/ManualMeterController.cs

@@ -0,0 +1,309 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Collections.Generic;
+using System.Dynamic;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class ManualMeterController : Controller {
+        private readonly ILogger<ManualMeterController> _logger;
+        private readonly FMSContext _context;
+        private readonly ManualMeterService _service;
+        private readonly StorageHelper _storage;
+
+        public ManualMeterController(
+            ILogger<ManualMeterController> logger,
+            FMSContext context,
+            ManualMeterService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = from facility in _context.CmFacility
+                        join point in _context.BemsMonitoringPoint on new { facility.SiteId, facility.FacilityCode } equals new { point.SiteId, point.FacilityCode } //into _point
+                        //from point in _point.DefaultIfEmpty()
+                        where facility.SiteId == Util.ToInt(siteId)
+                        orderby facility.FacilityCode
+                        select new {
+                            facility.SiteId,
+                            facility.FacilityTypeId,
+                            FacilityTypeName = facility.BemsFacilityType.Name,
+                            facility.FacilityCode,
+                            facility.BuildingId,
+                            BuildingName = facility.CmBuilding.Name,
+                            facility.FloorId,
+                            FloorName = facility.CmFloor.Name,
+                            facility.ZoneId,
+                            ZoneName = facility.CmZone.Name,
+                            facility.FirstClassId,
+                            FirstClassName = facility.FmsFacilityCodeClass.Name,
+                            facility.SecondClassId,
+                            SecondClassName = facility.FmsFacilityCodeClass1.Name,
+                            facility.ThirdClassId,
+                            ThirdClassName = facility.FmsFacilityCodeClass2.Name,
+                            facility.Name,
+                            facility.Nickname,
+                            facility.Manufacturer,
+                            facility.FacilityCapacity,
+                            facility.Rfid,
+                            point.PropertyId,
+                            MonitoringPointName = point.Name,
+                        };
+
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            var paging = PagingList.Pagination(query, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string facilityCode, [FromQuery] string propertyId, [FromQuery] DateTime date) {
+            var now = DateTime.Now;
+            var time = new DateTime(date.Year, date.Month, date.Day, now.Hour, 0, 0);
+
+            var query = from facility in _context.CmFacility
+                        join point in _context.BemsMonitoringPoint on new { facility.SiteId, facility.FacilityCode } equals new { point.SiteId, point.FacilityCode } //into _point
+                        select new {
+                            facility.SiteId,
+                            facility.FacilityTypeId,
+                            FacilityTypeName = facility.BemsFacilityType.Name,
+                            facility.FacilityCode,
+                            facility.BuildingId,
+                            BuildingName = facility.CmBuilding.Name ?? "",
+                            facility.FloorId,
+                            FloorName = facility.CmFloor.Name ?? "",
+                            facility.ZoneId,
+                            ZoneName = facility.CmZone.Name ?? "",
+                            facility.FirstClassId,
+                            FirstClassName = facility.FmsFacilityCodeClass.Name ?? "",
+                            facility.SecondClassId,
+                            SecondClassName = facility.FmsFacilityCodeClass1.Name ?? "",
+                            facility.ThirdClassId,
+                            ThirdClassName = facility.FmsFacilityCodeClass2.Name ?? "",
+                            facility.Name,
+                            facility.Nickname,
+                            facility.Manufacturer,
+                            facility.FacilityCapacity,
+                            facility.Rfid,
+                            point.PropertyId,
+                            MonitoringPointName = point.Name,
+                            Hour = time.Hour,
+                            facility.Multiple,
+                        };
+
+            var item = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.FacilityCode == Util.ToInt(facilityCode)
+                && x.PropertyId == Util.ToInt(propertyId)
+            ).FirstOrDefault();
+
+            var expansion = new BemsMonitoringPointHistoryHourly() {
+                PrevPinValue = (_context.BemsMonitoringPointHistory15minRawData.Where(
+                    x => x.SiteId == Util.ToInt(siteId)
+                    && x.FacilityCode == Util.ToInt(facilityCode)
+                    && x.PropertyId == Util.ToInt(propertyId)
+                    && x.CreatedDateTime < time
+                ).OrderByDescending(x => x.CreatedDateTime).FirstOrDefault() ?? new BemsMonitoringPointHistory15minRawData() { CurrentValue = 0 }).CurrentValue,
+                CurrentPinValue = (_context.BemsMonitoringPointHistory15minRawData.Where(
+                    x => x.SiteId == Util.ToInt(siteId)
+                    && x.FacilityCode == Util.ToInt(facilityCode)
+                    && x.PropertyId == Util.ToInt(propertyId)
+                    && x.CreatedDateTime == time
+                ).FirstOrDefault() ?? new BemsMonitoringPointHistory15minRawData() { CurrentValue = 0 }).CurrentValue,
+                CurrentValue = (_context.BemsMonitoringPointHistoryHourly.Where(
+                    x => x.SiteId == Util.ToInt(siteId)
+                    && x.FacilityCode == Util.ToInt(facilityCode)
+                    && x.PropertyId == Util.ToInt(propertyId)
+                    && x.CreatedDateTime == time
+                ).FirstOrDefault() ?? new BemsMonitoringPointHistoryHourly() { CurrentValue = 0 }).CurrentValue,
+            };
+
+            // 사용량 이나 검침값이 있는데 이전검침값이 없는경우
+            // 이전검침값을 계산해서 넣어준다.
+            if ((Math.Abs(expansion.CurrentValue) > Double.Epsilon || Math.Abs(expansion.CurrentPinValue) > Double.Epsilon)
+                && Math.Abs(expansion.PrevPinValue) <= Double.Epsilon) {
+                // 이전검침값 : 검침값 - (사용량 / 배수)
+                expansion.PrevPinValue = expansion.CurrentPinValue - (expansion.CurrentValue / item.Multiple);
+            }
+
+            return Ok(new {
+                Item = item,
+                Expansion = new {
+                    expansion.PrevPinValue,
+                    expansion.CurrentPinValue,
+                    expansion.CurrentValue,
+                }
+            });
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult GetCurrentValue([FromQuery] string siteId, [FromQuery] string facilityCode, [FromQuery] string propertyId, [FromQuery] DateTime date) {
+            var time = new DateTime(date.Year, date.Month, date.Day, date.Hour, 0, 0);
+
+            var facility = _context.CmFacility.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.FacilityCode == Util.ToInt(facilityCode)
+            ).FirstOrDefault();
+            if (facility == null) throw new ServiceException("존재하지 않는 설비 입니다.");
+
+            var currentRawData = _context.BemsMonitoringPointHistory15minRawData.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.FacilityCode == Util.ToInt(facilityCode)
+                && x.PropertyId == Util.ToInt(propertyId)
+                && x.CreatedDateTime == time
+            ).FirstOrDefault();
+            if (currentRawData == null) {
+                currentRawData = new BemsMonitoringPointHistory15minRawData() {
+                    CurrentValue = 0,
+                };
+            }
+
+            var prevRawData = _context.BemsMonitoringPointHistory15minRawData.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.FacilityCode == Util.ToInt(facilityCode)
+                && x.PropertyId == Util.ToInt(propertyId)
+                && x.CreatedDateTime < time
+            ).OrderByDescending(x => x.CreatedDateTime).FirstOrDefault();
+            if (prevRawData == null) {
+                prevRawData = new BemsMonitoringPointHistory15minRawData() {
+                    CurrentValue = 0,
+                };
+            }
+
+            var hourly = _context.BemsMonitoringPointHistoryHourly.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.FacilityCode == Util.ToInt(facilityCode)
+                && x.PropertyId == Util.ToInt(propertyId)
+                && x.CreatedDateTime == time
+            ).FirstOrDefault();
+            if (hourly == null) {
+                hourly = new BemsMonitoringPointHistoryHourly() {
+                    CreatedDateTime = time,
+                    CurrentValue = 0,
+                };
+            }
+
+            // 사용량 이나 검침값이 있는데 이전검침값이 없는경우
+            // 이전검침값을 계산해서 넣어준다.
+            if ((Math.Abs(hourly.CurrentValue) > Double.Epsilon || Math.Abs(currentRawData.CurrentValue) > Double.Epsilon)
+                && Math.Abs(prevRawData.CurrentValue) <= Double.Epsilon) {
+                // 이전검침값 : 검침값 - (사용량 / 배수)
+                prevRawData.CurrentValue = currentRawData.CurrentValue - (hourly.CurrentValue / facility.Multiple);
+            }
+
+            return Ok(new {
+                Hour = hourly.CreatedDateTime.Hour,
+                PrevPinValue = prevRawData.CurrentValue,
+                CurrentPinValue = currentRawData.CurrentValue,
+                CurrentValue = hourly.CurrentValue,
+            });
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult GetLastMeteredHour([FromQuery] string siteId, [FromQuery] string facilityCode, [FromQuery] string propertyId, [FromQuery] DateTime date) {
+            var time = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0);
+            int? hour = null;
+
+            var hourly = _context.BemsMonitoringPointHistoryHourly.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.FacilityCode == Util.ToInt(facilityCode)
+                && x.PropertyId == Util.ToInt(propertyId)
+                && x.CreatedDateTime.Date == time
+            ).OrderByDescending(x => x.CreatedDateTime).FirstOrDefault();
+            if (hourly != null) {
+                hour = hourly.CreatedDateTime.Hour;
+            }
+
+            return Ok(new {
+                Hour = hour,
+            });
+        }
+
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] BemsMonitoringPointHistoryHourly data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+
+        [HttpGet("[action]")]
+        public IActionResult MeteredList([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] string facilityCode, [FromQuery] string propertyId, [FromQuery] DateTime date, [FromQuery] string period) {
+            var startDate = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0);
+            var endDate = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0);
+            startDate = startDate.AddDays(Util.ToInt(period) * -1);
+            endDate = endDate.AddDays(1);
+
+            var query = _context.BemsMonitoringPointHistoryHourly.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.FacilityCode == Util.ToInt(facilityCode)
+                && x.PropertyId == Util.ToInt(propertyId)
+                && x.CreatedDateTime >= startDate
+                && x.CreatedDateTime < endDate
+            );
+            query = query.Filter(req.conditions);
+
+            var list = query.Select(x => new {
+                x.CreatedDateTime,
+                x.CurrentValue,
+            }).ToList();
+
+            return Ok(list);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult ReportList([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] string facilityCode, [FromQuery] DateTime date, [FromQuery] string period) {
+            var startDate = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0);
+            var endDate = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0);
+            startDate = startDate.AddDays(Util.ToInt(period) * -1);
+            endDate = endDate.AddDays(1);
+
+            var query = from report in _context.BemsReportHistory
+                        join format in _context.BemsReportFormat on new { report.SiteId, report.ReportFormatId } equals new { format.SiteId, format.ReportFormatId }
+                        join ff in _context.BemsReportFormatToFacility on new { format.SiteId, format.ReportFormatId } equals new { ff.SiteId, ff.ReportFormatId }
+                        join facility in _context.CmFacility on new { ff.SiteId, ff.FacilityCode } equals new { facility.SiteId, facility.FacilityCode }
+                        where facility.SiteId == Util.ToInt(siteId)
+                            && facility.FacilityCode == Util.ToInt(facilityCode)
+                            && report.CreateDate >= startDate && report.CreateDate < endDate
+                        select report;
+
+            var list = query.Select(x => new {
+                x.ReportName,
+                x.Writer,
+                x.CreateDate,
+            }).ToList();
+
+            return Ok(list);
+        }
+
+    }
+}

+ 255 - 0
Controllers/App/MaterialController.cs

@@ -0,0 +1,255 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class MaterialController : Controller {
+        private readonly ILogger<MaterialController> _logger;
+        private readonly FMSContext _context;
+        private readonly MaterialService _service;
+        private readonly StorageHelper _storage;
+
+        public MaterialController(
+            ILogger<MaterialController> logger,
+            FMSContext context,
+            MaterialService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, bool businessAuth) {
+            var query = _FilterAndSort(req);
+
+            query = BusinessFieldHelper.Apply<MaterialStock>(_context, User, businessAuth, query, "Material");
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("자재정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.MaterialId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.BusinessFieldId,
+                CmBusinessField = new {
+                    x.CmBusinessField.Name,
+                },
+                x.MaterialTypeId,
+                FmsMaterialCodeType = new {
+                    x.FmsMaterialCodeType.Name,
+                },
+                x.FirstClassId,
+                FmsMaterialCodeClass = new {
+                    x.FmsMaterialCodeClass.MaterialClassId,
+                    x.FmsMaterialCodeClass.Name,
+                },
+                x.SecondClassId,
+                FmsMaterialCodeClass1 = new {
+                    x.FmsMaterialCodeClass1.MaterialClassId,
+                    x.FmsMaterialCodeClass1.Name,
+                },
+                x.ThirdClassId,
+                FmsMaterialCodeClass2 = new {
+                    x.FmsMaterialCodeClass2.MaterialClassId,
+                    x.FmsMaterialCodeClass2.Name,
+                },
+                x.MaterialCode,
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.Name,
+                x.Standard,
+                x.Unit,
+                x.Rfid,
+                x.Manufacturer,
+                x.DurableYears,
+                x.FinalPrice,
+                x.ReasonableStockCount,
+                x.IsDiscontinued,
+                x.IsUse,
+                x.Note,
+            });
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] FmsMaterial data) {
+            if (id != data.MaterialId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsMaterial data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<MaterialStock> query) {
+            var list = query.Select(x => new {
+                x.Material.MaterialId,
+                x.Material.MaterialCode,
+                x.Material.SiteId,
+                SiteName = x.Material.CmSite.Name,
+                x.Material.BusinessFieldId,
+                BusinessFieldName = x.Material.CmBusinessField.Name,
+                x.Material.FirstClassId,
+                FirstClassName = x.Material.FmsMaterialCodeClass.Name,
+                x.Material.SecondClassId,
+                SecondClassName = x.Material.FmsMaterialCodeClass1.Name,
+                x.Material.ThirdClassId,
+                ThirdClassName = x.Material.FmsMaterialCodeClass2.Name,
+                x.Material.Name,
+                x.Material.Standard,
+                x.Material.Unit,
+                x.Material.FinalPrice,
+                x.Material.ReasonableStockCount,
+                x.Material.IsDiscontinued,
+                x.Material.IsUse,
+                x.StockCount,
+                x.StockAmount,
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<MaterialStock> _FilterAndSort(PagingRequest req) {
+            var query = _service.Stock();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.Material.SiteId == Util.ToInt(req.siteId));
+            }
+            // 업무분야 필드가 있는경우만 추가
+            if (req.businessFieldId != null) {
+                query = query.Where(x => new int[] { req.businessFieldId.Value, 1 }.Contains(x.Material.BusinessFieldId));
+            }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 197 - 0
Controllers/App/MaterialDailyStockController.cs

@@ -0,0 +1,197 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class MaterialDailyStockController : Controller {
+        private readonly ILogger<MaterialDailyStockController> _logger;
+        private readonly FMSContext _context;
+        private readonly MaterialService _service;
+
+        public MaterialDailyStockController(
+            ILogger<MaterialDailyStockController> logger,
+            FMSContext context,
+            MaterialService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery]PagingRequest req, bool businessAuth) {
+            var query = _FilterAndSort(req);
+
+            query = BusinessFieldHelper.Apply<MaterialStockHistory>(_context, User, businessAuth, query, "Material");
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery]PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("자재정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.MaterialId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.BusinessFieldId,
+                CmBusinessField = new {
+                    x.CmBusinessField.Name,
+                },
+                x.MaterialTypeId,
+                FmsMaterialCodeType = new {
+                    x.FmsMaterialCodeType.Name,
+                },
+                x.FirstClassId,
+                FmsMaterialCodeClass = new {
+                    x.FmsMaterialCodeClass.MaterialClassId,
+                    x.FmsMaterialCodeClass.Name,
+                },
+                x.SecondClassId,
+                FmsMaterialCodeClass1 = new {
+                    x.FmsMaterialCodeClass1.MaterialClassId,
+                    x.FmsMaterialCodeClass1.Name,
+                },
+                x.ThirdClassId,
+                FmsMaterialCodeClass2 = new {
+                    x.FmsMaterialCodeClass2.MaterialClassId,
+                    x.FmsMaterialCodeClass2.Name,
+                },
+                x.MaterialCode,
+                x.Name,
+                x.Standard,
+                x.Unit,
+                x.Rfid,
+                x.Manufacturer,
+                x.DurableYears,
+                x.FinalPrice,
+                x.ReasonableStockCount,
+                x.IsDiscontinued,
+                x.IsUse,
+                x.Note,
+            });
+
+            return Ok(item.First());
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<MaterialStockHistory> query) {
+            var list = query.Select(x => new {
+                x.Material.MaterialId,
+                SiteName = x.Material.CmSite.Name,
+                x.Material.BusinessFieldId,
+                BusinessFieldName = x.Material.CmBusinessField.Name,
+                x.Material.FirstClassId,
+                FirstClassName = x.Material.FmsMaterialCodeClass.Name,
+                x.Material.SecondClassId,
+                SecondClassName = x.Material.FmsMaterialCodeClass1.Name,
+                x.Material.ThirdClassId,
+                ThirdClassName = x.Material.FmsMaterialCodeClass2.Name,
+                x.Date,
+                x.Material.Name,
+                x.Material.Standard,
+                x.Material.Unit,
+                x.StockCount,
+                x.StockAmount,
+                x.StoredCount,
+                x.ReleaseCount,
+                x.ReleaseLossCount,
+                x.ReleaseReturnCount,
+                x.LastStockCount,
+                x.LastStockDate,
+            });
+
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<MaterialStockHistory> _FilterAndSort(PagingRequest req) {
+            var query = _service.DailyStock();
+
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.Material.SiteId == Util.ToInt(req.siteId));
+            }
+
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+
+}
+

+ 196 - 0
Controllers/App/MaterialMonthlyStockController.cs

@@ -0,0 +1,196 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class MaterialMonthlyStockController : Controller {
+        private readonly ILogger<MaterialMonthlyStockController> _logger;
+        private readonly FMSContext _context;
+        private readonly MaterialService _service;
+
+        public MaterialMonthlyStockController(
+            ILogger<MaterialMonthlyStockController> logger,
+            FMSContext context,
+            MaterialService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery]PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery]PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("자재정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.MaterialId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.BusinessFieldId,
+                CmBusinessField = new {
+                    x.CmBusinessField.Name,
+                },
+                x.MaterialTypeId,
+                FmsMaterialCodeType = new {
+                    x.FmsMaterialCodeType.Name,
+                },
+                x.FirstClassId,
+                FmsMaterialCodeClass = new {
+                    x.FmsMaterialCodeClass.MaterialClassId,
+                    x.FmsMaterialCodeClass.Name,
+                },
+                x.SecondClassId,
+                FmsMaterialCodeClass1 = new {
+                    x.FmsMaterialCodeClass1.MaterialClassId,
+                    x.FmsMaterialCodeClass1.Name,
+                },
+                x.ThirdClassId,
+                FmsMaterialCodeClass2 = new {
+                    x.FmsMaterialCodeClass2.MaterialClassId,
+                    x.FmsMaterialCodeClass2.Name,
+                },
+                x.MaterialCode,
+                x.Name,
+                x.Standard,
+                x.Unit,
+                x.Rfid,
+                x.Manufacturer,
+                x.DurableYears,
+                x.FinalPrice,
+                x.ReasonableStockCount,
+                x.IsDiscontinued,
+                x.IsUse,
+                x.Note,
+            });
+
+            return Ok(item.First());
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<MaterialStockHistory> query) {
+            var list = query.Select(x => new {
+                x.Material.MaterialId,
+                SiteName = x.Material.CmSite.Name,
+                x.Material.BusinessFieldId,
+                BusinessFieldName = x.Material.CmBusinessField.Name,
+                x.Material.FirstClassId,
+                FirstClassName = x.Material.FmsMaterialCodeClass.Name,
+                x.Material.SecondClassId,
+                SecondClassName = x.Material.FmsMaterialCodeClass1.Name,
+                x.Material.ThirdClassId,
+                ThirdClassName = x.Material.FmsMaterialCodeClass2.Name,
+                x.Date,
+                x.Year,
+                x.Month,
+                x.Material.Name,
+                x.Material.Standard,
+                x.Material.Unit,
+                x.StockCount,
+                x.StockAmount,
+                x.StoredCount,
+                x.ReleaseCount,
+                x.ReleaseLossCount,
+                x.ReleaseReturnCount,
+                x.LastStockCount,
+                x.LastStockDate,
+            });
+
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<MaterialStockHistory> _FilterAndSort(PagingRequest req) {
+            var query = _service.MonthlyStock();
+
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.Material.SiteId == Util.ToInt(req.siteId));
+            }
+
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}
+

+ 173 - 0
Controllers/App/MobileShortcutController.cs

@@ -0,0 +1,173 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Collections.Generic;
+using System.Linq.Dynamic.Core;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class MobileShortcutController : Controller {
+        private readonly ILogger<MobileShortcutController> _logger;
+        private readonly FMSContext _context;
+        private readonly MobileShortcutService _service;
+
+        public MobileShortcutController(
+            ILogger<MobileShortcutController> logger,
+            FMSContext context,
+            MobileShortcutService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult DefaultDashboardList() {
+            return Ok(List(null, ShortcutType.대시보드메뉴, false));
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult DefaultBottomList() {
+            return Ok(List(null, ShortcutType.하단메뉴, false));
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult DashboardList([FromQuery] string userId) {
+            var list = List(userId, ShortcutType.대시보드메뉴);
+            if (list.Count() == 0) {
+                return DefaultDashboardList();
+            } else {
+                return Ok(RemoveDeleted(list));
+            }
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult BottomList([FromQuery] string userId) {
+            var list = List(userId, ShortcutType.하단메뉴);
+            _logger.LogInformation($"BottomList - list.Count = {list.Count()}");
+            if (list.Count() == 0) {
+                return DefaultBottomList();
+            } else {
+                return Ok(RemoveDeleted(list));
+            }
+        }
+
+        private IQueryable<object> RemoveDeleted(IQueryable<object> list) {
+            return list.Where("IsDelete == false");
+        }
+
+        private IQueryable<object> List(string userId, ShortcutType type, bool useDeleted = true) {
+            var query = _service.GetAll();
+
+            query = query.Where(x => x.Type == type);
+            if (string.IsNullOrEmpty(userId)) {
+                query = query.Where(x => x.UserId == null || x.UserId == "");
+            } else {
+                query = query.Where(x => x.UserId == userId);
+            }
+            if (!useDeleted) {
+                query = query.Where(x => x.IsDelete == false);
+            }
+
+            query = query.OrderBy(x => x.Position);
+
+            var list = query.Select(x => new {
+                x.MobileShortcutId,
+                x.UserId,
+                UserName = x.CmUser.Name,
+                x.SitemapId,
+                Sitemap = new {
+                    x.Sitemap.Name,
+                    x.Sitemap.Path,
+                },
+                x.Type,
+                x.Icon,
+                x.IsDelete,
+            });
+            return list;
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string shortcutId) {
+            var shortcut = _service.GetAll().Where(
+                x => x.MobileShortcutId == Util.ToInt(shortcutId)
+            ).FirstOrDefault();
+
+            return Ok(new {
+                shortcut.MobileShortcutId,
+                UserId = shortcut.UserId ?? "",
+                UserName = shortcut.CmUser?.Name ?? "",
+                shortcut.SitemapId,
+                Sitemap = new {
+                    shortcut.Sitemap.Name,
+                    shortcut.Sitemap.Path,
+                },
+                shortcut.Type,
+                shortcut.Icon,
+            });
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] MobileShortcut data) {
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpDelete("{userId}/{sitemapId}/{type}")]
+        public IActionResult DeleteBottom(string userId, int sitemapId, ShortcutType type) {
+            _service.Delete(userId, sitemapId, type);
+            return Ok();
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult PositionUp([FromQuery] string userId, [FromQuery] int sitemapId, [FromQuery] ShortcutType type) {
+            _service.PositionUp(userId, sitemapId, type);
+            return Ok();
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult PositionDown([FromQuery] string userId, [FromQuery] int sitemapId, [FromQuery] ShortcutType type) {
+            _service.PositionDown(userId, sitemapId, type);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<MobileShortcut> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 756 - 0
Controllers/App/MonthlyReportController.cs

@@ -0,0 +1,756 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Text;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class MonthlyReportController : Controller {
+        private readonly ILogger<MonthlyReportController> _logger;
+        private readonly FMSContext _context;
+        private readonly MonthlyReportService _service;
+        private readonly FMSAdmin.Services.SiteWeatherInfoService _siteWeatherInfoService;
+        private readonly StorageHelper _storage;
+
+        public enum WorkTypeId {
+            PeriodicInspection = 1,
+            ForensicExamination = 2,
+            CivilComplaint = 3,
+            SiAlarm = 4,
+            Occasional = 5,
+            Construct = 6,
+            ForensicEducation = 7,
+        }
+
+        public enum WorkProgressId {
+            WorkStatusPending = 3,
+            WorkStatusInProgress = 4,
+            WorkStatusComplete = 5,
+            WorkStatusApproved = 6,
+            WorkStatusDeferred = 7,
+        }
+
+        public enum AdjustmentTypeId {
+            Regular = 0,
+            Loss = 1,
+            Return = 2
+        }
+
+        public MonthlyReportController(
+            ILogger<MonthlyReportController> logger,
+            FMSContext context,
+            MonthlyReportService service,
+            FMSAdmin.Services.SiteWeatherInfoService siteWeatherInfoService,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _siteWeatherInfoService = siteWeatherInfoService;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            var query = _FilterAndSort(req);
+            query = BusinessFieldHelper.Apply<FmsMonthlyReport>(_context, User, businessAuth, query);
+            query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.AddDate,
+                x.BusinessFieldId,
+                x.Name,
+                x.Contents,
+                x.RegisterUserId,
+                RegisterUserName = _context.CmUser.FirstOrDefault(user => user.UserId == x.RegisterUserId).Name,
+                x.ApproveDate,
+                x.RegisterDate,
+                x.ApproverUserId,
+                ApproverUserName = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId).Name,
+            });
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] DateTime addDate, [FromQuery] string businessFieldId) {
+            var query = _service.GetAll();
+
+            var item = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.AddDate.Year == addDate.Date.Year
+                && x.AddDate.Month == addDate.Date.Month
+                && x.BusinessFieldId == Util.ToInt(businessFieldId)
+            ).Select(x => new {
+                x.SiteId,
+                AddDate = x.AddDate.ToString("yyyy-MM"),
+                x.BusinessFieldId,
+                x.Name,
+                x.Contents,
+                x.SiteInfoDatas,
+                x.FacilityStandards,
+                x.RentalStandards,
+                ParkingManagement = new {
+                    FileId = x.ParkingManagement.FileId,
+                    Path = _storage.getFilePath(x.ParkingManagement),
+                    Name = x.ParkingManagement.Name
+                },
+                RentalManagement = new {
+                    FileId = x.RentalManagement.FileId,
+                    Path = _storage.getFilePath(x.RentalManagement),
+                    Name = x.RentalManagement.Name
+                },
+                MonthlyPriorityWork = new {
+                    FileId = x.MonthlyPriorityWork.FileId,
+                    Path = _storage.getFilePath(x.MonthlyPriorityWork),
+                    Name = x.MonthlyPriorityWork.Name
+                },
+                ImprovementReport = new {
+                    FileId = x.ImprovementReport.FileId,
+                    Path = _storage.getFilePath(x.ImprovementReport),
+                    Name = x.ImprovementReport.Name
+                },
+                NextMonthPriorityWork = new {
+                    FileId = x.NextMonthPriorityWork.FileId,
+                    Path = _storage.getFilePath(x.NextMonthPriorityWork),
+                    Name = x.NextMonthPriorityWork.Name
+                },
+                Cost = new {
+                    FileId = x.Cost.FileId,
+                    Path = _storage.getFilePath(x.Cost),
+                    Name = x.Cost.Name
+                },
+                SurroundingStatus = new {
+                    FileId = x.SurroundingStatus.FileId,
+                    Path = _storage.getFilePath(x.SurroundingStatus),
+                    Name = x.SurroundingStatus.Name
+                },
+                x.HumanCountStatus,
+                x.ManagementOfficeContact,
+                x.RelatedInstitutionsContact,
+                x.UserChangeStatus,
+                x.TenantContact,
+                x.RegisterUserId,
+                x.RegisterDate,
+                x.ApproveDate,
+                x.ApproverUserId,
+                ApproverUserName = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId).Name,
+                x.ApproverUserId2,
+                ApproverUserName2 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId2).Name,
+                x.ApproverUserId3,
+                ApproverUserName3 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId3).Name,
+                x.ApproverUserId4,
+                ApproverUserName4 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId4).Name,
+                x.ApproverUserId5,
+                ApproverUserName5 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId5).Name,
+                x.FacilityTime,
+                x.FacilityTimeGraph,
+                x.EnergyUsage,
+                x.EnergyUsageGraph,
+                x.EnergyCost,
+                x.EnergyCharge,
+                x.EnergyChargeGraph,
+                x.EnergyAnalysis,
+            }).FirstOrDefault();
+
+
+            var organizationChartStatus =
+                    _context.CmOrganizationChartStatus.Where(
+                        x => x.SiteId == Util.ToInt(siteId)
+                        && x.Year == addDate.Date.Year
+                        && x.Month == addDate.Date.Month
+                    ).Select(x => new {
+                        x.CmFile.FileId,
+                        Path = _storage.getFilePath(x.CmFile),
+                        x.CmFile.Name
+                    }).FirstOrDefault();
+
+            var rentalStatus =
+                  _context.CmRentalStatus.Where(
+                      x => x.SiteId == Util.ToInt(siteId)
+                      && x.Year == addDate.Date.Year
+                      && x.Month == addDate.Date.Month
+                  ).Select(x => new {
+                      x.CmFile.FileId,
+                      Path = _storage.getFilePath(x.CmFile),
+                      x.CmFile.Name
+                  }).FirstOrDefault();
+
+            _logger.LogInformation("========================== 법정검사 =======================");
+            DataTable legalExamination = _service.GetLegalExamination(Util.ToInt(siteId), addDate);
+
+            var layoutStatus =
+                  _context.CmLayout.Where(
+                      x => x.SiteId == Util.ToInt(siteId)
+                      && x.Year == addDate.Date.Year
+                      && x.Month == addDate.Date.Month
+                  ).Select(x => new {
+                      x.CmFile.FileId,
+                      Path = _storage.getFilePath(x.CmFile),
+                      x.CmFile.Name
+                  }).FirstOrDefault();
+
+
+            return Ok(new {
+                report = item,
+                OrganizationChartStatus = organizationChartStatus,
+                RentalStatus = rentalStatus,
+                LayoutStatus = layoutStatus,
+                LegalExamination = legalExamination,
+            });
+        }
+
+        [AllowAnonymous]
+        [HttpGet("[action]")]
+        public string GetJson(string siteId, DateTime? addDate, string tabName, string year, string month) {
+            _logger.LogInformation(tabName);
+            var query = _service.GetAll();
+
+            if (addDate == null) {
+                if (string.IsNullOrEmpty(year) || string.IsNullOrEmpty(month)) {
+                    throw new Exception("날짜가 입력되지 않았습니다.");
+                }
+            }
+
+            // Date or year,month 
+            int currentYear = addDate != null ? addDate.Value.Year : Util.ToInt(year);
+            int currentMonth = addDate != null ? addDate.Value.Month : Util.ToInt(month);
+            DateTime currentDate = addDate != null ? addDate.Value : new DateTime(currentYear, currentMonth, 1);
+
+            var item = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.AddDate.Year == currentYear
+                && x.AddDate.Month == currentMonth
+            ).Select(x => new {
+                CmSite = new {
+                    x.Name
+                },
+                AddDate = x.AddDate.ToString("yyyy-MM"),
+                x.Name,
+                x.Contents,
+                x.SiteInfoDatas,
+                x.FacilityStandards,
+                x.RentalStandards,
+                x.HumanCountStatus,
+                x.UserChangeStatus,
+                x.ManagementOfficeContact,
+                x.RelatedInstitutionsContact,
+                x.TenantContact,
+                x.ParkingManagement,
+                x.RentalManagement,
+                x.MonthlyPriorityWork,
+                x.ImprovementReport,
+                x.NextMonthPriorityWork,
+                x.Cost,
+                x.SurroundingStatus,
+                x.FacilityTime,
+                x.FacilityTimeGraph,
+                x.EnergyUsage,
+                x.EnergyUsageGraph,
+                x.EnergyCost,
+                x.EnergyCharge,
+                x.EnergyChargeGraph,
+                x.EnergyAnalysis,
+                x.ApproveDate,
+                x.ApproveDate2,
+                x.ApproveDate3,
+                x.ApproverUserId,
+                x.ApproverUserId2,
+                x.ApproverUserId3
+            }).FirstOrDefault();
+            string approve = "";
+            if (item != null) {
+                approve += "[" + getApproverInfo(item.ApproverUserId, item.ApproveDate);
+                approve += getApproverInfo(item.ApproverUserId2, item.ApproveDate2);
+                approve += getApproverInfo(item.ApproverUserId3, item.ApproveDate3);
+                approve += "]";
+            }
+
+            string json = "";
+            if (item != null) {
+                if (tabName == "Building") {
+                    json = $@"
+                    {{ 
+                        Building : {{  
+                            SiteInfo: {item.SiteInfoDatas},
+                            FacilityStandards: {item.FacilityStandards},
+                            RentalStandards: {item.RentalStandards},
+                            Approver : {approve}
+                        }} 
+                    }}";
+                } else if (tabName == "UserInfo") {
+                    var OrganizationChartStatus = _context.CmOrganizationChartStatus.Where(x =>
+                                                    x.SiteId == Util.ToInt(siteId) &&
+                                                    x.Year == currentYear &&
+                                                    x.Month == currentMonth).Select(x => x.CmFile).SingleOrDefault();
+                    json = $@"
+                    {{ 
+                        UserInfo : {{  
+                            OrganizationChartStatus: {JsonConvert.SerializeObject(_storage.GetPdfImages(OrganizationChartStatus))},
+                            HumanCountStatus : {item.HumanCountStatus},
+                            ManagementOfficeContact: {item.ManagementOfficeContact},
+                            RelatedInstitutionsContact : {item.RelatedInstitutionsContact},
+                            UserChangeStatus: {item.UserChangeStatus},
+                            Approver : {approve}
+                        }} 
+                    }}";
+                } else if (tabName == "TenantInfo") {
+                    var RentalStatus = _context.CmRentalStatus.Where(x =>
+                                        x.SiteId == Util.ToInt(siteId) &&
+                                        x.Year == currentYear &&
+                                        x.Month == currentMonth).Select(x => x.CmFile).SingleOrDefault();
+                    json = $@"
+                    {{ 
+                        Images :{JsonConvert.SerializeObject(_storage.GetPdfImages(RentalStatus))},
+                        Approver : {approve}
+                    }}";
+                } else if (tabName == "TenantInfoFloor") {
+                    var LayoutStatus = _context.CmLayout.Where(x =>
+                                                      x.SiteId == Util.ToInt(siteId) &&
+                                                      x.Year == currentYear &&
+                                                      x.Month == currentMonth).Select(x => x.CmFile).SingleOrDefault();
+                    json = $@"
+                    {{ 
+                        TenantInfoFloor : {{  
+                            LayoutStatus: {JsonConvert.SerializeObject(_storage.GetPdfImages(LayoutStatus))},
+                            TenantContact : {item.TenantContact},
+                            Approver : {approve}
+                        }} 
+                    }}";
+                } else if (tabName == "ParkingManagement") {
+                    json = $@"
+                    {{ 
+                        Images : {JsonConvert.SerializeObject(_storage.GetPdfImages(item.ParkingManagement))},
+                        Approver : {approve}
+                    }}";
+                } else if (tabName == "RentalManagement") {
+                    json = $@"
+                    {{ 
+                        Images : {JsonConvert.SerializeObject(_storage.GetPdfImages(item.RentalManagement))},
+                        Approver : {approve}
+                    }}";
+                } else if (tabName == "LegalExamination") {
+                    DataTable legalExamination = _service.GetLegalExamination(Util.ToInt(siteId), currentDate);
+                    json = $@"
+                    {{ 
+                        Images : {JsonConvert.SerializeObject(legalExamination)},
+                        Approver : {approve}
+                    }}";
+
+                } else if (tabName == "MonthlyPriorityWork") {
+                    json = $@"
+                    {{ 
+                        Images :{JsonConvert.SerializeObject(_storage.GetPdfImages(item.MonthlyPriorityWork))},
+                        Approver : {approve}
+                    }}";
+
+                } else if (tabName == "ImprovementReport") {
+                    json = $@"
+                    {{ 
+                        Images : {JsonConvert.SerializeObject(_storage.GetPdfImages(item.ImprovementReport))},
+                        Approver : {approve}
+                    }}";
+
+                } else if (tabName == "NextMonthPriorityWork") {
+                    json = $@"
+                    {{ 
+                        Images : {JsonConvert.SerializeObject(_storage.GetPdfImages(item.NextMonthPriorityWork))},
+                        Approver : {approve}
+                    }}";
+
+                } else if (tabName == "Cost") {
+                    json = $@"
+                    {{ 
+                        Images : {JsonConvert.SerializeObject(_storage.GetPdfImages(item.Cost))},
+                        Approver : {approve}
+                    }}";
+
+                } else if (tabName == "SurroundingStatus") {
+                    json = $@"
+                    {{ 
+                        Images : {JsonConvert.SerializeObject(_storage.GetPdfImages(item.SurroundingStatus))},
+                        Approver : {approve}
+                    }}";
+                } else if (tabName == "FacilityTime") {
+                    json = $@"
+                    {{ 
+                        FacilityTime : {{  
+                            FacilityTime: {item.FacilityTime},
+                            FacilityTimeGraph: {item.FacilityTimeGraph},
+                            Approver : {approve}
+                        }} 
+                    }}";
+                } else if (tabName == "EnergyUsageElect") {
+                    string energyUsage = "", energyUsageGraph = "", energyCost = "";
+                    JArray jarr1 = JArray.Parse(item.EnergyUsage);
+                    foreach (var val in jarr1) {
+                        if (val["FuelTypeId"].ToString() == "1") {
+                            energyUsage += (energyUsage != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    JArray jarr2 = JArray.Parse(item.EnergyUsageGraph);
+                    foreach (var val in jarr2) {
+                        if (val["FuelTypeId"].ToString() == "1") {
+                            energyUsageGraph += (energyUsageGraph != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    JArray jarr3 = JArray.Parse(item.EnergyCost);
+                    foreach (var val in jarr3) {
+                        if (val["FuelTypeId"].ToString() == "1") {
+                            energyCost += (energyCost != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    json = $@"
+                    {{ 
+                        EnergyUsageElect : {{  
+                            EnergyUsage: [{energyUsage}],
+                            EnergyUsageGraph: [{energyUsageGraph}],
+                            EnergyCost: [{energyCost}],
+                            Approver : {approve}
+                        }} 
+                    }}";
+                } else if (tabName == "EnergyUsageGasWater") {
+                    string energyUsage = "", energyUsageGraph = "", energyCost = "";
+                    JArray jarr1 = JArray.Parse(item.EnergyUsage);
+                    foreach (var val in jarr1) {
+                        if (val["FuelTypeId"].ToString() != "1") {
+                            energyUsage += (energyUsage != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    JArray jarr2 = JArray.Parse(item.EnergyUsageGraph);
+                    foreach (var val in jarr2) {
+                        if (val["FuelTypeId"].ToString() != "1") {
+                            energyUsageGraph += (energyUsageGraph != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    JArray jarr3 = JArray.Parse(item.EnergyCost);
+                    foreach (var val in jarr3) {
+                        if (val["FuelTypeId"].ToString() != "1") {
+                            energyCost += (energyCost != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    json = $@"
+                    {{ 
+                        EnergyUsageGasWater : {{  
+                            EnergyUsage: [{energyUsage}],
+                            EnergyUsageGraph: [{energyUsageGraph}],
+                            EnergyCost: [{energyCost}],
+                            Approver : {approve}
+                        }} 
+                    }}";
+                } else if (tabName == "EnergyChargeElect") {
+                    string energyCharge = "", energyChargeGraph = "", energyAnalysis = "";
+                    JArray jarr1 = JArray.Parse(item.EnergyCharge);
+                    foreach (var val in jarr1) {
+                        if (val["FuelTypeId"].ToString() == "1") {
+                            energyCharge += (energyCharge != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    JArray jarr2 = JArray.Parse(item.EnergyChargeGraph);
+                    foreach (var val in jarr2) {
+                        if (val["FuelTypeId"].ToString() == "1") {
+                            energyChargeGraph += (energyChargeGraph != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    JArray jarr3 = JArray.Parse(item.EnergyAnalysis);
+                    foreach (var val in jarr3) {
+                        if (val["FuelTypeId"].ToString() == "1") {
+                            energyAnalysis += (energyAnalysis != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    json = $@"
+                    {{ 
+                        EnergyChargeElect : {{  
+                            EnergyCharge: [{energyCharge}],
+                            EnergyChargeGraph: [{energyChargeGraph}],
+                            EnergyAnalysis: [{energyAnalysis}],
+                            Approver : {approve}
+                        }} 
+                    }}";
+                } else if (tabName == "EnergyChargeGasWater") {
+                    string energyCharge = "", energyChargeGraph = "", energyAnalysis = "";
+                    JArray jarr1 = JArray.Parse(item.EnergyCharge);
+                    foreach (var val in jarr1) {
+                        if (val["FuelTypeId"].ToString() != "1") {
+                            energyCharge += (energyCharge != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    JArray jarr2 = JArray.Parse(item.EnergyChargeGraph);
+                    foreach (var val in jarr2) {
+                        if (val["FuelTypeId"].ToString() != "1") {
+                            energyChargeGraph += (energyChargeGraph != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    JArray jarr3 = JArray.Parse(item.EnergyAnalysis);
+                    foreach (var val in jarr3) {
+                        if (val["FuelTypeId"].ToString() != "1") {
+                            energyAnalysis += (energyAnalysis != "" ? "," : "") + val.ToString(Newtonsoft.Json.Formatting.None);
+                        }
+                    }
+                    json = $@"
+                    {{ 
+                        EnergyChargeGasWater : {{  
+                            EnergyCharge: [{energyCharge}],
+                            EnergyChargeGraph: [{energyChargeGraph}],
+                            EnergyAnalysis: [{energyAnalysis}],
+                            Approver : {approve}
+                        }} 
+                    }}";
+                }
+            } else {
+                json = "{}";
+                if (tabName == "TenantInfo") {
+                    json = "{ Images :[], Approver : [] }";
+                } else if (tabName == "ParkingManagement") {
+                    json = "{ Images :[], Approver : [] }";
+                } else if (tabName == "RentalManagement") {
+                    json = "{ Images :[], Approver : [] }";
+                } else if (tabName == "MonthlyPriorityWork") {
+                    json = "{ Images :[], Approver : [] }";
+                } else if (tabName == "ImprovementReport") {
+                    json = "{ Images :[], Approver : [] }";
+                } else if (tabName == "NextMonthPriorityWork") {
+                    json = "{ Images :[], Approver : [] }";
+                } else if (tabName == "Cost") {
+                    json = "{ Images :[], Approver : [] }";
+                } else if (tabName == "SurroundingStatus") {
+                    json = "{ Images :[], Approver : [] }";
+                }
+            }
+            return json;
+        }
+
+
+        public string getApproverInfo(string id, DateTime? date) {
+            if (!Util.IsNullOrEmpty(id)) {
+                var userInfo = _context.CmUser.Where(x => x.UserId == id).Select(x => new {
+                    GroupNm = x.CmUserGroup.Name,
+                    Date = date.Value.ToString("yyyy-MM-dd"),
+                    image = x.CmFile1 != null ? _storage.GetFullUrl(x.CmFile1.Path) + _storage.getFilePath(x.CmFile1) : ""
+                }).SingleOrDefault();
+                return JsonConvert.SerializeObject(userInfo) + ",";
+            } else return "";
+        }
+
+        private static string AreaParse(String areaVal) {
+            string returnVal = "";
+            float val;
+            if (!Util.IsNullOrEmpty(areaVal) && float.TryParse(areaVal, out val)) {
+                returnVal = val.ToString("#,##0.00") + "㎡ (" + (val / 3.3058).ToString("#,##0.00") + "평)";
+            }
+            return returnVal;
+        }
+
+
+        [HttpGet("[action]")]
+        public IActionResult GetCurrentInfo([FromQuery] string siteId, [FromQuery] DateTime addDate, [FromQuery] string businessFieldId) {
+
+            var today = addDate.Date;
+
+            _logger.LogInformation("========================== 기본사항! =======================");
+            var siteInfo = _context.CmSite.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+            ).Select(x => new {
+                x.SiteId,
+                x.Name,
+                x.PhoneNo,
+                x.AddressZip1,
+                x.CompletionDate,
+                Address1 = x.Address1 + x.Adderss2,
+                x.BuildingSize,
+                GrossArea = AreaParse(x.GrossArea),
+                BuildingArea = AreaParse(x.BuildingArea),
+                x.FloorAreaRatio,
+                x.BuildingCoverageRatio,
+                x.MainUse,
+                x.Standard,
+                x.Url,
+                ImageFile = _storage.ParseFile(x.ImageFile),
+                x.DesignSupervision,
+                x.ConstructionCompany,
+                x.ConstructionPeriod,
+                x.Structure,
+                x.LobbyFloor,
+                x.OfficeFloor,
+                x.ToiletFloor,
+                x.EVHall,
+                x.ParkingLot,
+                x.ParkingFacilities,
+                x.OuterWall,
+                LandArea = AreaParse(x.LandArea),
+                LandscapingArea = AreaParse(x.LandscapingArea),
+                x.BuildingFloors,
+                x.BuildingHeight,
+                x.ConversionRate,
+                x.AuditoriumFacilities
+            }).FirstOrDefault();
+
+            _logger.LogInformation("========================== 조직도현황! =======================");
+            var organizationChartStatus = _context.CmOrganizationChartStatus.Where(
+                x => x.SiteId == Util.ToInt(siteId) &&
+                x.Year == addDate.Year &&
+                x.Month == addDate.Month
+            ).Select(x => new {
+                FileId = x.FileId,
+                Path = _storage.getFilePath(x.CmFile),
+                Name = x.CmFile.Name
+            }).FirstOrDefault();
+
+            _logger.LogInformation("========================== 입주사현황(임대현황)! =======================");
+            var rentalStatus = _context.CmRentalStatus.Where(
+                x => x.SiteId == Util.ToInt(siteId) &&
+                x.Year == addDate.Year &&
+                x.Month == addDate.Month
+            ).Select(x => new {
+                FileId = x.FileId,
+                Path = _storage.getFilePath(x.CmFile),
+                Name = x.CmFile.Name
+            }).FirstOrDefault();
+
+
+            _logger.LogInformation("========================== 층별사용현황(레이아웃)! =======================");
+            var layoutStatus = _context.CmLayout.Where(
+                x => x.SiteId == Util.ToInt(siteId) &&
+                x.Year == addDate.Year &&
+                x.Month == addDate.Month
+            ).Select(x => new {
+                FileId = x.FileId,
+                Path = _storage.getFilePath(x.CmFile),
+                Name = x.CmFile.Name
+            }).FirstOrDefault();
+
+            _logger.LogInformation("========================== 기온및설비관리(설비가동현황) =======================");
+            DataTable facilityTime = _service.GetFacilityTime(Util.ToInt(siteId), today);
+
+            //그래프
+            DataTable facilityTimeGraph = _service.GetFacilityTimeGraph(Util.ToInt(siteId), today);
+
+            _logger.LogInformation("========================== 에너지사용량(전기/가스/수도) =======================");
+            // 에너지사용량 - 사용량
+            DataTable energyUsage = _service.GetEnergyData(Util.ToInt(siteId), today, 1, false);
+            // 에너지사용량 - 사용량 그래프
+            DataTable energyUsageGraph = _service.GetEnergyData(Util.ToInt(siteId), today, 1, true);
+            // 에너지사용량 - 단가변동
+            DataTable energyCost = _service.GetEnergyData(Util.ToInt(siteId), today, 2, false);
+
+            _logger.LogInformation("========================== 에너지요금분석(전기/가스/수도) =======================");
+            // 에너지요금분석 - 요금분석
+            DataTable energyCharge = _service.GetEnergyData(Util.ToInt(siteId), today, 3, false);
+            // 에너지요금분석 - 요금분석 그래프
+            DataTable energyChargeGraph = _service.GetEnergyData(Util.ToInt(siteId), today, 3, true);
+            // 에너지요금분석 - 증감사유
+            var energyAnalysis = _context.FmsEnergyAnalysis.Where(x => x.SiteId == Util.ToInt(siteId) && x.Year == addDate.Year && x.Month == addDate.Month)
+            .Select(x => new {
+                x.FuelTypeId,
+                x.PriceTypeId,
+                x.Content
+            }).ToList();
+
+
+            _logger.LogInformation("========================== 법정검사 =======================");
+            DataTable legalExamination = _service.GetLegalExamination(Util.ToInt(siteId), addDate);
+
+
+
+            _logger.LogInformation("========================== 지난달 월간보고서 데이터! =======================");
+            var lastMonthDatas = _context.FmsMonthlyReport.Where(
+              x => x.SiteId == Util.ToInt(siteId) &&
+              x.AddDate.Year == addDate.AddMonths(-1).Year &&
+              x.AddDate.Month == addDate.AddMonths(-1).Month
+            ).Select(x => new {
+                x.FacilityStandards,
+                x.RentalStandards,
+                x.HumanCountStatus,
+                x.ManagementOfficeContact,
+                x.RelatedInstitutionsContact,
+                x.UserChangeStatus,
+                x.TenantContact,
+            }).FirstOrDefault();
+
+            return Ok(new {
+                SiteInfo = siteInfo,
+                OrganizationChartStatus = organizationChartStatus,
+                LastMonthDatas = lastMonthDatas,
+                RentalStatus = rentalStatus,
+                LayoutStatus = layoutStatus,
+                FacilityTime = facilityTime,
+                FacilityTimeGraph = facilityTimeGraph,
+                LegalExamination = legalExamination,
+                EnergyUsage = energyUsage,
+                EnergyUsageGraph = energyUsageGraph,
+                EnergyCost = energyCost,
+                EnergyCharge = energyCharge,
+                EnergyChargeGraph = energyChargeGraph,
+                EnergyAnalysis = energyAnalysis,
+            });
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] FmsMonthlyReport data, [FromQuery] bool isCreate) {
+            _logger.LogInformation($"Save isCreate({isCreate})");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data, isCreate);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpPost("[action]")]
+        public IActionResult Approve([FromQuery] string siteId, [FromQuery] string approverUserId, [FromQuery] DateTime addDate, [FromQuery] string businessFieldId) {
+            _service.Approve(Util.ToInt(siteId), approverUserId, addDate, Util.ToInt(businessFieldId));
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsMonthlyReport> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        [HttpDelete("[action]")]
+        public IActionResult Delete([FromQuery] string siteId, [FromQuery] DateTime addDate, [FromQuery] string businessFieldId) {
+            _service.Delete(Util.ToInt(siteId), addDate, Util.ToInt(businessFieldId));
+            return Ok();
+        }
+
+    }
+}

+ 158 - 0
Controllers/App/PartnerController.cs

@@ -0,0 +1,158 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class PartnerController : Controller {
+        private readonly ILogger<PartnerController> _logger;
+        private readonly FMSContext _context;
+        private readonly PartnerService _service;
+        private readonly StorageHelper _storage;
+
+
+
+
+        public PartnerController(
+            ILogger<PartnerController> logger,
+            FMSContext context,
+            PartnerService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] bool useServicePartner, bool businessAuth) {
+            var query = _FilterAndSort(req);
+            query = BusinessFieldHelper.Apply<CmPartner>(_context, User, businessAuth, query);
+
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            if (useServicePartner) {
+                query = query.Where(x => x.PartnerType.Name != "용역업체");
+            }
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] int siteId, [FromQuery] int partnerId) {
+            var data = _service.Gets(partnerId, siteId);
+            if (data.Count() == 0) {
+                return NotFound("정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.PartnerId,
+                x.PartnerTypeId,
+                PartnerTypeName = x.PartnerType.Name,
+                x.BusinessFieldId,
+                BusinessFieldName = x.BusinessField.Name,
+                x.SiteId,
+                x.Name,
+                x.RepresentativeName,
+                x.SalesName,
+                x.PhoneNo,
+                x.SalesPhoneNo,
+                x.FaxNo,
+                x.RegistrationNo,
+                x.HomepageUri,
+                x.Address1,
+                x.Adderss2,
+                x.AddressZip1,
+                x.AddressZip2,
+                x.Note,
+                x.IsUse,
+            }).FirstOrDefault();
+            return Ok(item);
+        }
+
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] CmPartner data) {
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<CmPartner> query) {
+            var list = query.Select(x => new {
+                x.PartnerId,
+                x.PartnerTypeId,
+                PartnerTypeName = x.PartnerType.Name,
+                x.SiteId,
+                x.Name,
+                x.RepresentativeName,
+                x.PhoneNo,
+                x.SalesPhoneNo,
+                x.IsUse,
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmPartner> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+    }
+}

+ 149 - 0
Controllers/App/ReportHistoryController.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Text;
+using System.Collections.Generic;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class ReportHistoryController : Controller {
+        private readonly ILogger<ReportHistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly ReportHistoryService _service;
+        private readonly StorageHelper _storage;
+
+        public ReportHistoryController(
+            ILogger<ReportHistoryController> logger,
+            FMSContext context,
+            ReportHistoryService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            var query = _FilterAndSort(req);
+            query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            query = BusinessFieldHelper.Apply<BemsReportHistory>(_context, User, businessAuth, query, "BemsReportFormat");
+
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.Seq,
+                x.CreateDate,
+                x.ReportName,
+                x.ReportFormatId,
+                x.BemsReportFormat.BusinessFieldId,
+                x.FormatName,
+                x.Writer,
+                x.Confirm1,
+                x.Confirm2,
+                x.FileName,
+            });
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string seq, [FromQuery] DateTime datetime, [FromQuery] string reportFormatId) {
+            var history = _context.BemsReportHistory.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.Seq == Util.ToInt(seq)
+            ).FirstOrDefault();
+
+            var builder = new StringBuilder();
+            if (history != null) {
+                foreach (var content in history.BemsReportHistoryContent.OrderBy(x => x.ContentId)) {
+                    builder.Append(content.Content);
+                }
+            }
+
+            return Ok(new {
+                history.SiteId,
+                history.Seq,
+                history.CreateDate,
+                history.ReportName,
+                history.ReportFormatId,
+                history.FormatName,
+                history.Writer,
+                history.Confirm1,
+                history.Confirm2,
+                history.FileName,
+                BusinessFieldName = Util.V(history, x => x.BemsReportFormat.CmBusinessField.Name),
+                Content = builder.ToString(),
+                CmFile = _storage.ParseFile(history.CmFile),
+            });
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult FacilityList([FromQuery] string siteId, [FromQuery] string seq) {
+            var history = _context.BemsReportHistory.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.Seq == Util.ToInt(seq)
+            ).FirstOrDefault();
+
+            if (history == null) return Ok(new List<CmFacility>());
+
+            var list = _context.BemsReportFormatToFacility.Where(
+                x => x.SiteId == history.SiteId
+                && x.ReportFormatId == history.ReportFormatId
+            ).Select(x => new {
+                x.CmFacility.SiteId,
+                x.CmFacility.FacilityCode,
+                x.CmFacility.Name,
+                x.CmFacility.Manufacturer,
+                x.CmFacility.FacilityCapacity,
+                BuildingName = x.CmFacility.CmBuilding.Name,
+                FloorName = x.CmFacility.CmFloor.Name,
+                FirstClassName = x.CmFacility.FmsFacilityCodeClass.Name,
+                SecondClassName = x.CmFacility.FmsFacilityCodeClass1.Name,
+                ThirdClassName = x.CmFacility.FmsFacilityCodeClass2.Name,
+            }).ToList();
+
+            return Ok(list);
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<BemsReportHistory> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 126 - 0
Controllers/App/SiteController.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class SiteController : Controller {
+        private readonly ILogger<SiteController> _logger;
+        private readonly FMSContext _context;
+        private readonly SiteService _service;
+        private readonly StorageHelper _storage;
+
+        public SiteController(
+            ILogger<SiteController> logger,
+            FMSContext context,
+            SiteService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.Name,
+                x.AddressZip1,
+                x.Address1,
+                x.Adderss2,
+                x.PhoneNo,
+                x.IsUse,
+                x.IsControlSchedule,
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId) {
+            var site = _service.GetAll().Where(x => x.SiteId == Util.ToInt(siteId)).FirstOrDefault();
+
+            return Ok(new {
+                site.SiteId,
+                site.Name,
+                site.AddressZip1,
+                site.Address1,
+                site.Adderss2,
+                site.PhoneNo,
+                site.IsUse,
+                site.CompletionDate, //준공연도
+                site.BuildingSize, //건축규모
+                site.GrossArea, // 연면적
+                site.BuildingArea,  // 건축면적
+                site.FloorAreaRatio,    // 용적율
+                site.BuildingCoverageRatio, // 건폐율
+                site.MainUse,   // 주용도
+                site.IsControlSchedule, // 제어스케줄
+                site.ScheduleCheckHour, // 스케줄알람시간
+                site.Hdd,   // 기준온도 HDD
+                site.Cdd,   // 기준온도 CDD
+                site.Standard, // 용도별표준값
+                site.Url,   // 홈페이지
+                site.Comment, // 비고
+                ImageFile = _storage.ParseFile(site.ImageFile)
+            });
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] CmSite data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmSite> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 155 - 0
Controllers/App/TenantController.cs

@@ -0,0 +1,155 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class TenantController : Controller {
+        private readonly ILogger<TenantController> _logger;
+        private readonly FMSContext _context;
+        private readonly TenantService _service;
+        private readonly StorageHelper _storage;
+
+        public TenantController(
+            ILogger<TenantController> logger,
+            FMSContext context,
+            TenantService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.TenantId,
+                x.Name,
+                x.CompanyTypeId,
+                CmCompanyType = new {
+                    x.CmCompanyType.Name
+                },
+                x.RepresentativeName,
+                x.RepresentativePhone,
+                x.AreaSize,
+                x.ExclusiveArea,
+                x.BuildingId,
+                CmBuilding = new {
+                    x.CmBuilding.Name
+                },
+                x.FloorId,
+                CmFloor = new {
+                    x.CmFloor.Name
+                },
+                x.ChargeEmail,
+                x.IsUse
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string tenantId) {
+            var query = _service.GetAll()
+                .Where(x => x.SiteId == Util.ToInt(siteId) && x.TenantId == Util.ToInt(tenantId));
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.TenantId,
+                x.CompanyTypeId,
+                CompanyTypeName = x.CmCompanyType.Name,
+                x.BuildingId,
+                BuildingName = x.CmBuilding.Name,
+                x.FloorId,
+                x.Name,
+                x.RepresentativeName,
+                x.RepresentativePhone,
+                x.Fax,
+                x.AreaSize,
+                x.ExclusiveArea,
+                x.ChargeName,
+                x.ChargePhone,
+                x.AddressZip,
+                x.Address1,
+                x.Address2,
+                x.IsUse,
+                x.ChargeEmail,
+                x.Homepage,
+                x.Comment
+            }).FirstOrDefault();
+            return Ok(item);
+        }
+
+        [HttpPost("[action]")]
+        public IActionResult UpdateListData([FromBody] CmTenant data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            _service.UpdateListData(data);
+            return Ok();
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] CmTenant data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpDelete("{siteId}/{tenantId}")]
+        public IActionResult Delete(int siteId, int tenantId) {
+            _service.Delete(siteId, tenantId);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmTenant> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 255 - 0
Controllers/App/UserController.cs

@@ -0,0 +1,255 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/users")]
+    public class UserController : Controller {
+        private readonly ILogger<UserController> _logger;
+        private readonly FMSContext _context;
+        private readonly UserService _service;
+        private readonly StorageHelper _storage;
+
+        public UserController(
+            ILogger<UserController> logger,
+            FMSContext context,
+            UserService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 로그인
+        /// </summary>
+        [AllowAnonymous]
+        [HttpPost("[action]")]
+        public IActionResult SignIn([FromBody] SignInModel model) {
+            _logger.LogInformation(model.Dump());
+
+            var user = _service.Login(int.Parse(model.SiteId), model.UserId, model.Passwd, null);
+
+            if (user == null)
+                return BadRequest("아이디나 비밀번호가 잘못되었습니다.");
+
+            return Ok(new {
+                user.SiteId,
+                SiteName = user.CmSite.Name,
+                user.UserId,
+                user.UserGroupId,
+                user.Name,
+                user.OfficePhoneNo,
+                user.Email,
+                user.AccessToken,
+                IsChangePassword = (user.PasswordUpdatedDate != null && user.PasswordUpdatedDate < DateTime.Now.AddMonths(-3))
+            });
+        }
+
+        [HttpPost("verify/{siteid}/{userid}")]
+        public IActionResult Verify(int siteid, string userid) {
+            var verifyKey = User.Identity.Name;
+            _logger.LogInformation(verifyKey.ToString());
+
+            var user = _service.GetByUserId(siteid, userid);
+
+            if (user == null) return NotFound();
+            if (verifyKey != user.UserId) return NotFound();
+
+            return Ok(new {
+                user.SiteId,
+                user.UserId,
+                user.UserGroupId,
+                user.Name,
+                user.OfficePhoneNo,
+                user.Email,
+                IsChangePassword = (user.PasswordUpdatedDate != null && user.PasswordUpdatedDate < DateTime.Now.AddMonths(-3))
+            });
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            _logger.LogInformation(req.Dump());
+
+            var query = _FilterAndSort(req);
+            query = query.Where(x => x.IsUse == true && x.IsSysop == false);
+            if (!string.IsNullOrEmpty(siteId)) {
+                query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+            }
+            var list = query.Select(emp => new {
+                emp.SiteId,
+                emp.UserId,
+                emp.UserGroupId,
+                emp.CompanyId,
+                CmCompany = new {
+                    emp.CmCompany.Name,
+                    emp.CmCompany.CompanyTypeId,
+                    CmCompanyType = new {
+                        emp.CmCompany.CmCompanyType.Name
+                    }
+                },
+                CmDepartment = new {
+                    emp.CmDepartment.Name,
+                    emp.CmDepartment.DepartmentId
+                },
+                emp.PositionId,
+                CmPosition = new {
+                    emp.CmPosition.Name
+                },
+                emp.BusinessFieldId,
+                CmBusinessField = new {
+                    emp.CmBusinessField.Name
+                },
+                emp.Name,
+                emp.MobilePhoneNo,
+                emp.OfficePhoneNo,
+                emp.Email,
+                emp.EnterDate,
+                //UserGroupName = emp.CmUserGroup.Name,
+                emp.AccessToken,
+                CmFile = _storage.ParseFile(emp.CmFile),
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, [FromQuery] string mapping) {
+            var query = _FilterAndSort(req);
+
+            var stream = new MemoryStream();
+
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+                workSheet.Cells.LoadFromCollection(query, true);
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"UserList-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+
+            //return File(stream, "application/octet-stream", excelName);  
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 상세조회
+        /// </summary>
+
+        [HttpGet("[action]")]
+        public IActionResult GetByUserId([FromQuery] string siteId, [FromQuery] string userId) {
+            var emp = _service.GetByUserId(userId);
+
+            if (emp == null)
+                return NotFound();
+
+            //return Ok(emp);
+            return Ok(new {
+                emp.UserId,
+                emp.Passwd,
+                emp.CompanyId,
+                CompanyName = emp.CmCompany?.Name,
+                emp.DepartmentId,
+                DepartmentName = emp.CmDepartment?.Name,
+                emp.PositionId,
+                PositionName = emp.CmPosition?.Name,
+                emp.BusinessFieldId,
+                BusinessFieldName = emp.CmBusinessField?.Name,
+                emp.UserGroupId,
+                emp.Name,
+                emp.EmploymentType,
+                emp.EmploymentStatus,
+                emp.MobilePhoneNo,
+                emp.HomePhoneNo,
+                emp.OfficePhoneNo,
+                emp.Comment,
+                emp.Email,
+                emp.EnterDate,
+                emp.RetireDate,
+                emp.Certificated, //자격증
+                emp.IsScheduleUser, //제어스케쥴담당자
+                emp.IsSi, //SI접근권한
+                emp.IsMobile, //모바일접근권한
+                emp.IsUse,
+                CmFile = _storage.ParseFile(emp.CmFile),
+                CmFile1 = _storage.ParseFile(emp.CmFile1),
+                //UserGroupName = emp.CmUserGroup.Name,
+                //emp.AccessToken,
+            });
+        }
+
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] CmUser data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+        [HttpDelete("{siteId}/{userId}")]
+        public IActionResult Delete(int siteId, string userId) {
+            _service.Delete(siteId, userId);
+            return Ok();
+        }
+
+        [HttpPost("[action]")]
+        public IActionResult UpdatePushToken([FromBody] CmUser data) {
+            _service.UpdatePushToken(data);
+            return Ok();
+        }
+
+        // Role 제한 테스트
+        [AllowAnonymous]
+        [HttpGet("auth")]
+        public IActionResult Auth() {
+
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmUser> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 282 - 0
Controllers/App/WorkHistoryController.cs

@@ -0,0 +1,282 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class WorkHistoryController : Controller {
+        private readonly ILogger<WorkHistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly WorkRequestService _service;
+        private readonly StorageHelper _storage;
+
+        public WorkHistoryController(
+            ILogger<WorkHistoryController> logger,
+            FMSContext context,
+            WorkRequestService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 작업이력 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+
+            _logger.LogInformation("===========================");
+            _logger.LogInformation(req.Dump());
+            _logger.LogInformation("===========================");
+
+            var query = _FilterAndSort(req);
+            query = BusinessFieldHelper.Apply<FmsWorkRequest>(_context, User, businessAuth, query);
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.WorkProgressId > 1 // 작업계획 이후의 모든 프로세스
+                && (x.FmsWorkSchedule == null || (x.FmsWorkSchedule != null && x.FmsWorkSchedule.IsUse == true))
+            );
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.WorkTypeId,
+                WorkType = new {
+                    x.WorkType.Name
+                },
+                x.WorkDetailTypeId,
+                WorkDetailType = new {
+                    x.WorkDetailType.Code,
+                    x.WorkDetailType.Name,
+                },
+                x.BusinessFieldId,
+                BusinessField = new {
+                    x.CmBusinessField.Name
+                },
+                x.WorkProgressId,
+                WorkProgress = new {
+                    x.WorkProgress.Name
+                },
+                x.Title,
+                x.Content,
+                x.RequestUserId,
+                RequestUserName = x.CmUserNavigation.Name,
+                x.RequestDate,
+                x.StartWorkDate,
+                x.RejectUserId,
+                RejectUserName = x.CmUser.Name,
+                x.RejectDate,
+                x.RejectReason,
+                x.InspectionAgencyId,
+                InspectionAgencyName = x.CmPartner.Name,
+                x.WorkScheduleId,
+                x.WorkPartnerId,
+                WorkPartnerName = x.WorkPartner.Name,
+                x.WorkPrice,
+                x.WorkItem,
+                x.WorkEndDate,
+                FmsWorkOrder = new {
+                    x.FmsWorkOrder.OrderDate,
+                    x.FmsWorkOrder.Content,
+                },
+                FmsWorkResult = new {
+                    x.FmsWorkResult.StartDate,
+                    x.FmsWorkResult.EndDate,
+                    x.FmsWorkResult.ConfirmedDate,
+                    x.FmsWorkResult.ConfirmedUserId,
+                    x.FmsWorkResult.IsConfirmed,
+                    x.FmsWorkResult.CauseClassId,
+                    x.FmsWorkResult.Content,
+                    x.FmsWorkResult.ConfirmDesc,
+                    ConfirmUserName = x.FmsWorkResult.CmUser.Name,
+                    WorkerUserName = x.FmsWorkResult.CmUserNavigation.Name,
+                },
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 작업이력 상세 조회
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string workRequestId) {
+            var query = _service.GetAll()
+                .Where(x => x.SiteId == Util.ToInt(siteId) && x.WorkRequestId == Util.ToInt(workRequestId));
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.WorkTypeId,
+                WorkType = new {
+                    x.WorkType.Name
+                },
+                x.WorkDetailTypeId,
+                WorkDetailType = new {
+                    x.WorkDetailType.Code,
+                    x.WorkDetailType.Name,
+                },
+                x.BusinessFieldId,
+                BusinessField = new {
+                    x.CmBusinessField.Name
+                },
+                x.WorkProgressId,
+                WorkProgress = new {
+                    x.WorkProgress.Name
+                },
+                x.Title,
+                x.Content,
+                x.RequestUserId,
+                RequestUserName = x.CmUserNavigation.Name,
+                x.RequestDate,
+                x.StartWorkDate,
+                x.RejectUserId,
+                RejectUserName = x.CmUser.Name,
+                x.RejectDate,
+                x.RejectReason,
+                x.InspectionAgencyId,
+                InspectionAgencyName = x.CmPartner.Name,
+                x.WorkScheduleId,
+                x.WorkPartnerId,
+                WorkPartnerName = x.WorkPartner.Name,
+                x.WorkPrice,
+                x.WorkItem,
+                x.WorkEndDate,
+                CmFile = _storage.ParseFile(x.CmFile),
+                FmsWorkOrder = new {
+                    x.FmsWorkOrder.OrderDate,
+                    x.FmsWorkOrder.Content,
+                },
+                FmsWorkResult = new {
+                    x.FmsWorkResult.StartDate,
+                    x.FmsWorkResult.EndDate,
+                    x.FmsWorkResult.ConfirmedDate,
+                    x.FmsWorkResult.ConfirmedUserId,
+                    x.FmsWorkResult.IsConfirmed,
+                    x.FmsWorkResult.CauseClassId,
+                    x.FmsWorkResult.Content,
+                    x.FmsWorkResult.ConfirmDesc,
+                    ConfirmUserName = x.FmsWorkResult.CmUser.Name,
+                    WorkerUserName = x.FmsWorkResult.CmUserNavigation.Name,
+                },
+                FmsWorkRequestToFacility = x.FmsWorkRequestToFacility.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.FacilityCode,
+                    CmFacility = new {
+                        f.CmFacility.Name,
+                        f.CmFacility.BuildingId,
+                        CmBuilding = new {
+                            f.CmFacility.CmBuilding.Name,
+                        },
+                        f.CmFacility.FloorId,
+                        CmFloor = new {
+                            f.CmFacility.CmFloor.Name,
+                        },
+                        f.CmFacility.FirstClassId,
+                        FirstClass = new {
+                            f.CmFacility.FmsFacilityCodeClass.Name,
+                        },
+                        f.CmFacility.SecondClassId,
+                        SecondClass = new {
+                            f.CmFacility.FmsFacilityCodeClass1.Name,
+                        },
+                        f.CmFacility.ThirdClassId,
+                        ThirdClass = new {
+                            f.CmFacility.FmsFacilityCodeClass2.Name,
+                        },
+                    },
+                }).ToList(),
+                FmsWorkRequestToAccident = x.FmsWorkRequestToAccident.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.AccidentId,
+                    FmsAccident = new {
+                        f.FmsAccident.SiteId,
+                        f.FmsAccident.AccidentId,
+                        f.FmsAccident.Name,
+                        f.FmsAccident.AccidentTypeId,
+                        AccidentTypeName = f.FmsAccident.FmsAccidentCodeType.Name,
+                        Dates = new {
+                            StartDate = f.FmsAccident.StartDate == null ? null : ((DateTime)f.FmsAccident.StartDate).ToString("yyyy.MM.dd"),
+                            EndDate = f.FmsAccident.EndDate == null ? null : ((DateTime)f.FmsAccident.EndDate).ToString("yyyy.MM.dd"),
+                        },
+                        f.FmsAccident.DepartmentId,
+                        DepartmentName = f.FmsAccident.CmDepartment.Name,
+                        f.FmsAccident.AccidentLocation,
+                    },
+                }).ToList(),
+                FmsWorkRequestToComplaint = x.FmsWorkRequestToComplaint.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.ComplaintId,
+                    FmsComplaints = new {
+                        f.FmsComplaints.SiteId,
+                        f.FmsComplaints.ComplaintId,
+                        f.FmsComplaints.Name,
+                        f.FmsComplaints.ComplaintTypeId,
+                        ComplaintTypeName = f.FmsComplaints.FmsComplaintCodeType.Name,
+                        Dates = new {
+                            StartDate = f.FmsComplaints.StartDate == null ? null : ((DateTime)f.FmsComplaints.StartDate).ToString("yyyy.MM.dd"),
+                            EndDate = f.FmsComplaints.EndDate == null ? null : ((DateTime)f.FmsComplaints.EndDate).ToString("yyyy.MM.dd"),
+                        },
+                        f.FmsComplaints.DepartmentId,
+                        DepartmentName = f.FmsComplaints.CmDepartment.Name,
+                        f.FmsComplaints.ComplaintLocation,
+                    },
+                }).ToList(),
+            }).FirstOrDefault();
+            return Ok(item);
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsWorkRequest> _FilterAndSort(PagingRequest req) {
+
+            var query = _service.GetAll();
+
+            // 시설유형 검색조건 처리
+            if (req != null && req.conditions != null) {
+                var cond = req.conditions.Where(x => x.field == "FacilityTypeId").FirstOrDefault();
+                if (cond != null && !string.IsNullOrEmpty(cond.op) && !string.IsNullOrEmpty(cond.value)) {
+                    query = query.Where(
+                        x => _context.FmsWorkRequestToFacility.Where(
+                            y => y.SiteId == x.SiteId
+                            && y.WorkRequestId == x.WorkRequestId
+                            && y.CmFacility.FacilityTypeId == Util.ToInt(cond.value)
+                        ).Count() > 0
+                    );
+                }
+            }
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            if (Util.V(req, x => x.sort.field) == "Custom") {
+                query = query.OrderByDescending(x => x.FmsWorkResult.StartDate).ThenBy(x => x.Title);
+            } else {
+                query = query.Sort(req.sort);
+            }
+
+            return query;
+        }
+
+    }
+}

+ 209 - 0
Controllers/App/WorkOrderController.cs

@@ -0,0 +1,209 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class WorkOrderController : Controller {
+        private readonly ILogger<WorkOrderController> _logger;
+        private readonly FMSContext _context;
+        private readonly WorkOrderService _service;
+        private readonly StorageHelper _storage;
+
+        public WorkOrderController(
+            ILogger<WorkOrderController> logger,
+            FMSContext context,
+            WorkOrderService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            var query = _FilterAndSort(req);
+            query = BusinessFieldHelper.Apply<FmsWorkOrder>(_context, User, businessAuth, query);
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && (x.FmsWorkRequest.WorkProgressId == 3 || x.FmsWorkRequest.WorkProgressId == 7) // 작업대기 혹은 보류
+            );
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.FmsWorkRequest.WorkProgressId,
+                WorkProgress = new {
+                    x.FmsWorkRequest.WorkProgress.Name
+                },
+                x.FmsWorkRequest.WorkTypeId,
+                WorkType = new {
+                    x.FmsWorkRequest.WorkType.Name
+                },
+                x.BusinessFieldId,
+                BusinessField = new {
+                    x.CmBusinessField.Name
+                },
+                x.Title,
+                x.Content,
+                x.OrderDate,
+                x.StartWorkDate,
+                x.OrderUserId,
+                OrderUser = new {
+                    x.CmUser.Name
+                },
+                WorkPartnerName = x.FmsWorkRequest.WorkPartner.Name,
+                x.FmsWorkRequest.WorkPrice,
+                x.FmsWorkRequest.WorkItem,
+                x.FmsWorkRequest.WorkEndDate
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string workRequestId) {
+            var query = _service.GetAll()
+                .Where(x => x.SiteId == Util.ToInt(siteId) && x.WorkRequestId == Util.ToInt(workRequestId));
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.FmsWorkRequest.WorkProgressId,
+                WorkProgress = new {
+                    x.FmsWorkRequest.WorkProgress.Name
+                },
+                x.FmsWorkRequest.WorkTypeId,
+                WorkType = new {
+                    x.FmsWorkRequest.WorkType.Name
+                },
+                x.BusinessFieldId,
+                BusinessField = new {
+                    x.CmBusinessField.Name
+                },
+                x.Title,
+                x.Content,
+                RequestContent = x.FmsWorkRequest.Content,
+                x.OrderDate,
+                WorkDueDate = (x.FmsWorkRequest.FmsWorkSchedule == null ? x.FmsWorkRequest.StartWorkDate : x.FmsWorkRequest.FmsWorkSchedule.WorkDueDate),
+                x.StartWorkDate,
+                x.OrderUserId,
+                OrderUser = new {
+                    x.CmUser.Name
+                },
+                x.FmsWorkRequest.WorkPartnerId,
+                WorkPartnerName = x.FmsWorkRequest.WorkPartner.Name,
+                x.FmsWorkRequest.WorkPrice,
+                x.FmsWorkRequest.WorkItem,
+                x.FmsWorkRequest.WorkEndDate,
+                CmFile = _storage.ParseFile(x.FmsWorkRequest.CmFile),
+                FmsWorkOrderToFacility = x.FmsWorkOrderToFacility.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.FacilityCode,
+                    CmFacility = new {
+                        f.CmFacility.Name,
+                        f.CmFacility.BuildingId,
+                        CmBuilding = new {
+                            f.CmFacility.CmBuilding.Name,
+                        },
+                        f.CmFacility.FloorId,
+                        CmFloor = new {
+                            f.CmFacility.CmFloor.Name,
+                        },
+                        f.CmFacility.FirstClassId,
+                        FirstClass = new {
+                            f.CmFacility.FmsFacilityCodeClass.Name,
+                        },
+                        f.CmFacility.SecondClassId,
+                        SecondClass = new {
+                            f.CmFacility.FmsFacilityCodeClass1.Name,
+                        },
+                        f.CmFacility.ThirdClassId,
+                        ThirdClass = new {
+                            f.CmFacility.FmsFacilityCodeClass2.Name,
+                        },
+                    },
+                }).ToList(),
+                FmsWorkRequestToAccident = x.FmsWorkRequest.FmsWorkRequestToAccident.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.AccidentId,
+                    FmsAccident = new {
+                        f.FmsAccident.SiteId,
+                        f.FmsAccident.AccidentId,
+                        f.FmsAccident.Name,
+                        f.FmsAccident.AccidentTypeId,
+                        AccidentTypeName = f.FmsAccident.FmsAccidentCodeType.Name,
+                        Dates = new {
+                            StartDate = f.FmsAccident.StartDate == null ? null : ((DateTime)f.FmsAccident.StartDate).ToString("yyyy.MM.dd"),
+                            EndDate = f.FmsAccident.EndDate == null ? null : ((DateTime)f.FmsAccident.EndDate).ToString("yyyy.MM.dd"),
+                        },
+                        f.FmsAccident.DepartmentId,
+                        DepartmentName = f.FmsAccident.CmDepartment.Name,
+                        f.FmsAccident.AccidentLocation,
+                    },
+                }).ToList(),
+                FmsWorkRequestToComplaint = x.FmsWorkRequest.FmsWorkRequestToComplaint.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.ComplaintId,
+                    FmsComplaints = new {
+                        f.FmsComplaints.SiteId,
+                        f.FmsComplaints.ComplaintId,
+                        f.FmsComplaints.Name,
+                        f.FmsComplaints.ComplaintTypeId,
+                        ComplaintTypeName = f.FmsComplaints.FmsComplaintCodeType.Name,
+                        Dates = new {
+                            StartDate = f.FmsComplaints.StartDate == null ? null : ((DateTime)f.FmsComplaints.StartDate).ToString("yyyy.MM.dd"),
+                            EndDate = f.FmsComplaints.EndDate == null ? null : ((DateTime)f.FmsComplaints.EndDate).ToString("yyyy.MM.dd"),
+                        },
+                        f.FmsComplaints.DepartmentId,
+                        DepartmentName = f.FmsComplaints.CmDepartment.Name,
+                        f.FmsComplaints.ComplaintLocation,
+                    },
+                }).ToList(),
+            }).FirstOrDefault();
+            return Ok(item);
+        }
+
+        [HttpPost("[action]")]
+        public IActionResult Process([FromBody] FmsWorkResult data) {
+            _service.Process(data);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsWorkOrder> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 355 - 0
Controllers/App/WorkRequestController.cs

@@ -0,0 +1,355 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Collections.Generic;
+using FMSApp.Repositories;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class WorkRequestController : Controller {
+        private readonly ILogger<WorkRequestController> _logger;
+        private readonly FMSContext _context;
+        private readonly WorkRequestService _service;
+        private readonly StorageHelper _storage;
+        private readonly WorkConfigRepository _workConfig;
+
+        public WorkRequestController(
+            ILogger<WorkRequestController> logger,
+            FMSContext context,
+            WorkRequestService service,
+            StorageHelper storage,
+            WorkConfigRepository workConfig
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+            _workConfig = workConfig;
+        }
+
+        private IActionResult RequestList(PagingRequest req, string siteId, string workTypeId, bool businessAuth, int[] workProgressIds = null) {
+            var query = _FilterAndSort(req);
+            query = BusinessFieldHelper.Apply<FmsWorkRequest>(_context, User, businessAuth, query);
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && (x.FmsWorkSchedule == null || (x.FmsWorkSchedule != null && x.FmsWorkSchedule.IsUse == true))
+                //&& ((x.FmsWorkSchedule != null && x.FmsWorkSchedule.IsUse == true) || !(new int[] { 1, 2, 7 }).Contains(x.WorkTypeId))
+            );
+            if (workProgressIds == null || workProgressIds.Length == 0) {
+                query = query.Where(x => x.WorkProgressId == 1);
+            } else {
+                query = query.Where(x => workProgressIds.Contains(x.WorkProgressId));
+            }
+            if (!string.IsNullOrEmpty(workTypeId)) {
+                query = query.Where(
+                    x => x.WorkTypeId == Util.ToInt(workTypeId)
+                );
+            }
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.WorkTypeId,
+                WorkType = new {
+                    x.WorkType.Name
+                },
+                x.WorkDetailTypeId,
+                WorkDetailType = new {
+                    x.WorkDetailType.Code,
+                    x.WorkDetailType.Name,
+                },
+                x.BusinessFieldId,
+                BusinessField = new {
+                    x.CmBusinessField.Name
+                },
+                x.WorkScheduleId,
+                x.WorkProgressId,
+                WorkProgress = new {
+                    x.WorkProgress.Name
+                },
+                x.Title,
+                x.Content,
+                x.RequestUserId,
+                CmUserNavigation = new {
+                    x.CmUserNavigation.Name
+                },
+                x.RequestDate,
+                x.StartWorkDate,
+                x.RejectUserId,
+                x.RejectDate,
+                x.RejectReason,
+                x.InspectionAgencyId,
+                InspectionAgency = new {
+                    x.CmPartner.Name
+                },
+                x.WorkPartnerId,
+                WorkPartnerName = x.WorkPartner.Name,
+                x.WorkPrice,
+                x.WorkItem,
+                x.WorkEndDate
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 수시작업계획 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult AnyList([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            return RequestList(req, siteId, "5", businessAuth, new int[] { 1 });  // 계획
+        }
+
+        /// <summary>
+        /// 공사작업계획 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult ConstructList([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            return RequestList(req, siteId, "6", businessAuth, new int[] { 1 });  // 계획
+        }
+
+        /// <summary>
+        /// 실행계획 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, string workTypeId, bool businessAuth) {
+            return RequestList(req, siteId, workTypeId, businessAuth, new int[] { 1 }); // 계획
+        }
+
+        /// <summary>
+        /// 실행계획/수시작업계획/공사작업계획 상세 데이터 조회
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string workRequestId) {
+            var query = _service.GetAll()
+                .Where(x => x.SiteId == Util.ToInt(siteId) && x.WorkRequestId == Util.ToInt(workRequestId));
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.WorkTypeId,
+                WorkType = new {
+                    x.WorkType.Name
+                },
+                x.WorkDetailTypeId,
+                WorkDetailType = new {
+                    x.WorkDetailType.Code,
+                    x.WorkDetailType.Name,
+                },
+                x.BusinessFieldId,
+                BusinessField = new {
+                    x.CmBusinessField.Name
+                },
+                x.WorkScheduleId,
+                x.WorkProgressId,
+                WorkProgress = new {
+                    x.WorkProgress.Name
+                },
+                x.Title,
+                x.Content,
+                x.RequestUserId,
+                CmUserNavigation = new {
+                    x.CmUserNavigation.Name,
+                },
+                x.RequestDate,
+                x.StartWorkDate,
+                x.RejectUserId,
+                x.RejectDate,
+                x.RejectReason,
+                x.InspectionAgencyId,
+                InspectionAgency = new {
+                    x.CmPartner.Name
+                },
+                x.WorkPartnerId,
+                WorkPartnerName = x.WorkPartner.Name,
+                x.WorkPrice,
+                x.WorkItem,
+                x.WorkEndDate,
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.FmsWorkSchedule.CycleSize,
+                x.FmsWorkSchedule.CycleUnitId,
+                CycleUnitName = x.FmsWorkSchedule.FmsWorkCodeCycleUnit.Name ?? "",
+                x.FmsWorkSchedule.HolidayWorkTypeId,
+                HolidayWorkTypeName = x.FmsWorkSchedule.FmsWorkCodeHolidayWorkType.Name ?? "",
+                FmsWorkRequestToFacility = x.FmsWorkRequestToFacility.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.FacilityCode,
+                    CmFacility = new {
+                        f.CmFacility.Name,
+                        f.CmFacility.BuildingId,
+                        CmBuilding = new {
+                            f.CmFacility.CmBuilding.Name,
+                        },
+                        f.CmFacility.FloorId,
+                        CmFloor = new {
+                            f.CmFacility.CmFloor.Name,
+                        },
+                        f.CmFacility.FirstClassId,
+                        FirstClass = new {
+                            f.CmFacility.FmsFacilityCodeClass.Name,
+                        },
+                        f.CmFacility.SecondClassId,
+                        SecondClass = new {
+                            f.CmFacility.FmsFacilityCodeClass1.Name,
+                        },
+                        f.CmFacility.ThirdClassId,
+                        ThirdClass = new {
+                            f.CmFacility.FmsFacilityCodeClass2.Name,
+                        },
+                    },
+                }).ToList(),
+                FmsWorkRequestToAccident = x.FmsWorkRequestToAccident.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.AccidentId,
+                    FmsAccident = new {
+                        f.FmsAccident.SiteId,
+                        f.FmsAccident.AccidentId,
+                        f.FmsAccident.Name,
+                        f.FmsAccident.AccidentTypeId,
+                        AccidentTypeName = f.FmsAccident.FmsAccidentCodeType.Name,
+                        Dates = new {
+                            StartDate = f.FmsAccident.StartDate == null ? null : ((DateTime)f.FmsAccident.StartDate).ToString("yyyy.MM.dd"),
+                            EndDate = f.FmsAccident.EndDate == null ? null : ((DateTime)f.FmsAccident.EndDate).ToString("yyyy.MM.dd"),
+                        },
+                        f.FmsAccident.DepartmentId,
+                        DepartmentName = f.FmsAccident.CmDepartment.Name,
+                        f.FmsAccident.AccidentLocation,
+                    },
+                }).ToList(),
+                FmsWorkRequestToComplaint = x.FmsWorkRequestToComplaint.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.ComplaintId,
+                    FmsComplaints = new {
+                        f.FmsComplaints.SiteId,
+                        f.FmsComplaints.ComplaintId,
+                        f.FmsComplaints.Name,
+                        f.FmsComplaints.ComplaintTypeId,
+                        AccidentTypeName = f.FmsComplaints.FmsComplaintCodeType.Name,
+                        Dates = new {
+                            StartDate = f.FmsComplaints.StartDate == null ? null : ((DateTime)f.FmsComplaints.StartDate).ToString("yyyy.MM.dd"),
+                            EndDate = f.FmsComplaints.EndDate == null ? null : ((DateTime)f.FmsComplaints.EndDate).ToString("yyyy.MM.dd"),
+                        },
+                        f.FmsComplaints.DepartmentId,
+                        DepartmentName = f.FmsComplaints.CmDepartment.Name,
+                        f.FmsComplaints.ComplaintLocation,
+                    },
+                }).ToList()
+            }).FirstOrDefault();
+            return Ok(item);
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] FmsWorkRequest data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            ModelState.RemoveStartOf("FmsWorkRequestToFacility");
+            ModelState.RemoveStartOf("FmsWorkRequestToAccident");
+            ModelState.RemoveStartOf("FmsWorkRequestToComplaint");
+
+            if (ModelState.IsValid) {
+                //_service.Save(data);
+                _service.SaveBeforeAddHistory(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 사고 내역 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult SaveAccidents([FromBody] FmsWorkRequest data) {
+            ModelState.Remove("Title");
+            ModelState.RemoveStartOf("FmsWorkRequestToFacility");
+            ModelState.RemoveStartOf("FmsWorkRequestToAccident");
+            ModelState.RemoveStartOf("FmsWorkRequestToComplaint");
+
+            if (ModelState.IsValid) {
+                _service.SaveAccidents(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 민원 내역 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult SaveComplaints([FromBody] FmsWorkRequest data) {
+            ModelState.Remove("Title");
+            ModelState.RemoveStartOf("FmsWorkRequestToFacility");
+            ModelState.RemoveStartOf("FmsWorkRequestToAccident");
+            ModelState.RemoveStartOf("FmsWorkRequestToComplaint");
+
+            if (ModelState.IsValid) {
+                _service.SaveComplaints(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpPost("[action]")]
+        public IActionResult Reject([FromBody] FmsWorkRequest data, [FromQuery] bool useSchedule, [FromQuery] DateTime? confirmDate) {
+            _logger.LogInformation($"###########################################################################");
+            _logger.LogInformation($"useSchedule: {useSchedule}, confirmDate: {confirmDate}");
+            _service.Reject(data, useSchedule, confirmDate);
+            return Ok();
+        }
+
+        [HttpPost("[action]")]
+        public IActionResult Order([FromBody] FmsWorkOrder data) {
+            _service.Order(data);
+            return Ok();
+        }
+
+        [HttpDelete("{siteId}/{workRequestId}")]
+        public IActionResult Delete(int siteId, int workRequestId) {
+            _service.Delete(siteId, workRequestId);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsWorkRequest> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 479 - 0
Controllers/App/WorkResultController.cs

@@ -0,0 +1,479 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Collections.Generic;
+using LinqKit;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class WorkResultController : Controller {
+        private readonly ILogger<WorkResultController> _logger;
+        private readonly FMSContext _context;
+        private readonly WorkResultService _service;
+        private readonly StorageHelper _storage;
+
+        public WorkResultController(
+            ILogger<WorkResultController> logger,
+            FMSContext context,
+            WorkResultService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 작업진행목록 / 작업완료목록 조회
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] string workProgressId, bool businessAuth) {
+            int nWorkProgressId = 0;
+            int.TryParse(workProgressId, out nWorkProgressId);
+            if (nWorkProgressId != 0 && nWorkProgressId != 4 && nWorkProgressId != 5) {
+                throw new ServiceException("입력 파라메터가 잘못되었습니다.(WorkProgress:0/4/5)");
+            }
+
+            var query = _FilterAndSort(req);
+            query = BusinessFieldHelper.Apply<FmsWorkResult>(_context, User, businessAuth, query, "FmsWorkOrder");
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+            );
+            if (nWorkProgressId == 4) {
+                // 작업진행 목록에서는 작업진행(4), 작업보류(7) 데이터를 조회
+                query = query.Where(x => (x.FmsWorkRequest.WorkProgressId == 4 || x.FmsWorkRequest.WorkProgressId == 7));
+            }
+            if (nWorkProgressId == 5) {
+                // 작업완료 목록에서는 작업완료(5) 데이터를 조회
+                query = query.Where(x => x.FmsWorkRequest.WorkProgressId == 5);
+            }
+            if (nWorkProgressId == 0) {
+                // 작업진행/작업완료 통합에 의해 작업진행(4), 작업완료(5), 작업보류 데이터조회 되도록
+                query = query.Where(x => (new int[] { 4, 5, 7 }).Contains(x.FmsWorkRequest.WorkProgressId));
+            }
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.FmsWorkRequest.WorkProgressId,
+                WorkProgress = new {
+                    x.FmsWorkRequest.WorkProgress.Name
+                },
+                x.FmsWorkRequest.WorkTypeId,
+                WorkType = new {
+                    x.FmsWorkRequest.WorkType.Name
+                },
+                x.FmsWorkOrder.BusinessFieldId,
+                BusinessField = new {
+                    x.FmsWorkOrder.CmBusinessField.Name
+                },
+                x.FmsWorkOrder.Title,
+                x.Content,
+                OrderContent = x.FmsWorkOrder.Content,
+                RequestContent = x.FmsWorkRequest.Content,
+                x.StartDate,
+                x.EndDate,
+                x.FmsWorkOrder.OrderDate,
+                x.FmsWorkOrder.StartWorkDate,
+                x.FmsWorkOrder.OrderUserId,
+                OrderUser = new {
+                    x.FmsWorkOrder.CmUser.Name
+                },
+                x.WorkerUserId,
+                WorkerUser = new {
+                    x.CmUserNavigation.Name
+                },
+                x.IsConfirmed,
+                x.ConfirmedUserId,
+                ConfirmedUser = new {
+                    x.CmUser.Name
+                },
+                WorkPartnerName = x.FmsWorkRequest.WorkPartner.Name,
+                x.FmsWorkRequest.WorkPrice,
+                x.FmsWorkRequest.WorkItem,
+                x.FmsWorkRequest.WorkEndDate,
+                HasCheckItem = x.FmsWorkResultCheckItem.Any()
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 작업진행달력
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult CalendarList([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] int year, [FromQuery] int month, bool businessAuth) {
+            DateTime target = new DateTime(year, month, 1);
+            DateTime start = target.AddMonths(-1);
+            DateTime end = target.AddMonths(2).AddDays(-1);
+
+            var query = _FilterAndSort(req);
+            query = BusinessFieldHelper.Apply<FmsWorkResult>(_context, User, businessAuth, query, "FmsWorkOrder");
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && (new int[] { 4, 7 }).Contains(x.FmsWorkRequest.WorkProgressId)
+                && x.StartDate <= end
+                && x.EndDate >= start
+            );
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.FmsWorkRequest.WorkProgressId,
+                WorkProgress = new {
+                    x.FmsWorkRequest.WorkProgress.Name
+                },
+                x.FmsWorkRequest.WorkTypeId,
+                WorkTypeName = x.FmsWorkRequest.WorkType.Name,
+                x.FmsWorkOrder.BusinessFieldId,
+                BusinessField = new {
+                    x.FmsWorkOrder.CmBusinessField.Name
+                },
+                x.FmsWorkOrder.Title,
+                x.Content,
+                OrderContent = x.FmsWorkOrder.Content,
+                RequestContent = x.FmsWorkRequest.Content,
+                x.StartDate,
+                x.EndDate,
+                x.FmsWorkOrder.OrderDate,
+                x.FmsWorkOrder.StartWorkDate,
+                x.FmsWorkOrder.OrderUserId,
+                OrderUser = new {
+                    x.FmsWorkOrder.CmUser.Name
+                },
+                x.WorkerUserId,
+                WorkerUser = new {
+                    x.CmUserNavigation.Name
+                },
+                x.IsConfirmed,
+                x.ConfirmedUserId,
+                ConfirmedUser = new {
+                    x.CmUser.Name
+                },
+            });
+            return Ok(list);
+        }
+
+        /// <summary>
+        /// 항목조회
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] string workRequestId) {
+            var query = _service.GetAll()
+                .Where(x => x.SiteId == Util.ToInt(siteId) && x.WorkRequestId == Util.ToInt(workRequestId));
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.FmsWorkRequest.WorkScheduleId,
+                x.FmsWorkRequest.WorkProgressId,
+                x.FmsWorkRequest.WorkDetailTypeId,
+                WorkDetailType = new {
+                    x.FmsWorkRequest.WorkDetailType.Code,
+                    x.FmsWorkRequest.WorkDetailType.Name,
+                },
+                WorkProgress = new {
+                    x.FmsWorkRequest.WorkProgress.Name
+                },
+                x.FmsWorkRequest.WorkTypeId,
+                WorkType = new {
+                    x.FmsWorkRequest.WorkType.Name
+                },
+                x.FmsWorkOrder.BusinessFieldId,
+                BusinessField = new {
+                    x.FmsWorkOrder.CmBusinessField.Name
+                },
+                x.FmsWorkOrder.Title,
+                x.Content,
+                FmsWorkOrder = new {
+                    x.FmsWorkOrder.Content,
+                },
+                FmsWorkRequest = new {
+                    x.FmsWorkRequest.Content,
+                },
+                x.StartDate,
+                x.EndDate,
+                x.FmsWorkOrder.OrderDate,
+                x.FmsWorkOrder.StartWorkDate,
+                WorkDueDate = (x.FmsWorkRequest.FmsWorkSchedule == null ? x.FmsWorkRequest.StartWorkDate : x.FmsWorkRequest.FmsWorkSchedule.WorkDueDate),
+                x.FmsWorkOrder.OrderUserId,
+                OrderUser = new {
+                    x.FmsWorkOrder.CmUser.Name
+                },
+                x.WorkerUserId,
+                WorkerUser = new {
+                    x.CmUserNavigation.Name
+                },
+                x.IsConfirmed,
+                x.ConfirmedUserId,
+                ConfirmedUser = new {
+                    x.CmUser.Name
+                },
+                x.ConfirmDesc,
+                x.FmsWorkRequest.WorkPartnerId,
+                WorkPartnerName = x.FmsWorkRequest.WorkPartner.Name,
+                x.FmsWorkRequest.WorkPrice,
+                x.FmsWorkRequest.WorkItem,
+                x.FmsWorkRequest.WorkEndDate,
+                CmFile = _storage.ParseFile(x.FmsWorkRequest.CmFile),
+                HasCheckItem = x.FmsWorkResultCheckItem.Any(),
+                FmsWorkResultToFacility = x.FmsWorkResultToFacility.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.FacilityCode,
+                    CmFacility = new {
+                        f.CmFacility.Name,
+                        f.CmFacility.BuildingId,
+                        CmBuilding = new {
+                            f.CmFacility.CmBuilding.Name,
+                        },
+                        f.CmFacility.FloorId,
+                        CmFloor = new {
+                            f.CmFacility.CmFloor.Name,
+                        },
+                        f.CmFacility.FirstClassId,
+                        FirstClass = new {
+                            f.CmFacility.FmsFacilityCodeClass.Name,
+                        },
+                        f.CmFacility.SecondClassId,
+                        SecondClass = new {
+                            f.CmFacility.FmsFacilityCodeClass1.Name,
+                        },
+                        f.CmFacility.ThirdClassId,
+                        ThirdClass = new {
+                            f.CmFacility.FmsFacilityCodeClass2.Name,
+                        },
+                    },
+                }).ToList(),
+                FmsWorkRequestToAccident = x.FmsWorkRequest.FmsWorkRequestToAccident.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.AccidentId,
+                    FmsAccident = new {
+                        f.FmsAccident.SiteId,
+                        f.FmsAccident.AccidentId,
+                        f.FmsAccident.Name,
+                        f.FmsAccident.AccidentTypeId,
+                        AccidentTypeName = f.FmsAccident.FmsAccidentCodeType.Name,
+                        Dates = new {
+                            StartDate = f.FmsAccident.StartDate == null ? null : ((DateTime)f.FmsAccident.StartDate).ToString("yyyy.MM.dd"),
+                            EndDate = f.FmsAccident.EndDate == null ? null : ((DateTime)f.FmsAccident.EndDate).ToString("yyyy.MM.dd"),
+                        },
+                        f.FmsAccident.DepartmentId,
+                        DepartmentName = f.FmsAccident.CmDepartment.Name,
+                        f.FmsAccident.AccidentLocation,
+                    },
+                }).ToList(),
+                FmsWorkRequestToComplaint = x.FmsWorkRequest.FmsWorkRequestToComplaint.Select(f => new {
+                    f.SiteId,
+                    f.WorkRequestId,
+                    f.ComplaintId,
+                    FmsComplaints = new {
+                        f.FmsComplaints.SiteId,
+                        f.FmsComplaints.ComplaintId,
+                        f.FmsComplaints.Name,
+                        f.FmsComplaints.ComplaintTypeId,
+                        ComplaintTypeName = f.FmsComplaints.FmsComplaintCodeType.Name,
+                        Dates = new {
+                            StartDate = f.FmsComplaints.StartDate == null ? null : ((DateTime)f.FmsComplaints.StartDate).ToString("yyyy.MM.dd"),
+                            EndDate = f.FmsComplaints.EndDate == null ? null : ((DateTime)f.FmsComplaints.EndDate).ToString("yyyy.MM.dd"),
+                        },
+                        f.FmsComplaints.DepartmentId,
+                        DepartmentName = f.FmsComplaints.CmDepartment.Name,
+                        f.FmsComplaints.ComplaintLocation,
+                    },
+                }).ToList(),
+            }).FirstOrDefault();
+            return Ok(item);
+        }
+
+        /// <summary>
+        /// 작업내 사용 자재목록 조회
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult Materials([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] string workRequestId) {
+            var query = _context.FmsWorkResultCheckItemMaterial as IQueryable<FmsWorkResultCheckItemMaterial>;
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.WorkRequestId == Util.ToInt(workRequestId)
+            );
+            var list = query.Select(x => new {
+                x.FmsMaterial.Name,
+                x.FmsMaterial.Standard,
+                x.MaterialCount,
+            });
+            return Ok(list);
+        }
+
+        /// <summary>
+        /// 작업이력 조회
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult CheckItems([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] string workRequestId) {
+            var query = _context.FmsWorkResultCheckItem as IQueryable<FmsWorkResultCheckItem>;
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.WorkRequestId == Util.ToInt(workRequestId)
+            );
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.CheckItemId,
+                x.CheckDate,
+                x.TextResult,
+                x.BoolResult,
+                CmFile1 = _storage.ParseFile(x.CmFile1),
+                CmFile2 = _storage.ParseFile(x.CmFile2),
+            });
+            return Ok(list);
+        }
+
+        /// <summary>
+        /// 작업이력 항목 조회 (이력 내 사용 자재목록 포함하여 조회)
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult GetCheckItem([FromQuery] string siteId, [FromQuery] string workRequestId, [FromQuery] string checkItemId) {
+            var query = _context.FmsWorkResultCheckItem.AsExpandable() as IQueryable<FmsWorkResultCheckItem>;
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.WorkRequestId == Util.ToInt(workRequestId)
+                && x.CheckItemId == Util.ToInt(checkItemId)
+            );
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.CheckItemId,
+                x.CheckDate,
+                x.TextResult,
+                x.BoolResult,
+                CmFile1 = _storage.ParseFile(x.CmFile1),
+                CmFile2 = _storage.ParseFile(x.CmFile2),
+                WorkProgressId = x.FmsWorkResult.FmsWorkRequest.WorkProgressId,
+                FmsWorkResultCheckItemMaterial = x.FmsWorkResultCheckItemMaterial.Select(f => new {
+                    f.CheckItemId,
+                    f.CheckItemMaterialId,
+                    f.MaterialId,
+                    f.MaterialCode,
+                    f.MaterialCount,
+                    f.Name,
+                    FmsMaterial = new {
+                        f.FmsMaterial.MaterialId,
+                        f.FmsMaterial.MaterialCode,
+                        f.FmsMaterial.Name,
+                        f.FmsMaterial.Standard,
+                        f.FmsMaterial.Unit,
+                    },
+                    StockCount = FmsMaterial.GetStockCount.Invoke(f.FmsMaterial),
+                    StockAmount = FmsMaterial.GetStockAmount.Invoke(f.FmsMaterial),
+                }).ToList()
+            }).FirstOrDefault();
+            return Ok(item);
+        }
+
+        /// <summary>
+        /// 작업이력 저장 (완료처리 및 보류처리 포함)
+        /// </summary>
+        [HttpPost("[action]")]
+        public IActionResult SaveCheckItem([FromBody] FmsWorkResultCheckItem data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            ModelState.RemoveStartOf("CmFile1");
+            ModelState.RemoveStartOf("CmFile2");
+            ModelState.RemoveStartOf("FmsWorkResultCheckItemMaterial");
+            if (ModelState.IsValid) {
+                _service.SaveCheckItem(data, User.Identity.Name);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 작업진행(보류해제)
+        /// </summary>
+        [HttpPost("[action]")]
+        public IActionResult Continue([FromBody] FmsWorkResult data) {
+            _service.Continue(data);
+            return Ok();
+        }
+
+        /// <summary>
+        /// 작업완료
+        /// </summary>
+        [HttpPost("[action]")]
+        public IActionResult Complete([FromBody] FmsWorkResult data) {
+            _service.CompleteWithAdditionalData(data);
+            return Ok();
+        }
+
+        /// <summary>
+        /// 작업보류
+        /// </summary>
+        [HttpPost("[action]")]
+        public IActionResult Hold([FromBody] FmsWorkRequest data) {
+            _service.Hold(data);
+            return Ok();
+        }
+
+        /// <summary>
+        /// 승인
+        /// </summary>
+        [HttpPost("[action]")]
+        public IActionResult Confirm([FromBody] FmsWorkResult data, [FromQuery] DateTime? date) {
+            _service.Confirm(data, date);
+            return Ok();
+        }
+
+        /// <summary>
+        /// 차기작업 확정일자 및 현작업 종료일자 조회
+        /// { WorkDueDate, EndDate }
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult GetAvailDueDate([FromQuery] string siteId, [FromQuery] string workRequestId) {
+            return Ok(_service.GetAvailDueDate(Util.ToInt(siteId), Util.ToInt(workRequestId)));
+        }
+
+        [HttpDelete("{siteId}/{workRequestId}")]
+        public IActionResult Delete(int siteId, int workRequestId) {
+            _service.Delete(siteId, workRequestId);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsWorkResult> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            if (Util.V(req, x => x.sort.field) == "Custom") {
+                query = query.OrderByDescending(x => x.StartDate).ThenBy(x => x.FmsWorkRequest.Title);
+            } else {
+                query = query.Sort(req.sort);
+            }
+
+            return query;
+        }
+
+    }
+}

+ 390 - 0
Controllers/App/WorkScheduleController.cs

@@ -0,0 +1,390 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace FMSApp.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/app/[controller]")]
+    public class WorkScheduleController : Controller {
+        private readonly ILogger<WorkScheduleController> _logger;
+        private readonly FMSContext _context;
+        private readonly WorkScheduleService _service;
+        private readonly WorkRequestService _requestService;
+        private readonly StorageHelper _storage;
+
+        public WorkScheduleController(
+            ILogger<WorkScheduleController> logger,
+            FMSContext context,
+            WorkScheduleService service,
+            WorkRequestService requestService,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _requestService = requestService;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 정기점검 계획 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult RegularListFromRequest([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            return ListFromRequest(req, siteId, new int[] { 1 }, businessAuth);
+        }
+
+        /// <summary>
+        /// 법정검사/법정교육 계획 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult LegalListFromRequest([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            return ListFromRequest(req, siteId, new int[] { 2, 7 }, businessAuth);
+        }
+
+        private IActionResult ListFromRequest(PagingRequest req, string siteId, int[] workTypeIds, bool businessAuth) {
+            var query = _FilterAndSortFromRequest(req);
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && workTypeIds.Contains(x.WorkTypeId)
+                && x.WorkProgressId == 1
+            );
+            query = BusinessFieldHelper.Apply<FmsWorkRequest>(_context, User, businessAuth, query);
+
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.WorkRequestId,
+                x.WorkTypeId,
+                WorkType = new {
+                    x.WorkType.Name
+                },
+                x.BusinessFieldId,
+                BusinessField = new {
+                    x.CmBusinessField.Name
+                },
+                x.InspectionAgencyId,
+                InspectionAgency = new {
+                    x.CmPartner.Name
+                },
+                x.Title,
+                x.StartWorkDate,
+                x.RequestUserId,
+                WorkPartnerName = x.WorkPartner.Name,
+                x.WorkPrice,
+                x.WorkItem,
+                x.WorkEndDate,
+                x.FmsWorkSchedule.WorkScheduleId,
+                x.FmsWorkSchedule.CycleSize,
+                x.FmsWorkSchedule.CycleUnitId,
+                CycleUnitName = x.FmsWorkSchedule.FmsWorkCodeCycleUnit.Name,
+                x.FmsWorkSchedule.HolidayWorkTypeId,
+                HolidayWorkTypeName = x.FmsWorkSchedule.FmsWorkCodeHolidayWorkType.Name,
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        private IQueryable<FmsWorkRequest> _FilterAndSortFromRequest(PagingRequest req) {
+            var query = _requestService.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult RegularFromRequest([FromQuery] string siteId, [FromQuery] string workScheduleId) {
+            return GetFromRequest(siteId, workScheduleId, 1);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult LegalFromRequest([FromQuery] string siteId, [FromQuery] string workScheduleId) {
+            return GetFromRequest(siteId, workScheduleId, 2);
+        }
+
+        private IActionResult GetFromRequest(string siteId, string workScheduleId, int workTypeId) {
+            var query = _requestService.GetAll()
+                .Where(x => x.SiteId == Util.ToInt(siteId) && x.WorkScheduleId == Util.ToInt(workScheduleId));
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.WorkScheduleId,
+                x.WorkTypeId,
+                WorkType = new {
+                    x.WorkType.Name
+                },
+                x.BusinessFieldId,
+                BusinessField = new {
+                    x.CmBusinessField.Name
+                },
+                x.InspectionAgencyId,
+                InspectionAgency = new {
+                    x.CmPartner.Name
+                },
+                x.Title,
+                x.StartWorkDate,
+                x.RequestUserId,
+                x.WorkPartnerId,
+                WorkPartnerName = x.WorkPartner.Name,
+                x.WorkPrice,
+                x.WorkItem,
+                x.WorkEndDate,
+                CmFile = _storage.ParseFile(x.CmFile),
+                FmsWorkRequestToFacility = x.FmsWorkRequestToFacility.Select(f => new {
+                    f.SiteId,
+                    f.FacilityCode,
+                    CmFacility = new {
+                        f.CmFacility.Name,
+                        f.CmFacility.BuildingId,
+                        CmBuilding = new {
+                            f.CmFacility.CmBuilding.Name,
+                        },
+                        f.CmFacility.FloorId,
+                        CmFloor = new {
+                            f.CmFacility.CmFloor.Name,
+                        },
+                        f.CmFacility.FirstClassId,
+                        FirstClass = new {
+                            f.CmFacility.FmsFacilityCodeClass.Name,
+                        },
+                        f.CmFacility.SecondClassId,
+                        SecondClass = new {
+                            f.CmFacility.FmsFacilityCodeClass1.Name,
+                        },
+                        f.CmFacility.ThirdClassId,
+                        ThirdClass = new {
+                            f.CmFacility.FmsFacilityCodeClass2.Name,
+                        },
+                    },
+                }).ToList()
+            }).FirstOrDefault();
+            return Ok(item);
+        }
+
+        [HttpPost("[action]")]
+        public IActionResult SaveExpectedRequest([FromBody] FmsWorkSchedule data) {
+            _logger.LogInformation("SaveExpectedRequest");
+            //_logger.LogInformation(data.Dump());
+
+            ModelState.RemoveStartOf("FmsWorkScheduleToFacility");
+            ModelState.RemoveStartOf("CmFile");
+
+            if (ModelState.IsValid) {
+                _service.SaveExpectedRequest(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+
+
+
+
+
+
+
+        /// <summary>
+        /// 정기점검 계획 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult RegularList([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            return List(req, siteId, new int[] { 1 }, businessAuth);
+        }
+
+        /// <summary>
+        /// 법정검사/법정교육 계획 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult LegalList([FromQuery] PagingRequest req, [FromQuery] string siteId, bool businessAuth) {
+            return List(req, siteId, new int[] { 2, 7 }, businessAuth);
+        }
+
+        private IActionResult List(PagingRequest req, string siteId, int[] workTypeIds, bool businessAuth) {
+            var query = _FilterAndSort(req);
+            query = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && workTypeIds.Contains(x.WorkTypeId)
+            );
+            query = BusinessFieldHelper.Apply<FmsWorkSchedule>(_context, User, businessAuth, query);
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.WorkScheduleId,
+                x.WorkTypeId,
+                WorkType = new {
+                    x.WorkType.Name
+                },
+                x.BusinessFieldId,
+                BusinessField = new {
+                    x.CmBusinessField.Name
+                },
+                x.InspectionAgencyId,
+                InspectionAgency = new {
+                    x.CmPartner.Name
+                },
+                x.Name,
+                x.CycleSize,
+                x.CycleUnitId,
+                CycleUnit = new {
+                    x.FmsWorkCodeCycleUnit.Name
+                },
+                x.HolidayWorkTypeId,
+                HolidayWorkType = new {
+                    x.FmsWorkCodeHolidayWorkType.Name
+                },
+                x.WorkDueDate,
+                x.UpdateUserId,
+                x.WorkPartnerName,
+                x.WorkPrice,
+                x.WorkItem,
+                x.IsUse,
+                x.IsAutoOrder,
+            });
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Regular([FromQuery] string siteId, [FromQuery] string workScheduleId) {
+            return Get(siteId, workScheduleId, 1);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Legal([FromQuery] string siteId, [FromQuery] string workScheduleId) {
+            return Get(siteId, workScheduleId, 2);
+        }
+
+        private IActionResult Get(string siteId, string workScheduleId, int workTypeId) {
+            var query = _service.GetAll()
+                .Where(x => x.SiteId == Util.ToInt(siteId) && x.WorkScheduleId == Util.ToInt(workScheduleId));
+            var item = query.Select(x => new {
+                x.SiteId,
+                x.WorkScheduleId,
+                x.WorkTypeId,
+                WorkType = new {
+                    x.WorkType.Name
+                },
+                x.BusinessFieldId,
+                BusinessField = new {
+                    x.CmBusinessField.Name
+                },
+                x.InspectionAgencyId,
+                InspectionAgency = new {
+                    x.CmPartner.Name
+                },
+                x.Name,
+                x.CycleSize,
+                x.CycleUnitId,
+                CycleUnit = new {
+                    x.FmsWorkCodeCycleUnit.Name
+                },
+                x.HolidayWorkTypeId,
+                HolidayWorkType = new {
+                    x.FmsWorkCodeHolidayWorkType.Name
+                },
+                x.WorkDueDate,
+                x.UpdateUserId,
+                x.WorkPartnerName,
+                x.WorkPrice,
+                x.WorkItem,
+                x.IsUse,
+                x.IsAutoOrder,
+                FmsWorkScheduleToFacility = x.FmsWorkScheduleToFacility.Select(f => new {
+                    f.SiteId,
+                    f.WorkScheduleId,
+                    f.FacilityCode,
+                    CmFacility = new {
+                        f.CmFacility.Name,
+                        f.CmFacility.BuildingId,
+                        CmBuilding = new {
+                            f.CmFacility.CmBuilding.Name,
+                        },
+                        f.CmFacility.FloorId,
+                        CmFloor = new {
+                            f.CmFacility.CmFloor.Name,
+                        },
+                        f.CmFacility.FirstClassId,
+                        FirstClass = new {
+                            f.CmFacility.FmsFacilityCodeClass.Name,
+                        },
+                        f.CmFacility.SecondClassId,
+                        SecondClass = new {
+                            f.CmFacility.FmsFacilityCodeClass1.Name,
+                        },
+                        f.CmFacility.ThirdClassId,
+                        ThirdClass = new {
+                            f.CmFacility.FmsFacilityCodeClass2.Name,
+                        },
+                    },
+                }).ToList()
+            }).FirstOrDefault();
+            return Ok(item);
+        }
+
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] FmsWorkSchedule data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            ModelState.RemoveStartOf("FmsWorkScheduleToFacility");
+
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpDelete("[action]/{siteId}/{workRequestId}")]
+        public IActionResult DeleteWithResult(int siteId, int workRequestId) {
+            _service.DeleteWithResult(siteId, workRequestId);
+            return Ok();
+        }
+
+        [HttpDelete("{siteId}/{workScheduleId}")]
+        public IActionResult Delete(int siteId, int workScheduleId) {
+            _service.Delete(siteId, workScheduleId);
+            return Ok();
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult GetUpdateScheduleWarningMessage([FromQuery] string siteId, [FromQuery] string workScheduleId) {
+            return Ok(_service.GetUpdateScheduleWarningMessage(Util.ToInt(siteId), Util.ToInt(workScheduleId)));
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsWorkSchedule> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 178 - 0
Controllers/BuildingController.cs

@@ -0,0 +1,178 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class BuildingController : Controller {
+        private readonly ILogger<BuildingController> _logger;
+        private readonly FMSContext _context;
+        private BuildingService _service;
+
+        public BuildingController(
+            ILogger<BuildingController> logger,
+            FMSContext context,
+            BuildingService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmBuilding> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmBuilding> query) {
+            var list = query.Select(x => new {
+                x.BuildingId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.Name,
+                x.IsUse,
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.BuildingId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.Name,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] CmBuilding data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.BuildingId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmBuilding data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+            return Ok();
+        }
+
+    }
+}

+ 191 - 0
Controllers/BusinessFieldController.cs

@@ -0,0 +1,191 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class BusinessFieldController : Controller {
+        private readonly ILogger<BusinessFieldController> _logger;
+        private readonly FMSContext _context;
+        private readonly BusinessFieldService _service;
+        private readonly StorageHelper _storage;
+
+        public BusinessFieldController(
+            ILogger<BusinessFieldController> logger,
+            FMSContext context,
+            BusinessFieldService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 그룹 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        //[HttpGet("{siteId}/{id}")]
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Gets(id);
+            if (data.Count() == 0) {
+                return NotFound("업무분야 정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.BusinessFieldId,
+                x.Name,
+                x.IsUse,
+            });
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        //[HttpPost("{siteId}/{id}")]
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] CmBusinessField data) {
+            if (id != data.BusinessFieldId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmBusinessField data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        //[HttpDelete("{siteId}/{id}")]
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<CmBusinessField> query) {
+            var list = query.Select(x => new {
+                x.BusinessFieldId,
+                x.Name,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N"
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmBusinessField> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+    }
+}

+ 193 - 0
Controllers/CertificateStatusController.cs

@@ -0,0 +1,193 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class CertificateStatusController : Controller {
+        private readonly ILogger<CertificateStatusController> _logger;
+        private readonly FMSContext _context;
+        private CertificateStatusService _service;
+        private readonly StorageHelper _storage; public CertificateStatusController(
+           ILogger<CertificateStatusController> logger,
+           FMSContext context,
+           CertificateStatusService service,
+            StorageHelper storage
+       ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmCertificateStatus> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+            // 고정정렬 (월->사옥 준공순)
+
+            var sortQuery = query.OrderByDescending(x => x.Year);
+            sortQuery = sortQuery.ThenByDescending(x => x.Month);
+            sortQuery = sortQuery.ThenBy(x => x.CmSite.SortOrderNo);
+            sortQuery = sortQuery.ThenSort(req.sort);
+            return sortQuery;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmCertificateStatus> query) {
+            var list = query.Select(x => new {
+                x.CertificateStatusId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.Year,
+                x.Month,
+                Title = x.Year.ToString() + "년 " + x.Month.ToString() + "월 자격증현황",
+                FilePath = _storage.getFilePath(x.CmFile),
+                FileName = x.CmFile.Name,
+                FilePath2 = _storage.getFilePath(x.CmFile2),
+                FileName2 = x.CmFile2.Name
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.CertificateStatusId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.Year,
+                x.Month,
+                CmUser = new { x.CmUser.Name },
+                CmFile = _storage.ParseFile(x.CmFile),
+                CmFile2 = _storage.ParseFile(x.CmFile2),
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] CmCertificateStatus data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+
+            if (id != data.CertificateStatusId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+            ModelState.Remove("RegisterUserId");
+            if (ModelState.IsValid) {
+                data.UpdateUserId = User.Identity.Name;
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmCertificateStatus data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+            ModelState.Remove("RegisterUserId");
+            if (ModelState.IsValid) {
+                data.RegisterUserId = User.Identity.Name;
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+            return Ok();
+        }
+
+    }
+}

+ 224 - 0
Controllers/CompanyController.cs

@@ -0,0 +1,224 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class CompanyController : Controller {
+        private readonly ILogger<CompanyController> _logger;
+        private readonly FMSContext _context;
+        private readonly CompanyService _service;
+        private readonly StorageHelper _storage;
+
+        public CompanyController(
+            ILogger<CompanyController> logger,
+            FMSContext context,
+            CompanyService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 그룹 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("회사정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.CompanyId,
+                x.SiteId,
+                CmSite = new {
+                    x.Site.Name,
+                },
+                x.CompanyTypeId,
+                CmCompanyType = new {
+                    x.CmCompanyType.Name,
+                },
+                CompanyTypeName = x.CmCompanyType.Name,
+                x.Name,
+                x.PhoneNo,
+                x.FaxNo,
+                x.RepresentativeName,
+                x.SalesName,
+                x.SalesPhoneNo,
+                x.AddressZip1,
+                x.AddressZip2,
+                x.Address1,
+                x.Adderss2,
+                x.Homepage,
+                x.Comment,
+                x.IsUse,
+            });
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] CmCompany data) {
+            if (id != data.CompanyId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmCompany data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<CmCompany> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = x.Site.Name,
+                x.CompanyId,
+                x.CompanyTypeId,
+                CompanyTypeName = x.CmCompanyType.Name,
+                x.Name,
+                x.RepresentativeName,
+                x.PhoneNo,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N"
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmCompany> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            // 고정정렬 (사옥 준공순)
+            var sortQuery = query.OrderBy(x => x.Site.SortOrderNo);
+
+            // 추가 정렬
+            sortQuery = sortQuery.ThenSort(req.sort);
+
+            return sortQuery;
+        }
+    }
+}

+ 194 - 0
Controllers/CompanyTypeController.cs

@@ -0,0 +1,194 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class CompanyTypeController : Controller {
+        private readonly ILogger<CompanyTypeController> _logger;
+        private readonly FMSContext _context;
+        private readonly CompanyTypeService _service;
+        private readonly StorageHelper _storage;
+
+        public CompanyTypeController(
+            ILogger<CompanyTypeController> logger,
+            FMSContext context,
+            CompanyTypeService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 그룹 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        //[HttpGet("{siteId}/{id}")]
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Gets(id);
+            if (data.Count() == 0) {
+                return NotFound("회사 구분 정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.CompanyTypeId,
+                /*
+                x.SiteId,
+                CmSite = new {
+                    x.Site.Name,
+                },
+                */
+                x.Name,
+                x.IsUse,
+            });
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        //[HttpPost("{siteId}/{id}")]
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] CmCompanyType data) {
+            if (id != data.CompanyTypeId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmCompanyType data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        //[HttpDelete("{siteId}/{id}")]
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<CmCompanyType> query) {
+            var list = query.Select(x => new {
+                /*
+                x.SiteId,
+                SiteName = x.Site.Name,
+                */
+                x.CompanyTypeId,
+                x.Name,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N"
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmCompanyType> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            /*
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            */
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+    }
+}

+ 162 - 0
Controllers/ComplaintCodeTypeController.cs

@@ -0,0 +1,162 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class ComplaintCodeTypeController : Controller {
+        private readonly ILogger<ComplaintCodeTypeController> _logger;
+        private readonly FMSContext _context;
+        private ComplaintCodeTypeService _service;
+
+        public ComplaintCodeTypeController(
+            ILogger<ComplaintCodeTypeController> logger,
+            FMSContext context,
+            ComplaintCodeTypeService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsComplaintCodeType> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsComplaintCodeType> query) {
+            var list = query.Select(x => new {
+                x.ComplaintTypeId,
+                x.Name,
+                IsUse = (x.IsUse.GetValueOrDefault() ? "Y" : "N")
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.ComplaintTypeId,
+                x.Name,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] FmsComplaintCodeType data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.ComplaintTypeId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsComplaintCodeType data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 245 - 0
Controllers/ComplaintController.cs

@@ -0,0 +1,245 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class ComplaintController : Controller {
+        private readonly ILogger<ComplaintController> _logger;
+        private readonly FMSContext _context;
+        private ComplaintService _service;
+        private readonly StorageHelper _storage;
+
+        public ComplaintController(
+            ILogger<ComplaintController> logger,
+            FMSContext context,
+            ComplaintService service,
+             StorageHelper storage
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsComplaints> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            var sortQuery = query.OrderBy(x => x.CmSite.SortOrderNo);
+            sortQuery = sortQuery.ThenByDescending(x => x.StartDate);
+
+            sortQuery = sortQuery.ThenSort(req.sort);
+
+            return sortQuery;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsComplaints> query) {
+            var list = query.Select(x => new {
+                x.ComplaintId,
+                x.ComplaintTypeId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                ComplaintTypeName = x.FmsComplaintCodeType.Name,
+                x.Name,
+                DepartmentName = x.CmDepartment.Name,
+                StartDate = x.StartDate != null ? x.StartDate.Value.ToString("yyyy/MM/dd") : null,
+                EndDate = x.EndDate != null ? x.EndDate.Value.ToString("yyyy/MM/dd") : null,
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            _logger.LogError("#############");
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.ComplaintId,
+                x.ComplaintTypeId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.Name,
+                x.ComplaintLocation,
+                FmsComplaintCodeType = new {
+                    x.FmsComplaintCodeType.Name
+                },
+                x.StartDate,
+                x.EndDate,
+                CmCompany = new {
+                    x.CmCompany.Name
+                },
+                CmDepartment = new {
+                    x.CmDepartment.Name
+                },
+                x.CompanyId,
+                x.DepartmentId,
+                x.Reason,
+                x.Action,
+                x.Damage,
+                x.Measurement,
+                x.Firstwitness,
+                CmFile1 = _storage.ParseFile(x.CmFile1),
+                CmFile2 = _storage.ParseFile(x.CmFile2),
+                CmFile3 = _storage.ParseFile(x.CmFile3)
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] FmsComplaints data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            _logger.LogInformation("siteId:" + siteId);
+            if (id != data.ComplaintId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsComplaints data) {
+            _logger.LogInformation("Create");
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+            return Ok();
+        }
+
+        /// <summary>
+        /// 보고서
+        /// </summary>
+        [AllowAnonymous]
+        [HttpGet("Report/{siteId}/{id}")]
+        public IActionResult Report(int siteId, int id) {
+
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.ComplaintId,
+                x.ComplaintTypeId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.Name,
+                x.ComplaintLocation,
+                ComplaintTypeName = x.FmsComplaintCodeType.Name,
+                x.StartDate,
+                x.EndDate,
+                CompanyName = x.CmCompany.Name,
+                DepartmentName = x.CmDepartment.Name,
+                x.CompanyId,
+                x.DepartmentId,
+                x.Reason,
+                x.Action,
+                x.Damage,
+                x.Measurement,
+                CmFileUrl1 = _storage.ParseFileUrl(x.CmFile1),
+                CmFileUrl2 = _storage.ParseFileUrl(x.CmFile2),
+                x.Firstwitness,
+            });
+
+            return Ok(list.ToList());
+        }
+
+    }
+}

+ 295 - 0
Controllers/ConstructionPartnerController.cs

@@ -0,0 +1,295 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class ConstructionPartnerController : Controller {
+        private readonly ILogger<ConstructionPartnerController> _logger;
+        private readonly FMSContext _context;
+        private readonly ConstructionPartnerService _service;
+
+        public ConstructionPartnerController(
+            ILogger<ConstructionPartnerController> logger,
+            FMSContext context,
+            ConstructionPartnerService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 함수명 : List 
+        /// 기  능 : 데이터 목록(grid 조회)
+        /// 작성자 : 신현갑
+        /// 작성일 : 2020-02-14
+        /// 수정자 : 
+        /// 수정일 :
+        /// 비  고 :  
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 함수명 : Get 
+        /// 기  능 : 데이터 조회(상세 Popup DetailForm 조회)
+        /// 작성자 : 신현갑
+        /// 작성일 : 2020-02-14
+        /// 수정자 : 
+        /// 수정일 :
+        /// 비  고 : 
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="partnerId"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{partnerId}")]
+        public IActionResult Get(int siteId, int partnerId) {
+            var data = _service.Get(siteId, partnerId);
+            if (data.Count() == 0) {
+                return NotFound("협력업체 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.PartnerId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                //SiteName = x.CmSite.Name,
+                x.Name,
+                x.PartnerTypeId,
+                CmPartnerType = new {
+                    x.PartnerType.Name,
+                },
+                x.BusinessFieldId,
+                CmBusinessField = new {
+                    x.BusinessField.Name,
+                },
+                //BusinessFieldName = x.BusinessField.Name,
+                //PartnerTypeName = x.PartnerType.Name,
+                x.RegistrationNo,
+                x.RepresentativeName,
+                x.PhoneNo,
+                x.FaxNo,
+                x.SalesName,
+                x.SalesPhoneNo,
+                x.AddressZip1,
+                x.AddressZip2,
+                x.Address1,
+                x.Adderss2,
+                x.HomepageUri,
+                x.Note,
+                x.IsUse,
+            });
+
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 함수명 : ExportExcel 
+        /// 기  능 : 엑셀 파일 생성(ExportExcel)
+        /// 작성자 : 신현갑
+        /// 작성일 : 2020-02-14
+        /// 수정자 : 
+        /// 수정일 :
+        /// 비  고 : 
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"공사업체관리-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 함수명 : Create 
+        /// 기  능 : 데이터 신규 생성(Create)
+        /// 작성자 : 신현갑
+        /// 작성일 : 2020-02-14
+        /// 수정자 : 
+        /// 수정일 :
+        /// 비  고 : 
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public IActionResult Create([FromBody] CmPartner data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 함수명 : Edit 
+        /// 기  능 : 데이터 수정(Update)
+        /// 작성자 : 신현갑
+        /// 작성일 : 2020-02-14
+        /// 수정자 : 
+        /// 수정일 :
+        /// 비  고 :   
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="partnerId"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{siteId}/{partnerId}")]
+        public IActionResult Edit(int siteId, int partnerId, [FromBody] CmPartner data) {
+            if (partnerId != data.PartnerId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(siteId, partnerId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 함수명 : Delete 
+        /// 기  능 : 데이터 삭제(Delete)
+        /// 작성자 : 신현갑
+        /// 작성일 : 2020-02-14
+        /// 수정자 : 
+        /// 수정일 :
+        /// 비  고 :
+        /// </summary>
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="partnerId"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{partnerId}")]
+        public IActionResult Delete(int siteId, int partnerId) {
+            _service.Delete(siteId, partnerId);
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 함수명 : _ListSelect 
+        /// 기  능 : 셀렉트 공통(grid 조회)
+        /// 작성자 : 신현갑
+        /// 작성일 : 2020-02-14
+        /// 수정자 : 
+        /// 수정일 :
+        /// 비  고 :
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmPartner> query) {
+            var list = query.Select(x => new {
+                x.PartnerId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.Name,
+                x.BusinessFieldId,
+                BusinessFieldName = x.BusinessField.Name,
+                x.PartnerTypeId,
+                PartnerTypeName = x.PartnerType.Name,
+                x.RegistrationNo,
+                x.RepresentativeName,
+                x.PhoneNo,
+                x.FaxNo,
+                x.SalesName,
+                x.SalesPhoneNo,
+                x.AddressZip1,
+                x.AddressZip2,
+                x.Address1,
+                x.Adderss2,
+                x.HomepageUri,
+                x.Note,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N",
+            });
+            return list;
+        }
+
+        /// <summary>
+        /// 함수명 : _FilterAndSort 
+        /// 기  능 : 검색 & 정렬
+        /// 작성자 : 신현갑
+        /// 작성일 : 2020-02-14
+        /// 수정자 : 
+        /// 수정일 :
+        /// 비  고 :
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmPartner> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == req.siteId);
+            }
+
+            // 업무분야 필드가 있는경우만 추가
+            if (req.businessFieldId != null) {
+                query = query.Where(x => x.SiteId == req.siteId);
+                query = query.Where(x => x.BusinessFieldId == req.businessFieldId || x.BusinessField.Name == "공통");
+            }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 201 - 0
Controllers/ContractClassController.cs

@@ -0,0 +1,201 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class ContractClassController : Controller {
+        private readonly ILogger<ContractClassController> _logger;
+        private readonly FMSContext _context;
+        private readonly ContractClassService _service;
+
+        public ContractClassController(
+            ILogger<ContractClassController> logger,
+            FMSContext context,
+            ContractClassService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 목록(List)
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{contractClassId}")]
+        public IActionResult Get(int contractClassId) {
+            var data = _service.Get(contractClassId);
+            if (data.Count() == 0) {
+                return NotFound("근무 형태 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.ContractClassId,
+                /*
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                */
+                x.Name,
+                x.IsUse,
+            });
+
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"근무형태코드관리-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 수정(Edit)
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{contractClassId}")]
+        public IActionResult Edit(int contractClassId, [FromBody] FmsContractClass data) {
+            if (contractClassId != data.ContractClassId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(contractClassId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 신규(Creae)
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public IActionResult Create([FromBody] FmsContractClass data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제(Delete)
+        /// </summary>
+        [HttpDelete("{contractClassId}")]
+        public IActionResult Delete(int contractClassId) {
+            _service.Delete(contractClassId);
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 셀렉트 공통(_ListSelect)
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsContractClass> query) {
+            var list = query.Select(x => new {
+                x.ContractClassId,
+                /*
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                */
+                x.Name,
+                x.IsUse,
+            });
+            return list;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬(_FilterAndSort)
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsContractClass> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            /*
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == req.siteId);
+            }
+            */
+
+            // // 업무분야 필드가 있는경우만 추가
+            // if (req.businessFieldId != null) {
+            //     query = query.Where(x => x.SiteId == req.siteId);
+            //     query = query.Where(x => x.BusinessFieldId == req.businessFieldId || x.CmBusinessField.Name == "공통");
+            // }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 311 - 0
Controllers/ContractManagementController.cs

@@ -0,0 +1,311 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class ContractManagementController : Controller {
+        private readonly ILogger<ContractManagementController> _logger;
+        private readonly FMSContext _context;
+        private readonly ContractManagementService _service;
+        private readonly StorageHelper _storage;
+
+        public ContractManagementController(
+            ILogger<ContractManagementController> logger,
+            FMSContext context,
+            ContractManagementService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+
+        /// <summary>
+        /// 목록(List)
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{contractId}")]
+        public IActionResult Get(int siteId, int contractId) {
+            var data = _service.Get(siteId, contractId);
+            if (data.Count() == 0) {
+                return NotFound("계약 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.ContractId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.Name,
+                x.PartnerTypeId,
+                CmPartnerType = new {
+                    x.PartnerType.Name
+                },
+                x.PartnerId,
+                CmPartner = new {
+                    x.CmPartner.Name
+                },
+                // ContractDate = x.ContractDate == null ? "" : ((DateTime)x.ContractDate).ToString("yyyy-MM-dd"),
+                // StartDate = x.StartDate == null ? "" : ((DateTime)x.StartDate).ToString("yyyy-MM-dd"),
+                // EndDate = x.EndDate == null ? "" : ((DateTime)x.EndDate).ToString("yyyy-MM-dd"),
+
+                // ContractDate = x.ContractDate != null ? x.ContractDate.Value.ToString("yyyy/MM/dd") : null,
+                // StartDate = x.StartDate != null ? x.StartDate.Value.ToString("yyyy/MM/dd") : null,
+                // EndDate = x.EndDate != null ? x.EndDate.Value.ToString("yyyy/MM/dd") : null,
+
+                x.ContractDate,
+                x.StartDate,
+                x.EndDate,
+                x.OwnerShipName,
+                x.OwnerShipPhoneNo,
+                x.CommitmentMan,
+                x.Comment,
+                x.ContractClassId,
+                FmsContractClass = new {
+                    x.FmsContractClass.Name
+                },
+                x.ContractTypeId,
+                FmsContractType = new {
+                    x.FmsContractType.Name
+                },
+                x.ContractMethodId,
+                FmsContractMethod = new {
+                    x.FmsContractMethod.Name
+                },
+                x.PaymentTypeId,
+                FmsPaymentType = new {
+                    x.FmsPaymentType.Name
+                },
+                //2020-04-13 컬럼 추가
+                x.ContractAmountText,
+                x.ContractExeGuaranteeRate,
+                x.DefectGuaranteeRate,
+                x.DefectGuaranteePeriodText,
+                x.FileId,
+                CmFile = _storage.ParseFile(x.CmFile),
+
+                // CmFile = new {
+                //     x.CmFile.Name
+                //     x.CmFile.Name
+                // },
+            });
+
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"계약등록조회-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 수정(Edit)
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{siteId}/{contractId}")]
+        public IActionResult Edit(int siteId, int contractId, [FromBody] FmsContract data) {
+            if (contractId != data.ContractId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+            ModelState.RemoveStartOf("CmFile");
+            if (ModelState.IsValid) {
+                _service.Edit(siteId, contractId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 신규(Creae)
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public IActionResult Create([FromBody] FmsContract data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제(Delete)
+        /// </summary>
+        [HttpDelete("{siteId}/{contractId}")]
+        public IActionResult Delete(int siteId, int contractId) {
+            _service.Delete(siteId, contractId);
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 셀렉트 공통(_ListSelect)
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsContract> query) {
+            var list = query.Select(x => new {
+                x.ContractId,
+                x.SiteId,
+                //컬럼 정렬 순서 변경 2020-05-27 수정
+                StartDate = x.StartDate != null ? x.StartDate.Value.ToString("yyyy-MM-dd") : null,
+                EndDate = x.EndDate != null ? x.EndDate.Value.ToString("yyyy-MM-dd") : null,
+                SiteName = x.CmSite.Name,
+                x.Name,
+                x.PartnerTypeId,
+                PartnerTypeName = x.PartnerType.Name,
+                FilePath = _storage.getFilePath(x.CmFile),
+                FileName = x.CmFile.Name,
+                // ContractDate = x.ContractDate == null ? "" : ((DateTime)x.ContractDate).ToString("yyyy-MM-dd"),
+                // StartDate = x.StartDate == null ? "" : ((DateTime)x.StartDate).ToString("yyyy-MM-dd"),
+                // EndDate = x.EndDate == null ? "" : ((DateTime)x.EndDate).ToString("yyyy-MM-dd"),
+
+                ContractDate = x.ContractDate != null ? x.ContractDate.Value.ToString("yyyy-MM-dd") : null,
+
+                x.PartnerId,
+                PartnerName = x.CmPartner.Name,
+                // x.ContractDate,
+                // x.StartDate,
+                // x.EndDate,
+                x.OwnerShipName,
+                x.OwnerShipPhoneNo,
+                x.CommitmentMan,
+                x.Comment,
+                x.ContractClassId,
+                ContractClassName = x.FmsContractClass.Name,
+                x.ContractTypeId,
+                ContractTypeName = x.FmsContractType.Name,
+                x.ContractMethodId,
+                ContractMethodName = x.FmsContractMethod.Name,
+                x.PaymentTypeId,
+                PaymentTypeName = x.FmsPaymentType.Name,
+                x.ContractAmountText,
+                x.ContractExeGuaranteeRate,
+                x.DefectGuaranteeRate,
+                x.DefectGuaranteePeriodText,
+                x.FileId,
+                File = x.CmFile.Name,
+            });
+            return list;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬(_FilterAndSort)
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsContract> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            //정렬 (시작일)
+            //query = query.OrderBy(x => x.StartDate);
+
+            //고정정렬 (사옥 준공순)
+            //var sortQuery = query.OrderBy(x => x.CmSite.SortOrderNo);
+
+            // 추가 정렬
+            // if (req.sort.field == "StoredAndAproval") { // 기본 커스텀 정렬 예제 (입고역순,승인일순)
+            //     sortQuery = sortQuery.ThenByDescending(x => x.StoredDate);
+            //     sortQuery = sortQuery.ThenBy(x => x.ApprovalDate);
+            // } else {
+            //     sortQuery = sortQuery.ThenSort(req.sort);
+            // }     
+
+            //정렬 (시작일)
+            var sortQuery = query.OrderByDescending(x => x.StartDate);
+
+            // 기본 Entity 정렬
+            sortQuery = sortQuery.ThenSort(req.sort);
+
+            // 추가 정렬
+            // if (req.sort.field == "StoredAndAproval") { // 기본 커스텀 정렬 예제 (입고역순,승인일순)
+            //     sortQuery = sortQuery.ThenByDescending(x => x.StoredDate);
+            //     sortQuery = sortQuery.ThenBy(x => x.ApprovalDate);
+            // } else {
+            //     sortQuery = sortQuery.ThenSort(req.sort);
+            // }            
+
+            // 기본 Entity 정렬
+            //query = query.Sort(req.sort);
+
+            return sortQuery;
+
+        }
+
+    }
+}

+ 201 - 0
Controllers/ContractMethodController.cs

@@ -0,0 +1,201 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class ContractMethodController : Controller {
+        private readonly ILogger<ContractMethodController> _logger;
+        private readonly FMSContext _context;
+        private readonly ContractMethodService _service;
+
+        public ContractMethodController(
+            ILogger<ContractMethodController> logger,
+            FMSContext context,
+            ContractMethodService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 목록(List)
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{contractMethodId}")]
+        public IActionResult Get(int contractMethodId) {
+            var data = _service.Get(contractMethodId);
+            if (data.Count() == 0) {
+                return NotFound("계약 방법 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.ContractMethodId,
+                /*
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                */
+                x.Name,
+                x.IsUse,
+            });
+
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"계약방법코드관리-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 수정(Save)
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{contractMethodId}")]
+        public IActionResult Edit(int contractMethodId, [FromBody] FmsContractMethod data) {
+            if (contractMethodId != data.ContractMethodId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(contractMethodId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 신규(Creae)
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public IActionResult Create([FromBody] FmsContractMethod data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제(Delete)
+        /// </summary>
+        [HttpDelete("{contractMethodId}")]
+        public IActionResult Delete(int contractMethodId) {
+            _service.Delete(contractMethodId);
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 셀렉트 공통(_ListSelect)
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsContractMethod> query) {
+            var list = query.Select(x => new {
+                x.ContractMethodId,
+                /*
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                */
+                x.Name,
+                x.IsUse,
+            });
+            return list;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬(_FilterAndSort)
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsContractMethod> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            /*
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == req.siteId);
+            }
+            */
+
+            // // 업무분야 필드가 있는경우만 추가
+            // if (req.businessFieldId != null) {
+            //     query = query.Where(x => x.SiteId == req.siteId);
+            //     query = query.Where(x => x.BusinessFieldId == req.businessFieldId || x.CmBusinessField.Name == "공통");
+            // }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 201 - 0
Controllers/ContractTypeController.cs

@@ -0,0 +1,201 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class ContractTypeController : Controller {
+        private readonly ILogger<ContractTypeController> _logger;
+        private readonly FMSContext _context;
+        private readonly ContractTypeService _service;
+
+        public ContractTypeController(
+            ILogger<ContractTypeController> logger,
+            FMSContext context,
+            ContractTypeService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 목록(List)
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{contractTypeId}")]
+        public IActionResult Get(int contractTypeId) {
+            var data = _service.Get(contractTypeId);
+            if (data.Count() == 0) {
+                return NotFound("계약 종류 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.ContractTypeId,
+                /*
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                */
+                x.Name,
+                x.IsUse,
+            });
+
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"계약종류코드관리-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 수정(Save)
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{contractTypeId}")]
+        public IActionResult Edit(int contractTypeId, [FromBody] FmsContractType data) {
+            if (contractTypeId != data.ContractTypeId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(contractTypeId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 신규(Creae)
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public IActionResult Create([FromBody] FmsContractType data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제(Delete)
+        /// </summary>
+        [HttpDelete("{contractTypeId}")]
+        public IActionResult Delete(int contractTypeId) {
+            _service.Delete(contractTypeId);
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 셀렉트 공통(_ListSelect)
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsContractType> query) {
+            var list = query.Select(x => new {
+                x.ContractTypeId,
+                /*
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                */
+                x.Name,
+                x.IsUse,
+            });
+            return list;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬(_FilterAndSort)
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsContractType> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            /*
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == req.siteId);
+            }
+            */
+
+            // // 업무분야 필드가 있는경우만 추가
+            // if (req.businessFieldId != null) {
+            //     query = query.Where(x => x.SiteId == req.siteId);
+            //     query = query.Where(x => x.BusinessFieldId == req.businessFieldId || x.CmBusinessField.Name == "공통");
+            // }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 77 - 0
Controllers/ControlController.cs

@@ -0,0 +1,77 @@
+
+using FMSAdmin.Data;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using FMSAdmin.Helpers;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Authorization;
+using System;
+using System.IO;
+
+namespace FMSAdmin.Controllers {
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class ControlController : Controller {
+        private readonly ILogger<ControlController> _logger;
+        private readonly FMSContext _context;
+        private readonly StorageHelper _storage;
+
+        public ControlController(
+            ILogger<ControlController> logger,
+            FMSContext context,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _storage = storage;
+        }
+
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult Upload(IFormFile file) {
+            var uploadFile = _storage.SaveTemp(file);
+
+            return Ok(uploadFile);
+        }
+
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult UploadImage(IFormFile file) {
+            var uploadFile = _storage.SaveTempImage(file);
+
+            return Ok(uploadFile);
+        }
+
+        /// <summary>
+        /// 웹에디터 업로드
+        /// </summary>
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult FroalaImageUpload(IFormFile file) {
+            var today = DateTime.Now.ToString("yyyy-MM-dd");
+            _storage.CreateDirectory($"/editor/{today}");
+
+            // 파일명 중복처리
+            var filename = file.FileName;
+            var ext = Path.GetExtension(filename);
+            var name = Path.GetFileNameWithoutExtension(filename);
+            var filePath = $"/editor/{today}/{filename}";
+            var count = 0;
+            while (_storage.FileExists(filePath)) {
+                filename = name + ("(" + (count + 1) + ")") + (ext.Length > 0 ? ext : "");
+                filePath = $"/editor/{today}/{filename}";
+                _logger.LogInformation("filePath:" + filePath);
+                count++;
+            }
+
+            var uploadFile = _storage.SaveImage(filePath, file);
+
+            return Ok(new {
+                link = uploadFile.Url
+            });
+        }
+
+    }
+}

+ 537 - 0
Controllers/DailyReportController.cs

@@ -0,0 +1,537 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+//using FMSApp.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using FMSAdmin.Services;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Text;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using System.Text.RegularExpressions;
+using Newtonsoft.Json.Linq;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class DailyReportController : Controller {
+        private readonly ILogger<DailyReportController> _logger;
+        private readonly FMSContext _context;
+        private readonly FMSApp.Services.DailyReportService _service;
+        private readonly SiteWeatherInfoService _siteWeatherInfoService;
+        private readonly StorageHelper _storage;
+
+        public enum WorkTypeId {
+            PeriodicInspection = 1,
+            ForensicExamination = 2,
+            CivilComplaint = 3,
+            SiAlarm = 4,
+            Occasional = 5,
+            Construct = 6,
+            ForensicEducation = 7,
+        }
+
+        public enum WorkProgressId {
+            WorkStatusPending = 3,
+            WorkStatusInProgress = 4,
+            WorkStatusComplete = 5,
+            WorkStatusApproved = 6,
+            WorkStatusDeferred = 7,
+        }
+
+        public enum AdjustmentTypeId {
+            Regular = 0,
+            Loss = 1,
+            Return = 2
+        }
+
+        public DailyReportController(
+            ILogger<DailyReportController> logger,
+            FMSContext context,
+            FMSApp.Services.DailyReportService service,
+            SiteWeatherInfoService siteWeatherInfoService,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _siteWeatherInfoService = siteWeatherInfoService;
+            _storage = storage;
+        }
+
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+
+        public dynamic _ListSelect(IQueryable<FmsDailyReport> query) {
+            // var query = _FilterAndSort(req);
+            // query = BusinessFieldHelper.Apply<FmsDailyReport>(_context, User, businessAuth, query);
+            // query = query.Where(x => x.SiteId == Util.ToInt(siteId));
+
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = _context.CmSite.FirstOrDefault(site => site.SiteId == x.SiteId).Name,
+                AddDate = x.AddDate.ToString("yyyy-MM-dd"),
+                x.BusinessFieldId,
+                x.Name,
+                x.Contents,
+                x.RegisterUserId,
+                RegisterUserName = _context.CmUser.FirstOrDefault(user => user.UserId == x.RegisterUserId).Name,
+                x.ApproveDate,
+                RegisterDate = x.RegisterDate.Value.ToString("yyyy-MM-dd"),
+                x.ApproverUserId,
+                ApproverUserName = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId).Name,
+            });
+
+            return list;
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult Get([FromQuery] string siteId, [FromQuery] DateTime addDate, [FromQuery] string businessFieldId) {
+            var query = _service.GetAll();
+
+
+            // 업무분야 필드가 있는경우만 추가
+            if (businessFieldId != null) {
+                query = query.Where(x => x.BusinessFieldId == Util.ToInt(businessFieldId) || x.CmBusinessField.Name == "공통");
+            }
+
+            var item = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.AddDate == addDate.Date
+                //&& x.BusinessFieldId == Util.ToInt(businessFieldId)
+            ).Select(x => new {
+                x.SiteId,
+                SiteName = _context.CmSite.FirstOrDefault(site => site.SiteId == x.SiteId).Name,
+                AddDate = x.AddDate.ToString("yyyy-MM-dd"),
+                x.BusinessFieldId,
+                x.Name,
+                x.Contents,
+                x.Datas,
+                x.RegisterUserId,
+                RegisterDate = x.RegisterDate.Value.ToString("yyyy-MM-dd"),
+                x.ApproveDate,
+                x.ApproverUserId,
+                ApproverUserName = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId).Name,
+                x.ApproverUserId2,
+                ApproverUserName2 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId2).Name,
+                x.ApproverUserId3,
+                ApproverUserName3 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId3).Name,
+                x.ApproverUserId4,
+                ApproverUserName4 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId4).Name,
+                x.ApproverUserId5,
+                ApproverUserName5 = _context.CmUser.FirstOrDefault(user => user.UserId == x.ApproverUserId5).Name,
+            }).FirstOrDefault();
+
+            return Ok(item);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult GetCurrentInfo([FromQuery] string siteId, [FromQuery] DateTime addDate, [FromQuery] string businessFieldId) {
+
+            var today = addDate.Date;
+            var yesterday = addDate.Date.AddDays(-1);
+            var tomorrow = addDate.Date.AddDays(1);
+            var currentMonthFirstDay = new DateTime(addDate.Year, addDate.Month, 1);
+            var prevMonthFirstDay = new DateTime(addDate.Year, addDate.Month - 1, 1);
+
+            var site = _context.CmSite.FirstOrDefault(x => x.SiteId == Util.ToInt(siteId));
+            var sido = _siteWeatherInfoService.ReplaceSido(site.ApiSido);
+            var sigun = site.ApiSigun;
+            var weatherDateTime = new DateTime(addDate.Year, addDate.Month, addDate.Day, 10, 0, 0);
+            SiteWeatherInfo weather = null;
+            if (!string.IsNullOrEmpty(sido) && !string.IsNullOrEmpty(sigun)) {
+                weather = _context.SiteWeatherInfo.Where(
+                    x => x.Sido == sido
+                    && x.Sigun == sigun
+                    && x.BaseTime >= weatherDateTime
+                    && x.BaseTime < weatherDateTime.Date.AddDays(1)
+                ).OrderBy(x => x.BaseTime).FirstOrDefault();
+            }
+
+            var workStatus = new Dictionary<string, int> {
+                ["WorkStatusTypePeriodicInspection"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.PeriodicInspection, addDate),
+                ["WorkStatusTypeForensicExamination"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.ForensicExamination, addDate),
+                ["WorkStatusTypeForensicEducation"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.ForensicEducation, addDate),
+                ["WorkStatusTypeOccasional"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.Occasional, addDate),
+                ["WorkStatusTypeConstruct"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.Construct, addDate),
+                ["WorkStatusTypeEtc"] = _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.SiAlarm, addDate)
+                    + _service.GetWorkStatusByWorkTypeId(Util.ToInt(siteId), (int)WorkTypeId.CivilComplaint, addDate),
+
+                ["WorkStatusPending"] = _service.GetWorkStatusByWorkProgressId(Util.ToInt(siteId), (int)WorkProgressId.WorkStatusPending, addDate),
+                ["WorkStatusInProgress"] = _service.GetWorkStatusByWorkProgressId(Util.ToInt(siteId), (int)WorkProgressId.WorkStatusInProgress, addDate),
+                ["WorkStatusDeferred"] = _service.GetWorkStatusByWorkProgressId(Util.ToInt(siteId), (int)WorkProgressId.WorkStatusDeferred, addDate),
+                ["WorkStatusComplete"] = _service.GetFmsWorkResultComplete(Util.ToInt(siteId), (int)WorkProgressId.WorkStatusComplete, addDate),
+                ["WorkStatusApproved"] = _service.GetFmsWorkResultApproval(Util.ToInt(siteId), (int)WorkProgressId.WorkStatusApproved, addDate),
+            };
+            workStatus["WorkStatusTypeTotal"] = workStatus["WorkStatusTypeOccasional"]
+                + workStatus["WorkStatusTypePeriodicInspection"]
+                + workStatus["WorkStatusTypeForensicExamination"]
+                + workStatus["WorkStatusTypeEtc"];
+            workStatus["WorkStatusTotal"] = workStatus["WorkStatusPending"]
+                + workStatus["WorkStatusInProgress"]
+                + workStatus["WorkStatusDeferred"]
+                + workStatus["WorkStatusComplete"]
+                + workStatus["WorkStatusApproved"];
+
+            var energies = _context.BemsFormula.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.FormulaId == 1
+                && (new int[] { 100, 101, 102 }).Contains(x.FacilityTypeId)
+            ).Select(x => new {
+                x.FacilityTypeId,
+                FacilityTypeName = _context.BemsFacilityType.FirstOrDefault(ft => ft.FacilityTypeId == x.FacilityTypeId).Name,
+                x.FacilityCode,
+                List = x.BemsFormulaParameter.Select(p => new {
+                    Parameter = new {
+                        p.ParameterFacilityCode,
+                        p.ParameterPropertyId,
+                        p.FormulaId,
+                        p.ParameterId,
+                    },
+                    MonitoringPoint = _context.BemsMonitoringPoint.Where(
+                        mp => mp.SiteId == p.SiteId
+                        && mp.FacilityCode == p.ParameterFacilityCode
+                        && mp.PropertyId == p.ParameterPropertyId
+                    ).Select(mp => new {
+                        mp.FuelTypeId,
+                        mp.Name,
+                        // 당일(15minRaw 에서 해당 일자의 마지막 검침값)
+                        Today = mp.BemsMonitoringPointHistory15minRawData.Where(
+                            raw => raw.CreatedDateTime >= today && raw.CreatedDateTime < tomorrow
+                        ).OrderByDescending(raw => raw.CreatedDateTime).FirstOrDefault().CurrentValue,
+
+                        // 전일(15minRaw 에서 해당 일자 전날의 마지막 검침값)
+                        DayBefore = mp.BemsMonitoringPointHistory15minRawData.Where(
+                            raw => raw.CreatedDateTime >= yesterday && raw.CreatedDateTime < today
+                        ).OrderByDescending(raw => raw.CreatedDateTime).FirstOrDefault().CurrentValue,
+
+                        // 당일 - 전일(당일 사용한 값이므로 당일 - 전일)
+                        DailyUsage = mp.BemsMonitoringPointHistory15minRawData.Where(
+                            raw => raw.CreatedDateTime >= today && raw.CreatedDateTime < tomorrow
+                        ).OrderByDescending(raw => raw.CreatedDateTime).FirstOrDefault().CurrentValue
+                        - mp.BemsMonitoringPointHistory15minRawData.Where(
+                            raw => raw.CreatedDateTime >= yesterday && raw.CreatedDateTime < today
+                        ).OrderByDescending(raw => raw.CreatedDateTime).FirstOrDefault().CurrentValue,
+
+                        // 당월(당월 사용한 값이므로 15minRaw 의 [당월 마지막 검침값] - [전월 마지막 검침값])
+                        // 기존에는 Daily 테이블의 DailyValue 를 1일 - 당일 까지 합산하였으나, 
+                        // Daily 에는 값이 들어오지 않을거라 하여 15minRaw 로 계산.
+                        MonthUsage = mp.BemsMonitoringPointHistory15minRawData.Where(
+                            raw => raw.CreatedDateTime >= currentMonthFirstDay && raw.CreatedDateTime < tomorrow
+                        ).OrderByDescending(raw => raw.CreatedDateTime).FirstOrDefault().CurrentValue
+                        - mp.BemsMonitoringPointHistory15minRawData.Where(
+                            raw => raw.CreatedDateTime >= prevMonthFirstDay && raw.CreatedDateTime < currentMonthFirstDay
+                        ).OrderByDescending(raw => raw.CreatedDateTime).FirstOrDefault().CurrentValue,
+                    }).FirstOrDefault()
+                }).ToList()
+            }).ToList();
+
+            var works = _context.CmBusinessField.Where(
+                x => x.IsUse == true
+            ).OrderBy(x => x.BusinessFieldId).Select(x => new {
+                x.BusinessFieldId,
+                BusinessFieldName = x.Name,
+                /* 기존 Bems 소스 그대로 가져 넣음... where 절의 and 와 or 가 난해함... 의도한건지 정확하지 않음. */
+                List = (from req in _context.FmsWorkRequest
+                        let res = from res in _context.FmsWorkResult
+                                  where (res.IsConfirmed == false && (res.StartDate <= addDate.Date)) ||
+                                  (res.ConfirmedDate.Value.Year == addDate.Year &&
+                                  res.ConfirmedDate.Value.Month == addDate.Month &&
+                                  res.ConfirmedDate.Value.Day == addDate.Day) &&
+                                  res.SiteId == Util.ToInt(siteId)
+                                  select res.WorkRequestId
+                        where
+                        (res.Contains(req.WorkRequestId) && req.BusinessFieldId == x.BusinessFieldId)
+                        || (req.WorkProgressId <= 5 && req.WorkProgressId >= 4 && req.BusinessFieldId == x.BusinessFieldId)
+                        && (req.StartWorkDate <= addDate.Date)
+                        && req.SiteId == Util.ToInt(siteId)
+                        orderby req.RequestDate, req.WorkProgressId ascending
+                        select new {
+                            WorkRequestId = req.WorkRequestId,
+                            BusinessFieldId = req.BusinessFieldId,
+                            WorkProgressId = req.WorkProgressId,
+                            WorkProgressName = req.WorkProgress.Name,
+                            WorkTypeId = req.WorkTypeId,
+                            WorkTypeName = req.WorkType.Name,
+                            RequestDate = req.RequestDate,
+                            Title = req.Title,
+                            WorkUsers = req.WorkUsers,
+                            WorkPartnerId = req.WorkPartnerId,
+                            WorkPartnerName = req.WorkPartner.Name,
+                            req.FmsWorkResult.StartDate,
+                            req.FmsWorkResult.EndDate,
+                            CheckItems = _context.FmsWorkResultCheckItem.Where(
+                                check => check.SiteId == req.SiteId
+                                && check.WorkRequestId == req.WorkRequestId
+                            ).OrderBy(check => check.CheckItemId).Select(check => new {
+                                check.CheckDate,
+                                check.TextResult,
+                            }).ToList()
+                        }).ToList()
+            }).ToList();
+
+            var materials = _context.CmBusinessField.Where(
+                x => x.IsUse == true
+            ).OrderBy(x => x.BusinessFieldId).Select(x => new {
+                x.BusinessFieldId,
+                BusinessFieldName = x.Name,
+                List = _context.FmsMaterialRelease.Where(
+                    release => release.SiteId == Util.ToInt(siteId)
+                    && release.ReleaseDate.Date == addDate.Date
+                    && release.BusinessFieldId == x.BusinessFieldId
+                    && (release.AdjustmentTypeId == 0 || release.AdjustmentTypeId == 1)
+                ).OrderBy(release => release.ReleaseDate).Select(release => new {
+                    ReleaseTypeName = release.ReleaseType.Name,
+                    FirstClassId = release.FmsMaterial.FirstClassId,
+                    FirstClassName = release.FmsMaterial.FmsMaterialCodeClass.Name,
+                    SecondClassId = release.FmsMaterial.SecondClassId,
+                    SecondClassName = release.FmsMaterial.FmsMaterialCodeClass1.Name,
+                    ThirdClassId = release.FmsMaterial.ThirdClassId,
+                    ThirdClassName = release.FmsMaterial.FmsMaterialCodeClass2.Name,
+                    MaterialName = release.FmsMaterial.Name,
+                    Standard = release.FmsMaterial.Standard,
+                    WorkName = release.FmsWorkResult.FmsWorkOrder.Title,
+                    Unit = release.FmsMaterial.Unit,
+                    MaterialCount = (int)release.MaterialCount,
+                    WorkerName = release.FmsWorkResult.CmUser.Name
+                }).ToList()
+            }).ToList();
+
+            var accidents = _context.FmsAccident.Where(
+                x => x.StartDate >= today
+                && x.StartDate < tomorrow
+            ).OrderBy(x => x.AccidentId).Select(x => new {
+                x.AccidentTypeId,
+                AccidentTypeName = x.FmsAccidentCodeType.Name,
+                x.AccidentId,
+                x.Name,
+                x.DepartmentId,
+                DepartmentName = x.CmDepartment.Name,
+                x.StartDate,
+                x.EndDate,
+            }).ToList();
+
+            var alarms = _context.FmsAlarmLog.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.CreatedDateTime >= today
+                && x.CreatedDateTime < tomorrow
+                && x.CurrentValue != 0
+            ).OrderBy(x => x.CreatedDateTime).Select(x => new {
+                x.FacilityTypeId,
+                FacilityTypeName = x.AlarmPoint.FacilityType.Name,
+                x.FacilityCode,
+                x.PropertyId,
+                MonitoringPointName = x.AlarmPoint.BemsMonitoringPoint.Name,
+                FacilityName = x.AlarmPoint.Facility.Name,
+                x.CreatedDateTime,
+                x.CurrentValue,
+            }).ToList();
+
+            return Ok(new {
+                Weather = weather,
+                WorkStatus = workStatus,
+                Energies = energies,
+                Works = works,
+                Materials = materials,
+                Accidents = accidents,
+                Alarms = alarms,
+            });
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] FmsDailyReport data, [FromQuery] bool isCreate) {
+            _logger.LogInformation($"Save isCreate({isCreate})");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data, isCreate);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpPost("[action]")]
+        public IActionResult Approve([FromQuery] string siteId, [FromQuery] string approverUserId, [FromQuery] DateTime addDate, [FromQuery] string businessFieldId) {
+            _service.Approve(Util.ToInt(siteId), approverUserId, addDate, Util.ToInt(businessFieldId));
+            return Ok();
+        }
+
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{addData}")]
+        public IActionResult Delete(int siteId, DateTime addData) {
+            _service.Delete(siteId, addData);
+
+            return Ok();
+        }
+
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsDailyReport> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+            // 업무분야 필드가 있는경우만 추가
+            if (req.businessFieldId != null) {
+                query = query.Where(x => x.SiteId == User.GetSiteId());
+                query = query.Where(x => x.BusinessFieldId == req.businessFieldId || x.CmBusinessField.Name == "공통");
+            }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        [AllowAnonymous]
+        [HttpGet("GetJson/{siteId}/{date}")]
+        public string GetJson(string siteId, DateTime date) {
+            var query = _service.GetAll();
+
+            var item = query.Where(
+                x => x.SiteId == Util.ToInt(siteId)
+                && x.AddDate.Date == date.Date
+            ).Select(x => new {
+                CmSite = new {
+                    x.Site.Name
+                },
+                AddDate = x.AddDate.ToString("yyyy-MM-dd"),
+                x.Name,
+                x.Contents,
+                x.Datas,
+                x.ApproveDate,
+                x.ApproveDate2,
+                x.ApproveDate3,
+                x.ApproverUserId,
+                x.ApproverUserId2,
+                x.ApproverUserId3
+            }).FirstOrDefault();
+            string approve = "";
+            if (item != null) {
+                approve += "[" + getApproverInfo(item.ApproverUserId, item.ApproveDate);
+                approve += getApproverInfo(item.ApproverUserId2, item.ApproveDate2);
+                approve += getApproverInfo(item.ApproverUserId3, item.ApproveDate3);
+                approve += "]";
+            }
+
+            string datas = "{}";
+            if (!string.IsNullOrEmpty(item.Datas)) {
+                dynamic jsonObject = JObject.Parse(item.Datas);
+                var energiesTotal = new JArray();
+                foreach (dynamic energy in jsonObject.Energies) {
+                    if (energy != null) {
+                        energy.List = null; // 에너지 합계만 표출
+                        if (energy.List == null) {
+                            energy.List = new JArray();
+                        } else {
+                            foreach (dynamic i in energy.List) {
+                                energiesTotal.Add(JToken.FromObject(new {
+                                    Name = i.Name,
+                                    Today = i.Today,
+                                    DayBefore = i.DayBefore,
+                                    DailyUsage = i.DailyUsage,
+                                    MonthUsage = i.MonthUsage,
+                                }));
+                            }
+                        }
+                        energiesTotal.Add(JToken.FromObject(new {
+                            Name = energy.Total.Name,
+                            Today = energy.Total.Today,
+                            DayBefore = energy.Total.DayBefore,
+                            DailyUsage = energy.Total.DailyUsage,
+                            MonthUsage = energy.Total.MonthUsage,
+                        }));
+                    }
+                }
+
+                var workList = new JArray();
+                foreach (dynamic work in jsonObject.Works) {
+                    if (work != null) {
+                        if (work.List == null) {
+                            work.List = new JArray();
+                        }
+                        foreach (dynamic i in work.List) {
+                            workList.Add(JToken.FromObject(new {
+                                BusinessFieldName = work.BusinessFieldName,
+                                WorkProgressName = i.WorkProgressName,
+                                WorkTypeName = i.WorkTypeName,
+                                Title = i.Title,
+                            }));
+                        }
+                    }
+                }
+                jsonObject.EnergiesTotal = energiesTotal;
+                jsonObject.WorkList = workList;
+
+                datas = JsonConvert.SerializeObject(jsonObject);
+            }
+
+            string json = "";
+            if (item != null)
+                json = $@"
+                    {{ 
+                        SiteNm : ""{item.CmSite.Name}"",
+                        Datas: {datas},
+                        Approver : {approve}
+                    }}";
+            return json;
+        }
+
+
+        public string getApproverInfo(string id, DateTime? date) {
+            if (!Util.IsNullOrEmpty(id)) {
+                var userInfo = _context.CmUser.Where(x => x.UserId == id).Select(x => new {
+                    GroupNm = x.CmUserGroup.Name,
+                    Date = date.Value.ToString("yyyy-MM-dd"),
+                    image = x.CmFile1 != null ? _storage.GetFullUrl(x.CmFile1.Path) + _storage.getFilePath(x.CmFile1) : ""
+                }).SingleOrDefault();
+                return JsonConvert.SerializeObject(userInfo) + ",";
+            } else return "";
+        }
+
+
+    }
+}

+ 1653 - 0
Controllers/DashboardController.cs

@@ -0,0 +1,1653 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using FMSAdmin.Data;
+using FMSAdmin.Helpers;
+using System.Collections;
+using System.Text.RegularExpressions;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Models.Formula;
+using FMSAdmin.Helpers.Formula;
+using FMSAdmin.Services;
+using LinqKit;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class DashboardController : Controller {
+        private readonly ILogger<DashboardController> _logger;
+        private readonly AlarmPointService _serviceAlarmPoint;
+        private readonly EnergyUsageService _serviceEnergyUsage;
+        private readonly MonthWorkService _serviceMonthWork;
+        private readonly MaterialStockService _serviceMaterialStock;
+        private readonly StorageHelper _storageHelper;
+        private readonly FMSContext _context;
+
+        public DashboardController(
+            ILogger<DashboardController> logger,
+            EnergyUsageService serviceEnergyUsage,
+            AlarmPointService serviceAlarmPoint,
+            MonthWorkService serviceMonthWork,
+            MaterialStockService serviceMaterialStock,
+            StorageHelper storageHelper,
+            FMSContext context
+            ) {
+            _logger = logger;
+            _serviceEnergyUsage = serviceEnergyUsage;
+            _serviceAlarmPoint = serviceAlarmPoint;
+            _serviceMonthWork = serviceMonthWork;
+            _serviceMaterialStock = serviceMaterialStock;
+            _storageHelper = storageHelper;
+            _context = context;
+        }
+
+
+        /** 
+         * 작업 달력 현황
+         */
+        [HttpGet("[action]")]
+        public IActionResult WorkCalendar(int? year, int? month, int? siteId) {
+            var curSiteId = User.GetSiteId();
+            if (siteId != null) {
+                curSiteId = siteId.Value;
+            }
+
+            DateTime selectMonth;
+            if (year == null || month == null) {
+                selectMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+            } else {
+                selectMonth = new DateTime(year.Value, month.Value, 1);
+            }
+            var startDate = selectMonth.AddDays(-7);
+            var endDate = selectMonth.AddDays(45);
+
+            // 작업예정
+            var requestList = _GetRequestEventList(curSiteId, startDate, endDate);
+
+            // 작업완료
+            var resultList = _GetResultEventList(curSiteId, startDate, endDate);
+
+            // 휴일
+            var holidayList = _GetHolidayList(curSiteId, selectMonth.Year, selectMonth.Month, startDate, endDate);
+
+            // 주말
+            var weekend = _context.CmHolidayWeekend.Where(
+                x => x.SiteId == curSiteId
+                && x.IsUse == true
+            ).FirstOrDefault();
+
+            // 주별 중점업무
+            var weekList = _GetWeekKeyWorkEventList(curSiteId, startDate, endDate);
+
+            var workName = _serviceMonthWork.GetKeyWork(curSiteId, selectMonth.Month);
+
+
+            if (weekend != null) {
+            }
+
+            var result = new {
+                Year = selectMonth.Year,
+                Month = selectMonth.Month,
+                Theme = workName,
+                StartDate = startDate.ToString("yyyy-MM-dd"),
+                EndDate = endDate.ToString("yyyy-MM-dd"),
+                RequestList = requestList,
+                ResultList = resultList,
+                HolidayList = holidayList,
+                Saturday = weekend?.Saturday ?? false,
+                Sunday = weekend?.Sunday ?? false,
+                WeekList = weekList,
+            };
+
+            return Ok(result);
+        }
+
+        /** 
+         * 공지목록
+         */
+        [HttpGet("[action]")]
+        public IActionResult SiteAnnouncement(int? siteId) {
+            var curSiteId = User.GetSiteId();
+            if (siteId != null) {
+                curSiteId = siteId.Value;
+            }
+
+            var query = from x in _context.CmAnnouncement
+                        where ((x.SiteId == curSiteId || x.IsAllView == true) && x.UpdateDate.Date >= DateTime.Now.Date)
+                        orderby x.AddDate descending
+                        select new {
+                            x.AnnouncementId,
+                            x.SiteId,
+                            SiteName = x.CmSite.Name,
+                            x.Title,
+                            x.Contents,
+                            x.AddDate,
+                            x.IsAllView,
+                            IsSysop = x.CmUser.IsSysop
+                        };
+            return Ok(query.Take(5));
+        }
+
+        /** 
+         * 공지목록 (관리자)
+         */
+        [HttpGet("[action]")]
+        public IActionResult Announcement() {
+
+            var query = from x in _context.CmAnnouncement
+                        where (x.UpdateDate.Date >= DateTime.Now.Date)
+                        orderby x.AddDate descending
+                        select new {
+                            x.AnnouncementId,
+                            x.SiteId,
+                            SiteName = x.CmSite.Name,
+                            x.Title,
+                            x.Contents,
+                            x.AddDate,
+                            x.IsAllView,
+                            IsSysop = x.CmUser.IsSysop
+                        };
+            return Ok(query.Take(5));
+        }
+
+        /** 
+         * 경보상태 (관리자)
+         */
+        [HttpGet("[action]")]
+        public IActionResult AlarmMonitoringHistory() {
+            var list = from x in _context.FmsAlarmLog
+                       orderby x.CreatedDateTime descending
+                       where x.CreatedDateTime.Date == DateTime.Now.Date
+                       select new {
+                           x.CreatedDateTime,
+                           x.CurrentValue,
+                           x.FacilityCode,
+                           x.PropertyId,
+                           Name = x.AlarmPoint.BemsMonitoringPoint.Name,
+                           FacilityName = x.AlarmPoint.BemsMonitoringPoint.CmFacility.Name,
+                           x.FacilityTypeId,
+                           FacilityTypeName = x.AlarmPoint.FacilityType.Name,
+                           x.SiteId,
+                           SiteName = x.Site.Name,
+                           Class1 = x.AlarmPoint.BemsMonitoringPoint.CmFacility.FmsFacilityCodeClass.Name,
+                           Class2 = x.AlarmPoint.BemsMonitoringPoint.CmFacility.FmsFacilityCodeClass1.Name,
+                           Class3 = x.AlarmPoint.BemsMonitoringPoint.CmFacility.FmsFacilityCodeClass2.Name,
+                       };
+            var returnList = list.ToList().Where(x => (int)x.CurrentValue == 1 &&
+                                                        _context.FmsAlarmLog.Where(l => l.SiteId == x.SiteId
+                                                                                    && l.FacilityCode == x.FacilityCode
+                                                                                    && l.PropertyId == x.PropertyId
+                                                                                    && l.FacilityTypeId == x.FacilityTypeId
+                                                                                    && l.CreatedDateTime > x.CreatedDateTime
+                                                                                    && l.CurrentValue == 0
+                                                                                    ).Count() == 0).Take(5);
+
+            return Ok(returnList);
+        }
+
+        /** 
+         * 경보상태
+         */
+        [HttpGet("[action]")]
+        public IActionResult SiteAlarmMonitoringHistory(int? siteId) {
+            var curSiteId = User.GetSiteId();
+            if (siteId != null) {
+                curSiteId = siteId.Value;
+            }
+            //var query = _serviceAlarmPoint.GetPointHistoryList();
+            //query = query.Where(x => x.SiteId == curSiteId);
+            var list = from x in _context.FmsAlarmLog
+                       orderby x.CreatedDateTime descending
+                       where x.SiteId == curSiteId
+                       where x.CreatedDateTime.Date == DateTime.Now.Date
+                       select new {
+                           x.CreatedDateTime,
+                           x.CurrentValue,
+                           Name = x.AlarmPoint.BemsMonitoringPoint.Name,
+                           FacilityName = x.AlarmPoint.BemsMonitoringPoint.CmFacility.Name,
+                           x.FacilityTypeId,
+                           FacilityTypeName = x.AlarmPoint.FacilityType.Name,
+                           x.SiteId,
+                           SiteName = x.Site.Name,
+                           Class1 = x.AlarmPoint.BemsMonitoringPoint.CmFacility.FmsFacilityCodeClass.Name,
+                           Class2 = x.AlarmPoint.BemsMonitoringPoint.CmFacility.FmsFacilityCodeClass1.Name,
+                           Class3 = x.AlarmPoint.BemsMonitoringPoint.CmFacility.FmsFacilityCodeClass2.Name,
+                       };
+            // var list= from x in _context.BemsMonitoringPointHistory15min
+            //             orderby x.CreatedDateTime descending
+            //             where x.FmsAlarmPoint != null //&& x.CurrentValue != 0
+            //             where x.SiteId == curSiteId
+            //             where x.CreatedDateTime.Date == DateTime.Now.Date
+            //             select new {
+            //                 x.CreatedDateTime,
+            //                 x.CurrentValue,
+            //                 Name = x.BemsMonitoringPoint.Name,
+            //                 FacilityName = x.BemsMonitoringPoint.CmFacility.Name,
+            //                 x.FacilityTypeId,
+            //                 FacilityTypeName = x.FacilityType.Name,
+            //                 x.SiteId,
+            //                 SiteName = x.Site.Name,
+            //                 Class1 = x.BemsMonitoringPoint.CmFacility.FmsFacilityCodeClass.Name,
+            //                 Class2 = x.BemsMonitoringPoint.CmFacility.FmsFacilityCodeClass1.Name,
+            //                 Class3 = x.BemsMonitoringPoint.CmFacility.FmsFacilityCodeClass2.Name,
+            //             };
+            return Ok(list.ToList().Where(x => (int)x.CurrentValue == 1).Take(5));
+        }
+
+        /** 
+         * 지도 검색 (관리자)
+         */
+        [HttpGet("[action]")]
+        public IActionResult ImageMap() {
+            var query = from x in _context.CmSite
+                        where x.ImageMapX != null && x.ImageMapY != null && x.IsUse == true
+                        select new {
+                            x.SiteId,
+                            x.Name,
+                            x.ImageMapX,
+                            x.ImageMapY,
+                        };
+            return Ok(query);
+        }
+
+        /** 
+           * 오늘 작업 현황
+           */
+        [HttpGet("[action]")]
+        public IActionResult TodayWorkSummary(string date, int? siteId) {
+            var curSiteId = User.GetSiteId();
+            if (siteId != null) {
+                curSiteId = siteId.Value;
+            }
+
+            var today = DateTime.Now.Date;
+            if (!string.IsNullOrEmpty(date)) {
+                try {
+                    today = DateTime.Parse(date);
+                } catch (Exception ex) {
+                    today = DateTime.Now.Date;
+                }
+            }
+
+            // 작업 종류별
+            var resultQuery = from x in _context.FmsWorkResult
+                              where x.SiteId == curSiteId
+                              where x.StartDate.Date <= today && today <= x.EndDate.Date
+                              where new int[] { 5, 6 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 5:완료, 6:확인
+                              select new {
+                                  x.FmsWorkRequest.WorkProgress.WorkProgressId,
+                                  x.FmsWorkRequest.WorkType,
+                              };
+
+            var requestQuery = from x in _context.FmsWorkRequest
+                               where x.SiteId == curSiteId
+                               where x.StartWorkDate.Date >= today && x.StartWorkDate.Date <= today
+                               select new {
+                                   WorkProgressId = 1,
+                                   x.WorkType,
+                               };
+
+            var groupQuery = from x in resultQuery.Concat(requestQuery)
+                             group x by new { x.WorkType.WorkTypeId, x.WorkProgressId } into g
+                             select new {
+                                 WorkTypeId = g.Key.WorkTypeId,
+                                 WorkProgressId = g.Key.WorkProgressId,
+                                 WorkTypeName = g.Min(x => x.WorkType.Name),
+                                 Count = g.Count(),
+                             };
+
+
+            var groupTodaySum = from x in groupQuery
+                                group x by x.WorkTypeId into g
+                                select new {
+                                    WorkTypeId = g.Key,
+                                    WorkTypeName = g.Min(x => x.WorkTypeName),
+                                    계획 = g.Sum(x => x.WorkProgressId == 1 ? x.Count : 0),
+                                    완료 = g.Sum(x => x.WorkProgressId != 1 ? x.Count : 0),
+                                };
+
+            var groupTodayList = from x in groupTodaySum
+                                 select new {
+                                     WorkTypeId = x.WorkTypeId,
+                                     WorkTypeName = x.WorkTypeName,
+                                     계획 = 100 - (x.계획 != 0 ? (double)x.완료 / (double)x.계획 * 100 : 0),
+                                     완료 = x.계획 != 0 ? (double)x.완료 / (double)x.계획 * 100 : 0,
+                                 };
+
+
+
+            var groupToday = from x in groupQuery
+                             group x by x into g
+                             select new {
+                                 정기점검 = g.Sum(x => x.WorkTypeId == 1 && x.WorkProgressId != 1 ? x.Count : 0),
+                                 정기점검계획 = g.Sum(x => x.WorkTypeId == 1 && x.WorkProgressId == 1 ? x.Count : 0),
+                                 법정검사 = g.Sum(x => x.WorkTypeId == 2 && x.WorkProgressId != 1 ? x.Count : 0),
+                                 법정검사계획 = g.Sum(x => x.WorkTypeId == 2 && x.WorkProgressId == 1 ? x.Count : 0),
+                                 수시점검 = g.Sum(x => x.WorkTypeId == 5 && x.WorkProgressId != 1 ? x.Count : 0),
+                                 수시점검계획 = g.Sum(x => x.WorkTypeId == 5 && x.WorkProgressId == 1 ? x.Count : 0),
+                                 공사작업 = g.Sum(x => x.WorkTypeId == 6 && x.WorkProgressId != 1 ? x.Count : 0),
+                                 공사작업계획 = g.Sum(x => x.WorkTypeId == 6 && x.WorkProgressId == 1 ? x.Count : 0),
+                                 법정교육 = g.Sum(x => x.WorkTypeId == 7 && x.WorkProgressId != 1 ? x.Count : 0),
+                                 법정교육계획 = g.Sum(x => x.WorkTypeId == 7 && x.WorkProgressId == 1 ? x.Count : 0),
+                             };
+
+
+            var todayInfo = new {
+                정기점검 = groupToday.Sum(x => x.정기점검),
+                정기점검계획 = groupToday.Sum(x => x.정기점검계획),
+                법정검사 = groupToday.Sum(x => x.법정검사),
+                법정검사계획 = groupToday.Sum(x => x.법정검사계획),
+                수시점검 = groupToday.Sum(x => x.수시점검),
+                수시점검계획 = groupToday.Sum(x => x.수시점검계획),
+                공사작업 = groupToday.Sum(x => x.공사작업),
+                공사작업계획 = groupToday.Sum(x => x.공사작업계획),
+                법정교육 = groupToday.Sum(x => x.법정교육),
+                법정교육계획 = groupToday.Sum(x => x.법정교육계획),
+            };
+
+
+
+            return Ok(new {
+                todayInfo = todayInfo,
+                list = groupTodayList,
+            });
+        }
+
+        /** 
+         * 승인대기란
+         */
+        [HttpGet("[action]")]
+        public IActionResult ApporovalSummary(int? siteId) {
+            var curSiteId = User.GetSiteId();
+            if (siteId != null) {
+                curSiteId = siteId.Value;
+            }
+
+            var today = DateTime.Now.Date;
+            var startDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+            var endDate = startDate.AddMonths(1).AddDays(-1);
+
+
+            // 작업 진행  -- 업무 
+            var working = (from x in _context.FmsWorkResult
+                           where x.SiteId == curSiteId
+                           where x.StartDate.Date <= today && today <= x.EndDate.Date
+                           where new int[] { 4 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 4:진행
+                           select x).Count();
+
+            // 자재 입고 
+            var materialStored = (from x in _context.FmsMaterialStored
+                                  where x.SiteId == curSiteId
+                                  where x.StoredDate >= today
+                                  where x.IsApproval == true
+                                  select x).Count();
+
+            //자재조정
+            var materialRelease = (from x in _context.FmsMaterialRelease
+                                   where x.SiteId == curSiteId
+                                   where new int[] { 1, 2 }.Contains(x.AdjustmentTypeId) // 반납,손실
+                                   where x.ReleaseDate >= today
+                                   select x).Count();
+
+            //공기구
+            var equipment = (from x in _context.FmsEquipment
+                             where x.SiteId == curSiteId
+                             where x.AddDate >= today
+                             select x).Count();
+
+            //일지
+            var reportJob = (from x in _context.BemsReportHistory
+                             where x.SiteId == curSiteId
+                             where x.CreateDate >= today
+                             select x).Count();
+
+            //보고서 
+            var report = (from x in _context.FmsDailyReport
+                          where x.SiteId == curSiteId
+                          where x.AddDate >= today
+                          select x).Count();
+
+            //자재부족
+            var materialStock = _serviceMaterialStock.Stock();
+            materialStock = materialStock.Where(x => x.Material.SiteId == curSiteId);
+            var stockList = materialStock.Select(x => new {
+                InsufficientStock = Util.ToLong(x.Material.ReasonableStockCount) - x.StockCount
+            }).ToList();
+            var InsufficientStock = stockList.Count() != 0 ? stockList.Where(x => x.InsufficientStock > 0).Count() : 0;
+
+
+            return Ok(new {
+                work = new {
+                    업무 = working,
+                    자재입고 = materialStored,
+                    자재조정 = materialRelease,
+                    공기구 = equipment,
+                    일지 = reportJob,
+                    보고서 = report,
+                    자재부족 = InsufficientStock
+                }
+            });
+        }
+
+
+        /** 
+         * 작업 현황(상태별)
+         */
+        [HttpGet("[action]")]
+        public IActionResult WorkStateSummary(int? siteId) {
+            var curSiteId = User.GetSiteId();
+            if (siteId != null) {
+                curSiteId = siteId.Value;
+            }
+
+            var today = DateTime.Now.Date;
+            var startDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+            var endDate = startDate.AddMonths(1).AddDays(-1);
+
+            //작업 대기
+            var wating = (from x in _context.FmsWorkRequest
+                          where x.SiteId == curSiteId
+                          where x.StartWorkDate.Date == today
+                          where new int[] { 3 }.Contains(x.WorkProgress.WorkProgressId) // 3:대기
+                          select x).Count();
+
+            // 작업 진행
+            var working = (from x in _context.FmsWorkResult
+                           where x.SiteId == curSiteId
+                           where x.StartDate.Date <= today && today <= x.EndDate.Date
+                           where new int[] { 4 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 4:진행
+                           select x).Count();
+
+            // 작업 보류 (2020-03-12요청 전체로)
+            var holding = (from x in _context.FmsWorkResult
+                           where x.SiteId == curSiteId
+                           //where x.StartDate.Date <= today && today <= x.EndDate.Date
+                           where new int[] { 7 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 7:보류
+                           select x).Count();
+
+            // 작업 완료
+            var completed = (from x in _context.FmsWorkResult
+                             where x.SiteId == curSiteId
+                             where x.EndDate.Date == today
+                             where new int[] { 5 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 5:완료
+                             select x).Count();
+
+            // 작업 승인
+            var approvaled = (from x in _context.FmsWorkResult
+                              where x.SiteId == curSiteId
+                              where x.ConfirmedDate.Value.Date == today
+                              where new int[] { 6 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 6:승인
+                              select x).Count();
+
+            // 미결 작업 (2020-03-12요청 전체로)
+            var unapprovaled = (from x in _context.FmsWorkResult
+                                where x.SiteId == curSiteId
+                                where new int[] { 5 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 5:완료
+                                select x).Count();
+
+            // 미결 일별보고서 (2020-03-12요청 전체로)
+            var unapprovalReport = (from x in _context.FmsDailyReport
+                                    where x.SiteId == curSiteId
+                                    //where x.AddDate.Date == today
+                                    where x.ApproverUserId == null
+                                    select x).Count();
+
+            return Ok(new {
+                work = new {
+                    작업대기 = wating,
+                    작업진행 = working,
+                    작업완료 = completed,
+                    작업승인 = approvaled,
+                    작업보류 = holding,
+                    작업미승인 = unapprovaled,
+                },
+                report = new {
+                    미결일지 = unapprovalReport
+                }
+            });
+        }
+
+
+        /** 
+         * (시설관리>업무분야별 작업현황)
+         */
+        [HttpGet("[action]")]
+        public IActionResult DailyWorkSummary(int? siteId) {
+            if (siteId == null) siteId = User.GetSiteId();
+
+            var startDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+            var endDate = startDate.AddMonths(1).AddDays(-1);
+
+            var query = from x in _context.FmsWorkResult
+                        where x.SiteId == siteId
+                        where x.EndDate.Date >= startDate && x.EndDate.Date <= endDate
+                        select new {
+                            x.FmsWorkRequest,
+                            x.FmsWorkRequest.WorkProgress,
+                            x.FmsWorkRequest.CmBusinessField,
+                        };
+
+            var groupQuery = from x in query
+                             group x by new { x.FmsWorkRequest.WorkProgressId, x.FmsWorkRequest.BusinessFieldId } into g
+                             select new {
+                                 BusinessFieldId = g.Key.BusinessFieldId,
+                                 BusinessFieldName = g.Min(x => x.CmBusinessField.Name),
+                                 WorkProgressId = g.Key.WorkProgressId,
+                                 WorkProgressName = g.Min(x => x.WorkProgress.Name),
+                                 Count = g.Count(),
+                             };
+
+            var groupBusiness = from x in groupQuery
+                                group x by x.BusinessFieldId into g
+                                select new {
+                                    BusinessFieldId = g.Key,
+                                    BusinessFieldName = g.Min(x => x.BusinessFieldName),
+                                    작업대기 = g.Sum(x => x.WorkProgressId == 3 ? x.Count : 0),
+                                    작업진행 = g.Sum(x => x.WorkProgressId == 4 ? x.Count : 0),
+                                    작업완료 = g.Sum(x => x.WorkProgressId == 5 ? x.Count : 0),
+                                    작업승인 = g.Sum(x => x.WorkProgressId == 6 ? x.Count : 0),
+                                    작업보류 = g.Sum(x => x.WorkProgressId == 7 ? x.Count : 0),
+                                };
+
+            return Ok(new {
+                year = startDate.Year,
+                month = startDate.Month,
+                list = groupBusiness
+            });
+        }
+
+        /** 
+         * 월별 작업 현황 (현장용)
+         */
+        [HttpGet("[action]")]
+        public IActionResult MonthlyWorkSummary(int? year, int? month, int? siteId) {
+            var curSiteId = User.GetSiteId();
+            if (siteId != null) {
+                curSiteId = siteId.Value;
+            }
+
+            DateTime startDate;
+            if (year == null || month == null) {
+                startDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+            } else {
+                startDate = new DateTime(year.Value, month.Value, 1);
+            }
+            var endDate = startDate.AddMonths(1).AddDays(-1);
+
+            var query = from x in _context.FmsWorkResult
+                        where x.SiteId == curSiteId
+                        where x.EndDate.Date >= startDate && x.EndDate.Date <= endDate
+                        where new int[] { 6 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 07/28 승인건만 완료로 노출
+                        select new {
+                            x.FmsWorkRequest,
+                            x.FmsWorkRequest.WorkType,
+                            x.FmsWorkRequest.CmBusinessField,
+                        };
+
+            var groupQuery = from x in query
+                             group x by new { x.FmsWorkRequest.WorkTypeId, x.FmsWorkRequest.BusinessFieldId } into g
+                             select new {
+                                 BusinessFieldId = g.Key.BusinessFieldId,
+                                 BusinessFieldName = g.Min(x => x.CmBusinessField.Name),
+                                 WorkTypeId = g.Key.WorkTypeId,
+                                 WorkTypeName = g.Min(x => x.WorkType.Name),
+                                 Count = g.Count(),
+                             };
+
+            var groupBusiness = from x in groupQuery
+                                group x by x.BusinessFieldId into g
+                                select new {
+                                    BusinessFieldId = g.Key,
+                                    BusinessFieldName = g.Min(x => x.BusinessFieldName),
+                                    정기점검 = g.Sum(x => x.WorkTypeId == 1 ? x.Count : 0),
+                                    법정검사 = g.Sum(x => x.WorkTypeId == 2 ? x.Count : 0),
+                                    수시점검 = g.Sum(x => x.WorkTypeId == 5 ? x.Count : 0),
+                                    공사작업 = g.Sum(x => x.WorkTypeId == 6 ? x.Count : 0),
+                                    법정교육 = g.Sum(x => x.WorkTypeId == 7 ? x.Count : 0),
+                                };
+
+            return Ok(new {
+                year = startDate.Year,
+                month = startDate.Month,
+                list = groupBusiness
+            });
+        }
+
+        /** 
+         * 월별 작업 현황 (관리자)
+         */
+        [HttpGet("[action]")]
+        public IActionResult MonthlySiteWorkSummary(int? year, int? month) {
+            DateTime startDate;
+            if (year == null || month == null) {
+                startDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+            } else {
+                startDate = new DateTime(year.Value, month.Value, 1);
+            }
+            var endDate = startDate.AddMonths(1).AddDays(-1);
+
+            var query = from x in _context.FmsWorkResult
+                        where x.EndDate.Date >= startDate && x.EndDate.Date <= endDate
+                        where new int[] { 6 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 07/28 승인건만 완료로 노출
+                        select new {
+                            x.FmsWorkRequest,
+                            x.FmsWorkRequest.Site,
+                            x.FmsWorkRequest.WorkType,
+                            x.FmsWorkRequest.CmBusinessField,
+                        };
+
+            var groupQuery = from x in query
+                             group x by new { x.FmsWorkRequest.WorkTypeId, x.Site.SiteId } into g
+                             select new {
+                                 SiteId = g.Key.SiteId,
+                                 SiteName = g.Min(x => x.Site.Name),
+                                 SortOrderNo = g.Min(x => x.Site.SortOrderNo),
+                                 WorkTypeId = g.Key.WorkTypeId,
+                                 WorkTypeName = g.Min(x => x.WorkType.Name),
+                                 Count = g.Count(),
+                             };
+
+            var groupBusiness = from x in groupQuery
+                                group x by x.SiteId into g
+                                select new {
+                                    SiteId = g.Key,
+                                    SiteName = g.Min(x => x.SiteName),
+                                    SortOrderNo = g.Min(x => x.SortOrderNo),
+                                    정기점검 = g.Sum(x => x.WorkTypeId == 1 ? x.Count : 0),
+                                    법정검사 = g.Sum(x => x.WorkTypeId == 2 ? x.Count : 0),
+                                    수시점검 = g.Sum(x => x.WorkTypeId == 5 ? x.Count : 0),
+                                    공사작업 = g.Sum(x => x.WorkTypeId == 6 ? x.Count : 0),
+                                    법정교육 = g.Sum(x => x.WorkTypeId == 7 ? x.Count : 0),
+                                };
+
+            return Ok(new {
+                year = startDate.Year,
+                month = startDate.Month,
+                list = groupBusiness.OrderBy(x => x.SortOrderNo)
+            });
+        }
+
+        /** 
+        * 월별 작업 현황 상세 (관리자)
+        */
+        [HttpGet("[action]")]
+        public IActionResult MonthlySiteWorkDetail(int? siteId, int? year, int? month) {
+            DateTime startDate;
+            if (year == null || month == null) {
+                startDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+            } else {
+                startDate = new DateTime(year.Value, month.Value, 1);
+            }
+            var endDate = startDate.AddMonths(1).AddDays(-1);
+
+            var resultQuery = from x in _context.FmsWorkResult
+                              where x.EndDate.Date >= startDate && x.EndDate.Date <= endDate
+                              where new int[] { 6 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 승인건만 완료로노출
+                              where x.SiteId == siteId
+                              select new {
+                                  x.FmsWorkRequest.Site,
+                                  x.FmsWorkRequest.WorkType,
+                                  x.FmsWorkRequest.WorkProgress.WorkProgressId,
+                              };
+
+            var requestQuery = from x in _context.FmsWorkRequest
+                               where x.StartWorkDate.Date >= startDate && x.StartWorkDate.Date <= endDate
+                               //  where new int[] { 1 }.Contains(x.WorkProgress.WorkProgressId) // 1: 계획
+                               where x.SiteId == siteId
+                               select new {
+                                   x.Site,
+                                   x.WorkType,
+                                   WorkProgressId = 1,
+                               };
+
+            var groupQuery = from x in resultQuery.Concat(requestQuery)
+                             group x by new { x.WorkType.WorkTypeId } into g
+                             select new {
+                                 SiteId = g.Min(x => x.Site.SiteId),
+                                 SiteName = g.Min(x => x.Site.Name),
+                                 TypeName = g.Min(x => x.WorkType.Name),
+                                 계획 = g.Sum(x => x.WorkProgressId == 1 ? 1 : 0),
+                                 완료 = g.Sum(x => x.WorkProgressId == 6 ? 1 : 0),
+                             };
+            return Ok(new {
+                year = startDate.Year,
+                month = startDate.Month,
+                list = groupQuery,
+            });
+        }
+
+        /** 
+                    * 금일 작업 현황 상세 (현장)
+                    */
+        [HttpGet("[action]")]
+        public IActionResult DailiySiteWorkDetail(int? siteId, string date, int? businessId) {
+            var today = DateTime.Now.Date;
+            if (!string.IsNullOrEmpty(date)) {
+                try {
+                    today = DateTime.Parse(date);
+                } catch (Exception ex) {
+                    today = DateTime.Now.Date;
+                }
+            }
+
+            var resultQuery = from x in _context.FmsWorkResult
+                              where x.EndDate.Date == today.Date
+                              where new int[] { 5, 6 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 5:완료, 6:확인]
+                              where x.SiteId == siteId
+                              where x.FmsWorkRequest.BusinessFieldId == businessId
+                              select new {
+                                  x.FmsWorkRequest.Site,
+                                  x.FmsWorkRequest.WorkType,
+                                  x.FmsWorkRequest.WorkProgress.WorkProgressId,
+                                  x.FmsWorkRequest.CmBusinessField,
+                              };
+
+            var requestQuery = from x in _context.FmsWorkRequest
+                               where x.StartWorkDate.Date == today.Date
+                               where x.SiteId == siteId
+                               where x.BusinessFieldId == businessId
+                               select new {
+                                   x.Site,
+                                   x.WorkType,
+                                   WorkProgressId = 1,
+                                   x.CmBusinessField
+                               };
+
+            var groupQuery = from x in resultQuery.Concat(requestQuery)
+                             group x by new { x.CmBusinessField.BusinessFieldId, x.WorkType.WorkTypeId } into g
+                             select new {
+                                 SiteId = g.Min(x => x.Site.SiteId),
+                                 SiteName = g.Min(x => x.Site.Name),
+                                 BusinessName = g.Min(x => x.CmBusinessField.Name),
+                                 TypeName = g.Min(x => x.WorkType.Name),
+                                 계획 = g.Sum(x => x.WorkProgressId == 1 ? 1 : 0),
+                                 완료 = g.Sum(x => x.WorkProgressId == 5 ? 1 : 0),
+                                 승인 = g.Sum(x => x.WorkProgressId == 6 ? 1 : 0),
+                             };
+
+            return Ok(new {
+                list = groupQuery,
+            });
+        }
+
+
+        /** 
+            * 월별 작업 현황 상세 (현장)
+            */
+        [HttpGet("[action]")]
+        public IActionResult MonthlyWorkDetail(int? siteId, int? year, int? month, int? businessId) {
+            DateTime startDate;
+            if (year == null || month == null) {
+                startDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+            } else {
+                startDate = new DateTime(year.Value, month.Value, 1);
+            }
+            var endDate = startDate.AddMonths(1).AddDays(-1);
+
+            var resultQuery = from x in _context.FmsWorkResult
+                              where x.EndDate.Date >= startDate && x.EndDate.Date <= endDate
+                              where new int[] { 6 }.Contains(x.FmsWorkRequest.WorkProgress.WorkProgressId) // 승인건만 완료로노출
+                              where x.SiteId == siteId
+                              where x.FmsWorkRequest.BusinessFieldId == businessId
+                              select new {
+                                  x.FmsWorkRequest.Site,
+                                  x.FmsWorkRequest.WorkType,
+                                  x.FmsWorkRequest.WorkProgress.WorkProgressId,
+                                  x.FmsWorkRequest.CmBusinessField,
+                              };
+
+            var requestQuery = from x in _context.FmsWorkRequest
+                               where x.StartWorkDate.Date >= startDate && x.StartWorkDate.Date <= endDate
+                               //where new int[] { 1 }.Contains(x.WorkProgress.WorkProgressId) // 1: 계획
+                               where x.SiteId == siteId
+                               where x.BusinessFieldId == businessId
+                               select new {
+                                   x.Site,
+                                   x.WorkType,
+                                   WorkProgressId = 1,
+                                   x.CmBusinessField
+                               };
+
+            var groupQuery = from x in resultQuery.Concat(requestQuery)
+                             group x by new { x.CmBusinessField.BusinessFieldId, x.WorkType.WorkTypeId } into g
+                             select new {
+                                 SiteId = g.Min(x => x.Site.SiteId),
+                                 SiteName = g.Min(x => x.Site.Name),
+                                 BusinessName = g.Min(x => x.CmBusinessField.Name),
+                                 TypeName = g.Min(x => x.WorkType.Name),
+                                 계획 = g.Sum(x => x.WorkProgressId == 1 ? 1 : 0),
+                                 완료 = g.Sum(x => x.WorkProgressId == 6 ? 1 : 0),
+                             };
+
+            return Ok(new {
+                year = startDate.Year,
+                month = startDate.Month,
+                list = groupQuery,
+            });
+        }
+
+
+        /**
+         * 사이트별 월별 에너지
+         */
+        [HttpGet("[action]")]
+        public IActionResult EnergyWholeMonthSite(int? siteId, EnergyType? type, string month) {
+            var start = DateTime.Parse(month + "-01");
+            var end = start.Date.AddMonths(1).AddDays(-1);
+
+            return EnergyDaily(type, start.ToString("yyyy-MM-dd"), end.ToString("yyyy-MM-dd"), siteId);
+        }
+
+
+        /**
+            * 사이트별 연도별 에너지
+            */
+        [HttpGet("[action]")]
+        public IActionResult EnergyWholeYearSite(int? siteId, EnergyType? type, string year) {
+            var curSiteId = User.GetSiteId();
+            if (siteId != null) {
+                curSiteId = siteId.Value;
+            }
+            if (Util.IsNullOrEmpty(year)) year = DateTime.Now.ToString("yyyy");
+            var start = DateTime.Parse(year + "-01-01");
+            var end = DateTime.Parse(year + "-12-31");
+
+            var lastStart = start.Date.AddYears(-1);
+            var lastEnd = end.Date.AddYears(-1);
+            IList<EnergyDaily> list = new List<EnergyDaily>();
+
+            var cacheFile = $"/temp/_EnergyWholeYearSite_{siteId ?? 0}_{(type == null ? "전체" : type.ToString())}_{year}.json";
+            if (_storageHelper.FileExists(cacheFile)) {
+                var jsonText = _storageHelper.ReadAllText(cacheFile);
+                return Ok(new {
+                    type,
+                    list = JsonConvert.DeserializeObject(jsonText)
+                });
+            }
+
+            try {
+                IDictionary<string, CalculationResult> electMap = null;
+                IDictionary<string, CalculationResult> lastelectMap = null;
+                IDictionary<string, CalculationResult> gasMap = null;
+                IDictionary<string, CalculationResult> lastgasMap = null;
+                IDictionary<string, CalculationResult> waterMap = null;
+                IDictionary<string, CalculationResult> lastwaterMap = null;
+                IDictionary<string, CalculationResult> lampOilMap = null;
+                IDictionary<string, CalculationResult> lastlampOilMap = null;
+                IDictionary<string, CalculationResult> lpgMap = null;
+                IDictionary<string, CalculationResult> lastlpgMap = null;
+                IDictionary<string, CalculationResult> mediumWaterMap = null;
+                IDictionary<string, CalculationResult> lastmediumWaterMap = null;
+
+                try {
+                    if (type == null || type.Value == EnergyType.전기) {
+                        electMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.전기, start, end);
+                        lastelectMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.전기, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+                try {
+                    if (type == null || type.Value == EnergyType.가스) {
+                        gasMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.가스, start, end);
+                        lastgasMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.가스, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+                try {
+                    if (type == null || type.Value == EnergyType.수도) {
+                        waterMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.수도, start, end);
+                        lastwaterMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.수도, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+                try {
+                    if (type == null || type.Value == EnergyType.등유) {
+                        lampOilMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.등유, start, end);
+                        lastlampOilMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.등유, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+                try {
+                    if (type == null || type.Value == EnergyType.LPG) {
+                        lpgMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.LPG, start, end);
+                        lastlpgMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.LPG, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+                try {
+                    if (type == null || type.Value == EnergyType.중온수) {
+                        mediumWaterMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.중온수, start, end);
+                        lastmediumWaterMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.월, EnergyType.중온수, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+
+                var months = (end.Month - start.Month) + 12 * (end.Year - start.Year) + 1;
+                var date = start;
+                var lastDate = lastStart;
+                for (var i = 0; i < months; i++) {
+                    var dateStr = date.ToString("yyyy-MM-dd");
+                    var lastStr = lastDate.ToString("yyyy-MM-dd");
+
+                    list.Add(new EnergyDaily {
+                        Date = date.ToString("yyyy-MM"),
+                        전기 = electMap?.GetValue(dateStr)?.Value ?? 0,
+                        이전_전기 = lastelectMap?.GetValue(lastStr)?.Value ?? 0,
+                        가스 = gasMap?.GetValue(dateStr)?.Value ?? 0,
+                        이전_가스 = lastgasMap?.GetValue(lastStr)?.Value ?? 0,
+                        수도 = waterMap?.GetValue(dateStr)?.Value ?? 0,
+                        이전_수도 = lastwaterMap?.GetValue(lastStr)?.Value ?? 0,
+                        등유 = lampOilMap?.GetValue(dateStr).Value ?? 0,
+                        이전_등유 = lastlampOilMap?.GetValue(lastStr).Value ?? 0,
+                        LPG = lpgMap?.GetValue(dateStr).Value ?? 0,
+                        이전_LPG = lastlpgMap?.GetValue(lastStr).Value ?? 0,
+                        중온수 = mediumWaterMap?.GetValue(dateStr).Value ?? 0,
+                        이전_중온수 = lastmediumWaterMap?.GetValue(lastStr).Value ?? 0
+                    });
+                    date = date.AddMonths(1);
+                    lastDate = lastDate.AddMonths(1);
+                }
+
+                if (type == null)
+                    list = convertTOESite(list, start, end, EnergyInterval.월);
+
+                if (list != null) {
+                    _storageHelper.WriteAllText(cacheFile, JsonConvert.SerializeObject(list));
+                }
+
+                return Ok(new {
+                    type,
+                    list
+                });
+            } catch (FormatException ex) {
+                _logger.LogError("", ex.ToString());
+                throw new Exception("날짜형식을 올바르게 입력해주세요");
+            }
+        }
+
+
+        public IList<EnergyDaily> convertTOESite(IList<EnergyDaily> list, DateTime start, DateTime end, EnergyInterval interval) {
+            var date = start;
+            var returnList = new List<EnergyDaily>();
+            var days = interval == EnergyInterval.월 ? (end.Month - start.Month) + 12 * (end.Year - start.Year) + 1 : end.Subtract(start).Days;
+            var dateStrFormat = interval == EnergyInterval.월 ? "yyyy-MM" : "yyyy-MM-dd";
+
+            for (var i = 0; i < days; i++) {
+                var dateStr = date.ToString(dateStrFormat);
+                var temp = list.Where(x => x.Date == dateStr).FirstOrDefault();
+                var toe = 0.0;
+                var pre_toe = 0.0;
+                toe += getTOE(EnergyType.전기, temp.전기);
+                pre_toe += getTOE(EnergyType.전기, temp.이전_전기);
+                toe += getTOE(EnergyType.가스, temp.가스);
+                pre_toe += getTOE(EnergyType.가스, temp.이전_가스);
+                toe += getTOE(EnergyType.등유, temp.등유);
+                pre_toe += getTOE(EnergyType.등유, temp.이전_등유);
+                toe += getTOE(EnergyType.LPG, temp.LPG);
+                pre_toe += getTOE(EnergyType.LPG, temp.이전_LPG);
+                toe += getTOE(EnergyType.중온수, temp.중온수);
+                pre_toe += getTOE(EnergyType.중온수, temp.이전_중온수);
+
+                returnList.Add(new EnergyDaily {
+                    Date = dateStr,
+                    TOE = Math.Round(toe, 2),
+                    이전_TOE = Math.Round(pre_toe, 2),
+                });
+                date = interval == EnergyInterval.월 ? date.AddMonths(1) : date.AddDays(1);
+            }
+            return returnList;
+        }
+
+        /**
+         * 사이트별 일별 에너지
+         */
+        [HttpGet("[action]")]
+        public IActionResult EnergyDaily(EnergyType? type, string startDate, string endDate, int? siteId) {
+            var curSiteId = User.GetSiteId();
+            if (siteId != null) {
+                curSiteId = siteId.Value;
+            }
+            _logger.LogInformation("siteId:" + curSiteId);
+
+            IList<EnergyDaily> list = new List<EnergyDaily>();
+
+            try {
+                var start = DateTime.Now.Date.AddDays(-2);
+                if (!string.IsNullOrEmpty(startDate)) {
+                    start = DateTime.Parse(startDate);
+                }
+                var end = DateTime.Now.Date;
+                if (!string.IsNullOrEmpty(endDate)) {
+                    end = DateTime.Parse(endDate);
+                }
+
+                var lastStart = start.Date.AddMonths(-1);
+                var lastEnd = end.Date.AddMonths(-1);
+                IDictionary<string, CalculationResult> electMap = null;
+                IDictionary<string, CalculationResult> lastelectMap = null;
+                IDictionary<string, CalculationResult> gasMap = null;
+                IDictionary<string, CalculationResult> lastgasMap = null;
+                IDictionary<string, CalculationResult> waterMap = null;
+                IDictionary<string, CalculationResult> lastwaterMap = null;
+                IDictionary<string, CalculationResult> lampOilMap = null;
+                IDictionary<string, CalculationResult> lastlampOilMap = null;
+                IDictionary<string, CalculationResult> lpgMap = null;
+                IDictionary<string, CalculationResult> lastlpgMap = null;
+                IDictionary<string, CalculationResult> mediumWaterMap = null;
+                IDictionary<string, CalculationResult> lastmediumWaterMap = null;
+
+
+                try {
+                    if (type == null || type.Value == EnergyType.전기) {
+                        electMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.전기, start, end);
+                        lastelectMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.전기, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+                try {
+                    if (type == null || type.Value == EnergyType.가스) {
+                        gasMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.가스, start, end);
+                        lastgasMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.가스, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+                try {
+                    if (type == null || type.Value == EnergyType.수도) {
+                        waterMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.수도, start, end);
+                        lastwaterMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.수도, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+                try {
+                    if (type == null || type.Value == EnergyType.등유) {
+                        lampOilMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.등유, start, end);
+                        lastlampOilMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.등유, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+                try {
+                    if (type == null || type.Value == EnergyType.LPG) {
+                        lpgMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.LPG, start, end);
+                        lastlpgMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.LPG, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+                try {
+                    if (type == null || type.Value == EnergyType.중온수) {
+                        mediumWaterMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.중온수, start, end);
+                        lastmediumWaterMap = _serviceEnergyUsage.GetEnergyDailyDic(curSiteId, EnergyInterval.일, EnergyType.중온수, lastStart, lastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogInformation(ex, "");
+                }
+                // 취합
+                var days = end.Subtract(start).Days + 1;
+                _logger.LogInformation("days " + days);
+                var date = start;
+
+                for (var i = 0; i < days; i++) {
+                    var dateStr = date.ToString("yyyy-MM-dd");
+                    var lastStr = date.Date.AddMonths(-1).ToString("yyyy-MM-dd");
+
+                    list.Add(new EnergyDaily {
+                        Date = dateStr,
+                        전기 = electMap?.GetValue(dateStr)?.Value ?? 0,
+                        이전_전기 = lastelectMap?.GetValue(lastStr)?.Value ?? 0,
+                        가스 = gasMap?.GetValue(dateStr)?.Value ?? 0,
+                        이전_가스 = lastgasMap?.GetValue(lastStr)?.Value ?? 0,
+                        수도 = waterMap?.GetValue(dateStr)?.Value ?? 0,
+                        이전_수도 = lastwaterMap?.GetValue(lastStr)?.Value ?? 0,
+                        등유 = lampOilMap?.GetValue(dateStr)?.Value ?? 0,
+                        이전_등유 = lastlampOilMap?.GetValue(lastStr)?.Value ?? 0,
+                        LPG = lpgMap?.GetValue(dateStr)?.Value ?? 0,
+                        이전_LPG = lastlpgMap?.GetValue(lastStr)?.Value ?? 0,
+                        중온수 = mediumWaterMap?.GetValue(dateStr)?.Value ?? 0,
+                        이전_중온수 = lastmediumWaterMap?.GetValue(lastStr)?.Value ?? 0
+
+                    });
+                    date = date.AddDays(1);
+                }
+
+                if (type == null)
+                    list = convertTOESite(list, start, end, EnergyInterval.일);
+                return Ok(new {
+                    type,
+                    list
+                });
+            } catch (FormatException ex) {
+                _logger.LogInformation(ex, "");
+                throw new Exception("날짜형식을 올바르게 입력해주세요");
+            }
+        }
+
+
+
+        /**
+        * 전체 연도별 에너지
+        */
+        [HttpGet("[action]")]
+        public IActionResult EnergyWholeYear(EnergyType? type, string year) {
+
+            if (Util.IsNullOrEmpty(year)) year = DateTime.Now.ToString("yyyy");
+            var start = DateTime.Parse(year + "-01-01");
+            var end = DateTime.Parse(year + "-12-31");
+            var lastStart = start.Date.AddYears(-1);
+            var lastEnd = end.Date.AddYears(-1);
+            IDictionary<string, EnergyDaily> list = new Dictionary<string, EnergyDaily>();
+
+            var cacheFile = $"/temp/_EnergyWholeYear_{(type == null ? "전체" : type.ToString())}_{year}.json";
+            if (_storageHelper.FileExists(cacheFile)) {
+                var jsonText = _storageHelper.ReadAllText(cacheFile);
+                return Ok(new {
+                    type,
+                    list = JsonConvert.DeserializeObject(jsonText)
+                });
+            }
+
+            try {
+                var isTOE = type == null ? true : false;
+                var siteList = _context.CmSite.ToList();
+                foreach (var site in siteList) {
+                    var siteId = site.SiteId;
+
+                    //_logger.LogInformation("siteId:" + siteId);
+
+                    IDictionary<string, CalculationResult> electMap = null;
+                    IDictionary<string, CalculationResult> lastelectMap = null;
+                    IDictionary<string, CalculationResult> gasMap = null;
+                    IDictionary<string, CalculationResult> lastgasMap = null;
+                    IDictionary<string, CalculationResult> waterMap = null;
+                    IDictionary<string, CalculationResult> lastwaterMap = null;
+                    IDictionary<string, CalculationResult> lampOilMap = null;
+                    IDictionary<string, CalculationResult> lastlampOilMap = null;
+                    IDictionary<string, CalculationResult> lpgMap = null;
+                    IDictionary<string, CalculationResult> lastlpgMap = null;
+                    IDictionary<string, CalculationResult> mediumWaterMap = null;
+                    IDictionary<string, CalculationResult> lastmediumWaterMap = null;
+
+                    try {
+                        if (type == null || type.Value == EnergyType.전기) {
+                            electMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.전기, start, end);
+                            lastelectMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.전기, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+                    try {
+                        if (type == null || type.Value == EnergyType.가스) {
+                            gasMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.가스, start, end);
+                            lastgasMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.가스, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+                    try {
+                        if (type == null || type.Value == EnergyType.수도) {
+                            waterMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.수도, start, end);
+                            lastwaterMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.수도, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+                    try {
+                        if (type == null || type.Value == EnergyType.등유) {
+                            lampOilMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.등유, start, end);
+                            lastlampOilMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.등유, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+                    try {
+                        if (type == null || type.Value == EnergyType.LPG) {
+                            lpgMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.LPG, start, end);
+                            lastlpgMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.LPG, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+                    try {
+                        if (type == null || type.Value == EnergyType.중온수) {
+                            mediumWaterMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.중온수, start, end);
+                            lastmediumWaterMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.월, EnergyType.중온수, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+
+                    var months = (end.Month - start.Month) + 12 * (end.Year - start.Year) + 1;
+                    var date = start;
+                    var lastDate = lastStart;
+
+                    for (var i = 0; i < months; i++) {
+                        var dateStr = date.ToString("yyyy-MM-dd");
+                        var lastStr = lastDate.ToString("yyyy-MM-dd");
+
+                        if (list.ContainsKey(dateStr)) {
+                            list[dateStr].전기 += electMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].가스 += gasMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].수도 += waterMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].등유 += lampOilMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].LPG += lpgMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].중온수 += mediumWaterMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].이전_전기 += lastelectMap?.GetValue(lastStr)?.Value ?? 0;
+                            list[dateStr].이전_가스 += lastgasMap?.GetValue(lastStr)?.Value ?? 0;
+                            list[dateStr].이전_수도 += lastwaterMap?.GetValue(lastStr)?.Value ?? 0;
+                            list[dateStr].이전_등유 += lastlampOilMap?.GetValue(lastStr)?.Value ?? 0;
+                            list[dateStr].이전_LPG += lastlpgMap?.GetValue(lastStr)?.Value ?? 0;
+                            list[dateStr].이전_중온수 += lastmediumWaterMap?.GetValue(lastStr)?.Value ?? 0;
+                        } else {
+                            list.Add(dateStr, new EnergyDaily {
+                                Date = date.ToString("yyyy-MM"),
+                                전기 = electMap?.GetValue(dateStr)?.Value ?? 0,
+                                이전_전기 = lastelectMap?.GetValue(lastStr)?.Value ?? 0,
+                                가스 = gasMap?.GetValue(dateStr)?.Value ?? 0,
+                                이전_가스 = lastgasMap?.GetValue(lastStr)?.Value ?? 0,
+                                수도 = waterMap?.GetValue(dateStr)?.Value ?? 0,
+                                이전_수도 = lastwaterMap?.GetValue(lastStr)?.Value ?? 0,
+                                등유 = lampOilMap?.GetValue(dateStr).Value ?? 0,
+                                이전_등유 = lastlampOilMap?.GetValue(lastStr).Value ?? 0,
+                                LPG = lpgMap?.GetValue(dateStr).Value ?? 0,
+                                이전_LPG = lastlpgMap?.GetValue(lastStr).Value ?? 0,
+                                중온수 = mediumWaterMap?.GetValue(dateStr).Value ?? 0,
+                                이전_중온수 = lastmediumWaterMap?.GetValue(lastStr).Value ?? 0
+                            });
+                        }
+                        date = date.AddMonths(1);
+                        lastDate = lastDate.AddMonths(1);
+                    }
+                }
+                if (type == null)
+                    list = convertTOE(list, start, end, EnergyInterval.월);
+
+
+                if (list != null) {
+                    _storageHelper.WriteAllText(cacheFile, JsonConvert.SerializeObject(list.Values));
+                }
+                return Ok(new {
+                    type,
+                    list = list.Values
+                });
+            } catch (FormatException ex) {
+                _logger.LogError("", ex.ToString());
+                throw new Exception("날짜형식을 올바르게 입력해주세요");
+            }
+        }
+
+        /**
+       * 전체 월별 에너지
+       */
+        [HttpGet("[action]")]
+        public IActionResult EnergyWholeMonth(EnergyType? type, string month) {
+            var start = DateTime.Parse(month + "-01");
+            var end = start.Date.AddMonths(1).AddDays(-1);
+
+            return EnergyWholeDaily(type, start.ToString("yyyy-MM-dd"), end.ToString("yyyy-MM-dd"));
+        }
+
+        /**
+         * 전체 일별 에너지
+         */
+        [HttpGet("[action]")]
+        public IActionResult EnergyWholeDaily(EnergyType? type, string startDate, string endDate) {
+
+            IDictionary<string, EnergyDaily> list = new Dictionary<string, EnergyDaily>();
+
+            try {
+                var isTOE = type == null ? true : false;
+
+                var start = DateTime.Now.Date.AddDays(-2);
+                if (!string.IsNullOrEmpty(startDate)) {
+                    start = DateTime.Parse(startDate);
+                }
+                var end = DateTime.Now.Date;
+                if (!string.IsNullOrEmpty(endDate)) {
+                    end = DateTime.Parse(endDate);
+                }
+
+                var lastStart = start.Date.AddMonths(-1);
+                var lastEnd = end.Date.AddMonths(-1);
+
+                var siteList = _context.CmSite.ToList();
+                _logger.LogInformation("site count:" + siteList.Count());
+                foreach (var site in siteList) {
+                    var siteId = site.SiteId;
+
+                    _logger.LogInformation("siteId:" + siteId);
+
+                    IDictionary<string, CalculationResult> electMap = null;
+                    IDictionary<string, CalculationResult> lastelectMap = null;
+                    IDictionary<string, CalculationResult> gasMap = null;
+                    IDictionary<string, CalculationResult> lastgasMap = null;
+                    IDictionary<string, CalculationResult> waterMap = null;
+                    IDictionary<string, CalculationResult> lastwaterMap = null;
+                    IDictionary<string, CalculationResult> lampOilMap = null;
+                    IDictionary<string, CalculationResult> lastlampOilMap = null;
+                    IDictionary<string, CalculationResult> lpgMap = null;
+                    IDictionary<string, CalculationResult> lastlpgMap = null;
+                    IDictionary<string, CalculationResult> mediumWaterMap = null;
+                    IDictionary<string, CalculationResult> lastmediumWaterMap = null;
+
+                    try {
+                        if (type == null || type.Value == EnergyType.전기) {
+                            electMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.전기, start, end);
+                            lastelectMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.전기, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+                    try {
+                        if (type == null || type.Value == EnergyType.가스) {
+                            gasMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.가스, start, end);
+                            lastgasMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.가스, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+                    try {
+                        if (type == null || type.Value == EnergyType.수도) {
+                            waterMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.수도, start, end);
+                            lastwaterMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.수도, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+                    try {
+                        if (type == null || type.Value == EnergyType.등유) {
+                            lampOilMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.등유, start, end);
+                            lastlampOilMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.등유, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+                    try {
+                        if (type == null || type.Value == EnergyType.LPG) {
+                            lpgMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.LPG, start, end);
+                            lastlpgMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.LPG, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+                    try {
+                        if (type == null || type.Value == EnergyType.중온수) {
+                            mediumWaterMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.중온수, start, end);
+                            lastmediumWaterMap = _serviceEnergyUsage.GetEnergyDailyDic(siteId, EnergyInterval.일, EnergyType.중온수, lastStart, lastEnd);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogInformation(ex, "");
+                    }
+                    // 취합
+                    var days = end.Subtract(start).Days + 1;
+                    _logger.LogInformation("days:" + days);
+                    var date = start;
+                    var lastDate = lastStart;
+
+                    for (var i = 0; i < days; i++) {
+                        var dateStr = date.ToString("yyyy-MM-dd");
+                        var lastStr = lastDate.ToString("yyyy-MM-dd");
+                        if (list.ContainsKey(dateStr)) {
+                            list[dateStr].전기 += electMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].가스 += gasMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].수도 += waterMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].등유 += lampOilMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].LPG += lpgMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].중온수 += mediumWaterMap?.GetValue(dateStr)?.Value ?? 0;
+                            list[dateStr].이전_전기 += lastelectMap?.GetValue(lastStr)?.Value ?? 0;
+                            list[dateStr].이전_가스 += lastgasMap?.GetValue(lastStr)?.Value ?? 0;
+                            list[dateStr].이전_수도 += lastwaterMap?.GetValue(lastStr)?.Value ?? 0;
+                            list[dateStr].이전_등유 += lastlampOilMap?.GetValue(lastStr)?.Value ?? 0;
+                            list[dateStr].이전_LPG += lastlpgMap?.GetValue(lastStr)?.Value ?? 0;
+                            list[dateStr].이전_중온수 += lastmediumWaterMap?.GetValue(lastStr)?.Value ?? 0;
+
+                        } else {
+                            list.Add(dateStr, new EnergyDaily {
+                                Date = date.ToString("yyyy-MM-dd"),
+                                전기 = electMap?.GetValue(dateStr)?.Value ?? 0,
+                                이전_전기 = lastelectMap?.GetValue(lastStr)?.Value ?? 0,
+                                가스 = gasMap?.GetValue(dateStr)?.Value ?? 0,
+                                이전_가스 = lastgasMap?.GetValue(lastStr)?.Value ?? 0,
+                                수도 = waterMap?.GetValue(dateStr)?.Value ?? 0,
+                                이전_수도 = lastwaterMap?.GetValue(lastStr)?.Value ?? 0,
+                                등유 = lampOilMap?.GetValue(dateStr).Value ?? 0,
+                                이전_등유 = lastlampOilMap?.GetValue(lastStr).Value ?? 0,
+                                LPG = lpgMap?.GetValue(dateStr).Value ?? 0,
+                                이전_LPG = lastlpgMap?.GetValue(lastStr).Value ?? 0,
+                                중온수 = mediumWaterMap?.GetValue(dateStr).Value ?? 0,
+                                이전_중온수 = lastmediumWaterMap?.GetValue(lastStr).Value ?? 0
+                            });
+                        }
+                        date = date.AddDays(1);
+                        lastDate = lastDate.AddDays(1);
+                    }
+
+                }
+                if (type == null)
+                    list = convertTOE(list, start, end, EnergyInterval.일);
+                return Ok(new {
+                    type,
+                    list = list.Values
+                });
+            } catch (FormatException ex) {
+                _logger.LogError("", ex.ToString());
+                throw new Exception("날짜형식을 올바르게 입력해주세요");
+            }
+        }
+
+        public IDictionary<string, EnergyDaily> convertTOE(IDictionary<string, EnergyDaily> list, DateTime start, DateTime end, EnergyInterval interval) {
+            var date = start;
+            var days = interval == EnergyInterval.월 ? (end.Month - start.Month) + 12 * (end.Year - start.Year) + 1 : end.Subtract(start).Days;
+
+            for (var i = 0; i < days; i++) {
+                var dateStr = date.ToString("yyyy-MM-dd");
+                var toe = 0.0;
+                var pre_toe = 0.0;
+                toe += getTOE(EnergyType.전기, list.GetValue(dateStr).전기);
+                pre_toe += getTOE(EnergyType.전기, list.GetValue(dateStr).이전_전기);
+                toe += getTOE(EnergyType.가스, list.GetValue(dateStr).가스);
+                pre_toe += getTOE(EnergyType.가스, list.GetValue(dateStr).이전_가스);
+                toe += getTOE(EnergyType.등유, list.GetValue(dateStr).등유);
+                pre_toe += getTOE(EnergyType.등유, list.GetValue(dateStr).이전_등유);
+                toe += getTOE(EnergyType.LPG, list.GetValue(dateStr).LPG);
+                pre_toe += getTOE(EnergyType.LPG, list.GetValue(dateStr).이전_LPG);
+                toe += getTOE(EnergyType.중온수, list.GetValue(dateStr).중온수);
+                pre_toe += getTOE(EnergyType.중온수, list.GetValue(dateStr).이전_중온수);
+
+                if (list.ContainsKey(dateStr)) {
+                    list[dateStr].TOE = Math.Round(toe, 2);
+                    list[dateStr].이전_TOE = Math.Round(pre_toe, 2);
+                }
+                date = interval == EnergyInterval.월 ? date.AddMonths(1) : date.AddDays(1);
+            }
+            return list;
+        }
+        public double getTOE(EnergyType? type, double val) {
+            var exp = 0.0;
+            if (val == 0) return 0;
+            if (type == EnergyType.전기) {
+                exp = 0.000215;
+            } else if (type == EnergyType.가스)
+                exp = 0.001055;
+            else if (type == EnergyType.등유)
+                exp = 0.000895;
+            else if (type == EnergyType.LPG)
+                exp = 0.0015;
+            else if (type == EnergyType.중온수)
+                exp = 0.001055;
+            else
+                return val;
+            return val * exp;
+        }
+
+
+        private IQueryable<EnergyDaily> _GetEnergyOld(int? siteId, DateTime startDate, DateTime endDate) {
+            var energy = _context.BemsEnergyDaily.Select(x => x);
+
+            if (siteId != null) {
+                energy = energy.Where(x => x.SiteId == siteId.Value);
+            }
+
+            var query = from x in energy
+                        where x.CreatedDate.Date >= startDate.Date && x.CreatedDate.Date <= endDate.Date
+                        select new {
+                            x.CreatedDate.Date,
+                            x.FuelType,
+                            x.Measurement,
+                        };
+
+            var groupQuery = from x in query
+                             group x by new { x.FuelType.FuelTypeId, x.Date } into g
+                             orderby g.Key.Date, g.Key.FuelTypeId
+                             select new {
+                                 Date = g.Key.Date,
+                                 FuelTypeId = g.Key.FuelTypeId,
+                                 FuelTypeName = g.Min(x => x.FuelType.Name),
+                                 Measurement = g.Sum(x => x.Measurement)
+                             };
+
+
+            var groupDate = from x in groupQuery
+                            group x by x.Date into g
+                            select new EnergyDaily {
+                                Date = g.Key.Date.ToString("yyyy-MM-dd"),
+                                전기 = g.Sum(x => x.FuelTypeId == 1 ? x.Measurement : 0) ?? 0,
+                                가스 = g.Sum(x => x.FuelTypeId == 2 ? x.Measurement : 0) ?? 0,
+                                수도 = g.Sum(x => x.FuelTypeId == 3 ? x.Measurement : 0) ?? 0,
+                            };
+
+            return groupDate;
+        }
+
+        private IQueryable<CalendarEvent> _GetRequestEventList(int? siteId, DateTime startDate, DateTime endDate) {
+            var events = _context.FmsWorkRequest.Select(x => x);
+            if (siteId != null) {
+                events = events.Where(x => x.SiteId == siteId.Value);
+            }
+
+            var query = from x in events
+                        where x.FmsWorkResult == null
+                        where x.StartWorkDate.Date >= startDate.Date && x.StartWorkDate.Date <= endDate.Date
+                        && (x.FmsWorkSchedule == null || (x.FmsWorkSchedule != null && x.FmsWorkSchedule.IsUse == true))
+                        select x;
+
+            var list = query.Select(x => new CalendarEvent {
+                Title = x.Title,
+                Start = x.StartWorkDate.ToString("yyyy-MM-dd"),
+                End = x.StartWorkDate.ToString("yyyy-MM-dd"),
+                WorkType = x.WorkType.Name,
+                Type = "예정",
+                WorkRequestId = x.WorkRequestId,
+                SiteId = x.SiteId,
+            });
+
+            return list;
+        }
+
+        private IQueryable<CalendarEvent> _GetResultEventList(int? siteId, DateTime startDate, DateTime endDate) {
+            var events = _context.FmsWorkResult.Select(x => x);
+            if (siteId != null) {
+                events = events.Where(x => x.SiteId == siteId.Value);
+            }
+
+            var query = from x in events
+                        where x.StartDate.Date >= startDate.Date && x.StartDate <= endDate.Date
+                        select x;
+
+            var list = query.Select(x => new CalendarEvent {
+                Title = x.FmsWorkRequest.Title,
+                Start = x.StartDate.ToString("yyyy-MM-dd"),
+                End = x.EndDate.ToString("yyyy-MM-dd"),
+                WorkType = x.FmsWorkRequest.WorkType.Name,
+                Type = "진행",
+                WorkRequestId = x.WorkRequestId,
+                SiteId = x.SiteId,
+            });
+
+            return list;
+        }
+
+        private IList<CalendarBackground> _GetHolidayList(int siteId, int year, int month, DateTime startDate, DateTime endDate) {
+            var list = new List<CalendarBackground>();
+
+            var holidays = _context.CmHoliday.Where(
+                x => x.SiteId == siteId
+                && x.IsUse == true
+            );
+
+            foreach (var item in holidays) {
+                if (item.IsLunar) {
+                    var solar = LunarSolarConverter.LunarToSolar(new Lunar() {
+                        lunarYear = year,
+                        lunarMonth = item.HolidayMonth,
+                        lunarDay = item.HolidayDay
+                    });
+                    list.Add(new CalendarBackground {
+                        Title = item.Name + "(음력)",
+                        Date = new DateTime(solar.solarYear, solar.solarMonth, solar.solarDay).ToString("yyyy-MM-dd"),
+                        Type = "공휴일",
+                    });
+                } else {
+                    list.Add(new CalendarBackground {
+                        Title = item.Name,
+                        Date = new DateTime(year, item.HolidayMonth, item.HolidayDay).ToString("yyyy-MM-dd"),
+                        Type = "공휴일",
+                    });
+                }
+            }
+
+            var query = from x in _context.CmHolidayCustom
+                        where x.SiteId == siteId
+                        where x.HolidayDate.Date >= startDate.Date && x.HolidayDate <= endDate.Date
+                        select x;
+
+            var customs = query.Select(x => new CalendarBackground {
+                Title = x.Name,
+                Date = x.HolidayDate.ToString("yyyy-MM-dd"),
+                Type = "휴일",
+            });
+            list.AddRange(customs);
+
+            return list;
+        }
+
+        private IQueryable<CalendarEvent> _GetWeekKeyWorkEventList(int? siteId, DateTime startDate, DateTime endDate) {
+            var query = _context.FmsWeekKeyWork.Select(x => x);
+            if (siteId != null) {
+                query = query.Where(x => x.SiteId == siteId.Value);
+            }
+            query = query.Where(x => x.WorkDate.Date >= startDate.Date && x.WorkDate.Date <= endDate.Date);
+
+            var list = query.Select(x => new CalendarEvent {
+                Title = x.WorkName,
+                Start = x.WorkDate.ToString("yyyy-MM-dd"),
+                End = x.WorkDate.ToString("yyyy-MM-dd"),
+                WorkType = "주별중점업무",
+                Type = "주별 중점업무",
+                WorkRequestId = x.WeekKeyWorkId,
+                SiteId = x.SiteId,
+            });
+
+            return list;
+        }
+
+    }
+
+}

+ 214 - 0
Controllers/DepartmentController.cs

@@ -0,0 +1,214 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class DepartmentController : Controller {
+        private readonly ILogger<DepartmentController> _logger;
+        private readonly FMSContext _context;
+        private readonly DepartmentService _service;
+        private readonly StorageHelper _storage;
+
+        public DepartmentController(
+            ILogger<DepartmentController> logger,
+            FMSContext context,
+            DepartmentService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 그룹 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{companyId}/{id}")]
+        public IActionResult Get(int siteId, int companyId, int id) {
+            var data = _service.Gets(id, siteId, companyId);
+            if (data.Count() == 0) {
+                return NotFound("부서정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.DepartmentId,
+                x.SiteId,
+                SiteName = x.Site.Name,
+                CmSite = new {
+                    x.Site.Name,
+                },
+                x.CompanyId,
+                CompanyName = x.CmCompany.Name,
+                CmCompany = new {
+                    x.CmCompany.Name,
+                },
+                x.Name,
+                x.ShortName,
+                x.FaxNo,
+                x.PhoneNo,
+                x.Comment,
+                x.IsUse,
+            });
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{siteId}/{companyId}/{id}")]
+        public IActionResult Edit(int siteId, int companyId, int id, [FromBody] CmDepartment data) {
+            if (id != data.DepartmentId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, companyId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmDepartment data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{companyId}/{id}")]
+        public IActionResult Delete(int siteId, int companyId, int id) {
+            _service.Delete(id, siteId, companyId);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<CmDepartment> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = x.Site.Name,
+                x.DepartmentId,
+                x.CompanyId,
+                CompanyName = x.CmCompany.Name,
+                x.Name,
+                x.PhoneNo,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N",
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmDepartment> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            // 고정정렬 (사옥 준공순)
+            var sortQuery = query.OrderBy(x => x.Site.SortOrderNo);
+
+            // 추가 정렬
+            sortQuery = sortQuery.ThenSort(req.sort);
+
+            return sortQuery;
+        }
+    }
+}

+ 162 - 0
Controllers/DrawingCodeGroupController.cs

@@ -0,0 +1,162 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class DrawingCodeGroupController : Controller {
+        private readonly ILogger<DrawingCodeGroupController> _logger;
+        private readonly FMSContext _context;
+        private DrawingCodeGroupService _service;
+
+        public DrawingCodeGroupController(
+            ILogger<DrawingCodeGroupController> logger,
+            FMSContext context,
+            DrawingCodeGroupService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsDrawingCodeGroup> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsDrawingCodeGroup> query) {
+            var list = query.Select(x => new {
+                x.DrawingGroupId,
+                x.Name,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N",
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.DrawingGroupId,
+                x.Name,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] FmsDrawingCodeGroup data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.DrawingGroupId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsDrawingCodeGroup data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 162 - 0
Controllers/DrawingCodeTypeController.cs

@@ -0,0 +1,162 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class DrawingCodeTypeController : Controller {
+        private readonly ILogger<DrawingCodeTypeController> _logger;
+        private readonly FMSContext _context;
+        private DrawingCodeTypeService _service;
+
+        public DrawingCodeTypeController(
+            ILogger<DrawingCodeTypeController> logger,
+            FMSContext context,
+            DrawingCodeTypeService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsDrawingCodeType> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsDrawingCodeType> query) {
+            var list = query.Select(x => new {
+                x.DrawingTypeId,
+                x.Name,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N"
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.DrawingTypeId,
+                x.Name,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] FmsDrawingCodeType data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.DrawingTypeId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsDrawingCodeType data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 199 - 0
Controllers/DrawingController.cs

@@ -0,0 +1,199 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class DrawingController : Controller {
+        private readonly ILogger<DrawingController> _logger;
+        private readonly FMSContext _context;
+        private DrawingService _service;
+
+        public DrawingController(
+            ILogger<DrawingController> logger,
+            FMSContext context,
+            DrawingService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsDrawing> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+
+            // 고정정렬 (사옥 준공순)
+            var sortQuery = query.OrderBy(x => x.CmSite.SortOrderNo);
+
+            // 추가 정렬
+            sortQuery = sortQuery.ThenSort(req.sort);
+
+            return sortQuery;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsDrawing> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                DrawingGroupName = x.FmsDrawingCodeGroup.Name,
+                DrawingTypeName = x.FmsDrawingCodeType.Name,
+                x.DrawingNo,
+                x.Name,
+                x.DrawingGroupId,
+                x.DrawingTypeId,
+                x.DrawingId,
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.DrawingId,
+                x.Name,
+                x.SiteId,
+                x.DrawingGroupId,
+                x.DrawingTypeId,
+                x.DrawingNo,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                FmsDrawingCodeGroup = new {
+                    x.FmsDrawingCodeGroup.Name
+                },
+                FmsDrawingCodeType = new {
+                    x.FmsDrawingCodeType.Name
+                },
+                x.Description
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] FmsDrawing data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.DrawingId) {
+                return BadRequest("잘못된 접근입니다.>>");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsDrawing data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                data.CreateUserId = User.Identity.Name;
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+            return Ok();
+        }
+
+    }
+}

+ 175 - 0
Controllers/DrawingHistoryController.cs

@@ -0,0 +1,175 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using FMSAdmin.Services;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class DrawingHistoryController : Controller {
+        private readonly ILogger<DrawingHistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly DrawingHistoryService _service;
+        private readonly StorageHelper _storage;
+
+        public DrawingHistoryController(
+            ILogger<DrawingHistoryController> logger,
+            FMSContext context,
+            DrawingHistoryService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsDrawingHistory> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.DrawingId,
+                x.HistoryId,
+                x.RevisionNo,
+                x.FileId,
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.Description,
+                x.UpdatedDate,
+                UpdateUserName = x.CmUser.Name,
+            });
+            return list;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] string drawingId) {
+
+            //수정필요
+            var query = _FilterAndSort(req);
+            query = query.Where(x => x.SiteId == Util.ToInt(siteId) && x.DrawingId == Util.ToInt(drawingId));
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpDelete("{siteId}/{drawingId}/{historyId}")]
+        public IActionResult Delete(int siteId, int drawingId, int historyId) {
+            _service.Delete(siteId, drawingId, historyId);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsDrawingHistory> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{drawingId}/{historyId}")]
+        public IActionResult Get(int siteId, int drawingId, int historyId) {
+            var data = _service.Get(siteId, drawingId, historyId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.SiteId,
+                x.DrawingId,
+                x.HistoryId,
+                x.RevisionNo,
+                CmUser = new {
+                    x.CmUser.Name,
+                },
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.Description
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{drawingId}")]
+        public int GetNextRevisionNo(int siteId, int drawingId) {
+            int nextRevisionNo = _service.GetRevisionNo(siteId, drawingId);
+            return nextRevisionNo;
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsDrawingHistory data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{drawingId}/{historyId}")]
+        public IActionResult Edit(int siteId, int drawingId, int historyId, [FromBody] FmsDrawingHistory data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + historyId);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+            if (historyId != data.HistoryId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                data.UpdatedUserId = User.Identity.Name;
+                _service.Edit(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        // [HttpDelete("{siteId}/{drawingId}/{historyId}")]
+        // public IActionResult Delete(int siteId, int id) {
+        //     _service.Delete(id, siteId);
+        //     return Ok();
+        // }
+    }
+}

+ 400 - 0
Controllers/EnergyDayAnalysisController.cs

@@ -0,0 +1,400 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using FMSAdmin.Data;
+using FMSAdmin.Helpers;
+using System.Collections;
+using System.Text.RegularExpressions;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Models.Formula;
+using FMSAdmin.Helpers.Formula;
+using FMSAdmin.Services;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class EnergyDayAnalysisController : Controller {
+        private readonly ILogger<EnergyDayAnalysisController> _logger;
+        private readonly EnergyDayAnalysisService _service;
+        private readonly EnergyUsageService _serviceUsage;
+        private readonly FMSContext _context;
+
+        public EnergyDayAnalysisController(
+            ILogger<EnergyDayAnalysisController> logger,
+            EnergyDayAnalysisService service,
+            EnergyUsageService serviceUsage,
+            FMSContext context
+            ) {
+            _logger = logger;
+            _service = service;
+            _serviceUsage = serviceUsage;
+            _context = context;
+        }
+
+        /**
+         * 사이트별 일별 에너지
+         */
+        [HttpGet("[action]")]
+        public IActionResult Data(int siteId, EnergyInterval interval, EnergyType? type, string startDate, string endDate, bool excel = false) {
+            _logger.LogInformation("siteId:" + siteId);
+
+            try {
+                var start = DateTime.Now.Date.AddDays(-2);
+                var end = DateTime.Now.Date;
+                if (!string.IsNullOrEmpty(startDate)) {
+                    start = DateTime.Parse(startDate);
+                }
+                if (!string.IsNullOrEmpty(endDate)) {
+                    end = DateTime.Parse(endDate);
+                }
+                if (interval == EnergyInterval.월) {
+                    end = end.AddMonths(1).AddDays(-1);
+                }
+                var lastStart = start.AddYears(-1);
+                var lsastEnd = end.AddYears(-1);
+                if (interval == EnergyInterval.일) {
+                    lastStart = GetLastYearSameWeekDate(start);
+                    lsastEnd = GetLastYearSameWeekDate(end);
+                }
+
+                _logger.LogInformation("start" + start.ToShortDateString());
+
+                IDictionary<string, EnergyDaily> list = new Dictionary<string, EnergyDaily>();
+                IDictionary<string, CalculationResult> energyMap = null;
+                IDictionary<string, CalculationResult> energyLastMap = null;
+                IDictionary<string, CalculationResult> electMap = null;
+                IDictionary<string, CalculationResult> lastElectMap = null;
+                IDictionary<string, CalculationResult> gasMap = null;
+                IDictionary<string, CalculationResult> lastGasMap = null;
+                IDictionary<string, CalculationResult> lampOilMap = null;
+                IDictionary<string, CalculationResult> lastLampOilMap = null;
+                IDictionary<string, CalculationResult> lpgMap = null;
+                IDictionary<string, CalculationResult> lastLpgMap = null;
+                IDictionary<string, CalculationResult> mediumWaterMap = null;
+                IDictionary<string, CalculationResult> lastMediumWaterMap = null;
+                try {
+                    if (type != null) {
+                        energyMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, (EnergyType)type, start, end);
+                        energyLastMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, (EnergyType)type, lastStart, lsastEnd);
+                    } else {
+                        //TOE
+                        electMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, EnergyType.전기, start, end);
+                        lastElectMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, EnergyType.전기, lastStart, lsastEnd);
+                        gasMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, EnergyType.가스, start, end);
+                        lastGasMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, EnergyType.가스, lastStart, lsastEnd);
+                        lampOilMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, EnergyType.등유, start, end);
+                        lastLampOilMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, EnergyType.등유, lastStart, lsastEnd);
+                        lpgMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, EnergyType.LPG, start, end);
+                        lastLpgMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, EnergyType.LPG, lastStart, lsastEnd);
+                        mediumWaterMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, EnergyType.중온수, start, end);
+                        lastMediumWaterMap = _serviceUsage.GetEnergyDailyDic(siteId, interval, EnergyType.중온수, lastStart, lsastEnd);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogError(ex, "");
+                }
+
+                if (type != null) {
+                    _DateFillAll(interval, start, end, lastStart, (EnergyType)type, energyMap, energyLastMap, list);
+                } else {
+                    //TOE
+                    _DateFillAll(interval, start, end, lastStart, EnergyType.전기, electMap, lastElectMap, list);
+                    _DateFillAll(interval, start, end, lastStart, EnergyType.가스, gasMap, lastGasMap, list);
+                    _DateFillAll(interval, start, end, lastStart, EnergyType.등유, lampOilMap, lastLampOilMap, list);
+                    _DateFillAll(interval, start, end, lastStart, EnergyType.LPG, lpgMap, lastLpgMap, list);
+                    _DateFillAll(interval, start, end, lastStart, EnergyType.중온수, mediumWaterMap, lastMediumWaterMap, list);
+
+                    list = convertTOE(list, start, end, interval);
+                }
+
+                IDictionary<string, CalculationLastResult> energyList = new Dictionary<string, CalculationLastResult>();
+                _DataGrid(interval, start, end, lastStart, list, energyList, type);
+
+                if (excel) {
+                    var stream = new MemoryStream();
+                    using (var package = new ExcelPackage(stream)) {
+                        var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                        workSheet.Cells.LoadFromCollection(energyList.Values, true);
+
+                        workSheet.AddStyle(new PagingRequest.Column[] {
+                            new PagingRequest.Column{
+                                name = "올해",
+                                field = "ShortDateTime",
+                            },
+                            new PagingRequest.Column{
+                                name = type.ToString(),
+                                field = "Value",
+                                align = "right",
+                                format = "pricePoint"
+                            },
+                            new PagingRequest.Column{
+                                name = "작년",
+                                field = "LastShortDateTime",
+                            },
+                            new PagingRequest.Column{
+                                name = type.ToString(),
+                                field = "LastValue",
+                                align = "right",
+                                format = "pricePoint"
+                            },
+
+                        });
+
+                        package.Save();
+                    }
+                    stream.Position = 0;
+                    string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+                    return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+                }
+                double maxValue = energyList.Select(x => x.Value.LastValue).Max() > energyList.Select(x => x.Value.Value).Max() ? energyList.Select(x => x.Value.LastValue).Max() : energyList.Select(x => x.Value.Value).Max();
+
+                //증감이유 조회
+                int fuelTypeId = type != null ? (int)type + 1 : 0;
+                //var dataAnalysis = _service.GetAllAnalysis(115, 1, 2020, 6);
+                var dataAnalysis = _service.GetAllAnalysis(siteId, fuelTypeId, end.Year, end.Month);
+                var analysisGrid = dataAnalysis.Select(x => new {
+                    YearMonth = x.Year.ToString() + "." + x.Month.ToString("D2"),
+                    SiteId = x.SiteId,
+                    SiteName = x.CmSite.Name,
+                    Year = x.Year,
+                    Month = x.Month,
+                    FuelTypeId = x.FuelTypeId,
+                    x.FuelPriceId,
+                    // FuelTypePriceName = x.FuelType.Name + (x.FuelTypeId == 1 ? x.FuelPriceId == 1 ? " (일반용)" : x.FuelPriceId == 2 ? " (심야용)" : " (산업용)" : ""),
+                    FuelTypePriceName = x.FuelType.Name,
+                    x.PriceTypeId,
+                    PriceTypeName = x.PriceType.PriceTypeIdDesc,
+                });
+
+                return Ok(new {
+                    type,
+                    list = list.Values,
+                    listGrid = energyList.Values,
+                    maxValue = maxValue,
+                    analysisGrid = analysisGrid
+                });
+            } catch (FormatException ex) {
+                _logger.LogError(ex, "");
+                throw new Exception("날짜형식을 올바르게 입력해주세요");
+            }
+        }
+
+        private void _DateFillAll(EnergyInterval interval, DateTime start, DateTime end, DateTime lastStart, EnergyType energyType, IDictionary<string, CalculationResult> source, IDictionary<string, CalculationResult> lastSource, IDictionary<string, EnergyDaily> target) {
+            var duration = (interval == EnergyInterval.일) ? end.Subtract(start).Days + 1 : (end.Month - start.Month) + 12 * (end.Year - start.Year) + 1;
+            var date = start;
+            var lastDate = lastStart;
+            for (var i = 0; i < duration; i++) {
+                var dateStr = date.ToString("yyyy-MM-dd");
+                var lastStr = lastDate.ToString("yyyy-MM-dd");
+
+                if (target.ContainsKey(dateStr)) {
+                    if (energyType == EnergyType.전기) target[dateStr].전기 += source?.GetValue(dateStr)?.Value ?? 0;
+                    if (energyType == EnergyType.가스) target[dateStr].가스 += source?.GetValue(dateStr)?.Value ?? 0;
+                    if (energyType == EnergyType.수도) target[dateStr].수도 += source?.GetValue(dateStr)?.Value ?? 0;
+                    if (energyType == EnergyType.등유) target[dateStr].등유 += source?.GetValue(dateStr)?.Value ?? 0;
+                    if (energyType == EnergyType.LPG) target[dateStr].LPG += source?.GetValue(dateStr)?.Value ?? 0;
+                    if (energyType == EnergyType.중온수) target[dateStr].중온수 += source?.GetValue(dateStr)?.Value ?? 0;
+
+                    //전년도
+                    if (energyType == EnergyType.전기) target[dateStr].이전_전기 += lastSource?.GetValue(lastStr)?.Value ?? 0;
+                    if (energyType == EnergyType.가스) target[dateStr].이전_가스 += lastSource?.GetValue(lastStr)?.Value ?? 0;
+                    if (energyType == EnergyType.수도) target[dateStr].이전_수도 += lastSource?.GetValue(lastStr)?.Value ?? 0;
+                    if (energyType == EnergyType.등유) target[dateStr].이전_등유 += lastSource?.GetValue(lastStr)?.Value ?? 0;
+                    if (energyType == EnergyType.LPG) target[dateStr].이전_LPG += lastSource?.GetValue(lastStr)?.Value ?? 0;
+                    if (energyType == EnergyType.중온수) target[dateStr].이전_중온수 += lastSource?.GetValue(lastStr)?.Value ?? 0;
+                } else {
+                    target.Add(dateStr, new EnergyDaily {
+                        Date = dateStr,
+                        전기 = energyType == EnergyType.전기 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                        가스 = energyType == EnergyType.가스 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                        수도 = energyType == EnergyType.수도 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                        등유 = energyType == EnergyType.등유 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                        LPG = energyType == EnergyType.LPG ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                        중온수 = energyType == EnergyType.중온수 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                        TOE = 0,
+                        이전_전기 = energyType == EnergyType.전기 ? lastSource?.GetValue(lastStr)?.Value ?? 0 : 0,
+                        이전_가스 = energyType == EnergyType.가스 ? lastSource?.GetValue(lastStr)?.Value ?? 0 : 0,
+                        이전_수도 = energyType == EnergyType.수도 ? lastSource?.GetValue(lastStr)?.Value ?? 0 : 0,
+                        이전_등유 = energyType == EnergyType.등유 ? lastSource?.GetValue(lastStr)?.Value ?? 0 : 0,
+                        이전_LPG = energyType == EnergyType.LPG ? lastSource?.GetValue(lastStr)?.Value ?? 0 : 0,
+                        이전_중온수 = energyType == EnergyType.중온수 ? lastSource?.GetValue(lastStr)?.Value ?? 0 : 0,
+                        이전_TOE = 0,
+                    });
+                }
+                if (interval == EnergyInterval.일) {
+                    date = date.AddDays(1);
+                    lastDate = lastDate.AddDays(1);
+                } else if (interval == EnergyInterval.월) {
+                    date = date.AddMonths(1);
+                    lastDate = lastDate.AddMonths(1);
+                }
+            }
+        }
+
+        public IDictionary<string, EnergyDaily> convertTOE(IDictionary<string, EnergyDaily> list, DateTime start, DateTime end, EnergyInterval interval) {
+            var date = start;
+            var days = interval == EnergyInterval.월 ? (end.Month - start.Month) + 12 * (end.Year - start.Year) + 1 : end.Subtract(start).Days;
+
+            for (var i = 0; i < days; i++) {
+                var dateStr = date.ToString("yyyy-MM-dd");
+                if (list.ContainsKey(dateStr)) {
+                    list[dateStr].TOE = getTOE(EnergyType.전기, list.GetValue(dateStr).전기);
+                    list[dateStr].TOE += getTOE(EnergyType.가스, list.GetValue(dateStr).가스);
+                    list[dateStr].TOE += getTOE(EnergyType.수도, list.GetValue(dateStr).수도);
+                    list[dateStr].TOE += getTOE(EnergyType.등유, list.GetValue(dateStr).등유);
+                    list[dateStr].TOE += getTOE(EnergyType.LPG, list.GetValue(dateStr).LPG);
+                    list[dateStr].중온수 = getTOE(EnergyType.중온수, list.GetValue(dateStr).중온수);
+                    list[dateStr].이전_TOE = getTOE(EnergyType.전기, list.GetValue(dateStr).이전_전기);
+                    list[dateStr].이전_TOE += getTOE(EnergyType.가스, list.GetValue(dateStr).이전_가스);
+                    list[dateStr].이전_TOE += getTOE(EnergyType.수도, list.GetValue(dateStr).이전_수도);
+                    list[dateStr].이전_TOE += getTOE(EnergyType.등유, list.GetValue(dateStr).이전_등유);
+                    list[dateStr].이전_TOE += getTOE(EnergyType.LPG, list.GetValue(dateStr).이전_LPG);
+                    list[dateStr].이전_TOE += getTOE(EnergyType.중온수, list.GetValue(dateStr).이전_중온수);
+                    list[dateStr].TOE = Math.Round(list[dateStr].TOE, 2);
+                    list[dateStr].이전_TOE = Math.Round(list[dateStr].이전_TOE, 2);
+                }
+                date = interval == EnergyInterval.월 ? date.AddMonths(1) : date.AddDays(1);
+            }
+
+            return list;
+        }
+
+        public double getTOE(EnergyType? type, double val) {
+            var exp = 0.0;
+            if (val == 0) return 0;
+            if (type == EnergyType.전기) {
+                exp = 0.000215;
+            } else if (type == EnergyType.가스)
+                exp = 0.001055;
+            else if (type == EnergyType.등유)
+                exp = 0.000895;
+            else if (type == EnergyType.LPG)
+                exp = 0.0015;
+            else if (type == EnergyType.중온수)
+                exp = 0.001055;
+            else
+                return val;
+            return Math.Round(val * exp, 2);
+        }
+
+        private void _DataGrid(EnergyInterval interval, DateTime start, DateTime end, DateTime lastStart, IDictionary<string, EnergyDaily> source, IDictionary<string, CalculationLastResult> target, EnergyType? type) {
+            if (target == null) {
+                target = new Dictionary<string, CalculationLastResult>();
+            }
+            var days = (interval == EnergyInterval.일) ? end.Subtract(start).Days + 1 : (end.Month - start.Month) + 12 * (end.Year - start.Year) + 1;
+            var date = start;
+            var lastDate = lastStart;
+            for (var i = 0; i < days; i++) {
+                var dateStr = date.ToString("yyyy-MM-dd");
+                var lastStr = lastDate.ToString("yyyy-MM-dd");
+                double d_a = 0, dd_a = 0;
+                if (type != null) {
+                    d_a = Math.Round((source[dateStr].전기 + source[dateStr].가스 + source[dateStr].수도 + source[dateStr].등유 + source[dateStr].LPG + source[dateStr].중온수), 2);
+                    dd_a = Math.Round((source[dateStr].이전_전기 + source[dateStr].이전_가스 + source[dateStr].이전_수도 + source[dateStr].이전_등유 + source[dateStr].이전_LPG + source[dateStr].이전_중온수), 2);
+                } else {
+                    d_a = source[dateStr].TOE;
+                    dd_a = source[dateStr].이전_TOE;
+                }
+
+                target.Add(dateStr, new CalculationLastResult {
+                    DateTime = date,
+                    ShortDateTime = interval == EnergyInterval.일 ? dateStr : date.ToString("yyyy-MM"),
+                    Value = d_a,
+                    LastDateTime = lastDate,
+                    LastShortDateTime = interval == EnergyInterval.일 ? lastStr : lastDate.ToString("yyyy-MM"),
+                    LastValue = dd_a,
+                    DayOfWeek = interval == EnergyInterval.일 ? " (" + date.GetDayofWeek() + ")" : ""
+                });
+
+                if (interval == EnergyInterval.일) {
+                    date = date.AddDays(1);
+                    lastDate = lastDate.AddDays(1);
+                } else if (interval == EnergyInterval.월) {
+                    date = date.AddMonths(1);
+                    lastDate = lastDate.AddMonths(1);
+                }
+            }
+        }
+
+        private DateTime GetLastYearSameWeekDate(DateTime dDate) {
+            DateTime rDate = dDate.AddYears(-1);
+
+            for (var i = 0; i < 7; i++) {
+                if (rDate.DayOfWeek == dDate.DayOfWeek) {
+                    break;
+                }
+
+                rDate = rDate.AddDays(1);
+            }
+
+            return rDate;
+        }
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        [HttpPost("{siteId}/{fuelTypeId}/{fuelPriceId}/{year}/{month}/{priceTypeId}")]
+        public IActionResult Save(int siteId, int fuelTypeId, int fuelPriceId, string year, string month, int priceTypeId, [FromBody] FmsEnergyAnalysis data) {
+            if (ModelState.IsValid) {
+                _service.Save(siteId, fuelTypeId, fuelPriceId, Util.ToInt(year), Util.ToInt(month), priceTypeId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult GetAnalysisData(int siteId, int fuelTypeId, int fuelPriceId, string year, string month, int priceTypeId) {
+            var data = _service.GetAnalysis(siteId, fuelTypeId, fuelPriceId, year, month, priceTypeId);
+
+            var item = data.Select(x => new {
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.FuelTypeId,
+                BemsFuelType = new {
+                    x.FuelType.Name,
+                },
+                x.FuelPriceId,
+                x.Year,
+                x.Month,
+                x.Content,
+                x.PriceTypeId,
+                BemsPriceType = new {
+                    x.PriceType.PriceTypeIdDesc,
+                },
+            });
+
+            return Ok(item);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult GetPriceType(int siteId, int fuelTypeId) {
+            var query = from x in _context.BemsPriceType
+                        join s in _context.BemsSitePrice on new { x.PriceTypeId } equals new { s.PriceTypeId }
+                        where s.SiteId == siteId
+                        where s.FuelTypeId == fuelTypeId
+                        where s.UseYn == "Y"
+                        select new {
+                            PriceTypeId = x.PriceTypeId,
+                            PriceTypeIdDesc = x.PriceTypeIdDesc
+                        };
+
+            var list = query.GroupBy(x => new { x.PriceTypeId, x.PriceTypeIdDesc }).Select(x => new { x.Key.PriceTypeId, x.Key.PriceTypeIdDesc }).ToList();
+
+            return Ok(list);
+        }
+    }
+}

+ 430 - 0
Controllers/EnergyUsageController.cs

@@ -0,0 +1,430 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using FMSAdmin.Data;
+using FMSAdmin.Helpers;
+using System.Collections;
+using System.Text.RegularExpressions;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Models.Formula;
+using FMSAdmin.Helpers.Formula;
+using FMSAdmin.Services;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class EnergyUsageController : Controller {
+        private readonly ILogger<EnergyUsageController> _logger;
+        private readonly EnergyUsageService _service;
+        private readonly FMSContext _context;
+
+        public EnergyUsageController(
+            ILogger<EnergyUsageController> logger,
+            EnergyUsageService service,
+            FMSContext context
+            ) {
+            _logger = logger;
+            _service = service;
+            _context = context;
+        }
+
+        /**
+         * 사이트별 일별 에너지
+         */
+        [HttpGet("[action]")]
+        public IActionResult Data(int siteId, EnergyInterval interval, EnergyType? type, string startDate, string endDate, bool excel = false) {
+            _logger.LogInformation("siteId:" + siteId);
+
+            try {
+                var start = DateTime.Now.Date.AddDays(-2);
+                if (!string.IsNullOrEmpty(startDate)) {
+                    start = DateTime.Parse(startDate);
+                }
+                var end = DateTime.Now.Date;
+                if (!string.IsNullOrEmpty(endDate)) {
+                    end = DateTime.Parse(endDate);
+                }
+                if (interval == EnergyInterval.월) {
+                    end = end.AddMonths(1).AddDays(-1);
+                }
+
+                _logger.LogInformation("start" + start.ToShortDateString());
+
+                IDictionary<string, EnergyDaily> list = new Dictionary<string, EnergyDaily>();
+                IDictionary<string, CalculationResult> energyMap = null;
+                IDictionary<string, CalculationResult> electMap = null;
+                IDictionary<string, CalculationResult> gasMap = null;
+                IDictionary<string, CalculationResult> waterMap = null;
+                IDictionary<string, CalculationResult> lampOilMap = null;
+                IDictionary<string, CalculationResult> lpgMap = null;
+                IDictionary<string, CalculationResult> mediumWaterMap = null;
+                try {
+                    if (type != null) {
+                        energyMap = _service.GetEnergyDailyDic(siteId, interval, (EnergyType)type, start, end);
+                    } else {
+                        //TOE
+                        electMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.전기, start, end);
+                        gasMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.가스, start, end);
+                        waterMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.수도, start, end);
+                        lampOilMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.등유, start, end);
+                        lpgMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.LPG, start, end);
+                        mediumWaterMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.중온수, start, end);
+                    }
+                } catch (Exception ex) {
+                    _logger.LogError(ex, "");
+                }
+
+                if (type != null) {
+                    _DateFillAll(interval, start, end, (EnergyType)type, energyMap, list);
+                } else {
+                    //TOE                   
+                    _DateFillAll(interval, start, end, EnergyType.전기, electMap, list);
+                    _DateFillAll(interval, start, end, EnergyType.가스, gasMap, list);
+                    _DateFillAll(interval, start, end, EnergyType.등유, lampOilMap, list);
+                    _DateFillAll(interval, start, end, EnergyType.LPG, lpgMap, list);
+                    _DateFillAll(interval, start, end, EnergyType.중온수, mediumWaterMap, list);
+
+                    list = convertTOE(list, start, end, interval);
+                }
+
+                IDictionary<string, CalculationResult> energyList = new Dictionary<string, CalculationResult>();
+                _DataGrid(interval, start, end, list, energyList, type);
+
+                if (excel) {
+                    var stream = new MemoryStream();
+                    using (var package = new ExcelPackage(stream)) {
+                        var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+                        string typeName = type.ToString();
+                        if (type == null) typeName = "TOE";
+
+                        workSheet.Cells.LoadFromCollection(energyList.Values, true);
+
+                        workSheet.AddStyle(new PagingRequest.Column[] {
+                            new PagingRequest.Column{
+                                name = "날짜",
+                                field = "ShortDateTime",
+                            },
+                            new PagingRequest.Column{
+                                name = typeName,
+                                field = "Value",
+                                align = "right",
+                                format = "pricePoint"
+                            },
+                        });
+
+                        package.Save();
+                    }
+                    stream.Position = 0;
+                    string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+                    return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+                }
+
+                return Ok(new {
+                    type,
+                    list = list.Values,
+                    listGrid = energyList.Values,
+                    maxValue = energyList.Select(x => x.Value.Value).Max()
+                });
+            } catch (FormatException ex) {
+                _logger.LogError(ex, "");
+                throw new Exception("날짜형식을 올바르게 입력해주세요");
+            }
+        }
+
+        /**
+         * 전체 일별 에너지
+         */
+        [HttpGet("[action]")]
+        public IActionResult WholeData(EnergyInterval interval, EnergyType? type, string startDate, string endDate, bool excel = false) {
+            IDictionary<string, EnergyDaily> list = new Dictionary<string, EnergyDaily>();
+            IDictionary<string, CalculationResult> energyList = new Dictionary<string, CalculationResult>();
+
+            try {
+                var start = DateTime.Now.Date.AddDays(-2);
+                if (!string.IsNullOrEmpty(startDate)) {
+                    start = DateTime.Parse(startDate);
+                }
+                var end = DateTime.Now.Date;
+                if (!string.IsNullOrEmpty(endDate)) {
+                    end = DateTime.Parse(endDate);
+                }
+                if (interval == EnergyInterval.월) {
+                    end = end.AddMonths(1).AddDays(-1);
+                }
+
+                var siteList = _context.CmSite.ToList();
+                foreach (var site in siteList) {
+                    var siteId = site.SiteId;
+
+                    IDictionary<string, CalculationResult> energyMap = null;
+                    IDictionary<string, CalculationResult> electMap = null;
+                    IDictionary<string, CalculationResult> gasMap = null;
+                    IDictionary<string, CalculationResult> waterMap = null;
+                    IDictionary<string, CalculationResult> lampOilMap = null;
+                    IDictionary<string, CalculationResult> lpgMap = null;
+                    IDictionary<string, CalculationResult> mediumWaterMap = null;
+                    try {
+                        if (type != null) {
+                            energyMap = _service.GetEnergyDailyDic(siteId, interval, (EnergyType)type, start, end);
+                        } else {
+                            //TOE
+                            electMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.전기, start, end);
+                            gasMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.가스, start, end);
+                            waterMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.수도, start, end);
+                            lampOilMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.등유, start, end);
+                            lpgMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.LPG, start, end);
+                            mediumWaterMap = _service.GetEnergyDailyDic(siteId, interval, EnergyType.중온수, start, end);
+                        }
+                    } catch (Exception ex) {
+                        _logger.LogError(ex, "");
+                    }
+
+                    if (type != null) {
+                        _DateFillAll(interval, start, end, (EnergyType)type, energyMap, list);
+                    } else {
+                        //TOE                   
+                        _DateFillAll(interval, start, end, EnergyType.전기, electMap, list);
+                        _DateFillAll(interval, start, end, EnergyType.가스, gasMap, list);
+                        _DateFillAll(interval, start, end, EnergyType.등유, lampOilMap, list);
+                        _DateFillAll(interval, start, end, EnergyType.LPG, lpgMap, list);
+                        _DateFillAll(interval, start, end, EnergyType.중온수, mediumWaterMap, list);
+                    }
+                }
+                if (type == null) list = convertTOE(list, start, end, interval);
+                _DataGrid(interval, start, end, list, energyList, type);
+
+                if (excel) {
+                    var stream = new MemoryStream();
+                    using (var package = new ExcelPackage(stream)) {
+                        var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+                        string typeName = type.ToString();
+                        if (type == null) typeName = "TOE";
+
+                        workSheet.Cells.LoadFromCollection(energyList.Values, true);
+
+                        workSheet.AddStyle(new PagingRequest.Column[] {
+                            new PagingRequest.Column{
+                                name = "날짜",
+                                field = "ShortDateTime",
+                            },
+                            new PagingRequest.Column{
+                                name = typeName,
+                                field = "Value",
+                                align = "right",
+                                format = "pricePoint"
+                            },
+                        });
+
+                        package.Save();
+                    }
+                    stream.Position = 0;
+                    string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+                    return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+                }
+
+                return Ok(new {
+                    type,
+                    list = list.Values,
+                    listGrid = energyList.Values,
+                    maxValue = energyList.Select(x => x.Value.Value).Max()
+                });
+            } catch (FormatException ex) {
+                _logger.LogError(ex, "");
+                throw new Exception("날짜형식을 올바르게 입력해주세요");
+            }
+        }
+
+        private void _DateFill(EnergyInterval interval, DateTime start, DateTime end, IDictionary<string, CalculationResult> source, IDictionary<string, CalculationResult> target) {
+            if (target == null) {
+                target = new Dictionary<string, CalculationResult>();
+            }
+            if (interval == EnergyInterval.일) {
+                var days = end.Subtract(start).Days + 1;
+                var date = start;
+                for (var i = 0; i < days; i++) {
+                    var dateStr = date.ToString("yyyy-MM-dd");
+                    if (target.ContainsKey(dateStr)) {
+                        target[dateStr].Value += source?.GetValue(dateStr)?.Value ?? 0;
+                    } else {
+                        target.Add(dateStr, new CalculationResult {
+                            DateTime = date,
+                            ShortDateTime = dateStr,
+                            Value = source?.GetValue(dateStr)?.Value ?? 0,
+                        });
+                    }
+                    date = date.AddDays(1);
+                }
+            } else if (interval == EnergyInterval.월) {
+                var months = (end.Month - start.Month) + 12 * (end.Year - start.Year) + 1;
+                _logger.LogInformation("months:" + months);
+                var date = start;
+                for (var i = 0; i < months; i++) {
+                    var dateStr = date.ToString("yyyy-MM-dd");
+                    if (target.ContainsKey(dateStr)) {
+                        target[dateStr].Value += source?.GetValue(dateStr)?.Value ?? 0;
+                    } else {
+                        target.Add(dateStr, new CalculationResult {
+                            DateTime = date,
+                            ShortDateTime = dateStr,
+                            Value = source?.GetValue(dateStr)?.Value ?? 0,
+                        });
+                    }
+                    date = date.AddMonths(1);
+                }
+            }
+        }
+
+        private void _DateFillAll(EnergyInterval interval, DateTime start, DateTime end, EnergyType energyType, IDictionary<string, CalculationResult> source, IDictionary<string, EnergyDaily> target) {
+            if (interval == EnergyInterval.일) {
+                var days = end.Subtract(start).Days + 1;
+                var date = start;
+                for (var i = 0; i < days; i++) {
+                    var dateStr = date.ToString("yyyy-MM-dd");
+                    if (target.ContainsKey(dateStr)) {
+                        if (energyType == EnergyType.전기) target[dateStr].전기 += source?.GetValue(dateStr)?.Value ?? 0;
+                        if (energyType == EnergyType.가스) target[dateStr].가스 += source?.GetValue(dateStr)?.Value ?? 0;
+                        if (energyType == EnergyType.수도) target[dateStr].수도 += source?.GetValue(dateStr)?.Value ?? 0;
+                        if (energyType == EnergyType.등유) target[dateStr].등유 += source?.GetValue(dateStr)?.Value ?? 0;
+                        if (energyType == EnergyType.LPG) target[dateStr].LPG += source?.GetValue(dateStr)?.Value ?? 0;
+                        if (energyType == EnergyType.중온수) target[dateStr].중온수 += source?.GetValue(dateStr)?.Value ?? 0;
+                    } else {
+                        target.Add(dateStr, new EnergyDaily {
+                            Date = dateStr,
+                            전기 = energyType == EnergyType.전기 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            가스 = energyType == EnergyType.가스 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            수도 = energyType == EnergyType.수도 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            등유 = energyType == EnergyType.등유 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            LPG = energyType == EnergyType.LPG ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            중온수 = energyType == EnergyType.중온수 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            TOE = 0,
+                        });
+                    }
+                    date = date.AddDays(1);
+                }
+            } else if (interval == EnergyInterval.월) {
+                var months = (end.Month - start.Month) + 12 * (end.Year - start.Year) + 1;
+                _logger.LogInformation("months:" + months);
+                var date = start;
+                for (var i = 0; i < months; i++) {
+                    var dateStr = date.ToString("yyyy-MM-dd");
+                    if (target.ContainsKey(dateStr)) {
+                        if (energyType == EnergyType.전기) target[dateStr].전기 += source?.GetValue(dateStr)?.Value ?? 0;
+                        if (energyType == EnergyType.가스) target[dateStr].가스 += source?.GetValue(dateStr)?.Value ?? 0;
+                        if (energyType == EnergyType.수도) target[dateStr].수도 += source?.GetValue(dateStr)?.Value ?? 0;
+                        if (energyType == EnergyType.등유) target[dateStr].등유 += source?.GetValue(dateStr)?.Value ?? 0;
+                        if (energyType == EnergyType.LPG) target[dateStr].LPG += source?.GetValue(dateStr)?.Value ?? 0;
+                        if (energyType == EnergyType.중온수) target[dateStr].중온수 += source?.GetValue(dateStr)?.Value ?? 0;
+                    } else {
+                        target.Add(dateStr, new EnergyDaily {
+                            Date = dateStr,
+                            전기 = energyType == EnergyType.전기 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            가스 = energyType == EnergyType.가스 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            수도 = energyType == EnergyType.수도 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            등유 = energyType == EnergyType.등유 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            LPG = energyType == EnergyType.LPG ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            중온수 = energyType == EnergyType.중온수 ? source?.GetValue(dateStr)?.Value ?? 0 : 0,
+                            TOE = 0,
+                        });
+                    }
+                    date = date.AddMonths(1);
+                }
+            }
+        }
+
+        public IDictionary<string, EnergyDaily> convertTOE(IDictionary<string, EnergyDaily> list, DateTime start, DateTime end, EnergyInterval interval) {
+            var date = start;
+            var days = interval == EnergyInterval.월 ? (end.Month - start.Month) + 12 * (end.Year - start.Year) + 1 : end.Subtract(start).Days;
+
+            for (var i = 0; i < days; i++) {
+                var dateStr = date.ToString("yyyy-MM-dd");
+                if (list.ContainsKey(dateStr)) {
+                    list[dateStr].TOE = getTOE(EnergyType.전기, list.GetValue(dateStr).전기);
+                    list[dateStr].TOE += getTOE(EnergyType.가스, list.GetValue(dateStr).가스);
+                    list[dateStr].TOE += getTOE(EnergyType.수도, list.GetValue(dateStr).수도);
+                    list[dateStr].TOE += getTOE(EnergyType.등유, list.GetValue(dateStr).등유);
+                    list[dateStr].TOE += getTOE(EnergyType.LPG, list.GetValue(dateStr).LPG);
+                    list[dateStr].TOE += getTOE(EnergyType.중온수, list.GetValue(dateStr).중온수);
+                    list[dateStr].이전_TOE = getTOE(EnergyType.전기, list.GetValue(dateStr).이전_전기);
+                    list[dateStr].이전_TOE += getTOE(EnergyType.가스, list.GetValue(dateStr).이전_가스);
+                    list[dateStr].이전_TOE += getTOE(EnergyType.수도, list.GetValue(dateStr).이전_수도);
+                    list[dateStr].이전_TOE += getTOE(EnergyType.등유, list.GetValue(dateStr).이전_등유);
+                    list[dateStr].이전_TOE += getTOE(EnergyType.LPG, list.GetValue(dateStr).이전_LPG);
+                    list[dateStr].이전_TOE += getTOE(EnergyType.중온수, list.GetValue(dateStr).이전_중온수);
+                    list[dateStr].TOE = Math.Round(list[dateStr].TOE, 2);
+                    list[dateStr].이전_TOE = Math.Round(list[dateStr].이전_TOE, 2);
+                }
+                date = interval == EnergyInterval.월 ? date.AddMonths(1) : date.AddDays(1);
+            }
+            return list;
+        }
+
+        public double getTOE(EnergyType? type, double val) {
+            var exp = 0.0;
+            if (val == 0) return 0;
+            if (type == EnergyType.전기) {
+                exp = 0.000215;
+            } else if (type == EnergyType.가스)
+                exp = 0.001055;
+            else if (type == EnergyType.등유)
+                exp = 0.000895;
+            else if (type == EnergyType.LPG)
+                exp = 0.0015;
+            else if (type == EnergyType.중온수)
+                exp = 0.001055;
+            else
+                return val;
+            return Math.Round(val * exp, 2);
+        }
+
+        private void _DataGrid(EnergyInterval interval, DateTime start, DateTime end, IDictionary<string, EnergyDaily> source, IDictionary<string, CalculationResult> target, EnergyType? type) {
+            if (target == null) {
+                target = new Dictionary<string, CalculationResult>();
+            }
+            var days = (interval == EnergyInterval.일) ? end.Subtract(start).Days + 1 : (end.Month - start.Month) + 12 * (end.Year - start.Year) + 1;
+            var date = start;
+            for (var i = 0; i < days; i++) {
+                var dateStr = date.ToString("yyyy-MM-dd");
+                double d_a = 0;
+                if (type != null) {
+                    d_a = Math.Round((source[dateStr].전기 + source[dateStr].가스 + source[dateStr].수도 + source[dateStr].등유 + source[dateStr].LPG + source[dateStr].중온수), 2);
+                } else {
+                    d_a = source[dateStr].TOE;
+                }
+                target.Add(dateStr, new CalculationResult {
+                    DateTime = date,
+                    ShortDateTime = dateStr,
+                    Value = d_a,
+                });
+
+                if (interval == EnergyInterval.일) {
+                    date = date.AddDays(1);
+                } else if (interval == EnergyInterval.월) {
+                    date = date.AddMonths(1);
+                }
+            }
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult GetFuelType() {
+            var query = _service.GetFuelType();
+
+            var list = query.Select(x => new {
+                x.FuelTypeId,
+                x.Name
+            }).ToList();
+
+            return Ok(list);
+        }
+    }
+
+}

+ 131 - 0
Controllers/EntityController.cs

@@ -0,0 +1,131 @@
+
+using FMSAdmin.Data;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using System.Data;
+using System.Collections.Generic;
+using System.Linq;
+using FMSAdmin.Entities;
+using System;
+using FMSAdmin.Helpers;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class EntityController : Controller {
+        private readonly ILogger<EntityController> _logger;
+        private readonly FMSContext _context;
+
+        public EntityController(
+            ILogger<EntityController> logger,
+            FMSContext context
+            ) {
+            _logger = logger;
+            _context = context;
+        }
+
+        /// <summary>
+        /// 셀렉트
+        /// </summary>
+        [HttpGet]
+        [Route("[action]")]
+        public IActionResult Select(string siteId, string entity, string id, string name, string fields, string sort = "name", string where = null, bool useCheck = false, bool businessAuth = false) {
+            if (string.IsNullOrEmpty(entity)) {
+                return BadRequest("누락된 정보가 있습니다.");
+            }
+            if (string.IsNullOrEmpty(id)) {
+                if (entity.StartsWith("Cm")) {
+                    id = entity.Substring(2) + "Id";
+                } else if (entity.StartsWith("Bems")) {
+                    id = entity.Substring(4) + "Id";
+                } else if (entity.StartsWith("Fms")) {
+                    id = entity.Substring(3) + "Id";
+                }
+            }
+            if (string.IsNullOrEmpty(name)) {
+                name = "Name";
+            }
+
+            CmUser user = null;
+            if (entity == "CmBusinessField" && businessAuth) {
+                user = _context.CmUser.FirstOrDefault(x => x.UserId == User.Identity.Name);
+            }
+
+            // fields 로 넘어온 추가 필드 처리
+            var additionalFields = (fields ?? "").Split(',', StringSplitOptions.RemoveEmptyEntries);
+            var additionalFieldsSelect = "";
+            if (!string.IsNullOrEmpty(fields)) {
+                additionalFieldsSelect = "," + fields;
+            }
+
+            // Entity 검증 일단 SKIP
+
+            IList<Dictionary<string, object>> list = new List<Dictionary<string, object>>();
+            using (var command = _context.Database.GetDbConnection().CreateCommand()) {
+                string wheresql = "";
+                IList<string> conds = new List<string>();
+                if (!string.IsNullOrEmpty(siteId)) {
+                    conds.Add($"SiteId = {siteId}");
+                }
+                if (useCheck) {
+                    conds.Add("IsUse = 1");
+                }
+                if (entity == "CmBusinessField" && businessAuth && user != null && user.BusinessFieldId != null) {
+                    // User 의 BusinessFieldId 와 공통 만 조회되도록 변경
+                    var commonBusinessField = _context.CmBusinessField.FirstOrDefault(
+                        x => x.Name == "공통"
+                    );
+                    if (commonBusinessField != null) {
+                        conds.Add($"BusinessFieldId IN ({commonBusinessField.BusinessFieldId},{user.BusinessFieldId})");
+                    } else {
+                        conds.Add($"BusinessFieldId IN ({user.BusinessFieldId})");
+                    }
+                }
+                if (!string.IsNullOrEmpty(where)) {
+                    conds.Add(where);
+                }
+                if (conds.Count > 0) {
+                    wheresql = $"WHERE {string.Join(" AND ", conds)}";
+                }
+                string ordersql = "";
+                if (entity == "CmSite") {
+                    ordersql = $"ORDER BY SortOrderNo";
+
+                } else {
+                    if (sort == "name") {
+                        ordersql = $"ORDER BY {name}";
+                    } else if (sort == "id") {
+                        ordersql = $"ORDER BY {id}";
+                    }
+                }
+                string sql = $"SELECT {id},{name}{additionalFieldsSelect} FROM {entity} {wheresql} {ordersql}";
+                //_logger.LogInformation(sql);
+                command.CommandText = sql;
+                _context.Database.OpenConnection();
+                using (var reader = command.ExecuteReader()) {
+                    if (reader.HasRows) {
+                        while (reader.Read()) {
+                            var row = new Dictionary<string, object>();
+                            row["Id"] = reader.GetValue(id);
+                            row["Name"] = reader.GetValue(name);
+                            // fields 로 넘어온 추가 필드의 값을 전달
+                            if (additionalFields != null && additionalFields.Length > 0) {
+                                foreach (var field in additionalFields) {
+                                    row[field] = reader.GetValue(field);
+                                }
+                            }
+                            list.Add(row);
+                        }
+                    }
+                }
+            }
+            return Ok(list);
+        }
+
+    }
+}

+ 164 - 0
Controllers/EquipmentCodeTypeController.cs

@@ -0,0 +1,164 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class EquipmentCodeTypeController : Controller {
+        private readonly ILogger<EquipmentCodeTypeController> _logger;
+        private readonly FMSContext _context;
+        private EquipmentCodeTypeService _service;
+
+        public EquipmentCodeTypeController(
+            ILogger<EquipmentCodeTypeController> logger,
+            FMSContext context,
+            EquipmentCodeTypeService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<FmsEquipmentCodeType> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<FmsEquipmentCodeType> query) {
+            var list = query.Select(x => new {
+                x.EquipmentTypeId,
+                x.Name,
+                x.Description,
+                IsUse = x.IsUse ? "Y" : "N",
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.EquipmentTypeId,
+                x.Name,
+                x.Description,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] FmsEquipmentCodeType data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.EquipmentTypeId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsEquipmentCodeType data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 379 - 0
Controllers/EquipmentController.cs

@@ -0,0 +1,379 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class EquipmentController : Controller {
+        private readonly ILogger<EquipmentController> _logger;
+        private readonly FMSContext _context;
+        private readonly EquipmentService _service;
+        private readonly StorageHelper _storage;
+
+        public EquipmentController(
+            ILogger<EquipmentController> logger,
+            FMSContext context,
+            EquipmentService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+
+        }
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{id}/{historyId}")]
+        public IActionResult Get(int siteId, int id, int historyId) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("공기구정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.EquipmentId,
+                x.EquipmentTypeId,
+                FmsEquipmentCodeType = new {
+                    x.FmsEquipmentCodeType.Name
+                },
+                x.WarehouseId,
+                BusinessFieldId = x.FmsMaterialWarehouse.BusinessFieldId,
+                FmsMaterialWarehouse = new {
+                    x.FmsMaterialWarehouse.Name,
+                },
+                x.SiteId,
+                CmSite = new {
+                    x.Site.Name,
+                },
+                ImageFile = _storage.ParseFile(x.ImageFile),
+                x.Name,
+                x.Standard,
+                x.Unit,
+                x.SupplierName,
+                x.SupplierPhoneNo,
+                TotalStockCount = x.FmsEquipmentHistory.Where(x => x.EquipmentHistoryId == historyId).Select(x => x.TotalStockCount).FirstOrDefault(),
+                CurrentStockCount = x.FmsEquipmentHistory.Where(x => x.EquipmentHistoryId == historyId).Select(x => x.CurrentStockCount).FirstOrDefault(),
+            });
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("공기구정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.EquipmentId,
+                x.EquipmentTypeId,
+                FmsEquipmentCodeType = new {
+                    x.FmsEquipmentCodeType.Name
+                },
+                x.WarehouseId,
+                FmsMaterialWarehouse = new {
+                    x.FmsMaterialWarehouse.Name
+                },
+                x.SiteId,
+                CmSite = new {
+                    x.Site.Name,
+                },
+                ImageFile = _storage.ParseFile(x.ImageFile),
+                x.Name,
+                x.Standard,
+                x.Unit,
+                x.SupplierName,
+                x.SupplierPhoneNo,
+                TotalStockCount = x.FmsEquipmentHistory.OrderByDescending(x => x.EquipmentHistoryId).Select(x => x.TotalStockCount).FirstOrDefault(),
+                CurrentStockCount = x.FmsEquipmentHistory.OrderByDescending(x => x.EquipmentHistoryId).Select(x => x.CurrentStockCount).FirstOrDefault(),
+            });
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] FmsEquipment data) {
+            if (id != data.EquipmentId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+            ModelState.Remove("RegisterUserId");
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsEquipment data) {
+            ModelState.Remove("RegisterUserId");
+            data.RegisterUserId = User.Identity.Name;
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult ImportExcelAdmin(IFormFile excelFile) {
+            return (ImportExcel(excelFile, true));
+        }
+
+        /// <summary>
+        /// 엑셀파일 임포트
+        /// </summary>
+        /// <param name="file"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult ImportExcel(IFormFile excelFile, bool isAdmin = false) {
+            IList<FmsEquipment> list = new List<FmsEquipment>();
+            var userInfo = _context.CmUser.Where(x => x.UserId == User.Identity.Name).SingleOrDefault();
+
+            using (ExcelPackage excelPackage = new ExcelPackage(excelFile.OpenReadStream())) {
+                var worksheet = excelPackage.Workbook.Worksheets[0];
+                int colCnt = isAdmin ? 10 : 9;
+                ExcelUploadHepler excelUploadHelper = new ExcelUploadHepler();
+                //헤더세팅
+                excelUploadHelper.GenerateColumns(worksheet, 1, colCnt);
+
+                for (int i = worksheet.Dimension.Start.Row + 1; i <= worksheet.Dimension.End.Row; i++) {
+                    if (!excelUploadHelper.IsRowEmpty(worksheet, i, colCnt)) {
+                        var site = isAdmin ? excelUploadHelper.GetValue(worksheet, "사옥(*)", i, true) : "";
+                        var business = excelUploadHelper.GetValue(worksheet, "업무분야(*)", i, true);
+                        var type = excelUploadHelper.GetValue(worksheet, "분류(*)", i, true);
+                        var name = excelUploadHelper.GetValue(worksheet, "이름(*)", i, true);
+                        var unit = excelUploadHelper.GetValue(worksheet, "단위", i);
+                        var standard = excelUploadHelper.GetValue(worksheet, "규격", i);
+                        var warehouse = excelUploadHelper.GetValue(worksheet, "창고명칭(*)", i, true);
+                        var storedCount = excelUploadHelper.GetValue(worksheet, "최초입고수량", i);
+                        var supplierName = excelUploadHelper.GetValue(worksheet, "공급업체", i);
+                        var supplierPhoneNo = excelUploadHelper.GetValue(worksheet, "업체연락처", i);
+
+                        _logger.LogInformation("site " + site);
+                        _logger.LogInformation("type " + type);
+                        _logger.LogInformation("codnamee " + name);
+
+                        var rowName = "(" + (i) + "번 행)";
+
+                        var siteId = isAdmin ? _context.CmSite.Where(x => x.Name == site && x.IsUse == true).Select(x => x.SiteId).SingleOrDefault() : userInfo.SiteId;
+                        var typeId = _context.FmsEquipmentCodeType.Where(x => x.Name == type && x.IsUse == true).Select(x => x.EquipmentTypeId).SingleOrDefault();
+                        var businessId = _context.CmBusinessField.Where(x => x.Name == business && x.IsUse == true).Select(x => x.BusinessFieldId).SingleOrDefault();
+                        var warehouseId = _context.FmsMaterialWarehouse.Where(x => x.SiteId == siteId && x.Name == warehouse && x.BusinessFieldId == businessId && x.IsUse == true).Select(x => x.WarehouseId).SingleOrDefault();
+
+                        if (isAdmin) excelUploadHelper.EntityIdCheck(siteId, "사옥", rowName);
+
+                        _logger.LogError("########" + warehouse + "++__" + business);
+                        excelUploadHelper.EntityIdCheck(typeId, "분류명", rowName);
+                        excelUploadHelper.EntityIdCheck(businessId, "업무분야", rowName);
+                        excelUploadHelper.EntityIdCheck(warehouseId, "창고명", rowName);
+                        excelUploadHelper.NumericCheck(storedCount, "입고수량", rowName);
+
+
+                        var equipment = new FmsEquipment {
+                            SiteId = siteId,
+                            EquipmentTypeId = typeId,
+                            Name = name,
+                            WarehouseId = warehouseId,
+                            StoredCount = Util.ToInt(storedCount),
+                            Unit = unit,
+                            Standard = standard,
+                            AddDate = DateTime.Now,
+                            UpdateDate = DateTime.Now,
+                            RegisterUserId = User.Identity.Name,
+                            SupplierName = supplierName,
+                            SupplierPhoneNo = supplierPhoneNo,
+                            ExcelRowNum = i
+                        };
+                        // 글자수 체크
+                        TryValidateModel(equipment);
+
+                        if (!ModelState.IsValid) throw new ServiceException(string.Join(" ",
+                                        ModelState.Values
+                                        .SelectMany(x => x.Errors)
+                                        .Select(x => x.ErrorMessage)) + rowName);
+
+                        //중복 체크
+                        var overlapRowNum = list.Where(x => x.SiteId == equipment.SiteId
+                                                        && x.EquipmentTypeId == equipment.EquipmentTypeId
+                                                        && x.Name == equipment.Name
+                                                        && x.Standard == equipment.Standard).Select(x => x.ExcelRowNum).SingleOrDefault();
+
+                        if (overlapRowNum != 0) {
+                            throw new ServiceException("파일 내 동일 정보가 존재합니다.[공기구분류-공기구명-규격]" + "(" + (overlapRowNum) + "번 행 - " + (i) + "번 행)");
+                        }
+
+                        if (_service.checkName(equipment, true) > 0) {
+                            throw new ServiceException("기존 공기구와 동일 정보가 존재합니다.[공기구분류-공기구명-규격]" + rowName);
+                        }
+
+                        // 데이터 값 설정
+                        list.Add(equipment);
+                    }
+                }
+            }
+            if (list.Count > 0) {
+                _service.ImportExcel(list);
+
+                return Ok($"엑셀업로드를 성공하였습니다. {list.Count}건의 데이터가 등록되었습니다.");
+            } else {
+                throw new ServiceException("등록할 데이터가 없습니다.");
+            }
+        }
+
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<FmsEquipment> query) {
+            var list = query.Select(x => new {
+                x.EquipmentId,
+                x.SiteId,
+                SiteName = x.Site.Name,
+                x.EquipmentTypeId,
+                EquipmentTypeName = x.FmsEquipmentCodeType.Name,
+                x.Name,
+                x.Standard,
+                x.Unit,
+                StoredCount = x.FmsEquipmentHistory.Where(x => x.EquipmentStateTypeId == 1).Sum(x => x.StoredCount),
+                x.AddDate,
+                x.SupplierName,
+                x.SupplierPhoneNo,
+                x.WarehouseId,
+                WarehouseName = x.FmsMaterialWarehouse.Name,
+                CurrentStockCount = x.FmsEquipmentHistory.OrderByDescending(x => x.EquipmentHistoryId).Select(x => x.CurrentStockCount).FirstOrDefault() ?? 0,
+                TotalStockCount = x.FmsEquipmentHistory.OrderByDescending(x => x.EquipmentHistoryId).Select(x => x.TotalStockCount).FirstOrDefault() ?? 0,
+                RentCount = x.FmsEquipmentHistory.Where(x => x.IsReturned == false).Sum(x => x.CurrentRentCount) ?? 0,
+                LossCount = x.FmsEquipmentHistory.Sum(x => x.LossCount),
+                x.UpdateDate,
+
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsEquipment> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 343 - 0
Controllers/EquipmentHistoryController.cs

@@ -0,0 +1,343 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class EquipmentHistoryController : Controller {
+        private readonly ILogger<EquipmentHistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly EquipmentHistoryService _service;
+        private readonly StorageHelper _storage;
+
+        public EquipmentHistoryController(
+            ILogger<EquipmentHistoryController> logger,
+            FMSContext context,
+            EquipmentHistoryService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+
+        /// <summary>
+        /// 공기구 등록 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult AddStockList([FromQuery] PagingRequest req) {
+            return List(req, 1);
+        }
+
+
+        /// <summary>
+        /// 공기구 대여 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult RentList([FromQuery] PagingRequest req) {
+            return List(req, 2);
+        }
+
+        /// <summary>
+        /// 공기구 반납/손실 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult ReturnList([FromQuery] PagingRequest req) {
+            return List(req, 3);
+        }
+
+        /// <summary>
+        /// 공기구 등록 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult ExcelAddStockList([FromQuery] PagingRequest req, string filename) {
+            return ExportExcel(req, 1, filename);
+        }
+
+        /// <summary>
+        /// 공기구 반납/손실 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult ExcelReturnList([FromQuery] PagingRequest req, string filename) {
+            return ExportExcel(req, 3, filename);
+        }
+
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, int? stateType) {
+            var query = _FilterAndSort(req, stateType);
+
+            if (stateType != null) {
+                if (stateType == 2) { // 대여 
+                    query = query.Where(x => x.EquipmentStateTypeId != 1 && x.IsReturned == false);
+                } else if (stateType == 3) {
+                    query = query.Where(x => x.EquipmentStateTypeId == 3 || x.EquipmentStateTypeId == 4 || x.EquipmentStateTypeId == 5);
+                } else {
+                    query = query.Where(x => x.EquipmentStateTypeId == stateType);
+                }
+            } else {
+                if (!_context.SiteConfig.Single().UseEquipmentRent) {
+                    query = query.Where(x => x.EquipmentStateTypeId != 2);
+                }
+            }
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, int? stateType, string filename) {
+            var query = _FilterAndSort(req, stateType);
+            if (stateType != null) {
+                if (stateType == 2) { // 대여 
+                    query = query.Where(x => x.EquipmentStateTypeId != 1 && x.IsReturned == false);
+                } else if (stateType == 3) {
+                    query = query.Where(x => x.EquipmentStateTypeId == 3 || x.EquipmentStateTypeId == 4 || x.EquipmentStateTypeId == 5);
+                } else {
+                    query = query.Where(x => x.EquipmentStateTypeId == stateType);
+                }
+            } else {
+                if (!_context.SiteConfig.Single().UseEquipmentRent) {
+                    query = query.Where(x => x.EquipmentStateTypeId != 2);
+                }
+            }
+
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("공기구정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.SiteId,
+                x.EquipmentId,
+                x.EquipmentHistoryId,
+                x.EquipmentRentId,
+                Site = new {
+                    x.CmSite.Name,
+                },
+                EquipmentStateType = new {
+                    x.EquipmentStateType.Name
+                },
+                RentInfo = new {
+                    Title = x.FmsEquipmentRentInfo.Title,
+                    RentUserName = x.FmsEquipmentRentInfo.RentUserName,
+                    RentDate = x.FmsEquipmentRentInfo.RentDate,
+                    ReturnDueDate = x.FmsEquipmentRentInfo.ReturnDueDate,
+                    ReturnFixDate = x.FmsEquipmentRentInfo.ReturnFixDate
+                },
+                EquipmentInfo = new {
+                    x.FmsEquipment.Name,
+                    Standard = x.FmsEquipment.Standard,
+                },
+                EquipmentName = x.FmsEquipment.Name,
+                x.RentCount,
+                x.CurrentRentCount,
+                x.LossCount,
+                x.ReturnCount,
+                x.ReturnReason,
+                x.IsReturnApproval,
+                ReturnApprovalDate = x.ReturnApprovalDate != null ? x.ReturnApprovalDate.Value.ToString("yyyy/MM/dd") : "",
+                ReturnApprovalUser = x.CmUser.Name,
+                CmFile = _storage.ParseFile(x.CmFile),
+            });
+
+            return Ok(item.First());
+        }
+
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] FmsEquipmentHistory data) {
+            if (id != data.EquipmentId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+            ModelState.Remove("RegisterUserId");
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsEquipmentHistory data) {
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<FmsEquipmentHistory> query) {
+            var list = query.Select(x => new {
+                x.EquipmentHistoryId,
+                x.EquipmentId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                EquipmentStateTypeName = x.EquipmentStateType.Name,
+                EquipmentTypeId = x.FmsEquipment.EquipmentTypeId,
+                EquipmentTypeName = x.FmsEquipment.FmsEquipmentCodeType.Name,
+                EquipmentName = x.FmsEquipment.Name,
+                Standard = x.FmsEquipment.Standard,
+                Unit = x.FmsEquipment.Unit,
+                x.TotalStockCount,
+                x.StoredCount,
+                x.ReturnCount,
+                x.LossCount,
+                x.RentCount,
+                x.CurrentRentCount,
+                x.CurrentStockCount,
+                x.ReturnReason,
+                AddDate = x.AddDate != null ? x.AddDate.Value.ToString("yyyy/MM/dd") : null,
+                SupplierName = x.FmsEquipment.SupplierName,
+                SupplierPhoneNo = x.FmsEquipment.SupplierPhoneNo,
+                x.EquipmentRentId,
+                x.EquipmentStateTypeId,
+                RentUserName = x.FmsEquipmentRentInfo.RentUserName,
+                RentDate = x.FmsEquipmentRentInfo.RentDate != null ? x.FmsEquipmentRentInfo.RentDate.Value.ToString("yyyy/MM/dd") : null,
+                ReturnDueDate = x.FmsEquipmentRentInfo.ReturnDueDate != null ? x.FmsEquipmentRentInfo.ReturnDueDate.Value.ToString("yyyy/MM/dd") : null,
+                ReturnFixDate = x.FmsEquipmentRentInfo.ReturnFixDate != null ? x.FmsEquipmentRentInfo.ReturnFixDate.Value.ToString("yyyy/MM/dd") : null,
+                RegisterUserId = x.FmsEquipment.RegisterUserId,
+                WarehouseId = x.FmsEquipment.WarehouseId,
+                WarehouseName = x.FmsEquipment.FmsMaterialWarehouse.Name,
+                ReturnApprovalUserName = x.ReturnApprovalUserId != null ? x.CmUser.Name : "",
+                ReturnApprovalDate = x.ReturnApprovalDate != null ? x.ReturnApprovalDate.Value.ToString("yyyy/MM/dd") : null,
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsEquipmentHistory> _FilterAndSort(PagingRequest req, int? stateType) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            if (Util.IsNullOrEmpty(stateType)) {
+                // 고정정렬 (사옥 준공순)
+                var sortQuery = query.OrderBy(x => x.CmSite.SortOrderNo);
+                // 추가 정렬
+                sortQuery = sortQuery.ThenSort(req.sort);
+                return sortQuery;
+            } else {
+                if (stateType == 1) {
+                    var sortQuery = query.OrderByDescending(x => x.EquipmentId);
+                    sortQuery = sortQuery.ThenByDescending(x => x.EquipmentHistoryId);
+                    // 추가 정렬
+                    sortQuery = sortQuery.ThenSort(req.sort);
+                    return sortQuery;
+                } else {
+                    // 기본 Entity 정렬
+                    query = query.Sort(req.sort);
+                    return query;
+                }
+            }
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult ReturnApproval([FromQuery] int siteId, [FromQuery] int id, [FromQuery] string userId) {
+            _service.ReturnApproval(siteId, id, userId);
+
+            return Ok();
+        }
+
+    }
+}

+ 278 - 0
Controllers/EquipmentRentInfoController.cs

@@ -0,0 +1,278 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class EquipmentRentInfoController : Controller {
+        private readonly ILogger<EquipmentRentInfoController> _logger;
+        private readonly EquipmentRentInfoService _service;
+        private readonly StorageHelper _storage;
+
+        public EquipmentRentInfoController(
+            ILogger<EquipmentRentInfoController> logger,
+            EquipmentRentInfoService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 리스트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, int? stateType) {
+            var query = _FilterAndSort(req);
+            if (stateType != null)
+                query = query.Where(x => x.EquipmentStateTypeId == stateType);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, int? stateType, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+            if (stateType != null)
+                query = query.Where(x => x.EquipmentStateTypeId == stateType);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 엑셀파일 임포트
+        /// </summary>
+        /// <param name="file"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult ImportExcel(IFormFile file) {
+            IList<Hashtable> list = new List<Hashtable>();
+            using (ExcelPackage excelPackage = new ExcelPackage(file.OpenReadStream())) {
+                var worksheet = excelPackage.Workbook.Worksheets[0];
+                for (int i = worksheet.Dimension.Start.Row; i <= worksheet.Dimension.End.Row; i++) {
+
+                    var name = worksheet.Cells[i, 1].Value;
+                    var rfid = worksheet.Cells[i, 2].Value;
+                    var code = worksheet.Cells[i, 3].Value;
+                    list.Add(new Hashtable {
+                        {"Name", name}
+                    });
+                }
+            }
+            //_service.ImportExcel(list);
+            //return BadRequest(ex.Message);
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("공기구대여정보를 찾을 수 없습니다.");
+            }
+            var item = data.Select(x => new {
+                x.EquipmentRentId,
+                x.EquipmentStateTypeId,
+                FmsEquipmentCodeStateType = new {
+                    x.EquipmentStateType.Name
+                },
+                x.RentDate,
+                x.ReturnDueDate,
+                x.SiteId,
+                Site = new {
+                    x.Site.Name,
+                },
+                x.Title,
+                x.Comment,
+                x.RentUserName,
+                EquipmentHistory = x.FmsEquipmentHistory.Where(x => x.EquipmentStateTypeId == 2 && x.CurrentRentCount > 0).Select(h => new {
+                    h.EquipmentHistoryId,
+                    h.EquipmentId,
+                    Name = h.FmsEquipment.Name,
+                    RentCount = h.CurrentRentCount,
+                    Standard = h.FmsEquipment.Standard,
+                    WarehouseName = h.FmsEquipment.FmsMaterialWarehouse.Name
+                }),
+            });
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] FmsEquipmentRentInfo data) {
+            if (id != data.EquipmentRentId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+            ModelState.Remove("RegisterUserId");
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+
+        /// <summary>
+        /// 공기구 반납/손실 
+        /// </summary>
+        [HttpPost("[action]")]
+        public IActionResult EditReturnInfo(int siteId, int historyId, [FromBody] FmsEquipmentRentInfo data) {
+
+            ModelState.Remove("RegisterUserId");
+            ModelState.Remove("Title");
+            if (ModelState.IsValid) {
+                _service.CreateReturnInfo(historyId, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsEquipmentRentInfo data) {
+            //_logger.LogInformation(data.Dump());
+
+            // 현장ID설정            
+            foreach (var history in data.FmsEquipmentHistory) {
+                history.SiteId = data.SiteId;
+                _logger.LogInformation("SiteId:" + history.SiteId);
+                _logger.LogInformation("EquipmentId:" + history.EquipmentId);
+            }
+            ModelState.Remove("RegisterUserId");
+            data.RegisterUserId = User.Identity.Name;
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(siteId, id);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<FmsEquipmentRentInfo> query) {
+            var list = query.Select(x => new {
+                x.EquipmentRentId,
+                x.SiteId,
+                SiteName = x.Site.Name,
+                x.EquipmentStateTypeId,
+                EquipmentStateTypeName = x.EquipmentStateType.Name,
+                x.Title,
+                x.Comment,
+                x.RentUserName,
+                RentDate = x.RentDate != null ? x.RentDate.Value.ToString("yyyy/MM/dd") : "",
+                ReturnDueDate = x.ReturnDueDate != null ? x.ReturnDueDate.Value.ToString("yyyy/MM/dd") : ""
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsEquipmentRentInfo> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 75 - 0
Controllers/ErrorController.cs

@@ -0,0 +1,75 @@
+using System;
+using FMSAdmin.Helpers;
+using Microsoft.AspNetCore.Diagnostics;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Logging;
+
+namespace FMSAdmin.Controllers {
+    [ApiController]
+    public class ErrorController : ControllerBase {
+        private readonly ILogger<ErrorController> _logger;
+        public ErrorController(ILogger<ErrorController> logger) {
+            _logger = logger;
+        }
+
+        [Route("/error-local-development")]
+        public IActionResult ErrorLocalDevelopment(
+            [FromServices] IWebHostEnvironment webHostEnvironment) {
+            if (webHostEnvironment.EnvironmentName != "Development") {
+                throw new InvalidOperationException(
+                    "This shouldn't be invoked in non-development environments.");
+            }
+
+            var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
+
+            if (context.Error is ServiceException) {
+                return BadRequest(context.Error.Message);
+            } else if (context.Error is SqlException) {
+                var sqlException = context.Error as SqlException;
+                if (HttpMethods.IsDelete(HttpContext.Request.Method) && sqlException.Number == 547) return BadRequest("이미 다른 화면에서 사용중이므로 삭제할 수 없습니다.");
+
+                return BadRequest(context.Error.Message);
+            } else if (context.Error.InnerException is SqlException) {
+                var sqlException = context.Error.InnerException as SqlException;
+                if (HttpMethods.IsDelete(HttpContext.Request.Method) && sqlException.Number == 547) return BadRequest("이미 다른 화면에서 사용중이므로 삭제할 수 없습니다.");
+
+                return BadRequest(context.Error.InnerException.Message);
+            }
+            return Problem(
+                type: context.Error.GetType().ToString(),
+                detail: context.Error.StackTrace,
+                title: context.Error.Message);
+        }
+
+        [Route("/error")]
+        public IActionResult Error(
+            [FromServices] IWebHostEnvironment webHostEnvironment) {
+
+            var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
+
+            if (context.Error is ServiceException) {
+                return BadRequest(context.Error.Message);
+            } else if (context.Error is SqlException) {
+                var sqlException = context.Error as SqlException;
+                if (HttpMethods.IsDelete(HttpContext.Request.Method) && sqlException.Number == 547) return BadRequest("이미 다른 화면에서 사용중이므로 삭제할 수 없습니다.");
+
+                return BadRequest(context.Error.Message);
+            } else if (context.Error.InnerException is SqlException) {
+                var sqlException = context.Error.InnerException as SqlException;
+                if (HttpMethods.IsDelete(HttpContext.Request.Method) && sqlException.Number == 547) return BadRequest("이미 다른 화면에서 사용중이므로 삭제할 수 없습니다.");
+
+                return BadRequest(context.Error.InnerException.Message);
+            }
+
+            return Problem(
+                type: context.Error.GetType().ToString(),
+                title: context.Error.Message);
+        }
+
+        [Route("/error-default")]
+        public IActionResult ErrorDefault() => Problem();
+    }
+}

+ 362 - 0
Controllers/FacilityCodeClassController.cs

@@ -0,0 +1,362 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using System.Collections.Generic;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class FacilityCodeClassController : Controller {
+        private readonly ILogger<FacilityCodeClassController> _logger;
+        private readonly FMSContext _context;
+        private readonly FacilityCodeClassService _service;
+
+        public FacilityCodeClassController(
+            ILogger<FacilityCodeClassController> logger,
+            FMSContext context,
+            FacilityCodeClassService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string depth) {
+            var query = _FilterAndSort(req);
+
+            if (!string.IsNullOrEmpty(depth)) {
+                query = query.Where(x => x.Depth == Util.ToInt(depth));
+            }
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+
+        private dynamic _ListSelect(IQueryable<FmsFacilityCodeClass> query) {
+            //query = query.Where(x => x.SiteId.Name));
+
+            var list = query.Select(x => new {
+                x.FacilityClassId,
+                x.ParentFacilityClassId,
+                x.Depth,
+                x.Name,
+                x.Abbreviation,
+                x.IsUse
+            });
+            return list;
+        }
+
+        private void ExcelHeaderError() {
+            throw new Exception("엑셀 형식이 일치하지 않습니다");
+        }
+
+
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult ImportExcelAdmin(IFormFile excelFile) {
+            return (ImportExcel(excelFile, true));
+        }
+
+        /// <summary>
+        /// 엑셀파일 임포트
+        /// </summary>
+        /// <param name="file"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult ImportExcel(IFormFile excelFile, bool isAdmin = false) {
+
+            IList<FmsFacilityCodeClass> list = new List<FmsFacilityCodeClass>();
+            var userInfo = _context.CmUser.Where(x => x.UserId == User.Identity.Name).SingleOrDefault();
+            using (ExcelPackage excelPackage = new ExcelPackage(excelFile.OpenReadStream())) {
+                var worksheet = excelPackage.Workbook.Worksheets[0];
+                int colCnt = isAdmin ? 4 : 3;
+                //헤더세팅
+
+                ExcelUploadHepler excelUploadHelper = new ExcelUploadHepler();
+                excelUploadHelper.GenerateColumns(worksheet, 1, colCnt);
+                for (int i = worksheet.Dimension.Start.Row + 1; i <= worksheet.Dimension.End.Row; i++) {
+                    if (!excelUploadHelper.IsRowEmpty(worksheet, i, colCnt)) {
+                        //var site = isAdmin ? excelUploadHelper.GetValue(worksheet, "사이트명(*)", i, true) : "";
+                        var class1 = excelUploadHelper.GetValue(worksheet, "시설 대분류(*)", i, true);
+                        var class2 = excelUploadHelper.GetValue(worksheet, "시설 중분류", i);
+                        var class3 = excelUploadHelper.GetValue(worksheet, "시설 소분류", i);
+                        //var isUse = excelUploadHelper.GetValue(worksheet, "사용여부(*)[사용 or 미사용]", i, true);
+
+                        //_logger.LogInformation("site " + site);
+
+                        var rowName = "(" + (i) + "번 행)";
+
+                        //var siteId = isAdmin ? _context.CmSite.Where(x => x.Name == site && x.IsUse == true).Select(x => x.SiteId).SingleOrDefault() : userInfo.SiteId;
+                        var classId1 = _context.FmsFacilityCodeClass.Where(x => x.IsUse == true && x.Name == class1 && x.ParentFacilityClassId == null).Select(x => x.FacilityClassId).SingleOrDefault();
+                        var classId2 = _context.FmsFacilityCodeClass.Where(x => x.IsUse == true && x.ParentFacilityClassId == classId1 && x.Name == class2).Select(x => x.FacilityClassId).SingleOrDefault();
+
+                        //if (isAdmin) excelUploadHelper.EntityIdCheck(siteId, "사이트명", rowName);
+
+                        //if (!isUse.Equals("사용") && !isUse.Equals("미사용")) throw new Exception($"사용여부를 정확히 입력해야합니다.[사용 or 미사용] {rowName}");
+
+                        var depth = (class2 == "" || class2 == null) ? 0 : (class3 == "" || class3 == null) ? 1 : 2;
+                        var name = depth == 0 ? class1 : depth == 1 ? class2 : class3;
+
+                        var facilityCodeClass = new FmsFacilityCodeClass();
+                        facilityCodeClass = new FmsFacilityCodeClass {
+                            Depth = depth,
+                            Name = name,
+                            IsUse = true,
+                            ExcelRowNum = i
+                        };
+
+                        if (depth == 1) {
+                            if (classId1 != 0) {
+                                facilityCodeClass.ParentFacilityClassId = classId1;
+                            } else {
+                                facilityCodeClass.FmsFacilityCodeClass2 = new FmsFacilityCodeClass {
+                                    Depth = 0,
+                                    Name = class1,
+                                    Abbreviation = rowName,
+                                    IsUse = true
+                                };
+                            }
+                        } else if (depth == 2) {
+                            if (classId1 != 0 && classId2 != 0) {
+                                facilityCodeClass.ParentFacilityClassId = classId2;
+                            } else {
+                                facilityCodeClass.FmsFacilityCodeClass2 = new FmsFacilityCodeClass {
+                                    Depth = 1,
+                                    Name = class2,
+                                    Abbreviation = rowName,
+                                    IsUse = true,
+                                    FmsFacilityCodeClass2 = new FmsFacilityCodeClass {
+                                        Depth = 0,
+                                        Name = class1,
+                                        IsUse = true
+                                    }
+                                };
+                            }
+                        }
+
+                        var check = 0;
+                        if (depth == 0) check = _context.FmsFacilityCodeClass.Where(x => x.Depth == depth && x.Name == name).Count();
+                        else check = _context.FmsFacilityCodeClass.Where(x => x.Depth == depth && x.ParentFacilityClassId == facilityCodeClass.ParentFacilityClassId && x.Name == name).Count();
+                        if (check > 0) {
+                            throw new ServiceException("기존 분류코드와 동일 정보가 존재합니다." + rowName);
+                        }
+
+                        //글자수 체크
+                        TryValidateModel(facilityCodeClass);
+
+                        if (!ModelState.IsValid) throw new ServiceException(string.Join(" ",
+                                        ModelState.Values
+                                        .SelectMany(x => x.Errors)
+                                        .Select(x => x.ErrorMessage)) + rowName);
+
+                        //중복체크
+                        var overlapRowNum = list.Where(
+                            x => x.ParentFacilityClassId == facilityCodeClass.ParentFacilityClassId
+                            && x.Depth == facilityCodeClass.Depth
+                            && x.Name == facilityCodeClass.Name
+                        ).Select(x => x.ExcelRowNum).SingleOrDefault();
+                        if (overlapRowNum != 0)
+                            throw new ServiceException("파일 내 동일 정보가 존재합니다." + "(" + (overlapRowNum) + "번 행 - " + (i) + "번 행)");
+
+                        list.Add(facilityCodeClass);
+                    }
+                }
+            }
+            if (list.Count > 0) {
+                _service.ImportExcel(list);
+
+                return Ok($"엑셀업로드를 성공하였습니다. {list.Count}건의 데이터가 등록되었습니다.");
+            } else {
+                throw new ServiceException("등록할 데이터가 없습니다.");
+            }
+        }
+
+
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+
+
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = query.Select(x => new {
+                x.FacilityClassId,
+                SiteName = "",
+                ClassName1 = x.Depth == 0 ? x.Name : x.Depth == 1 ? x.FmsFacilityCodeClass2.Name : x.FmsFacilityCodeClass2.FmsFacilityCodeClass2.Name,
+                ClassName2 = x.Depth == 1 ? x.Name : x.Depth == 2 ? x.FmsFacilityCodeClass2.Name : "",
+                ClassName3 = x.Depth == 2 ? x.Name : "",
+            });
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("코드정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.FacilityClassId,
+                x.ParentFacilityClassId,
+                x.Depth,
+                x.Name,
+                x.Abbreviation,
+                x.IsUse,
+            });
+
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] FmsFacilityCodeClass row) {
+            _logger.LogInformation(row.Dump());
+
+            if (id != row.FacilityClassId) {
+                return NotFound();
+            }
+
+            //ModelState.Remove("Password"); // 이 수정에서는 비밀번호 제외
+            if (ModelState.IsValid) {
+                try {
+                    _service.Save(row);
+                } catch (ServiceException ex) {
+                    return BadRequest(ex.Message);
+                }
+            } else {
+                foreach (var ms in ModelState.ToArray()) {
+                    _logger.LogInformation(ms.Key);
+                }
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] FmsFacilityCodeClass row) {
+            // if (!User.IsInRole("Admin"))
+            //     return Forbid();
+
+            if (ModelState.IsValid) {
+                try {
+                    _service.Save(row);
+                } catch (ServiceException ex) {
+                    return BadRequest(ex.Message);
+                }
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 자식 노드 리스트
+        /// </summary>
+        /// <param name="parentId"></param>
+        /// <param name="type"></param>
+        /// <returns></returns>
+        [HttpGet("childs/{parentId?}")]
+        public IActionResult Childs(int? parentId) {
+            var query = _service.GetAll().Where(x => x.ParentFacilityClassId == parentId);
+
+            // 최대 3뎁스
+            var list = query.OrderBy(x => x.Name).Select(c => new {
+                id = c.FacilityClassId,
+                name = c.Name,
+                children = c.FmsFacilityCodeClass1.OrderBy(x => x.Name).Select(c1 => new {
+                    id = c1.FacilityClassId,
+                    name = c1.Name,
+                    children = c1.FmsFacilityCodeClass1.OrderBy(x => x.Name).Select(c2 => new {
+                        id = c2.FacilityClassId,
+                        name = c2.Name,
+                    })
+                })
+            });
+            return Ok(list);
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsFacilityCodeClass> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            query = query.Where(x => x.IsUse == true);
+
+            query = query.Where(x => !string.IsNullOrEmpty(x.Name));
+            //query = query.Where(x => !string.IsNullOrEmpty(x.Abbreviation));
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 646 - 0
Controllers/FacilityController.cs

@@ -0,0 +1,646 @@
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+using System.IO.Compression;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+
+    public class FacilityController : Controller {
+        private readonly ILogger<FacilityController> _logger;
+        private readonly FMSContext _context;
+        private readonly FacilityService _service;
+        private readonly StorageHelper _storage;
+        private readonly QrCodeHelper _QrCode;
+        public FacilityController(
+            ILogger<FacilityController> logger,
+            FMSContext context,
+            FacilityService service,
+            StorageHelper storage,
+            QrCodeHelper QrCode
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+            _QrCode = QrCode;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+
+            query = query.Where(x => !string.IsNullOrEmpty(x.Name));
+            //query = query.Where(x => !string.IsNullOrEmpty(x.Nickname));
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+
+        private dynamic _ListSelect(IQueryable<CmFacility> query) {
+
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                SiteSort = x.CmSite.SortOrderNo,
+                x.FacilityCode,
+                FirstClassId = x.FmsFacilityCodeClass.FacilityClassId,
+                FirstClassName = x.FmsFacilityCodeClass.Name,
+                SecondClassId = x.FmsFacilityCodeClass1.FacilityClassId,
+                SecondClassName = x.FmsFacilityCodeClass1.Name,
+                x.FacilityTypeId,
+                FmsFacilityCodeType = x.BemsFacilityType.Name,
+                ThirdClassId = x.FmsFacilityCodeClass2.FacilityClassId,
+                ThirdClassName = x.FmsFacilityCodeClass2.Name,
+                x.IsUse,
+                FacilityName = x.Name,
+                x.ManufactureModel,
+                x.FacilityCapacity,
+                x.Rfid,
+                x.DeviceType,
+                x.Nickname,
+                x.FacilityUsage,
+                x.Manufacturer,
+                x.ManufactureDate,
+                x.RatedPowerConsumption,
+                x.RatedCop,
+                x.ManufactureSerial,
+                x.Supplier,
+                x.SupplierPhoneNo,
+                FirstdFacilityLoc = x.CmBuilding.Name,
+                SecondFacilityLoc = x.CmFloor.Name,
+                ThirdFacilityLoc = x.CmZone.Name,
+                x.InstallDate,
+                x.OperationStartDate,
+                MainContactorUser = x.MainContactor.Name,
+                SubContactorUser = x.SubContactor.Name,
+                x.FacilityCost,
+                x.FacilityCount,
+                FuelType = x.BemsFuelType.Name,
+                ContractType = x.BemsNoticePriceContractType.Name,
+                x.Note,
+                x.Sort1,
+                x.Sort2,
+            }).AsEnumerable().OrderBy(x => x.SiteSort).ThenBy(x => x.FacilityTypeId).ThenBy(x => x.Sort1).ThenBy(x => x.Sort2).AsQueryable();
+            //설비명 Sort 때문에 임시. 후에 수정해야 함.
+            return list;
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = query.Select(x => new {
+                // SiteName = x.CmSite.Name,
+                // FirstClassName = x.FmsFacilityCodeClass.Name,
+                // SecondClassName = x.FmsFacilityCodeClass1.Name,
+                // ThirdClassName = x.FmsFacilityCodeClass2.Name,
+                // FacilityName = x.Name + "/" + x.Nickname,
+                // x.ManufactureModel,
+                // x.FacilityCapacity,
+                // FacilityLoc = x.CmFloor.Name,
+
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.FacilityCode,
+                FirstClassId = x.FmsFacilityCodeClass.FacilityClassId,
+                FirstClassName = x.FmsFacilityCodeClass.Name,
+                SecondClassId = x.FmsFacilityCodeClass1.FacilityClassId,
+                SecondClassName = x.FmsFacilityCodeClass1.Name,
+                FmsFacilityCodeType = x.BemsFacilityType.Name,
+                ThirdClassId = x.FmsFacilityCodeClass2.FacilityClassId,
+                ThirdClassName = x.FmsFacilityCodeClass2.Name,
+                IsUse = Util.ToBool(x.IsUse) ? "활성화" : "취소",
+                FacilityName = x.Name,
+                x.ManufactureModel,
+                x.FacilityCapacity,
+                x.Rfid,
+                x.DeviceType,
+                x.Nickname,
+                x.FacilityUsage,
+                x.Manufacturer,
+                ManufactureDate = x.ManufactureDate != null ? Convert.ToDateTime(x.ManufactureDate).ToString("yyyyMMdd") : "",
+                x.RatedPowerConsumption,
+                x.RatedCop,
+                x.ManufactureSerial,
+                x.Supplier,
+                x.SupplierPhoneNo,
+                FirstdFacilityLoc = x.CmBuilding.Name,
+                SecondFacilityLoc = x.CmFloor.Name,
+                ThirdFacilityLoc = x.CmZone.Name,
+                InstallDate = x.InstallDate != null ? Convert.ToDateTime(x.InstallDate).ToString("yyyyMMdd") : "",
+                OperationStartDate = x.OperationStartDate != null ? Convert.ToDateTime(x.OperationStartDate).ToString("yyyyMMdd") : "",
+                MainContactorUser = x.MainContactor.Name,
+                SubContactorUser = x.SubContactor.Name,
+                x.FacilityCost,
+                x.FacilityCount,
+                FuelType = x.BemsFuelType.Name,
+                ContractType = x.BemsNoticePriceContractType.Name,
+                x.Note
+            });
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+
+
+        [HttpGet("qr")]
+        public IActionResult QRDown([FromQuery] PagingRequest req) {
+
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var folder = DateTime.Now.ToString("yyyyMMddhhmmsss");
+            var fileName = $"QR코드_{folder}.zip";
+            var list = query.Where(x => x.Rfid != null && x.Rfid != "").Select(x => new {
+                x.SiteId,
+                x.FacilityCode,
+                x.Rfid,
+            });
+
+            if (list.Count() == 0) {
+                return BadRequest("다운로드 가능한 QR코드가 없습니다.");
+            }
+
+            using (MemoryStream newZip = new MemoryStream()) {
+                //using (FileStream newZip = new FileStream(@"c:\users\exampleuser\release.zip", FileMode.CreateNew)) {
+                using (ZipArchive archive = new ZipArchive(newZip, ZipArchiveMode.Create)) {
+                    foreach (var item in list) {
+                        archive.CreateEntryFromFile(_storage.GetLocalPath($"/qr/facility/{item.SiteId}_{item.FacilityCode}.jpg"), $"{item.SiteId}_{item.FacilityCode}.jpg");
+                    }
+                }
+                return File(newZip.ToArray(), "application/zip", fileName);
+            }
+        }
+
+        [HttpGet("QrCodeRegen")]
+        [AllowAnonymous]
+        public IActionResult QrCodeRegen() {
+            var query = _service.GetAll();
+            var list = query.Where(x => x.Rfid != null && x.Rfid != "").Select(x => new {
+                x.SiteId,
+                x.FacilityCode,
+                x.Rfid,
+            });
+
+            foreach (var item in list) {
+                _QrCode.Save(item.Rfid, $"{item.SiteId}_{item.FacilityCode}", "Facility");
+            }
+
+            return Content(":)");
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] CmFacility data) {
+            //_logger.LogInformation(data.Dump());
+
+            if (siteId != data.SiteId && id != data.FacilityCode) {
+                return NotFound();
+            }
+
+            //ModelState.Remove("Password"); // 이 수정에서는 비밀번호 제외
+            ModelState.RemoveStartOf("CmFile");
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                foreach (var ms in ModelState.ToArray()) {
+                    _logger.LogInformation(ms.Key);
+                }
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.FacilityTypeId,
+                FmsFacilityCodeType = new {
+                    x.BemsFacilityType.Name,
+                },
+                x.FacilityCode,
+                x.FirstClassId,
+                FmsFacilityCodeClass = new {
+                    x.FmsFacilityCodeClass.FacilityClassId,
+                    x.FmsFacilityCodeClass.Name,
+                },
+
+                x.SecondClassId,
+                FmsFacilityCodeClass1 = new {
+                    x.FmsFacilityCodeClass1.FacilityClassId,
+                    x.FmsFacilityCodeClass1.Name,
+                },
+                x.ThirdClassId,
+                FmsFacilityCodeClass2 = new {
+                    x.FmsFacilityCodeClass2.FacilityClassId,
+                    x.FmsFacilityCodeClass2.Name,
+                },
+                x.DeviceType,
+                x.ParentFacilityCode,
+                x.Name,
+                x.Nickname,
+                x.Rfid,
+                x.Manufacturer,
+                x.ManufactureDate,
+                x.ManufactureSerial,
+                x.ManufactureModel,
+                x.Supplier,
+                x.SupplierPhoneNo,
+                x.BuildingId,
+                CmBuilding = new {
+                    x.CmBuilding.Name,
+                },
+                x.FloorId,
+                CmFloor = new {
+                    x.CmFloor.Name,
+                },
+                x.ZoneId,
+                CmZone = new {
+                    x.CmZone.Name,
+                },
+                x.InstallDate,
+                x.OperationStartDate,
+                x.FacilityCapacity,
+                x.FacilityCost,
+                x.FacilityCount,
+                x.FacilitySeviceLifeYear,
+                x.FacilityUsage,
+                x.RatedPowerConsumption,
+                x.RatedCop,
+                x.MainContactorUserId,
+                MainContactorUserName = new {
+                    x.MainContactor.Name,
+                },
+                x.SubContactorUserId,
+                SubContactorUserName = new {
+                    x.SubContactor.Name,
+                },
+                x.Note,
+                x.Reserved1,
+                x.Reserved2,
+                x.IsUse,
+                //x.FileId,
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.IsVirtualFacility,
+                x.FuelTypeId,
+                FuelTypeName = new {
+                    x.BemsFuelType.Name
+                },
+                x.ContractType,
+                PriceContractType = new {
+                    x.BemsNoticePriceContractType.Name,
+
+                },
+                x.Multiple
+            });
+
+            return Ok(list.First());
+        }
+
+        private void ExcelHeaderError() {
+            throw new Exception("엑셀 형식이 일치하지 않습니다");
+        }
+
+
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult ImportExcelAdmin(IFormFile excelFile) {
+            return (ImportExcel(excelFile, true));
+        }
+
+        /// <summary>
+        /// 엑셀파일 임포트
+        /// </summary>
+        /// <param name="file"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult ImportExcel(IFormFile excelFile, bool isAdmin = false) {
+
+            IList<CmFacility> list = new List<CmFacility>();
+            var userInfo = _context.CmUser.Where(x => x.UserId == User.Identity.Name).SingleOrDefault();
+
+            using (ExcelPackage excelPackage = new ExcelPackage(excelFile.OpenReadStream())) {
+                var worksheet = excelPackage.Workbook.Worksheets[0];
+                int colCnt = isAdmin ? 31 : 30;
+                ExcelUploadHepler excelUploadHelper = new ExcelUploadHepler();
+                //헤더세팅
+                excelUploadHelper.GenerateColumns(worksheet, 1, colCnt);
+
+                for (int i = worksheet.Dimension.Start.Row + 1; i <= worksheet.Dimension.End.Row; i++) {
+                    if (!excelUploadHelper.IsRowEmpty(worksheet, i, colCnt)) {
+                        var site = isAdmin ? excelUploadHelper.GetValue(worksheet, "사옥명(*)", i, true) : "";
+
+                        var facilityType = excelUploadHelper.GetValue(worksheet, "시설유형(*)", i, true);
+                        var deviceType = excelUploadHelper.GetValue(worksheet, "종류", i);
+                        var name = excelUploadHelper.GetValue(worksheet, "시설명칭(*)", i, true);
+                        //var nickName = excelUploadHelper.GetValue(worksheet, "시설별칭(*)", i, true);
+                        var facilityUsage = excelUploadHelper.GetValue(worksheet, "시설용도(*)", i);
+                        var manufacturer = excelUploadHelper.GetValue(worksheet, "제조업체", i);
+                        var manufactureDt = excelUploadHelper.GetValue(worksheet, "제조일자", i);
+                        var isUse = excelUploadHelper.GetValue(worksheet, "사용여부(*)[활성화 or 취소]", i, true);
+                        var fristClass = excelUploadHelper.GetValue(worksheet, "시설 대분류(*)", i, true);
+                        var secondClass = excelUploadHelper.GetValue(worksheet, "시설 중분류", i);
+                        var thirdClass = excelUploadHelper.GetValue(worksheet, "시설 소분류", i);
+                        var facilityCapacity = excelUploadHelper.GetValue(worksheet, "시설용량", i);
+                        var qrCode = excelUploadHelper.GetValue(worksheet, "QR코드", i);
+                        var ratedPowerConsumption = excelUploadHelper.GetValue(worksheet, "정격전력소비량", i);
+                        var rateCop = excelUploadHelper.GetValue(worksheet, "정격COP", i);
+                        var manufactureSerial = excelUploadHelper.GetValue(worksheet, "제조번호", i);
+                        var manufactureModel = excelUploadHelper.GetValue(worksheet, "제조모델", i);
+                        var supplier = excelUploadHelper.GetValue(worksheet, "납품업체", i);
+                        var supplierPhoneNo = excelUploadHelper.GetValue(worksheet, "업체전화", i);
+                        var building = excelUploadHelper.GetValue(worksheet, "설치위치(대)", i);
+                        var floor = excelUploadHelper.GetValue(worksheet, "설치위치(중)", i);
+                        var zone = excelUploadHelper.GetValue(worksheet, "설치위치(소)", i);
+                        var installDt = excelUploadHelper.GetValue(worksheet, "설치일자", i);
+                        var operStartDt = excelUploadHelper.GetValue(worksheet, "운전시작일", i);
+                        var mainContactorUser = excelUploadHelper.GetValue(worksheet, "정담당자", i);
+                        var subContactorUser = excelUploadHelper.GetValue(worksheet, "부담당자", i);
+                        var facilityCost = excelUploadHelper.GetValue(worksheet, "시설비용", i);
+                        var facilityCount = excelUploadHelper.GetValue(worksheet, "시설수량", i);
+                        var fuelType = excelUploadHelper.GetValue(worksheet, "연료종류", i);
+                        var contractType = excelUploadHelper.GetValue(worksheet, "계약종류", i);
+                        var note = excelUploadHelper.GetValue(worksheet, "Note", i);
+
+
+                        _logger.LogInformation("site " + site);
+                        _logger.LogInformation("codnamee " + name);
+
+                        var rowName = "(" + (i) + "번 행)";
+
+                        var siteId = isAdmin ? _context.CmSite.Where(x => x.Name == site && x.IsUse == true).Select(x => x.SiteId).SingleOrDefault() : userInfo.SiteId;
+                        int[] virtualFacility = new int[] { 99, 100, 101, 102, 103 };
+                        var facilityTypeId = _context.BemsFacilityType.Where(x => x.Name == facilityType && !virtualFacility.Contains(x.FacilityTypeId)).Select(x => x.FacilityTypeId).SingleOrDefault();
+
+                        var fristClassId = _context.FmsFacilityCodeClass.Where(x => x.IsUse == true && x.Name == fristClass && x.ParentFacilityClassId == null).Select(x => x.FacilityClassId).SingleOrDefault();
+                        var secondClassId = _context.FmsFacilityCodeClass.Where(x => x.IsUse == true && x.ParentFacilityClassId == fristClassId && x.Name == secondClass).Select(x => x.FacilityClassId).SingleOrDefault();
+                        var thirdClassId = _context.FmsFacilityCodeClass.Where(x => x.IsUse == true && x.ParentFacilityClassId == secondClassId && x.Name == thirdClass).Select(x => x.FacilityClassId).SingleOrDefault();
+
+                        var buildingId = _context.CmBuilding.Where(x => x.SiteId == siteId && x.IsUse == true && x.Name == building).Select(x => x.BuildingId).SingleOrDefault();
+                        var floorId = _context.CmFloor.Where(x => x.SiteId == siteId && x.BuildingId == buildingId && x.Name == floor).Select(x => x.FloorId).SingleOrDefault();
+                        var zoneId = _context.CmZone.Where(x => x.SiteId == siteId && x.FloorId == floorId && x.Name == zone).Select(x => x.ZoneId).SingleOrDefault();
+
+                        var fuelTypeId = _context.BemsFuelType.Where(x => x.Name == fuelType).Select(x => x.FuelTypeId).SingleOrDefault();
+                        var contractTypeId = _context.BemsNoticePriceContractType.Where(x => x.FuelTypeId == fuelTypeId && x.Name == contractType).Select(x => x.ContractType).SingleOrDefault();
+
+                        var mainContactorUserId = _context.CmUser.Where(x => x.SiteId == siteId && x.IsUse == true && x.Name == mainContactorUser).Select(x => x.UserId).SingleOrDefault();
+                        var subContactorUserId = _context.CmUser.Where(x => x.SiteId == siteId && x.IsUse == true && x.Name == subContactorUser).Select(x => x.UserId).SingleOrDefault();
+
+                        if (isAdmin) excelUploadHelper.EntityIdCheck(siteId, "사옥명", rowName);
+                        if (!isUse.Equals("활성화") && !isUse.Equals("취소"))
+                            throw new Exception($"사용여부를 정확히 입력해야합니다.[활성화 or 취소] {rowName}");
+
+                        excelUploadHelper.EntityIdCheck(facilityTypeId, "시설유형(*)", rowName);
+                        excelUploadHelper.EntityIdCheck(fristClassId, "시설 대분류", rowName);
+
+                        if (secondClass != null) excelUploadHelper.EntityIdCheck(secondClassId, "시설 중분류", rowName);
+                        if (thirdClass != null) excelUploadHelper.EntityIdCheck(thirdClassId, "시설 소분류", rowName);
+
+                        manufactureDt = excelUploadHelper.DateTimeCheck(manufactureDt, "제조일자", rowName);
+                        installDt = excelUploadHelper.DateTimeCheck(installDt, "설치일자", rowName);
+                        operStartDt = excelUploadHelper.DateTimeCheck(operStartDt, "운전시작일", rowName);
+
+                        if (!Util.IsNullOrEmpty(building)) excelUploadHelper.EntityIdCheck(buildingId, "설치위치[대]", rowName);
+                        if (!Util.IsNullOrEmpty(floor)) excelUploadHelper.EntityIdCheck(floorId, "설치위치[중]", rowName);
+                        if (!Util.IsNullOrEmpty(zone)) excelUploadHelper.EntityIdCheck(zoneId, "설치위치[소]", rowName);
+
+                        if (!Util.IsNullOrEmpty(fuelType)) excelUploadHelper.EntityIdCheck(fuelTypeId, "연료종류", rowName);
+                        if (!Util.IsNullOrEmpty(mainContactorUser)) excelUploadHelper.EntityIdCheck(mainContactorUserId, "정담당자", rowName);
+                        if (!Util.IsNullOrEmpty(subContactorUser)) excelUploadHelper.EntityIdCheck(subContactorUserId, "부담당자", rowName);
+                        if (!Util.IsNullOrEmpty(contractType)) excelUploadHelper.EntityIdCheck(contractTypeId, "계약종류", rowName);
+
+                        excelUploadHelper.NumericCheck(rateCop, "정격COP", rowName);
+                        excelUploadHelper.NumericCheck(facilityCost, "시설비용", rowName);
+                        excelUploadHelper.NumericCheck(facilityCount, "시설수량", rowName);
+
+
+                        var facility = new CmFacility {
+                            SiteId = siteId,
+                            FacilityTypeId = Util.ToInt(facilityTypeId),
+                            DeviceType = deviceType,
+                            Name = name,
+                            //Nickname = nickName,
+                            FacilityUsage = facilityUsage,
+                            Manufacturer = manufacturer,
+                            IsUse = isUse.Equals("활성화") ? true : false,
+                            FirstClassId = fristClassId,
+                            SecondClassId = secondClassId,
+                            ThirdClassId = thirdClassId,
+                            FacilityCapacity = facilityCapacity,
+                            Rfid = qrCode.Trim(),
+                            RatedPowerConsumption = ratedPowerConsumption,
+                            ManufactureSerial = manufactureSerial,
+                            ManufactureModel = manufactureModel,
+                            Supplier = supplier,
+                            SupplierPhoneNo = supplierPhoneNo,
+                            BuildingId = buildingId,
+                            FloorId = floorId,
+                            ZoneId = zoneId,
+                            MainContactorUserId = mainContactorUserId,
+                            SubContactorUserId = subContactorUserId,
+                            FuelTypeId = fuelTypeId,
+                            ContractType = contractTypeId,
+                            Note = note,
+                            IsVirtualFacility = Util.ToInt(facilityTypeId) > 99 ? true : false,
+                            ExcelRowNum = i,
+                        };
+                        if (manufactureDt != null) facility.ManufactureDate = DateTime.Parse(manufactureDt);
+                        if (installDt != null) facility.InstallDate = DateTime.Parse(installDt);
+                        if (operStartDt != null) facility.OperationStartDate = DateTime.Parse(operStartDt);
+                        if (rateCop != null) facility.RatedCop = Util.ToInt(rateCop);
+                        if (facilityCost != null) facility.FacilityCost = Util.ToInt(facilityCost);
+                        if (facilityCount != null) facility.FacilityCount = Util.ToInt(facilityCount);
+
+                        //글자수 체크
+                        TryValidateModel(facility);
+
+                        if (!ModelState.IsValid) throw new ServiceException(string.Join(" ",
+                                        ModelState.Values
+                                        .SelectMany(x => x.Errors)
+                                        .Select(x => x.ErrorMessage)) + rowName);
+
+                        //중복체크 
+                        //var overlapRowNum = list.Where(x => x.SiteId == facility.SiteId && x.Name == facility.Name && x.Nickname == facility.Nickname).Select(x => x.ExcelRowNum).SingleOrDefault();
+                        var overlapRowNum = list.Where(x => x.SiteId == facility.SiteId && x.Name == facility.Name).Select(x => x.ExcelRowNum).SingleOrDefault();
+
+                        if (overlapRowNum != 0)
+                            throw new ServiceException("파일 내 동일 정보가 존재합니다.[시설명칭]" + "(" + (overlapRowNum) + "번 행 - " + (i) + "번 행)");
+                        //throw new ServiceException("파일 내 동일 정보가 존재합니다.[시설명칭-시설별칭]" + "(" + (overlapRowNum) + "번 행 - " + (i) + "번 행)");
+
+                        overlapRowNum = list.Where(x => x.Rfid == facility.Rfid && Util.IsNullOrEmpty(facility.Rfid) == false).Select(x => x.ExcelRowNum).SingleOrDefault();
+
+                        if (overlapRowNum != 0)
+                            throw new ServiceException("파일 내 동일 정보가 존재합니다.[QR코드]" + "(" + (overlapRowNum) + "번 행 - " + (i) + "번 행)");
+
+                        if (_service.checkName(facility, true) > 0)
+                            throw new ServiceException("기존 시설과 동일 정보가 존재합니다.[시설명칭]" + rowName);
+                        //throw new ServiceException("기존 시설과 동일 정보가 존재합니다.[시설명칭-시설별칭]" + rowName);
+
+                        if (!Util.IsNullOrEmpty(facility.Rfid) && _service.checkRfid(facility, true) > 0)
+                            throw new ServiceException("기존 시설과 동일 정보가 존재합니다.[QR코드]" + rowName);
+
+
+                        // 데이터 값 설정
+                        list.Add(facility);
+                    }
+                }
+            }
+
+            if (list.Count > 0) {
+                _service.ImportExcel(list);
+                return Ok($"엑셀업로드를 성공하였습니다. {list.Count}건의 데이터가 등록되었습니다.");
+            } else {
+                throw new ServiceException("등록할 데이터가 없습니다.");
+            }
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmFacility data) {
+            // if (!User.IsInRole("Admin"))
+            //     return Forbid();
+
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <param name="siteId"></param>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int id, int siteId) {
+            _service.Delete(id, siteId);
+
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmFacility> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            int[] virtualFacility = new int[] { 99, 100, 101, 102, 103 };
+            query = query.Where(x => !virtualFacility.Contains(x.FacilityTypeId));
+
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            // 기본 Entity 정렬
+            //query = query.Sort(req.sort);
+
+            // 설비명 Sort 때문에 임시. _ListSelect 에서 Sort 함. 후에 수정해야 함.
+            // 고정정렬 (사옥 준공순)
+            var sortQuery = query.OrderBy(x => x.CmSite.SortOrderNo);
+
+            // 추가 정렬
+            if (req.sort != null) {
+                if (req.sort.field == "Custom") { // 기본 커스텀 정렬 예제 (입고역순,승인일순)
+                    sortQuery = sortQuery.ThenBy(x => x.FmsFacilityCodeClass.Name);
+                    sortQuery = sortQuery.ThenBy(x => x.FmsFacilityCodeClass1.Name);
+                    sortQuery = sortQuery.ThenBy(x => x.FmsFacilityCodeClass2.Name);
+                    sortQuery = sortQuery.ThenBy(x => x.FacilityUsage);
+                    sortQuery = sortQuery.ThenBy(x => x.FacilityCapacity);
+                    sortQuery = sortQuery.ThenBy(x => x.Manufacturer);
+                    sortQuery = sortQuery.ThenBy(x => x.ManufactureDate);
+                } else if (req.sort.field == "Custom2") {
+                    sortQuery = sortQuery.ThenBy(x => x.FacilityTypeId);
+                } else {
+                    sortQuery = sortQuery.ThenSort(req.sort);
+                }
+            }
+
+            // if (req.sort.field == "StoredAndAproval") { // 기본 커스텀 정렬 예제 (입고역순,승인일순)
+            //     query = query.ThenBy(x => x.FmsFacilityCodeClass.Name);
+            // .ThenBy(x => x.FmsFacilityCodeClass1.Name)
+            // .ThenBy(x => x.FmsFacilityCodeClass2.Name)
+            // .ThenBy(x => x.FacilityUsage)
+            // .ThenBy(x => x.FacilityCapacity)
+            // .ThenBy(x => x.Manufacturer)
+            // .ThenBy(x => x.ManufactureDate);
+            // } else {
+            //     query = query.ThenSort(req.sort);
+            // }
+
+            return sortQuery;
+        }
+
+    }
+}

+ 277 - 0
Controllers/FacilityStateController.cs

@@ -0,0 +1,277 @@
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+
+    public class FacilityStateController : Controller {
+        private readonly ILogger<FacilityStateController> _logger;
+        private readonly FMSContext _context;
+        private readonly FacilityStateService _service;
+        private readonly StorageHelper _storage;
+        public FacilityStateController(
+            ILogger<FacilityStateController> logger,
+            FMSContext context,
+            FacilityStateService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("정보를 찾을 수 없습니다.");
+            }
+
+            DateTime dt = DateTime.Today;
+            var list = data.Select(x => new {
+                x.FacilityStateId,
+                x.Title,
+                x.StartDate,
+                x.EndDate,
+                x.Note,
+                StateType = x.StartDate > dt ? 1 : (x.StartDate <= dt && x.EndDate >= dt ? 2 : 3),
+                StateTypeName = x.StartDate > dt ? "준비중" : (x.StartDate <= dt && x.EndDate >= dt ? "진행중" : "완료"),
+                FmsFacilityStateDetailList = x.FmsFacilityStateDetail.Select(f => new {
+                    rownum = f.Type == 1 ? f.QuestionId.ToString() : f.CmSite.Name,
+                    f.FacilityStateDetailId,
+                    f.FacilityStateId,
+                    f.QuestionId,
+                    f.Type,
+                    f.SiteId,
+                    SiteName = f.CmSite.Name,
+                    SiteSort = f.CmSite.SortOrderNo,
+                    f.Content,
+                }).OrderBy(f => f.QuestionId).ThenBy(f => f.Type).ThenBy(f => f.SiteSort).ToList(),
+            });
+
+            return Ok(list.First());
+        }
+
+        [HttpGet("{id}/{siteId}")]
+        public IActionResult GetReply(int id, int siteId) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("정보를 찾을 수 없습니다.");
+            }
+
+            DateTime dt = DateTime.Today;
+            var list = data.Select(x => new {
+                x.FacilityStateId,
+                x.Title,
+                x.StartDate,
+                x.EndDate,
+                x.Note,
+                StateType = x.StartDate > dt ? 1 : (x.StartDate <= dt && x.EndDate >= dt ? 2 : 3),
+                StateTypeName = x.StartDate > dt ? "준비중" : (x.StartDate <= dt && x.EndDate >= dt ? "진행중" : "완료"),
+                FmsFacilityStateDetailList = x.FmsFacilityStateDetail.Where(f => f.Type == 1 || (f.Type == 2 && f.SiteId == siteId)).Select(f => new {
+                    rownum = f.Type == 1 ? f.QuestionId.ToString() : "답변",
+                    f.FacilityStateDetailId,
+                    f.FacilityStateId,
+                    f.QuestionId,
+                    f.Type,
+                    f.SiteId,
+                    SiteName = f.CmSite.Name,
+                    SiteSort = f.CmSite.SortOrderNo,
+                    f.Content,
+                }).OrderBy(f => f.QuestionId).ThenBy(f => f.Type).ThenBy(f => f.SiteSort).ToList(),
+            });
+
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            int id = 0;
+            if (req.conditions != null) {
+                foreach (var item in req.conditions) {
+                    if (item.field == "FamilityStateId") {
+                        id = Util.ToInt(item.value);
+                        break;
+                    }
+                }
+            }
+            var query = _service.GetDetail(id);
+            query = query.OrderBy(x => x.QuestionId).ThenBy(x => x.Type).ThenBy(x => x.CmSite.SortOrderNo);
+            var list = query.Select(x => new {
+                rownum = x.Type == 1 ? x.QuestionId.ToString() : x.CmSite.Name,
+                x.FacilityStateDetailId,
+                x.FacilityStateId,
+                x.QuestionId,
+                x.Type,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.Content,
+            });
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort != null ? req.sort.order.ToLower() == "asc" : true, filename);
+                }
+
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut()]
+        public IActionResult Create([FromBody] FmsFacilityState data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] FmsFacilityState data) {
+            if (id != data.FacilityStateId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 답변
+        /// </summary>
+        [HttpPost("{id}/{siteId}")]
+        public IActionResult Reply(int id, int siteId, [FromBody] FmsFacilityState data) {
+            if (id != data.FacilityStateId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Reply(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        /// <returns></returns>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+
+            return Ok();
+        }
+
+        private dynamic _ListSelect(IQueryable<FmsFacilityState> query) {
+            DateTime dt = DateTime.Today;
+
+            var list = query.Select(x => new {
+                x.FacilityStateId,
+                x.Title,
+                StartDate = x.StartDate.ToString("yyyy-MM-dd"),
+                EndDate = x.EndDate.ToString("yyyy-MM-dd"),
+                StateType = x.StartDate > dt ? 1 : (x.StartDate <= dt && x.EndDate >= dt ? 2 : 3),
+                StateTypeName = x.StartDate > dt ? "준비중" : (x.StartDate <= dt && x.EndDate >= dt ? "진행중" : "완료"),
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<FmsFacilityState> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            //진행상태 검색
+            if (req.conditions != null) {
+                foreach (var item in req.conditions) {
+                    if (item.field == "StateType") {
+                        DateTime dt = DateTime.Today;
+                        if (item.value == "1") {
+                            query = query.Where(x => x.StartDate > dt);
+                        } else if (item.value == "2") {
+                            query = query.Where(x => x.StartDate <= dt && x.EndDate >= dt);
+                        } else if (item.value == "3") {
+                            query = query.Where(x => x.EndDate < dt);
+                        }
+                    }
+                }
+            }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

+ 177 - 0
Controllers/FacilityTimeController.cs

@@ -0,0 +1,177 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using FMSAdmin.Data;
+using FMSAdmin.Helpers;
+using System.Collections;
+using System.Text.RegularExpressions;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Models.Formula;
+using FMSAdmin.Helpers.Formula;
+using FMSAdmin.Services;
+using System.IO;
+using OfficeOpenXml;
+using System.Data;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class FacilityTimeController : Controller {
+        private readonly ILogger<FacilityTimeController> _logger;
+        private readonly EnergyUsageService _service;
+        private readonly FMSContext _context;
+
+        public FacilityTimeController(
+            ILogger<FacilityTimeController> logger,
+            EnergyUsageService service,
+            FMSContext context
+            ) {
+            _logger = logger;
+            _service = service;
+            _context = context;
+        }
+
+        /**
+         * 가동시간 대상 설비종류
+         */
+        [HttpGet("[action]")]
+        public IActionResult FacilityTypeList(int siteId) {
+            var query = from x in _context.BemsFacilityType
+                        where x.BemsMonitoringPoint.Where(p => p.PropertyId == 32 && p.SiteId == siteId).Any()
+                        select x;
+            var list = query.Select(x => new {
+                x.FacilityTypeId,
+                x.Name,
+            });
+
+            return Ok(list);
+        }
+
+        /**
+         * 사이트별 월별 시설종류별 가동시간
+         */
+        [HttpGet("[action]")]
+        public IActionResult Data(int siteId, int? facilityType, int? year, int? month, bool excel = false) {
+            _logger.LogInformation("siteId:" + siteId);
+
+            try {
+                if (year == null) year = DateTime.Now.Year;
+                if (month == null) month = DateTime.Now.Month;
+                var start = new DateTime(year.Value, month.Value, 1);
+                var end = start.AddMonths(1).AddDays(-1);
+
+                _logger.LogInformation("start" + start.ToShortDateString());
+                _logger.LogInformation("end" + end.ToShortDateString());
+
+                var days = end.Subtract(start).Days + 1;
+                var rawdata = _GetRawData(siteId, facilityType, start, end);
+                var list_t = (from x in rawdata
+                                  // orderby x.CmFacility.Name
+                              select new {
+                                  x.CmFacility.Name,
+                                  x.FacilityCode,
+                                  x.CreatedDateTime.Day,
+                                  x.DailyValue,
+                                  Sort1 = x.CmFacility.Name.Substring(0, x.CmFacility.Name.IndexOf("-") != -1 ? x.CmFacility.Name.IndexOf("-") : x.CmFacility.Name.Length),
+                                  Sort2 = Util.ToInt(x.CmFacility.Name.Substring(x.CmFacility.Name.LastIndexOf("-") + 1, x.CmFacility.Name.Length - x.CmFacility.Name.LastIndexOf("-") - 1)),
+                              }).ToList();
+
+                var list = list_t.OrderBy(x => x.Sort1).ThenBy(x => x.Sort2);
+
+                //_logger.LogInformation(list.Dump());
+
+                var columns = new List<string>();
+                PagingRequest.Column[] pageColumn = new PagingRequest.Column[days + 2];
+
+                var table = new DataTable();
+                table.Columns.Add("설비명칭");
+                table.Columns.Add("가동시간(분)", typeof(Double));
+                columns.Add("설비명칭");
+                columns.Add("가동시간(분)");
+                pageColumn[0] = new PagingRequest.Column { name = "설비명칭", field = "설비명칭" };
+                pageColumn[1] = new PagingRequest.Column { name = "가동시간(분)", field = "가동시간(분)", format = "price2" };
+                for (var d = 1; d <= days; d++) {
+                    table.Columns.Add($"{d}일", typeof(Double));
+                    columns.Add($"{d}일");
+                    pageColumn[d + 1] = new PagingRequest.Column { name = $"{d}일", field = $"{d}일", format = "price2" };
+                }
+                int currentCode = -1;
+                DataRow row = null;
+                double total = 0;
+                foreach (var item in list) {
+                    if (currentCode != item.FacilityCode) {
+                        if (row != null) {
+                            row["가동시간(분)"] = total;
+                            total = 0;
+                            table.Rows.Add(row);
+                            row = null;
+                        }
+                        row = table.NewRow();
+                        row["설비명칭"] = item.Name;
+                        for (var d = 1; d <= days; d++) {
+                            row[$"{d}일"] = 0;
+                        }
+                    }
+                    row[$"{item.Day}일"] = Math.Round(item.DailyValue);
+                    total += Math.Round(item.DailyValue);
+                    currentCode = item.FacilityCode;
+                }
+                if (row != null) {
+                    row["가동시간(분)"] = total;
+                    total = 0;
+                    table.Rows.Add(row);
+                    row = null;
+                }
+
+                if (excel) {
+                    var stream = new MemoryStream();
+                    using (var package = new ExcelPackage(stream)) {
+                        var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                        workSheet.Cells.LoadFromDataTable(table, true);
+                        // workSheet.AddStyle();
+                        workSheet.AddStyle(pageColumn, true, "설비 가동시간");
+                        package.Save();
+                    }
+                    stream.Position = 0;
+                    string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+                    return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+                }
+
+                return Ok(new {
+                    columns,
+                    data = table
+                });
+            } catch (FormatException ex) {
+                _logger.LogError(ex, "");
+                throw new Exception("날짜형식을 올바르게 입력해주세요");
+            }
+        }
+
+        private IQueryable<BemsMonitoringPointHistoryDaily> _GetRawData(int siteId, int? facilityType, DateTime startDate, DateTime endDate) {
+            const int propertyId = 32; // 가동시간
+
+            var query = from x in _context.BemsMonitoringPointHistoryDaily
+                        where x.SiteId == siteId &&
+                            //x.FacilityTypeId == facilityType &&
+                            x.PropertyId == propertyId &&
+                            startDate <= x.CreatedDateTime && x.CreatedDateTime <= endDate
+                        select x;
+            if (facilityType != null) {
+                query = query.Where(x => x.FacilityTypeId == facilityType.Value);
+            }
+
+            return query;
+        }
+
+    }
+
+}

+ 67 - 0
Controllers/FileDownloadController.cs

@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using FMSAdmin.Data;
+using FMSAdmin.Helpers;
+using System.Collections;
+using System.Text.RegularExpressions;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSApp.Services;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Models.Formula;
+using FMSAdmin.Helpers.Formula;
+using FMSAdmin.Services;
+using System.IO;
+using OfficeOpenXml;
+using System.Data;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Net;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class FileDownloadController : Controller {
+        private readonly ILogger<FileDownloadController> _logger;
+        private readonly StorageHelper _storage;
+
+        public FileDownloadController(
+            ILogger<FileDownloadController> logger,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _storage = storage;
+        }
+        [AllowAnonymous]
+        [HttpGet("DownloadFile")]
+        public IActionResult DownloadFile(String filePath, String fileName) {
+            try {
+                return File(_storage.GetFileStream(filePath), "application/octet-stream", fileName);
+            } catch (Exception ex) {
+                _logger.LogError(ex.ToString());
+                return Content("<html><head><meta charset='UTF-8'></head><body><script>alert('파일을 찾을 수 없습니다.');window.close();</script></body></html>", "text/html");
+            }
+        }
+
+        [AllowAnonymous]
+        [HttpGet("PDFViewer/{fileName}")]
+        public IActionResult PDFViewer(String filePath, String fileName) {
+            try {
+                Response.Headers.Add("Content-Disposition", "inline;filename*=UTF-8'" + Uri.EscapeDataString(fileName));
+                Response.Headers.Add("X-Content-Type-Options", "nosniff");
+                Response.Headers.Add("filename", WebUtility.UrlEncode("anvd"));
+
+                return File(System.IO.File.ReadAllBytes(_storage.GetLocalPath(filePath)), "application/pdf");
+            } catch (Exception ex) {
+                _logger.LogError(ex.ToString());
+                return Content("<html><head><meta charset='UTF-8'></head><body><script>alert('파일을 찾을 수 없습니다.');window.close();</script></body></html>", "text/html");
+            }
+        }
+    }
+}

+ 165 - 0
Controllers/FloorCodeController.cs

@@ -0,0 +1,165 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class FloorCodeController : Controller {
+        private readonly ILogger<FloorCodeController> _logger;
+        private readonly FMSContext _context;
+        private FloorCodeService _service;
+
+        public FloorCodeController(
+            ILogger<FloorCodeController> logger,
+            FMSContext context,
+            FloorCodeService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmFloorCode> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmFloorCode> query) {
+            var list = query.Select(x => new {
+                x.FloorCode,
+                x.Name,
+                x.SortNum
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.FloorCode,
+                x.Name,
+                x.SortNum
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] CmFloorCode data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.FloorCode) {
+                return BadRequest("잘못된 접근입니다.>>");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmFloorCode data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 276 - 0
Controllers/FloorController.cs

@@ -0,0 +1,276 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class FloorController : Controller {
+        private readonly ILogger<FloorController> _logger;
+        private readonly FMSContext _context;
+        private FloorService _service;
+
+        public FloorController(
+            ILogger<FloorController> logger,
+            FMSContext context,
+            FloorService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmFloor> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            // 고정정렬 (사옥 준공순)
+            var sortQuery = query.OrderBy(x => x.CmSite.SortOrderNo);
+
+            // 추가 정렬
+            sortQuery = sortQuery.ThenSort(req.sort);
+
+            return sortQuery;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmFloor> query) {
+            var list = query.Select(x => new {
+                x.FloorId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.BuildingId,
+                BuildingName = x.CmBuilding.Name,
+                x.Name,
+                SortNum = x.CmFloorCode.SortNum
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.FloorId,
+                x.Name,
+                x.BuildingId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                CmBuilding = new {
+                    x.CmBuilding.Name
+                }
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] CmFloor data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.FloorId) {
+                return BadRequest("잘못된 접근입니다.>>");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmFloor data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+            return Ok();
+        }
+
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult ImportExcelAdmin(IFormFile excelFile) {
+            return (ImportExcel(excelFile, true));
+        }
+
+        /// <summary>
+        /// 엑셀파일 임포트
+        /// </summary>
+        /// <param name="file"></param>
+        /// <returns></returns>
+        [HttpPost]
+        [Route("[action]")]
+        public IActionResult ImportExcel(IFormFile excelFile, bool isAdmin = false) {
+            IList<CmFloor> list = new List<CmFloor>();
+            var userInfo = _context.CmUser.Where(x => x.UserId == User.Identity.Name).SingleOrDefault();
+
+            using (ExcelPackage excelPackage = new ExcelPackage(excelFile.OpenReadStream())) {
+                var worksheet = excelPackage.Workbook.Worksheets[0];
+                int colCnt = isAdmin ? 3 : 2;
+                ExcelUploadHepler excelUploadHelper = new ExcelUploadHepler();
+                //헤더세팅
+                excelUploadHelper.GenerateColumns(worksheet, 1, colCnt);
+
+                for (int i = worksheet.Dimension.Start.Row + 1; i <= worksheet.Dimension.End.Row; i++) {
+                    if (!excelUploadHelper.IsRowEmpty(worksheet, i, colCnt)) {
+                        var site = isAdmin ? excelUploadHelper.GetValue(worksheet, "사옥(*)", i, true) : "";
+                        var building = excelUploadHelper.GetValue(worksheet, "빌딩명(*)", i, true);
+                        var name = excelUploadHelper.GetValue(worksheet, "층명칭(*)", i, true);
+
+                        _logger.LogInformation("site " + site);
+                        _logger.LogInformation("type " + building);
+                        _logger.LogInformation("codnamee " + name);
+
+                        var rowName = "(" + (i) + "번 행)";
+
+                        var siteId = isAdmin ? _context.CmSite.Where(x => x.Name == site && x.IsUse == true).Select(x => x.SiteId).SingleOrDefault() : userInfo.SiteId;
+                        var buildingId = _context.CmBuilding.Where(x => x.SiteId == siteId && x.Name == building && x.IsUse == true).Select(x => x.BuildingId).SingleOrDefault();
+                        var floorCodeId = _context.CmFloorCode.Where(x => x.Name == name).Select(x => x.FloorCode).SingleOrDefault();
+
+                        if (isAdmin) excelUploadHelper.EntityIdCheck(siteId, "사옥", rowName);
+                        excelUploadHelper.EntityIdCheck(buildingId, "빌딩명", rowName);
+                        excelUploadHelper.EntityIdCheck(floorCodeId, "층명칭", rowName);
+
+                        var floor = new CmFloor {
+                            SiteId = siteId,
+                            BuildingId = buildingId,
+                            FloorCode = floorCodeId,
+                            Name = name,
+                            ExcelRowNum = i
+                        };
+
+
+                        //StringLength 체크 
+                        TryValidateModel(floor);
+
+                        if (!ModelState.IsValid) throw new ServiceException(string.Join(" ",
+                                        ModelState.Values
+                                        .SelectMany(x => x.Errors)
+                                        .Select(x => x.ErrorMessage)) + rowName);
+
+                        //중복체크
+                        var overlapRowNum = list.Where(x => x.SiteId == floor.SiteId && x.BuildingId == floor.BuildingId && x.Name == floor.Name).Select(x => x.ExcelRowNum).SingleOrDefault();
+
+                        if (overlapRowNum != 0)
+                            throw new ServiceException("파일 내 동일 정보가 존재합니다.[빌딩명-층명칭]" + "(" + (overlapRowNum) + "번 행 - " + (i) + "번 행)");
+
+                        if (_service.checkName(floor, true) > 0) {
+                            throw new ServiceException("기존 층정보와 동일 정보가 존재합니다.[빌딩명-층명칭]" + rowName);
+                        }
+
+                        // 데이터 값 설정
+                        list.Add(floor);
+                    }
+                }
+            }
+            if (list.Count > 0) {
+
+                _service.ImportExcel(list);
+
+                return Ok($"엑셀업로드를 성공하였습니다. {list.Count}건의 데이터가 등록되었습니다.");
+            } else {
+                throw new ServiceException("등록할 데이터가 없습니다.");
+            }
+        }
+
+
+    }
+}

+ 247 - 0
Controllers/FormulaControllers.cs

@@ -0,0 +1,247 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using System.Text.RegularExpressions;
+using System.Collections;
+using System.Collections.Generic;
+using FMSAdmin.Models.Formula;
+using FMSAdmin.Helpers.Formula;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class FormulaController : Controller {
+
+        private static Regex regexParameter = new Regex(@"\b[A-Z]\b", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
+        private static Regex regexFunction = new Regex(@"\$([\w]+).+\$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase);
+        private static FormulaTableManager tableManager;
+        private readonly ILogger<FormulaController> _logger;
+        private readonly FormulaService _service;
+        private readonly FMSContext _context;
+        public FormulaController(
+            ILogger<FormulaController> logger,
+            FormulaService service,
+            FMSContext context
+            ) {
+            _logger = logger;
+            _service = service;
+            _context = context;
+            tableManager = new FormulaTableManager(context);
+        }
+
+
+
+        /// <summary>
+        /// 자식 노드 리스트
+        /// </summary>
+        /// <param name="parentId"></param>
+        /// <param name="type"></param>
+        /// <returns></returns>
+        [HttpGet("childs/{siteId}")]
+        public IActionResult Childs(int siteId) {
+
+
+
+            var normalFacility = from x in _context.BemsFacilityType
+                                 where x.FacilityTypeId < 99
+                                 orderby x.FacilityTypeId ascending
+                                 select new {
+                                     key = x.FacilityTypeId.ToString() + 0.ToString() + "n",
+                                     title = x.Name,
+                                     depth = 0,
+                                     FacilityTypeId = x.FacilityTypeId,
+                                     type = "n",
+                                     children = _context.CmFacility.Where(item => item.FacilityTypeId == x.FacilityTypeId && item.SiteId == siteId && !item.IsVirtualFacility && item.IsUse == true).Select(c => new {
+                                         key = c.FacilityCode.ToString() + 1.ToString() + "n",
+                                         title = c.Name,
+                                         FacilityTypeId = c.FacilityTypeId,
+                                         depth = 1,
+                                         type = "n",
+                                         children = _context.BemsFormulaBase.Where(item => item.FacilityTypeId == x.FacilityTypeId).Select(d => new {
+                                             key = x.FacilityTypeId.ToString() + d.FormulaId.ToString() + 2.ToString() + "n",
+                                             FacilityCode = c.FacilityCode,
+                                             FacilityTypeId = x.FacilityTypeId,
+                                             FormulaId = d.FormulaId,
+                                             Formula = d.Formulas.Where(item => item.SiteId == siteId && item.FacilityTypeId == x.FacilityTypeId && item.FacilityCode == c.FacilityCode && item.FormulaId == d.FormulaId).Select(x => x.Formula).FirstOrDefault(),
+                                             title = d.Name,
+                                             depth = 2,
+                                             type = "n",
+
+                                         }).ToList()
+
+                                     }).ToList()
+                                 };
+
+            var systemFacility = from x in _context.BemsFacilityType
+                                 where x.FacilityTypeId > 99
+                                 orderby x.FacilityTypeId ascending
+                                 select new {
+                                     key = x.FacilityTypeId.ToString() + 0.ToString() + "s",
+                                     title = x.Name,
+                                     depth = 0,
+                                     FacilityTypeId = x.FacilityTypeId,
+                                     type = "s",
+                                     children = _context.CmFacility.Where(item => item.FacilityTypeId == x.FacilityTypeId && item.SiteId == siteId && item.IsVirtualFacility && item.IsUse == true).Select(c => new {
+                                         key = c.FacilityCode.ToString() + 1.ToString() + "s",
+                                         title = c.Name,
+                                         FacilityTypeId = c.FacilityTypeId,
+                                         depth = 1,
+                                         type = "s",
+                                         children = _context.BemsFormulaBase.Where(item => item.FacilityTypeId == x.FacilityTypeId).Select(d => new {
+                                             key = x.FacilityTypeId.ToString() + d.FormulaId.ToString() + 2.ToString() + "s",
+                                             FacilityCode = c.FacilityCode,
+                                             FacilityTypeId = x.FacilityTypeId,
+                                             FormulaId = d.FormulaId,
+                                             Formula = d.Formulas.Where(item => item.SiteId == siteId && item.FacilityTypeId == x.FacilityTypeId && item.FormulaId == d.FormulaId).Select(x => x.Formula).FirstOrDefault(),
+                                             title = d.Name,
+                                             depth = 2,
+                                             type = "s",
+
+                                         }).ToList()
+                                     }).ToList()
+                                 };
+
+            // var systemFacility = from x in _context.BemsFacilityType
+            //                      where x.FacilityTypeId > 99
+            //                      orderby x.FacilityTypeId ascending
+            //                      select new {
+            //                          key = x.FacilityTypeId.ToString() + 0.ToString() + "s",
+            //                          title = x.Name,
+            //                          depth = 0,
+            //                          FacilityTypeId = x.FacilityTypeId,
+            //                          type = "s",
+            //                          children = _context.BemsFormulaBase.Where(item => item.FacilityTypeId == x.FacilityTypeId).Select(d => new {
+            //                              key = x.FacilityTypeId.ToString() + d.FormulaId.ToString() + 2.ToString() + "s",
+            //                              //FacilityCode = c.FacilityCode,
+            //                              FacilityTypeId = x.FacilityTypeId,
+            //                              FormulaId = d.FormulaId,
+            //                              Formula = d.Formulas.Where(item => item.SiteId == siteId && item.FacilityTypeId == x.FacilityTypeId && item.FormulaId == d.FormulaId).Select(x => x.Formula).FirstOrDefault(),
+            //                              title = d.Name,
+            //                              depth = 2,
+            //                              type = "s",
+
+            //                          }).ToList()
+            //                      };
+
+            var result = new {
+                normalFacility,
+                systemFacility
+            };
+
+            return Ok(result);
+        }
+
+
+        [HttpGet("[action]")]
+        public IActionResult GetFormula(int siteId, int facilityTypeId, int facilityCode, int formulaId) {
+
+            var item = (from x in _context.BemsFormula
+                        where x.SiteId == siteId && x.FacilityTypeId == facilityTypeId && x.FacilityCode == facilityCode && x.FormulaId == formulaId
+                        select new {
+                            x.SiteId,
+                            x.FacilityTypeId,
+                            x.FacilityCode,
+                            x.FormulaId,
+                            x.Formula,
+                        }).ToList().FirstOrDefault();
+
+            return Ok(item);
+        }
+
+        [HttpGet("[action]")]
+        public IActionResult GetFormulaTable() {
+
+            var funcList = from x in _context.BemsFormulaTable
+                           orderby x.TableId ascending
+                           select new {
+                               FunctionName = x.FunctionName,
+                               Description = x.Description
+                           };
+
+
+
+            return Ok(funcList);
+        }
+
+
+        [HttpGet("[action]")]
+        public IActionResult GetFormulaFacility(int siteId, int facilityTypeId, int facilityCode, int formulaId) {
+
+            if (facilityCode != 0) {
+                var noarmalFacilityList = from x in _context.BemsFormulaParameter
+                                          where x.SiteId == siteId && x.FacilityTypeId == facilityTypeId && x.FacilityCode == facilityCode && x.FormulaId == formulaId
+                                          orderby x.ParameterId ascending
+                                          select new {
+                                              SiteId = x.SiteId,
+                                              ParameterId = x.ParameterId,
+                                              //CmFacilityName = x.CmFacility.Name,
+                                              CmFacilityName = _context.CmFacility.Where(item => item.SiteId == x.SiteId && item.FacilityCode == x.ParameterFacilityCode).Select(x => x.Name).FirstOrDefault(),
+                                              FacilityTypeId = x.FacilityTypeId,
+                                              FacilityCode = x.CmFacility.FacilityCode,
+                                              FormulaId = x.FormulaId,
+                                              ParameterFacilityCode = x.ParameterFacilityCode,
+                                              ParameterPropertyId = x.ParameterPropertyId,
+
+                                              Name = _context.BemsMonitoringPoint.Where(item => item.SiteId == x.SiteId && item.FacilityCode == x.ParameterFacilityCode && item.PropertyId == x.ParameterPropertyId).Select(x => x.Name).FirstOrDefault()
+                                          };
+                return Ok(noarmalFacilityList);
+            } else {
+                var systemFacilityList = from x in _context.BemsFormulaParameter
+                                         where x.SiteId == siteId && x.FacilityTypeId == facilityTypeId && x.FormulaId == formulaId
+                                         orderby x.ParameterId ascending
+                                         select new {
+                                             SiteId = x.SiteId,
+                                             ParameterId = x.ParameterId,
+                                             FacilityTypeId = x.FacilityTypeId,
+                                             //CmFacilityName = x.CmFacility.Name,
+                                             CmFacilityName = _context.CmFacility.Where(item => item.SiteId == x.SiteId && item.FacilityCode == x.ParameterFacilityCode).Select(x => x.Name).FirstOrDefault(),
+                                             FacilityCode = x.CmFacility.FacilityCode,
+                                             FormulaId = x.FormulaId,
+                                             ParameterFacilityCode = x.ParameterFacilityCode,
+                                             ParameterPropertyId = x.ParameterPropertyId,
+                                             Name = _context.BemsMonitoringPoint.Where(item => item.SiteId == x.SiteId && item.FacilityCode == x.ParameterFacilityCode && item.PropertyId == x.ParameterPropertyId).Select(x => x.Name).FirstOrDefault()
+                                         };
+                return Ok(systemFacilityList);
+            }
+
+        }
+
+
+        /// <summary>
+        /// 저장
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("[action]")]
+        public IActionResult Save([FromBody] BemsFormula data) {
+            _logger.LogInformation("Save");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Save(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+
+
+    }
+
+}

+ 214 - 0
Controllers/GuidelineController.cs

@@ -0,0 +1,214 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class GuidelineController : Controller {
+        private readonly ILogger<GuidelineController> _logger;
+        private readonly FMSContext _context;
+        private readonly GuidelineService _service;
+        private readonly StorageHelper _storage;
+
+        public GuidelineController(
+            ILogger<GuidelineController> logger,
+            FMSContext context,
+            GuidelineService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}/{isCount}")]
+        public IActionResult Get(int siteId, int id, int isCount) {
+            var data = _service.Gets(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("공지사항 정보를 찾을 수 없습니다.");
+            }
+
+            //조회수 증가
+            if (isCount == 1) {
+                _service.AddCount(id, siteId);
+            }
+
+            var item = data.Select(x => new {
+                x.GuidelineId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.BusinessFieldId,
+                CmBusinessField = new {
+                    x.CmBusinessField.Name,
+                },
+                x.Title,
+                x.Contents,
+                RegisterUser = x.CmUser.Name,
+                x.FileId,
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.AddDate,
+                x.UpdateDate,
+                x.IsUse,
+                x.ReadCount,
+                IsSysop = x.CmUser.IsSysop,
+                IsAllView = Util.ToBool(x.IsAllView),
+                IsAllViewName = (Util.ToBool(x.IsAllView)) ? "본사지침" : "",
+            });
+
+
+            return Ok(item.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] CmGuideline data) {
+            if (id != data.GuidelineId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmGuideline data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+
+            return Ok();
+        }
+
+        // 셀렉트 공통
+        private dynamic _ListSelect(IQueryable<CmGuideline> query) {
+            var list = query.Select(x => new {
+                x.GuidelineId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.BusinessFieldId,
+                BusinessFieldName = x.CmBusinessField.Name,
+                IsAllView = x.IsAllView.GetValueOrDefault() ? "Y" : "N",
+                x.Title,
+                RegisterUser = x.CmUser.Name,
+                AddDate = x.AddDate.ToString("yyyy/MM/dd"),
+                UpdateDate = x.UpdateDate.ToString("yyyy/MM/dd"),
+                x.IsUse,
+                IsSysop = x.CmUser.IsSysop,
+                FilePath = _storage.getFilePath(x.CmFile),
+                FileName = x.CmFile.Name
+            });
+            return list;
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmGuideline> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId) || x.IsAllView == true);
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            // 업무분야 필드가 있는경우만 추가
+            if (req.businessFieldId != null) {
+                query = query.Where(x => x.SiteId == req.siteId);
+                query = query.Where(x => x.BusinessFieldId == req.businessFieldId || x.CmBusinessField.Name == "공통");
+            }
+
+            var sortQuery = query.OrderByDescending(x => x.CmSite.IsAdmin);
+            sortQuery = sortQuery.ThenBy(x => x.CmSite.SortOrderNo);
+            sortQuery = sortQuery.ThenSort(req.sort);
+
+
+            return query;
+        }
+    }
+}

+ 261 - 0
Controllers/HolidayController.cs

@@ -0,0 +1,261 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class HolidayController : Controller {
+        private readonly ILogger<HolidayController> _logger;
+        private readonly FMSContext _context;
+        private readonly HolidayService _service;
+        private readonly StorageHelper _storage;
+        private readonly MonthWorkService _serviceMonthWork;
+        public HolidayController(
+            ILogger<HolidayController> logger,
+            FMSContext context,
+            HolidayService service,
+            MonthWorkService serviceMonthWork,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+            _serviceMonthWork = serviceMonthWork;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet("[action]")]
+        public IActionResult ListCalendar(int siteId, int? year, int? month) {
+            DateTime selectMonth;
+            if (year == null || month == null) {
+                selectMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+            } else {
+                selectMonth = new DateTime(year.Value, month.Value, 1);
+            }
+            var startDate = selectMonth.AddDays(-7);
+            var endDate = selectMonth.AddDays(37);
+
+            // 휴일
+            var holidayList = _GetHolidayList(siteId, selectMonth.Year, selectMonth.Month, startDate, endDate);
+
+            // 주말
+            var weekend = _context.CmHolidayWeekend.Where(
+                x => x.SiteId == siteId
+                && x.IsUse == true
+            ).FirstOrDefault();
+
+            if (weekend != null) {
+            }
+
+            // 주별 중점업무
+            var weekList = _GetWeekKeyWorkEventList(siteId, startDate, endDate);
+
+            var workName = _serviceMonthWork.GetKeyWork(siteId, selectMonth.Month);
+            var result = new {
+                Year = selectMonth.Year,
+                Month = selectMonth.Month,
+                Theme = workName,
+                StartDate = startDate.ToString("yyyy-MM-dd"),
+                EndDate = endDate.ToString("yyyy-MM-dd"),
+                HolidayList = holidayList,
+                Saturday = weekend?.Saturday ?? false,
+                Sunday = weekend?.Sunday ?? false,
+                WeekList = weekList
+            };
+
+            return Ok(result);
+        }
+
+        /// <summary>
+        /// 휴일 조회 (정기/특정)
+        /// </summary>
+        [HttpGet("{siteId}/{type}/{id}")]
+        public IActionResult Get(int siteId, string type, string id) {
+            if (type == "H") {      //정기                
+                var array = id.Split('-');
+                int month = Util.ToInt(array[0]);
+                int day = Util.ToInt(array[1]);
+                var data = _service.Gets(month, day, siteId);
+                if (data.Count() == 0) {
+                    return NotFound("휴일 정보를 찾을 수 없습니다.");
+                }
+                var item = data.Select(x => new {
+                    x.SiteId,
+                    SiteName = x.Site.Name,
+                    CmSite = new {
+                        x.Site.Name,
+                    },
+                    x.HolidayMonth,
+                    x.HolidayDay,
+                    x.IsLunar,
+                    x.Name,
+                    HolidayType = "H",
+                    isHolidayType = true,
+                    x.IsUse
+                });
+
+                return Ok(item.First());
+            } else if (type == "C") {       //특정
+                var data = _service.CustomGets(id, siteId);
+                if (data.Count() == 0) {
+                    return NotFound("휴일 정보를 찾을 수 없습니다.");
+                }
+                var item = data.Select(x => new {
+                    x.SiteId,
+                    SiteName = x.Site.Name,
+                    CmSite = new {
+                        x.Site.Name,
+                    },
+                    x.HolidayDate,
+                    x.Name,
+                    HolidayType = "C",
+                    isHolidayType = false,
+                    x.IsUse
+                });
+
+                return Ok(item.First());
+            } else {
+                return Ok();
+            }
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut("{dates}")]
+        public IActionResult Create(string dates, [FromBody] CmHoliday data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(dates, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, string id, [FromBody] CmHoliday data) {
+            string[] arrId = id.Split('-');
+            if (Util.ToInt(arrId[0]) != data.HolidayMonth || Util.ToInt(arrId[1]) != data.HolidayDay) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(Util.ToInt(arrId[0]), Util.ToInt(arrId[1]), siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{dates}")]
+        public IActionResult Delete(int siteId, string dates) {
+            _service.Delete(dates, siteId);
+
+            return Ok();
+        }
+
+        private IList<CalendarBackground> _GetHolidayList(int siteId, int year, int month, DateTime startDate, DateTime endDate) {
+            var list = new List<CalendarBackground>();
+
+            var holiday = _context.CmHoliday.Where(
+                x => x.SiteId == siteId
+                && x.IsUse == true
+            ).ToList();
+            foreach (var item in holiday) {
+                DateTime dt;
+                if (!DateTime.TryParse(year.ToString() + "-" + item.HolidayMonth.ToString("D2") + "-" + item.HolidayDay.ToString("D2"), out dt)) {
+                    continue;
+                }
+                if (item.IsLunar) {
+                    var solar = LunarSolarConverter.LunarToSolar(new Lunar() {
+                        lunarYear = year,
+                        lunarMonth = item.HolidayMonth,
+                        lunarDay = item.HolidayDay
+                    });
+                    list.Add(new CalendarBackground {
+                        Id = "H-" + item.SiteId + "-" + item.HolidayMonth.ToString() + "-" + item.HolidayDay.ToString(),
+                        Title = item.Name + "(음력)",
+                        Date = new DateTime(solar.solarYear, solar.solarMonth, solar.solarDay).ToString("yyyy-MM-dd"),
+                        SunDate = new DateTime(year, item.HolidayMonth, item.HolidayDay).ToString("yyyy-MM-dd"),
+                        Type = "H",
+                        IsLunar = item.IsLunar,
+                    });
+                } else {
+                    list.Add(new CalendarBackground {
+                        Id = "H-" + item.SiteId + "-" + item.HolidayMonth.ToString() + "-" + item.HolidayDay.ToString(),
+                        Title = item.Name,
+                        Date = new DateTime(year, item.HolidayMonth, item.HolidayDay).ToString("yyyy-MM-dd"),
+                        Type = "H",
+                        IsLunar = false,
+                    });
+                }
+            }
+
+            var holiday2 = _context.CmHolidayCustom.Where(x => x.SiteId == siteId && x.HolidayDate.Date >= startDate.Date && x.HolidayDate <= endDate.Date).ToList();
+            foreach (var item in holiday2) {
+                list.Add(new CalendarBackground {
+                    Id = "C-" + item.SiteId + "-" + item.HolidayDate.ToString("yyyyMMdd"),
+                    Title = item.Name,
+                    Date = item.HolidayDate.ToString("yyyy-MM-dd"),
+                    Type = "C",
+                });
+            }
+
+            return list;
+        }
+
+        private IQueryable<CalendarEvent> _GetWeekKeyWorkEventList(int? siteId, DateTime startDate, DateTime endDate) {
+            var query = _context.FmsWeekKeyWork.Select(x => x);
+            if (siteId != null) {
+                query = query.Where(x => x.SiteId == siteId.Value);
+            }
+            query = query.Where(x => x.WorkDate.Date >= startDate.Date && x.WorkDate.Date <= endDate.Date);
+
+            var list = query.Select(x => new CalendarEvent {
+                Title = x.WorkName,
+                Start = x.WorkDate.ToString("yyyy-MM-dd"),
+                End = x.WorkDate.AddDays(7).ToString("yyyy-MM-dd"),
+                WorkType = "주별중점업무",
+                Type = "주별 중점업무",
+                WorkRequestId = x.WeekKeyWorkId,
+                SiteId = x.SiteId,
+            });
+
+            return list;
+        }
+    }
+}

+ 92 - 0
Controllers/HolidayCustomController.cs

@@ -0,0 +1,92 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class HolidayCustomController : Controller {
+        private readonly ILogger<HolidayCustomController> _logger;
+        private readonly FMSContext _context;
+        private readonly HolidayCustomService _service;
+        private readonly StorageHelper _storage;
+
+        public HolidayCustomController(
+            ILogger<HolidayCustomController> logger,
+            FMSContext context,
+            HolidayCustomService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPut("{dates}")]
+        public IActionResult Create(string dates, [FromBody] CmHolidayCustom data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(dates, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{dates}")]
+        public IActionResult Edit(int siteId, string dates, [FromBody] CmHolidayCustom data) {
+            if (dates != data.HolidayDate.ToString("yyyyMMdd")) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(dates, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{dates}")]
+        public IActionResult Delete(int siteId, string dates) {
+            _service.Delete(dates, siteId);
+
+            return Ok();
+        }
+    }
+}

+ 93 - 0
Controllers/HolidayWeekendController.cs

@@ -0,0 +1,93 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using Newtonsoft.Json;
+using Microsoft.AspNetCore.Http;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class HolidayWeekendController : Controller {
+        private readonly ILogger<HolidayWeekendController> _logger;
+        private readonly FMSContext _context;
+        private readonly HolidayWeekendService _service;
+        private readonly StorageHelper _storage;
+
+        public HolidayWeekendController(
+            ILogger<HolidayWeekendController> logger,
+            FMSContext context,
+            HolidayWeekendService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet("{siteId}")]
+        public IActionResult Get(string siteId) {
+            var data = _service.Gets(Util.ToInt(siteId));
+            var sYear = DateTime.Today.Year.ToString();
+
+            if (data == null || data.Count() == 0) {
+                var item = new CmHolidayWeekend {
+                    SiteId = Util.ToInt(siteId),
+                    Site = new CmSite(),
+                    Saturday = false,
+                    Sunday = false,
+                    IsUse = true,
+                };
+
+                return Ok(item);
+            } else {
+                var item = data.Select(x => new {
+                    x.SiteId,
+                    Site = new {
+                        x.Site.Name,
+                    },
+                    x.Saturday,
+                    x.Sunday,
+                    x.IsUse,
+                });
+
+                return Ok(item.First());
+            }
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}")]
+        public IActionResult Save(int siteId, [FromBody] CmHolidayWeekend data) {
+            if (ModelState.IsValid) {
+                _service.Save(siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+    }
+}

+ 44 - 0
Controllers/HomeController.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using FMSAdmin.Models;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Data;
+using System.Net.Mail;
+using System.Net;
+using FMSAdmin.Helpers;
+using SelectPdf;
+
+namespace FMSAdmin.Controllers {
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api")]
+    public class HomeController : Controller {
+        private readonly ILogger<HomeController> _logger;
+        private readonly FMSContext _context;
+        private readonly StorageHelper _storage;
+
+        public HomeController(
+                ILogger<HomeController> logger,
+                FMSContext context,
+                StorageHelper storage
+                ) {
+            _logger = logger;
+            _context = context;
+            _storage = storage;
+        }
+
+        [HttpGet]
+        public IActionResult Index() {
+            return Content("api server");
+        }
+
+    }
+}

+ 162 - 0
Controllers/Investigation1CodeGroupController.cs

@@ -0,0 +1,162 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation1CodeGroupController : Controller {
+        private readonly ILogger<Investigation1CodeGroupController> _logger;
+        private readonly FMSContext _context;
+        private Investigation1CodeGroupService _service;
+
+        public Investigation1CodeGroupController(
+            ILogger<Investigation1CodeGroupController> logger,
+            FMSContext context,
+            Investigation1CodeGroupService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmInvestigation1CodeGroup> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation1CodeGroup> query) {
+            var list = query.Select(x => new {
+                x.InvestigationGroupId,
+                x.Name,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N",
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("코드 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.InvestigationGroupId,
+                x.Name,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] CmInvestigation1CodeGroup data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.InvestigationGroupId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation1CodeGroup data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 162 - 0
Controllers/Investigation1CodeTypeController.cs

@@ -0,0 +1,162 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation1CodeTypeController : Controller {
+        private readonly ILogger<Investigation1CodeTypeController> _logger;
+        private readonly FMSContext _context;
+        private Investigation1CodeTypeService _service;
+
+        public Investigation1CodeTypeController(
+            ILogger<Investigation1CodeTypeController> logger,
+            FMSContext context,
+            Investigation1CodeTypeService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmInvestigation1CodeType> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation1CodeType> query) {
+            var list = query.Select(x => new {
+                x.InvestigationTypeId,
+                x.Name,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N"
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.InvestigationTypeId,
+                x.Name,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] CmInvestigation1CodeType data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.InvestigationTypeId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation1CodeType data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 193 - 0
Controllers/Investigation1Controller.cs

@@ -0,0 +1,193 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation1Controller : Controller {
+        private readonly ILogger<Investigation1Controller> _logger;
+        private readonly FMSContext _context;
+        private Investigation1Service _service;
+
+        public Investigation1Controller(
+            ILogger<Investigation1Controller> logger,
+            FMSContext context,
+            Investigation1Service service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmInvestigation1> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId) || x.IsAllSite == true);
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation1> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                Investigation1GroupName = x.CmInvestigation1CodeGroup.Name,
+                Investigation1TypeName = x.CmInvestigation1CodeType.Name,
+                x.InvestigationNo,
+                Name = x.IsAllSite ? "[본사] " + x.Name : x.Name,
+                x.InvestigationGroupId,
+                x.InvestigationTypeId,
+                x.InvestigationId,
+                x.IsAllSite
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.InvestigationId,
+                x.Name,
+                x.SiteId,
+                x.InvestigationGroupId,
+                x.InvestigationTypeId,
+                x.InvestigationNo,
+                x.IsAllSite,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                CmInvestigation1CodeGroup = new {
+                    x.CmInvestigation1CodeGroup.Name
+                },
+                CmInvestigation1CodeType = new {
+                    x.CmInvestigation1CodeType.Name
+                },
+                x.Description
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] CmInvestigation1 data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.InvestigationId) {
+                return BadRequest("잘못된 접근입니다.>>");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation1 data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+            if (ModelState.IsValid) {
+                data.CreateUserId = User.Identity.Name;
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+            return Ok();
+        }
+
+    }
+}

+ 178 - 0
Controllers/Investigation1HistoryController.cs

@@ -0,0 +1,178 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using FMSAdmin.Services;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation1HistoryController : Controller {
+        private readonly ILogger<Investigation1HistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly Investigation1HistoryService _service;
+        private readonly StorageHelper _storage;
+
+        public Investigation1HistoryController(
+            ILogger<Investigation1HistoryController> logger,
+            FMSContext context,
+            Investigation1HistoryService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation1History> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.InvestigationId,
+                x.HistoryId,
+                x.RevisionNo,
+                x.FileId,
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.Description,
+                x.UpdatedDate,
+                UpdateUserName = x.CmInvestigation1.IsAllSite ? x.CmUser.CmSite.Name : x.CmUser.Name,
+            });
+            return list;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] string investigationId) {
+
+            var query = _FilterAndSort(req);
+            query = query.Where(x => x.InvestigationId == Util.ToInt(investigationId));
+            if (!Util.IsNullOrEmpty(siteId)) {
+                var currentUserSiteId = _context.CmUser.Where(x => x.UserId == User.Identity.Name).Select(x => x.SiteId).SingleOrDefault();
+                query = query.Where(x => x.CmUser.SiteId == currentUserSiteId || x.CmUser.SiteId == x.CmInvestigation1.SiteId);
+            }
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpDelete("{siteId}/{investigationId}/{historyId}")]
+        public IActionResult Delete(int siteId, int investigationId, int historyId) {
+            _service.Delete(siteId, investigationId, historyId);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmInvestigation1History> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{investigationId}/{historyId}")]
+        public IActionResult Get(int siteId, int investigationId, int historyId) {
+            var data = _service.Get(siteId, investigationId, historyId);
+            if (data.Count() == 0) {
+                return NotFound("정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.SiteId,
+                x.InvestigationId,
+                x.HistoryId,
+                x.RevisionNo,
+                CmUser = new {
+                    x.CmUser.Name,
+                },
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.Description
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{InvestigationId}")]
+        public int GetNextRevisionNo(int siteId, int investigationId) {
+            int nextRevisionNo = _service.GetRevisionNo(siteId, investigationId);
+            return nextRevisionNo;
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation1History data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{investigationId}/{historyId}")]
+        public IActionResult Edit(int siteId, int investigationId, int historyId, [FromBody] CmInvestigation1History data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + historyId);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+            if (historyId != data.HistoryId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                data.UpdatedUserId = User.Identity.Name;
+                _service.Edit(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        // [HttpDelete("{siteId}/{InvestigationId}/{historyId}")]
+        // public IActionResult Delete(int siteId, int id) {
+        //     _service.Delete(id, siteId);
+        //     return Ok();
+        // }
+    }
+}

+ 162 - 0
Controllers/Investigation2CodeGroupController.cs

@@ -0,0 +1,162 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation2CodeGroupController : Controller {
+        private readonly ILogger<Investigation2CodeGroupController> _logger;
+        private readonly FMSContext _context;
+        private Investigation2CodeGroupService _service;
+
+        public Investigation2CodeGroupController(
+            ILogger<Investigation2CodeGroupController> logger,
+            FMSContext context,
+            Investigation2CodeGroupService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmInvestigation2CodeGroup> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation2CodeGroup> query) {
+            var list = query.Select(x => new {
+                x.InvestigationGroupId,
+                x.Name,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N",
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.InvestigationGroupId,
+                x.Name,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] CmInvestigation2CodeGroup data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.InvestigationGroupId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation2CodeGroup data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 162 - 0
Controllers/Investigation2CodeTypeController.cs

@@ -0,0 +1,162 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation2CodeTypeController : Controller {
+        private readonly ILogger<Investigation2CodeTypeController> _logger;
+        private readonly FMSContext _context;
+        private Investigation2CodeTypeService _service;
+
+        public Investigation2CodeTypeController(
+            ILogger<Investigation2CodeTypeController> logger,
+            FMSContext context,
+            Investigation2CodeTypeService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmInvestigation2CodeType> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation2CodeType> query) {
+            var list = query.Select(x => new {
+                x.InvestigationTypeId,
+                x.Name,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N"
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.InvestigationTypeId,
+                x.Name,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] CmInvestigation2CodeType data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.InvestigationTypeId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation2CodeType data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 193 - 0
Controllers/Investigation2Controller.cs

@@ -0,0 +1,193 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation2Controller : Controller {
+        private readonly ILogger<Investigation2Controller> _logger;
+        private readonly FMSContext _context;
+        private Investigation2Service _service;
+
+        public Investigation2Controller(
+            ILogger<Investigation2Controller> logger,
+            FMSContext context,
+            Investigation2Service service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmInvestigation2> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId) || x.IsAllSite == true);
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation2> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                Investigation2GroupName = x.CmInvestigation2CodeGroup.Name,
+                Investigation2TypeName = x.CmInvestigation2CodeType.Name,
+                x.InvestigationNo,
+                Name = x.IsAllSite ? "[본사] " + x.Name : x.Name,
+                x.InvestigationGroupId,
+                x.InvestigationTypeId,
+                x.InvestigationId,
+                x.IsAllSite
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.InvestigationId,
+                x.Name,
+                x.SiteId,
+                x.InvestigationGroupId,
+                x.InvestigationTypeId,
+                x.InvestigationNo,
+                x.IsAllSite,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                CmInvestigation2CodeGroup = new {
+                    x.CmInvestigation2CodeGroup.Name
+                },
+                CmInvestigation2CodeType = new {
+                    x.CmInvestigation2CodeType.Name
+                },
+                x.Description
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] CmInvestigation2 data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.InvestigationId) {
+                return BadRequest("잘못된 접근입니다.>>");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation2 data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+            if (ModelState.IsValid) {
+                data.CreateUserId = User.Identity.Name;
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+            return Ok();
+        }
+
+    }
+}

+ 178 - 0
Controllers/Investigation2HistoryController.cs

@@ -0,0 +1,178 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using FMSAdmin.Services;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation2HistoryController : Controller {
+        private readonly ILogger<Investigation2HistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly Investigation2HistoryService _service;
+        private readonly StorageHelper _storage;
+
+        public Investigation2HistoryController(
+            ILogger<Investigation2HistoryController> logger,
+            FMSContext context,
+            Investigation2HistoryService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation2History> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.InvestigationId,
+                x.HistoryId,
+                x.RevisionNo,
+                x.FileId,
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.Description,
+                x.UpdatedDate,
+                UpdateUserName = x.CmInvestigation2.IsAllSite ? x.CmUser.CmSite.Name : x.CmUser.Name,
+            });
+            return list;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] string investigationId) {
+
+            var query = _FilterAndSort(req);
+            query = query.Where(x => x.InvestigationId == Util.ToInt(investigationId));
+            if (!Util.IsNullOrEmpty(siteId)) {
+                var currentUserSiteId = _context.CmUser.Where(x => x.UserId == User.Identity.Name).Select(x => x.SiteId).SingleOrDefault();
+                query = query.Where(x => x.CmUser.SiteId == currentUserSiteId || x.CmUser.SiteId == x.CmInvestigation2.SiteId);
+            }
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpDelete("{siteId}/{investigationId}/{historyId}")]
+        public IActionResult Delete(int siteId, int investigationId, int historyId) {
+            _service.Delete(siteId, investigationId, historyId);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmInvestigation2History> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{investigationId}/{historyId}")]
+        public IActionResult Get(int siteId, int investigationId, int historyId) {
+            var data = _service.Get(siteId, investigationId, historyId);
+            if (data.Count() == 0) {
+                return NotFound("정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.SiteId,
+                x.InvestigationId,
+                x.HistoryId,
+                x.RevisionNo,
+                CmUser = new {
+                    x.CmUser.Name,
+                },
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.Description
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{InvestigationId}")]
+        public int GetNextRevisionNo(int siteId, int investigationId) {
+            int nextRevisionNo = _service.GetRevisionNo(siteId, investigationId);
+            return nextRevisionNo;
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation2History data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{investigationId}/{historyId}")]
+        public IActionResult Edit(int siteId, int investigationId, int historyId, [FromBody] CmInvestigation2History data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + historyId);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+            if (historyId != data.HistoryId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                data.UpdatedUserId = User.Identity.Name;
+                _service.Edit(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        // [HttpDelete("{siteId}/{InvestigationId}/{historyId}")]
+        // public IActionResult Delete(int siteId, int id) {
+        //     _service.Delete(id, siteId);
+        //     return Ok();
+        // }
+    }
+}

+ 162 - 0
Controllers/Investigation3CodeGroupController.cs

@@ -0,0 +1,162 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation3CodeGroupController : Controller {
+        private readonly ILogger<Investigation3CodeGroupController> _logger;
+        private readonly FMSContext _context;
+        private Investigation3CodeGroupService _service;
+
+        public Investigation3CodeGroupController(
+            ILogger<Investigation3CodeGroupController> logger,
+            FMSContext context,
+            Investigation3CodeGroupService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmInvestigation3CodeGroup> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation3CodeGroup> query) {
+            var list = query.Select(x => new {
+                x.InvestigationGroupId,
+                x.Name,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N",
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.InvestigationGroupId,
+                x.Name,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] CmInvestigation3CodeGroup data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.InvestigationGroupId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation3CodeGroup data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 162 - 0
Controllers/Investigation3CodeTypeController.cs

@@ -0,0 +1,162 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation3CodeTypeController : Controller {
+        private readonly ILogger<Investigation3CodeTypeController> _logger;
+        private readonly FMSContext _context;
+        private Investigation3CodeTypeService _service;
+
+        public Investigation3CodeTypeController(
+            ILogger<Investigation3CodeTypeController> logger,
+            FMSContext context,
+            Investigation3CodeTypeService service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmInvestigation3CodeType> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation3CodeType> query) {
+            var list = query.Select(x => new {
+                x.InvestigationTypeId,
+                x.Name,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N"
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{id}")]
+        public IActionResult Get(int id) {
+            var data = _service.Get(id);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.InvestigationTypeId,
+                x.Name,
+                x.IsUse
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{id}")]
+        public IActionResult Edit(int id, [FromBody] CmInvestigation3CodeType data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.InvestigationTypeId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation3CodeType data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{id}")]
+        public IActionResult Delete(int id) {
+            _service.Delete(id);
+            return Ok();
+        }
+
+    }
+}

+ 193 - 0
Controllers/Investigation3Controller.cs

@@ -0,0 +1,193 @@
+using System;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation3Controller : Controller {
+        private readonly ILogger<Investigation3Controller> _logger;
+        private readonly FMSContext _context;
+        private Investigation3Service _service;
+
+        public Investigation3Controller(
+            ILogger<Investigation3Controller> logger,
+            FMSContext context,
+            Investigation3Service service
+        ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmInvestigation3> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId) || x.IsAllSite == true);
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+
+            query = query.Sort(req.sort);
+            return query;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation3> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                Investigation3GroupName = x.CmInvestigation3CodeGroup.Name,
+                Investigation3TypeName = x.CmInvestigation3CodeType.Name,
+                x.InvestigationNo,
+                Name = x.IsAllSite ? "[본사] " + x.Name : x.Name,
+                x.InvestigationGroupId,
+                x.InvestigationTypeId,
+                x.InvestigationId,
+                x.IsAllSite
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.InvestigationId,
+                x.Name,
+                x.SiteId,
+                x.InvestigationGroupId,
+                x.InvestigationTypeId,
+                x.InvestigationNo,
+                x.IsAllSite,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                CmInvestigation3CodeGroup = new {
+                    x.CmInvestigation3CodeGroup.Name
+                },
+                CmInvestigation3CodeType = new {
+                    x.CmInvestigation3CodeType.Name
+                },
+                x.Description
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] CmInvestigation3 data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+            if (id != data.InvestigationId) {
+                return BadRequest("잘못된 접근입니다.>>");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation3 data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+            if (ModelState.IsValid) {
+                data.CreateUserId = User.Identity.Name;
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+            return Ok();
+        }
+
+    }
+}

+ 178 - 0
Controllers/Investigation3HistoryController.cs

@@ -0,0 +1,178 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+using FMSAdmin.Services;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class Investigation3HistoryController : Controller {
+        private readonly ILogger<Investigation3HistoryController> _logger;
+        private readonly FMSContext _context;
+        private readonly Investigation3HistoryService _service;
+        private readonly StorageHelper _storage;
+
+        public Investigation3HistoryController(
+            ILogger<Investigation3HistoryController> logger,
+            FMSContext context,
+            Investigation3HistoryService service,
+            StorageHelper storage
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmInvestigation3History> query) {
+            var list = query.Select(x => new {
+                x.SiteId,
+                x.InvestigationId,
+                x.HistoryId,
+                x.RevisionNo,
+                x.FileId,
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.Description,
+                x.UpdatedDate,
+                UpdateUserName = x.CmInvestigation3.IsAllSite ? x.CmUser.CmSite.Name : x.CmUser.Name,
+            });
+            return list;
+        }
+
+        /// <summary>
+        /// 목록
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId, [FromQuery] string investigationId) {
+
+            var query = _FilterAndSort(req);
+            query = query.Where(x => x.InvestigationId == Util.ToInt(investigationId));
+            if (!Util.IsNullOrEmpty(siteId)) {
+                var currentUserSiteId = _context.CmUser.Where(x => x.UserId == User.Identity.Name).Select(x => x.SiteId).SingleOrDefault();
+                query = query.Where(x => x.CmUser.SiteId == currentUserSiteId || x.CmUser.SiteId == x.CmInvestigation3.SiteId);
+            }
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        [HttpDelete("{siteId}/{investigationId}/{historyId}")]
+        public IActionResult Delete(int siteId, int investigationId, int historyId) {
+            _service.Delete(siteId, investigationId, historyId);
+            return Ok();
+        }
+
+        // 검색 & 정렬 공통
+        private IQueryable<CmInvestigation3History> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{investigationId}/{historyId}")]
+        public IActionResult Get(int siteId, int investigationId, int historyId) {
+            var data = _service.Get(siteId, investigationId, historyId);
+            if (data.Count() == 0) {
+                return NotFound("정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.SiteId,
+                x.InvestigationId,
+                x.HistoryId,
+                x.RevisionNo,
+                CmUser = new {
+                    x.CmUser.Name,
+                },
+                CmFile = _storage.ParseFile(x.CmFile),
+                x.Description
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{InvestigationId}")]
+        public int GetNextRevisionNo(int siteId, int investigationId) {
+            int nextRevisionNo = _service.GetRevisionNo(siteId, investigationId);
+            return nextRevisionNo;
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmInvestigation3History data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{investigationId}/{historyId}")]
+        public IActionResult Edit(int siteId, int investigationId, int historyId, [FromBody] CmInvestigation3History data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + historyId);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+            if (historyId != data.HistoryId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                data.UpdatedUserId = User.Identity.Name;
+                _service.Edit(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        // [HttpDelete("{siteId}/{InvestigationId}/{historyId}")]
+        // public IActionResult Delete(int siteId, int id) {
+        //     _service.Delete(id, siteId);
+        //     return Ok();
+        // }
+    }
+}

+ 193 - 0
Controllers/LayoutController.cs

@@ -0,0 +1,193 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using FMSAdmin.Data;
+using FMSAdmin.Entities;
+using FMSAdmin.Helpers;
+using FMSAdmin.Models;
+using FMSAdmin.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class LayoutController : Controller {
+        private readonly ILogger<LayoutController> _logger;
+        private readonly FMSContext _context;
+        private LayoutService _service;
+        private readonly StorageHelper _storage; public LayoutController(
+           ILogger<LayoutController> logger,
+           FMSContext context,
+           LayoutService service,
+            StorageHelper storage
+       ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+            _storage = storage;
+        }
+
+        /// <summary>
+        /// /// 검색 & 정렬
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmLayout> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+            query = query.Filter(req.conditions);
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == Util.ToInt(req.siteId));
+            }
+            if (req.siteIds != null && req.siteIds.Length > 0) {
+                query = query.Where(x => req.siteIds.Contains(x.SiteId));
+            }
+            // 고정정렬 (월->사옥 준공순)
+
+            var sortQuery = query.OrderByDescending(x => x.Year);
+            sortQuery = sortQuery.ThenByDescending(x => x.Month);
+            sortQuery = sortQuery.ThenBy(x => x.CmSite.SortOrderNo);
+            sortQuery = sortQuery.ThenSort(req.sort);
+            return sortQuery;
+        }
+
+
+        /// <summary>
+        /// 셀렉트 공통
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmLayout> query) {
+            var list = query.Select(x => new {
+                x.LayoutId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.Year,
+                x.Month,
+                Title = x.Year.ToString() + "년 " + x.Month.ToString() + "월 레이아웃현황",
+                FilePath = _storage.getFilePath(x.CmFile),
+                FileName = x.CmFile.Name,
+                FilePath2 = _storage.getFilePath(x.CmFile2),
+                FileName2 = x.CmFile2.Name
+            });
+            return list;
+        }
+        /// <summary>
+        /// 목록
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="siteId"></param>
+        /// <returns></returns>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+
+            return Ok(paging);
+        }
+
+        // 엑셀 다운로드
+        [AllowAnonymous]
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            var query = _FilterAndSort(req);
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"excel-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{id}")]
+        public IActionResult Get(int siteId, int id) {
+            var data = _service.Get(id, siteId);
+            if (data.Count() == 0) {
+                return NotFound("사이트 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.LayoutId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                x.Year,
+                x.Month,
+                CmUser = new { x.CmUser.Name },
+                CmFile = _storage.ParseFile(x.CmFile),
+                CmFile2 = _storage.ParseFile(x.CmFile2),
+            });
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 수정
+        /// </summary>
+        [HttpPost("{siteId}/{id}")]
+        public IActionResult Edit(int siteId, int id, [FromBody] CmLayout data) {
+            _logger.LogInformation("Edit");
+            _logger.LogInformation("id:" + id);
+            _logger.LogInformation("siteId:" + siteId);
+            //_logger.LogInformation(data.Dump());
+
+            if (id != data.LayoutId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+            ModelState.Remove("RegisterUserId");
+            if (ModelState.IsValid) {
+                data.UpdateUserId = User.Identity.Name;
+                _service.Edit(id, siteId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 등록
+        /// </summary>
+        [HttpPut]
+        public IActionResult Create([FromBody] CmLayout data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+            ModelState.Remove("RegisterUserId");
+            if (ModelState.IsValid) {
+                data.RegisterUserId = User.Identity.Name;
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제
+        /// </summary>
+        [HttpDelete("{siteId}/{id}")]
+        public IActionResult Delete(int siteId, int id) {
+            _service.Delete(id, siteId);
+            return Ok();
+        }
+
+    }
+}

+ 233 - 0
Controllers/LegalCheckPartnerController.cs

@@ -0,0 +1,233 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FMSAdmin.Data;
+using FMSAdmin.Models;
+using FMSAdmin.Entities;
+using FMSAdmin.Services;
+
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.EntityFrameworkCore;
+using FMSAdmin.Helpers;
+using System.Data;
+using System.IO;
+using OfficeOpenXml;
+
+namespace FMSAdmin.Controllers {
+    [Authorize]
+    [ApiController]
+    [ApiVersion("1")]
+    [Route("api/[controller]")]
+    public class LegalCheckPartnerController : Controller {
+        private readonly ILogger<LegalCheckPartnerController> _logger;
+        private readonly FMSContext _context;
+        private readonly LegalCheckPartnerService _service;
+
+        public LegalCheckPartnerController(
+            ILogger<LegalCheckPartnerController> logger,
+            FMSContext context,
+            LegalCheckPartnerService service
+            ) {
+            _logger = logger;
+            _context = context;
+            _service = service;
+        }
+
+        /// <summary>
+        /// 목록(List)
+        /// </summary>
+        [HttpGet]
+        public IActionResult List([FromQuery] PagingRequest req, [FromQuery] string siteId) {
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var paging = PagingList.Pagination(list, req.page, req.limit);
+            return Ok(paging);
+        }
+
+        /// <summary>
+        /// 조회
+        /// </summary>
+        [HttpGet("{siteId}/{partnerId}")]
+        public IActionResult Get(int siteId, int partnerId) {
+            var data = _service.Get(siteId, partnerId);
+            if (data.Count() == 0) {
+                return NotFound("법정 검사 기관 정보를 찾을 수 없습니다.");
+            }
+            var list = data.Select(x => new {
+                x.PartnerId,
+                x.SiteId,
+                CmSite = new {
+                    x.CmSite.Name,
+                },
+                SiteName = x.CmSite.Name,
+                x.Name,
+                x.PartnerTypeId,
+                CmPartnerType = new {
+                    x.PartnerType.Name,
+                },
+                x.BusinessFieldId,
+                CmBusinessField = new {
+                    x.BusinessField.Name,
+                },
+                x.RegistrationNo,
+                x.RepresentativeName,
+                x.PhoneNo,
+                x.FaxNo,
+                x.SalesName,
+                x.SalesPhoneNo,
+                x.AddressZip1,
+                x.AddressZip2,
+                x.Address1,
+                x.Adderss2,
+                x.HomepageUri,
+                x.Note,
+                x.IsUse,
+            });
+
+            return Ok(list.First());
+        }
+
+        /// <summary>
+        /// 엑셀파일 익스포트
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpGet("excel")]
+        public IActionResult ExportExcel([FromQuery] PagingRequest req, string filename) {
+            req.page = 1;
+            req.limit = 0;
+            var query = _FilterAndSort(req);
+
+            var list = _ListSelect(query);
+
+            var stream = new MemoryStream();
+            using (var package = new ExcelPackage(stream)) {
+                var workSheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                workSheet.Cells.LoadFromCollection(list, true);
+
+                if (req.columns != null && req.columns.Length > 0) {
+                    workSheet.AddStyle(req.columns, req.sort?.order?.ToLower() == "asc", filename);
+                }
+                package.Save();
+            }
+            stream.Position = 0;
+            string excelName = $"법정검사기관관리-{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.xlsx";
+            return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelName);
+        }
+
+        /// <summary>
+        /// 수정(Edit)
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost("{siteId}/{partnerId}")]
+        public IActionResult Edit(int siteId, int partnerId, [FromBody] CmPartner data) {
+            if (partnerId != data.PartnerId) {
+                return BadRequest("잘못된 접근입니다.");
+            }
+
+            if (ModelState.IsValid) {
+                _service.Edit(siteId, partnerId, data);
+            } else {
+                return BadRequest(ModelState);
+            }
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 신규(Creae)
+        /// </summary>
+        /// <param name="data"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public IActionResult Create([FromBody] CmPartner data) {
+            _logger.LogInformation("Create");
+            //_logger.LogInformation(data.Dump());
+
+            if (ModelState.IsValid) {
+                _service.Create(data);
+            } else {
+                return BadRequest(ModelState);
+            }
+            return Ok();
+        }
+
+        /// <summary>
+        /// 삭제(Delete)
+        /// </summary>
+        [HttpDelete("{siteId}/{partnerId}")]
+        public IActionResult Delete(int siteId, int partnerId) {
+            _service.Delete(siteId, partnerId);
+
+            return Ok();
+        }
+
+        /// <summary>
+        /// 셀렉트 공통(_ListSelect)
+        /// </summary>
+        /// <param name="query"></param>
+        /// <returns></returns>
+        private dynamic _ListSelect(IQueryable<CmPartner> query) {
+            var list = query.Select(x => new {
+                x.PartnerId,
+                x.SiteId,
+                SiteName = x.CmSite.Name,
+                x.Name,
+                x.BusinessFieldId,
+                BusinessFieldName = x.BusinessField.Name,
+                x.PartnerTypeId,
+                PartnerTypeName = x.PartnerType.Name,
+                x.RegistrationNo,
+                x.RepresentativeName,
+                x.PhoneNo,
+                x.FaxNo,
+                x.SalesName,
+                x.SalesPhoneNo,
+                x.AddressZip1,
+                x.AddressZip2,
+                x.Address1,
+                x.Adderss2,
+                x.HomepageUri,
+                x.Note,
+                IsUse = x.IsUse.GetValueOrDefault() ? "Y" : "N",
+            });
+            return list;
+        }
+
+        /// <summary>
+        /// 검색 & 정렬(_FilterAndSort)
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        private IQueryable<CmPartner> _FilterAndSort(PagingRequest req) {
+            var query = _service.GetAll();
+
+            // 기본 Entity 검색
+            query = query.Filter(req.conditions);
+
+            if (req.siteId != null) {
+                query = query.Where(x => x.SiteId == req.siteId);
+            }
+
+
+            // 업무분야 필드가 있는경우만 추가
+            if (req.businessFieldId != null) {
+                query = query.Where(x => x.SiteId == req.siteId);
+                query = query.Where(x => x.BusinessFieldId == req.businessFieldId || x.BusinessField.Name == "공통");
+            }
+
+            // 기본 Entity 정렬
+            query = query.Sort(req.sort);
+
+            return query;
+        }
+
+    }
+}

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików