diff --git a/src/main/java/com/ffii/fpsms/api/service/ApiCallerService.kt b/src/main/java/com/ffii/fpsms/api/service/ApiCallerService.kt index a8d742d..f42f7a2 100644 --- a/src/main/java/com/ffii/fpsms/api/service/ApiCallerService.kt +++ b/src/main/java/com/ffii/fpsms/api/service/ApiCallerService.kt @@ -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 get( urlPath: String, params: MultiValueMap, customHeaders: Map? ): Mono { - + 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 get( urlPath: String, params: Map?, @@ -72,7 +119,29 @@ open class ApiCallerService( return get(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 get( + urlPath: String, + params: Map? + ): Mono { + return get(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 get( urlPath: String, params: U?, @@ -95,4 +164,21 @@ open class ApiCallerService( return get(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 get( + urlPath: String, + params: U?, + ): Mono { + + return get(urlPath, params, null) + } + } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/m18/M18Config.kt b/src/main/java/com/ffii/fpsms/m18/M18Config.kt index 161895c..dedcb89 100644 --- a/src/main/java/com/ffii/fpsms/m18/M18Config.kt +++ b/src/main/java/com/ffii/fpsms/m18/M18Config.kt @@ -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"); + */ } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/m18/model/M18MasterDataRequest.kt b/src/main/java/com/ffii/fpsms/m18/model/M18MasterDataRequest.kt new file mode 100644 index 0000000..288eeaa --- /dev/null +++ b/src/main/java/com/ffii/fpsms/m18/model/M18MasterDataRequest.kt @@ -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, +) diff --git a/src/main/java/com/ffii/fpsms/m18/model/M18MasterDataResponse.kt b/src/main/java/com/ffii/fpsms/m18/model/M18MasterDataResponse.kt new file mode 100644 index 0000000..c8d293f --- /dev/null +++ b/src/main/java/com/ffii/fpsms/m18/model/M18MasterDataResponse.kt @@ -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? +) + +data class M18ItemData ( + val pro: List? +) + +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?, +) + +data class M18ItemListValue ( + val id: Long, + val lastModifyDate: String?, +) + +/** Vendor Response */ +data class M18VendorResponse ( + val data: M18VendorData?, + val messages: List? +) + +data class M18VendorData ( + val ven: List? +) + +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?, +) + +data class M18VendorListValue ( + val id: Long, + val lastModifyDate: String?, +) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/m18/model/M18PurchaseOrderRequest.kt b/src/main/java/com/ffii/fpsms/m18/model/M18PurchaseOrderRequest.kt new file mode 100644 index 0000000..5cd95bb --- /dev/null +++ b/src/main/java/com/ffii/fpsms/m18/model/M18PurchaseOrderRequest.kt @@ -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, +) diff --git a/src/main/java/com/ffii/fpsms/m18/model/M18PurchaseOrderResponse.kt b/src/main/java/com/ffii/fpsms/m18/model/M18PurchaseOrderResponse.kt new file mode 100644 index 0000000..87a9f57 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/m18/model/M18PurchaseOrderResponse.kt @@ -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, + val pot: List +) + +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 +) + +data class M18PurchaseOrderListValue ( + val id: Long, + val code: String, + val lastModifyDate: String, +) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/m18/service/M18DataLogService.kt b/src/main/java/com/ffii/fpsms/m18/service/M18DataLogService.kt new file mode 100644 index 0000000..a019e2c --- /dev/null +++ b/src/main/java/com/ffii/fpsms/m18/service/M18DataLogService.kt @@ -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 + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/m18/service/M18MasterDataService.kt b/src/main/java/com/ffii/fpsms/m18/service/M18MasterDataService.kt new file mode 100644 index 0000000..d6dd603 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/m18/service/M18MasterDataService.kt @@ -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( + 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( + M18_LOAD_ITEM_API, + itemParams + ).block() + + return item + } + + open fun saveItems() { + + val items = getItems() + val exampleItems = listOf(10946L, 3825L) + + val successList = mutableListOf() + val failList = mutableListOf() + + 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( +// 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( + 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( + M18_LOAD_VENDOR_API, + vendorParams + ).block() + + return vendor + } + + open fun saveVendors() { + val vendors = getVendors() + val exampleVendors = listOf(191L) + + val successList = mutableListOf() + val failList = mutableListOf() + + 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") + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/m18/service/M18PurchaseOrderService.kt b/src/main/java/com/ffii/fpsms/m18/service/M18PurchaseOrderService.kt new file mode 100644 index 0000000..1ab8f6c --- /dev/null +++ b/src/main/java/com/ffii/fpsms/m18/service/M18PurchaseOrderService.kt @@ -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( + 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( + 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(4764034L) + + val successList = mutableListOf() + val successDetailList = mutableListOf() + val failList = mutableListOf() + val failDetailList = mutableListOf() + + 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") + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/m18/utils/CommonUtils.kt b/src/main/java/com/ffii/fpsms/m18/utils/CommonUtils.kt new file mode 100644 index 0000000..dd49b1d --- /dev/null +++ b/src/main/java/com/ffii/fpsms/m18/utils/CommonUtils.kt @@ -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, prefix: String, delimiter: String): String { + return numbers.joinToString(delimiter) { "$prefix$it" } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/m18/web/M18TestController.kt b/src/main/java/com/ffii/fpsms/m18/web/M18TestController.kt new file mode 100644 index 0000000..69c9532 --- /dev/null +++ b/src/main/java/com/ffii/fpsms/m18/web/M18TestController.kt @@ -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() + } +} \ No newline at end of file