Compare commits

...
11 Commits
56 changed files with 818 additions and 730 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,28 +0,0 @@
package net.jwsi.jcms.vpp.RatePlanDetailTime;
import lombok.extern.slf4j.Slf4j;
import net.jwsi.jcms.base.BaseController;
import net.jwsi.jcms.exception.JsonDataException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@Slf4j
@RequestMapping("/cms/ratePlanDetailTime")
public class RatePlanDetailTimeController extends BaseController<RatePlanDetailTime,Integer,RatePlanDetailTimeRepository,RatePlanDetailTimeService> {
public RatePlanDetailTimeController(RatePlanDetailTimeService service) {
super(service);
}
@Override
protected void chkSelectList(List<RatePlanDetailTime> list) throws JsonDataException {
}
@Override
protected void chkSelect(RatePlanDetailTime data) throws JsonDataException {
}
}
@@ -1,55 +0,0 @@
package net.jwsi.jcms.vpp.RatePlanDetailTime;
import lombok.extern.slf4j.Slf4j;
import net.jwsi.jcms.base.BaseService;
import net.jwsi.jcms.base.EnumSqlType;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@Slf4j
public class RatePlanDetailTimeService extends BaseService<RatePlanDetailTime,Integer,RatePlanDetailTimeRepository> {
public RatePlanDetailTimeService(RatePlanDetailTimeRepository repository) {
super(repository);
}
@Override
protected Integer getUserId() {
return null;
}
@Override
protected String getUserName(Integer id) {
return null;
}
@Override
protected RatePlanDetailTime newSearchParam(RatePlanDetailTime ratePlanDetailTime) {
return null;
}
@Override
protected EnumSqlType useSqlTyp() {
return null;
}
@Override
protected Specification<RatePlanDetailTime> getSpecification(RatePlanDetailTime ratePlanDetailTime) {
return null;
}
@Override
protected Page<RatePlanDetailTime> _selectPage(RatePlanDetailTime ratePlanDetailTime) {
return null;
}
@Override
protected List<RatePlanDetailTime> _selectList(RatePlanDetailTime ratePlanDetailTime) {
return null;
}
}
@@ -1,4 +0,0 @@
package net.jwsi.jcms.vpp.RatePlanDetailTime;
public class SeasonTimeDetailDto {
}
@@ -5,14 +5,12 @@ import net.jwsi.jcms.vpp.chgr.ChgrResponseDto;
import net.jwsi.jcms.vpp.chgrCurrState.ChgrCurrState;
import net.jwsi.jcms.vpp.chgrHist.ChgrHistResponseDto;
import net.jwsi.jcms.vpp.provider.ProviderDto;
import net.jwsi.jcms.vpp.rehchgingList.RechgingList;
import net.jwsi.jcms.vpp.rehchgingList.RechgingListDto;
import net.jwsi.jcms.vpp.rechgingList.RechgingListDto;
import net.jwsi.jcms.vpp.ratePlan.RatePlanResponseDto;
import net.jwsi.jcms.vpp.station.StationDto;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -82,11 +80,22 @@ public class ApiClient {
public ApiResponse<List<RatePlanResponseDto>> fetchRatePlan(){
Map<String, String> requestBody = Map.of(
"type","all",
"pageSize", "10000"
);
return externalApiClient.post()
.uri("/api/ratePlan/list")
.body(Collections.emptyMap())
.body(requestBody)
.retrieve()
.body(new ParameterizedTypeReference<ApiResponse<List<RatePlanResponseDto>>>() {
});
}
public ApiResponse<?> registerRatePlan(RatePlanResponseDto requestDto) {
return externalApiClient.post()
.uri("/api/ratePlan/register")
.body(requestDto)
.retrieve()
.body(ApiResponse.class);
}
}
@@ -1,28 +0,0 @@
package net.jwsi.jcms.vpp.chargerRatePlan;
import lombok.extern.slf4j.Slf4j;
import net.jwsi.jcms.base.BaseController;
import net.jwsi.jcms.exception.JsonDataException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@Slf4j
@RequestMapping("/cms/chargerRatePlan")
public class ChargerRatePlanController extends BaseController<ChargerRatePlan,Integer,ChargerRatePlanRepository,ChargerRatePlanService> {
public ChargerRatePlanController(ChargerRatePlanService service) {
super(service);
}
@Override
protected void chkSelectList(List<ChargerRatePlan> list) throws JsonDataException {
}
@Override
protected void chkSelect(ChargerRatePlan data) throws JsonDataException {
}
}
@@ -1,53 +0,0 @@
package net.jwsi.jcms.vpp.chargerRatePlan;
import lombok.extern.slf4j.Slf4j;
import net.jwsi.jcms.base.BaseService;
import net.jwsi.jcms.base.EnumSqlType;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@Slf4j
public class ChargerRatePlanService extends BaseService<ChargerRatePlan,Integer,ChargerRatePlanRepository> {
public ChargerRatePlanService(ChargerRatePlanRepository repository) {
super(repository);
}
@Override
protected Integer getUserId() {
return null;
}
@Override
protected String getUserName(Integer id) {
return null;
}
@Override
protected ChargerRatePlan newSearchParam(ChargerRatePlan chargerRatePlan) {
return null;
}
@Override
protected EnumSqlType useSqlTyp() {
return null;
}
@Override
protected Specification<ChargerRatePlan> getSpecification(ChargerRatePlan chargerRatePlan) {
return null;
}
@Override
protected Page<ChargerRatePlan> _selectPage(ChargerRatePlan chargerRatePlan) {
return null;
}
@Override
protected List<ChargerRatePlan> _selectList(ChargerRatePlan chargerRatePlan) {
return null;
}
}
@@ -14,7 +14,7 @@ import java.util.Map;
@Controller
@Slf4j
@RequestMapping("/cms/chgr")
@RequestMapping("/cms/chgr/")
public class ChgrController extends BaseController<Chgr,String,ChgrRepository,ChgrService> {
private final String path = "/system/chgr/";
@@ -12,7 +12,7 @@ public class ChgrResponseDto {
private String chgrId;
private String chgrNm;
private String speedTp;
private String chgrTp;
private String chgrType;
private double gpsXpos;
private double gpsYpos;
private String locInfo;
@@ -29,7 +29,7 @@ public class ChgrResponseDto {
chgr.setStNm(this.stNm);
chgr.setChgrNm(this.chgrNm);
chgr.setSpeedTp(this.speedTp);
chgr.setChgrTp(this.chgrTp);
chgr.setChgrTp(this.chgrType);
return chgr;
}
}
@@ -73,7 +73,12 @@ public class ChgrService extends BaseService<Chgr,String,ChgrRepository> {
@Override
protected Specification<Chgr> getSpecification(Chgr chgr) {
return null;
Specification<Chgr> rst = null;
if(chgr == null) return null;
rst = addWhere(rst, chgr.getChgrId(), ChgrSpecification.getChgrId(chgr.getChgrId()));
rst = addWhere(rst, chgr.getStId(), ChgrSpecification.getStId(chgr.getStId()));
return rst;
}
@Override
@@ -1,4 +1,17 @@
package net.jwsi.jcms.vpp.chgr;
public class ChgrSpecification {
import net.jwsi.jcms.base.BaseSpecification;
import org.springframework.data.jpa.domain.Specification;
public class ChgrSpecification extends BaseSpecification {
public static Specification<Chgr> getStId(String stId) {
return (root, query, cb) -> {
return cb.equal(root.get("stId"), stId);
};
}
public static Specification<Chgr> getChgrId(String chgrId) {
return (root, query, cb) -> {
return cb.equal(root.get("chgrId"), chgrId);
};
}
}
@@ -12,7 +12,7 @@ import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/cms/chgrCurrState")
@RequestMapping("/cms/chgrCurrState/")
public class ChgrCurrStateController extends BaseController<ChgrCurrState,ChgrCurrStateId,ChgrCurrStateRepository,ChgrCurrStateService> {
private final ChgrCurrStateService chgrCurrStateService;
@@ -72,7 +72,15 @@ public class ChgrCurrStateService extends BaseService<ChgrCurrState,ChgrCurrStat
@Override
protected Specification<ChgrCurrState> getSpecification(ChgrCurrState chgrCurrState) {
return null;
Specification<ChgrCurrState> rst = null;
if(chgrCurrState == null) return null;
rst = addWhere(rst, chgrCurrState.getChgrId(), ChgrCurrStateSpecification.getChgrId(chgrCurrState.getChgrId()));
rst = addWhere(rst, chgrCurrState.getStId(), ChgrCurrStateSpecification.getStId(chgrCurrState.getStId()));
rst = addWhere(rst, chgrCurrState.getCh1DoorStateCd(), ChgrCurrStateSpecification.getDoorState(chgrCurrState.getCh1DoorStateCd()));
rst = addWhere(rst, chgrCurrState.getCh1PlugStateCd(), ChgrCurrStateSpecification.getPlugState(chgrCurrState.getCh1PlugStateCd()));
rst = addWhere(rst, chgrCurrState.getCh1RechgStateCd(), ChgrCurrStateSpecification.getRechgState(chgrCurrState.getCh1RechgStateCd()));
return rst;
}
@Override
@@ -0,0 +1,35 @@
package net.jwsi.jcms.vpp.chgrCurrState;
import net.jwsi.jcms.base.BaseSpecification;
import org.springframework.data.jpa.domain.Specification;
public class ChgrCurrStateSpecification extends BaseSpecification {
public static Specification<ChgrCurrState> getStId(String stId) {
return (root, query, cb) -> {
return cb.equal(root.get("stId"), stId);
};
}
public static Specification<ChgrCurrState> getChgrId(String chgrId) {
return (root, query, cb) -> {
return cb.equal(root.get("chgrId"), chgrId);
};
}
public static Specification<ChgrCurrState> getDoorState(String doorState) {
return (root, query, cb) -> {
return cb.equal(root.get("ch1DoorStateCd"), doorState);
};
}
public static Specification<ChgrCurrState> getPlugState(String plugState) {
return (root, query, cb) -> {
return cb.equal(root.get("ch1PlugStateCd"), plugState);
};
}
public static Specification<ChgrCurrState> getRechgState(String rechgState) {
return (root, query, cb) -> {
return cb.equal(root.get("ch1RechgStateCd"), rechgState);
};
}
}
@@ -22,6 +22,9 @@ public class ChgrHist extends BaseStEntity {
@Column(name = "CHGR_ID", nullable = false)
private String chgrId; // 충전기 id
@Column(name = "CH_ID")
private Integer chId;
@Column(name = "ST_ID", nullable = false)
private String stId; // 충전소 id
@@ -10,7 +10,7 @@ import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/cms/chgrHist")
@RequestMapping("/cms/chgrHist/")
@Slf4j
public class ChgrHistController extends BaseController<ChgrHist,Integer,ChgrHistRepository,ChgrHistService> {
@@ -23,6 +23,7 @@ public class ChgrHistController extends BaseController<ChgrHist,Integer,ChgrHist
@GetMapping("list")
public String list() throws IllegalAccessException {
chgrHistService.getChgrHist();
return path+"list";
}
@@ -5,4 +5,5 @@ import org.springframework.stereotype.Repository;
@Repository
public interface ChgrHistRepository extends BaseRecStRepository<ChgrHist,Integer> {
boolean existsByChgrIdAndChIdAndRechgEDt(String chgrId, Integer chId,String rechgEDt);
}
@@ -52,6 +52,7 @@ public class ChgrHistResponseDto {
chgrHist.setRechgAmt(this.rechgAmt);
chgrHist.setPayFnsh(this.payFnsh);
chgrHist.setRechGwh(this.rechgWh);
chgrHist.setChId(this.chId);
return chgrHist;
}
}
@@ -30,7 +30,7 @@ public class ChgrHistService extends BaseService<ChgrHist, Integer, ChgrHistRepo
@Transactional
@Scheduled(cron = "0 */10 * * * *")
public void getChgrHist() throws IllegalAccessException {
public void getChgrHist() {
String startDt = LocalDate.now().toString();
ApiResponse<List<ChgrHistResponseDto>> response = apiClient.fetchChgrHist(startDt, "50");
@@ -51,21 +51,17 @@ public class ChgrHistService extends BaseService<ChgrHist, Integer, ChgrHistRepo
.toList();
int insertCount = 0;
int updateCount = 0;
for (ChgrHist h : entityList) {
Integer id = BaseService.getId(h);
if (id != null && super.repository.existsById(id)) {
super.update(h);
updateCount++;
} else {
boolean isExist = ((ChgrHistRepository) super.repository).existsByChgrIdAndChIdAndRechgEDt(h.getChgrId(), h.getChId(),h.getRechgEDt());
if (!isExist) {
super.insert(h);
insertCount++;
}
}
log.info("충전이력 동기화 완료 (신규 추가: {}건 / 기존 업데이트: {}건)", insertCount, updateCount);
log.info("충전이력 수집 완료: API 응답 {}건 중 신규 저장 {}건", entityList.size(), insertCount);
}
@Override
@@ -93,7 +89,12 @@ public class ChgrHistService extends BaseService<ChgrHist, Integer, ChgrHistRepo
@Override
protected Specification<ChgrHist> getSpecification(ChgrHist chgrHist) {
return null;
Specification<ChgrHist> rst = null;
if(chgrHist == null) return null;
rst = addWhere(rst, chgrHist.getChgrId(), ChgrHistSpecification.getChgrId(chgrHist.getChgrId()));
rst = addWhere(rst, chgrHist.getStId(), ChgrHistSpecification.getStId(chgrHist.getStId()));
return rst;
}
@Override
@@ -0,0 +1,17 @@
package net.jwsi.jcms.vpp.chgrHist;
import net.jwsi.jcms.base.BaseSpecification;
import org.springframework.data.jpa.domain.Specification;
public class ChgrHistSpecification extends BaseSpecification {
public static Specification<ChgrHist> getStId(String stId) {
return (root, query, cb) -> {
return cb.equal(root.get("stId"), stId);
};
}
public static Specification<ChgrHist> getChgrId(String chgrId) {
return (root, query, cb) -> {
return cb.equal(root.get("chgrId"), chgrId);
};
}
}
@@ -26,6 +26,7 @@ public class ProviderController extends BaseController<Provider, String, Provide
@GetMapping("list")
public String list() {
providerService.getProviders();
return path + "list";
}
@@ -79,8 +79,13 @@ public class ProviderService extends BaseService<Provider, String, ProviderRepos
@Override
protected Specification<Provider> getSpecification(Provider srch) {
if (srch == null) return null;
Specification<Provider> rst = null;
rst = addWhere(rst, srch.getProviderId(), ProviderSpecification.providerId(srch.getProviderId()));
rst = addWhere(rst, srch.getProviderNm(), ProviderSpecification.providerNm(srch.getProviderNm()));
rst = addWhere(rst, srch.getBizTaxId(), ProviderSpecification.bizTaxId(srch.getBizTaxId()));
rst = addWhere(rst, srch.getCeo(), ProviderSpecification.ceo(srch.getCeo()));
rst = addWhere(rst, srch.getTel(), ProviderSpecification.tel(srch.getTel()));
rst = addWhere(rst, srch.getFax(), ProviderSpecification.fax(srch.getFax()));
return rst;
}
@@ -1,10 +1,28 @@
package net.jwsi.jcms.vpp.provider;
import net.jwsi.jcms.utils.StrUtil;
import net.jwsi.jcms.vpp.rechgingList.RechgingList;
import org.springframework.data.jpa.domain.Specification;
public class ProviderSpecification {
public static Specification<Provider> providerId(String providerId) {
return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("providerId"), providerId);
public static Specification<Provider> providerNm(String providerNm) {
return (root, query, cb) -> cb.like(root.get("providerNm"), StrUtil.sqlLikeParam(providerNm));
}
public static Specification<Provider> bizTaxId(String bizTaxId) {
return (root, query, cb) -> cb.like(root.get("bizTaxId"), StrUtil.sqlLikeParam(bizTaxId));
}
public static Specification<Provider> ceo(String ceo) {
return (root, query, cb) -> cb.like(root.get("ceo"), StrUtil.sqlLikeParam(ceo));
}
public static Specification<Provider> tel(String tel) {
return (root, query, cb) -> cb.like(root.get("tel"), StrUtil.sqlLikeParam(tel));
}
public static Specification<Provider> fax(String fax) {
return (root, query, cb) -> cb.like(root.get("fax"), StrUtil.sqlLikeParam(fax));
}
}
@@ -3,6 +3,8 @@ package net.jwsi.jcms.vpp.ratePlan;
import lombok.extern.slf4j.Slf4j;
import net.jwsi.jcms.base.BaseController;
import net.jwsi.jcms.exception.JsonDataException;
import net.jwsi.jcms.vpp.api.ApiClient;
import net.jwsi.jcms.vpp.api.ApiResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@@ -12,28 +14,33 @@ import java.util.Map;
@Controller
@Slf4j
@RequestMapping("/cms/ratePlan")
public class RatePlanController extends BaseController<RatePlan,Integer,RatePlanRepository,RatePlanService> {
@RequestMapping("/cms/ratePlan/")
public class RatePlanController extends BaseController<RatePlan, Integer, RatePlanRepository, RatePlanService> {
private final String path = "/system/ratePlan/";
private final RatePlanService ratePlanService;
private final ApiClient apiClient;
public RatePlanController(RatePlanService service, RatePlanService ratePlanService) {
public RatePlanController(RatePlanService service, RatePlanService ratePlanService, ApiClient apiClient) {
super(service);
this.ratePlanService = ratePlanService;
this.apiClient = apiClient;
}
@GetMapping("list")
public String list(){
public String list() {
ratePlanService.getRatePlan();
return path+"list";
return path + "list";
}
@PostMapping("list.json")
@ResponseBody
public Map<String,Object> list(RatePlan ratePlan){
if(ratePlan == null) ratePlan = new RatePlan();
public Map<String, Object> list(RatePlan ratePlan) {
if (ratePlan == null) ratePlan = new RatePlan();
return svcSelectList(ratePlan);
}
@PostMapping("view.json")
@ResponseBody
public Map<String, Object> viewData(@RequestParam("ratePlanId") Integer ratePlanId) {
@@ -49,6 +56,28 @@ public class RatePlanController extends BaseController<RatePlan,Integer,RatePlan
return result;
}
@PostMapping("insert.json")
@ResponseBody
public Map<String, Object> insertRatePlan(@RequestBody RatePlanResponseDto requestDto) {
Map<String, Object> resultMap = new HashMap<>();
try {
ApiResponse<?> apiRes = apiClient.registerRatePlan(requestDto);
if (apiRes == null || !Integer.valueOf(200).equals(apiRes.getCode())) {
String msg = (apiRes != null) ? apiRes.getMessage() : "API 서버 응답 없음";
throw new RuntimeException("API 서버 등록 실패: " + msg);
}
resultMap.put("result", "success");
resultMap.put("message", "요금제 등록 요청이 완료되었습니다. (승인 대기)");
} catch (Exception e) {
log.error("요금제 등록 중 오류 발생", e);
resultMap.put("result", "fail");
resultMap.put("message", e.getMessage());
}
return resultMap;
}
@Override
protected void chkSelectList(List<RatePlan> list) throws JsonDataException {
@@ -41,7 +41,9 @@ public class RatePlanResponseDto {
ratePlan.setApplyStartDate(this.applyStartDate);
ratePlan.setApplyEndDate(this.applyEndDate);
ratePlan.setFixedPrice(this.fixedPrice);
ratePlan.setIsActive(this.isActive);
ratePlan.setApprovalStatus(this.approvalStatus);
ratePlan.setDescription(this.description);
return ratePlan;
}
@@ -77,10 +79,7 @@ public class RatePlanResponseDto {
chargerRatePlan.setChargerCd(chargerDto.getChargerCd());
chargerRatePlan.setApplyStartDate(chargerDto.getApplyStartDate());
chargerRatePlan.setApplyEndDate(chargerDto.getApplyEndDate());
if (chargerDto.getIsActive() != null) {
chargerRatePlan.setIsActive(chargerDto.getIsActive() ? "Y" : "N");
}
chargerRatePlan.setIsActive(chargerDto.getIsActive());
chargerRatePlanList.add(chargerRatePlan);
}
@@ -112,6 +111,6 @@ public class RatePlanResponseDto {
private String chargerCd;
private String applyStartDate;
private String applyEndDate;
private Boolean isActive;
private String isActive;
}
}
@@ -48,8 +48,32 @@ public class RatePlanService extends BaseService<RatePlan, Integer, RatePlanRepo
}
List<RatePlanResponseDto> dtoList = response.getData();
if (dtoList == null || dtoList.isEmpty()) {
log.info("가져온 요금제 데이터가 없습니다.");
if (dtoList == null) {
dtoList = new ArrayList<>();
}
Set<Integer> validApiIds = dtoList.stream()
.map(RatePlanResponseDto::getRatePlanId)
.collect(Collectors.toSet());
List<RatePlan> localRatePlans = ratePlanRepository.findAll();
int deleteCount = 0;
for (RatePlan localPlan : localRatePlans) {
if (!validApiIds.contains(localPlan.getRatePlanId())) {
Integer idToDelete = localPlan.getRatePlanId();
ratePlanDetailTimeRepository.deleteByRatePlanId(idToDelete);
chargerRatePlanRepository.deleteByRatePlanId(idToDelete);
ratePlanRepository.deleteById(idToDelete);
deleteCount++;
log.info("운영 DB에서 삭제된 요금제를 로컬 DB에서 제거했습니다. - ID: {}", idToDelete);
}
}
if (dtoList.isEmpty()) {
log.info("가져온 요금제 데이터가 없습니다. (로컬 동기화 및 삭제 완료. 삭제 건수: {})", deleteCount);
return;
}
@@ -104,8 +128,8 @@ public class RatePlanService extends BaseService<RatePlan, Integer, RatePlanRepo
chargerRatePlanRepository.saveAll(allChargerRatePlans);
}
log.info("요금제 동기화 완료: 마스터 {}건, 상세단가 {}건, 충전기매핑 {}건",
ratePlanEntities.size(), allDetailTimes.size(), allChargerRatePlans.size());
log.info("요금제 동기화 완료: 마스터 추가/수정 {}건, 삭제 {}건, 상세단가 {}건, 충전기매핑 {}건",
ratePlanEntities.size(), deleteCount, allDetailTimes.size(), allChargerRatePlans.size());
}
public RatePlanResponseDto getRatePlanDetail(Integer ratePlanId) {
@@ -151,7 +175,7 @@ public class RatePlanService extends BaseService<RatePlan, Integer, RatePlanRepo
cpDto.setChargerCd(cp.getChargerCd());
cpDto.setApplyStartDate(cp.getApplyStartDate());
cpDto.setApplyEndDate(cp.getApplyEndDate());
cpDto.setIsActive("Y".equals(cp.getIsActive()));
cpDto.setIsActive(cp.getIsActive());
return cpDto;
}).collect(Collectors.toList());
@@ -159,7 +183,6 @@ public class RatePlanService extends BaseService<RatePlan, Integer, RatePlanRepo
return dto;
}
@Override
protected Integer getUserId() {
return HttpUtil.getUserId();
@@ -185,9 +208,17 @@ public class RatePlanService extends BaseService<RatePlan, Integer, RatePlanRepo
@Override
protected Specification<RatePlan> getSpecification(RatePlan ratePlan) {
return null;
Specification<RatePlan> rst = null;
if(ratePlan == null) return null;
rst = addWhere(rst, ratePlan.getRatePlanName(), RatePlanSpecification.getRatePlanNm(ratePlan.getRatePlanName()));
rst = addWhere(rst, ratePlan.getApprovalStatus(), RatePlanSpecification.getApprovalStatus(ratePlan.getApprovalStatus()));
rst = addWhere(rst, ratePlan.getIsActive(), RatePlanSpecification.getIsActive(ratePlan.getIsActive()));
return rst;
}
@Override
protected Page<RatePlan> _selectPage(RatePlan ratePlan) {
return null;
@@ -0,0 +1,23 @@
package net.jwsi.jcms.vpp.ratePlan;
import net.jwsi.jcms.base.BaseSpecification;
import org.springframework.data.jpa.domain.Specification;
public class RatePlanSpecification extends BaseSpecification {
public static Specification<RatePlan> getRatePlanNm(String ratePlanName) {
return (root, query, cb) -> {
return cb.like(root.get("ratePlanName"), "%"+ratePlanName+"%");
};
}
public static Specification<RatePlan> getApprovalStatus(String approvalStatus) {
return (root, query, cb) -> {
return cb.equal(root.get("approvalStatus"), approvalStatus);
};
}
public static Specification<RatePlan> getIsActive(String isActive) {
return (root, query, cb) -> {
return cb.equal(root.get("isActive"), isActive);
};
}
}
@@ -1,4 +1,4 @@
package net.jwsi.jcms.vpp.rehchgingList;
package net.jwsi.jcms.vpp.rechgingList;
import jakarta.persistence.*;
import lombok.Getter;
@@ -1,4 +1,4 @@
package net.jwsi.jcms.vpp.rehchgingList;
package net.jwsi.jcms.vpp.rechgingList;
import lombok.extern.slf4j.Slf4j;
import net.jwsi.jcms.base.BaseController;
@@ -14,7 +14,7 @@ import java.util.Map;
@Controller
@Slf4j
@RequestMapping("/cms/rechgingList")
@RequestMapping("/cms/rechgingList/")
public class RechgingListController extends BaseController<RechgingList, Long, RechgingListRepository, RechgingListService> {
private final String path = "/system/rechgingList/";
@@ -1,4 +1,4 @@
package net.jwsi.jcms.vpp.rehchgingList;
package net.jwsi.jcms.vpp.rechgingList;
import lombok.*;
@@ -1,4 +1,4 @@
package net.jwsi.jcms.vpp.rehchgingList;
package net.jwsi.jcms.vpp.rechgingList;
import net.jwsi.jcms.base.BaseRecStRepository;
import org.springframework.stereotype.Repository;
@@ -1,4 +1,4 @@
package net.jwsi.jcms.vpp.rehchgingList;
package net.jwsi.jcms.vpp.rechgingList;
import jakarta.transaction.Transactional;
import lombok.extern.slf4j.Slf4j;
@@ -8,13 +8,11 @@ import net.jwsi.jcms.utils.HttpUtil;
import net.jwsi.jcms.utils.LoginUtil;
import net.jwsi.jcms.vpp.api.ApiClient;
import net.jwsi.jcms.vpp.api.ApiResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -30,7 +28,7 @@ public class RechgingListService extends BaseService<RechgingList, Long, Rechgin
}
@Transactional
@Scheduled(cron = "0 0 0 * * *")
@Scheduled(cron = "0 0/15 * * * *")
public void getRechgingList() {
ApiResponse<List<RechgingListDto>> response = apiClient.fetchRechgingList();
@@ -82,9 +80,19 @@ public class RechgingListService extends BaseService<RechgingList, Long, Rechgin
@Override
protected Specification<RechgingList> getSpecification(RechgingList srch) {
if (srch == null) return null;
Specification<RechgingList> rst = null;
rst = addWhere(rst, srch.getRechgingListId(), RechgingListSpecification.rechgingListId(srch.getRechgingListId()));
rst = addWhere(rst, srch.getProviderId(), RechgingListSpecification.providerId(srch.getProviderId()));
rst = addWhere(rst, srch.getStId(), RechgingListSpecification.stId(srch.getStId()));
rst = addWhere(rst, srch.getChgrId(), RechgingListSpecification.chgrId(srch.getChgrId()));
rst = addWhere(rst, srch.getPlugType(), RechgingListSpecification.plugType(srch.getPlugType()));
rst = addWhere(rst, srch.getMemAuthInputNo(), RechgingListSpecification.memAuthInputNo(srch.getMemAuthInputNo()));
rst = addWhere(rst, srch.getCreditPPayTrxNo(), RechgingListSpecification.creditPPayTrxNo(srch.getCreditPPayTrxNo()));
rst = addWhere(rst, srch.getCreditPPayTrxDt(), RechgingListSpecification.creditPPayTrxDt(srch.getCreditPPayTrxDt()));
rst = addWhere(rst, srch.getRechgSdt(), RechgingListSpecification.rechgSdtBetween(srch.getRechgSdt()));
rst = addWhere(rst, srch.getPayType(), RechgingListSpecification.payType(srch.getPayType()));
return rst;
}
@Override
@@ -0,0 +1,57 @@
package net.jwsi.jcms.vpp.rechgingList;
import net.jwsi.jcms.base.BaseSpecification;
import net.jwsi.jcms.utils.StrUtil;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;
public class RechgingListSpecification extends BaseSpecification {
public static Specification<RechgingList> providerId(String providerId) {
return (root, query, cb) -> cb.like(root.get("providerId"), StrUtil.sqlLikeParam(providerId));
}
public static Specification<RechgingList> stId(String stId) {
return (root, query, cb) -> cb.like(root.get("stId"), StrUtil.sqlLikeParam(stId));
}
public static Specification<RechgingList> chgrId(String chgrId) {
return (root, query, cb) -> cb.like(root.get("chgrId"), StrUtil.sqlLikeParam(chgrId));
}
public static Specification<RechgingList> plugType(String plugType) {
return (root, query, cb) -> cb.like(root.get("plugType"), StrUtil.sqlLikeParam(plugType));
}
public static Specification<RechgingList> memAuthInputNo(String memAuthInputNo) {
return (root, query, cb) -> cb.like(root.get("memAuthInputNo"), StrUtil.sqlLikeParam(memAuthInputNo));
}
public static Specification<RechgingList> creditPPayTrxNo(String creditPPayTrxNo) {
return (root, query, cb) -> cb.like(root.get("creditPPayTrxNo"), StrUtil.sqlLikeParam(creditPPayTrxNo));
}
public static Specification<RechgingList> creditPPayTrxDt(String creditPPayTrxDt) {
return (root, query, cb) -> cb.like(root.get("creditPPayTrxDt"), StrUtil.sqlLikeParam(creditPPayTrxDt));
}
public static Specification<RechgingList> rechgSdtBetween(String rechgSdt) {
return (root, query, cb) -> {
// 날짜 파라미터가 없으면 검색 조건에서 제외 (null 반환)
if (!StringUtils.hasText(rechgSdt)) {
return null;
}
// 예: "2026-07-31" -> "2026-07-31 00:00:00" ~ "2026-07-31 23:59:59"
String startDateTime = rechgSdt.trim() + " 00:00:00";
String endDateTime = rechgSdt.trim() + " 23:59:59";
return cb.between(root.get("rechgSdt"), startDateTime, endDateTime);
};
}
public static Specification<RechgingList> payType(String payType) {
return (root, query, cb) -> cb.like(root.get("payType"), StrUtil.sqlLikeParam(payType));
}
}
@@ -1,11 +0,0 @@
package net.jwsi.jcms.vpp.rehchgingList;
import org.springframework.data.jpa.domain.Specification;
public class RechgingListSpecification {
public static Specification<RechgingList> rechgingListId(Long rechgingListId) {
return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("rechgingListId"), rechgingListId);
}
}
@@ -77,7 +77,8 @@ public class StationService extends BaseService<Station,String,StationRepository
protected Specification<Station> getSpecification(Station srch) {
if(srch == null) return null;
Specification<Station> rst = null;
rst = addWhere(rst, srch.getStationId(), StationSpecification.stId(srch.getStationId()));
rst = addWhere(rst, srch.getStNm(), StationSpecification.stNm(srch.getStNm()));
rst = addWhere(rst, srch.getAddrLdM(), StationSpecification.addrLdM(srch.getAddrLdM()));
return rst;
}
@@ -5,7 +5,10 @@ import org.springframework.data.jpa.domain.Specification;
public class StationSpecification {
public static Specification<Station> stId(String stId) {
return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("stId"), stId);
public static Specification<Station> stNm(String stNm) {
return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("stNm"),"%"+stNm+"%");
}
public static Specification<Station> addrLdM(String addrLdM) {
return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("addrLdM"),"%"+addrLdM+"%");
}
}
@@ -1,27 +0,0 @@
package net.jwsi.jcms.vpp.timeslots;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.jwsi.jcms.base.BaseStEntity;
@NoArgsConstructor
@Setter
@Getter
@Entity(name="vpp_time_slots")
public class TimeSlots extends BaseStEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id; //시간대 id(pk)
private String name; //시간대명 (예: 경부하, 최대부하, 09시)
private String slotType; //시간대 구분 (basic=계시별, hour=시간단위)
private Integer hourNumber; //시간 구분 (slot_type=hour일 경우 0~23)
private String startTime; //시작 시간
private String endTime; //종료 시간
private String description; //시간대 설명
private String isActive; //활성화 여부
}
@@ -1,28 +0,0 @@
package net.jwsi.jcms.vpp.timeslots;
import lombok.extern.slf4j.Slf4j;
import net.jwsi.jcms.base.BaseController;
import net.jwsi.jcms.exception.JsonDataException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@Slf4j
@RequestMapping("/cms/timeSlots")
public class TimeSlotsController extends BaseController<TimeSlots,Integer,TimeSlotsRepository,TimeSlotsService> {
public TimeSlotsController(TimeSlotsService service) {
super(service);
}
@Override
protected void chkSelectList(List<TimeSlots> list) throws JsonDataException {
}
@Override
protected void chkSelect(TimeSlots data) throws JsonDataException {
}
}
@@ -1,6 +0,0 @@
package net.jwsi.jcms.vpp.timeslots;
import net.jwsi.jcms.base.BaseRecStRepository;
public interface TimeSlotsRepository extends BaseRecStRepository<TimeSlots,Integer> {
}
@@ -1,53 +0,0 @@
package net.jwsi.jcms.vpp.timeslots;
import lombok.extern.slf4j.Slf4j;
import net.jwsi.jcms.base.BaseService;
import net.jwsi.jcms.base.EnumSqlType;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@Slf4j
public class TimeSlotsService extends BaseService<TimeSlots,Integer,TimeSlotsRepository> {
public TimeSlotsService(TimeSlotsRepository repository) {
super(repository);
}
@Override
protected Integer getUserId() {
return null;
}
@Override
protected String getUserName(Integer id) {
return null;
}
@Override
protected TimeSlots newSearchParam(TimeSlots timeSlots) {
return null;
}
@Override
protected EnumSqlType useSqlTyp() {
return null;
}
@Override
protected Specification<TimeSlots> getSpecification(TimeSlots timeSlots) {
return null;
}
@Override
protected Page<TimeSlots> _selectPage(TimeSlots timeSlots) {
return null;
}
@Override
protected List<TimeSlots> _selectList(TimeSlots timeSlots) {
return null;
}
}
+2 -2
View File
@@ -15,8 +15,8 @@ spring:
jcms-db:
driver-class-name: org.mariadb.jdbc.Driver
jdbc-url: jdbc:mariadb://192.168.62.37:3306/jcmsdb
username: jcms
password: jcms3593
username: root
password: jcms
server:
port: 8080
+2 -2
View File
@@ -9,9 +9,9 @@ spring:
datasource:
jcms-db:
driver-class-name: org.mariadb.jdbc.Driver
jdbc-url: jdbc:mariadb://192.168.62.37:33068/jcms
jdbc-url: jdbc:mariadb://dev.jinwoosi.co.kr:33091/jcms
username: jcms
password: ENC(nqt7VLhtxqwiJcQQw9ncNg==)
password: jw3593
jpa:
entity-packages: net.jwsi.jcms
show-sql: false
@@ -6,12 +6,6 @@
<div th:replace="~{common/fragments/searchBox :: SearchBoxFragment(~{ :: #searchBoxContent}, ~{ :: #searchBoxFooter})}">
<div id="searchBoxContent">
<div class="row px-2">
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:140px">충전사업자 ID</span>
<input type="text" id="providerIdLike" name="providerIdLike" class="form-control" placeholder="충전사업자 ID 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:140px">충전소 ID</span>
@@ -24,30 +18,6 @@
<input type="text" id="chgrIdLike" name="chgrIdLike" class="form-control" placeholder="충전기 ID 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:140px">충전소 명</span>
<input type="text" id="stNmLike" name="stNmLike" class="form-control" placeholder="충전소 명 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:140px">충전기 명</span>
<input type="text" id="chgrNmLike" name="chgrNmLike" class="form-control" placeholder="충전기 명 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:140px">충전기속도</span>
<input type="text" id="speedTpLike" name="speedTpLike" class="form-control" placeholder="충전기속도 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:140px">충전기타입</span>
<input type="text" id="chgrTpLike" name="chgrTpLike" class="form-control" placeholder="충전기타입 입력" maxlength="50">
</div>
</div>
</div>
</div>
<div id="searchBoxFooter">
@@ -8,31 +8,31 @@
<div class="row px-2">
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:180px">st_id</span>
<input type="text" id="stIdLike" name="stIdLike" class="form-control" placeholder="st_id 입력" maxlength="50">
<span class="input-group-text" style="width:180px">충전소 아이디</span>
<input type="text" id="stIdLike" name="stIdLike" class="form-control" placeholder="충전소 아이디 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:180px">chgr_id</span>
<input type="text" id="chgrIdLike" name="chgrIdLike" class="form-control" placeholder="chgr_id 입력" maxlength="50">
<span class="input-group-text" style="width:180px">충전기 아이디</span>
<input type="text" id="chgrIdLike" name="chgrIdLike" class="form-control" placeholder="충전기 아이디 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:180px">ch1_rechg_state_cd</span>
<span class="input-group-text" style="width:180px">충전상태</span>
<select id="ch1RechgStateCdLike" name="ch1RechgStateCdLike" class="form-control"></select>
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:180px">ch1_door_state_cd</span>
<span class="input-group-text" style="width:180px">도어상태</span>
<select id="ch1DoorStateCdLike" name="ch1DoorStateCdLike" class="form-control"></select>
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:180px">ch1_plug_state_cd</span>
<span class="input-group-text" style="width:180px">플러그상태</span>
<select id="ch1PlugStateCdLike" name="ch1PlugStateCdLike" class="form-control"></select>
</div>
</div>
@@ -8,52 +8,16 @@
<div class="row px-2">
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:160px">충전소 </span>
<span class="input-group-text" style="width:160px">충전소 아이디</span>
<input type="text" id="stIdLike" name="stIdLike" class="form-control" placeholder="충전소 명 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:160px">충전기 </span>
<span class="input-group-text" style="width:160px">충전기 아이디</span>
<input type="text" id="chgrIdLike" name="chgrIdLike" class="form-control" placeholder="충전기 명 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:160px">결제구분</span>
<input type="text" id="payTpLike" name="payTpLike" class="form-control" placeholder="결제구분 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:160px">충전기타입 코드</span>
<input type="text" id="plugTpLike" name="plugTpLike" class="form-control" placeholder="충전기타입 코드 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:160px">충전 시작일시</span>
<input type="text" id="rechgSDtLike" name="rechgSDtLike" class="form-control" placeholder="충전 시작일시 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:160px">충전 종료일시</span>
<input type="text" id="rechgEDtLike" name="rechgEDtLike" class="form-control" placeholder="충전 종료일시 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:160px">충전시간</span>
<input type="text" id="rechgTimeLike" name="rechgTimeLike" class="form-control" placeholder="충전시간 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:160px">신용결제완료구분</span>
<input type="text" id="payFnshLike" name="payFnshLike" class="form-control" placeholder="신용결제완료구분 입력" maxlength="50">
</div>
</div>
</div>
</div>
<div id="searchBoxFooter">
@@ -96,15 +60,18 @@
if (strUtil.isNotEmpty($('#rechgEDtLike').val())) {d.rechgEDt = $('#rechgEDtLike').val();}
if (strUtil.isNotEmpty($('#rechgTimeLike').val())) {d.rechgTime = $('#rechgTimeLike').val();}
if (strUtil.isNotEmpty($('#payFnshLike').val())) {d.payFnsh = $('#payFnshLike').val();}
if (strUtil.isNotEmpty($('#chIdLike').val())) {d.chId = $('#chIdLike').val();}
return d;
},
{
order: [],
order: [[5, 'desc']],
columns: [
{title: "충전소 ", name: "ST_NM", data: "stId", className, orderable: false},
{title: "충전기 ", name: "CHGR_NM", data: "chgrId", className, orderable: false},
{title: "충전소 아이디", name: "ST_ID", data: "stId", className, orderable: false},
{title: "충전기 아이디", name: "CHGR_ID", data: "chgrId", className, orderable: false},
{title: "결제구분", name: "PAY_TP", data: "payTp", className, orderable: false},
{title: "충전기타입 코드", name: "CHGR_TP", data: "plugTp", className, orderable: false},
{title: "채널", name: "CH_ID", data: "chId", className, orderable: false},
{title: "충전기타입 코드", name: "PLUG_TP", data: "plugTp", className, orderable: false},
{title: "충전 시작일시", name: "RECHG_S_DT", data: "rechgSDt", className, orderable: false},
{title: "충전 종료일시", name: "RECHG_E_DT", data: "rechgEDt", className, orderable: false},
{title: "충전시간", name: "RECHG_TIME", data: "rechgTime", className, orderable: false},
@@ -170,6 +137,17 @@
minLen: 0,
maxLen: 50,
}),
new ModalInput({
inputType: ModalInputType.text,
inputId: "chId",
inputName: "chId",
inputLabel: "채널",
inputPlaceholder: "채널",
isReq: false,
isEnable: true,
minLen: 0,
maxLen: 50,
}),
new ModalInput({
inputType: ModalInputType.textarea,
inputId: "payTp",
@@ -283,12 +261,6 @@
$("#resetBtn").click(function() {
$("#stIdLike").val("");
$("#chgrIdLike").val("");
$("#payTpLike").val("");
$("#plugTpLike").val("");
$("#rechgSDtLike").val("");
$("#rechgEDtLike").val("");
$("#rechgTimeLike").val("");
$("#payFnshLike").val("");
datatable.ajax.reload();
});
</script>
@@ -12,30 +12,6 @@
<input type="text" id="providerNmLike" name="providerNmLike" class="form-control" placeholder="충전사업자 명 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:140px">사업자번호</span>
<input type="text" id="bizTaxIdLike" name="bizTaxIdLike" class="form-control" placeholder="사업자번호 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:140px">대표자</span>
<input type="text" id="ceoLike" name="ceoLike" class="form-control" placeholder="대표자 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:140px">전화번호</span>
<input type="text" id="telLike" name="telLike" class="form-control" placeholder="전화번호 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:140px">팩스번호</span>
<input type="text" id="faxLike" name="faxLike" class="form-control" placeholder="팩스번호 입력" maxlength="50">
</div>
</div>
</div>
</div>
<div id="searchBoxFooter">
@@ -281,10 +257,6 @@
$("#resetBtn").click(function() {
$("#providerNmLike").val("");
$("#bizTaxIdLike").val("");
$("#ceoLike").val("");
$("#telLike").val("");
$("#faxLike").val("");
datatable.ajax.reload();
});
</script>
@@ -39,6 +39,9 @@
<div class="col-xs-12 ">
<button type="button" class="btn btn-sm btn-info" id="searchBtn">검색</button>
<button type="button" class="btn btn-sm btn-info" id="resetBtn">리셋</button>
<th:block th:if="${menuRole?.roleWrite eq true}">
<button type="button" class="btn btn-sm btn-primary pl-2" onclick="showModal(ModalMode.REGISTER, {}, '');">등록</button>
</th:block>
</div>
</div>
</div>
@@ -54,83 +57,60 @@
</div>
<style>
#ratePlanDetailScrollArea {
max-height: 65vh;
overflow-y: auto;
overflow-x: hidden;
padding-right: 5px;
}
#ratePlanDetailScrollArea { max-height: 65vh; overflow-y: auto; overflow-x: hidden; padding-right: 5px; }
#ratePlanDetailScrollArea::-webkit-scrollbar { width: 6px; }
#ratePlanDetailScrollArea::-webkit-scrollbar-track { background: #f5f5f5; border-radius: 3px; }
#ratePlanDetailScrollArea::-webkit-scrollbar-thumb { background: #d0d0d0; border-radius: 3px; }
#ratePlanDetailScrollArea::-webkit-scrollbar-thumb:hover { background: #b5b5b5; }
.custom-card {
border: 1px solid #e5e5e5;
border-radius: 0.25rem;
margin-bottom: 1rem;
background-color: #fff;
}
.custom-card-header {
color: #333;
font-weight: 600;
font-size: 0.88rem;
padding: 0.6rem 1rem;
border-bottom: 1px solid #e5e5e5;
background-color: #fafafa;
}
.custom-card-body {
padding: 0.75rem 1rem;
}
.info-table th, .info-table td {
padding: 0.15rem 0 !important;
font-size: 0.85rem;
color: #444;
}
.info-table th { font-weight: 600; width: 100px; color: #222; }
.custom-card { border: 1px solid #e5e5e5; border-radius: 0.25rem; margin-bottom: 1rem; background-color: #fff; }
.custom-card-header { color: #333; font-weight: 600; font-size: 0.88rem; padding: 0.6rem 1rem; border-bottom: 1px solid #e5e5e5; background-color: #fafafa; }
.custom-card-body { padding: 0.75rem 1rem; }
.info-table th, .info-table td { padding: 0.2rem 0.4rem !important; font-size: 0.85rem; color: #444; }
.info-table th { font-weight: 600; width: 110px; color: #222; white-space: nowrap; }
.info-table td { width: 100%; }
.price-table { font-size: 0.85rem; border-color: #e5e5e5; }
.price-table th, .price-table td { border-color: #e5e5e5; padding: 0.5rem; }
.price-table thead th { border-bottom-width: 1px; font-weight: 600; color: #555; }
.price-table thead th:first-child { color: #222; }
.val-price { color: #222; font-weight: 700; }
.val-unit { color: #999; font-size: 0.8rem; }
.charger-item {
border: 1px solid #e5e5e5;
border-radius: 0.35rem;
padding: 0.6rem;
background-color: #fff;
height: 100%;
}
.charger-item { border: 1px solid #e5e5e5; border-radius: 0.35rem; padding: 0.6rem; background-color: #fff; height: 100%; }
.charger-cd { font-size: 0.9rem; font-weight: bold; color: #222; margin-bottom: 0.1rem; }
.charger-dt { font-size: 0.75rem; color: #aaa; margin-bottom: 0.4rem; }
.badge-square {
background-color: #f0f0f0;
color: #333;
border: 1px solid #d8d8d8;
padding: 0.15rem 0.4rem;
font-size: 0.7rem;
border-radius: 2px;
font-weight: 500;
}
.badge-square.active {
background-color: #eef1fb;
color: #3b5bdb;
border-color: #d6dcf5;
}
.badge-square { background-color: #f0f0f0; color: #333; border: 1px solid #d8d8d8; padding: 0.15rem 0.4rem; font-size: 0.7rem; border-radius: 2px; font-weight: 500; }
.badge-square.active { background-color: #eef1fb; color: #3b5bdb; border-color: #d6dcf5; }
.approval-ok { color: #333; font-weight: 600; }
.approval-ok i { color: #3b5bdb; }
.season-tabs { display: flex; border-bottom: 1px solid #e5e5e5; margin-bottom: 0.75rem; }
.season-tab-btn { flex: 1; text-align: center; padding: 0.5rem 0; cursor: pointer; font-size: 0.85rem; color: #666; border-bottom: 2px solid transparent; background: none; border-top:none; border-left:none; border-right:none; }
.season-tab-btn.active { color: #3b5bdb; font-weight: 600; border-bottom-color: #3b5bdb; }
.time-mode-toggle { display: flex; border: 1px solid #e5e5e5; border-radius: 0.25rem; overflow: hidden; margin-bottom: 0.75rem; }
.time-mode-btn { flex: 1; text-align: center; padding: 0.4rem 0; cursor: pointer; font-size: 0.82rem; color: #666; background: #fafafa; }
.time-mode-btn.active { background: #3b5bdb; color: #fff; font-weight: 600; }
.basic-price-row { display: flex; align-items: center; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid #f2f2f2; }
.basic-price-row:last-child { border-bottom: none; }
.basic-price-label { font-size: 0.85rem; color: #333; }
.basic-price-input { width: 140px; text-align: right; }
.detail-price-row { display: flex; align-items: center; justify-content: space-between; padding: 0.4rem 0; border-bottom: 1px solid #f5f5f5; }
.detail-price-label { font-size: 0.82rem; color: #444; }
.detail-price-input { width: 140px; text-align: right; }
.detail-scroll { max-height: 260px; overflow-y: auto; padding-right: 4px; }
.charger-search-list { max-height: 260px; overflow-y: auto; border: 1px solid #eee; border-radius: 0.25rem; padding: 0.4rem; }
.charger-check-item { display: flex; align-items: flex-start; gap: 0.5rem; padding: 0.5rem; border: 1px solid #eee; border-radius: 0.3rem; margin-bottom: 0.4rem; background:#fff; }
.charger-check-item label { margin: 0; font-size: 0.82rem; }
</style>
<script th:inline="none">
const rootPath = "/cms/ratePlan/";
let datatable;
const BASIC_SLOTS = [
{ id: 1, label: "경부하 (22:00 - 08:00)", hours: [22,23,0,1,2,3,4,5,6,7] },
{ id: 2, label: "중간부하 (08:00 - 16:00)", hours: [8,9,10,11,12,13,14,15] },
{ id: 3, label: "최대부하 (16:00 - 22:00)", hours: [16,17,18,19,20,21] }
];
const SEASON_LABEL = { spring:"봄", summer:"여름", fall:"가을", winter:"겨울", all_year:"연중" };
$(document).ready(function() {
const className = "dt-head-center dt-body-center";
datatable = newDataTable(
@@ -145,24 +125,17 @@
{
order: [[0, 'desc']],
columns: [
{title: "요금제 ID", name: "RATE_PLAN_ID", data: "ratePlanId", className, width: "80px"},
{title: "요금제명", name: "RATE_PLAN_NAME", data: "ratePlanName", className: "dt-head-center dt-body-left"},
{title: "요금제 구분", name: "RATE_PLAN_TYPE", data: "ratePlanType", className,
render: (data) => data === 'time' ? '계시별 요금제' : data
},
{title: "적용 시작일", name: "APPLY_START_DATE", data: "applyStartDate", className, width: "120px"},
{title: "적용 종료일", name: "APPLY_END_DATE", data: "applyEndDate", className, width: "120px"},
{title: "승인 상태", name: "APPROVAL_STATUS", data: "approvalStatus", className, width: "100px",
render: (data) => data === 'approved' ? '<span class="text-success fw-bold"><i class="bi bi-check-lg"></i> 승인완료</span>' : '<span class="text-secondary">대기중</span>'
},
{title: "활성화", name: "IS_ACTIVE", data: "isActive", className, width: "80px",
render: (data) => data === "true" || data === 'Y' ? '<span class="text-primary fw-bold">활성</span>' : '<span class="text-secondary">비활성</span>'
},
{title: "기능", orderable:false, width:"80px",
render: (data, type, row) => {
return mkRowDataFunctions({ratePlanId: row['ratePlanId']}, row['ratePlanName'], true, false, false);
}
}
{title: "요금제 ID", name: "RATE_PLAN_ID", data: "ratePlanId", className, orderable:false, width: "80px"},
{title: "요금제명", name: "RATE_PLAN_NAME", data: "ratePlanName", orderable:false, className: "dt-head-center dt-body-left"},
{title: "요금제 구분", name: "RATE_PLAN_TYPE", data: "ratePlanType", orderable:false, className, render: (data) => data === 'time' ? '계시별 요금제' : data},
{title: "적용 시작일", name: "APPLY_START_DATE", data: "applyStartDate", orderable:false, className, width: "120px"},
{title: "적용 종료일", name: "APPLY_END_DATE", data: "applyEndDate", orderable:false, className, width: "120px"},
{title: "승인 상태", name: "APPROVAL_STATUS", data: "approvalStatus", orderable:false, className, width: "100px", render: (data) => data === 'approved' ? '<span class="text-success fw-bold"><i class="bi bi-check-lg"></i> 승인완료</span>' : '<span class="text-secondary">대기중</span>'},
{title: "활성화", name: "IS_ACTIVE", data: "isActive", orderable:false, className, width: "80px", render: (data) => {
let val = String(data || '').toLowerCase();
return (val === 'true' || val === 'y') ? '<span class="text-primary fw-bold">활성</span>' : '<span class="text-secondary">비활성</span>';
}},
{title: "기능", orderable:false, width:"120px", render: (data, type, row) => mkRowDataFunctions({ratePlanId: row['ratePlanId']}, row['ratePlanName'], true, true, true)}
],
allCheck: false,
}
@@ -172,105 +145,57 @@
const showModal = (mode, ids, title) => {
if(!checkRole(mode)) { alert("권한이 없습니다."); return; }
let modalInputArray = [
new ModalInput({
inputType: ModalInputType.customer,
inputId: "detailViewTemplate",
inputName: "detailViewTemplate",
options: () => {
return `
<div class="col-12 p-0" id="ratePlanDetailScrollArea">
<div class="custom-card">
<div class="custom-card-header">기본 정보</div>
<div class="custom-card-body">
<table class="table table-borderless mb-0 info-table">
<tr><th>요금제명:</th><td id="v_ratePlanName">-</td></tr>
<tr><th>요금제 구분:</th><td id="v_ratePlanType">-</td></tr>
<tr><th>적용 기간:</th><td id="v_applyDate">-</td></tr>
<tr><th>활성화 상태:</th><td id="v_isActive">-</td></tr>
<tr><th>설명:</th><td id="v_description">-</td></tr>
</table>
</div>
</div>
<div class="custom-card">
<div class="custom-card-header">승인 정보</div>
<div class="custom-card-body">
<table class="table table-borderless mb-0 info-table">
<tr><th>승인 상태:</th><td id="v_approvalStatus">-</td></tr>
</table>
</div>
</div>
<div class="custom-card">
<div class="custom-card-header">시간대별 단가 설정</div>
<div class="custom-card-body p-0">
<table class="table mb-0 text-center align-middle price-table">
<thead>
<tr>
<th class="border-end text-dark">시간대구분</th>
<th>봄 (Spring)</th>
<th>여름 (Summer)</th>
<th>가을 (Fall)</th>
<th>겨울 (Winter)</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border-end py-2">
<div class="fw-bold text-dark">경부하</div>
<div class="text-muted" style="font-size:0.7rem; letter-spacing: -0.5px;">(22:00~08:00)</div>
</td>
<td id="price_spring_4">-</td>
<td id="price_summer_4">-</td>
<td id="price_fall_4">-</td>
<td id="price_winter_4">-</td>
</tr>
<tr>
<td class="border-end py-2">
<div class="fw-bold text-dark">중간부하</div>
<div class="text-muted" style="font-size:0.7rem; letter-spacing: -0.5px;">(08:00~16:00)</div>
</td>
<td id="price_spring_12">-</td>
<td id="price_summer_12">-</td>
<td id="price_fall_12">-</td>
<td id="price_winter_12">-</td>
</tr>
<tr>
<td class="border-end py-2">
<div class="fw-bold text-dark">최대부하</div>
<div class="text-muted" style="font-size:0.7rem; letter-spacing: -0.5px;">(16:00~22:00)</div>
</td>
<td id="price_spring_20">-</td>
<td id="price_summer_20">-</td>
<td id="price_fall_20">-</td>
<td id="price_winter_20">-</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="custom-card mb-1">
<div class="custom-card-header" style="border-bottom: 1px solid #e5e5e5;">
적용 충전기 목록 (<span id="v_chargerCount">0</span>개)
</div>
<div class="custom-card-body pt-3 pb-2" style="background-color: #fcfcfc;">
<div class="row g-2" id="v_chargerListArea"></div>
</div>
</div>
</div>
`;
}
})
];
const loadUrl = rootPath + "view.json";
const modalWidth = 780;
const labelWidth = '1px';
if(mode === ModalMode.VIEW || mode === ModalMode.VIEW_ONLY) {
newModal(new ModalInfo({modalTitle: "요금제 상세보기", inputArray: modalInputArray, modalWidth}), mode, 'main', {
let viewInputArray = [
new ModalInput({
inputType: ModalInputType.customer,
inputId: "detailViewTemplate",
inputName: "detailViewTemplate",
options: () => {
return `
<div class="col-12 p-0" id="ratePlanDetailScrollArea">
<div class="custom-card">
<div class="custom-card-header">기본 정보</div>
<div class="custom-card-body">
<table class="table table-borderless mb-0 info-table">
<tr><th>요금제명:</th><td id="v_ratePlanName">-</td></tr>
<tr><th>요금제 구분:</th><td id="v_ratePlanType">-</td></tr>
<tr><th>계절 구분:</th><td id="v_seasonType">-</td></tr>
<tr><th>적용 기간:</th><td id="v_applyDate">-</td></tr>
<tr><th>활성화 상태:</th><td id="v_isActive">-</td></tr>
<tr><th>설명:</th><td id="v_description">-</td></tr>
</table>
</div>
</div>
<div class="custom-card">
<div class="custom-card-header">승인 정보</div>
<div class="custom-card-body">
<table class="table table-borderless mb-0 info-table">
<tr><th>승인 상태:</th><td id="v_approvalStatus">-</td></tr>
</table>
</div>
</div>
<div class="custom-card">
<div class="custom-card-header">시간대별 단가 설정</div>
<div class="custom-card-body p-0" id="v_priceTableArea"></div>
</div>
<div class="custom-card mb-1">
<div class="custom-card-header" style="border-bottom: 1px solid #e5e5e5;">적용 충전기 목록 (<span id="v_chargerCount">0</span>개)</div>
<div class="custom-card-body pt-3 pb-2" style="background-color: #fcfcfc;">
<div class="row g-2" id="v_chargerListArea"></div>
</div>
</div>
</div>
`;
}
})
];
newModal(new ModalInfo({modalTitle: "요금제 상세보기", inputArray: viewInputArray, modalWidth: modalWidth}), mode, 'main', {
loadUrl: loadUrl,
ids: ids,
labelWidth: labelWidth,
@@ -281,16 +206,17 @@
else if (param1 && param1.data) data = param1.data;
else if (param2 && param2.data) data = param2.data;
if(!data) {
console.error("데이터 추출 실패!");
return;
}
if(!data) { return; }
setTimeout(() => {
$('#v_ratePlanName').text(data.ratePlanName || '-');
$('#v_ratePlanType').text(data.ratePlanType === 'time' ? '계시별 요금제' : data.ratePlanType);
$('#v_seasonType').text(data.seasonType === 'season' ? '계절별' : '연중');
$('#v_applyDate').text(`${data.applyStartDate || ''} ~ ${data.applyEndDate || ''}`);
$('#v_isActive').text((data.isActive === "true" || data.isActive === 'Y' || data.active === true) ? '활성화' : '비활성');
let mainActiveVal = String(data.isActive || data.active || '').toLowerCase();
$('#v_isActive').text((mainActiveVal === 'true' || mainActiveVal === 'y') ? '활성화' : '비활성');
$('#v_description').text(data.description || '-');
if(data.approvalStatus === 'approved') {
@@ -299,24 +225,15 @@
$('#v_approvalStatus').text(data.approvalStatus || '대기중');
}
if (data.detailsTime && data.detailsTime.length > 0) {
data.detailsTime.forEach(seasonData => {
let season = seasonData.seasonType;
if(seasonData.timeRates) {
seasonData.timeRates.forEach(rate => {
$(`#price_${season}_${rate.id}`).html(`<span class="val-price">${rate.price}</span> <span class="val-unit">원/kWh</span>`);
});
}
});
}
renderViewPriceTable(data.detailsTime || []);
if (data.chargerList && data.chargerList.length > 0) {
$('#v_chargerCount').text(data.chargerList.length);
let chargerHtml = data.chargerList.map(c => {
let isActive = (c.isActive === true || c.active === true || c.isActive === 'Y');
let isActiveVal = String(c.isActive || c.active || '').toLowerCase();
let isActive = (isActiveVal === 'true' || isActiveVal === 'y');
let badgeClass = isActive ? 'badge-square active' : 'badge-square';
let badgeText = isActive ? '적용중' : '중지';
return `
<div class="col-6 col-sm-4 mb-2">
<div class="charger-item">
@@ -335,8 +252,314 @@
}
});
}
else if(mode === ModalMode.REGISTER) {
let registerInputArray = [
new ModalInput({
inputType: ModalInputType.customer,
inputId: "registerViewTemplate",
options: () => {
return `
<div class="col-12 p-0" id="ratePlanDetailScrollArea">
<div class="custom-card">
<div class="custom-card-header">기본 정보</div>
<div class="custom-card-body">
<table class="table table-borderless mb-0 info-table">
<tr><th>요금제명:</th><td><input type="text" id="i_ratePlanName" class="form-control form-control-sm" placeholder="예: 2026년 계시별 요금제"></td></tr>
<tr><th>요금제 구분:</th><td><select id="i_ratePlanType" class="form-control form-control-sm" disabled><option value="time" selected>계시별</option></select></td></tr>
<tr><th>계절 선택:</th><td>
<select id="i_seasonType" class="form-control form-control-sm">
<option value="season" selected>계절별</option>
<option value="all_year">연중</option>
</select>
</td></tr>
<tr><th>적용 시작일:</th><td><input type="date" id="i_applyStartDate" class="form-control form-control-sm"></td></tr>
<tr><th>적용 종료일:</th><td><input type="date" id="i_applyEndDate" class="form-control form-control-sm"></td></tr>
<tr><th>요금제 설명:</th><td><textarea id="i_description" class="form-control form-control-sm" rows="2"></textarea></td></tr>
</table>
</div>
</div>
<div class="custom-card">
<div class="custom-card-header">계시별 단가 설정</div>
<div class="custom-card-body">
<div id="i_seasonTabsArea"></div>
<div id="i_pricePanelsArea"></div>
</div>
</div>
<div class="custom-card mb-1">
<div class="custom-card-header">적용할 충전기 선택</div>
<div class="custom-card-body">
<div class="d-flex gap-2 mb-2">
<input type="text" id="i_chargerSearch" class="form-control form-control-sm" placeholder="충전기명 또는 위치로 검색...">
<select id="i_chargerFilter" class="form-control form-control-sm" style="max-width:120px;">
<option value="">전체</option>
<option value="완속">완속</option>
<option value="급속">급속</option>
<option value="초급속">초급속</option>
</select>
</div>
<div class="mb-2">
<input type="checkbox" id="i_chargerAll"> <label for="i_chargerAll" class="ms-1">전체 선택 (<span id="i_chargerAllCount">0</span>개)</label>
</div>
<div class="charger-search-list" id="i_chargerListArea"></div>
</div>
</div>
</div>
`;
}
})
];
newModal(new ModalInfo({modalTitle: "새 단가 정책 생성", inputArray: registerInputArray, modalWidth: modalWidth}), mode, 'main', {
labelWidth: '1px',
callInit: function() {
setTimeout(() => {
initRegisterForm();
$('.modal-footer button[name="approve"]').off('click').on('click', function(e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
if(confirm("등록하시겠습니까?")) {
submitRegisterForm();
}
});
}, 200);
}
});
}
};
let priceState = {};
let activeSeason = null;
function getSeasons() {
return $('#i_seasonType').val() === 'season' ? ['spring','summer','fall','winter'] : ['all_year'];
}
function initPriceStateFor(seasonKey) {
if (!priceState[seasonKey]) {
let basic = {}; BASIC_SLOTS.forEach(s => basic[s.id] = 0);
let detail = {}; for (let h = 0; h < 24; h++) detail[h] = 0;
priceState[seasonKey] = { mode: 'basic', basic, detail };
}
}
function initRegisterForm() {
priceState = {};
$('#i_seasonType').off('change').on('change', renderSeasonTabsAndPanel);
renderSeasonTabsAndPanel();
loadChargerList();
$('#i_chargerSearch, #i_chargerFilter').off('input change').on('input change', filterChargerList);
$('#i_chargerAll').off('change').on('change', function() {
$('#i_chargerListArea .charger-check-cb:visible').prop('checked', $(this).is(':checked'));
updateAllCheckState();
});
}
function renderSeasonTabsAndPanel() {
const seasons = getSeasons();
seasons.forEach(initPriceStateFor);
activeSeason = seasons[0];
let tabsHtml = '';
if (seasons.length > 1) {
tabsHtml = `<div class="season-tabs">` +
seasons.map((s, i) => `<button type="button" class="season-tab-btn ${i===0?'active':''}" data-season="${s}">${SEASON_LABEL[s]}</button>`).join('') +
`</div>`;
}
$('#i_seasonTabsArea').html(tabsHtml);
$('.season-tab-btn').off('click').on('click', function() {
saveCurrentPanelValues(activeSeason);
$('.season-tab-btn').removeClass('active');
$(this).addClass('active');
activeSeason = $(this).data('season');
renderPricePanel(activeSeason);
});
renderPricePanel(activeSeason);
}
function renderPricePanel(seasonKey) {
const state = priceState[seasonKey];
const modeToggleHtml = `
<div class="d-flex align-items-center gap-2 mb-2">
<div class="time-mode-toggle flex-grow-1">
<div class="time-mode-btn ${state.mode==='basic'?'active':''}" data-mode="basic">기본 시간대</div>
<div class="time-mode-btn ${state.mode==='detail'?'active':''}" data-mode="detail">세부 시간대</div>
</div>
</div>
`;
let bodyHtml;
if (state.mode === 'basic') {
bodyHtml = `<div id="i_basicPanel">` + BASIC_SLOTS.map(slot => `
<div class="basic-price-row">
<span class="basic-price-label">${slot.label}</span>
<div><input type="number" class="form-control form-control-sm basic-price-input i_basic_input" data-id="${slot.id}" value="${state.basic[slot.id]}"> 원/kWh</div>
</div>
`).join('') + `</div>`;
} else {
let rows = '';
for (let h = 0; h < 24; h++) {
const start = String(h).padStart(2,'0') + ':00';
const end = h === 23 ? '23:59' : String(h+1).padStart(2,'0') + ':00';
rows += `
<div class="detail-price-row">
<span class="detail-price-label">${String(h).padStart(2,'0')}시 (${start} - ${end})</span>
<div><input type="number" class="form-control form-control-sm detail-price-input i_detail_input" data-hour="${h}" value="${state.detail[h]}"> 원/kWh</div>
</div>`;
}
bodyHtml = `<div class="detail-scroll" id="i_detailPanel">${rows}</div>`;
}
$('#i_pricePanelsArea').html(modeToggleHtml + bodyHtml);
$('.time-mode-btn').off('click').on('click', function() {
saveCurrentPanelValues(seasonKey);
priceState[seasonKey].mode = $(this).data('mode');
renderPricePanel(seasonKey);
});
}
function saveCurrentPanelValues(seasonKey) {
const state = priceState[seasonKey];
if (state.mode === 'basic') {
$('.i_basic_input').each(function() { state.basic[$(this).data('id')] = parseFloat($(this).val()) || 0; });
} else {
$('.i_detail_input').each(function() { state.detail[$(this).data('hour')] = parseFloat($(this).val()) || 0; });
}
}
function loadChargerList() {
$.ajax({
url: "/cms/chgr/list.json", type: "POST", data: { start: 0, length: 9999 },
success: function(res) { renderChargerList(res.data || res.list || []); },
error: function() { $('#i_chargerListArea').html('<div class="text-muted small p-2">충전기 목록을 불러오지 못했습니다.</div>'); }
});
}
function renderChargerList(list) {
const mapped = list.map(c => ({
chargerCd: `${c.providerId || ''}${c.stId || ''}${c.chgrId || ''}`,
chargerName: c.chgrNm || `${c.providerId || ''}${c.stId || ''}${c.chgrId || ''}`,
location: c.stNm || '',
chargerType: c.speedTp || ''
}));
$('#i_chargerAllCount').text(mapped.length);
let html = mapped.map(c => `
<div class="charger-check-item" data-name="${c.chargerName.toLowerCase()}" data-loc="${c.location.toLowerCase()}" data-type="${c.chargerType}">
<input type="checkbox" class="charger-check-cb" value="${c.chargerCd}">
<label>
<div><strong>${c.chargerName}</strong> <span class="badge-square">${c.chargerType}</span></div>
<div class="text-muted" style="font-size:0.75rem;">${c.chargerCd} · ${c.location}</div>
</label>
</div>
`).join('');
$('#i_chargerListArea').html(html || '<div class="text-muted small p-2">등록된 충전기가 없습니다.</div>');
$('.charger-check-cb').off('change').on('change', updateAllCheckState);
}
function filterChargerList() {
const kw = ($('#i_chargerSearch').val() || '').toLowerCase();
const type = $('#i_chargerFilter').val();
$('.charger-check-item').each(function() {
const nameMatch = $(this).data('name').includes(kw) || $(this).data('loc').includes(kw);
const typeMatch = !type || $(this).data('type') === type;
$(this).toggle(nameMatch && typeMatch);
});
}
function updateAllCheckState() {
const total = $('.charger-check-cb:visible').length;
const checked = $('.charger-check-cb:visible:checked').length;
$('#i_chargerAll').prop('checked', total > 0 && total === checked);
}
function renderViewPriceTable(detailsTime) {
const seasonsSet = new Set(detailsTime.map(d => d.seasonType));
const seasons = seasonsSet.size ? Array.from(seasonsSet) : ['all_year'];
let priceMap = {};
let allIds = new Set();
detailsTime.forEach(d => {
(d.timeRates || []).forEach(r => {
priceMap[r.id] = priceMap[r.id] || {};
priceMap[r.id][d.seasonType] = r.price;
allIds.add(r.id);
});
});
const isBasic = [1, 2, 3].some(id => allIds.has(id));
let headerHtml = seasons.map(s => `<th>${SEASON_LABEL[s] || s}</th>`).join('');
let rows = '';
if (isBasic) {
BASIC_SLOTS.forEach(slot => {
rows += `<tr><td class="border-end py-2 fw-bold">${slot.label}</td>` +
seasons.map(s => `<td>${priceMap[slot.id] && priceMap[slot.id][s] != null ? `<span class="val-price">${priceMap[slot.id][s]}</span> <span class="val-unit">원/kWh</span>` : '-'}</td>`).join('') + `</tr>`;
});
} else {
for (let h = 0; h < 24; h++) {
const id = h + 4;
rows += `<tr><td class="border-end py-2">${String(h).padStart(2, '0')}시</td>` +
seasons.map(s => `<td>${priceMap[id] && priceMap[id][s] != null ? `<span class="val-price">${priceMap[id][s]}</span> <span class="val-unit">원/kWh</span>` : '-'}</td>`).join('') + `</tr>`;
}
}
$('#v_priceTableArea').html(`
<table class="table mb-0 text-center align-middle price-table">
<thead><tr><th class="border-end text-dark">시간대구분</th>${headerHtml}</tr></thead>
<tbody>${rows}</tbody>
</table>
`);
}
function submitRegisterForm() {
const seasons = getSeasons();
saveCurrentPanelValues(activeSeason);
let detailsTime = seasons.map(seasonKey => {
const state = priceState[seasonKey];
let timeRates = [];
if (state.mode === 'basic') {
BASIC_SLOTS.forEach(slot => timeRates.push({ id: slot.id, price: state.basic[slot.id] || 0 }));
} else {
for (let h = 0; h < 24; h++) timeRates.push({ id: h + 4, price: state.detail[h] || 0 });
}
return { seasonType: seasonKey, timeRates };
});
let chargerList = [];
$('.charger-check-cb:checked').each(function() {
chargerList.push({
chargerCd: $(this).val(),
applyStartDate: $('#i_applyStartDate').val(),
applyEndDate: $('#i_applyEndDate').val(),
isActive: true
});
});
let requestDto = {
ratePlanName: $('#i_ratePlanName').val(),
ratePlanType: "time",
seasonType: $('#i_seasonType').val(),
applyStartDate: $('#i_applyStartDate').val(),
applyEndDate: $('#i_applyEndDate').val(),
description: $('#i_description').val(),
isActive: true,
approvalStatus: "pending",
detailsTime: detailsTime,
chargerList: chargerList
};
if(!requestDto.ratePlanName) { alert("요금제명을 입력해주세요."); return; }
if(!requestDto.applyStartDate) { alert("적용 시작일을 입력해주세요."); return; }
if(!requestDto.applyEndDate) { alert("적용 종료일을 입력해주세요."); return; }
if(chargerList.length === 0) { alert("적용할 충전기를 1개 이상 선택해주세요."); return; }
$.ajax({
url: rootPath + "insert.json", type: "POST", contentType: "application/json; charset=utf-8", data: JSON.stringify(requestDto),
success: function(res) {
if(res.result === 'success') { alert("등록되었습니다."); } else { alert("오류 발생: " + res.message); }
$('.modal-footer .btn-cls-main').trigger('click');
datatable.ajax.reload();
},
error: function(err) {
alert("서버 통신 오류가 발생했습니다.");
$('.modal-footer .btn-cls-main').trigger('click');
}
});
}
$("#searchBtn").click(function() { datatable.ajax.reload(); });
$("#resetBtn").click(function() {
$("#ratePlanNameLike").val("");
@@ -6,12 +6,12 @@
<div th:replace="~{common/fragments/searchBox :: SearchBoxFragment(~{ :: #searchBoxContent}, ~{ :: #searchBoxFooter})}">
<div id="searchBoxContent">
<div class="row px-2">
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:300px">요청 일시</span>
<input type="text" id="searchDtLike" name="searchDtLike" class="form-control" placeholder="요청 일시 입력" maxlength="50">
</div>
</div>
<!-- <div class="col-lg-4 col-sm-6 col-xs-12 pb-1">-->
<!-- <div class="input-group">-->
<!-- <span class="input-group-text" style="width:300px">요청 일시</span>-->
<!-- <input type="text" id="searchDtLike" name="searchDtLike" class="form-control" placeholder="요청 일시 입력" maxlength="50">-->
<!-- </div>-->
<!-- </div>-->
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:300px">충전사업자 ID</span>
@@ -30,12 +30,12 @@
<input type="text" id="chgrIdLike" name="chgrIdLike" class="form-control" placeholder="충전기 ID 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:300px">플러그 타입</span>
<input type="text" id="plugTypeLike" name="plugTypeLike" class="form-control" placeholder="플러그 타입 입력" maxlength="50">
</div>
</div>
<!-- <div class="col-lg-4 col-sm-6 col-xs-12 pb-1">-->
<!-- <div class="input-group">-->
<!-- <span class="input-group-text" style="width:300px">플러그 타입</span>-->
<!-- <input type="text" id="plugTypeLike" name="plugTypeLike" class="form-control" placeholder="플러그 타입 입력" maxlength="50">-->
<!-- </div>-->
<!-- </div>-->
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:300px">회원카드번호</span>
@@ -44,34 +44,39 @@
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:300px">선결제 관련 신용카드 승인번호</span>
<input type="text" id="creditPPayTrxNoLike" name="creditPPayTrxNoLike" class="form-control" placeholder="선결제 관련 신용카드 승인번호 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:300px">선결제 관련 신용카드 승인일시</span>
<input type="text" id="creditPPayTrxDtLike" name="creditPPayTrxDtLike" class="form-control" placeholder="선결제 관련 신용카드 승인일시 입력" maxlength="50">
<span class="input-group-text" style="width:300px">신용카드 승인번호</span>
<input type="text" id="creditPPayTrxNoLike" name="creditPPayTrxNoLike" class="form-control" placeholder="신용카드 승인번호 입력" maxlength="50">
</div>
</div>
<!-- <div class="col-lg-4 col-sm-6 col-xs-12 pb-1">-->
<!-- <div class="input-group">-->
<!-- <span class="input-group-text" style="width:300px">신용카드 승인일시</span>-->
<!-- <input type="text" id="creditPPayTrxDtLike" name="creditPPayTrxDtLike" class="form-control" placeholder="신용카드 승인일시 입력" maxlength="50">-->
<!-- </div>-->
<!-- </div>-->
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:300px">충전 시작일시</span>
<input type="text" id="rechgSdtLike" name="rechgSdtLike" class="form-control" placeholder="충전 시작일시 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:300px">충전 잔여 시간</span>
<input type="text" id="rechgRemainTimeLike" name="rechgRemainTimeLike" class="form-control" placeholder="충전 잔여 시간 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:300px">결제타입</span>
<input type="text" id="payTypeLike" name="payTypeLike" class="form-control" placeholder="결제타입 입력" maxlength="50">
<input type="date" id="rechgSdtLike" name="rechgSdtLike" class="form-control" placeholder="충전 시작일시 입력" maxlength="50">
</div>
</div>
<!-- <div class="col-lg-4 col-sm-6 col-xs-12 pb-1">-->
<!-- <div class="input-group">-->
<!-- <span class="input-group-text" style="width:300px">충전 잔여 시간</span>-->
<!-- <input type="text" id="rechgRemainTimeLike" name="rechgRemainTimeLike" class="form-control" placeholder="충전 잔여 시간 입력" maxlength="50">-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="col-lg-4 col-sm-6 col-xs-12 pb-1">-->
<!-- <div class="input-group">-->
<!-- <span class="input-group-text" style="width:300px">결제타입</span>-->
<!-- <select id="payType" name="payType" class="form-select">-->
<!-- <option value="">전체</option>-->
<!-- <option value="회원카드">회원카드</option>-->
<!-- <option value="신용카드">신용카드</option>-->
<!-- <option value="무과금">무과금</option>-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
</div>
</div>
<div id="searchBoxFooter">
@@ -123,11 +128,11 @@
order: [[0, 'desc'], [2, 'desc'], [3, 'desc'], [4, 'desc']],
columns: [
{title: "충전중 ID", name: "RECHGING_LIST_ID", data: "rechgingListId", className, visible: false},
{title: "요청 일시", name: "SEARCH_DT", data: "searchDt", className, orderable: false},
// {title: "요청 일시", name: "SEARCH_DT", data: "searchDt", className, orderable: false},
{title: "충전사업자 ID", name: "PROVIDER_ID", data: "providerId", className},
{title: "충전소 ID", name: "ST_ID", data: "stId", className},
{title: "충전기 ID", name: "CHGR_ID", data: "chgrId", className},
{title: "채널 ID", name: "CH_ID", data: "chId", className, orderable: false},
// {title: "채널 ID", name: "CH_ID", data: "chId", className, orderable: false},
{title: "플러그 타입", name: "PLUG_TYPE", data: "plugType", className, orderable: false},
{title: "회원카드번호", name: "MEM_AUTH_INPUT_NO", data: "memAuthInputNo", className, orderable: false},
{title: "신용카드 승인번호", name: "CREDIT_P_PAY_TRX_NO", data: "creditPPayTrxNo", className, orderable: false},
@@ -138,21 +143,9 @@
{title: "선결제 금액", name: "RECHG_DEMAND_AMT", data: "rechgDemandAmt", className, orderable: false},
{title: "진행 충전 금액", name: "RECHGING_AMT", data: "rechgingAmt", className, orderable: false},
{title: "결제타입", name: "PAY_TYPE", data: "payType", className, orderable: false},
{title: "적산량", name: "INTEGRATED_WH", data: "integratedWh", className, orderable: false},
{title: "DC 적산량", name: "DC_POWER_WH", data: "dcPowerWh", className, orderable: false},
{title: "AC 적산량", name: "AC_POWER_WH", data: "acPowerWh", className, orderable: false},
{title: "현재 전압", name: "CURR_VOLT", data: "currVolt", className, orderable: false},
{title: "현재 전류", name: "CURR_C", data: "currC", className, orderable: false},
{title: "등록인ID", name: "REG_ID", data: "regIdNm", className, orderable: false},
{title: "등록일시", name: "REG_DT", data: "regDt", className, orderable: false,
render: (data, type, row) => dateUtil.toYmd(row["regDt"]) },
{title: "수정인ID", name: "UPD_ID", data: "updIdNm", className, orderable: false},
{title: "수정일시", name: "UPD_DT", data: "updDt", className, orderable: false,
render: (data, type, row) => dateUtil.toYmd(row["updDt"]) },
{title: "삭제인ID", name: "DEL_ID", data: "delIdNm", className, orderable: false},
{title: "삭제일시", name: "DEL_DT", data: "delDt", className, orderable: false,
render: (data, type, row) => dateUtil.toYmd(row["delDt"]) },
{title: "사용상태", name: "REC_ST", data: "recSt", className, orderable: false}
// {title: "기능", orderable:false, width:"110px",
// render: (data, type, row) => {
// return mkRowDataFunctions({rechgingListId: row['rechgingListId'], providerId: row['providerId'], stId: row['stId'], chgrId: row['chgrId']}, row['#####Title'], true, menuRoleModify, menuRoleDel);}}
],
allCheck: false,
});
@@ -247,22 +240,22 @@
maxLen: 20,
}),
new ModalInput({
inputType: ModalInputType.text,
inputType: ModalInputType.textarea,
inputId: "creditPPayTrxNo",
inputName: "creditPPayTrxNo",
inputLabel: "선결제 관련 신용카드 승인번호",
inputPlaceholder: "선결제 관련 신용카드 승인번호",
inputLabel: "신용카드 승인번호",
inputPlaceholder: "신용카드 승인번호",
isReq: false,
isEnable: true,
minLen: 0,
maxLen: 20,
maxLen: 250,
}),
new ModalInput({
inputType: ModalInputType.textarea,
inputId: "creditPPayTrxDt",
inputName: "creditPPayTrxDt",
inputLabel: "선결제 관련 신용카드 승인일시",
inputPlaceholder: "선결제 관련 신용카드 승인일시",
inputLabel: "신용카드 승인일시",
inputPlaceholder: "신용카드 승인일시",
isReq: false,
isEnable: true,
minLen: 0,
@@ -368,8 +361,8 @@
inputType: ModalInputType.text,
inputId: "currC",
inputName: "currC",
inputLabel: "현재 류",
inputPlaceholder: "현재 류",
inputLabel: "현재 류",
inputPlaceholder: "현재 류",
isReq: false,
isEnable: true,
})
@@ -380,7 +373,7 @@
const loadUrl = rootPath+"view.json";
const deleteUrl = rootPath+"delete.json";
const modalWidth = 500;
const labelWidth = '90px';
const labelWidth = '100px';
if(mode === ModalMode.REGISTER) {
newModal(new ModalInfo({modalTitle: "등록", inputArray: modalInputArray, modalWidth}), mode, 'main', {insertUrl, labelWidth, callBack: () => { defaultCallback(); }})
} else if(mode===ModalMode.VIEW || mode===ModalMode.VIEW_ONLY || mode===ModalMode.MODIFY) {
@@ -18,24 +18,6 @@
<input type="text" id="addrLdMLike" name="addrLdMLike" class="form-control" placeholder="주소 지번 메인 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:200px">법정동 도 명</span>
<input type="text" id="pnuDoNmLike" name="pnuDoNmLike" class="form-control" placeholder="법정동 도 명 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:200px">법정동 시 명</span>
<input type="text" id="pnuSiNmLike" name="pnuSiNmLike" class="form-control" placeholder="법정동 시 명 입력" maxlength="50">
</div>
</div>
<div class="col-lg-4 col-sm-6 col-xs-12 pb-1">
<div class="input-group">
<span class="input-group-text" style="width:200px">법정동 읍면동 명</span>
<input type="text" id="pnuDongNmLike" name="pnuDongNmLike" class="form-control" placeholder="법정동 읍면동 명 입력" maxlength="50">
</div>
</div>
</div>
</div>
<div id="searchBoxFooter">