Ver código fonte

no message

master
DESKTOP-064TTA1\Fai LUK 17 horas atrás
pai
commit
ea1a2686a9
16 arquivos alterados com 2376 adições e 409 exclusões
  1. +394
    -405
      python/Bag2.py
  2. +1718
    -0
      python/Bag3.py
  3. BIN
      python/__pycache__/Bag2.cpython-313.pyc
  4. +9
    -0
      python/bag3_settings.json
  5. +1
    -0
      python/installAndExe.txt
  6. +13
    -2
      src/main/java/com/ffii/fpsms/modules/jobOrder/service/PlasticBagPrinterService.kt
  7. +8
    -0
      src/main/java/com/ffii/fpsms/py/PrintedQtyByChannel.kt
  8. +35
    -2
      src/main/java/com/ffii/fpsms/py/PyController.kt
  9. +6
    -0
      src/main/java/com/ffii/fpsms/py/PyJobOrderListItem.kt
  10. +35
    -0
      src/main/java/com/ffii/fpsms/py/PyJobOrderPrintSubmitRepository.kt
  11. +11
    -0
      src/main/java/com/ffii/fpsms/py/PyJobOrderPrintSubmitRequest.kt
  12. +9
    -0
      src/main/java/com/ffii/fpsms/py/PyJobOrderPrintSubmitResponse.kt
  13. +72
    -0
      src/main/java/com/ffii/fpsms/py/PyJobOrderPrintSubmitService.kt
  14. +8
    -0
      src/main/java/com/ffii/fpsms/py/PyPrintChannel.kt
  15. +35
    -0
      src/main/java/com/ffii/fpsms/py/entity/PyJobOrderPrintSubmit.kt
  16. +22
    -0
      src/main/resources/db/changelog/changes/20260326_fai/01_create_py_job_order_print_submit.sql

+ 394
- 405
python/Bag2.py
Diferenças do arquivo suprimidas por serem muito extensas
Ver arquivo


+ 1718
- 0
python/Bag3.py
Diferenças do arquivo suprimidas por serem muito extensas
Ver arquivo


BIN
python/__pycache__/Bag2.cpython-313.pyc Ver arquivo


+ 9
- 0
python/bag3_settings.json Ver arquivo

@@ -0,0 +1,9 @@
{
"api_ip": "127.0.0.1",
"api_port": "8090",
"dabag_ip": "192.168.17.27",
"dabag_port": "3008",
"laser_ip": "192.168.18.68",
"laser_port": "45678",
"label_com": "TSC TTP-246M Pro"
}

+ 1
- 0
python/installAndExe.txt Ver arquivo

@@ -2,6 +2,7 @@ py -m pip install pyinstaller
py -m pip install --upgrade pyinstaller
py -m PyInstaller --onefile --windowed --name "Bag1" Bag1.py

py -m PyInstaller --onefile --windowed --name "Bag3" Bag3.py

python -m pip install pyinstaller
python -m pip install --upgrade pyinstaller


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

@@ -12,7 +12,9 @@ import com.ffii.fpsms.modules.jobOrder.web.model.LaserRequest
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.stereotype.Service
import java.awt.Color
import java.awt.Font
@@ -50,6 +52,7 @@ class PlasticBagPrinterService(
private val jdbcDao: JdbcDao,
private val stockInLineRepository: StockInLineRepository,
private val settingsService: SettingsService,
private val pyJobOrderPrintSubmitService: PyJobOrderPrintSubmitService,
) {
private val logger = LoggerFactory.getLogger(javaClass)

@@ -108,7 +111,11 @@ class PlasticBagPrinterService(
allowed.contains(code)
}
}
return filtered.map { jo -> toPyJobOrderListItem(jo) }
val ids = filtered.mapNotNull { it.id }
val printed = pyJobOrderPrintSubmitService.sumPrintedQtyByJobOrderIds(ids)
return filtered.map { jo ->
toPyJobOrderListItem(jo, printed[jo.id!!])
}
}

private fun parseLaserItemCodeFilters(raw: String?): Set<String> {
@@ -119,13 +126,14 @@ class PlasticBagPrinterService(
.toSet()
}

private fun toPyJobOrderListItem(jo: JobOrder): PyJobOrderListItem {
private fun toPyJobOrderListItem(jo: JobOrder, printed: PrintedQtyByChannel?): PyJobOrderListItem {
val itemCode = jo.bom?.item?.code ?: jo.bom?.code
val itemName = jo.bom?.name ?: jo.bom?.item?.name
val itemId = jo.bom?.item?.id
val stockInLine = jo.id?.let { stockInLineRepository.findFirstByJobOrder_IdAndDeletedFalse(it) }
val stockInLineId = stockInLine?.id
val lotNo = stockInLine?.lotNo
val p = printed ?: PrintedQtyByChannel()
return PyJobOrderListItem(
id = jo.id!!,
code = jo.code,
@@ -136,6 +144,9 @@ class PlasticBagPrinterService(
stockInLineId = stockInLineId,
itemId = itemId,
lotNo = lotNo,
bagPrintedQty = p.bagPrintedQty,
labelPrintedQty = p.labelPrintedQty,
laserPrintedQty = p.laserPrintedQty,
)
}



+ 8
- 0
src/main/java/com/ffii/fpsms/py/PrintedQtyByChannel.kt Ver arquivo

@@ -0,0 +1,8 @@
package com.ffii.fpsms.py

/** Per–job-order cumulative printed qty, split by printer channel (not mixed). */
data class PrintedQtyByChannel(
val bagPrintedQty: Long = 0,
val labelPrintedQty: Long = 0,
val laserPrintedQty: Long = 0,
)

+ 35
- 2
src/main/java/com/ffii/fpsms/py/PyController.kt Ver arquivo

@@ -7,9 +7,13 @@ import com.ffii.fpsms.modules.stock.entity.StockInLineRepository
import org.springframework.format.annotation.DateTimeFormat
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.server.ResponseStatusException
import org.springframework.http.HttpStatus
import java.time.LocalDate
import java.time.LocalDateTime

@@ -23,6 +27,7 @@ open class PyController(
private val jobOrderRepository: JobOrderRepository,
private val stockInLineRepository: StockInLineRepository,
private val plasticBagPrinterService: PlasticBagPrinterService,
private val pyJobOrderPrintSubmitService: PyJobOrderPrintSubmitService,
) {
companion object {
private const val PACKAGING_PROCESS_NAME = "包裝"
@@ -46,10 +51,34 @@ open class PyController(
dayEndExclusive,
PACKAGING_PROCESS_NAME,
)
val list = orders.map { jo -> toListItem(jo) }
val ids = orders.mapNotNull { it.id }
val printed = pyJobOrderPrintSubmitService.sumPrintedQtyByJobOrderIds(ids)
val list = orders.map { jo ->
toListItem(jo, printed[jo.id!!])
}
return ResponseEntity.ok(list)
}

/**
* Record a print submit from Bag2 (e.g. 標簽機). No login.
* POST /py/job-order-print-submit
* Body: { "jobOrderId": 1, "qty": 10, "printChannel": "LABEL" | "DATAFLEX" | "LASER" }
*/
@PostMapping("/job-order-print-submit")
open fun submitJobOrderPrint(
@RequestBody body: PyJobOrderPrintSubmitRequest,
): ResponseEntity<PyJobOrderPrintSubmitResponse> {
val channel = body.printChannel?.trim()?.takeIf { it.isNotEmpty() } ?: PyPrintChannel.LABEL
if (
channel != PyPrintChannel.LABEL &&
channel != PyPrintChannel.DATAFLEX &&
channel != PyPrintChannel.LASER
) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Unsupported printChannel: $channel")
}
return ResponseEntity.ok(pyJobOrderPrintSubmitService.recordPrint(body.jobOrderId, body.qty, channel))
}

/**
* Same as [listJobOrders] but filtered by system setting [com.ffii.fpsms.modules.common.SettingNames.LASER_PRINT_ITEM_CODES]
* (comma-separated item codes). Public — no login (same as /py/job-orders).
@@ -62,13 +91,14 @@ open class PyController(
return ResponseEntity.ok(plasticBagPrinterService.listLaserPrintJobOrders(date))
}

private fun toListItem(jo: JobOrder): PyJobOrderListItem {
private fun toListItem(jo: JobOrder, printed: PrintedQtyByChannel?): PyJobOrderListItem {
val itemCode = jo.bom?.item?.code ?: jo.bom?.code
val itemName = jo.bom?.name ?: jo.bom?.item?.name
val itemId = jo.bom?.item?.id
val stockInLine = jo.id?.let { stockInLineRepository.findFirstByJobOrder_IdAndDeletedFalse(it) }
val stockInLineId = stockInLine?.id
val lotNo = stockInLine?.lotNo
val p = printed ?: PrintedQtyByChannel()
return PyJobOrderListItem(
id = jo.id!!,
code = jo.code,
@@ -79,6 +109,9 @@ open class PyController(
stockInLineId = stockInLineId,
itemId = itemId,
lotNo = lotNo,
bagPrintedQty = p.bagPrintedQty,
labelPrintedQty = p.labelPrintedQty,
laserPrintedQty = p.laserPrintedQty,
)
}
}

+ 6
- 0
src/main/java/com/ffii/fpsms/py/PyJobOrderListItem.kt Ver arquivo

@@ -19,4 +19,10 @@ data class PyJobOrderListItem(
val stockInLineId: Long?,
val itemId: Long?,
val lotNo: String?,
/** Cumulative qty from 打袋機 DataFlex submits (DATAFLEX). */
val bagPrintedQty: Long = 0,
/** Cumulative qty from 標簽機 submits (LABEL). */
val labelPrintedQty: Long = 0,
/** Cumulative qty from 激光機 submits (LASER). */
val laserPrintedQty: Long = 0,
)

+ 35
- 0
src/main/java/com/ffii/fpsms/py/PyJobOrderPrintSubmitRepository.kt Ver arquivo

@@ -0,0 +1,35 @@
package com.ffii.fpsms.py

import com.ffii.core.support.AbstractRepository
import com.ffii.fpsms.py.entity.PyJobOrderPrintSubmit
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param

interface PyJobOrderPrintSubmitRepository : AbstractRepository<PyJobOrderPrintSubmit, Long> {

@Query(
value =
"SELECT job_order_id, COALESCE(SUM(qty), 0) FROM py_job_order_print_submit " +
"WHERE deleted = 0 AND job_order_id IN (:ids) AND print_channel = :channel " +
"GROUP BY job_order_id",
nativeQuery = true,
)
fun sumQtyGroupedByJobOrderId(
@Param("ids") ids: List<Long>,
@Param("channel") channel: String,
): List<Array<Any>>

/**
* One row per (job_order_id, print_channel) with summed qty.
*/
@Query(
value =
"SELECT job_order_id, print_channel, COALESCE(SUM(qty), 0) FROM py_job_order_print_submit " +
"WHERE deleted = 0 AND job_order_id IN (:ids) " +
"GROUP BY job_order_id, print_channel",
nativeQuery = true,
)
fun sumQtyGroupedByJobOrderIdAndChannel(
@Param("ids") ids: List<Long>,
): List<Array<Any>>
}

+ 11
- 0
src/main/java/com/ffii/fpsms/py/PyJobOrderPrintSubmitRequest.kt Ver arquivo

@@ -0,0 +1,11 @@
package com.ffii.fpsms.py

/**
* POST /py/job-order-print-submit
*/
data class PyJobOrderPrintSubmitRequest(
val jobOrderId: Long,
val qty: Int,
/** [PyPrintChannel.LABEL] | [PyPrintChannel.DATAFLEX] | [PyPrintChannel.LASER]; omit or blank → LABEL. */
val printChannel: String? = null,
)

+ 9
- 0
src/main/java/com/ffii/fpsms/py/PyJobOrderPrintSubmitResponse.kt Ver arquivo

@@ -0,0 +1,9 @@
package com.ffii.fpsms.py

data class PyJobOrderPrintSubmitResponse(
val jobOrderId: Long,
val submittedQty: Int,
val printChannel: String,
/** Cumulative printed qty for this job order and [printChannel] after this submit. */
val cumulativeQtyForChannel: Long,
)

+ 72
- 0
src/main/java/com/ffii/fpsms/py/PyJobOrderPrintSubmitService.kt Ver arquivo

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

import com.ffii.fpsms.modules.jobOrder.entity.JobOrderRepository
import com.ffii.fpsms.py.entity.PyJobOrderPrintSubmit
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.server.ResponseStatusException

@Service
open class PyJobOrderPrintSubmitService(
private val repository: PyJobOrderPrintSubmitRepository,
private val jobOrderRepository: JobOrderRepository,
) {

/**
* Cumulative printed qty per job order, split by channel (打袋 / 標籤 / 激光).
*/
open fun sumPrintedQtyByJobOrderIds(ids: List<Long>): Map<Long, PrintedQtyByChannel> {
if (ids.isEmpty()) return emptyMap()
val rows = repository.sumQtyGroupedByJobOrderIdAndChannel(ids)
val out = mutableMapOf<Long, PrintedQtyByChannel>()
for (row in rows) {
val jobOrderId = (row[0] as Number).toLong()
val channel = (row[1] as String).trim()
val qty = (row[2] as Number).toLong()
val cur = out.getOrDefault(jobOrderId, PrintedQtyByChannel())
out[jobOrderId] =
when (channel) {
PyPrintChannel.DATAFLEX -> cur.copy(bagPrintedQty = qty)
PyPrintChannel.LABEL -> cur.copy(labelPrintedQty = qty)
PyPrintChannel.LASER -> cur.copy(laserPrintedQty = qty)
else -> cur
}
}
return out
}

private fun sumPrintedByJobOrderIdsAndChannel(ids: List<Long>, channel: String): Map<Long, Long> {
if (ids.isEmpty()) return emptyMap()
val rows = repository.sumQtyGroupedByJobOrderId(ids, channel)
return rows.associate { row ->
(row[0] as Number).toLong() to (row[1] as Number).toLong()
}
}

/**
* Persist one submit row and return cumulative print total for that job order and channel.
*/
@Transactional
open fun recordPrint(jobOrderId: Long, qty: Int, printChannel: String): PyJobOrderPrintSubmitResponse {
if (qty < 1) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "qty must be at least 1")
}
val jo = jobOrderRepository.findById(jobOrderId).orElseThrow {
ResponseStatusException(HttpStatus.NOT_FOUND, "Job order not found: $jobOrderId")
}
val row = PyJobOrderPrintSubmit().apply {
jobOrder = jo
this.qty = qty
this.printChannel = printChannel
}
repository.save(row)
val total = sumPrintedByJobOrderIdsAndChannel(listOf(jobOrderId), printChannel)[jobOrderId] ?: qty.toLong()
return PyJobOrderPrintSubmitResponse(
jobOrderId = jobOrderId,
submittedQty = qty,
printChannel = printChannel,
cumulativeQtyForChannel = total,
)
}
}

+ 8
- 0
src/main/java/com/ffii/fpsms/py/PyPrintChannel.kt Ver arquivo

@@ -0,0 +1,8 @@
package com.ffii.fpsms.py

/** Values for [com.ffii.fpsms.py.entity.PyJobOrderPrintSubmit.printChannel]. */
object PyPrintChannel {
const val LABEL = "LABEL"
const val DATAFLEX = "DATAFLEX"
const val LASER = "LASER"
}

+ 35
- 0
src/main/java/com/ffii/fpsms/py/entity/PyJobOrderPrintSubmit.kt Ver arquivo

@@ -0,0 +1,35 @@
package com.ffii.fpsms.py.entity

import com.ffii.core.entity.BaseEntity
import com.ffii.fpsms.modules.jobOrder.entity.JobOrder
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.FetchType
import jakarta.persistence.JoinColumn
import jakarta.persistence.ManyToOne
import jakarta.persistence.Table
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Size

/**
* One row each time a Bag/py client submits a print quantity for a job order (per printer channel).
* [printChannel] distinguishes 打袋 (DATAFLEX), 標籤 (LABEL), 激光 (LASER); cumulative [qty] per channel.
*/
@Entity
@Table(name = "py_job_order_print_submit")
open class PyJobOrderPrintSubmit : BaseEntity<Long>() {

@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "job_order_id", nullable = false, columnDefinition = "INT")
open var jobOrder: JobOrder? = null

@NotNull
@Column(name = "qty", nullable = false)
open var qty: Int? = null

@Size(max = 32)
@NotNull
@Column(name = "print_channel", nullable = false, length = 32)
open var printChannel: String? = null
}

+ 22
- 0
src/main/resources/db/changelog/changes/20260326_fai/01_create_py_job_order_print_submit.sql Ver arquivo

@@ -0,0 +1,22 @@
--liquibase formatted sql
--changeset fai:20260326_py_job_order_print_submit

CREATE TABLE IF NOT EXISTS `py_job_order_print_submit` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`createdBy` VARCHAR(30) NULL DEFAULT NULL,
`version` INT NOT NULL DEFAULT '0',
`modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`modifiedBy` VARCHAR(30) NULL DEFAULT NULL,
`deleted` TINYINT(1) NOT NULL DEFAULT '0',

`job_order_id` INT NOT NULL COMMENT 'FK job_order (must match job_order.id type)',
`qty` INT NOT NULL COMMENT 'Quantity printed this submit (labels, bags, etc.)',
`print_channel` VARCHAR(32) NOT NULL DEFAULT 'LABEL' COMMENT 'LABEL=標簽機, DATAFLEX=打袋機, …',

CONSTRAINT `pk_py_job_order_print_submit` PRIMARY KEY (`id`),
KEY `idx_py_jops_job_order` (`job_order_id`),
KEY `idx_py_jops_channel_created` (`print_channel`, `created`),
CONSTRAINT `fk_py_jops_job_order` FOREIGN KEY (`job_order_id`) REFERENCES `job_order` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
COMMENT='Per-submit print qty for Bag2/py clients; cumulative per job order for wastage/stock.';

Carregando…
Cancelar
Salvar