student 1 тиждень тому
батько
коміт
b4afdceb47

+ 99 - 1
KittyFallWs/KittyFallWs/src/com/vas/webservices/KittyFallWs.java

@@ -2673,7 +2673,105 @@ public class KittyFallWs extends WebserviceAbstract {
         return response;
     }
     
-    
+    @WebMethod(operationName = "wsAddedRewardCosts")
+    @SOAPBinding(style = SOAPBinding.Style.DOCUMENT, use = SOAPBinding.Use.LITERAL)
+    public Response wsAddedRewardCosts(
+            @WebParam(name = "WsUser") String wsUser,
+            @WebParam(name = "WsPass") String wsPass,
+            @WebParam(name = "Msisdn") String msisdn,
+            @WebParam(name = "PackageName") String packageName,
+            @WebParam(name = "Expense") String expense,
+            @WebParam(name = "ServiceId") String serviceId) {
+
+        Response response = new Response();
+        response.setErrorCode(SUCCESS);
+
+        try {
+            String ip = getIpClient();
+            UserInfo userInfo = null;
+            br.setLength(0);
+            br.append("wsAddPoint Params:\n").
+                    append("WsUser:").append(wsUser).
+                    append("\n Msisdn:").append(msisdn).
+                    append("\n PackageName:").append(packageName).
+                    append("\n Msisdn:").append(msisdn).
+                    append("\n ServiceId:").append(serviceId).
+                    append("\n expense:").append(expense);
+            logger.info(br);
+
+            Request request = new Request();
+            request.setUser(wsUser);
+//            request.setPass(password);
+            if (wsUser == null || wsUser.length() == 0) {
+                logger.info("wsUser is null");
+                response.setErrorCode(PARAM_NOT_ENOUGH);
+                response.setContent("wsUser is null");
+            } else if (wsPass == null || wsPass.length() == 0) {
+                logger.info("wsPass is null");
+                response.setErrorCode(PARAM_NOT_ENOUGH);
+                response.setContent("wsPass is null");
+            } else if (msisdn == null || msisdn.length() == 0) {
+                logger.info("msisdn is null");
+                response.setErrorCode(PARAM_NOT_ENOUGH);
+                response.setContent("msisdn is null");
+            } else if (packageName == null || packageName.length() == 0) {
+                logger.info("packageName is null");
+                response.setErrorCode(PARAM_NOT_ENOUGH);
+                response.setContent("packageName is null");
+            } else if (expense == null || expense.length() == 0) {
+                logger.info("expense is null");
+                response.setErrorCode(PARAM_NOT_ENOUGH);
+                response.setContent("point is null");
+            } else if (serviceId == null || serviceId.length() == 0) {
+                logger.info("serviceId is null");
+                response.setErrorCode(PARAM_NOT_ENOUGH);
+                response.setContent("serviceId is null");
+            }
+
+            if (SUCCESS.equals(response.getErrorCode())) {
+                // kiem tra thong tin dang nhap
+                userInfo = authenticate(wsUser, wsPass, ip);
+                if (userInfo == null || userInfo.getWsUserId() <= 0) {
+                    response.setErrorCode(WRONG_PASSWORD);
+                    response.setContent("Authenticate fail!");
+                } else {
+                    msisdn = formatMsisdn(msisdn);
+                    // check account
+                    AccountInfo accountInfo = db.iGetAccountByMsisdn(msisdn, serviceId);
+                    // create account
+                    if (accountInfo.getMsisdn() == null || accountInfo.getMsisdn().isEmpty()) {
+                        response.setErrorCode(Common.ErrorCode.ACCOUNT_NOT_EXISTED);
+                        response.setContent(MessageResponse.getDefaultMessage(Common.Message.ACCOUNT_NOT_EXISTED, logger));
+                    } else {
+
+                        int dataConvert = Integer.parseInt(expense);
+
+                        ChargeLog chargeLog = new ChargeLog();
+                        chargeLog.setFee(-dataConvert);
+                        chargeLog.setChargeTime(new Timestamp(System.currentTimeMillis()));
+                        chargeLog.setMsisdn(msisdn);
+                        chargeLog.setDescription("Exchange expense");
+                        chargeLog.setStatus(Common.Constant.UNCHARGE_STATUS);
+                        db.iInsertChargeLog(chargeLog);
+
+                        response.setErrorCode(Common.ErrorCode.SUCCESS);
+                        String message = MessageResponse.get(Common.Message.EXCHANGE_POINT_SUCCESS, logger);
+                        message = message.replaceAll("%point%", expense + "");
+                        response.setContent(message);
+
+                    }
+                }
+            }
+
+        } catch (Exception e) {
+            logger.error("Error processing", e);
+            response.setErrorCode(EXCEPTION);
+            String message = MessageResponse.get(Common.Message.SYSTEM_FAIL, logger);
+            response.setContent(message);
+        }
+
+        return response;
+    }
     
     
 

+ 73 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Cms/Controllers/MonthlyWinnerController.cs

@@ -1,4 +1,5 @@
 using Common;
+using ClosedXML.Excel;
 using Database.Database;
 using Kitty_Fall.Cms.Helpers;
 using Kitty_Fall.Cms.Models;
@@ -101,6 +102,78 @@ public class MonthlyWinnerController : CmsAuthorizedController
         return View(model);
     }
 
+    [HttpGet("/MonthlyWinner/ExportTickets")]
+    public async Task<IActionResult> ExportTickets(int? monthKey = null)
+    {
+        if (!CheckLogin()) return RedirectToLogin();
+        if (!HasMenuPermission(CmsSecurityHelper.MonthlyWinnersMenuKey)) return RedirectNoPermission();
+
+        var normalizedMonthKey = NormalizeMonthKey(monthKey);
+        var tickets = await _db.GameMonthlyDrawTickets
+            .AsNoTracking()
+            .Where(x => x.MonthKey == normalizedMonthKey)
+            .OrderBy(x => x.TicketNo)
+            .Select(x => new
+            {
+                x.TicketNo,
+                x.Msisdn,
+                x.TotalScore,
+                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 fileName = $"monthly-tickets-{normalizedMonthKey}.xlsx";
+        return File(
+            stream.ToArray(),
+            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+            fileName);
+    }
+
     [HttpPost]
     public async Task<JsonResult> SaveWinner(MonthlyWinnerSaveRequest request)
     {

+ 1 - 0
Kitty_Fall/Kitty_Fall/Kitty_Fall.Cms/Kitty_Fall.Cms.csproj

@@ -12,6 +12,7 @@
 	</ItemGroup>
 
 	<ItemGroup>
+		<PackageReference Include="ClosedXML" Version="0.102.3" />
 		<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
 	</ItemGroup>
 

+ 55 - 4
Kitty_Fall/Kitty_Fall/Kitty_Fall.Cms/Views/MonthlyWinner/Index.cshtml

@@ -20,10 +20,17 @@
             <h1 class="text-2xl font-bold text-gray-900">Monthly Winners</h1>
             <p class="mt-1 text-sm text-gray-500">Select cash prize winners manually and publish them to the website.</p>
         </div>
-        <button onclick="openWinnerModal()" class="inline-flex items-center justify-center rounded-2xl bg-admin-active px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-purple-700">
-            <i class="fas fa-plus mr-2"></i>
-            Add Winner
-        </button>
+        <div class="flex flex-col gap-2 sm:flex-row">
+            <a href="@Url.Content($"~/MonthlyWinner/ExportTickets?monthKey={Model.MonthKey}")"
+               class="inline-flex items-center justify-center rounded-2xl bg-emerald-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-emerald-700">
+                <i class="fas fa-file-excel mr-2"></i>
+                Export tickets
+            </a>
+            <button onclick="openWinnerModal()" class="inline-flex items-center justify-center rounded-2xl bg-admin-active px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-purple-700">
+                <i class="fas fa-plus mr-2"></i>
+                Add Winner
+            </button>
+        </div>
     </div>
 
     <div class="rounded-3xl bg-white shadow-sm ring-1 ring-gray-100">
@@ -205,6 +212,14 @@
                 </label>
             </div>
 
+            <label class="block">
+                <span class="mb-2 block text-sm font-semibold text-gray-700">Find by MSISDN</span>
+                <input type="search" id="ticketMsisdnSearch" inputmode="numeric" autocomplete="off"
+                       placeholder="Type a phone number to filter tickets"
+                       class="w-full rounded-2xl border border-gray-200 px-4 py-3 text-sm outline-none focus:border-admin-active focus:ring-2 focus:ring-purple-100" />
+                <span id="ticketSearchResult" class="mt-1 block text-xs text-gray-500"></span>
+            </label>
+
             <label class="block">
                 <span class="mb-2 block text-sm font-semibold text-gray-700">Eligible ticket</span>
                 <select id="ticketId" name="TicketId" class="w-full rounded-2xl border border-gray-200 px-4 py-3 text-sm outline-none focus:border-admin-active focus:ring-2 focus:ring-purple-100">
@@ -246,6 +261,9 @@
             $('#winnerForm')[0].reset();
             $('#winnerId').val('0');
             $('#winnerStatus').val('0');
+            $('#ticketMsisdnSearch').val('');
+            filterTicketOptions('');
+            syncMsisdnLock();
             $('#winnerModal').removeClass('hidden');
         }
 
@@ -253,11 +271,41 @@
             $('#winnerModal').addClass('hidden');
         }
 
+        function syncMsisdnLock() {
+            const hasTicket = !!$('#ticketId').val();
+            $('#msisdn')
+                .prop('readonly', hasTicket)
+                .toggleClass('bg-gray-100 text-gray-500 cursor-not-allowed', hasTicket);
+        }
+
+        function filterTicketOptions(searchValue) {
+            const query = (searchValue || '').replace(/\s+/g, '').toLowerCase();
+            let visibleCount = 0;
+            $('#ticketId option').each(function () {
+                if (!this.value) {
+                    this.hidden = false;
+                    this.disabled = false;
+                    return;
+                }
+                const msisdn = String($(this).data('msisdn') || '').toLowerCase();
+                const visible = !query || msisdn.includes(query);
+                this.hidden = !visible;
+                this.disabled = !visible;
+                if (visible) visibleCount++;
+            });
+            $('#ticketSearchResult').text(query ? `${visibleCount} matching ticket(s)` : '');
+        }
+
+        $('#ticketMsisdnSearch').on('input', function () {
+            filterTicketOptions($(this).val());
+        });
+
         $('#ticketId').on('change', function () {
             const msisdn = $(this).find(':selected').data('msisdn') || '';
             if (msisdn) {
                 $('#msisdn').val(msisdn);
             }
+            syncMsisdnLock();
         });
 
         $('.btn-edit-winner').on('click', function () {
@@ -268,6 +316,9 @@
             $('#winnerStatus').val(winner.status ?? winner.Status ?? 0);
             $('#ticketId').val(winner.ticketId || winner.TicketId || '');
             $('#msisdn').val(winner.msisdn || winner.Msisdn || '');
+            $('#ticketMsisdnSearch').val('');
+            filterTicketOptions('');
+            syncMsisdnLock();
             $('#winnerModal').removeClass('hidden');
         });