From 5672cf3364a8e90f490859d5915aafe0569da77c Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 30 Oct 2025 23:12:26 +0800 Subject: [PATCH 1/3] [API] Simplify (Rewrite) ApiCallerService, and add "post" function. --- .../fpsms/api/service/ApiCallerService.kt | 282 ++++++++++++------ src/main/java/com/ffii/fpsms/m18/M18Config.kt | 3 + .../fpsms/m18/service/M18SchedulerService.kt | 113 ------- src/main/resources/application.yml | 1 + 4 files changed, 195 insertions(+), 204 deletions(-) delete mode 100644 src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt 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 8a99759..ffd3257 100644 --- a/src/main/java/com/ffii/fpsms/api/service/ApiCallerService.kt +++ b/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{ val params = M18TokenRequest( grant_type = m18Config.GRANT_TYPE, client_id = m18Config.CLIENT_ID, @@ -59,96 +77,69 @@ open class ApiCallerService( password = m18Config.PASSWORD ) - get("/oauth/token", params, null) - .subscribe( - { response -> + return get("/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 get( + inline fun get( urlPath: String, - params: MultiValueMap, - customHeaders: Map? + params: U? = null, + customHeaders: Map? = null ): Mono { - 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().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() + else -> LinkedMultiValueMap().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(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 get( urlPath: String, params: Map?, - customHeaders: Map? + customHeaders: Map? = null ): Mono { - // convert to multiValueMap val queryParams = params?.let { paramMap -> LinkedMultiValueMap().apply { @@ -161,7 +152,7 @@ open class ApiCallerService( } } ?: LinkedMultiValueMap() - return get(urlPath, queryParams, customHeaders) + return executeGet(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 get( + inline fun executeGet( urlPath: String, - params: Map? + params: MultiValueMap, + customHeaders: Map? ): Mono { - return get(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 get( + inline fun post( urlPath: String, - params: U?, - customHeaders: Map? + params: U? = null, + customHeaders: Map? = null ): Mono { - - // convert to multiValueMap - val queryParams = params?.let { - LinkedMultiValueMap().apply { + val queryParams = when (params) { + is Map<*, *> -> LinkedMultiValueMap().apply { + params.forEach { (k, v) -> + when (v) { + is Collection<*> -> addAll(k.toString(), v.map { it.toString() }) + else -> add(k.toString(), v.toString()) + } + } + } + null -> LinkedMultiValueMap() + else -> LinkedMultiValueMap().apply { U::class.memberProperties.forEach { property -> val key = property.name val value = property.get(params) @@ -205,25 +250,80 @@ open class ApiCallerService( } } } + } + + return executePost(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 post( + urlPath: String, + params: Map?, + customHeaders: Map? = null + ): Mono { + // convert to multiValueMap + val queryParams = params?.let { paramMap -> + LinkedMultiValueMap().apply { + paramMap.forEach { (key, value) -> + when (value) { + is Collection<*> -> addAll(key, value.map { it.toString() }) + else -> add(key, value.toString()) + } + } + } } ?: LinkedMultiValueMap() - return get(urlPath, queryParams, customHeaders) + return executePost(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 get( + inline fun executePost( urlPath: String, - params: U?, + params: MultiValueMap, + customHeaders: Map? ): Mono { + println("ACCESS TOKEN: ${m18Config.ACCESS_TOKEN}") - return get(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}") } + ) } - } \ 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 92008e9..c6df36b 100644 --- a/src/main/java/com/ffii/fpsms/m18/M18Config.kt +++ b/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 = listOf("P06", "P07", "T62"); // If need oem type diff --git a/src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt b/src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt deleted file mode 100644 index 1a0a239..0000000 --- a/src/main/java/com/ffii/fpsms/m18/service/M18SchedulerService.kt +++ /dev/null @@ -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)}") - } -} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d129fd4..94fc5fb 100644 --- a/src/main/resources/application.yml +++ b/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 From eb0420364c6fe910efe5e9fd9d7907c852d08725 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 30 Oct 2025 23:25:00 +0800 Subject: [PATCH 2/3] [Inventory] Quick fix for searching --- .../fpsms/modules/stock/entity/InventoryRepository.kt | 8 +++++++- .../ffii/fpsms/modules/stock/service/InventoryService.kt | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt b/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt index 4d5ae3e..e5cb69e 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/entity/InventoryRepository.kt +++ b/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 { fun findInventoryInfoByDeletedIsFalse(): List - fun findInventoryInfoByItemCodeContainsAndItemNameContainsAndItemTypeContainsAndDeletedIsFalse(code: String, name: String, type: String, pageable: Pageable): Page + @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 fun findInventoryInfoByItemIdInAndDeletedIsFalse(itemIds: List): List diff --git a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt index 06f9292..9dd6fcb 100644 --- a/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt +++ b/src/main/java/com/ffii/fpsms/modules/stock/service/InventoryService.kt @@ -60,7 +60,7 @@ open class InventoryService( open fun allInventoriesByPage(request: SearchInventoryRequest): RecordsRes{ 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, From fa5bafd9e3eff946f15555b5c01239257c434243 Mon Sep 17 00:00:00 2001 From: "kelvin.yau" Date: Fri, 31 Oct 2025 10:34:22 +0800 Subject: [PATCH 3/3] dn fix phase 1 --- .../entity/DoPickOrderRepository.kt | 2 + .../service/DeliveryOrderService.kt | 159 ++++++++++++------ .../service/DoPickOrderService.kt | 30 ++-- .../service/DoReleaseCoordinatorService.kt | 4 +- .../web/models/ExportDNLabelsRequest.kt | 2 +- .../web/models/ExportDeliveryNoteRequest.kt | 3 +- .../web/models/PrintDNLabelsRequest.kt | 4 +- .../web/models/PrintDeliveryNoteRequest.kt | 3 +- .../service/PickExecutionIssueService.kt | 6 +- .../DeliveryNote/DeliveryNotePDF.jrxml | 92 +++++----- 10 files changed, 175 insertions(+), 130 deletions(-) diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt index ebc8eb9..b60f038 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/entity/DoPickOrderRepository.kt +++ b/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 ): List + } \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt index 6e0aeb3..1216d58 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DeliveryOrderService.kt +++ b/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>() - val params = mutableMapOf() + 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() + 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>() + val params = mutableMapOf() - 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() + + 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() val fields = mutableListOf>() + for (info in cartonLabelInfo) { val field = mutableMapOf() } - 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() @@ -828,7 +879,7 @@ open class DeliveryOrderService( val pdf = exportDNLabels( ExportDNLabelsRequest( - deliveryOrderIds = request.deliveryOrderId, + doPickOrderId = request.doPickOrderId, numOfCarton = request.numOfCarton ) ) diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt index 6634ae5..36cab53 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoPickOrderService.kt +++ b/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 { + open fun findByStoreIdOrderByTruckDepartureTime(storeId: String): List { 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 { + open fun updateHandledByForPickOrder(pickOrderId: Long, userId: Long): List { 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 { + open fun completeDoPickOrdersForPickOrder(pickOrderId: Long): List { 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 { + open fun updateRecordHandledByForPickOrder(pickOrderId: Long, userId: Long): List { 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 { + open fun completeDoPickOrderRecordsForPickOrder(pickOrderId: Long): List { 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 { + open fun findByPickOrderId(pickOrderId: Long): List { return doPickOrderRepository.findByPickOrderId(pickOrderId) } - fun updateDoOrderIdForPickOrder(pickOrderId: Long, doOrderId: Long): List { + open fun updateDoOrderIdForPickOrder(pickOrderId: Long, doOrderId: Long): List { 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, diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt index fb42cbd..60f02bf 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/service/DoReleaseCoordinatorService.kt +++ b/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() + private fun updateBatchTicketNumbers() { try { val updateSql = """ @@ -188,6 +189,7 @@ class DoReleaseCoordinatorService( e.printStackTrace() } } + private fun getOrderedDeliveryOrderIds(ids: List): List { 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()}", diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ExportDNLabelsRequest.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ExportDNLabelsRequest.kt index 4114f59..0e108bb 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ExportDNLabelsRequest.kt +++ b/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, ) diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ExportDeliveryNoteRequest.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ExportDeliveryNoteRequest.kt index e99dc7e..ea092c0 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/ExportDeliveryNoteRequest.kt +++ b/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, ) diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/PrintDNLabelsRequest.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/PrintDNLabelsRequest.kt index 970ce1f..276e903 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/PrintDNLabelsRequest.kt +++ b/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 ) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/PrintDeliveryNoteRequest.kt b/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/PrintDeliveryNoteRequest.kt index 22380c0..a0ee91f 100644 --- a/src/main/java/com/ffii/fpsms/modules/deliveryOrder/web/models/PrintDeliveryNoteRequest.kt +++ b/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, ) \ No newline at end of file diff --git a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt b/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt index 42fd4ae..92340b9 100644 --- a/src/main/java/com/ffii/fpsms/modules/pickOrder/service/PickExecutionIssueService.kt +++ b/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, diff --git a/src/main/resources/DeliveryNote/DeliveryNotePDF.jrxml b/src/main/resources/DeliveryNote/DeliveryNotePDF.jrxml index 8852a12..429d54f 100644 --- a/src/main/resources/DeliveryNote/DeliveryNotePDF.jrxml +++ b/src/main/resources/DeliveryNote/DeliveryNotePDF.jrxml @@ -1,15 +1,6 @@ - - - - - - - - - - + @@ -159,17 +150,7 @@ - - - - - - - - - - - + @@ -179,7 +160,7 @@ - + @@ -193,20 +174,11 @@ - + - - - - - - - - - @@ -217,7 +189,7 @@ - + @@ -226,7 +198,7 @@ - + @@ -235,7 +207,7 @@ - + @@ -244,7 +216,7 @@ - + @@ -252,12 +224,30 @@ + + + + + + + + + + + + + + + + + + - + @@ -268,7 +258,7 @@ - + @@ -279,7 +269,7 @@ - + @@ -289,7 +279,7 @@ - + @@ -325,28 +315,28 @@ - + - + - + - + - + - + @@ -355,16 +345,16 @@ - + - + - + @@ -375,10 +365,10 @@ - + - +