Переглянути джерело

fixing the auto gen Job order, adding the GRN

master
vluk@2fi-solutions.com.hk 3 дні тому
джерело
коміт
8015a0f951
6 змінених файлів з 188 додано та 9 видалено
  1. +41
    -0
      src/main/java/com/ffii/fpsms/api/service/ApiCallerService.kt
  2. +40
    -0
      src/main/java/com/ffii/fpsms/m18/model/GoodsReceiptNoteRequest.kt
  3. +15
    -0
      src/main/java/com/ffii/fpsms/m18/model/GoodsReceiptNoteResponse.kt
  4. +72
    -0
      src/main/java/com/ffii/fpsms/m18/service/M18GoodsReceiptNoteService.kt
  5. +3
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt
  6. +17
    -9
      src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt

+ 41
- 0
src/main/java/com/ffii/fpsms/api/service/ApiCallerService.kt Переглянути файл

@@ -326,4 +326,45 @@ open class ApiCallerService(
.doBeforeRetry { signal -> logger.info("Retrying due to: ${signal.failure().message}") }
)
}

// ------------------------------------ PUT ------------------------------------ //
/**
* Performs a PUT HTTP request to the specified URL path with query parameters and JSON body.
*
* @param urlPath The path to send the PUT request to (e.g. /root/api/save/an)
* @param queryParams Query parameters (e.g. menuCode=an, param optional)
* @param body The request body object (serialized as JSON)
* @param customHeaders Optional custom headers
* @return A Mono that emits the response body converted to type T
*/
inline fun <reified T : Any> put(
urlPath: String,
queryParams: MultiValueMap<String, String>,
body: Any,
customHeaders: Map<String, String>? = null
): Mono<T> {
return webClient.put()
.uri { uriBuilder -> uriBuilder.path(urlPath).queryParams(queryParams).build() }
.headers { headers -> customHeaders?.forEach { (k, v) -> headers.set(k, v) } }
.bodyValue(body)
.retrieve()
.bodyToMono(T::class.java)
.doOnError { error -> logger.error("PUT error: ${error.message}") }
.onErrorResume(WebClientResponseException::class.java) { error ->
logger.error("WebClientResponseException: ${error.statusCode} - ${error.statusText}, Body: ${error.responseBodyAsString}")
if (error.statusCode == HttpStatusCode.valueOf(400) || error.statusCode == HttpStatusCode.valueOf(401)) {
updateToken().flatMap { newToken ->
webClient.put()
.uri { uriBuilder -> uriBuilder.path(urlPath).queryParams(queryParams).build() }
.header(HttpHeaders.AUTHORIZATION, "Bearer $newToken")
.headers { headers -> customHeaders?.forEach { (k, v) -> headers.set(k, v) } }
.bodyValue(body)
.retrieve()
.bodyToMono(T::class.java)
}
} else {
Mono.error(error)
}
}
}
}

+ 40
- 0
src/main/java/com/ffii/fpsms/m18/model/GoodsReceiptNoteRequest.kt Переглянути файл

@@ -0,0 +1,40 @@
package com.ffii.fpsms.m18.model

/**
* Request body for M18 Goods Receipt Note (AN) save API.
* PUT /root/api/save/an?menuCode=an
*/
data class GoodsReceiptNoteRequest(
val mainan: GoodsReceiptNoteMainan,
val ant: GoodsReceiptNoteAnt,
)

data class GoodsReceiptNoteMainan(
val values: List<GoodsReceiptNoteMainanValue>,
)

data class GoodsReceiptNoteMainanValue(
val beId: Int,
val code: String,
val venId: Int,
val curId: Int,
val rate: Number,
val flowTypeId: Int,
val staffId: Int,
)

data class GoodsReceiptNoteAnt(
val values: List<GoodsReceiptNoteAntValue>,
)

data class GoodsReceiptNoteAntValue(
val sourceType: String,
val sourceId: Long,
val sourceLot: String,
val proId: Int,
val locId: Int,
val unitId: Int,
val qty: Number,
val up: Number,
val amt: Number,
)

+ 15
- 0
src/main/java/com/ffii/fpsms/m18/model/GoodsReceiptNoteResponse.kt Переглянути файл

@@ -0,0 +1,15 @@
package com.ffii.fpsms.m18.model

/**
* Response from M18 Goods Receipt Note (AN) save API.
*/
data class GoodsReceiptNoteResponse(
val recordId: Long = 0,
val messages: List<GoodsReceiptNoteMessage> = emptyList(),
val status: Boolean = false,
)

data class GoodsReceiptNoteMessage(
val msgDetail: String? = null,
val msgCode: String? = null,
)

+ 72
- 0
src/main/java/com/ffii/fpsms/m18/service/M18GoodsReceiptNoteService.kt Переглянути файл

@@ -0,0 +1,72 @@
package com.ffii.fpsms.m18.service

import com.ffii.fpsms.api.service.ApiCallerService
import com.ffii.fpsms.m18.M18Config
import com.ffii.fpsms.m18.model.GoodsReceiptNoteRequest
import com.ffii.fpsms.m18.model.GoodsReceiptNoteResponse
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.util.LinkedMultiValueMap
import reactor.core.publisher.Mono

/**
* Service to create Goods Receipt Note (AN) in M18 via the save API.
* API: PUT http://[server]/jsf/rfws/root/api/save/an
* Query: menuCode=an (required), param (optional, JSON string)
* Headers: authorization (Bearer token), client_id
*/
@Service
open class M18GoodsReceiptNoteService(
private val m18Config: M18Config,
private val apiCallerService: ApiCallerService,
) {
private val logger: Logger = LoggerFactory.getLogger(M18GoodsReceiptNoteService::class.java)

private val M18_SAVE_GOODS_RECEIPT_NOTE_API = "/root/api/save/an"
private val MENU_CODE_AN = "an"

/**
* Creates a goods receipt note in M18.
*
* @param request The request body containing mainan (header) and ant (lines).
* @param param Optional extra parameters in JSON format (query param "param").
* @return The M18 response with recordId, messages, and status; or null on failure.
*/
open fun createGoodsReceiptNote(
request: GoodsReceiptNoteRequest,
param: String? = null,
): GoodsReceiptNoteResponse? {
return createGoodsReceiptNoteMono(request, param).block()
}

/**
* Creates a goods receipt note in M18 (reactive).
*
* @param request The request body containing mainan (header) and ant (lines).
* @param param Optional extra parameters in JSON format (query param "param").
* @return Mono of the M18 response.
*/
open fun createGoodsReceiptNoteMono(
request: GoodsReceiptNoteRequest,
param: String? = null,
): Mono<GoodsReceiptNoteResponse> {
val queryParams = LinkedMultiValueMap<String, String>().apply {
add("menuCode", MENU_CODE_AN)
param?.let { add("param", it) }
}
return apiCallerService.put<GoodsReceiptNoteResponse>(
urlPath = M18_SAVE_GOODS_RECEIPT_NOTE_API,
queryParams = queryParams,
body = request,
).doOnSuccess { response ->
if (response.status) {
logger.info("Goods receipt note created in M18. recordId=${response.recordId}")
} else {
logger.warn("M18 save AN returned status=false. recordId=${response.recordId}, messages=${response.messages}")
}
}.doOnError { e ->
logger.error("Failed to create goods receipt note in M18: ${e.message}")
}
}
}

+ 3
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/service/JobOrderService.kt Переглянути файл

@@ -351,6 +351,9 @@ open class JobOrderService(
)
}

open fun jobOrderDetailByProdScheduleLineId(prodScheduleLineId: Long): JobOrderDetail =
jobOrderDetailByPsId(prodScheduleLineId)

open fun jobOrderDetailByItemId(itemId: Long): JobOrderDetail {
val sqlResult = jobOrderRepository.findJobOrderByItemId(itemId) ?: throw NoSuchElementException("Job Order not found with itemId: $itemId");



+ 17
- 9
src/main/java/com/ffii/fpsms/modules/master/service/ProductionScheduleService.kt Переглянути файл

@@ -420,14 +420,14 @@ open class ProductionScheduleService(
val itemId = item.id
?: throw IllegalStateException("Item ID is missing for Production Schedule Line $prodScheduleLineId.")

try {
jobOrderService.jobOrderDetailByItemId(itemId)
logger.info("jobOrderDetailByItemId ok itemId:$itemId")
} catch (e: NoSuchElementException) {
//try {
// jobOrderService.jobOrderDetailByItemId(itemId)
// logger.info("jobOrderDetailByItemId ok itemId:$itemId")
//} catch (e: NoSuchElementException) {
//only do with no JO is working
logger.info("NoSuchElementException itemId:$itemId")
//logger.info("NoSuchElementException itemId:$itemId")
try {
jobOrderService.jobOrderDetailByItemId(itemId)
jobOrderService.jobOrderDetailByProdScheduleLineId(prodScheduleLineId)
} catch (e: NoSuchElementException) {
val bom = bomService.findByItemId(itemId)
?: throw NoSuchElementException("BOM not found for Item ID $itemId.")
@@ -449,11 +449,15 @@ open class ProductionScheduleService(
logger.info("[releaseProdSchedule] prodScheduleLine.needNoOfJobOrder:" + prodScheduleLine.needNoOfJobOrder)
//repeat(prodScheduleLine.needNoOfJobOrder) {
// 6. Create Job Order
val produceAt = prodScheduleLine.productionSchedule?.produceAt
val joRequest = CreateJobOrderRequest(
bomId = bom.id, // bom is guaranteed non-null here
reqQty = bom.outputQty?.multiply(BigDecimal.valueOf(prodScheduleLine.batchNeed.toLong())),
approverId = approver?.id,

planStart = produceAt,
planEnd = produceAt,

// CRUCIAL FIX: Use the line ID, not the parent schedule ID
prodScheduleLineId = prodScheduleLine.id!!
)
@@ -473,7 +477,7 @@ open class ProductionScheduleService(
//}
}
}
//}

// No need to fetch latest detail inside the loop
}
@@ -542,13 +546,17 @@ open class ProductionScheduleService(

val approver = SecurityUtils.getUser().getOrNull() // Get approver once

val produceAt = prodScheduleLine.productionSchedule?.produceAt
val joRequest = CreateJobOrderRequest(
bomId = bom.id, // bom is guaranteed non-null here
reqQty = bom.outputQty?.multiply(BigDecimal.valueOf(prodScheduleLine.batchNeed.toLong())),
approverId = approver?.id,

planStart = produceAt,
planEnd = produceAt,

// CRUCIAL FIX: Use the line ID, not the parent schedule ID
prodScheduleLineId = prodScheduleLine.id!!
prodScheduleLineId = prodScheduleLine.id!!
)

// Assuming createJobOrder returns the created Job Order (jo)


Завантаження…
Відмінити
Зберегти