Ver a proveniência

Keep demo sample dates current so delivery orders and lots stay visible.

Shift DEMO- dates forward each day, add a today row for every DO tab and status, and hide goods-receipt rows that have no purchase order.
debrand
Harry Groves há 4 semanas
ascendente
cometimento
df14864c0f
6 ficheiros alterados com 566 adições e 8 eliminações
  1. +4
    -0
      docs/DEMO_BRANCH_SETUP.md
  2. +332
    -0
      src/main/java/com/ffii/fpsms/modules/common/demo/DemoDateKeeper.kt
  3. +188
    -3
      src/main/java/com/ffii/fpsms/modules/common/demo/DemoSandboxSeeder.kt
  4. +6
    -5
      src/main/java/com/ffii/fpsms/modules/dashboard/service/GoodsReceiptStatusService.kt
  5. +3
    -0
      src/main/resources/application-db-demo.yml
  6. +33
    -0
      src/test/kotlin/com/ffii/fpsms/modules/common/demo/DemoDateShiftTest.kt

+ 4
- 0
docs/DEMO_BRANCH_SETUP.md Ver ficheiro

@@ -93,6 +93,10 @@ JDK 17. From `FPSMS-backend`, on branch `debrand`:

The first boot inserts the fictional sandbox (`DemoSandboxSeeder`) and the extra report rows (`src/main/resources/demo/demo-coverage.sql`). Later boots skip both when user `demo` and setting `DEMO.coverage.v1` already exist.

`DemoDateKeeper` then keeps those sample dates current. It runs at startup, every day at 00:10, and when the truck board is opened. It moves `DEMO-` expiry, delivery, order, pick, job, and stock-take dates forward by the number of days since the last run (stored as setting `DEMO.dates.anchor`). Lots stay unexpired, and screens that default to today still show the sample orders. You do not need to re-seed or set the date filter back to the original seed day. Quarterly stock-ledger samples stay in January, April, July, and September, and roll into the new year.

On the first run it also fills 「送貨訂單」 so a search for today returns a row on each tab: 2/F and 4/F (pending, receiving, completed), Truck X, 加單, and 補貨. Those rows use `DEMO-` codes, so the same daily shift keeps their dates on today. The marker is setting `DEMO.do.showcase.v1`.

In the log, expect:

`Demo database login cannot read live schema fpsmsdb (MySQL error 1142).`


+ 332
- 0
src/main/java/com/ffii/fpsms/modules/common/demo/DemoDateKeeper.kt Ver ficheiro

@@ -0,0 +1,332 @@
package com.ffii.fpsms.modules.common.demo

import org.slf4j.LoggerFactory
import org.springframework.beans.factory.ObjectProvider
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.context.annotation.Profile
import org.springframework.core.annotation.Order
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
import org.springframework.transaction.support.TransactionTemplate
import java.time.LocalDate
import java.time.temporal.ChronoUnit
import javax.sql.DataSource

/**
* Keeps fictional sandbox dates lined up with today.
*
* Seed data is inserted once, with expiry and delivery dates relative to that day.
* Without a later shift, lots expire and list screens that default to today stop showing the samples.
* This only runs on the db-demo profile, and only against fpsms_demo, and only rows coded DEMO-.
*/
@Component
@Profile("db-demo")
@Order(20)
class DemoDateKeeper(
@Value("\${spring.datasource.jdbc-url}") private val jdbcUrl: String,
private val dataSource: DataSource,
private val jdbcTemplate: JdbcTemplate,
private val transactionTemplate: TransactionTemplate,
private val sandboxSeeder: ObjectProvider<DemoSandboxSeeder>,
) : ApplicationRunner {

private val log = LoggerFactory.getLogger(DemoDateKeeper::class.java)

override fun run(args: ApplicationArguments) {
refresh()
}

/** Catch a process that stays up past midnight. Startup and the truck board also call [refresh]. */
@Scheduled(cron = "\${fpsms.demo.date-refresh-cron:0 10 0 * * *}")
fun scheduledRefresh() {
try {
refresh()
} catch (ex: Exception) {
log.error("Demo date refresh failed", ex)
}
}

@Synchronized
fun refresh() {
assertDemoDatabase()
transactionTemplate.executeWithoutResult {
val stored = readAnchor()
val anchor = stored ?: inferAnchorFromPurchaseOrder()
val today = LocalDate.now()
if (anchor == null) {
saveAnchor(today)
log.warn("Demo date keeper found no DEMO-PO-001 order date. Marked {} without shifting.", today)
return@executeWithoutResult
}
when (val shift = demoDateShiftDays(anchor, today)) {
is DemoDateShift.TooLarge -> {
log.error(
"Demo date anchor {} is {} days before today, over the {} day limit. Dates were not moved.",
anchor,
shift.days,
DEMO_DATE_SHIFT_MAX_DAYS,
)
return@executeWithoutResult
}
is DemoDateShift.Forward -> {
val updated = shiftDemoDates(shift.days.toInt())
// Truck tickets are also pinned to today/tomorrow by the seeder. Re-pin after the
// shift so a board that was already on today is not pushed further ahead.
sandboxSeeder.getIfAvailable()?.realignDashboardTrucks()
log.info(
"Demo dates moved forward {} day(s), {} row update(s). Anchor {} -> {}.",
shift.days,
updated,
anchor,
today,
)
}
DemoDateShift.None -> Unit
}
val rolled = rollQuarterlyLedgerIntoCurrentYear()
if (rolled > 0) {
log.info("Rolled {} demo stock-ledger sample row(s) into the current year.", rolled)
}
if (stored != today) {
saveAnchor(today)
}
// After the shift, so a multi-day catch-up does not push brand-new "today" rows into the future.
sandboxSeeder.getIfAvailable()?.ensureDeliveryShowcase()
}
}

private fun assertDemoDatabase() {
val configuredName = databaseName(jdbcUrl)
val connectedName = dataSource.connection.use { it.catalog }
if (configuredName != DEMO_DATABASE || !DEMO_DATABASE.equals(connectedName, ignoreCase = true)) {
throw IllegalStateException(
"Demo date keeper refused to run. Configured database is '$configuredName', connected database is '$connectedName'.",
)
}
}

private fun readAnchor(): LocalDate? {
val value = jdbcTemplate.query(
"SELECT value FROM settings WHERE name = ? ORDER BY id DESC LIMIT 1",
{ rs, _ -> rs.getString("value") },
ANCHOR_SETTING,
).firstOrNull() ?: return null
return runCatching { LocalDate.parse(value) }.getOrNull()
}

/** DEMO-PO-001 is inserted with orderDate = the seed day, so an existing sandbox can catch up once. */
private fun inferAnchorFromPurchaseOrder(): LocalDate? {
return jdbcTemplate.query(
"SELECT DATE(orderDate) AS anchorDate FROM purchase_order WHERE code = 'DEMO-PO-001' LIMIT 1",
{ rs, _ -> rs.getDate("anchorDate")?.toLocalDate() },
).firstOrNull()
}

private fun saveAnchor(today: LocalDate) {
val value = today.toString()
val updated = jdbcTemplate.update(
"UPDATE settings SET value = ? WHERE name = ?",
value,
ANCHOR_SETTING,
)
if (updated == 0) {
jdbcTemplate.update(
"""
INSERT INTO settings (name, value, category, type)
SELECT ?, ?, 'DEMO', 'date'
WHERE NOT EXISTS (SELECT 1 FROM settings WHERE name = ?)
""".trimIndent(),
ANCHOR_SETTING,
value,
ANCHOR_SETTING,
)
}
}

private fun shiftDemoDates(days: Int): Int {
val statements = listOf(
"""
UPDATE purchase_order
SET orderDate = DATE_ADD(orderDate, INTERVAL $days DAY),
estimatedArrivalDate = DATE_ADD(estimatedArrivalDate, INTERVAL $days DAY),
completeDate = DATE_ADD(completeDate, INTERVAL $days DAY)
WHERE code LIKE 'DEMO-%'
""",
"""
UPDATE delivery_order
SET orderDate = DATE_ADD(orderDate, INTERVAL $days DAY),
estimatedArrivalDate = DATE_ADD(estimatedArrivalDate, INTERVAL $days DAY),
completeDate = DATE_ADD(completeDate, INTERVAL $days DAY)
WHERE code LIKE 'DEMO-%'
""",
"""
UPDATE delivery_order_pick_order
SET requiredDeliveryDate = DATE_ADD(requiredDeliveryDate, INTERVAL $days DAY),
ticketReleaseTime = DATE_ADD(ticketReleaseTime, INTERVAL $days DAY),
ticketCompleteDateTime = DATE_ADD(ticketCompleteDateTime, INTERVAL $days DAY)
WHERE ticketNo LIKE 'DEMO-%'
""",
"""
UPDATE do_pick_order_record
SET RequiredDeliveryDate = DATE_ADD(RequiredDeliveryDate, INTERVAL $days DAY),
ticket_release_time = DATE_ADD(ticket_release_time, INTERVAL $days DAY),
ticketCompleteDateTime = DATE_ADD(ticketCompleteDateTime, INTERVAL $days DAY)
WHERE ticket_no LIKE 'DEMO-%'
OR delivery_order_code LIKE 'DEMO-%'
OR pick_order_code LIKE 'DEMO-%'
""",
"""
UPDATE do_replenishment
SET deliveryDate = DATE_ADD(deliveryDate, INTERVAL $days DAY)
WHERE code LIKE 'DEMO-%'
""",
"""
UPDATE job_order
SET planStart = DATE_ADD(planStart, INTERVAL $days DAY),
actualStart = DATE_ADD(actualStart, INTERVAL $days DAY),
planEnd = DATE_ADD(planEnd, INTERVAL $days DAY),
actualEnd = DATE_ADD(actualEnd, INTERVAL $days DAY)
WHERE code LIKE 'DEMO-%'
""",
"""
UPDATE productprocess
SET startTime = DATE_ADD(startTime, INTERVAL $days DAY),
endTime = DATE_ADD(endTime, INTERVAL $days DAY),
date = DATE_ADD(date, INTERVAL $days DAY)
WHERE productprocesscode LIKE 'DEMO-%'
""",
"""
UPDATE pick_order
SET targetDate = DATE_ADD(targetDate, INTERVAL $days DAY),
completeDate = DATE_ADD(completeDate, INTERVAL $days DAY),
releasedDate = DATE_ADD(releasedDate, INTERVAL $days DAY)
WHERE code LIKE 'DEMO-%'
""",
"""
UPDATE stock_in
SET orderDate = DATE_ADD(orderDate, INTERVAL $days DAY),
estimatedCompleteDate = DATE_ADD(estimatedCompleteDate, INTERVAL $days DAY),
completeDate = DATE_ADD(completeDate, INTERVAL $days DAY)
WHERE code LIKE 'DEMO-%'
""",
"""
UPDATE stock_in_line sil
JOIN stock_in si ON si.id = sil.stockInId
SET sil.expiryDate = DATE_ADD(sil.expiryDate, INTERVAL $days DAY),
sil.receiptDate = DATE_ADD(sil.receiptDate, INTERVAL $days DAY),
sil.productionDate = DATE_ADD(sil.productionDate, INTERVAL $days DAY),
sil.dnDate = DATE_ADD(sil.dnDate, INTERVAL $days DAY)
WHERE si.code LIKE 'DEMO-%'
OR sil.lotNo LIKE 'DEMO-LOT-%'
OR sil.itemNo LIKE 'DEMO-%'
""",
"""
UPDATE inventory_lot
SET expiryDate = DATE_ADD(expiryDate, INTERVAL $days DAY),
stockInDate = DATE_ADD(stockInDate, INTERVAL $days DAY),
productionDate = DATE_ADD(productionDate, INTERVAL $days DAY)
WHERE lotNo LIKE 'DEMO-LOT-%'
""",
"""
UPDATE stock_out
SET completeDate = DATE_ADD(completeDate, INTERVAL $days DAY)
WHERE deliveryOrderCode LIKE 'DEMO-%'
OR consoPickOrderCode LIKE 'DEMO-%'
""",
"""
UPDATE stock_out_line sol
JOIN stock_out so ON so.id = sol.stockOutId
SET sol.pickTime = DATE_ADD(sol.pickTime, INTERVAL $days DAY),
sol.startTime = DATE_ADD(sol.startTime, INTERVAL $days DAY),
sol.endTime = DATE_ADD(sol.endTime, INTERVAL $days DAY)
WHERE so.deliveryOrderCode LIKE 'DEMO-%'
OR so.consoPickOrderCode LIKE 'DEMO-%'
""",
"""
UPDATE stock_take
SET planStart = DATE_ADD(planStart, INTERVAL $days DAY),
planEnd = DATE_ADD(planEnd, INTERVAL $days DAY),
actualStart = DATE_ADD(actualStart, INTERVAL $days DAY),
actualEnd = DATE_ADD(actualEnd, INTERVAL $days DAY)
WHERE code LIKE 'DEMO-%'
""",
"""
UPDATE stocktakerecord r
JOIN stock_take st ON st.id = r.stockTakeId
SET r.date = DATE_ADD(r.date, INTERVAL $days DAY),
r.approverTime = DATE_ADD(r.approverTime, INTERVAL $days DAY),
r.stockTakeStartTime = DATE_ADD(r.stockTakeStartTime, INTERVAL $days DAY),
r.stockTakeEndTime = DATE_ADD(r.stockTakeEndTime, INTERVAL $days DAY),
r.expiredDate = DATE_ADD(r.expiredDate, INTERVAL $days DAY)
WHERE st.code LIKE 'DEMO-%'
OR r.itemCode LIKE 'DEMO-%'
OR r.lotNo LIKE 'DEMO-LOT-%'
""",
"""
UPDATE stock_ledger
SET date = DATE_ADD(date, INTERVAL $days DAY),
modified = DATE_ADD(modified, INTERVAL $days DAY)
WHERE itemCode LIKE 'DEMO-%'
AND (type IS NULL OR type <> 'OUT')
""",
)
return statements.sumOf { jdbcTemplate.update(it.trimIndent()) }
}

/**
* January / April / July / September ledger samples are fixed calendar days, not offsets from the seed day.
* Leave them in those months, and move them into the new year when the calendar rolls over.
*/
private fun rollQuarterlyLedgerIntoCurrentYear(): Int {
return jdbcTemplate.update(
"""
UPDATE stock_ledger
SET date = DATE_ADD(date, INTERVAL (YEAR(CURDATE()) - YEAR(date)) YEAR),
modified = CASE
WHEN modified IS NULL THEN NULL
ELSE DATE_ADD(modified, INTERVAL (YEAR(CURDATE()) - YEAR(modified)) YEAR)
END
WHERE itemCode LIKE 'DEMO-%'
AND type = 'OUT'
AND date IS NOT NULL
AND YEAR(date) < YEAR(CURDATE())
""".trimIndent(),
)
}

private fun databaseName(jdbcUrl: String): String? {
val path = jdbcUrl.substringBefore('?').substringAfter("://")
val slash = path.indexOf('/')
if (slash < 0 || slash == path.lastIndex) return null
return path.substring(slash + 1).trim().ifEmpty { null }
}

companion object {
private const val DEMO_DATABASE = "fpsms_demo"
const val ANCHOR_SETTING = "DEMO.dates.anchor"
}
}

internal const val DEMO_DATE_SHIFT_MAX_DAYS = 2000L

internal fun demoDateShiftDays(
anchor: LocalDate,
today: LocalDate,
maxDays: Long = DEMO_DATE_SHIFT_MAX_DAYS,
): DemoDateShift {
val days = ChronoUnit.DAYS.between(anchor, today)
return when {
days > maxDays -> DemoDateShift.TooLarge(days)
days > 0 -> DemoDateShift.Forward(days)
else -> DemoDateShift.None
}
}

internal sealed class DemoDateShift {
data object None : DemoDateShift()
data class Forward(val days: Long) : DemoDateShift()
data class TooLarge(val days: Long) : DemoDateShift()
}

+ 188
- 3
src/main/java/com/ffii/fpsms/modules/common/demo/DemoSandboxSeeder.kt Ver ficheiro

@@ -115,6 +115,7 @@ class DemoSandboxSeeder(
private val deliveryOrderLineRepository: DeliveryOrderLineRepository,
private val pickOrderRepository: PickOrderRepository,
private val pickOrderLineRepository: PickOrderLineRepository,
private val demoDateKeeper: DemoDateKeeper,
) : ApplicationRunner {

private val log = LoggerFactory.getLogger(DemoSandboxSeeder::class.java)
@@ -200,8 +201,189 @@ class DemoSandboxSeeder(
* If those rows are still on an earlier day, shift them to the current date.
* Opening the dashboard does this, so a demo on a later day still has trucks to show.
*/
/** Put the sample truck board back on today, tomorrow, and the day after. */
fun realignDashboardTrucks() {
seedDashboardTrucks()
}

/**
* Gives 「送貨訂單」 a row for each status on 2/F, 4/F, Truck X, 加單, and 補貨.
* Dates are set to today once, after [DemoDateKeeper] has shifted older rows.
* Later runs leave the dates alone so the daily shift keeps them on today.
*/
fun ensureDeliveryShowcase() {
jdbcTemplate.update(
"""
UPDATE delivery_order d
JOIN shop sup ON sup.code = 'DEMO-SUP' AND sup.deleted = 0
SET d.supplierId = sup.id
WHERE d.code IN ('DEMO-DO-001', 'DEMO-DO-002', 'DEMO-DO-003')
AND d.supplierId IS NULL
""".trimIndent(),
)
val already = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM settings WHERE name = ?",
Int::class.java,
SHOWCASE_MARKER,
) ?: 0
if (already > 0) return

jdbcTemplate.update(
"""
UPDATE delivery_order
SET orderDate = CURDATE(),
estimatedArrivalDate = CURDATE(),
completeDate = CASE WHEN status = 'completed' THEN CURDATE() ELSE completeDate END
WHERE code IN ('DEMO-DO-001', 'DEMO-DO-002', 'DEMO-DO-003')
""".trimIndent(),
)
jdbcTemplate.update(
"""
UPDATE settings
SET value = 'DEMO-SUP2'
WHERE name = 'DO.floor.suppliers.4F'
AND value IN ('DEMO-SUP', '')
""".trimIndent(),
)
jdbcTemplate.update(
"""
INSERT INTO shop (code, name, type, contactName, contactNo, addr1, district, m18Id, m18LastModifyDate)
SELECT 'DEMO-SHP-X', '無車線門店', 'shop', '周先生', '51110009', '示範巷 1 號', '九龍', 910039, NOW()
WHERE NOT EXISTS (SELECT 1 FROM shop WHERE code = 'DEMO-SHP-X')
""".trimIndent(),
)
jdbcTemplate.update(
"""
INSERT INTO truck (TruckLanceCode, DepartureTime, shopId, ShopName, ShopCode, LoadingSequence, Store_id)
SELECT 'DEMO-4F', '15:00:00', s.id, s.name, s.code, 1, '4F'
FROM shop s
WHERE s.code = 'DEMO-SHP2' AND s.deleted = 0
AND NOT EXISTS (SELECT 1 FROM truck WHERE TruckLanceCode = 'DEMO-4F')
""".trimIndent(),
)
listOf(
ShowcaseDo("DEMO-DO-4F-P", "DEMO-SUP2", "DEMO-SHP2", "pending", 0),
ShowcaseDo("DEMO-DO-4F-R", "DEMO-SUP2", "DEMO-SHP2", "receiving", 0),
ShowcaseDo("DEMO-DO-4F-C", "DEMO-SUP2", "DEMO-SHP2", "completed", 0),
ShowcaseDo("DEMO-DO-X-P", "DEMO-SUP", "DEMO-SHP-X", "pending", 0),
ShowcaseDo("DEMO-DO-X-R", "DEMO-SUP", "DEMO-SHP-X", "receiving", 0),
ShowcaseDo("DEMO-DO-X-C", "DEMO-SUP", "DEMO-SHP-X", "completed", 0),
ShowcaseDo("DEMO-DO-EX-P", "DEMO-SUP", "DEMO-SHP", "pending", 1),
ShowcaseDo("DEMO-DO-EX-R", "DEMO-SUP", "DEMO-SHP", "receiving", 1),
ShowcaseDo("DEMO-DO-EX-C", "DEMO-SUP", "DEMO-SHP", "completed", 1),
).forEach { insertShowcaseDeliveryOrder(it) }
jdbcTemplate.update(
"""
UPDATE do_replenishment
SET deliveryDate = CURDATE()
WHERE code = 'DEMO-REPL-001'
""".trimIndent(),
)
listOf("pending" to "DEMO-REPL-P", "processing" to "DEMO-REPL-R").forEach { (status, code) ->
val exists = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM do_replenishment WHERE code = ?",
Int::class.java,
code,
) ?: 0
if (exists > 0) return@forEach
jdbcTemplate.update(
"""
INSERT INTO do_replenishment (
code, deliveryDate, sourceDoId, sourceDoCode, sourceDoLineId, sourceM18DataLogId, sourceM18Id,
itemId, itemNo, itemName, replenishQty, uomId, shopId, shopCode, shopName,
status, reason
)
SELECT ?, CURDATE(), d.id, d.code, l.id, d.m18DataLogId, 910601,
l.itemId, l.itemNo, i.name, 2, l.uomId, d.shopId, s.code, s.name,
?, '示範補貨'
FROM delivery_order d
JOIN delivery_order_line l ON l.deliveryOrderId = d.id
JOIN items i ON i.id = l.itemId
JOIN shop s ON s.id = d.shopId
WHERE d.code = 'DEMO-DO-003'
LIMIT 1
""".trimIndent(),
code,
status,
)
}
jdbcTemplate.update(
"""
INSERT INTO settings (name, value, category, type)
SELECT ?, 'loaded', 'DEMO', 'marker'
WHERE NOT EXISTS (SELECT 1 FROM settings WHERE name = ?)
""".trimIndent(),
SHOWCASE_MARKER,
SHOWCASE_MARKER,
)
log.info("Demo delivery showcase loaded. Each 送貨訂單 tab has a row dated today.")
}

private fun insertShowcaseDeliveryOrder(order: ShowcaseDo) {
val exists = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM delivery_order WHERE code = ?",
Int::class.java,
order.code,
) ?: 0
if (exists > 0) return
val lineStatus = when (order.status) {
"receiving" -> "picking"
else -> order.status
}
jdbcTemplate.update(
"""
INSERT INTO delivery_order (
code, shopId, supplierId, currencyId, orderDate, estimatedArrivalDate, completeDate,
status, m18DataLogId, m18BeId, isExtra
)
SELECT ?, shop.id, sup.id, src.currencyId, CURDATE(), CURDATE(),
CASE WHEN ? = 'completed' THEN CURDATE() ELSE NULL END,
?, src.m18DataLogId, src.m18BeId, ?
FROM shop shop
JOIN shop sup ON sup.code = ? AND sup.deleted = 0
JOIN (
SELECT currencyId, m18DataLogId, m18BeId
FROM delivery_order
WHERE code = 'DEMO-DO-001'
LIMIT 1
) src
WHERE shop.code = ? AND shop.deleted = 0
""".trimIndent(),
order.code,
order.status,
order.status,
order.extra,
order.supplierCode,
order.shopCode,
)
jdbcTemplate.update(
"""
INSERT INTO delivery_order_line (
deliveryOrderId, itemId, itemNo, uomId, uomIdM18, qty, qtyM18, status, m18DataLogId
)
SELECT d.id, src.itemId, src.itemNo, src.uomId, src.uomIdM18, 4, 4, ?, src.m18DataLogId
FROM delivery_order d
JOIN delivery_order_line src
ON src.deliveryOrderId = (SELECT id FROM delivery_order WHERE code = 'DEMO-DO-001' LIMIT 1)
WHERE d.code = ?
AND NOT EXISTS (SELECT 1 FROM delivery_order_line l WHERE l.deliveryOrderId = d.id)
""".trimIndent(),
lineStatus,
order.code,
)
}

private data class ShowcaseDo(
val code: String,
val supplierCode: String,
val shopCode: String,
val status: String,
val extra: Int,
)

@Synchronized
fun alignDashboardTrucksToToday() {
demoDateKeeper.refresh()
val anchored = jdbcTemplate.query(
"""
SELECT requiredDeliveryDate
@@ -584,10 +766,10 @@ class DemoSandboxSeeder(
BigDecimal("30"), "DEMO-LOT-LF1", today.plusDays(5), now.minusDays(2),
)

val doPending = deliveryOrder("DEMO-DO-001", customer, currency, dataLog, DeliveryOrderStatus.PENDING, now, null)
val doPending = deliveryOrder("DEMO-DO-001", supplier, customer, currency, dataLog, DeliveryOrderStatus.PENDING, now, null)
deliveryLine(doPending, bun, pcs, dataLog, BigDecimal("12"), DeliveryOrderLineStatus.PENDING)

val doPicking = deliveryOrder("DEMO-DO-002", customer, currency, dataLog, DeliveryOrderStatus.RECEIVING, now, null)
val doPicking = deliveryOrder("DEMO-DO-002", supplier, customer, currency, dataLog, DeliveryOrderStatus.RECEIVING, now, null)
deliveryLine(doPicking, loaf, pcs, dataLog, BigDecimal("4"), DeliveryOrderLineStatus.PICKING)
val doPick = pickOrderRepository.save(PickOrder().apply {
code = "DEMO-PK-DO2"
@@ -600,7 +782,7 @@ class DemoSandboxSeeder(
})
pickLine(doPick, loaf, pcs, BigDecimal("4"), PickOrderLineStatus.PENDING)

val doDone = deliveryOrder("DEMO-DO-003", customer, currency, dataLog, DeliveryOrderStatus.COMPLETED, now.minusDays(2), now.minusDays(1))
val doDone = deliveryOrder("DEMO-DO-003", supplier, customer, currency, dataLog, DeliveryOrderStatus.COMPLETED, now.minusDays(2), now.minusDays(1))
deliveryLine(doDone, loaf, pcs, dataLog, BigDecimal("6"), DeliveryOrderLineStatus.COMPLETED)
val donePick = pickOrderRepository.save(PickOrder().apply {
code = "DEMO-PK-DO3"
@@ -871,6 +1053,7 @@ class DemoSandboxSeeder(

private fun deliveryOrder(
code: String,
supplier: Shop,
shop: Shop,
currency: Currency,
dataLog: M18DataLog,
@@ -879,6 +1062,7 @@ class DemoSandboxSeeder(
completeDate: LocalDateTime?,
) = deliveryOrderRepository.save(DeliveryOrder().apply {
this.code = code
this.supplier = supplier
this.shop = shop
this.currency = currency
this.orderDate = orderDate
@@ -932,6 +1116,7 @@ class DemoSandboxSeeder(
const val DEMO_DATABASE = "fpsms_demo"
const val DEMO_USERNAME = "demo"
const val DEMO_PASSWORD = "Demo2026!"
const val SHOWCASE_MARKER = "DEMO.do.showcase.v1"
val DEMO_AUTHORITY_CODES = listOf("ADMIN", "STOCK", "PROD", "PACK", "REPORT_MGMT")
}
}

+ 6
- 5
src/main/java/com/ffii/fpsms/modules/dashboard/service/GoodsReceiptStatusService.kt Ver ficheiro

@@ -80,12 +80,13 @@ class GoodsReceiptStatusService(
agg.expectedNoOfDelivery = 1
}

// Receiving / IQC / Put-away (by stock-in receipt date)
// Receiving / IQC / Put-away (by stock-in receipt date).
// Job-order and other receipts have no purchase order; they are not supplier goods receipts.
stockInLines.forEach { sil ->
val po = sil.purchaseOrder
val poId = po?.id
val poCode = po?.code
val supplier = po?.supplier
val po = sil.purchaseOrder ?: return@forEach
val poId = po.id
val poCode = po.code
val supplier = po.supplier
val supplierId = supplier?.id
val supplierCode = supplier?.code
val supplierName = supplier?.name ?: "N/A"


+ 3
- 0
src/main/resources/application-db-demo.yml Ver ficheiro

@@ -8,6 +8,9 @@ spring:
# Demo sandbox only. Do not point this profile at fpsmsdb.
fpsms:
demo-mode: true
# Slide DEMO- expiry and delivery dates forward so sample rows stay on today's screens.
demo:
date-refresh-cron: "0 10 0 * * *"
monitoring:
enabled: false



+ 33
- 0
src/test/kotlin/com/ffii/fpsms/modules/common/demo/DemoDateShiftTest.kt Ver ficheiro

@@ -0,0 +1,33 @@
package com.ffii.fpsms.modules.common.demo

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.time.LocalDate

class DemoDateShiftTest {
private val today = LocalDate.of(2026, 10, 6)

@Test
fun same_day_does_not_shift() {
assertEquals(DemoDateShift.None, demoDateShiftDays(today, today))
}

@Test
fun future_anchor_does_not_shift() {
assertEquals(DemoDateShift.None, demoDateShiftDays(today.plusDays(2), today))
}

@Test
fun moves_forward_by_whole_days_since_the_anchor() {
val shift = demoDateShiftDays(today.minusDays(12), today)
assertEquals(DemoDateShift.Forward(12), shift)
}

@Test
fun refuses_a_gap_large_enough_to_be_a_bad_anchor() {
val shift = demoDateShiftDays(today.minusDays(DEMO_DATE_SHIFT_MAX_DAYS + 1), today)
assertTrue(shift is DemoDateShift.TooLarge)
assertEquals(DEMO_DATE_SHIFT_MAX_DAYS + 1, (shift as DemoDateShift.TooLarge).days)
}
}

Carregando…
Cancelar
Guardar