| @@ -1,6 +1,9 @@ | |||
| package com.ffii.fpsms.api.service | |||
| import com.ffii.core.utils.JwtTokenUtil | |||
| import com.ffii.fpsms.m18.M18Config | |||
| import org.slf4j.Logger | |||
| import org.slf4j.LoggerFactory | |||
| import org.springframework.beans.factory.annotation.Value | |||
| import org.springframework.http.HttpHeaders | |||
| import org.springframework.http.MediaType | |||
| @@ -11,28 +14,50 @@ import org.springframework.web.reactive.function.client.WebClient | |||
| import org.springframework.web.reactive.function.client.bodyToMono | |||
| import reactor.core.publisher.Mono | |||
| import kotlin.reflect.full.memberProperties | |||
| import org.springframework.cloud.context.config.annotation.RefreshScope | |||
| import org.springframework.http.HttpStatus | |||
| import org.springframework.web.reactive.function.client.ClientRequest | |||
| import org.springframework.web.reactive.function.client.WebClientResponseException | |||
| import org.springframework.web.reactive.function.client.awaitBody | |||
| @Service | |||
| open class ApiCallerService( | |||
| @Value("\${m18.config.base-url}") private val baseUrl: String, | |||
| private val m18Config: M18Config | |||
| open val m18Config: M18Config | |||
| ) { | |||
| val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java) | |||
| val webClient: WebClient = WebClient.builder() | |||
| .baseUrl(baseUrl) | |||
| .defaultHeaders { headers -> | |||
| headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) | |||
| headers.set(HttpHeaders.AUTHORIZATION, "Bearer ${m18Config.ACCESS_TOKEN}") | |||
| // headers.set(HttpHeaders.AUTHORIZATION, "Bearer ${m18Config.ACCESS_TOKEN}") | |||
| headers.set("client_id", m18Config.CLIENT_ID) | |||
| } | |||
| .filter { request, next -> | |||
| // Dynamically fetch the latest ACCESS_TOKEN | |||
| val updatedRequest = ClientRequest.from(request) | |||
| .header(HttpHeaders.AUTHORIZATION, "Bearer ${m18Config.ACCESS_TOKEN}") | |||
| .build() | |||
| next.exchange(updatedRequest) | |||
| } | |||
| .codecs { configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024) } | |||
| .build() | |||
| /** | |||
| * Performs a GET HTTP request to the specified URL path. | |||
| * | |||
| * @param urlPath The path to send the GET request to (after /jsf/rfws) | |||
| * @param params Optional query parameters to include in the request | |||
| * @param customHeaders Optional custom headers to include in the request. The default header includes CONTENT_TYPE, AUTHORIZATION, client_id | |||
| * @return A Mono that emits the response body converted to type T | |||
| */ | |||
| inline fun <reified T : Any> get( | |||
| urlPath: String, | |||
| params: MultiValueMap<String, String>, | |||
| customHeaders: Map<String, String>? | |||
| ): Mono<T> { | |||
| println("ACCESS TOKEN: ${m18Config.ACCESS_TOKEN}") | |||
| return webClient.get() | |||
| .uri { uriBuilder -> | |||
| uriBuilder | |||
| @@ -49,8 +74,30 @@ open class ApiCallerService( | |||
| } | |||
| .retrieve() | |||
| .bodyToMono(T::class.java) | |||
| .doOnError { error -> | |||
| println("Error occurred: ${error.message}") | |||
| } | |||
| .onErrorResume(WebClientResponseException::class.java) { error -> | |||
| logger.error("WebClientResponseException") | |||
| logger.error("Error Status: ${error.statusCode} - ${error.statusText}") | |||
| logger.error("Error Message: ${error.message}") | |||
| Mono.error(error) | |||
| } | |||
| .onErrorResume { error -> | |||
| logger.error("Exception") | |||
| logger.error("Error Message: ${error.message}") | |||
| Mono.error(error) | |||
| } | |||
| } | |||
| /** | |||
| * Performs a GET HTTP request to the specified URL path. | |||
| * | |||
| * @param urlPath The path to send the GET request to (after /jsf/rfws) | |||
| * @param params Optional query parameters to include in the request | |||
| * @param customHeaders Optional custom headers to include in the request. The default header includes CONTENT_TYPE, AUTHORIZATION, client_id | |||
| * @return A Mono that emits the response body converted to type T | |||
| */ | |||
| inline fun <reified T : Any> get( | |||
| urlPath: String, | |||
| params: Map<String, Any>?, | |||
| @@ -72,7 +119,29 @@ open class ApiCallerService( | |||
| return get<T>(urlPath, queryParams, customHeaders) | |||
| } | |||
| // T: Response, U: Request | |||
| /** | |||
| * Performs a GET HTTP request to the specified URL path. | |||
| * | |||
| * @param urlPath The path to send the GET request to (after /jsf/rfws) | |||
| * @param params Optional query parameters to include in the request | |||
| * @return A Mono that emits the response body converted to type T | |||
| */ | |||
| inline fun <reified T : Any> get( | |||
| urlPath: String, | |||
| params: Map<String, Any>? | |||
| ): Mono<T> { | |||
| return get<T>(urlPath, params, null) | |||
| } | |||
| /** | |||
| * Performs a GET HTTP request to the specified URL path. | |||
| * T: Response, U: Request | |||
| * | |||
| * @param urlPath The path to send the GET request to (after /jsf/rfws) | |||
| * @param params Optional query parameters to include in the request | |||
| * @param customHeaders Optional custom headers to include in the request. The default header includes CONTENT_TYPE, AUTHORIZATION, client_id | |||
| * @return A Mono that emits the response body converted to type T | |||
| */ | |||
| inline fun <reified T : Any, reified U : Any> get( | |||
| urlPath: String, | |||
| params: U?, | |||
| @@ -95,4 +164,21 @@ open class ApiCallerService( | |||
| return get<T>(urlPath, queryParams, customHeaders) | |||
| } | |||
| /** | |||
| * Performs a GET HTTP request to the specified URL path. | |||
| * T: Response, U: Request | |||
| * | |||
| * @param urlPath The path to send the GET request to (after /jsf/rfws) | |||
| * @param params Optional query parameters to include in the request | |||
| * @return A Mono that emits the response body converted to type T | |||
| */ | |||
| inline fun <reified T : Any, reified U : Any> get( | |||
| urlPath: String, | |||
| params: U?, | |||
| ): Mono<T> { | |||
| return get<T, U>(urlPath, params, null) | |||
| } | |||
| } | |||
| @@ -1,12 +1,15 @@ | |||
| package com.ffii.fpsms.m18 | |||
| import org.springframework.beans.factory.annotation.Value | |||
| import org.springframework.cloud.context.config.annotation.RefreshScope | |||
| import org.springframework.context.annotation.Bean | |||
| import org.springframework.context.annotation.Configuration | |||
| @Configuration | |||
| open class M18Config { | |||
| // Account | |||
| @Value("\${m18.config.grant-type}") | |||
| lateinit var GRANT_TYPE: String; | |||
| @@ -22,5 +25,55 @@ open class M18Config { | |||
| @Value("\${m18.config.password}") | |||
| lateinit var PASSWORD: String; | |||
| // Series | |||
| @Value("\${m18.config.seriesId.pp}") | |||
| var SERIESID_PP: Long? = null; | |||
| @Value("\${m18.config.seriesId.pf}") | |||
| var SERIESID_PF: Long? = null; | |||
| @Value("\${m18.config.seriesId.sc}") | |||
| var SERIESID_SC: Long? = null; | |||
| @Value("\${m18.config.seriesId.se}") | |||
| var SERIESID_SE: Long? = null; | |||
| @Value("\${m18.config.seriesId.sf}") | |||
| var SERIESID_SF: Long? = null; | |||
| @Value("\${m18.config.seriesId.sr}") | |||
| var SERIESID_SR: Long? = null; | |||
| // BE | |||
| @Value("\${m18.config.beId.pp}") | |||
| var BEID_PP: Long? = null; | |||
| @Value("\${m18.config.beId.pf}") | |||
| var BEID_PF: Long? = null; | |||
| @Value("\${m18.config.beId.toa}") | |||
| var BEID_TOA: Long? = null; | |||
| // Fetch | |||
| var ACCESS_TOKEN: String? = null; | |||
| /** | |||
| * Condition Detail | |||
| * Conds Format: | |||
| * Id=lessThan=5=and=id=largerOrEqual=3=or=(name=contains =ss=or=name=contains=bb) | |||
| * Which means: | |||
| * Id<5 and id >=3 or (name like ‘%ss%’ or name like ‘%bb%’) | |||
| * **Please use these formats to write the conds: ** | |||
| * ("equal", "="), | |||
| * ("unequal", "<>"), | |||
| * ("largerThan", ">"), | |||
| * ("lessThan", "<"), | |||
| * ("largerOrEqual", ">="), | |||
| * ("lessOrEqual", "<="), | |||
| * ("contains", "like"), | |||
| * ("doseNotContain", "notlike"), | |||
| * ("in", "in"), | |||
| * ("notIn", "notin"), | |||
| * ("startWith", "like"), | |||
| * ("endWith", "like"); | |||
| */ | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| package com.ffii.fpsms.m18.model | |||
| /** Product / Material Request */ | |||
| data class M18ItemRequest ( | |||
| val id: Long, | |||
| val params: String? = null, | |||
| ) | |||
| /** Product / Material List Request */ | |||
| data class M18ItemListRequest ( | |||
| val stSearch: String = "pro", | |||
| val params: String? = null, | |||
| val conds: String? = null, | |||
| ) | |||
| /** Vendor Request */ | |||
| data class M18VendorRequest ( | |||
| val id: Long, | |||
| val params: String? = null, | |||
| ) | |||
| /** Vendor List Request */ | |||
| data class M18VendorListRequest ( | |||
| val stSearch: String = "ven", | |||
| val params: String? = null, | |||
| val conds: String? = null, | |||
| ) | |||
| /** Customer Request */ | |||
| data class M18CustomerRequest ( | |||
| val id: Long, | |||
| val params: String? = null, | |||
| ) | |||
| /** Customer List Request */ | |||
| data class M18CustomerListRequest ( | |||
| val stSearch: String = "cus", | |||
| val params: String? = null, | |||
| val conds: String? = null, | |||
| ) | |||
| @@ -0,0 +1,75 @@ | |||
| package com.ffii.fpsms.m18.model | |||
| import java.time.Instant | |||
| import java.time.LocalDateTime | |||
| /** Error Messages */ | |||
| data class M18ErrorMessages ( | |||
| val msgDetail: String?, | |||
| val msgCode: String?, | |||
| ) | |||
| /** Product / Material Response */ | |||
| data class M18ItemResponse ( | |||
| val data: M18ItemData?, | |||
| val messages: List<M18ErrorMessages>? | |||
| ) | |||
| data class M18ItemData ( | |||
| val pro: List<M18ItemPro>? | |||
| ) | |||
| data class M18ItemPro ( | |||
| val id: Long, | |||
| val code: String, | |||
| val desc: String, | |||
| val unitId: Long, | |||
| val seriesId: Long, | |||
| val lastModifyDate: Long, | |||
| ) | |||
| /** Product / Material List Response */ | |||
| data class M18ItemListResponse ( | |||
| val values: List<M18ItemListValue>?, | |||
| ) | |||
| data class M18ItemListValue ( | |||
| val id: Long, | |||
| val lastModifyDate: String?, | |||
| ) | |||
| /** Vendor Response */ | |||
| data class M18VendorResponse ( | |||
| val data: M18VendorData?, | |||
| val messages: List<M18ErrorMessages>? | |||
| ) | |||
| data class M18VendorData ( | |||
| val ven: List<M18VendorVen>? | |||
| ) | |||
| data class M18VendorVen ( | |||
| val id: Long, | |||
| val code: String, | |||
| /** name */ | |||
| val desc: String, | |||
| val `desc_zh-TW`: String, | |||
| /** contactNo */ | |||
| val tel: String, | |||
| val email: String, | |||
| val ad1: String, | |||
| val ad2: String, | |||
| val ad3: String, | |||
| val ad4: String, | |||
| val lastModifyDate: Long, | |||
| ) | |||
| /** Vendor List Response */ | |||
| data class M18VendorListResponse ( | |||
| val values: List<M18VendorListValue>?, | |||
| ) | |||
| data class M18VendorListValue ( | |||
| val id: Long, | |||
| val lastModifyDate: String?, | |||
| ) | |||
| @@ -0,0 +1,14 @@ | |||
| package com.ffii.fpsms.m18.model | |||
| data class M18PurchaseOrderRequest( | |||
| val menuCode: String = "po", | |||
| val id: Long?, | |||
| val params: String? = null, | |||
| val conds: String? = null, | |||
| ) | |||
| data class M18PurchaseOrderListRequest( | |||
| val stSearch: String = "po", | |||
| val params: String? = null, | |||
| val conds: String? = null, | |||
| ) | |||
| @@ -0,0 +1,49 @@ | |||
| package com.ffii.fpsms.m18.model | |||
| import java.math.BigDecimal | |||
| import java.time.Instant | |||
| import java.time.LocalDateTime | |||
| /** Purchase Order Response */ | |||
| data class M18PurchaseOrderResponse ( | |||
| val data: M18PurchaseOrderData | |||
| ) | |||
| data class M18PurchaseOrderData ( | |||
| val mainPo: List<M18PurchaseOrderMainPo>, | |||
| val pot: List<M18PurchaseOrderPot> | |||
| ) | |||
| data class M18PurchaseOrderMainPo ( | |||
| val id: Long, | |||
| val code: String, | |||
| /** Supplier Id */ | |||
| val venId: Long, | |||
| /** ETA */ | |||
| val dDate: Long, | |||
| /** Order Date */ | |||
| val tDate: Long, | |||
| val lastModifyDate: Long | |||
| ) | |||
| data class M18PurchaseOrderPot ( | |||
| val id: Long, | |||
| val hId: Long, | |||
| val code: String, | |||
| val desc: String, | |||
| val unitId: Long, | |||
| val seriesId: Long, | |||
| val qty: BigDecimal, | |||
| val amt: BigDecimal, | |||
| ) | |||
| /** Purchase Order List Response */ | |||
| data class M18PurchaseOrderListResponse ( | |||
| val values: List<M18PurchaseOrderListValue> | |||
| ) | |||
| data class M18PurchaseOrderListValue ( | |||
| val id: Long, | |||
| val code: String, | |||
| val lastModifyDate: String, | |||
| ) | |||
| @@ -0,0 +1,42 @@ | |||
| package com.ffii.fpsms.m18.service | |||
| import com.ffii.fpsms.m18.entity.M18DataLog | |||
| import com.ffii.fpsms.m18.entity.M18DataLogRepository | |||
| import com.ffii.fpsms.m18.model.M18DataLogResponse | |||
| import com.ffii.fpsms.m18.model.SaveM18DataLogRequest | |||
| import org.springframework.stereotype.Service | |||
| import kotlin.jvm.optionals.getOrDefault | |||
| @Service | |||
| class M18DataLogService( | |||
| val m18DataLogRepository: M18DataLogRepository | |||
| ) { | |||
| fun findLatestM18DataLog(m18Id: Long, refType: String): M18DataLog? { | |||
| return m18DataLogRepository.findFirstByM18IdAndRefTypeAndDeletedIsFalseOrderByM18LastModifyDateDesc(m18Id, refType) | |||
| } | |||
| fun saveM18DataLog(request: SaveM18DataLogRequest): M18DataLogResponse { | |||
| val id = request.id | |||
| val m18DataLog = | |||
| if (id != null && id > 0) m18DataLogRepository.findById(id).getOrDefault(M18DataLog()) else M18DataLog() | |||
| m18DataLog.apply { | |||
| refType = request.refType | |||
| m18Id = request.m18Id | |||
| m18LastModifyDate = request.m18LastModifyDate | |||
| dataLog = request.dataLog | |||
| status = request.status | |||
| } | |||
| val response = m18DataLogRepository.saveAndFlush(m18DataLog).let { dataLog -> | |||
| M18DataLogResponse( | |||
| id = dataLog.id, | |||
| refType = dataLog.refType, | |||
| m18Id = dataLog.m18Id, | |||
| status = dataLog.status, | |||
| ) | |||
| } | |||
| return response | |||
| } | |||
| } | |||
| @@ -0,0 +1,268 @@ | |||
| package com.ffii.fpsms.m18.service | |||
| import com.ffii.core.utils.JwtTokenUtil | |||
| import com.ffii.fpsms.api.service.ApiCallerService | |||
| import com.ffii.fpsms.m18.M18Config | |||
| import com.ffii.fpsms.m18.model.* | |||
| import com.ffii.fpsms.m18.utils.CommonUtils | |||
| import com.ffii.fpsms.modules.master.enums.ShopType | |||
| import com.ffii.fpsms.modules.master.service.ItemsService | |||
| import com.ffii.fpsms.modules.master.service.ShopService | |||
| import com.ffii.fpsms.modules.master.web.models.ItemType | |||
| import com.ffii.fpsms.modules.master.web.models.NewItemRequest | |||
| import com.ffii.fpsms.modules.master.web.models.SaveShopRequest | |||
| import org.slf4j.Logger | |||
| import org.slf4j.LoggerFactory | |||
| import org.springframework.stereotype.Service | |||
| import java.time.LocalDate | |||
| import java.time.format.DateTimeFormatter | |||
| @Service | |||
| open class M18MasterDataService( | |||
| val m18Config: M18Config, | |||
| val apiCallerService: ApiCallerService, | |||
| val itemsService: ItemsService, | |||
| val shopService: ShopService, | |||
| ) { | |||
| val commonUtils = CommonUtils() | |||
| val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java) | |||
| // Everyday update the master data | |||
| val lastModifyDate = LocalDate.now().minusDays(1) | |||
| val lastModifyDateConds = "lastModifyDate=largerThan=$lastModifyDate" | |||
| val seriesIdList = | |||
| listOf(m18Config.SERIESID_SC, m18Config.SERIESID_SE, m18Config.SERIESID_SF, m18Config.SERIESID_SR) | |||
| val seriesIdConds = "(" + commonUtils.ListToString(seriesIdList.filterNotNull(), "seriesId=unequal=", "=or=") + ")" | |||
| val beIdList = listOf(m18Config.BEID_PF, m18Config.BEID_PP, m18Config.BEID_TOA) | |||
| val beIdConds = "(" + commonUtils.ListToString(beIdList.filterNotNull(), "beId=equal=", "=or=") + ")" | |||
| // val commonConds =seriesIdConds + beIdConds | |||
| // "(beId=equal=${m18Config.BEID_PF}=or=beId=equal=${m18Config.BEID_PP}=or=beId=equal=${m18Config.BEID_TOA})" | |||
| val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") | |||
| // M18 API | |||
| val M18_LOAD_ITEM_API = "/root/api/read/pro" | |||
| val M18_FETCH_ITEM_LIST_API = "/search/search" | |||
| val M18_LOAD_VENDOR_API = "/root/api/read/ven" | |||
| val M18_FETCH_VENDOR_LIST_API = "/search/search" | |||
| // --------------------------------------------- Item --------------------------------------------- /// | |||
| open fun getItems(): M18ItemListResponse? { | |||
| // seems no beId | |||
| val itemsParams = M18ItemListRequest( | |||
| params = null, | |||
| conds=seriesIdConds | |||
| // conds=commonConds | |||
| // conds = "lastModifyDate=largerThan=$lastModifyDate" | |||
| ) | |||
| val items = apiCallerService.get<M18ItemListResponse, M18ItemListRequest>( | |||
| M18_FETCH_ITEM_LIST_API, | |||
| itemsParams | |||
| ).block() | |||
| return items | |||
| } | |||
| open fun getItem(id: Long): M18ItemResponse? { | |||
| logger.info("M18 Item ID: $id") | |||
| val itemParams = M18ItemRequest( | |||
| id = id, | |||
| params = null, | |||
| ) | |||
| val item = apiCallerService.get<M18ItemResponse, M18ItemRequest>( | |||
| M18_LOAD_ITEM_API, | |||
| itemParams | |||
| ).block() | |||
| return item | |||
| } | |||
| open fun saveItems() { | |||
| val items = getItems() | |||
| val exampleItems = listOf<Long>(10946L, 3825L) | |||
| val successList = mutableListOf<Long>() | |||
| val failList = mutableListOf<Long>() | |||
| if (items?.values != null) { | |||
| items.values.forEach { item -> | |||
| // if (item.id in exampleItems) { | |||
| try { | |||
| val itemDetail = getItem(item.id) | |||
| if (itemDetail != null && itemDetail.data?.pro != null) { | |||
| val pro = itemDetail.data.pro[0] | |||
| val saveItemRequest = NewItemRequest( | |||
| code = pro.code, | |||
| name = pro.desc, | |||
| // type = if (pro.seriesId == m18Config.SERIESID_PF) ItemType.MATERIAL | |||
| // else ItemType.PRODUCT, | |||
| type = ItemType.MATERIAL, | |||
| id = null, | |||
| description = pro.desc, | |||
| remarks = null, | |||
| shelfLife = null, | |||
| countryOfOrigin = null, | |||
| maxQty = null, | |||
| m18Id = item.id, | |||
| m18LastModifyDate = commonUtils.InstantToLocalDateTime(pro.lastModifyDate) | |||
| ) | |||
| itemsService.saveItem(saveItemRequest) | |||
| successList.add(item.id) | |||
| logger.info("Success Count ${successList.size}: ${item.id} | ${pro.code} | ${pro.desc}") | |||
| } else { | |||
| failList.add(item.id) | |||
| logger.error("Fail Message: ${itemDetail?.messages?.get(0)?.msgDetail}") | |||
| logger.error("Fail Count ${failList.size}: Item ID ${item.id} Not Found") | |||
| } | |||
| } catch (e: Exception) { | |||
| failList.add(item.id) | |||
| logger.error("M18 Item Data: ${e.message}") | |||
| logger.error("Fail Count ${failList.size}: Item ID ${item.id} Not Found") | |||
| } | |||
| // val itemParams = M18ItemRequest( | |||
| // id = item.id, | |||
| // params = null | |||
| // ) | |||
| // apiCallerService.get<M18ItemResponse, M18ItemRequest>( | |||
| // M18_LOAD_ITEM_API, | |||
| // itemParams | |||
| // ).subscribe( | |||
| // { response -> | |||
| // val pro = response.data.pro[0] | |||
| //// when (pro.seriesId) { | |||
| //// m18Config.SERIESID_PF, m18Config.SERIESID_PP -> { | |||
| // val saveItemRequest = NewItemRequest( | |||
| // code = pro.code, | |||
| // name = pro.desc, | |||
| // type = if (pro.seriesId == m18Config.SERIESID_PF) ItemType.MATERIAL | |||
| // else ItemType.PRODUCT, | |||
| // id = null, | |||
| // description = null, | |||
| // remarks = null, | |||
| // shelfLife = null, | |||
| // countryOfOrigin = null, | |||
| // maxQty = null, | |||
| // m18Id = pro.id | |||
| // ) | |||
| // | |||
| // itemsService.saveItem(saveItemRequest) | |||
| //// } | |||
| //// } | |||
| // | |||
| // logger.info("Count ${++count}: ${pro.id} | ${pro.code} | ${pro.desc}") | |||
| // }, | |||
| // { error -> logger.error("WebClient Error: ${error.message}") } | |||
| // ) | |||
| // } | |||
| } | |||
| } else { | |||
| logger.error("Items List is null. May occur errors.") | |||
| } | |||
| logger.info("Total Success (${successList.size}): $successList") | |||
| if (failList.size > 0) { | |||
| logger.error("Total Fail (${failList.size}): $failList") | |||
| } | |||
| } | |||
| // --------------------------------------------- Vendor --------------------------------------------- /// | |||
| open fun getVendors(): M18VendorListResponse? { | |||
| val vendorsParams = M18VendorListRequest( | |||
| params = null, | |||
| conds = beIdConds | |||
| // conds = "lastModifyDate=largerThan=$lastModifyDate" | |||
| ) | |||
| val vendors = apiCallerService.get<M18VendorListResponse, M18VendorListRequest>( | |||
| M18_FETCH_VENDOR_LIST_API, | |||
| vendorsParams | |||
| ).block() | |||
| return vendors | |||
| } | |||
| open fun getVendor(id: Long): M18VendorResponse? { | |||
| logger.info("M18 Vendor ID: $id") | |||
| val vendorParams = M18VendorRequest( | |||
| id = id, | |||
| params = null, | |||
| ) | |||
| val vendor = apiCallerService.get<M18VendorResponse, M18VendorRequest>( | |||
| M18_LOAD_VENDOR_API, | |||
| vendorParams | |||
| ).block() | |||
| return vendor | |||
| } | |||
| open fun saveVendors() { | |||
| val vendors = getVendors() | |||
| val exampleVendors = listOf<Long>(191L) | |||
| val successList = mutableListOf<Long>() | |||
| val failList = mutableListOf<Long>() | |||
| if (vendors?.values != null) { | |||
| vendors.values.forEach { vendor -> | |||
| // if (vendor.id in exampleVendors) { | |||
| try { | |||
| val vendorDetail = getVendor(vendor.id) | |||
| if (vendorDetail != null && vendorDetail.data?.ven != null) { | |||
| val ven = vendorDetail.data.ven[0] | |||
| val saveShopRequest = SaveShopRequest( | |||
| id = null, | |||
| code = ven.code, | |||
| name = ven.desc.ifEmpty { ven.`desc_zh-TW` }, | |||
| brNo = null, | |||
| contactNo = ven.tel, | |||
| contactEmail = ven.email, | |||
| contactName = null, | |||
| addr1 = ven.ad1, | |||
| addr2 = ven.ad2, | |||
| addr3 = ven.ad3, | |||
| addr4 = ven.ad4, | |||
| district = null, | |||
| type = ShopType.SUPPLIER.value, | |||
| m18Id = vendor.id, | |||
| m18LastModifyDate = commonUtils.InstantToLocalDateTime(ven.lastModifyDate) | |||
| ) | |||
| shopService.saveShop(saveShopRequest) | |||
| successList.add(vendor.id) | |||
| logger.info("Success Count ${successList.size}: ${vendor.id} | ${ven.code} | ${ven.desc}") | |||
| } else { | |||
| failList.add(vendor.id) | |||
| logger.error("Fail Message: ${vendorDetail?.messages?.get(0)?.msgDetail}") | |||
| logger.error("Fail Count ${failList.size}: Vendor ID ${vendor.id} Not Found") | |||
| } | |||
| } catch (e: Exception) { | |||
| failList.add(vendor.id) | |||
| logger.error("M18 Vendor Data: ${e.message}") | |||
| logger.error("Fail Count ${failList.size}: Vendor ID ${vendor.id} Not Found") | |||
| } | |||
| // } | |||
| } | |||
| } else { | |||
| logger.error("Items List is null. May occur errors.") | |||
| } | |||
| logger.info("Total Success (${successList.size}): $successList") | |||
| if (failList.size > 0) { | |||
| logger.error("Total Fail (${failList.size}): $failList") | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,268 @@ | |||
| package com.ffii.fpsms.m18.service | |||
| import com.ffii.core.utils.JwtTokenUtil | |||
| import com.ffii.fpsms.api.service.ApiCallerService | |||
| import com.ffii.fpsms.m18.M18Config | |||
| import com.ffii.fpsms.m18.model.* | |||
| import com.ffii.fpsms.m18.utils.CommonUtils | |||
| import com.ffii.fpsms.modules.master.service.ItemsService | |||
| import com.ffii.fpsms.modules.master.service.ShopService | |||
| import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderLineStatus | |||
| import com.ffii.fpsms.modules.purchaseOrder.enums.PurchaseOrderStatus | |||
| import com.ffii.fpsms.modules.purchaseOrder.service.PurchaseOrderLineService | |||
| import com.ffii.fpsms.modules.purchaseOrder.service.PurchaseOrderService | |||
| import com.ffii.fpsms.modules.purchaseOrder.web.model.SavePurchaseOrderLineRequest | |||
| import com.ffii.fpsms.modules.purchaseOrder.web.model.SavePurchaseOrderRequest | |||
| import org.slf4j.Logger | |||
| import org.slf4j.LoggerFactory | |||
| import org.springframework.stereotype.Service | |||
| import java.time.LocalDate | |||
| import java.time.LocalDateTime | |||
| import kotlin.reflect.full.memberProperties | |||
| @Service | |||
| open class M18PurchaseOrderService( | |||
| val m18Config: M18Config, | |||
| val apiCallerService: ApiCallerService, | |||
| val m18DataLogService: M18DataLogService, | |||
| val purchaseOrderService: PurchaseOrderService, | |||
| val purchaseOrderLineService: PurchaseOrderLineService, | |||
| val itemsService: ItemsService, | |||
| val shopService: ShopService, | |||
| ) { | |||
| val dateTimeConverter = CommonUtils() | |||
| val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java) | |||
| val lastModifyDate = LocalDate.now().minusDays(1) | |||
| val commonConds="(beId=equal=${m18Config.BEID_PF}=or=beId=equal=${m18Config.BEID_PP}=or=beId=equal=${m18Config.BEID_TOA})=and=lastModifyDate=largerOrEqual=${lastModifyDate}" | |||
| // M18 API | |||
| val M18_LOAD_PURCHASE_ORDER_API = "/root/api/read/po" | |||
| val M18_FETCH_PURCHASE_ORDER_LIST_API = "/search/search" | |||
| open fun getPurchaseOrders(): M18PurchaseOrderListResponse? { | |||
| val purchaseOrdersParams = M18PurchaseOrderListRequest( | |||
| params = null, | |||
| conds = commonConds | |||
| ) | |||
| var purchaseOrders: M18PurchaseOrderListResponse? = null | |||
| try { | |||
| purchaseOrders = apiCallerService.get<M18PurchaseOrderListResponse, M18PurchaseOrderListRequest>( | |||
| M18_FETCH_PURCHASE_ORDER_LIST_API, | |||
| purchaseOrdersParams | |||
| ).block() | |||
| } catch (e: Exception) { | |||
| logger.error("Error on Function - ${e.stackTrace}") | |||
| logger.error(e.message) | |||
| } | |||
| return purchaseOrders | |||
| } | |||
| open fun getPurchaseOrder(id: Long): M18PurchaseOrderResponse? { | |||
| val purchaseOrderParams = M18PurchaseOrderRequest( | |||
| id = id | |||
| ) | |||
| var purchaseOrder: M18PurchaseOrderResponse? = null | |||
| try { | |||
| purchaseOrder = apiCallerService.get<M18PurchaseOrderResponse, M18PurchaseOrderRequest>( | |||
| M18_LOAD_PURCHASE_ORDER_API, | |||
| purchaseOrderParams | |||
| ).block() | |||
| } catch (e: Exception) { | |||
| logger.error("Error on Function - ${e.stackTrace}") | |||
| logger.error(e.message) | |||
| } | |||
| return purchaseOrder | |||
| } | |||
| open fun savePurchaseOrders() { | |||
| val purchaseOrders = getPurchaseOrders() | |||
| val examplePurchaseOrders = listOf<Long>(4764034L) | |||
| val successList = mutableListOf<Long>() | |||
| val successDetailList = mutableListOf<Long>() | |||
| val failList = mutableListOf<Long>() | |||
| val failDetailList = mutableListOf<Long>() | |||
| if (purchaseOrders != null) { | |||
| // Loop for Purchase Orders (values) | |||
| purchaseOrders.values.forEach { purchaseOrder -> | |||
| val purchaseOrderDetail = getPurchaseOrder(purchaseOrder.id) | |||
| var purchaseOrderId: Long? = null //FP-MTMS | |||
| // Process for Purchase Order (mainPo) | |||
| // Assume only one PO in the PO (search by PO ID) | |||
| val mainPo = purchaseOrderDetail?.data?.mainPo?.get(0) | |||
| val pot = purchaseOrderDetail?.data?.pot | |||
| // purchase_order + m18_data_log table | |||
| if (mainPo != null) { | |||
| val poRefType = "Purchase Order" | |||
| // Find the latest m18 data log by m18 id & type | |||
| val latestM18DataLog = m18DataLogService.findLatestM18DataLog(purchaseOrder.id, poRefType) | |||
| // Save to m18_data_log table | |||
| val mainPoJson = | |||
| mainPo::class.memberProperties.associate { prop -> prop.name to prop.getter.call(mainPo) } | |||
| .toMutableMap() | |||
| val saveM18PurchaseOrderLogRequest = SaveM18DataLogRequest( | |||
| id = null, | |||
| refType = poRefType, | |||
| m18Id = purchaseOrder.id, | |||
| m18LastModifyDate = dateTimeConverter.InstantToLocalDateTime(mainPo.lastModifyDate), | |||
| dataLog = mainPoJson, | |||
| status = true | |||
| ) | |||
| val saveM18PurchaseOrderLog = m18DataLogService.saveM18DataLog(saveM18PurchaseOrderLogRequest) | |||
| try { | |||
| // Find the purchase_order if exist | |||
| val existingPurchaseOrder = latestM18DataLog?.id?.let { purchaseOrderService.findPurchaseOrderByM18Id(it) } | |||
| // Save to purchase_order table | |||
| val supplierId = shopService.findByM18Id(mainPo.venId)?.id | |||
| val savePurchaseOrderRequest = SavePurchaseOrderRequest( | |||
| id = existingPurchaseOrder?.id, | |||
| code = mainPo.code, | |||
| supplierId = supplierId, | |||
| orderDate = dateTimeConverter.InstantToLocalDateTime(mainPo.tDate), | |||
| estimatedArrivalDate = dateTimeConverter.InstantToLocalDateTime(mainPo.dDate), | |||
| completeDate = null, | |||
| status = PurchaseOrderStatus.PENDING.value, | |||
| m18DataLogId = saveM18PurchaseOrderLog.id, | |||
| ) | |||
| val savePurchaseOrderResponse = purchaseOrderService.savePurchaseOrder(savePurchaseOrderRequest) | |||
| purchaseOrderId = savePurchaseOrderResponse.id | |||
| successList.add(purchaseOrder.id) | |||
| } catch (e: Exception) { | |||
| failList.add(purchaseOrder.id) | |||
| logger.error("Error on Function - ${e.stackTrace} | Type: Purchase Order | M18 ID: ${purchaseOrder.id} | Different? ${mainPo.id}") | |||
| logger.error(e.message) | |||
| val errorSaveM18PurchaseOrderLogRequest = SaveM18DataLogRequest( | |||
| id = saveM18PurchaseOrderLogRequest.id, | |||
| refType = "Purchase Order", | |||
| m18Id = purchaseOrder.id, | |||
| m18LastModifyDate = dateTimeConverter.InstantToLocalDateTime(mainPo.lastModifyDate), | |||
| dataLog = mainPoJson, | |||
| status = false | |||
| ) | |||
| m18DataLogService.saveM18DataLog(errorSaveM18PurchaseOrderLogRequest) | |||
| } | |||
| // purchase_order_line + m18_data_log | |||
| if (pot != null) { | |||
| // Loop for Purchase Order Lines (pot) | |||
| pot.forEach { line -> | |||
| val poLineRefType = "Purchase Order Line" | |||
| // Find the latest m18 data log by m18 id & type | |||
| val latestM18DataLog = m18DataLogService.findLatestM18DataLog(line.id, poLineRefType) | |||
| // Save to m18_data_log table | |||
| val lineJson = | |||
| line::class.memberProperties.associate { prop -> prop.name to prop.getter.call(line) } | |||
| .toMutableMap() | |||
| val saveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest( | |||
| id = null, | |||
| refType = poLineRefType, | |||
| m18Id = line.id, | |||
| m18LastModifyDate = dateTimeConverter.InstantToLocalDateTime(mainPo.lastModifyDate), | |||
| dataLog = lineJson, | |||
| status = true | |||
| ) | |||
| val saveM18PurchaseOrderLineLog = m18DataLogService.saveM18DataLog(saveM18PurchaseOrderLineLogRequest) | |||
| val item = itemsService.findByM18Id(line.id) | |||
| logger.info("Item ID: ${item?.id}") | |||
| try { | |||
| // Find the purchase_order_line if exist | |||
| val existingPurchaseOrderLine = latestM18DataLog?.id?.let { purchaseOrderLineService.findPurchaseOrderLineByM18Id(it) } | |||
| // Save to purchase_order_line table | |||
| val savePurchaseOrderLineRequest = SavePurchaseOrderLineRequest( | |||
| id = existingPurchaseOrderLine?.id, | |||
| itemId = item?.id, | |||
| uomId = null, | |||
| purchaseOrderId = purchaseOrderId, | |||
| qty = line.qty, | |||
| price = line.amt, | |||
| priceUnit = null, | |||
| status = existingPurchaseOrderLine?.status?.value ?: PurchaseOrderLineStatus.PENDING.value, | |||
| m18DataLogId = saveM18PurchaseOrderLineLog.id, | |||
| ) | |||
| purchaseOrderLineService.savePurchaseOrderLine(savePurchaseOrderLineRequest) | |||
| } catch (e: Exception) { | |||
| failDetailList.add(line.id) | |||
| logger.error("Error on Function - ${e.stackTrace} | Type: Purchase Order Line | M18 ID: ${line.id}") | |||
| logger.error(e.message) | |||
| val errorSaveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest( | |||
| id = saveM18PurchaseOrderLineLog.id, | |||
| refType = "Purchase Order", | |||
| m18Id = line.id, | |||
| m18LastModifyDate = dateTimeConverter.InstantToLocalDateTime(mainPo.lastModifyDate), | |||
| dataLog = lineJson, | |||
| status = false | |||
| ) | |||
| m18DataLogService.saveM18DataLog(errorSaveM18PurchaseOrderLineLogRequest) | |||
| } | |||
| } | |||
| } else { | |||
| val saveM18PurchaseOrderLineLogRequest = SaveM18DataLogRequest( | |||
| id = null, | |||
| refType = "Purchase Order Line", | |||
| m18Id = purchaseOrder.id, | |||
| m18LastModifyDate = dateTimeConverter.InstantToLocalDateTime(mainPo.lastModifyDate), | |||
| dataLog = mutableMapOf(Pair("Error Message", "Purchase Order Line is null")), | |||
| status = false | |||
| ) | |||
| m18DataLogService.saveM18DataLog(saveM18PurchaseOrderLineLogRequest) | |||
| } | |||
| } else { | |||
| val saveM18DataLogRequest = SaveM18DataLogRequest( | |||
| id = null, | |||
| refType = "Purchase Order", | |||
| m18Id = purchaseOrder.id, | |||
| // m18LastModifyDate = if(mainPo?.lastModifyDate != null) dateTimeConverter.InstantToLocalDateTime(mainPo.lastModifyDate) else LocalDateTime.now(), | |||
| m18LastModifyDate = LocalDateTime.now(), | |||
| dataLog = mutableMapOf(Pair("Error Message", "Purchase Order is null")), | |||
| status = false | |||
| ) | |||
| m18DataLogService.saveM18DataLog(saveM18DataLogRequest) | |||
| } | |||
| } | |||
| } else { | |||
| logger.error("Purchase Order List is null. May occur errors.") | |||
| } | |||
| // End of save. Check result | |||
| logger.info("Total Success (Purchase Order) (${successList.size}): $successList") | |||
| if (failList.size > 0) { | |||
| logger.error("Total Fail (Purchase Order) (${failList.size}): $failList") | |||
| } | |||
| logger.info("Total Success (Purchase Order Line) (${successDetailList.size}): $successDetailList") | |||
| if (failDetailList.size > 0) { | |||
| logger.error("Total Fail (Purchase Order Line) (${failDetailList.size}): $failDetailList") | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,20 @@ | |||
| package com.ffii.fpsms.m18.utils | |||
| import java.time.Instant | |||
| import java.time.LocalDateTime | |||
| import java.time.ZoneId | |||
| open class CommonUtils() { | |||
| open fun InstantToLocalDateTime(timestamp: Long):LocalDateTime { | |||
| val localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.of("Asia/Hong_Kong")) | |||
| println("ZoneId: ${ZoneId.systemDefault()}") | |||
| println("ZoneId: ${ZoneId.of("Asia/Hong_Kong")}") | |||
| println("Timestamp: $timestamp") | |||
| println("Local Date Time: $localDateTime") | |||
| return localDateTime | |||
| } | |||
| open fun ListToString(numbers: List<Long>, prefix: String, delimiter: String): String { | |||
| return numbers.joinToString(delimiter) { "$prefix$it" } | |||
| } | |||
| } | |||
| @@ -0,0 +1,42 @@ | |||
| package com.ffii.fpsms.m18.web | |||
| import com.ffii.core.utils.JwtTokenUtil | |||
| import com.ffii.fpsms.m18.M18Config | |||
| import com.ffii.fpsms.m18.service.M18MasterDataService | |||
| import com.ffii.fpsms.m18.service.M18PurchaseOrderService | |||
| import org.slf4j.Logger | |||
| import org.slf4j.LoggerFactory | |||
| import org.springframework.web.bind.annotation.GetMapping | |||
| import org.springframework.web.bind.annotation.RequestMapping | |||
| import org.springframework.web.bind.annotation.RestController | |||
| @RestController | |||
| @RequestMapping("/m18") | |||
| class M18TestController ( | |||
| private val m18MasterDataService: M18MasterDataService, | |||
| private val m18PurchaseOrderService: M18PurchaseOrderService, | |||
| private val m18Config: M18Config, | |||
| ) { | |||
| var logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java) | |||
| // --------------------------------------------- Master Data --------------------------------------------- /// | |||
| @GetMapping("/item") | |||
| fun m18Items() { | |||
| logger.info("Access token: ${m18Config.ACCESS_TOKEN}") | |||
| m18MasterDataService.saveItems() | |||
| } | |||
| @GetMapping("/vendor") | |||
| fun m18Vendor() { | |||
| logger.info("Access token: ${m18Config.ACCESS_TOKEN}") | |||
| m18MasterDataService.saveVendors() | |||
| } | |||
| // --------------------------------------------- Purchase Order --------------------------------------------- /// | |||
| @GetMapping("/po") | |||
| fun m18PO() { | |||
| logger.info("Access token: ${m18Config.ACCESS_TOKEN}") | |||
| m18PurchaseOrderService.savePurchaseOrders() | |||
| } | |||
| } | |||