Ver código fonte

no message

master
Fai Luk 2 dias atrás
pai
commit
1981b3fcf3
7 arquivos alterados com 138 adições e 6 exclusões
  1. +14
    -1
      src/main/java/com/ffii/fpsms/m18/M18GrnRules.kt
  2. +61
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt
  3. +10
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt
  4. +6
    -0
      src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/PlasticPrintRequest.kt
  5. +18
    -3
      src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt
  6. +5
    -0
      src/main/resources/application.yml
  7. +24
    -2
      src/main/resources/jasper/StockInTraceabilityReport.jrxml

+ 14
- 1
src/main/java/com/ffii/fpsms/m18/M18GrnRules.kt Ver arquivo

@@ -3,7 +3,20 @@ package com.ffii.fpsms.m18
/**
* M18 PO [createUid] is stored on [com.ffii.fpsms.modules.purchaseOrder.entity.PurchaseOrder.m18CreatedUId].
* For these M18 user ids, FPSMS does not post GRN to M18 (see [com.ffii.fpsms.modules.stock.service.StockInLineService]).
* Remarks are shown on PO stock-in traceability report ([com.ffii.fpsms.modules.report.service.ReportService.searchStockInTraceabilityReport]).
*/
object M18GrnRules {
val SKIP_GRN_FOR_M18_CREATED_UIDS: Set<Long> = setOf(2569L, 2676L)
private val REMARKS_FOR_M18_CREATED_UID: Map<Long, String> = mapOf(
2569L to "legato",
2676L to "xtech",
)

val SKIP_GRN_FOR_M18_CREATED_UIDS: Set<Long> = REMARKS_FOR_M18_CREATED_UID.keys

/** Display string for PDF/Excel: `2569 (legato)`, or plain id when unknown. */
fun formatM18CreatedUidForReport(uid: Long?): String {
if (uid == null) return ""
val remark = REMARKS_FOR_M18_CREATED_UID[uid]
return if (remark != null) "$uid ($remark)" else uid.toString()
}
}

+ 61
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt Ver arquivo

@@ -9,12 +9,14 @@ import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2SendResponse
import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2SettingsResponse
import com.ffii.fpsms.modules.jobOrder.web.model.PrintRequest
import com.ffii.fpsms.modules.jobOrder.web.model.LaserRequest
import com.ffii.fpsms.modules.jobOrder.web.model.NgpclPushResponse
import com.ffii.fpsms.modules.jobOrder.web.model.OnPackQrJobOrderRequest
import com.ffii.fpsms.modules.settings.service.SettingsService
import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import com.ffii.fpsms.py.PrintedQtyByChannel
import com.ffii.fpsms.py.PyJobOrderListItem
import com.ffii.fpsms.py.PyJobOrderPrintSubmitService
import org.springframework.core.env.Environment
import org.springframework.stereotype.Service
import java.awt.Color
import java.awt.Font
@@ -28,6 +30,10 @@ import javax.imageio.ImageIO
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.net.Socket
import java.net.InetSocketAddress
import java.io.PrintWriter
@@ -41,6 +47,7 @@ import java.net.ConnectException
import java.net.SocketTimeoutException
import org.springframework.core.io.ClassPathResource
import org.slf4j.LoggerFactory
import java.time.Duration
import java.time.LocalDate

// Data class to store bitmap bytes + width (for XML)
@@ -53,6 +60,7 @@ class PlasticBagPrinterService(
private val stockInLineRepository: StockInLineRepository,
private val settingsService: SettingsService,
private val pyJobOrderPrintSubmitService: PyJobOrderPrintSubmitService,
private val environment: Environment,
) {
private val logger = LoggerFactory.getLogger(javaClass)

@@ -537,6 +545,59 @@ class PlasticBagPrinterService(
return baos.toByteArray()
}

/**
* Builds the same ZIP as [generateOnPackQrTextZip] and POSTs it to [ngpcl.push-url] (application/zip).
* When the URL is blank, returns [NgpclPushResponse] with pushed=false so callers can fall back to manual download.
*/
fun pushOnPackQrTextZipToNgpcl(jobOrders: List<OnPackQrJobOrderRequest>): NgpclPushResponse {
val url = (environment.getProperty("ngpcl.push-url") ?: "").trim()
if (url.isEmpty()) {
return NgpclPushResponse(
pushed = false,
message = "NGPCL push URL not configured. Set ngpcl.push-url or NGPCL_PUSH_URL, or download the ZIP and transfer loose files manually.",
)
}
val zipBytes = try {
generateOnPackQrTextZip(jobOrders)
} catch (e: Exception) {
logger.warn("OnPack text ZIP generation failed before NGPCL push", e)
return NgpclPushResponse(
pushed = false,
message = e.message ?: "ZIP generation failed",
)
}
return try {
val client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build()
val request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(Duration.ofMinutes(2))
.header("Content-Type", "application/zip")
.POST(HttpRequest.BodyPublishers.ofByteArray(zipBytes))
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
val body = response.body()
if (response.statusCode() in 200..299) {
NgpclPushResponse(
pushed = true,
message = "NGPCL accepted (HTTP ${response.statusCode()})${if (body.isNotBlank()) ": ${body.take(300)}" else ""}",
)
} else {
NgpclPushResponse(
pushed = false,
message = "NGPCL returned HTTP ${response.statusCode()}: ${body.take(500)}",
)
}
} catch (e: Exception) {
logger.error("NGPCL push failed", e)
NgpclPushResponse(
pushed = false,
message = e.message ?: "Push failed: ${e.javaClass.simpleName}",
)
}
}

/**
* Returns uppercase item codes present in `onpack_qr` with the given [templateType] (`bmp` or `text`).
* Empty or NULL `template_type` is treated as `bmp` for backward compatibility.


+ 10
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/web/PlasticBagPrinterController.kt Ver arquivo

@@ -7,6 +7,7 @@ import com.ffii.fpsms.modules.jobOrder.web.model.Laser2Request
import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2SendRequest
import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2SendResponse
import com.ffii.fpsms.modules.jobOrder.web.model.LaserBag2SettingsResponse
import com.ffii.fpsms.modules.jobOrder.web.model.NgpclPushResponse
import com.ffii.fpsms.modules.jobOrder.web.model.OnPackQrDownloadRequest
import com.ffii.fpsms.modules.jobOrder.web.model.PrinterStatusRequest
import com.ffii.fpsms.modules.jobOrder.web.model.PrinterStatusResponse
@@ -152,6 +153,15 @@ class PlasticBagPrinterController(
}
}

/**
* Same payload as [downloadOnPackQrText], but POSTs the generated ZIP to `ngpcl.push-url` (application/zip).
* Returns JSON: `{ pushed, message }`. When push URL is unset, `pushed` is false — use download instead.
*/
@PostMapping("/ngpcl/push-onpack-qr-text")
fun pushOnPackQrTextToNgpcl(@RequestBody request: OnPackQrDownloadRequest): ResponseEntity<NgpclPushResponse> {
return ResponseEntity.ok(plasticBagPrinterService.pushOnPackQrTextZipToNgpcl(request.jobOrders))
}

/**
* Test API to generate and download the printer job files as a ZIP.
* ONPACK2030


+ 6
- 0
src/main/java/com/ffii/fpsms/modules/jobOrder/web/model/PlasticPrintRequest.kt Ver arquivo

@@ -57,4 +57,10 @@ data class OnPackQrDownloadRequest(
data class OnPackQrJobOrderRequest(
val jobOrderId: Long,
val itemCode: String,
)

/** Result of POST /plastic/ngpcl/push-onpack-qr-text — server POSTs the lemon ZIP bytes to [ngpcl.push-url] when configured. */
data class NgpclPushResponse(
val pushed: Boolean,
val message: String,
)

+ 18
- 3
src/main/java/com/ffii/fpsms/modules/report/service/ReportService.kt Ver arquivo

@@ -647,9 +647,10 @@ return result
}

/**
* Queries the database for Stock In Traceability Report data.
* Queries the database for Stock In Traceability Report data (PO 入倉 / 入倉追蹤 PDF).
* Joins stock_in_line, stock_in, items, qc_result, inventory_lot, inventory_lot_line, warehouse, and shop tables.
* Supports comma-separated values for stockCategory (items.type) and itemCode.
* Adds `poM18CreatorDisplay` from `purchase_order.m18CreatedUId` via [M18GrnRules.formatM18CreatedUidForReport].
*/
fun searchStockInTraceabilityReport(
stockCategory: String?,
@@ -704,6 +705,7 @@ return result
COALESCE(wh.code, '') as storeLocation,
COALESCE(sp_si.code, sp_po.code, '') as supplierID,
COALESCE(sp_si.name, sp_po.name, '') as supplierName,
po.m18CreatedUId AS poM18CreatedUId,
TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(SUM(COALESCE(sil.acceptedQty, 0)) OVER (PARTITION BY it.id), 2))) as totalStockInQty,
TRIM(TRAILING '.' FROM TRIM(TRAILING '0' FROM FORMAT(SUM(COALESCE(sil.acceptedQty, 0)) OVER (PARTITION BY it.id), 2))) as totalIqcSampleQty
FROM stock_in_line sil
@@ -737,8 +739,21 @@ return result
$lastInDateEndSql
ORDER BY it.code, sil.lotNo
""".trimIndent()
return jdbcDao.queryForList(sql, args)

val rows = jdbcDao.queryForList(sql, args)
return rows.map { row ->
val m = LinkedHashMap<String, Any?>(row)
val raw = m.remove("poM18CreatedUId")
val uid = when (raw) {
null -> null
is Number -> raw.toLong()
is BigDecimal -> raw.toLong()
else -> raw.toString().toLongOrNull()
}
m["poM18CreatorDisplay"] = M18GrnRules.formatM18CreatedUidForReport(uid)
@Suppress("UNCHECKED_CAST")
m as Map<String, Any>
}
}

/**


+ 5
- 0
src/main/resources/application.yml Ver arquivo

@@ -50,6 +50,11 @@ jwt:
logging:
config: 'classpath:log4j2.yml'

# Optional NGPCL gateway: receives the same bytes as /plastic/download-onpack-qr-text (Content-Type: application/zip).
# Leave empty to disable; set NGPCL_PUSH_URL in production if you expose an HTTP receiver for the lemon OnPack ZIP.
ngpcl:
push-url: ${NGPCL_PUSH_URL:}

bom:
import:
temp-dir: ${java.io.tmpdir}/fpsms-bom-import


+ 24
- 2
src/main/resources/jasper/StockInTraceabilityReport.jrxml Ver arquivo

@@ -57,6 +57,7 @@
<field name="storeLocation" class="java.lang.String"/>
<field name="supplierID" class="java.lang.String"/>
<field name="supplierName" class="java.lang.String"/>
<field name="poM18CreatorDisplay" class="java.lang.String"/>
<field name="totalStockInQty" class="java.lang.String"/>
<field name="totalIqcSampleQty" class="java.lang.String"/>
<field name="totalIqcDefectQty" class="java.lang.String"/>
@@ -173,7 +174,7 @@
<text><![CDATA[送貨單編號]]></text>
</staticText>
<staticText>
<reportElement stretchType="RelativeToTallestObject" x="690" y="80" width="108" height="28" uuid="db5b9c55-0185-420b-ba6c-0e10d154cc8a">
<reportElement stretchType="RelativeToTallestObject" x="690" y="80" width="65" height="28" uuid="db5b9c55-0185-420b-ba6c-0e10d154cc8a">
<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"/>
@@ -183,6 +184,18 @@
</textElement>
<text><![CDATA[供應商名稱]]></text>
</staticText>
<staticText>
<reportElement stretchType="RelativeToTallestObject" x="758" y="80" width="42" height="28" uuid="a1b2c3d4-e5f6-7890-abcd-ef1234567890">
<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"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font fontName="微軟正黑體" size="9"/>
</textElement>
<text><![CDATA[PO建立者
(M18)]]></text>
</staticText>
<staticText>
<reportElement stretchType="RelativeToTallestObject" x="220" y="80" width="60" height="28" uuid="cd7a146a-1af0-4428-9b88-dcb159691656">
<property name="com.jaspersoft.studio.unit.y" value="px"/>
@@ -471,7 +484,7 @@
<textFieldExpression><![CDATA[$F{storeLocation}]]></textFieldExpression>
</textField>
<textField textAdjust="StretchHeight">
<reportElement x="690" y="2" width="108" height="18" uuid="eb6ed0fc-bfda-4a89-a163-fe08b00a0120">
<reportElement x="690" y="2" width="65" height="18" uuid="eb6ed0fc-bfda-4a89-a163-fe08b00a0120">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Top">
@@ -479,6 +492,15 @@
</textElement>
<textFieldExpression><![CDATA[$F{supplierName}]]></textFieldExpression>
</textField>
<textField textAdjust="StretchHeight">
<reportElement x="758" y="2" width="42" height="18" uuid="f2e3d4c5-b6a7-8901-cdef-234567890abc">
<property name="com.jaspersoft.studio.unit.height" value="px"/>
</reportElement>
<textElement textAlignment="Left" verticalAlignment="Top">
<font fontName="微軟正黑體" size="9"/>
</textElement>
<textFieldExpression><![CDATA[$F{poM18CreatorDisplay}]]></textFieldExpression>
</textField>
</band>
</detail>
</jasperReport>

Carregando…
Cancelar
Salvar