diff --git a/src/main/java/au/com/royalpay/payment/manage/management/clearing/core/SettleTasksService.java b/src/main/java/au/com/royalpay/payment/manage/management/clearing/core/SettleTasksService.java index 421c82062..10d3aeea5 100644 --- a/src/main/java/au/com/royalpay/payment/manage/management/clearing/core/SettleTasksService.java +++ b/src/main/java/au/com/royalpay/payment/manage/management/clearing/core/SettleTasksService.java @@ -1,11 +1,16 @@ package au.com.royalpay.payment.manage.management.clearing.core; +import au.com.royalpay.payment.manage.management.clearing.web.ManualSettleTask; import com.alibaba.fastjson.JSONObject; import java.util.List; public interface SettleTasksService { - List listLiveTasks(); + List listPlans(); JSONObject currentProcessingStatus(); + + List listMerchantsInfo(String mchMonikers); + + void submitTasks(List tasks); } diff --git a/src/main/java/au/com/royalpay/payment/manage/management/clearing/core/impl/SettleTaskServiceImpl.java b/src/main/java/au/com/royalpay/payment/manage/management/clearing/core/impl/SettleTaskServiceImpl.java index 384d22d3a..1a9db80b0 100644 --- a/src/main/java/au/com/royalpay/payment/manage/management/clearing/core/impl/SettleTaskServiceImpl.java +++ b/src/main/java/au/com/royalpay/payment/manage/management/clearing/core/impl/SettleTaskServiceImpl.java @@ -1,31 +1,55 @@ package au.com.royalpay.payment.manage.management.clearing.core.impl; import au.com.royalpay.payment.manage.management.clearing.core.SettleTasksService; +import au.com.royalpay.payment.manage.management.clearing.web.ManualSettleTask; import au.com.royalpay.payment.manage.mappers.system.SysSettlePlanMapper; +import au.com.royalpay.payment.tools.exceptions.ServerErrorException; +import au.com.royalpay.payment.tools.merchants.core.MerchantInfoProvider; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.JSONObject; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestClientResponseException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; @Service public class SettleTaskServiceImpl implements SettleTasksService { - private SysSettlePlanMapper planMapper; - private StringRedisTemplate redisTemplate; - private String prefix; + private final SysSettlePlanMapper planMapper; + private final StringRedisTemplate redisTemplate; + private final MerchantInfoProvider mchInfoProvider; + private final String prefix; + private final RestTemplate restTemplate; + private final String transactionHost; + private static final String SETTLE_TASK_URI = "/dev/manual/settle_tasks"; public SettleTaskServiceImpl(SysSettlePlanMapper planMapper, StringRedisTemplate redisTemplate, - @Value("${app.redis.prefix}") String prefix) { + MerchantInfoProvider mchInfoProvider, @Value("${app.redis.prefix}") String prefix, + RestTemplate restTemplate, @Value("${platform.services.transaction:http://127.0.0.1:6012}") String transactionHost) { this.planMapper = planMapper; this.redisTemplate = redisTemplate; + this.mchInfoProvider = mchInfoProvider; this.prefix = prefix; + this.restTemplate = restTemplate; + this.transactionHost = transactionHost; } @Override - public List listLiveTasks() { + public List listPlans() { return planMapper.listPlans(); } @@ -48,4 +72,62 @@ public class SettleTaskServiceImpl implements SettleTasksService { } return stdObj; } + + @Override + public List listMerchantsInfo(String mchMonikers) { + List monikers = new ArrayList<>(Arrays.asList(formatStringArray(mchMonikers).split(","))); + return monikers.parallelStream().map(this::getSimplifiedClientsInfo).filter(Objects::nonNull).collect(Collectors.toList()); + } + + private JSONObject getSimplifiedClientsInfo(String moniker) { + JSONObject info = mchInfoProvider.getClientInfoByMoniker(moniker); + if (info == null) { + return null; + } + JSONObject simple = new JSONObject(); + simple.put("client_moniker", info.getString("client_moniker")); + simple.put("clean_days", info.getIntValue("clean_days")); + simple.put("bd_user_name", info.getString("bd_user_name")); + return simple; + } + + @Override + public void submitTasks(List tasks) { + URI uri = UriComponentsBuilder.fromHttpUrl(transactionHost).replacePath(SETTLE_TASK_URI) + .build().toUri(); + try { + restTemplate.exchange(RequestEntity.post(uri).contentType(MediaType.APPLICATION_JSON) + .body(JSON.toJSONString(tasks).getBytes(StandardCharsets.UTF_8)), String.class); + } catch (RestClientResponseException e) { + String resp = e.getResponseBodyAsString(); + try { + throw new ServerErrorException("Transaction response error:" + JSON.parseObject(resp).getString("message")); + } catch (JSONException e1) { + throw new ServerErrorException("Connect Transaction error:" + resp); + } + } catch (RestClientException e) { + throw new ServerErrorException("Connect Transaction error:" + e.getMessage()); + } + } + + public static String formatStringArray(String str) { + str = str.replace(",", ","); + str = str.replace(" ", ","); + str = str.replace("\r\n", ","); + str = str.replace("\n", ","); + str = str.replace("\r", ","); + String nStr = str.replace(",,", ","); + while (!nStr.equals(str)) { + str = nStr; + nStr = str.replace(",,", ","); + } + str = nStr; + if (str.endsWith(",")) { + str = StringUtils.substring(str, 0, -1); + } + if (str.startsWith(",")) { + str = StringUtils.substring(str, 1); + } + return str; + } } diff --git a/src/main/java/au/com/royalpay/payment/manage/management/clearing/web/ManualSettleTask.java b/src/main/java/au/com/royalpay/payment/manage/management/clearing/web/ManualSettleTask.java new file mode 100644 index 000000000..ec65af537 --- /dev/null +++ b/src/main/java/au/com/royalpay/payment/manage/management/clearing/web/ManualSettleTask.java @@ -0,0 +1,91 @@ +package au.com.royalpay.payment.manage.management.clearing.web; + +import com.alibaba.fastjson.PropertyNamingStrategy; +import com.alibaba.fastjson.annotation.JSONType; +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; + +import java.util.Date; +import java.util.List; + +@JSONType(naming = PropertyNamingStrategy.SnakeCase) +public class ManualSettleTask { + private String taskId; + private String settleDate; + private String planRefId; + private String remark; + private String lastSettleDate; + private int[] disabledCleanDays; + + private List whitelistClients; + private boolean followSubPartner; + + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public Date getSettleDateValue() { + return StringUtils.isEmpty(settleDate) ? DateTime.now().withMillisOfDay(0).toDate() : DateTime.parse(settleDate).toDate(); + } + + public String getSettleDate() { + return settleDate; + } + + public void setSettleDate(String settleDate) { + this.settleDate = settleDate; + } + + public String getPlanRefId() { + return planRefId; + } + + public void setPlanRefId(String planRefId) { + this.planRefId = planRefId; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public String getLastSettleDate() { + return lastSettleDate; + } + + public void setLastSettleDate(String lastSettleDate) { + this.lastSettleDate = lastSettleDate; + } + + public int[] getDisabledCleanDays() { + return disabledCleanDays; + } + + public void setDisabledCleanDays(int[] disabledCleanDays) { + this.disabledCleanDays = disabledCleanDays; + } + + public List getWhitelistClients() { + return whitelistClients; + } + + public void setWhitelistClients(List whitelistClients) { + this.whitelistClients = whitelistClients; + } + + public boolean isFollowSubPartner() { + return followSubPartner; + } + + public void setFollowSubPartner(boolean followSubPartner) { + this.followSubPartner = followSubPartner; + } + +} diff --git a/src/main/java/au/com/royalpay/payment/manage/management/clearing/web/SettleTasksController.java b/src/main/java/au/com/royalpay/payment/manage/management/clearing/web/SettleTasksController.java index bcc2f2d66..3f68a3286 100644 --- a/src/main/java/au/com/royalpay/payment/manage/management/clearing/web/SettleTasksController.java +++ b/src/main/java/au/com/royalpay/payment/manage/management/clearing/web/SettleTasksController.java @@ -5,6 +5,8 @@ import au.com.royalpay.payment.manage.permission.manager.ManagerMapping; import au.com.royalpay.payment.tools.permission.enums.ManagerRole; import com.alibaba.fastjson.JSONObject; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -18,13 +20,24 @@ public class SettleTasksController { this.svc = svc; } - @GetMapping - public List listTasks() { - return svc.listLiveTasks(); + @GetMapping("/plans") + public List listPlans() { + return svc.listPlans(); } @GetMapping("/current_progress") public JSONObject getProcessingStatus() { return svc.currentProcessingStatus(); } + + @PostMapping("/merchants") + public List listMerchants(@RequestBody JSONObject mch) { + return svc.listMerchantsInfo(mch.getString("monikers")); + } + + @PostMapping + public JSONObject submitTasks(@RequestBody List tasks) { + svc.submitTasks(tasks); + return getProcessingStatus(); + } } diff --git a/src/main/ui/static/analysis/settle_tasks.js b/src/main/ui/static/analysis/settle_tasks.js new file mode 100644 index 000000000..d7c3b753a --- /dev/null +++ b/src/main/ui/static/analysis/settle_tasks.js @@ -0,0 +1,212 @@ +define(['angular', 'uiBootstrap', 'uiRouter'], function (angular) { + 'use strict'; + let app = angular.module('settleTasks', ['ui.bootstrap', 'ui.router']); + + app.config(['$stateProvider', function ($stateProvider) { + $stateProvider.state('settle_tasks', { + url: '/settle_tasks', + templateUrl: '/static/analysis/templates/settle_tasks_index.html', + controller: 'settleTasksRootCtrl' + }) + }]); + + app.controller('settleTasksRootCtrl', ['$scope', '$http', '$interval', '$q', '$uibModal', 'commonDialog', + function ($scope, $http, $interval, $q, $uibModal, commonDialog) { + let ctrl = this; + $scope.loadPlans = function () { + $http.get('/sys/settle_tasks/plans').then(function (res) { + $scope.staticPlans = res.data; + for (let plan of $scope.staticPlans) { + plan.name = plan.remark || plan.plan_id; + } + }) + }; + $scope.checkProgressStatus = function () { + let defer = $q.defer(); + $http.get('/sys/settle_tasks/curent_progress').then(function (res) { + $scope.progress = res.data; + defer.resolve(); + if (!$scope.progress.processing) { + if (ctrl.update_task) { + $interval.cancel(ctrl.update_task); + } + if (!$scope.currentTasks) { + $scope.currentTasks = []; + } + } + let finished = $scope.progress.finished_sequence; + if ($scope.currentTasks) { + for (let finId of finished) { + $scope.currentTasks.filter(task => task.task_id === finId).forEach(v => { + v.idle = false; + v.processed = true; + v.processint = false; + }) + } + if ($scope.progress.processing) { + $scope.currentTasks.filter(task => task.task_id === $scope.progress.progress_id).forEach(v => { + v.idle = false; + v.processed = false; + v.processing = true; + }) + } + } + }) + return defer.promise; + }; + + $scope.loadPlans(); + $scope.checkProgressStatus(); + + $scope.openBatchAddMerchantsDialog = function () { + if (!$scope.editingTask) { + return; + } + $uibModal.open({ + templateUrl: '/static/analysis/templates/settle_tasks_dialog_batch_add_merchants.html', + controller: 'batchAddMerchantsDialogCtrl', + size: 'lg' + }).result.then(function (merchants) { + let arr = $scope.editingTask.whitelist_clients; + for (let mch of merchants) { + if (arr.filter(wMch => wMch.client_moniker === mch.client_moniker).length < 1) { + arr.push(mch); + } + } + $scope.editingTask.whitelist_clients = arr; + }) + }; + $scope.removeFromTask = function (mch) { + let idx = $scope.editingTask.whitelist_clients.indexOf(mch); + if (idx >= 0) { + $scope.editingTask.whitelist_clients.splice(idx, 1); + } + }; + + $scope.newTask = function () { + $uibModal.open({ + size: 'lg', + templateUrl: '/static/analysis/templates/settle_tasks_new_task_dialog.html', + controller: 'newSettleTaskDialogCtrl', + resolve: { + plans: function () { + return $scope.staticPlans; + } + } + }).result.then(function (task) { + task.idle = true; + task.processing = false; + task.processed = false; + $scope.editingTask = task; + $scope.currentTasks.push(task); + }) + }; + $scope.submitTasks = function () { + let tasks = []; + for (let task of $scope.currentTasks) { + let submitTask = { + task_id: task.task_id, + settle_date: task.settle_date, + remark: task.remark, + }; + if (task.plan) { + submitTask.plan_ref_id = task.plan.plan_id; + } else { + submitTask.last_settle_date = task.last_settle_date; + submitTask.disabled_clean_days = task.disabled_clean_days; + submitTask.follow_sub_partner = task.follow_sub_partner; + submitTask.whitelist_clients = task.whitelist_clients.map(cli => cli.client_moniker) + } + tasks.push(submitTask) + } + $http.post('/sys/settle_tasks', tasks).then(function () { + commonDialog.alert({type: 'success', title: 'Success', content: resp.data.message}) + ctrl.update_task = $interval(function () { + $scope.checkProgressStatus() + }, 5000); + }, function (resp) { + commonDialog.alert({type: 'error', title: 'Error', content: resp.data.message}) + }) + } + + }]); + app.controller('batchAddMerchantsDialogCtrl', ['$scope', '$http', function ($scope, $http) { + $scope.data = {client_monikers: ''}; + $scope.sync = false; + $scope.analysisMerchants = function () { + $scope.sync = true; + $http.post('/sys/settle_tasks/merchants', {monikers: $scope.data.client_monikers}).then(function (res) { + $scope.merchantsInfo = res.data; + $scope.sync = false; + }, function () { + $scope.sync = false; + }) + }; + $scope.submit = function () { + $scope.$close($scope.merchantsInfo) + } + }]); + app.controller('newSettleTaskDialogCtrl', ['$scope', '$filter', 'plans', function ($scope, $filter, plans) { + $scope.plans = plans; + $scope.task = { + t1: true, + t2: true, + t3: true, + remark: '' + }; + $scope.lastTransDate = { + open: false + } + $scope.settleDate = { + open: false + } + $scope.today = new Date(); + + $scope.submitTask = function () { + + let chosenPlan = null; + if (!$scope.task.remark) { + $scope.task.remark = $filter('date')($scope.task.settle_date, 'yyyy-MM-dd') + ' Settle' + } + if ($scope.task.plan) { + let arr = $scope.plans.filter(p => p.plan_id === $scope.task.plan) + chosenPlan = arr.length ? arr[0] : null; + $scope.$close({ + plan_id: $scope.plan.remark.replace(' ', '') + randomString(6), + settle_date: $filter('date')($scope.task.settle_date, 'yyyy-MM-dd'), + remark: $scope.task.remark, + plan: chosenPlan + }) + } else { + let cleanDays = []; + if (!$scope.task.t1) { + cleanDays.push(1); + } + if (!$scope.task.t2) { + cleanDays.push(2) + } + if (!$scope.task.t3) { + cleanDays.push(3); + } + $scope.$close({ + plan_id: $scope.plan.remark.replace(' ', '') + randomString(6), + disabled_clean_days: cleanDays, + settle_date: $filter('date')($scope.task.settle_date, 'yyyy-MM-dd'), + last_settle_date: $scope.task.last_settle_date != null ? $filter('date')($scope.task.last_settle_date, 'yyyy-MM-dd') : null, + remark: $scope.task.remark, + follow_sub_partner: $scope.plan.follow_sub_partner + }) + } + + }; + + function randomString(length) { + const source = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' + let randomStr = ''; + for (let i = 0; i < length; i++) { + randomStr += source.charAt(Math.floor(Math.random() * source.length)) + } + return randomStr; + } + }]) +}) \ No newline at end of file diff --git a/src/main/ui/static/analysis/settle_tools.js b/src/main/ui/static/analysis/settle_tools.js deleted file mode 100644 index 7c9376ee5..000000000 --- a/src/main/ui/static/analysis/settle_tools.js +++ /dev/null @@ -1,8 +0,0 @@ -define(['angular', 'uiBootstrap', 'uiRouter'], function (angular) { - 'use strict'; - let app = angular.module('settleTools', ['ui.bootstrap', 'ui.router']); - - app.config(['$stateProvider', function ($stateProvider) { - $stateProvider.state('') - }]) -}) \ No newline at end of file diff --git a/src/main/ui/static/analysis/templates/settle_tasks_dialog_batch_add_merchants.html b/src/main/ui/static/analysis/templates/settle_tasks_dialog_batch_add_merchants.html new file mode 100644 index 000000000..1f86d129c --- /dev/null +++ b/src/main/ui/static/analysis/templates/settle_tasks_dialog_batch_add_merchants.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/src/main/ui/static/analysis/templates/settle_tasks_index.html b/src/main/ui/static/analysis/templates/settle_tasks_index.html new file mode 100644 index 000000000..c5f4d7db2 --- /dev/null +++ b/src/main/ui/static/analysis/templates/settle_tasks_index.html @@ -0,0 +1,115 @@ +
+
+

Settle Tasks

+ +
+
+
+
+ Status: {{progress.processing?'Processing':'Idle'}} +
+
+

{{progress.plan_remark}}|{{progress.current_step}}

+ +
+
+
+
+ Tasks Prepare + + +
+ +
+
+
+ {{task.remark}}-{{task.settle_date}} +
+ + + +
+
+
+
+
    +
  • Last Settle Date:{{task.last_settle_date||'Unset'}}
  • +
  • Disabled Clean Days: + +
  • +
  • With Sub partners: {{task.follow_sub_partner?'Yes':'No'}}
  • +
+ + + + + + + + + + + + + + + +
Client MonikerBD NameClean Days
+
+
+
+
+
+
Editing Task
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
Client MonikerBD NameClean DaysOperation
+ +
No Merchants applied
+
+ +
+
+
\ No newline at end of file diff --git a/src/main/ui/static/analysis/templates/settle_tasks_new_task_dialog.html b/src/main/ui/static/analysis/templates/settle_tasks_new_task_dialog.html new file mode 100644 index 000000000..344889b71 --- /dev/null +++ b/src/main/ui/static/analysis/templates/settle_tasks_new_task_dialog.html @@ -0,0 +1,66 @@ + + \ No newline at end of file diff --git a/src/main/ui/static/analysis/templates/settlement_detail.html b/src/main/ui/static/analysis/templates/settlement_detail.html index 703a0e606..a0ee513c2 100644 --- a/src/main/ui/static/analysis/templates/settlement_detail.html +++ b/src/main/ui/static/analysis/templates/settlement_detail.html @@ -186,8 +186,8 @@
-
-