Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 
 

127 строки
5.0 KiB

  1. package com.ffii.fpsms.m18.service
  2. import com.fasterxml.jackson.core.JsonGenerator
  3. import com.fasterxml.jackson.databind.JsonNode
  4. import com.fasterxml.jackson.databind.ObjectMapper
  5. import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
  6. import com.ffii.fpsms.api.service.ApiCallerService
  7. import com.ffii.fpsms.m18.M18Config
  8. import com.ffii.fpsms.m18.model.GoodsReceiptNoteRequest
  9. import com.ffii.fpsms.m18.model.GoodsReceiptNoteResponse
  10. import org.slf4j.Logger
  11. import org.slf4j.LoggerFactory
  12. import org.springframework.stereotype.Service
  13. import org.springframework.util.LinkedMultiValueMap
  14. import reactor.core.publisher.Mono
  15. /**
  16. * Service to create Goods Receipt Note (AN) in M18 via the save API.
  17. * API: PUT http://[server]/jsf/rfws/root/api/save/an
  18. * Query: menuCode=an (required), param (optional, JSON string)
  19. * Headers: authorization (Bearer token), client_id
  20. */
  21. @Service
  22. open class M18GoodsReceiptNoteService(
  23. private val m18Config: M18Config,
  24. private val apiCallerService: ApiCallerService,
  25. ) {
  26. private val logger: Logger = LoggerFactory.getLogger(M18GoodsReceiptNoteService::class.java)
  27. private val M18_SAVE_GOODS_RECEIPT_NOTE_API = "/root/api/save/an"
  28. private val M18_READ_GOODS_RECEIPT_NOTE_API = "/root/api/read/an"
  29. private val MENU_CODE_AN = "an"
  30. // Serialize with UTF-8 characters (no \\uXXXX) so M18 persists bDesc/bDesc_en like Postman
  31. @Suppress("DEPRECATION")
  32. private val grnRequestMapper: ObjectMapper = jacksonObjectMapper().apply {
  33. disable(JsonGenerator.Feature.ESCAPE_NON_ASCII)
  34. }
  35. /**
  36. * Creates a goods receipt note in M18.
  37. *
  38. * @param request The request body containing mainan (header) and ant (lines).
  39. * @param param Optional extra parameters in JSON format (query param "param").
  40. * @return The M18 response with recordId, messages, and status; or null on failure.
  41. */
  42. open fun createGoodsReceiptNote(
  43. request: GoodsReceiptNoteRequest,
  44. param: String? = null,
  45. ): GoodsReceiptNoteResponse? {
  46. return createGoodsReceiptNoteMono(request, param).block()
  47. }
  48. /**
  49. * Creates a goods receipt note in M18 (reactive).
  50. *
  51. * @param request The request body containing mainan (header) and ant (lines).
  52. * @param param Optional extra parameters in JSON format (query param "param").
  53. * @return Mono of the M18 response.
  54. */
  55. open fun createGoodsReceiptNoteMono(
  56. request: GoodsReceiptNoteRequest,
  57. param: String? = null,
  58. ): Mono<GoodsReceiptNoteResponse> {
  59. val queryParams = LinkedMultiValueMap<String, String>().apply {
  60. add("menuCode", MENU_CODE_AN)
  61. param?.let { add("param", it) }
  62. }
  63. val queryString = queryParams.entries.flatMap { (k, v) -> v.map { value -> "$k=$value" } }.joinToString("&")
  64. val fullUrl = "${m18Config.BASE_URL}$M18_SAVE_GOODS_RECEIPT_NOTE_API?$queryString"
  65. val requestJson = grnRequestMapper.writeValueAsString(request)
  66. logger.info("[M18 GRN API] call=PUT url=$fullUrl body=$requestJson")
  67. return apiCallerService.putWithJsonString<GoodsReceiptNoteResponse>(
  68. urlPath = M18_SAVE_GOODS_RECEIPT_NOTE_API,
  69. queryParams = queryParams,
  70. bodyJson = requestJson,
  71. ).doOnSuccess { response ->
  72. val responseJson = grnRequestMapper.writeValueAsString(response)
  73. logger.info("[M18 GRN API] response (all): $responseJson")
  74. if (response.status) {
  75. logger.info("Goods receipt note created in M18. recordId=${response.recordId}")
  76. } else {
  77. logger.warn("M18 save AN returned status=false. recordId=${response.recordId}, messages=${response.messages}")
  78. }
  79. }.doOnError { e ->
  80. logger.error("[M18 GRN API] call failed: $fullUrl error=${e.message}", e)
  81. }
  82. }
  83. /**
  84. * Reads AN (GRN) from M18 by record id. GET /root/api/read/an?id={recordId}
  85. * Document code is typically at data.mainan.values[0].code or data.mainan.code.
  86. */
  87. open fun readAnCodeByRecordId(recordId: Long): String? {
  88. return try {
  89. val node: JsonNode = apiCallerService.get<JsonNode>(
  90. M18_READ_GOODS_RECEIPT_NOTE_API,
  91. mapOf("id" to recordId),
  92. ).block() ?: return null
  93. extractAnCodeFromReadResponse(node)
  94. } catch (e: Exception) {
  95. logger.warn("[M18 read AN] id=$recordId failed: ${e.message}", e)
  96. null
  97. }
  98. }
  99. private fun extractAnCodeFromReadResponse(node: JsonNode): String? {
  100. val paths = listOf(
  101. "/data/mainan/0/code",
  102. "/data/mainan/values/0/code",
  103. "/data/mainan/code",
  104. "/data/ant/0/code",
  105. "/mainan/values/0/code",
  106. "/mainan/code",
  107. "/mainan/0/code",
  108. )
  109. for (p in paths) {
  110. val v = node.at(p)
  111. if (!v.isMissingNode && !v.isNull) {
  112. val text = v.asText()
  113. if (!text.isNullOrBlank()) return text
  114. }
  115. }
  116. return null
  117. }
  118. }