后端迁移库存仓储模块
This commit is contained in:
+72
-52
@@ -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(
|
||||||
`
|
`
|
||||||
@@ -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、库存换码、自动发货补测试
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user