后端迁移库存仓储模块

This commit is contained in:
yml
2026-05-21 13:58:31 +08:00
parent 173da43fdf
commit b803bbd007
2 changed files with 75 additions and 53 deletions
@@ -1,13 +1,21 @@
// @ts-check
import { query, withTransaction } from '../db/client.js' import { query, withTransaction } from '../db/client.js'
import type {
InventoryCreateItemInput,
InventoryListQueryInput,
InventorySkuSuggestionQueryInput,
} from '../types/repository-inputs.js'
import type {
InventoryItemRow,
InventoryListQueryResult,
InventorySkuSuggestionRow,
} from '../types/repository-rows.js'
/** @typedef {import('../types/repository-inputs.js').InventoryCreateItemInput} InventoryCreateItemInput */ type InventoryGroupCodesInput = string | string[] | null | undefined
/** @typedef {import('../types/repository-inputs.js').InventoryListQueryInput} InventoryListQueryInput */
/** @typedef {import('../types/repository-inputs.js').InventorySkuSuggestionQueryInput} InventorySkuSuggestionQueryInput */ type InventoryGroupListInput = {
/** @typedef {import('../types/repository-rows.js').InventoryItemRow} InventoryItemRow */ keyword?: string
/** @typedef {import('../types/repository-rows.js').InventoryListQueryResult} InventoryListQueryResult */ limit?: number | string
/** @typedef {import('../types/repository-rows.js').InventorySkuSuggestionRow} InventorySkuSuggestionRow */ }
const INVENTORY_ITEM_SELECT = ` const INVENTORY_ITEM_SELECT = `
SELECT SELECT
@@ -36,18 +44,17 @@ const INVENTORY_ITEM_SELECT = `
LEFT JOIN fulfillment_tasks ft ON ft.id = tib.task_id LEFT JOIN fulfillment_tasks ft ON ft.id = tib.task_id
` `
/** @returns {Promise<InventoryItemRow | null>} */
export async function findFirstAvailableInventoryItemBySkuCode( export async function findFirstAvailableInventoryItemBySkuCode(
skuCode, skuCode: string,
credentialType = 'tencent_code', credentialType = 'tencent_code',
inventoryGroupCodes = null, inventoryGroupCodes: InventoryGroupCodesInput = null,
) { ): Promise<InventoryItemRow | null> {
const normalizedInventoryGroupCodes = normalizeInventoryGroupCodes(inventoryGroupCodes) const normalizedInventoryGroupCodes = normalizeInventoryGroupCodes(inventoryGroupCodes)
if (normalizedInventoryGroupCodes && normalizedInventoryGroupCodes.length === 0) { if (normalizedInventoryGroupCodes && normalizedInventoryGroupCodes.length === 0) {
return null return null
} }
const params = [skuCode, credentialType] const params: unknown[] = [skuCode, credentialType]
const filters = [ const filters = [
'ii.sku_code = $1', 'ii.sku_code = $1',
'ii.credential_type = $2', 'ii.credential_type = $2',
@@ -59,7 +66,7 @@ export async function findFirstAvailableInventoryItemBySkuCode(
filters.push(`ii.inventory_group_code = ANY($${params.length}::text[])`) filters.push(`ii.inventory_group_code = ANY($${params.length}::text[])`)
} }
const result = await query( const result = await query<InventoryItemRow>(
`${INVENTORY_ITEM_SELECT} `${INVENTORY_ITEM_SELECT}
WHERE ${filters.join(' AND ')} WHERE ${filters.join(' AND ')}
ORDER BY ii.id ASC ORDER BY ii.id ASC
@@ -67,11 +74,15 @@ export async function findFirstAvailableInventoryItemBySkuCode(
params, params,
) )
return /** @type {InventoryItemRow | null} */ (result.rows[0] || null) return result.rows[0] || null
} }
/** @returns {Promise<InventoryItemRow | null>} */ export async function assignReservedInventoryItem(
export async function assignReservedInventoryItem(inventoryItemId, taskId, updatedAt, roleKey = 'primary_code') { inventoryItemId: number | string,
taskId: number | string,
updatedAt: string,
roleKey = 'primary_code',
): Promise<InventoryItemRow | null> {
return withTransaction(async (client) => { return withTransaction(async (client) => {
const inventoryResult = await client.query( const inventoryResult = await client.query(
` `
@@ -109,20 +120,21 @@ export async function assignReservedInventoryItem(inventoryItemId, taskId, updat
}) })
} }
/** @returns {Promise<InventoryItemRow | null>} */ export async function getInventoryItemById(inventoryItemId: number | string): Promise<InventoryItemRow | null> {
export async function getInventoryItemById(inventoryItemId) { const result = await query<InventoryItemRow>(
const result = await query(
`${INVENTORY_ITEM_SELECT} `${INVENTORY_ITEM_SELECT}
WHERE ii.id = $1 WHERE ii.id = $1
LIMIT 1`, LIMIT 1`,
[Number(inventoryItemId)], [Number(inventoryItemId)],
) )
return /** @type {InventoryItemRow | null} */ (result.rows[0] || null) return result.rows[0] || null
} }
/** @returns {Promise<InventoryItemRow | null>} */ export async function markInventoryItemDelivered(
export async function markInventoryItemDelivered(inventoryItemId, deliveredAt) { inventoryItemId: number | string,
deliveredAt: string,
): Promise<InventoryItemRow | null> {
return withTransaction(async (client) => { return withTransaction(async (client) => {
await client.query( await client.query(
` `
@@ -146,8 +158,11 @@ export async function markInventoryItemDelivered(inventoryItemId, deliveredAt) {
}) })
} }
/** @returns {Promise<InventoryItemRow | null>} */ export async function markInventoryItemConsumed(
export async function markInventoryItemConsumed(inventoryItemId, reason, consumedAt) { inventoryItemId: number | string,
reason: string,
consumedAt: string,
): Promise<InventoryItemRow | null> {
return withTransaction(async (client) => { return withTransaction(async (client) => {
await client.query( await client.query(
` `
@@ -175,8 +190,6 @@ export async function markInventoryItemConsumed(inventoryItemId, reason, consume
}) })
} }
/** @returns {Promise<InventoryListQueryResult>} */
/** @param {InventoryListQueryInput} [queryInput] */
export async function listInventoryItems({ export async function listInventoryItems({
page = 1, page = 1,
pageSize = 20, pageSize = 20,
@@ -186,7 +199,7 @@ export async function listInventoryItems({
batchNo = '', batchNo = '',
inventoryGroupCode = '', inventoryGroupCode = '',
allowedInventoryGroupCodes = null, allowedInventoryGroupCodes = null,
} = /** @type {InventoryListQueryInput} */ ({})) { }: InventoryListQueryInput = {}): Promise<InventoryListQueryResult> {
const normalizedAllowedInventoryGroupCodes = normalizeInventoryGroupCodes(allowedInventoryGroupCodes) const normalizedAllowedInventoryGroupCodes = normalizeInventoryGroupCodes(allowedInventoryGroupCodes)
if (normalizedAllowedInventoryGroupCodes && normalizedAllowedInventoryGroupCodes.length === 0) { if (normalizedAllowedInventoryGroupCodes && normalizedAllowedInventoryGroupCodes.length === 0) {
return { return {
@@ -196,8 +209,8 @@ export async function listInventoryItems({
} }
const offset = (page - 1) * pageSize const offset = (page - 1) * pageSize
const filters = [] const filters: string[] = []
const params = [] const params: unknown[] = []
if (skuCode) { if (skuCode) {
params.push(skuCode) params.push(skuCode)
@@ -230,14 +243,14 @@ export async function listInventoryItems({
} }
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '' const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
const totalResult = await query( const totalResult = await query<{ [column: string]: unknown, total: number }>(
`SELECT COUNT(*)::int AS total FROM inventory_items ii ${whereClause}`, `SELECT COUNT(*)::int AS total FROM inventory_items ii ${whereClause}`,
params, params,
) )
params.push(pageSize) params.push(pageSize)
params.push(offset) params.push(offset)
const itemsResult = await query( const itemsResult = await query<InventoryItemRow>(
`${INVENTORY_ITEM_SELECT} `${INVENTORY_ITEM_SELECT}
${whereClause} ${whereClause}
ORDER BY ii.id DESC ORDER BY ii.id DESC
@@ -246,27 +259,25 @@ export async function listInventoryItems({
) )
return { return {
items: /** @type {InventoryItemRow[]} */ (itemsResult.rows), items: itemsResult.rows,
total: Number(totalResult.rows[0]?.total || 0), total: Number(totalResult.rows[0]?.total || 0),
} }
} }
/** @returns {Promise<InventorySkuSuggestionRow[]>} */
/** @param {InventorySkuSuggestionQueryInput} [queryInput] */
export async function listInventorySkuSuggestions({ export async function listInventorySkuSuggestions({
credentialType = '', credentialType = '',
keyword = '', keyword = '',
limit = 50, limit = 50,
inventoryGroupCode = '', inventoryGroupCode = '',
allowedInventoryGroupCodes = null, allowedInventoryGroupCodes = null,
} = /** @type {InventorySkuSuggestionQueryInput} */ ({})) { }: InventorySkuSuggestionQueryInput = {}): Promise<InventorySkuSuggestionRow[]> {
const normalizedAllowedInventoryGroupCodes = normalizeInventoryGroupCodes(allowedInventoryGroupCodes) const normalizedAllowedInventoryGroupCodes = normalizeInventoryGroupCodes(allowedInventoryGroupCodes)
if (normalizedAllowedInventoryGroupCodes && normalizedAllowedInventoryGroupCodes.length === 0) { if (normalizedAllowedInventoryGroupCodes && normalizedAllowedInventoryGroupCodes.length === 0) {
return [] return []
} }
const filters = [] const filters: string[] = []
const params = [] const params: unknown[] = []
if (credentialType) { if (credentialType) {
params.push(credentialType) params.push(credentialType)
@@ -294,7 +305,7 @@ export async function listInventorySkuSuggestions({
params.push(normalizedLimit) params.push(normalizedLimit)
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '' const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
const result = await query( const result = await query<InventorySkuSuggestionRow>(
` `
SELECT SELECT
sku_code, sku_code,
@@ -317,18 +328,17 @@ export async function listInventorySkuSuggestions({
params, params,
) )
return /** @type {InventorySkuSuggestionRow[]} */ (result.rows) return result.rows
} }
/** @param {InventoryCreateItemInput[]} rows */ export async function createInventoryItems(rows: InventoryCreateItemInput[]): Promise<number> {
export async function createInventoryItems(rows) {
let created = 0 let created = 0
for (const row of rows) { for (const row of rows) {
const payloadJson = JSON.stringify(row.payload || { code: row.displayValue }) const payloadJson = JSON.stringify(row.payload || { code: row.displayValue })
const displayValue = String(row.displayValue || '').trim() const displayValue = String(row.displayValue || '').trim()
const inventoryGroupCode = String(row.inventoryGroupCode || '').trim() const inventoryGroupCode = String(row.inventoryGroupCode || '').trim()
const result = await query( const result = await query<{ [column: string]: unknown, id: number }>(
` `
INSERT INTO inventory_items ( INSERT INTO inventory_items (
batch_no, batch_no,
@@ -370,8 +380,8 @@ export async function createInventoryItems(rows) {
export async function listInventoryGroupCodes({ export async function listInventoryGroupCodes({
keyword = '', keyword = '',
limit = 100, limit = 100,
} = {}) { }: InventoryGroupListInput = {}): Promise<string[]> {
const params = [] const params: unknown[] = []
const filters = [`inventory_group_code <> ''`] const filters = [`inventory_group_code <> ''`]
if (keyword) { if (keyword) {
@@ -384,7 +394,7 @@ export async function listInventoryGroupCodes({
: 100 : 100
params.push(normalizedLimit) params.push(normalizedLimit)
const result = await query( const result = await query<{ [column: string]: unknown, inventory_group_code: string }>(
` `
SELECT inventory_group_code SELECT inventory_group_code
FROM inventory_items FROM inventory_items
@@ -399,7 +409,7 @@ export async function listInventoryGroupCodes({
return result.rows.map((row) => String(row.inventory_group_code || '').trim()).filter(Boolean) return result.rows.map((row) => String(row.inventory_group_code || '').trim()).filter(Boolean)
} }
function normalizeInventoryGroupCodes(inventoryGroupCodes) { function normalizeInventoryGroupCodes(inventoryGroupCodes: InventoryGroupCodesInput): string[] | null {
if (inventoryGroupCodes == null) { if (inventoryGroupCodes == null) {
return null return null
} }
@@ -409,8 +419,10 @@ function normalizeInventoryGroupCodes(inventoryGroupCodes) {
.filter(Boolean))) .filter(Boolean)))
} }
/** @returns {Promise<InventoryItemRow | null>} */ export async function releaseReservedInventoryItem(
export async function releaseReservedInventoryItem(inventoryItemId, updatedAt) { inventoryItemId: number | string,
updatedAt: string,
): Promise<InventoryItemRow | null> {
return withTransaction(async (client) => { return withTransaction(async (client) => {
await client.query( await client.query(
` `
@@ -434,8 +446,12 @@ export async function releaseReservedInventoryItem(inventoryItemId, updatedAt) {
}) })
} }
export async function invalidateInventoryItem(inventoryItemId, invalidReason, updatedAt) { export async function invalidateInventoryItem(
const result = await query( inventoryItemId: number | string,
invalidReason: string,
updatedAt: string,
): Promise<InventoryItemRow | null> {
const result = await query<{ [column: string]: unknown, id: number }>(
` `
UPDATE inventory_items UPDATE inventory_items
SET status = 'invalid', invalid_reason = $1, updated_at = $2 SET status = 'invalid', invalid_reason = $1, updated_at = $2
@@ -452,7 +468,11 @@ export async function invalidateInventoryItem(inventoryItemId, invalidReason, up
return getInventoryItemById(inventoryItemId) return getInventoryItemById(inventoryItemId)
} }
export async function invalidateReservedInventoryItem(inventoryItemId, invalidReason, updatedAt) { export async function invalidateReservedInventoryItem(
inventoryItemId: number | string,
invalidReason: string,
updatedAt: string,
): Promise<InventoryItemRow | null> {
return withTransaction(async (client) => { return withTransaction(async (client) => {
await client.query( await client.query(
` `
+3 -1
View File
@@ -231,12 +231,14 @@
15. repository 第四批已迁移到 `.ts` 15. repository 第四批已迁移到 `.ts`
- `src/repositories/order-repo.ts` - `src/repositories/order-repo.ts`
- `src/repositories/task-inventory-binding-repo.ts` - `src/repositories/task-inventory-binding-repo.ts`
16. 核心库存 repository 已迁移到 `.ts`
- `src/repositories/inventory-repo.ts`
## 下一步建议 ## 下一步建议
第一批继续推进时,建议按这个顺序: 第一批继续推进时,建议按这个顺序:
1. 继续迁移剩余核心 repository`inventory-repo``task-repo` 1. 继续迁移剩余核心 repository`task-repo`
2. 拆分并迁移 `runtime.js`,把 env 解析、默认配置加载、配置合并分开 2. 拆分并迁移 `runtime.js`,把 env 解析、默认配置加载、配置合并分开
3. 为 webhook、库存换码、自动发货补测试 3. 为 webhook、库存换码、自动发货补测试