@@ -1,6 +1,9 @@ | |||||
package com.ffii.fpsms.api.service | package com.ffii.fpsms.api.service | ||||
import com.ffii.core.utils.JwtTokenUtil | |||||
import com.ffii.fpsms.m18.M18Config | import com.ffii.fpsms.m18.M18Config | ||||
import org.slf4j.Logger | |||||
import org.slf4j.LoggerFactory | |||||
import org.springframework.beans.factory.annotation.Value | import org.springframework.beans.factory.annotation.Value | ||||
import org.springframework.http.HttpHeaders | import org.springframework.http.HttpHeaders | ||||
import org.springframework.http.MediaType | 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 org.springframework.web.reactive.function.client.bodyToMono | ||||
import reactor.core.publisher.Mono | import reactor.core.publisher.Mono | ||||
import kotlin.reflect.full.memberProperties | 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 | @Service | ||||
open class ApiCallerService( | open class ApiCallerService( | ||||
@Value("\${m18.config.base-url}") private val baseUrl: String, | @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() | val webClient: WebClient = WebClient.builder() | ||||
.baseUrl(baseUrl) | .baseUrl(baseUrl) | ||||
.defaultHeaders { headers -> | .defaultHeaders { headers -> | ||||
headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) | 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) | 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() | .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( | inline fun <reified T : Any> get( | ||||
urlPath: String, | urlPath: String, | ||||
params: MultiValueMap<String, String>, | params: MultiValueMap<String, String>, | ||||
customHeaders: Map<String, String>? | customHeaders: Map<String, String>? | ||||
): Mono<T> { | ): Mono<T> { | ||||
println("ACCESS TOKEN: ${m18Config.ACCESS_TOKEN}") | |||||
return webClient.get() | return webClient.get() | ||||
.uri { uriBuilder -> | .uri { uriBuilder -> | ||||
uriBuilder | uriBuilder | ||||
@@ -49,8 +74,30 @@ open class ApiCallerService( | |||||
} | } | ||||
.retrieve() | .retrieve() | ||||
.bodyToMono(T::class.java) | .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( | inline fun <reified T : Any> get( | ||||
urlPath: String, | urlPath: String, | ||||
params: Map<String, Any>?, | params: Map<String, Any>?, | ||||
@@ -72,7 +119,29 @@ open class ApiCallerService( | |||||
return get<T>(urlPath, queryParams, customHeaders) | 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( | inline fun <reified T : Any, reified U : Any> get( | ||||
urlPath: String, | urlPath: String, | ||||
params: U?, | params: U?, | ||||
@@ -95,4 +164,21 @@ open class ApiCallerService( | |||||
return get<T>(urlPath, queryParams, customHeaders) | 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 | package com.ffii.fpsms.m18 | ||||
import org.springframework.beans.factory.annotation.Value | 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.Bean | ||||
import org.springframework.context.annotation.Configuration | import org.springframework.context.annotation.Configuration | ||||
@Configuration | @Configuration | ||||
open class M18Config { | open class M18Config { | ||||
// Account | |||||
@Value("\${m18.config.grant-type}") | @Value("\${m18.config.grant-type}") | ||||
lateinit var GRANT_TYPE: String; | lateinit var GRANT_TYPE: String; | ||||
@@ -22,5 +25,55 @@ open class M18Config { | |||||
@Value("\${m18.config.password}") | @Value("\${m18.config.password}") | ||||
lateinit var PASSWORD: String; | 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; | 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() | |||||
} | |||||
} |