improvement: 기기 엑셀 내보내기 API 추가 (#241) #242
|
|
@ -92,6 +92,17 @@ public class DeviceController : ControllerBase
|
|||
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()
|
||||
{
|
||||
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 AdminDeleteAsync(long deviceId);
|
||||
Task<DeviceListResponseDto> GetListAsync(long? serviceId, DeviceListRequestDto request);
|
||||
Task<byte[]> ExportAsync(long? serviceId, DeviceExportRequestDto request);
|
||||
Task SetTagsAsync(long serviceId, DeviceTagsRequestDto 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)
|
||||
{
|
||||
var device = await _deviceRepository.GetByIdAndServiceAsync(request.DeviceId, serviceId);
|
||||
|
|
|
|||
|
|
@ -14,4 +14,9 @@ public interface IDeviceRepository : IRepository<Device>
|
|||
Platform? platform = null, bool? pushAgreed = null,
|
||||
bool? isActive = null, List<int>? tags = 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);
|
||||
}
|
||||
|
||||
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