浏览代码

Merge remote-tracking branch 'origin/master'

master
CANCERYS\kw093 1 个月前
父节点
当前提交
4997f21fd3
共有 16 个文件被更改,包括 378 次插入336 次删除
  1. +191
    -91
      src/main/java/com/ffii/fpsms/api/service/ApiCallerService.kt
  2. +3
    -0
      src/main/java/com/ffii/fpsms/m18/M18Config.kt
  3. +0
    -113
      src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt
  4. +2
    -0
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt
  5. +105
    -54
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt
  6. +15
    -15
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt
  7. +3
    -1
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt
  8. +1
    -1
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ExportDNLabelsRequest.kt
  9. +1
    -2
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ExportDeliveryNoteRequest.kt
  10. +2
    -2
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/PrintDNLabelsRequest.kt
  11. +1
    -2
      src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/PrintDeliveryNoteRequest.kt
  12. +4
    -2
      src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt
  13. +7
    -1
      src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt
  14. +1
    -1
      src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt
  15. +41
    -51
      src/main/resources/DeliveryNote/DeliveryNotePDF.jrxml
  16. +1
    -0
      src/main/resources/application.yml

+ 191
- 91
src/main/java/com/ffii/fpsms/api/service/ApiCallerService.kt 查看文件

@@ -18,18 +18,29 @@ import org.springframework.web.reactive.function.client.WebClient
import reactor.core.publisher.Mono
import kotlin.reflect.full.memberProperties
import org.springframework.context.annotation.Lazy
import org.springframework.http.HttpMethod
import org.springframework.http.HttpRequest
import org.springframework.http.HttpStatusCode
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.client.ClientRequest
import org.springframework.web.reactive.function.client.ExchangeFilterFunction
import org.springframework.web.reactive.function.client.WebClientResponseException
import org.springframework.web.service.invoker.HttpRequestValues
import reactor.util.retry.Retry
import java.time.Duration
import java.util.concurrent.atomic.AtomicReference

@Service
open class ApiCallerService(
val m18Config: M18Config,
) {
val logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
val logger: Logger = LoggerFactory.getLogger(ApiCallerService::class.java)
private val MAX_IN_MEMORY_SIZE = 10 * 1024 * 1024
val DEFAULT_RETRY_ATTEMPTS = 5L

val webClient: WebClient = WebClient.builder()
.baseUrl(m18Config.BASE_URL)
// .baseUrl(m18Config.BASE_URL_UAT)
.defaultHeaders { headers ->
headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
// headers.set(HttpHeaders.AUTHORIZATION, "Bearer ${m18Config.ACCESS_TOKEN}")
@@ -42,15 +53,22 @@ open class ApiCallerService(
.build()
next.exchange(updatedRequest)
}
.codecs { configurer -> configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024) }
.filter(ExchangeFilterFunction.ofRequestProcessor { request ->
// logger.info("Request: ${request.method()} ${request.url()}")
Mono.just(request)
}.andThen(ExchangeFilterFunction.ofResponseProcessor { response ->
// logger.info("Response: ${response.statusCode()}")
Mono.just(response)
}))
.codecs { configurer -> configurer.defaultCodecs().maxInMemorySize(MAX_IN_MEMORY_SIZE) }
.build()

@PostConstruct
fun init() {
updateToken()
updateToken().subscribe()
}

fun updateToken() {
fun updateToken() : Mono<String>{
val params = M18TokenRequest(
grant_type = m18Config.GRANT_TYPE,
client_id = m18Config.CLIENT_ID,
@@ -59,96 +77,69 @@ open class ApiCallerService(
password = m18Config.PASSWORD
)

get<M18TokenResponse, M18TokenRequest>("/oauth/token", params, null)
.subscribe(
{ response ->
return get<M18TokenResponse, M18TokenRequest>("/oauth/token", params, null)
.doOnSuccess { response ->
if (m18Config.ACCESS_TOKEN != response.access_token) {
m18Config.ACCESS_TOKEN = response.access_token
println("WebClient Response stored: $response")
},
{ error -> println("WebClient Error: ${error.message}") }
)
logger.info("Token updated: ${response.access_token}")
}
}
.doOnError { error -> logger.error("Token update failed: ${error.message}") }
.map { it.access_token }
}

// ------------------------------------ GET ------------------------------------ //
/**
* Performs a GET HTTP request to the specified URL path.
* Performs a GET HTTP request to the specified URL path with parameters.
* 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
* @param params Optional query parameters (supports Map, MultiValueMap, or custom object)
* @param customHeaders Optional custom headers to include in the request
* @return A Mono that emits the response body converted to type T
*/
inline fun <reified T : Any> get(
inline fun <reified T : Any, reified U : Any> get(
urlPath: String,
params: MultiValueMap<String, String>,
customHeaders: Map<String, String>?
params: U? = null,
customHeaders: Map<String, String>? = null
): Mono<T> {
println("ACCESS TOKEN: ${m18Config.ACCESS_TOKEN}")
return webClient.get()
.uri { uriBuilder ->
uriBuilder
.apply {
path(urlPath)

// convert to multiValueMap
queryParams(params)
val queryParams = when (params) {
is Map<*, *> -> LinkedMultiValueMap<String, String>().apply {
params.forEach { (k, v) ->
when (v) {
is Collection<*> -> addAll(k.toString(), v.map { it.toString() })
else -> add(k.toString(), v.toString())
}
.build()
}
.headers { headers ->
customHeaders?.forEach { (k, v) -> headers.set(k, v) }
}
.retrieve()
.bodyToMono(T::class.java)
// Below is for test bug (200 but error). Need to comment out bodyToMono and add jsonIgnore to data class
// .toEntity(String::class.java) // Get raw body as String
// .flatMap { entity ->
// logger.info("Response Status: ${entity.statusCode}")
// logger.info("Response Headers: ${entity.headers}")
// logger.info("Raw Response Body: ${entity.body}")
// try {
// val objectMapper = jacksonObjectMapper()
// val deserialized = objectMapper.readValue(entity.body, T::class.java)
// Mono.just(deserialized)
// } catch (e: Exception) {
// logger.error("Deserialization failed: ${e.message}")
// Mono.error(e)
// }
// }
.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}")
logger.error("Error Response: ${error.responseBodyAsString}")
if (error.statusCode == HttpStatusCode.valueOf(400)) {
updateToken()
}
Mono.error(error)
}
.onErrorResume { error ->
logger.error("Exception")
logger.error("Error Message: ${error.message}")
Mono.error(error)
null -> LinkedMultiValueMap<String, String>()
else -> LinkedMultiValueMap<String, String>().apply {
U::class.memberProperties.forEach { property ->
val key = property.name
val value = property.get(params)
when (value) {
is Collection<*> -> value.forEach { item -> add(key, item.toString()) }
else -> add(key, value.toString())
}
}
}
.retry(5)
}

return executeGet<T>(urlPath, queryParams, customHeaders)
}

/**
* Performs a GET HTTP request to the specified URL path.
* Performs a GET HTTP request to the specified URL path without parameters.
*
* @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
* @param customHeaders Optional custom headers 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>?,
customHeaders: Map<String, String>?
customHeaders: Map<String, String>? = null
): Mono<T> {

// convert to multiValueMap<String, String>
val queryParams = params?.let { paramMap ->
LinkedMultiValueMap<String, String>().apply {
@@ -161,7 +152,7 @@ open class ApiCallerService(
}
} ?: LinkedMultiValueMap<String, String>()

return get<T>(urlPath, queryParams, customHeaders)
return executeGet<T>(urlPath, queryParams, customHeaders)
}

/**
@@ -169,33 +160,87 @@ open class ApiCallerService(
*
* @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> executeGet(
urlPath: String,
params: Map<String, Any>?
params: MultiValueMap<String, String>,
customHeaders: Map<String, String>?
): Mono<T> {
return get<T>(urlPath, params, null)
println("ACCESS TOKEN: ${m18Config.ACCESS_TOKEN}")

return webClient.get()
.uri { uriBuilder -> uriBuilder.path(urlPath).queryParams(params).build() }
.headers { headers -> customHeaders?.forEach { (k, v) -> headers.set(k, v) } }
.retrieve()
.bodyToMono(T::class.java)
// Below is for test bug (200 but error). Need to comment out bodyToMono and add jsonIgnore to data class
// .toEntity(String::class.java) // Get raw body as String
// .flatMap { entity ->
// logger.info("Response Status: ${entity.statusCode}")
// logger.info("Response Headers: ${entity.headers}")
// logger.info("Raw Response Body: ${entity.body}")
// try {
// val objectMapper = jacksonObjectMapper()
// val deserialized = objectMapper.readValue(entity.body, T::class.java)
// Mono.just(deserialized)
// } catch (e: Exception) {
// logger.error("Deserialization failed: ${e.message}")
// Mono.error(e)
// }
// }
.doOnError { error -> println("Error occurred: ${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.get()
.uri { uriBuilder -> uriBuilder.path(urlPath).queryParams(params).build() }
.headers { headers ->
headers.set(HttpHeaders.AUTHORIZATION, "Bearer $newToken")
customHeaders?.forEach { (k, v) -> headers.set(k, v) }
}
.retrieve()
.bodyToMono(T::class.java)
}
} else {
Mono.error(error)
}
}
.retryWhen(
Retry.backoff(DEFAULT_RETRY_ATTEMPTS, Duration.ofSeconds(1))
// .filter { it is WebClientResponseException && it.statusCode != HttpStatusCode.valueOf(400) }
.doBeforeRetry { signal -> logger.info("Retrying due to: ${signal.failure().message}") }
)
}

// ------------------------------------ POST ------------------------------------ //
/**
* Performs a GET HTTP request to the specified URL path.
* Performs a POST HTTP request to the specified URL path with parameters.
* 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
* @param urlPath The path to send the POST request to (after /jsf/rfws)
* @param params Optional query parameters (supports Map, MultiValueMap, or custom object)
* @param customHeaders Optional custom headers 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(
inline fun <reified T : Any, reified U : Any> post(
urlPath: String,
params: U?,
customHeaders: Map<String, String>?
params: U? = null,
customHeaders: Map<String, String>? = null
): Mono<T> {

// convert to multiValueMap<String, String>
val queryParams = params?.let {
LinkedMultiValueMap<String, String>().apply {
val queryParams = when (params) {
is Map<*, *> -> LinkedMultiValueMap<String, String>().apply {
params.forEach { (k, v) ->
when (v) {
is Collection<*> -> addAll(k.toString(), v.map { it.toString() })
else -> add(k.toString(), v.toString())
}
}
}
null -> LinkedMultiValueMap<String, String>()
else -> LinkedMultiValueMap<String, String>().apply {
U::class.memberProperties.forEach { property ->
val key = property.name
val value = property.get(params)
@@ -205,25 +250,80 @@ open class ApiCallerService(
}
}
}
}

return executePost<T>(urlPath, queryParams, customHeaders)
}

/**
* Performs a POST HTTP request to the specified URL path without parameters.
*
* @param urlPath The path to send the POST request to (after /jsf/rfws)
* @param customHeaders Optional custom headers to include in the request
* @return A Mono that emits the response body converted to type T
*/
inline fun <reified T : Any> post(
urlPath: String,
params: Map<String, Any>?,
customHeaders: Map<String, String>? = null
): Mono<T> {
// convert to multiValueMap<String, String>
val queryParams = params?.let { paramMap ->
LinkedMultiValueMap<String, String>().apply {
paramMap.forEach { (key, value) ->
when (value) {
is Collection<*> -> addAll(key, value.map { it.toString() })
else -> add(key, value.toString())
}
}
}
} ?: LinkedMultiValueMap<String, String>()

return get<T>(urlPath, queryParams, customHeaders)
return executePost<T>(urlPath, queryParams, customHeaders)
}

/**
* Performs a GET HTTP request to the specified URL path.
* T: Response, U: Request
* Performs a POST 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, reified U : Any> get(
inline fun <reified T : Any> executePost(
urlPath: String,
params: U?,
params: MultiValueMap<String, String>,
customHeaders: Map<String, String>?
): Mono<T> {
println("ACCESS TOKEN: ${m18Config.ACCESS_TOKEN}")

return get<T, U>(urlPath, params, null)
return webClient.post()
.uri { uriBuilder -> uriBuilder.path(urlPath).build() }
.headers { headers -> customHeaders?.forEach { (k, v) -> headers.set(k, v) } }
.body(BodyInserters.fromValue(params))
.retrieve()
.bodyToMono(T::class.java)
.doOnError { error -> println("Error occurred: ${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.get()
.uri { uriBuilder -> uriBuilder.path(urlPath).queryParams(params).build() }
.headers { headers ->
headers.set(HttpHeaders.AUTHORIZATION, "Bearer $newToken")
customHeaders?.forEach { (k, v) -> headers.set(k, v) }
}
.retrieve()
.bodyToMono(T::class.java)
}
} else {
Mono.error(error)
}
}
.retryWhen(
Retry.backoff(DEFAULT_RETRY_ATTEMPTS, Duration.ofSeconds(1))
.doBeforeRetry { signal -> logger.info("Retrying due to: ${signal.failure().message}") }
)
}

}

+ 3
- 0
src/main/java/com/ffii/fpsms/m18/M18Config.kt 查看文件

@@ -28,6 +28,9 @@ open class M18Config {
@Value("\${m18.config.base-url}")
lateinit var BASE_URL: String;

@Value("\${m18.config.base-url-uat}")
lateinit var BASE_URL_UAT: String;

// Supplier
// var MATERIAL_PO_SUPPLIER_NOT: List<String> = listOf("P06", "P07", "T62"); // If need oem type



+ 0
- 113
src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt 查看文件

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

import com.ffii.core.utils.JwtTokenUtil
import com.ffii.fpsms.m18.web.models.M18CommonRequest
import com.ffii.fpsms.modules.common.SettingNames
import com.ffii.fpsms.modules.settings.service.SettingsService
import jakarta.annotation.PostConstruct
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.scheduling.TaskScheduler
//import org.springframework.scheduling.annotation.Async
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.scheduling.support.CronTrigger
import org.springframework.stereotype.Service
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.concurrent.ScheduledFuture
import kotlin.concurrent.Volatile
import kotlin.jvm.optionals.getOrNull

@Service
open class M18SchedulerService(
val m18PurchaseOrderService: M18PurchaseOrderService,
val m18DeliveryOrderService: M18DeliveryOrderService,
val m18MasterDataService: M18MasterDataService,
val settingsService: SettingsService,
val taskScheduler: TaskScheduler,
) {
var logger: Logger = LoggerFactory.getLogger(JwtTokenUtil::class.java)
val dataStringFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val defaultCronExpression = "0 0 2 * * *";

@Volatile
var scheduledM18Po: ScheduledFuture<*>? = null

@Volatile
var scheduledM18Master: ScheduledFuture<*>? = null

fun isValidCronExpression(cronExpression: String): Boolean {
return try {
CronTrigger(cronExpression)
true
} catch (e: IllegalArgumentException) {
false
}
}
@PostConstruct
fun init() {
scheduleM18PoTask()
scheduleM18MasterData()
}

fun commonSchedule(scheduled: ScheduledFuture<*>?, settingName: String, scheduleFunc: () -> Unit) {
scheduled?.cancel(false)

var cron = settingsService.findByName(settingName).getOrNull()?.value ?: defaultCronExpression;

if (!isValidCronExpression(cron)) {
cron = defaultCronExpression
}
scheduledM18Po = taskScheduler.schedule(
{
scheduleFunc()
},
CronTrigger(cron)
)
}

// Scheduler
fun scheduleM18PoTask() {
commonSchedule(scheduledM18Po, SettingNames.SCHEDULE_M18_PO, ::getM18Pos)
}

fun scheduleM18MasterData() {
commonSchedule(scheduledM18Master, SettingNames.SCHEDULE_M18_MASTER, ::getM18MasterData)
}

// Tasks
// @Async
// @Scheduled(cron = "0 0 2 * * *") // (SS/MM/HH/DD/MM/YY)
open fun getM18Pos() {
logger.info("Daily Scheduler - PO")
val currentTime = LocalDateTime.now()
val today = currentTime.toLocalDate().atStartOfDay()
val yesterday = today.minusDays(1L)
val request = M18CommonRequest(
modifiedDateTo = today.format(dataStringFormat),
modifiedDateFrom = yesterday.format(dataStringFormat)
)
m18PurchaseOrderService.savePurchaseOrders(request);
m18DeliveryOrderService.saveDeliveryOrders(request);
logger.info("today: ${today.format(dataStringFormat)}")
logger.info("yesterday: ${yesterday.format(dataStringFormat)}")
}

open fun getM18MasterData() {
logger.info("Daily Scheduler - Master Data")
val currentTime = LocalDateTime.now()
val today = currentTime.toLocalDate().atStartOfDay()
val yesterday = today.minusDays(1L)
val request = M18CommonRequest(
modifiedDateTo = today.format(dataStringFormat),
modifiedDateFrom = yesterday.format(dataStringFormat)
)
m18MasterDataService.saveUnits(request)
m18MasterDataService.saveProducts(request)
m18MasterDataService.saveVendors(request)
m18MasterDataService.saveBusinessUnits(request)
m18MasterDataService.saveCurrencies(request)
logger.info("today: ${today.format(dataStringFormat)}")
logger.info("yesterday: ${yesterday.format(dataStringFormat)}")
}
}

+ 2
- 0
src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt 查看文件

@@ -7,6 +7,7 @@ import com.ffii.fpsms.modules.deliveryOrder.enums.DoPickOrderStatus
import com.ffii.fpsms.modules.master.entity.projections.SearchId
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import org.springframework.stereotype.Repository
import java.io.Serializable
import java.time.LocalDateTime
@@ -29,4 +30,5 @@ fun findByStoreIdAndRequiredDeliveryDateAndTicketStatusIn(
requiredDeliveryDate: LocalDate,
status: List<DoPickOrderStatus>
): List<DoPickOrder>

}

+ 105
- 54
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt 查看文件

@@ -58,8 +58,10 @@ import org.springframework.core.io.ClassPathResource
import java.io.File
import java.io.FileNotFoundException
import com.ffii.core.support.JdbcDao;
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderLineRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecord
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRecordRepository
import com.ffii.fpsms.modules.deliveryOrder.entity.DoPickOrderRepository
import com.ffii.fpsms.modules.deliveryOrder.web.models.ExportDNLabelsRequest
import com.ffii.fpsms.modules.deliveryOrder.web.models.PrintDNLabelsRequest
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository
@@ -100,6 +102,8 @@ open class DeliveryOrderService(
private val inventoryLotRepository: InventoryLotRepository,
private val jdbcDao: JdbcDao,
private val pickExecutionIssueRepository: PickExecutionIssueRepository,
private val doPickOrderRepository: DoPickOrderRepository,
private val doPickOrderLineRepository: DoPickOrderLineRepository,
) {

open fun findByM18DataLogId(m18DataLogId: Long): DeliveryOrder? {
@@ -673,63 +677,104 @@ open class DeliveryOrderService(
if (!resource.exists()) {
throw FileNotFoundException("Report file not fount: $DELIVERYNOTE_PDF")
}
val inputStream = resource.inputStream
val deliveryNote = JasperCompileManager.compileReport(inputStream)
val deliveryNoteInfo =
deliveryOrderRepository.findDeliveryOrderInfoById(request.deliveryOrderIds).toMutableList()
val doPickOrder = doPickOrderRepository.findById(request.doPickOrderId).orElseThrow {
NoSuchElementException("DoPickOrder not found with ID: ${request.doPickOrderId}")
}
val pickOrderIds = if (doPickOrder.pickOrderId != null) {
// Single pick order case
listOf(doPickOrder.pickOrderId!!)
} else {
// Merged pick orders case - get from DoPickOrderLine
val doPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrder.id!!)
val orderIds = doPickOrderLines.mapNotNull { it.pickOrderId }.distinct()

val fields = mutableListOf<MutableMap<String, Any>>()
val params = mutableMapOf<String, Any>()
if (orderIds.isEmpty()) {
throw IllegalStateException("DoPickOrder ${request.doPickOrderId} has no associated pick orders")
}
orderIds
}

val deliveryOrderEntity = deliveryOrderRepository.findByIdAndDeletedIsFalse(request.deliveryOrderIds)
val selectedTruckNo = deliveryOrderEntity?.shop?.id?.let { shopId ->
val trucks = truckRepository.findByShopIdAndDeletedFalse(shopId)
trucks.firstOrNull()?.truckLanceCode
} ?: ""
val deliveryOrderIds = if (doPickOrder.doOrderId != null) {
// Single delivery order case
listOf(doPickOrder.doOrderId!!)
} else {
// Merged delivery orders case - get from DoPickOrderLine
val doPickOrderLines = doPickOrderLineRepository.findByDoPickOrderIdAndDeletedFalse(doPickOrder.id!!)
val orderIds = doPickOrderLines.mapNotNull { it.doOrderId }.distinct()

val selectedPickOrder = pickOrderRepository.findById(request.pickOrderIds).orElse(null)
if (orderIds.isEmpty()) {
throw IllegalStateException("DoPickOrder ${request.doPickOrderId} has no associated delivery orders")
}
orderIds
}

val deliveryNoteInfo = deliveryOrderIds.flatMap { deliveryOrderId ->
deliveryOrderRepository.findDeliveryOrderInfoById(deliveryOrderId)
}.toMutableList()

for (info in deliveryNoteInfo) {
val sortedLines = info.deliveryOrderLines.sortedBy { line ->
line.itemId?.let { itemId ->
getWarehouseOrderByItemId(itemId) // ✅ 改用 warehouse order
} ?: Int.MAX_VALUE
}
if (deliveryNoteInfo.isEmpty()) {
throw NoSuchElementException("Delivery orders not found for IDs: $deliveryOrderIds")
}

sortedLines.forEachIndexed { index, line ->
val pickOrderId = pickOrderIds.first()

val field = mutableMapOf<String, Any>()
val inputStream = resource.inputStream
val deliveryNote = JasperCompileManager.compileReport(inputStream)

field["sequenceNumber"] = (index + 1).toString()
field["itemNo"] = line.itemNo
field["itemName"] = line.itemName ?: ""
field["uom"] = line.uom ?: ""
field["qty"] = line.qty.toString()
field["shortName"] = line.uomShortDesc ?: ""
val fields = mutableListOf<MutableMap<String, Any>>()
val params = mutableMapOf<String, Any>()

val route = line.itemId?.let { itemId ->
getWarehouseCodeByItemId(itemId) // ✅ 使用新方法
} ?: ""
field["route"] = route
val truckNo = doPickOrder.truckLanceCode ?: ""
val selectedPickOrder = pickOrderRepository.findById(pickOrderId).orElse(null)

val lotNo = line.itemId?.let { itemId ->
getLotNumbersForPickOrderByItemId(itemId, request.pickOrderIds)
} ?: ""
field["lotNo"] = lotNo
val allLines = deliveryNoteInfo.flatMap { info ->
info.deliveryOrderLines.map { line -> line }
}

fields.add(field)
}
val sortedLines = allLines.sortedBy { line ->
line.itemId?.let { itemId ->
getWarehouseOrderByItemId(itemId)
} ?: Int.MAX_VALUE
}

sortedLines.forEach { line ->
val field = mutableMapOf<String, Any>()

field["sequenceNumber"] = (fields.size + 1).toString()
field["itemNo"] = line.itemNo
field["itemName"] = line.itemName ?: ""
field["uom"] = line.uom ?: ""
field["qty"] = line.qty.toString()
field["shortName"] = line.uomShortDesc ?: ""

val route = line.itemId?.let { itemId ->
getWarehouseCodeByItemId(itemId)
} ?: ""
field["route"] = route

val lotNo = line.itemId?.let { itemId ->
pickOrderIds.mapNotNull { pickOrderId ->
val lots = getLotNumbersForPickOrderByItemId(itemId, pickOrderId)
if (lots.isNotBlank()) lots else null
}.distinct().joinToString(", ")
} ?: ""
field["lotNo"] = lotNo

fields.add(field)
}

if (request.isDraft) {
params["dnTitle"] = "送貨單(初稿)"
params["colQty"] = "所需數量"
params["totalCartonTitle"] = ""
params["deliveryOrderCodeTitle"] = ""
params["deliveryOrderCode"] = ""
} else {
params["dnTitle"] = "送貨單"
params["colQty"] = "數量"
params["colQty"] = "已提數量"
params["totalCartonTitle"] = "總箱數:"
params["deliveryOrderCodeTitle"] = "送貨單編號:"
params["deliveryOrderCode"] = "GEN FROM CODE GENERATOR (NEED FIND TIMING)"
}

params["numOfCarton"] = request.numOfCarton.toString()
@@ -737,23 +782,20 @@ open class DeliveryOrderService(
params["numOfCarton"] = ""
}

params["deliveryOrderCode"] = deliveryNoteInfo[0].code
params["shopName"] = deliveryNoteInfo[0].shopName ?: ""
params["shopName"] = doPickOrder.shopName ?: deliveryNoteInfo[0].shopName ?:""
params["shopAddress"] = deliveryNoteInfo[0].shopAddress ?: ""
params["deliveryDate"] =
deliveryNoteInfo[0].estimatedArrivalDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: ""
params["truckNo"] = selectedTruckNo
params["ShopPurchaseOrderNo"] = deliveryNoteInfo[0].code
params["FGPickOrderNo"] = selectedPickOrder?.code ?: ""
params["deliveryDate"] = deliveryNoteInfo[0].estimatedArrivalDate?.format(DateTimeFormatter.ISO_LOCAL_DATE) ?: ""
params["truckNo"] = truckNo
params["ShopPurchaseOrderNo"] = doPickOrder.deliveryOrderCode ?: deliveryNoteInfo.joinToString(", ") { it.code }
params["FGPickOrderNo"] = doPickOrder.pickOrderCode ?: selectedPickOrder?.code ?: ""


return mapOf(
"report" to PdfUtils.fillReport(deliveryNote, fields, params),
"filename" to deliveryNoteInfo[0].code
"filename" to deliveryNoteInfo.joinToString("_") { it.code }
)
}


//Print Delivery Note
@Transactional
open fun printDeliveryNote(request: PrintDeliveryNoteRequest) {
@@ -761,10 +803,9 @@ open class DeliveryOrderService(

val pdf = exportDeliveryNote(
ExportDeliveryNoteRequest(
deliveryOrderIds = request.deliveryOrderId,
doPickOrderId = request.doPickOrderId,
numOfCarton = request.numOfCarton,
isDraft = request.isDraft,
pickOrderIds = request.pickOrderId
isDraft = request.isDraft
)
)

@@ -792,20 +833,30 @@ open class DeliveryOrderService(
if (!resource.exists()) {
throw FileNotFoundException("Label file not found: $DNLABELS_PDF")
}

val doPickOrder = doPickOrderRepository.findById(request.doPickOrderId).orElseThrow {
NoSuchElementException("DoPickOrder not found with ID: ${request.doPickOrderId}")
}

val deliveryOrderId = doPickOrder.doOrderId
?: throw IllegalStateException("DoPickOrder has no associated delivery order")

val inputStream = resource.inputStream
val cartonLabel = JasperCompileManager.compileReport(inputStream)
val cartonLabelInfo =
deliveryOrderRepository.findDeliveryOrderInfoById(request.deliveryOrderIds).toMutableList()
deliveryOrderRepository.findDeliveryOrderInfoById(request.doPickOrderId).toMutableList()

val params = mutableMapOf<String, Any>()
val fields = mutableListOf<MutableMap<String, Any>>()

for (info in cartonLabelInfo) {
val field = mutableMapOf<String, Any>()
}

params["shopPurchaseOrderNo"] = cartonLabelInfo[0].code
params["deliveryOrderCode"] = cartonLabelInfo[0].code
params["shopPurchaseOrderNo"] = doPickOrder.deliveryOrderCode ?: cartonLabelInfo[0].code
params["deliveryOrderCode"] = "GEN FROM CODE GENERATOR (NEED FIND TIMING)"
params["shopAddress"] = cartonLabelInfo[0].shopAddress ?: ""
params["shopName"] = cartonLabelInfo[0].shopName ?: ""
params["shopName"] = doPickOrder.shopName ?: cartonLabelInfo[0].shopName ?: ""

for (cartonNumber in 1..request.numOfCarton) {
val field = mutableMapOf<String, Any>()
@@ -828,7 +879,7 @@ open class DeliveryOrderService(

val pdf = exportDNLabels(
ExportDNLabelsRequest(
deliveryOrderIds = request.deliveryOrderId,
doPickOrderId = request.doPickOrderId,
numOfCarton = request.numOfCarton
)
)


+ 15
- 15
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt 查看文件

@@ -64,7 +64,7 @@ open class DoPickOrderService(
)
}

fun getNextTicketNumber(datePrefix: String, storeId: String): String {
open fun getNextTicketNumber(datePrefix: String, storeId: String): String {
println("🔍 DEBUG: Getting next ticket number for date prefix: $datePrefix, store: $storeId")
try {
val sanitizedStoreId = storeId.replace("/", "")
@@ -92,18 +92,18 @@ open class DoPickOrderService(
}
}

fun save(record: DoPickOrder): DoPickOrder {
open fun save(record: DoPickOrder): DoPickOrder {
return doPickOrderRepository.save(record)
}

fun findByStoreIdOrderByTruckDepartureTime(storeId: String): List<DoPickOrder> {
open fun findByStoreIdOrderByTruckDepartureTime(storeId: String): List<DoPickOrder> {
return doPickOrderRepository.findByStoreIdAndTicketStatusOrderByTruckDepartureTimeAsc(
storeId, DoPickOrderStatus.pending
)
}

// Add these missing methods
fun assignByStore(request: AssignByStoreRequest): MessageResponse {
open fun assignByStore(request: AssignByStoreRequest): MessageResponse {
// TODO: Implement store-based assignment logic
return MessageResponse(
id = null,
@@ -116,7 +116,7 @@ open class DoPickOrderService(
)
}

fun releaseAssignedByStore(request: AssignByStoreRequest): MessageResponse {
open fun releaseAssignedByStore(request: AssignByStoreRequest): MessageResponse {
// TODO: Implement store-based release logic
return MessageResponse(
id = null,
@@ -130,7 +130,7 @@ open class DoPickOrderService(
}

// ✅ Updated method to set ticketReleaseTime when assigning order to user
fun updateHandledByForPickOrder(pickOrderId: Long, userId: Long): List<DoPickOrder> {
open fun updateHandledByForPickOrder(pickOrderId: Long, userId: Long): List<DoPickOrder> {
val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId)
val user = userRepository.findById(userId).orElse(null) // ✅ 改用 orElse(null)
val handlerName = user?.name ?: "Unknown"
@@ -143,7 +143,7 @@ open class DoPickOrderService(
return doPickOrderRepository.saveAll(doPickOrders)
}

fun completeDoPickOrdersForPickOrder(pickOrderId: Long): List<DoPickOrder> {
open fun completeDoPickOrdersForPickOrder(pickOrderId: Long): List<DoPickOrder> {
val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId)
doPickOrders.forEach {
it.ticketStatus = DoPickOrderStatus.completed
@@ -169,7 +169,7 @@ open class DoPickOrderService(
// ✅ New method to remove do_pick_order records when auto-assigning by store
// ✅ 修改方法:先复制记录到record表,再删除原记录
@Transactional
fun removeDoPickOrdersForPickOrder(pickOrderId: Long): Int {
open fun removeDoPickOrdersForPickOrder(pickOrderId: Long): Int {
val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId)
var deletedCount = 0
@@ -229,12 +229,12 @@ open class DoPickOrderService(
return deletedCount
}

fun saveRecord(record: DoPickOrderRecord): DoPickOrderRecord {
open fun saveRecord(record: DoPickOrderRecord): DoPickOrderRecord {
return doPickOrderRecordRepository.save(record)
}

// ✅ Add method to update DoPickOrderRecord status
fun updateRecordHandledByForPickOrder(pickOrderId: Long, userId: Long): List<DoPickOrderRecord> {
open fun updateRecordHandledByForPickOrder(pickOrderId: Long, userId: Long): List<DoPickOrderRecord> {
val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId)
val user = userRepository.findById(userId).orElse(null) // ✅ 改用 orElse(null)
val handlerName = user?.name ?: "Unknown"
@@ -248,7 +248,7 @@ open class DoPickOrderService(
}

// ✅ Add method to complete DoPickOrderRecord
fun completeDoPickOrderRecordsForPickOrder(pickOrderId: Long): List<DoPickOrderRecord> {
open fun completeDoPickOrderRecordsForPickOrder(pickOrderId: Long): List<DoPickOrderRecord> {
val doPickOrderRecords = doPickOrderRecordRepository.findByPickOrderId(pickOrderId)
doPickOrderRecords.forEach {
it.ticketStatus = DoPickOrderStatus.completed
@@ -258,18 +258,18 @@ open class DoPickOrderService(
}

// Add method to find do_pick_order records by pick order ID
fun findByPickOrderId(pickOrderId: Long): List<DoPickOrder> {
open fun findByPickOrderId(pickOrderId: Long): List<DoPickOrder> {
return doPickOrderRepository.findByPickOrderId(pickOrderId)
}

fun updateDoOrderIdForPickOrder(pickOrderId: Long, doOrderId: Long): List<DoPickOrder> {
open fun updateDoOrderIdForPickOrder(pickOrderId: Long, doOrderId: Long): List<DoPickOrder> {
val doPickOrders = doPickOrderRepository.findByPickOrderId(pickOrderId)
doPickOrders.forEach {
it.doOrderId = doOrderId
}
return doPickOrderRepository.saveAll(doPickOrders)
}
fun getSummaryByStore(storeId: String, requiredDate: LocalDate?): StoreLaneSummary {
open fun getSummaryByStore(storeId: String, requiredDate: LocalDate?): StoreLaneSummary {
val targetDate = requiredDate ?: LocalDate.now()
println("🔍 DEBUG: Getting summary for store=$storeId, date=$targetDate")
@@ -372,7 +372,7 @@ open class DoPickOrderService(
}
}
// ✅ 修复:把 assignByLane 移到类里面
fun assignByLane(request: AssignByLaneRequest): MessageResponse {
open fun assignByLane(request: AssignByLaneRequest): MessageResponse {
val user = userRepository.findById(request.userId).orElse(null)
?: return MessageResponse(
id = null, code = "USER_NOT_FOUND", name = null, type = null,


+ 3
- 1
src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt 查看文件

@@ -41,6 +41,7 @@ class DoReleaseCoordinatorService(
private val poolSize = Runtime.getRuntime().availableProcessors()
private val executor = Executors.newFixedThreadPool(min(poolSize, 4))
private val jobs = ConcurrentHashMap<String, BatchReleaseJobStatus>()

private fun updateBatchTicketNumbers() {
try {
val updateSql = """
@@ -188,6 +189,7 @@ class DoReleaseCoordinatorService(
e.printStackTrace()
}
}

private fun getOrderedDeliveryOrderIds(ids: List<Long>): List<Long> {
try {
println("🔍 DEBUG: Getting ordered IDs for ${ids.size} orders")
@@ -492,7 +494,7 @@ class DoReleaseCoordinatorService(
"4F" -> "4/F"
else -> "2/F"
}
val doPickOrder = DoPickOrder(
storeId = storeId,
ticketNo = "TEMP-${System.currentTimeMillis()}",


+ 1
- 1
src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ExportDNLabelsRequest.kt 查看文件

@@ -1,6 +1,6 @@
package com.ffii.fpsms.modules.deliveryOrder.web.models

data class ExportDNLabelsRequest (
val deliveryOrderIds: Long,
val doPickOrderId: Long,
val numOfCarton: Int,
)

+ 1
- 2
src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ExportDeliveryNoteRequest.kt 查看文件

@@ -2,8 +2,7 @@ package com.ffii.fpsms.modules.deliveryOrder.web.models
import com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrderRepository

data class ExportDeliveryNoteRequest (
val deliveryOrderIds: Long,
val doPickOrderId: Long,
val numOfCarton: Int,
val isDraft: Boolean,
val pickOrderIds: Long,
)

+ 2
- 2
src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/PrintDNLabelsRequest.kt 查看文件

@@ -1,8 +1,8 @@
package com.ffii.fpsms.modules.deliveryOrder.web.models

data class PrintDNLabelsRequest (
val deliveryOrderId: Long,
val doPickOrderId: Long,
val printerId: Long,
val printQty: Int?,
val numOfCarton: Int,
val numOfCarton: Int
)

+ 1
- 2
src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/PrintDeliveryNoteRequest.kt 查看文件

@@ -1,10 +1,9 @@
package com.ffii.fpsms.modules.deliveryOrder.web.models

data class PrintDeliveryNoteRequest(
val deliveryOrderId: Long,
val doPickOrderId: Long,
val printerId: Long,
val printQty: Int?,
val numOfCarton: Int,
val isDraft: Boolean,
val pickOrderId: Long,
)

+ 4
- 2
src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt 查看文件

@@ -713,6 +713,8 @@ open fun createBatchReleaseIssue(
FROM fpsmsdb.delivery_order_line dol
INNER JOIN fpsmsdb.items i ON i.id = dol.itemId
WHERE dol.deliveryOrderId = :deliveryOrderId
INNER JOIN fpsmsdb.items i ON i.id = dol.itemId
WHERE dol.doId = :deliveryOrderId
AND dol.deleted = 0
""".trimIndent()
@@ -736,11 +738,11 @@ open fun createBatchReleaseIssue(
val issue = PickExecutionIssue(
id = null,
pickOrderId = null, // batch release 失败时可能还没有 pick order
pickOrderId = 0L, // batch release 失败时可能还没有 pick order
pickOrderCode = deliveryOrder["code"] as? String ?: "",
pickOrderCreateDate = LocalDate.now(),
pickExecutionDate = LocalDate.now(),
pickOrderLineId = null, // batch release 失败时可能还没有 pick order line
pickOrderLineId = 0L, // batch release 失败时可能还没有 pick order line
issueNo = issueNo,
joPickOrderId = null,
doPickOrderId = null,


+ 7
- 1
src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt 查看文件

@@ -5,6 +5,7 @@ import com.ffii.fpsms.modules.master.entity.Items
import com.ffii.fpsms.modules.stock.entity.projection.InventoryInfo
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
import java.io.Serializable
import java.util.Optional
@@ -13,7 +14,12 @@ import java.util.Optional
interface InventoryRepository: AbstractRepository<Inventory, Long> {
fun findInventoryInfoByDeletedIsFalse(): List<InventoryInfo>

fun findInventoryInfoByItemCodeContainsAndItemNameContainsAndItemTypeContainsAndDeletedIsFalse(code: String, name: String, type: String, pageable: Pageable): Page<InventoryInfo>
@Query("SELECT i FROM Inventory i " +
"WHERE (:code IS NULL OR i.item.code LIKE CONCAT('%', :code, '%')) " +
"AND (:name IS NULL OR i.item.name LIKE CONCAT('%', :name, '%')) " +
"AND (:type IS NULL OR :type = '' OR i.item.type = :type) " +
"AND i.deleted = false")
fun findInventoryInfoByItemCodeContainsAndItemNameContainsAndItemTypeAndDeletedIsFalse(code: String, name: String, type: String, pageable: Pageable): Page<InventoryInfo>

fun findInventoryInfoByItemIdInAndDeletedIsFalse(itemIds: List<Serializable>): List<InventoryInfo>



+ 1
- 1
src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt 查看文件

@@ -60,7 +60,7 @@ open class InventoryService(
open fun allInventoriesByPage(request: SearchInventoryRequest): RecordsRes<InventoryInfo>{
val pageable = PageRequest.of(request.pageNum ?: 0, request.pageSize ?: 10)

val response = inventoryRepository.findInventoryInfoByItemCodeContainsAndItemNameContainsAndItemTypeContainsAndDeletedIsFalse(
val response = inventoryRepository.findInventoryInfoByItemCodeContainsAndItemNameContainsAndItemTypeAndDeletedIsFalse(
code = request.code,
name = request.name,
type = request.type,


+ 41
- 51
src/main/resources/DeliveryNote/DeliveryNotePDF.jrxml 查看文件

@@ -1,15 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Jaspersoft Studio version 6.17.0.final using JasperReports Library version 6.17.0-6d93193241dd8cc42629e188b94f9e0bc5722efd -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="DeliveryNotePDF" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" isIgnorePagination="true" uuid="36f9d415-527f-4152-b7b0-eea81fe06f73">
<property name="com.jaspersoft.studio.unit." value="pixel"/>
<property name="com.jaspersoft.studio.unit.pageHeight" value="pixel"/>
<property name="com.jaspersoft.studio.unit.pageWidth" value="pixel"/>
<property name="com.jaspersoft.studio.unit.topMargin" value="pixel"/>
<property name="com.jaspersoft.studio.unit.bottomMargin" value="pixel"/>
<property name="com.jaspersoft.studio.unit.leftMargin" value="pixel"/>
<property name="com.jaspersoft.studio.unit.rightMargin" value="pixel"/>
<property name="com.jaspersoft.studio.unit.columnWidth" value="pixel"/>
<property name="com.jaspersoft.studio.unit.columnSpacing" value="pixel"/>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="DeliveryNotePDF" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20" uuid="36f9d415-527f-4152-b7b0-eea81fe06f73">
<parameter name="deliveryOrderCode" class="java.lang.String">
<parameterDescription><![CDATA[DeliveryOrderCode]]></parameterDescription>
</parameter>
@@ -159,17 +150,7 @@
<pageHeader>
<band height="110">
<staticText>
<reportElement x="0" y="10" width="110" height="18" uuid="7f991bbe-caf4-43c1-b8e1-d85b1f2d3815">
<property name="com.jaspersoft.studio.unit.y" value="px"/>
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="微軟正黑體" size="12"/>
</textElement>
<text><![CDATA[送貨單編號:]]></text>
</staticText>
<staticText>
<reportElement x="0" y="30" width="110" height="18" uuid="7bdf9657-d5d2-4fbf-a6c3-a5da5b292dc8">
<reportElement x="0" y="10" width="110" height="18" uuid="7bdf9657-d5d2-4fbf-a6c3-a5da5b292dc8">
<property name="com.jaspersoft.studio.unit.y" value="px"/>
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
@@ -179,7 +160,7 @@
<text><![CDATA[送貨日期:]]></text>
</staticText>
<staticText>
<reportElement x="275" y="10" width="110" height="18" uuid="f53e2068-dd24-4151-bd2a-033c6cbda674">
<reportElement x="0" y="30" width="110" height="18" uuid="f53e2068-dd24-4151-bd2a-033c6cbda674">
<property name="com.jaspersoft.studio.unit.y" value="px"/>
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
@@ -193,20 +174,11 @@
<property name="com.jaspersoft.studio.unit.y" value="px"/>
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<textElement textAlignment="Left" verticalAlignment="Top">
<font fontName="微軟正黑體" size="12"/>
</textElement>
<text><![CDATA[店鋪採購單編號:]]></text>
</staticText>
<textField>
<reportElement x="110" y="10" width="150" height="18" uuid="69d21d74-e625-41e9-b4bb-abde259d6620">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="微軟正黑體"/>
</textElement>
<textFieldExpression><![CDATA[$P{deliveryOrderCode}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="110" y="50" width="425" height="54" uuid="24a1331c-e50f-4a72-9a41-3e05b85f4c21">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
@@ -217,7 +189,7 @@
<textFieldExpression><![CDATA[$P{ShopPurchaseOrderNo}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="385" y="10" width="150" height="18" uuid="78f29b7d-c311-4c53-9c66-fda8752c9797">
<reportElement x="110" y="30" width="150" height="18" uuid="78f29b7d-c311-4c53-9c66-fda8752c9797">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Middle">
@@ -226,7 +198,7 @@
<textFieldExpression><![CDATA[$P{FGPickOrderNo}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="110" y="30" width="150" height="18" uuid="e67b4047-642b-46f5-8ec7-785ead88b97e">
<reportElement x="110" y="10" width="150" height="18" uuid="e67b4047-642b-46f5-8ec7-785ead88b97e">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Middle">
@@ -235,7 +207,7 @@
<textFieldExpression><![CDATA[$P{deliveryDate}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="385" y="30" width="150" height="18" uuid="8062f7b6-8917-432a-a02c-e5a5766c14e5">
<reportElement x="385" y="10" width="150" height="18" uuid="8062f7b6-8917-432a-a02c-e5a5766c14e5">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Middle">
@@ -244,7 +216,7 @@
<textFieldExpression><![CDATA[$P{numOfCarton}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="275" y="30" width="110" height="18" uuid="25254ea4-e2b2-4ae0-975b-99c8f9390a64">
<reportElement x="275" y="10" width="110" height="18" uuid="25254ea4-e2b2-4ae0-975b-99c8f9390a64">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Middle">
@@ -252,12 +224,30 @@
</textElement>
<textFieldExpression><![CDATA[$P{totalCartonTitle}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="385" y="30" width="150" height="18" uuid="69d21d74-e625-41e9-b4bb-abde259d6620">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="微軟正黑體"/>
</textElement>
<textFieldExpression><![CDATA[$P{deliveryOrderCode}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="275" y="30" width="110" height="18" uuid="db59b94d-5a33-4c84-98d9-30b8b86bd018">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Middle">
<font fontName="微軟正黑體"/>
</textElement>
<textFieldExpression><![CDATA[$P{deliveryOrderCodeTitle}]]></textFieldExpression>
</textField>
</band>
</pageHeader>
<columnHeader>
<band height="33">
<staticText>
<reportElement x="0" y="5" width="50" height="18" uuid="d0d76c93-d260-4b03-b116-6e7ba1fdbdd8">
<reportElement x="0" y="5" width="40" height="18" uuid="d0d76c93-d260-4b03-b116-6e7ba1fdbdd8">
<property name="com.jaspersoft.studio.unit.y" value="px"/>
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<property name="com.jaspersoft.studio.unit.width" value="px"/>
@@ -268,7 +258,7 @@
<text><![CDATA[序號]]></text>
</staticText>
<staticText>
<reportElement x="50" y="5" width="110" height="18" uuid="58a5c922-fd98-4997-9b17-16bdf9f78519">
<reportElement x="40" y="5" width="110" height="18" uuid="58a5c922-fd98-4997-9b17-16bdf9f78519">
<property name="com.jaspersoft.studio.unit.y" value="px"/>
<property name="com.jaspersoft.studio.unit.height" value="px"/>
<property name="com.jaspersoft.studio.unit.width" value="px"/>
@@ -279,7 +269,7 @@
<text><![CDATA[路綫]]></text>
</staticText>
<staticText>
<reportElement x="160" y="5" width="80" height="18" uuid="65c27cc0-f806-4930-930c-6b3fd632a52f">
<reportElement x="150" y="4" width="80" height="18" uuid="65c27cc0-f806-4930-930c-6b3fd632a52f">
<property name="com.jaspersoft.studio.unit.y" value="px"/>
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
@@ -289,7 +279,7 @@
<text><![CDATA[貨品編號]]></text>
</staticText>
<staticText>
<reportElement x="240" y="5" width="230" height="18" uuid="fa7ba1d5-003a-4c99-8a2f-4162756ee515">
<reportElement x="230" y="5" width="240" height="18" uuid="fa7ba1d5-003a-4c99-8a2f-4162756ee515">
<property name="com.jaspersoft.studio.unit.y" value="px"/>
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
@@ -325,28 +315,28 @@
<detail>
<band height="42">
<textField>
<reportElement x="0" y="1" width="50" height="18" uuid="ae87b739-dadf-452a-bc35-8c2da1a6a9a8">
<reportElement x="0" y="1" width="40" height="18" uuid="ae87b739-dadf-452a-bc35-8c2da1a6a9a8">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Top">
<textElement verticalAlignment="Middle">
<font fontName="微軟正黑體"/>
</textElement>
<textFieldExpression><![CDATA[$F{sequenceNumber}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="50" y="1" width="110" height="18" uuid="b4bcfa6c-5d2e-4fba-815a-cc2fccd39213">
<reportElement x="40" y="1" width="110" height="18" uuid="b4bcfa6c-5d2e-4fba-815a-cc2fccd39213">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Top">
<textElement verticalAlignment="Middle">
<font fontName="微軟正黑體"/>
</textElement>
<textFieldExpression><![CDATA[$F{route}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="160" y="1" width="80" height="18" uuid="3e4a71e7-d6e1-4da8-ae58-ef752c289a6d">
<reportElement x="150" y="0" width="80" height="18" uuid="3e4a71e7-d6e1-4da8-ae58-ef752c289a6d">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Top">
<textElement verticalAlignment="Middle">
<font fontName="微軟正黑體"/>
</textElement>
<textFieldExpression><![CDATA[$F{itemNo}]]></textFieldExpression>
@@ -355,16 +345,16 @@
<reportElement x="470" y="0" width="84" height="18" uuid="e60b7a29-273a-4a9f-a443-f4977292c429">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Top">
<textElement verticalAlignment="Middle">
<font fontName="微軟正黑體"/>
</textElement>
<textFieldExpression><![CDATA[$F{qty} + $F{shortName}]]></textFieldExpression>
</textField>
<textField>
<reportElement x="240" y="1" width="230" height="18" uuid="c2b4da75-fdca-4e99-8103-5769dea75841">
<reportElement x="230" y="1" width="240" height="18" uuid="c2b4da75-fdca-4e99-8103-5769dea75841">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Top">
<textElement verticalAlignment="Middle">
<font fontName="微軟正黑體"/>
</textElement>
<textFieldExpression><![CDATA[$F{itemName} + "(" + $F{uom} + ")"]]></textFieldExpression>
@@ -375,10 +365,10 @@
</reportElement>
</line>
<textField>
<reportElement x="240" y="18" width="230" height="18" uuid="af701932-2e78-47d4-a131-b668200dc376">
<reportElement x="230" y="18" width="240" height="18" uuid="af701932-2e78-47d4-a131-b668200dc376">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement verticalAlignment="Top">
<textElement verticalAlignment="Middle">
<font fontName="微軟正黑體"/>
</textElement>
<textFieldExpression><![CDATA[$F{lotNo}]]></textFieldExpression>


+ 1
- 0
src/main/resources/application.yml 查看文件

@@ -35,6 +35,7 @@ m18:
username: testingMTMS
password: db25f2fc14cd2d2b1e7af307241f548fb03c312a
base-url: https://toa.m18saas.com/jsf/rfws
base-url-uat: https://toauat.m18saas.com/jsf/rfws
base-password: qwer1234
supplier:
shop-po: P06, P07


正在加载...
取消
保存