|
|
@@ -1,10 +1,13 @@
|
|
|
using Common;
|
|
|
-using ClosedXML.Excel;
|
|
|
using Database.Database;
|
|
|
using Kitty_Fall.Cms.Helpers;
|
|
|
using Kitty_Fall.Cms.Models;
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
+using System.Globalization;
|
|
|
+using System.IO.Compression;
|
|
|
+using System.Text;
|
|
|
+using System.Xml;
|
|
|
|
|
|
namespace Kitty_Fall.Cms.Controllers;
|
|
|
|
|
|
@@ -113,63 +116,19 @@ public class MonthlyWinnerController : CmsAuthorizedController
|
|
|
.AsNoTracking()
|
|
|
.Where(x => x.MonthKey == normalizedMonthKey)
|
|
|
.OrderBy(x => x.TicketNo)
|
|
|
- .Select(x => new
|
|
|
+ .Select(x => new TicketExportRow
|
|
|
{
|
|
|
- x.TicketNo,
|
|
|
- x.Msisdn,
|
|
|
- x.TotalScore,
|
|
|
- x.CreatedTime
|
|
|
+ TicketNo = x.TicketNo,
|
|
|
+ Msisdn = x.Msisdn,
|
|
|
+ TotalScore = x.TotalScore,
|
|
|
+ CreatedTime = x.CreatedTime
|
|
|
})
|
|
|
.ToListAsync();
|
|
|
|
|
|
- using var workbook = new XLWorkbook();
|
|
|
- var worksheet = workbook.Worksheets.Add("Tickets");
|
|
|
- var headers = new[] { "No.", "Ticket No.", "MSISDN", "Total Score", "Created Time" };
|
|
|
- for (var column = 0; column < headers.Length; column++)
|
|
|
- {
|
|
|
- worksheet.Cell(1, column + 1).Value = headers[column];
|
|
|
- }
|
|
|
-
|
|
|
- for (var index = 0; index < tickets.Count; index++)
|
|
|
- {
|
|
|
- var ticket = tickets[index];
|
|
|
- var row = index + 2;
|
|
|
- worksheet.Cell(row, 1).Value = index + 1;
|
|
|
- worksheet.Cell(row, 2).Value = ticket.TicketNo;
|
|
|
- worksheet.Cell(row, 3).Value = ticket.Msisdn;
|
|
|
- worksheet.Cell(row, 4).Value = ticket.TotalScore;
|
|
|
- worksheet.Cell(row, 5).Value = ticket.CreatedTime;
|
|
|
- }
|
|
|
-
|
|
|
- var lastRow = Math.Max(1, tickets.Count + 1);
|
|
|
- var tableRange = worksheet.Range(1, 1, lastRow, headers.Length);
|
|
|
- tableRange.Style.Border.InsideBorder = XLBorderStyleValues.Thin;
|
|
|
- tableRange.Style.Border.OutsideBorder = XLBorderStyleValues.Thin;
|
|
|
- tableRange.Style.Border.InsideBorderColor = XLColor.LightGray;
|
|
|
- tableRange.Style.Border.OutsideBorderColor = XLColor.LightGray;
|
|
|
-
|
|
|
- var headerRange = worksheet.Range(1, 1, 1, headers.Length);
|
|
|
- headerRange.Style.Font.Bold = true;
|
|
|
- headerRange.Style.Font.FontColor = XLColor.White;
|
|
|
- headerRange.Style.Fill.BackgroundColor = XLColor.FromHtml("#6D28D9");
|
|
|
- headerRange.Style.Alignment.Horizontal = XLAlignmentHorizontalValues.Center;
|
|
|
-
|
|
|
- worksheet.Column(1).Style.NumberFormat.Format = "#,##0";
|
|
|
- worksheet.Column(2).Style.NumberFormat.Format = "#,##0";
|
|
|
- worksheet.Column(3).Style.NumberFormat.Format = "@";
|
|
|
- worksheet.Column(4).Style.NumberFormat.Format = "#,##0";
|
|
|
- worksheet.Column(5).Style.DateFormat.Format = "yyyy-mm-dd hh:mm:ss";
|
|
|
- worksheet.SheetView.FreezeRows(1);
|
|
|
- worksheet.RangeUsed()?.SetAutoFilter();
|
|
|
- worksheet.Columns().AdjustToContents();
|
|
|
- worksheet.Column(3).Width = Math.Max(worksheet.Column(3).Width, 18);
|
|
|
- worksheet.Column(5).Width = Math.Max(worksheet.Column(5).Width, 21);
|
|
|
-
|
|
|
- using var stream = new MemoryStream();
|
|
|
- workbook.SaveAs(stream);
|
|
|
+ var content = BuildTicketWorkbook(tickets);
|
|
|
var fileName = $"monthly-tickets-{normalizedMonthKey}.xlsx";
|
|
|
return File(
|
|
|
- stream.ToArray(),
|
|
|
+ content,
|
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
|
fileName);
|
|
|
}
|
|
|
@@ -234,6 +193,10 @@ public class MonthlyWinnerController : CmsAuthorizedController
|
|
|
{
|
|
|
return Json(new { errorCode = 404, message = "Winner not found." });
|
|
|
}
|
|
|
+ if (winner.Status == 2)
|
|
|
+ {
|
|
|
+ return Json(new { errorCode = 409, message = "A paid winner cannot be edited." });
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
if (winner == null)
|
|
|
@@ -281,6 +244,10 @@ public class MonthlyWinnerController : CmsAuthorizedController
|
|
|
{
|
|
|
return Json(new { errorCode = 404, message = "Winner not found." });
|
|
|
}
|
|
|
+ if (winner.Status == 2)
|
|
|
+ {
|
|
|
+ return Json(new { errorCode = 409, message = "A paid winner cannot be changed." });
|
|
|
+ }
|
|
|
|
|
|
var now = DateTime.Now;
|
|
|
winner.Status = NormalizeStatus(status);
|
|
|
@@ -291,6 +258,137 @@ public class MonthlyWinnerController : CmsAuthorizedController
|
|
|
return Json(new { errorCode = 200, message = "Success" });
|
|
|
}
|
|
|
|
|
|
+ private static byte[] BuildTicketWorkbook(IReadOnlyList<TicketExportRow> tickets)
|
|
|
+ {
|
|
|
+ using var output = new MemoryStream();
|
|
|
+ using (var archive = new ZipArchive(output, ZipArchiveMode.Create, true))
|
|
|
+ {
|
|
|
+ WriteZipEntry(archive, "[Content_Types].xml", """
|
|
|
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
|
+ <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
|
+ <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
|
|
+ <Default Extension="xml" ContentType="application/xml"/>
|
|
|
+ <Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
|
|
|
+ <Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
|
|
|
+ <Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
|
|
|
+ </Types>
|
|
|
+ """);
|
|
|
+ WriteZipEntry(archive, "_rels/.rels", """
|
|
|
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
|
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
|
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
|
|
|
+ </Relationships>
|
|
|
+ """);
|
|
|
+ WriteZipEntry(archive, "xl/workbook.xml", """
|
|
|
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
|
+ <workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
|
+ <sheets><sheet name="Tickets" sheetId="1" r:id="rId1"/></sheets>
|
|
|
+ </workbook>
|
|
|
+ """);
|
|
|
+ WriteZipEntry(archive, "xl/_rels/workbook.xml.rels", """
|
|
|
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
|
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
|
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>
|
|
|
+ <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
|
|
|
+ </Relationships>
|
|
|
+ """);
|
|
|
+ WriteZipEntry(archive, "xl/styles.xml", """
|
|
|
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
|
+ <styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
|
|
+ <numFmts count="1"><numFmt numFmtId="164" formatCode="yyyy-mm-dd hh:mm:ss"/></numFmts>
|
|
|
+ <fonts count="2"><font/><font><b/><color rgb="FFFFFFFF"/></font></fonts>
|
|
|
+ <fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="solid"><fgColor rgb="FF6D28D9"/><bgColor indexed="64"/></patternFill></fill></fills>
|
|
|
+ <borders count="1"><border/></borders>
|
|
|
+ <cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
|
|
|
+ <cellXfs count="3"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/><xf numFmtId="0" fontId="1" fillId="1" borderId="0" xfId="0" applyFont="1" applyFill="1" applyAlignment="1"><alignment horizontal="center"/></xf><xf numFmtId="164" fontId="0" fillId="0" borderId="0" xfId="0" applyNumberFormat="1"/></cellXfs>
|
|
|
+ <cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>
|
|
|
+ </styleSheet>
|
|
|
+ """);
|
|
|
+
|
|
|
+ var sheetEntry = archive.CreateEntry("xl/worksheets/sheet1.xml", CompressionLevel.Fastest);
|
|
|
+ using var sheetStream = sheetEntry.Open();
|
|
|
+ using var writer = XmlWriter.Create(sheetStream, new XmlWriterSettings { Encoding = new UTF8Encoding(false), Indent = false });
|
|
|
+ writer.WriteStartDocument(true);
|
|
|
+ writer.WriteStartElement("worksheet", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
|
|
|
+ writer.WriteStartElement("sheetViews");
|
|
|
+ writer.WriteStartElement("sheetView");
|
|
|
+ writer.WriteAttributeString("workbookViewId", "0");
|
|
|
+ writer.WriteStartElement("pane");
|
|
|
+ writer.WriteAttributeString("ySplit", "1");
|
|
|
+ writer.WriteAttributeString("topLeftCell", "A2");
|
|
|
+ writer.WriteAttributeString("activePane", "bottomLeft");
|
|
|
+ writer.WriteAttributeString("state", "frozen");
|
|
|
+ writer.WriteEndElement();
|
|
|
+ writer.WriteEndElement();
|
|
|
+ writer.WriteEndElement();
|
|
|
+ writer.WriteStartElement("cols");
|
|
|
+ WriteColumn(writer, 1, 1, 8); WriteColumn(writer, 2, 2, 13); WriteColumn(writer, 3, 3, 20); WriteColumn(writer, 4, 4, 15); WriteColumn(writer, 5, 5, 22);
|
|
|
+ writer.WriteEndElement();
|
|
|
+ writer.WriteStartElement("sheetData");
|
|
|
+ writer.WriteStartElement("row"); writer.WriteAttributeString("r", "1");
|
|
|
+ var headers = new[] { "No.", "Ticket No.", "MSISDN", "Total Score", "Created Time" };
|
|
|
+ for (var i = 0; i < headers.Length; i++) WriteInlineCell(writer, $"{(char)('A' + i)}1", headers[i], 1);
|
|
|
+ writer.WriteEndElement();
|
|
|
+ for (var i = 0; i < tickets.Count; i++)
|
|
|
+ {
|
|
|
+ var rowNumber = i + 2;
|
|
|
+ var ticket = tickets[i];
|
|
|
+ writer.WriteStartElement("row"); writer.WriteAttributeString("r", rowNumber.ToString(CultureInfo.InvariantCulture));
|
|
|
+ WriteNumberCell(writer, $"A{rowNumber}", i + 1);
|
|
|
+ WriteNumberCell(writer, $"B{rowNumber}", ticket.TicketNo);
|
|
|
+ WriteInlineCell(writer, $"C{rowNumber}", ticket.Msisdn, 0);
|
|
|
+ WriteNumberCell(writer, $"D{rowNumber}", ticket.TotalScore);
|
|
|
+ WriteNumberCell(writer, $"E{rowNumber}", ticket.CreatedTime.ToOADate(), 2);
|
|
|
+ writer.WriteEndElement();
|
|
|
+ }
|
|
|
+ writer.WriteEndElement();
|
|
|
+ writer.WriteStartElement("autoFilter"); writer.WriteAttributeString("ref", $"A1:E{Math.Max(1, tickets.Count + 1)}"); writer.WriteEndElement();
|
|
|
+ writer.WriteEndElement();
|
|
|
+ writer.WriteEndDocument();
|
|
|
+ }
|
|
|
+ return output.ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void WriteZipEntry(ZipArchive archive, string path, string content)
|
|
|
+ {
|
|
|
+ var entry = archive.CreateEntry(path, CompressionLevel.Fastest);
|
|
|
+ using var stream = entry.Open();
|
|
|
+ using var writer = new StreamWriter(stream, new UTF8Encoding(false));
|
|
|
+ writer.Write(content);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void WriteColumn(XmlWriter writer, int min, int max, double width)
|
|
|
+ {
|
|
|
+ writer.WriteStartElement("col");
|
|
|
+ writer.WriteAttributeString("min", min.ToString(CultureInfo.InvariantCulture));
|
|
|
+ writer.WriteAttributeString("max", max.ToString(CultureInfo.InvariantCulture));
|
|
|
+ writer.WriteAttributeString("width", width.ToString(CultureInfo.InvariantCulture));
|
|
|
+ writer.WriteAttributeString("customWidth", "1");
|
|
|
+ writer.WriteEndElement();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void WriteInlineCell(XmlWriter writer, string reference, string value, int style)
|
|
|
+ {
|
|
|
+ writer.WriteStartElement("c"); writer.WriteAttributeString("r", reference); writer.WriteAttributeString("t", "inlineStr"); writer.WriteAttributeString("s", style.ToString(CultureInfo.InvariantCulture));
|
|
|
+ writer.WriteStartElement("is"); writer.WriteElementString("t", value ?? string.Empty); writer.WriteEndElement();
|
|
|
+ writer.WriteEndElement();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void WriteNumberCell(XmlWriter writer, string reference, double value, int style = 0)
|
|
|
+ {
|
|
|
+ writer.WriteStartElement("c"); writer.WriteAttributeString("r", reference); writer.WriteAttributeString("s", style.ToString(CultureInfo.InvariantCulture));
|
|
|
+ writer.WriteElementString("v", value.ToString(CultureInfo.InvariantCulture));
|
|
|
+ writer.WriteEndElement();
|
|
|
+ }
|
|
|
+
|
|
|
+ private sealed class TicketExportRow
|
|
|
+ {
|
|
|
+ public long TicketNo { get; init; }
|
|
|
+ public string Msisdn { get; init; } = string.Empty;
|
|
|
+ public long TotalScore { get; init; }
|
|
|
+ public DateTime CreatedTime { get; init; }
|
|
|
+ }
|
|
|
+
|
|
|
private async Task<GameMonthlyDraw> EnsureDrawAsync(int monthKey)
|
|
|
{
|
|
|
var draw = await _db.GameMonthlyDraws.FirstOrDefaultAsync(x => x.MonthKey == monthKey);
|