improvement: 기기 엑셀 내보내기 API 추가 (#241) #242
|
|
@ -92,6 +92,17 @@ public class DeviceController : ControllerBase
|
||||||
return Ok(ApiResponse<DeviceListResponseDto>.Success(result));
|
return Ok(ApiResponse<DeviceListResponseDto>.Success(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("export")]
|
||||||
|
[Authorize]
|
||||||
|
[SwaggerOperation(Summary = "기기 엑셀 내보내기", Description = "목록 필터와 동일 조건으로 기기 목록을 엑셀(.xlsx)로 내보냅니다. JWT 인증 필요.")]
|
||||||
|
public async Task<IActionResult> ExportAsync([FromBody] DeviceExportRequestDto request)
|
||||||
|
{
|
||||||
|
var serviceId = GetOptionalServiceId();
|
||||||
|
var fileBytes = await _deviceService.ExportAsync(serviceId, request);
|
||||||
|
var fileName = $"device_export_{DateTime.UtcNow:yyyyMMdd}.xlsx";
|
||||||
|
return File(fileBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
|
||||||
|
}
|
||||||
|
|
||||||
private long GetServiceId()
|
private long GetServiceId()
|
||||||
{
|
{
|
||||||
if (HttpContext.Items.TryGetValue("ServiceId", out var serviceIdObj) && serviceIdObj is long serviceId)
|
if (HttpContext.Items.TryGetValue("ServiceId", out var serviceIdObj) && serviceIdObj is long serviceId)
|
||||||
|
|
|
||||||
24
SPMS.Application/DTOs/Device/DeviceExportRequestDto.cs
Normal file
24
SPMS.Application/DTOs/Device/DeviceExportRequestDto.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace SPMS.Application.DTOs.Device;
|
||||||
|
|
||||||
|
public class DeviceExportRequestDto
|
||||||
|
{
|
||||||
|
[JsonPropertyName("platform")]
|
||||||
|
public string? Platform { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("push_agreed")]
|
||||||
|
public bool? PushAgreed { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("tags")]
|
||||||
|
public List<int>? Tags { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("is_active")]
|
||||||
|
public bool? IsActive { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("keyword")]
|
||||||
|
public string? Keyword { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("marketing_agreed")]
|
||||||
|
public bool? MarketingAgreed { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ public interface IDeviceService
|
||||||
Task DeleteAsync(long serviceId, DeviceDeleteRequestDto request);
|
Task DeleteAsync(long serviceId, DeviceDeleteRequestDto request);
|
||||||
Task AdminDeleteAsync(long deviceId);
|
Task AdminDeleteAsync(long deviceId);
|
||||||
Task<DeviceListResponseDto> GetListAsync(long? serviceId, DeviceListRequestDto request);
|
Task<DeviceListResponseDto> GetListAsync(long? serviceId, DeviceListRequestDto request);
|
||||||
|
Task<byte[]> ExportAsync(long? serviceId, DeviceExportRequestDto request);
|
||||||
Task SetTagsAsync(long serviceId, DeviceTagsRequestDto request);
|
Task SetTagsAsync(long serviceId, DeviceTagsRequestDto request);
|
||||||
Task SetAgreeAsync(long serviceId, DeviceAgreeRequestDto request);
|
Task SetAgreeAsync(long serviceId, DeviceAgreeRequestDto request);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,61 @@ public class DeviceService : IDeviceService
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<byte[]> ExportAsync(long? serviceId, DeviceExportRequestDto request)
|
||||||
|
{
|
||||||
|
Platform? platform = null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.Platform))
|
||||||
|
platform = ParsePlatform(request.Platform);
|
||||||
|
|
||||||
|
var items = await _deviceRepository.GetAllFilteredAsync(
|
||||||
|
serviceId, platform, request.PushAgreed, request.IsActive,
|
||||||
|
request.Tags, request.Keyword, request.MarketingAgreed);
|
||||||
|
|
||||||
|
using var workbook = new ClosedXML.Excel.XLWorkbook();
|
||||||
|
var ws = workbook.Worksheets.Add("기기 목록");
|
||||||
|
|
||||||
|
ws.Cell(1, 1).Value = "Device ID";
|
||||||
|
ws.Cell(1, 2).Value = "Device Token";
|
||||||
|
ws.Cell(1, 3).Value = "서비스명";
|
||||||
|
ws.Cell(1, 4).Value = "서비스코드";
|
||||||
|
ws.Cell(1, 5).Value = "플랫폼";
|
||||||
|
ws.Cell(1, 6).Value = "모델";
|
||||||
|
ws.Cell(1, 7).Value = "OS 버전";
|
||||||
|
ws.Cell(1, 8).Value = "앱 버전";
|
||||||
|
ws.Cell(1, 9).Value = "푸시 동의";
|
||||||
|
ws.Cell(1, 10).Value = "광고 동의";
|
||||||
|
ws.Cell(1, 11).Value = "활성 상태";
|
||||||
|
ws.Cell(1, 12).Value = "태그";
|
||||||
|
ws.Cell(1, 13).Value = "등록일";
|
||||||
|
ws.Cell(1, 14).Value = "마지막 활동";
|
||||||
|
ws.Row(1).Style.Font.Bold = true;
|
||||||
|
|
||||||
|
var row = 2;
|
||||||
|
foreach (var d in items)
|
||||||
|
{
|
||||||
|
ws.Cell(row, 1).Value = d.Id;
|
||||||
|
ws.Cell(row, 2).Value = d.DeviceToken;
|
||||||
|
ws.Cell(row, 3).Value = d.Service?.ServiceName ?? string.Empty;
|
||||||
|
ws.Cell(row, 4).Value = d.Service?.ServiceCode ?? string.Empty;
|
||||||
|
ws.Cell(row, 5).Value = d.Platform.ToString().ToLowerInvariant();
|
||||||
|
ws.Cell(row, 6).Value = d.DeviceModel ?? string.Empty;
|
||||||
|
ws.Cell(row, 7).Value = d.OsVersion ?? string.Empty;
|
||||||
|
ws.Cell(row, 8).Value = d.AppVersion ?? string.Empty;
|
||||||
|
ws.Cell(row, 9).Value = d.PushAgreed ? "Y" : "N";
|
||||||
|
ws.Cell(row, 10).Value = d.MarketingAgreed ? "Y" : "N";
|
||||||
|
ws.Cell(row, 11).Value = d.IsActive ? "활성" : "비활성";
|
||||||
|
ws.Cell(row, 12).Value = d.Tags ?? string.Empty;
|
||||||
|
ws.Cell(row, 13).Value = d.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
|
ws.Cell(row, 14).Value = d.UpdatedAt?.ToString("yyyy-MM-dd HH:mm:ss") ?? string.Empty;
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
ws.Columns().AdjustToContents();
|
||||||
|
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
workbook.SaveAs(stream);
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SetTagsAsync(long serviceId, DeviceTagsRequestDto request)
|
public async Task SetTagsAsync(long serviceId, DeviceTagsRequestDto request)
|
||||||
{
|
{
|
||||||
var device = await _deviceRepository.GetByIdAndServiceAsync(request.DeviceId, serviceId);
|
var device = await _deviceRepository.GetByIdAndServiceAsync(request.DeviceId, serviceId);
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,9 @@ public interface IDeviceRepository : IRepository<Device>
|
||||||
Platform? platform = null, bool? pushAgreed = null,
|
Platform? platform = null, bool? pushAgreed = null,
|
||||||
bool? isActive = null, List<int>? tags = null,
|
bool? isActive = null, List<int>? tags = null,
|
||||||
string? keyword = null, bool? marketingAgreed = null);
|
string? keyword = null, bool? marketingAgreed = null);
|
||||||
|
Task<IReadOnlyList<Device>> GetAllFilteredAsync(
|
||||||
|
long? serviceId,
|
||||||
|
Platform? platform = null, bool? pushAgreed = null,
|
||||||
|
bool? isActive = null, List<int>? tags = null,
|
||||||
|
string? keyword = null, bool? marketingAgreed = null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,4 +82,50 @@ public class DeviceRepository : Repository<Device>, IDeviceRepository
|
||||||
|
|
||||||
return (items, totalCount);
|
return (items, totalCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<Device>> GetAllFilteredAsync(
|
||||||
|
long? serviceId,
|
||||||
|
Platform? platform = null, bool? pushAgreed = null,
|
||||||
|
bool? isActive = null, List<int>? tags = null,
|
||||||
|
string? keyword = null, bool? marketingAgreed = null)
|
||||||
|
{
|
||||||
|
IQueryable<Device> query = _dbSet.Include(d => d.Service);
|
||||||
|
|
||||||
|
if (serviceId.HasValue)
|
||||||
|
query = query.Where(d => d.ServiceId == serviceId.Value);
|
||||||
|
|
||||||
|
if (platform.HasValue)
|
||||||
|
query = query.Where(d => d.Platform == platform.Value);
|
||||||
|
|
||||||
|
if (pushAgreed.HasValue)
|
||||||
|
query = query.Where(d => d.PushAgreed == pushAgreed.Value);
|
||||||
|
|
||||||
|
if (isActive.HasValue)
|
||||||
|
query = query.Where(d => d.IsActive == isActive.Value);
|
||||||
|
|
||||||
|
if (marketingAgreed.HasValue)
|
||||||
|
query = query.Where(d => d.MarketingAgreed == marketingAgreed.Value);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(keyword))
|
||||||
|
{
|
||||||
|
var trimmed = keyword.Trim();
|
||||||
|
if (long.TryParse(trimmed, out var deviceId))
|
||||||
|
query = query.Where(d => d.Id == deviceId || d.DeviceToken.Contains(trimmed));
|
||||||
|
else
|
||||||
|
query = query.Where(d => d.DeviceToken.Contains(trimmed));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tags != null && tags.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var tag in tags)
|
||||||
|
{
|
||||||
|
var tagStr = tag.ToString();
|
||||||
|
query = query.Where(d => d.Tags != null && EF.Functions.Like(d.Tags, $"%{tagStr}%"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await query
|
||||||
|
.OrderByDescending(d => d.CreatedAt)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user