增加分组库存与客服
This commit is contained in:
@@ -1,8 +1,20 @@
|
||||
import { query } from '../db/client.js'
|
||||
import { query, withTransaction } from '../db/client.js'
|
||||
|
||||
const ADMIN_USER_SELECT = `
|
||||
SELECT
|
||||
au.*,
|
||||
COALESCE(bindings.inventory_group_codes, ARRAY[]::text[]) AS inventory_group_codes
|
||||
FROM admin_users au
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT ARRAY_AGG(auigb.inventory_group_code ORDER BY auigb.inventory_group_code) AS inventory_group_codes
|
||||
FROM admin_user_inventory_group_bindings auigb
|
||||
WHERE auigb.admin_user_id = au.id
|
||||
) bindings ON TRUE
|
||||
`
|
||||
|
||||
export async function getAdminUserById(userId) {
|
||||
const result = await query(
|
||||
'SELECT * FROM admin_users WHERE id = $1 LIMIT 1',
|
||||
`${ADMIN_USER_SELECT} WHERE au.id = $1 LIMIT 1`,
|
||||
[Number(userId)],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
@@ -10,7 +22,7 @@ export async function getAdminUserById(userId) {
|
||||
|
||||
export async function getAdminUserByUsername(username) {
|
||||
const result = await query(
|
||||
'SELECT * FROM admin_users WHERE username = $1 LIMIT 1',
|
||||
`${ADMIN_USER_SELECT} WHERE au.username = $1 LIMIT 1`,
|
||||
[String(username || '').trim().toLowerCase()],
|
||||
)
|
||||
return result.rows[0] || null
|
||||
@@ -27,7 +39,7 @@ export async function createAdminUser(input) {
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING *
|
||||
RETURNING id
|
||||
`,
|
||||
[
|
||||
input.username,
|
||||
@@ -39,7 +51,7 @@ export async function createAdminUser(input) {
|
||||
],
|
||||
)
|
||||
|
||||
return result.rows[0] || null
|
||||
return getAdminUserById(result.rows[0]?.id || 0)
|
||||
}
|
||||
|
||||
export async function updateAdminUser(userId, patch) {
|
||||
@@ -59,7 +71,7 @@ export async function updateAdminUser(userId, patch) {
|
||||
status = $4,
|
||||
updated_at = $5
|
||||
WHERE id = $6
|
||||
RETURNING *
|
||||
RETURNING id
|
||||
`,
|
||||
[
|
||||
next.username,
|
||||
@@ -71,7 +83,7 @@ export async function updateAdminUser(userId, patch) {
|
||||
],
|
||||
)
|
||||
|
||||
return result.rows[0] || null
|
||||
return getAdminUserById(result.rows[0]?.id || 0)
|
||||
}
|
||||
|
||||
export async function listAdminUsers({ page = 1, pageSize = 20, username = '', role = '', status = '' } = {}) {
|
||||
@@ -101,10 +113,9 @@ export async function listAdminUsers({ page = 1, pageSize = 20, username = '', r
|
||||
params.push(offset)
|
||||
const itemsResult = await query(
|
||||
`
|
||||
SELECT *
|
||||
FROM admin_users
|
||||
${ADMIN_USER_SELECT}
|
||||
${whereClause}
|
||||
ORDER BY id DESC
|
||||
ORDER BY au.id DESC
|
||||
LIMIT $${params.length - 1} OFFSET $${params.length}
|
||||
`,
|
||||
params,
|
||||
@@ -122,3 +133,35 @@ export async function countActiveAdminUsers() {
|
||||
)
|
||||
return Number(result.rows[0]?.total || 0)
|
||||
}
|
||||
|
||||
export async function replaceAdminUserInventoryGroupBindings(userId, inventoryGroupCodes = [], timestamp) {
|
||||
const normalizedUserId = Number(userId)
|
||||
const normalizedCodes = Array.from(new Set((Array.isArray(inventoryGroupCodes) ? inventoryGroupCodes : [])
|
||||
.map((value) => String(value || '').trim())
|
||||
.filter(Boolean)))
|
||||
|
||||
await withTransaction(async (client) => {
|
||||
await client.query(
|
||||
'DELETE FROM admin_user_inventory_group_bindings WHERE admin_user_id = $1',
|
||||
[normalizedUserId],
|
||||
)
|
||||
|
||||
for (const inventoryGroupCode of normalizedCodes) {
|
||||
await client.query(
|
||||
`
|
||||
INSERT INTO admin_user_inventory_group_bindings (
|
||||
admin_user_id,
|
||||
inventory_group_code,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (admin_user_id, inventory_group_code) DO UPDATE
|
||||
SET updated_at = EXCLUDED.updated_at
|
||||
`,
|
||||
[normalizedUserId, inventoryGroupCode, timestamp, timestamp],
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return getAdminUserById(normalizedUserId)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ const INVENTORY_ITEM_SELECT = `
|
||||
ii.sku_code,
|
||||
ii.batch_no,
|
||||
ii.credential_type,
|
||||
ii.inventory_group_code,
|
||||
ii.display_value,
|
||||
ii.status,
|
||||
ii.invalid_reason,
|
||||
@@ -36,13 +37,34 @@ const INVENTORY_ITEM_SELECT = `
|
||||
`
|
||||
|
||||
/** @returns {Promise<InventoryItemRow | null>} */
|
||||
export async function findFirstAvailableInventoryItemBySkuCode(skuCode, credentialType = 'tencent_code') {
|
||||
export async function findFirstAvailableInventoryItemBySkuCode(
|
||||
skuCode,
|
||||
credentialType = 'tencent_code',
|
||||
inventoryGroupCodes = null,
|
||||
) {
|
||||
const normalizedInventoryGroupCodes = normalizeInventoryGroupCodes(inventoryGroupCodes)
|
||||
if (normalizedInventoryGroupCodes && normalizedInventoryGroupCodes.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const params = [skuCode, credentialType]
|
||||
const filters = [
|
||||
'ii.sku_code = $1',
|
||||
'ii.credential_type = $2',
|
||||
`ii.status = 'available'`,
|
||||
]
|
||||
|
||||
if (normalizedInventoryGroupCodes) {
|
||||
params.push(normalizedInventoryGroupCodes)
|
||||
filters.push(`ii.inventory_group_code = ANY($${params.length}::text[])`)
|
||||
}
|
||||
|
||||
const result = await query(
|
||||
`${INVENTORY_ITEM_SELECT}
|
||||
WHERE ii.sku_code = $1 AND ii.credential_type = $2 AND ii.status = 'available'
|
||||
WHERE ${filters.join(' AND ')}
|
||||
ORDER BY ii.id ASC
|
||||
LIMIT 1`,
|
||||
[skuCode, credentialType],
|
||||
params,
|
||||
)
|
||||
|
||||
return /** @type {InventoryItemRow | null} */ (result.rows[0] || null)
|
||||
@@ -162,7 +184,17 @@ export async function listInventoryItems({
|
||||
credentialType = '',
|
||||
status = '',
|
||||
batchNo = '',
|
||||
inventoryGroupCode = '',
|
||||
allowedInventoryGroupCodes = null,
|
||||
} = /** @type {InventoryListQueryInput} */ ({})) {
|
||||
const normalizedAllowedInventoryGroupCodes = normalizeInventoryGroupCodes(allowedInventoryGroupCodes)
|
||||
if (normalizedAllowedInventoryGroupCodes && normalizedAllowedInventoryGroupCodes.length === 0) {
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const offset = (page - 1) * pageSize
|
||||
const filters = []
|
||||
const params = []
|
||||
@@ -187,6 +219,16 @@ export async function listInventoryItems({
|
||||
filters.push(`ii.batch_no ILIKE $${params.length}`)
|
||||
}
|
||||
|
||||
if (inventoryGroupCode) {
|
||||
params.push(inventoryGroupCode)
|
||||
filters.push(`ii.inventory_group_code = $${params.length}`)
|
||||
}
|
||||
|
||||
if (normalizedAllowedInventoryGroupCodes) {
|
||||
params.push(normalizedAllowedInventoryGroupCodes)
|
||||
filters.push(`ii.inventory_group_code = ANY($${params.length}::text[])`)
|
||||
}
|
||||
|
||||
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
||||
const totalResult = await query(
|
||||
`SELECT COUNT(*)::int AS total FROM inventory_items ii ${whereClause}`,
|
||||
@@ -215,7 +257,14 @@ export async function listInventorySkuSuggestions({
|
||||
credentialType = '',
|
||||
keyword = '',
|
||||
limit = 50,
|
||||
inventoryGroupCode = '',
|
||||
allowedInventoryGroupCodes = null,
|
||||
} = /** @type {InventorySkuSuggestionQueryInput} */ ({})) {
|
||||
const normalizedAllowedInventoryGroupCodes = normalizeInventoryGroupCodes(allowedInventoryGroupCodes)
|
||||
if (normalizedAllowedInventoryGroupCodes && normalizedAllowedInventoryGroupCodes.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const filters = []
|
||||
const params = []
|
||||
|
||||
@@ -229,6 +278,16 @@ export async function listInventorySkuSuggestions({
|
||||
filters.push(`sku_code ILIKE $${params.length}`)
|
||||
}
|
||||
|
||||
if (inventoryGroupCode) {
|
||||
params.push(inventoryGroupCode)
|
||||
filters.push(`inventory_group_code = $${params.length}`)
|
||||
}
|
||||
|
||||
if (normalizedAllowedInventoryGroupCodes) {
|
||||
params.push(normalizedAllowedInventoryGroupCodes)
|
||||
filters.push(`inventory_group_code = ANY($${params.length}::text[])`)
|
||||
}
|
||||
|
||||
const normalizedLimit = Number.isFinite(Number(limit))
|
||||
? Math.max(1, Math.min(Number(limit), 100))
|
||||
: 50
|
||||
@@ -240,17 +299,19 @@ export async function listInventorySkuSuggestions({
|
||||
SELECT
|
||||
sku_code,
|
||||
credential_type,
|
||||
inventory_group_code,
|
||||
COUNT(*)::int AS total_count,
|
||||
COUNT(*) FILTER (WHERE status = 'available')::int AS available_count,
|
||||
MAX(updated_at) AS latest_updated_at
|
||||
FROM inventory_items
|
||||
${whereClause}
|
||||
GROUP BY sku_code, credential_type
|
||||
GROUP BY sku_code, credential_type, inventory_group_code
|
||||
ORDER BY
|
||||
COUNT(*) FILTER (WHERE status = 'available') DESC,
|
||||
COUNT(*) DESC,
|
||||
MAX(updated_at) DESC,
|
||||
sku_code ASC
|
||||
sku_code ASC,
|
||||
inventory_group_code ASC
|
||||
LIMIT $${params.length}
|
||||
`,
|
||||
params,
|
||||
@@ -266,12 +327,14 @@ export async function createInventoryItems(rows) {
|
||||
for (const row of rows) {
|
||||
const payloadJson = JSON.stringify(row.payload || { code: row.displayValue })
|
||||
const displayValue = String(row.displayValue || '').trim()
|
||||
const inventoryGroupCode = String(row.inventoryGroupCode || '').trim()
|
||||
const result = await query(
|
||||
`
|
||||
INSERT INTO inventory_items (
|
||||
batch_no,
|
||||
sku_code,
|
||||
credential_type,
|
||||
inventory_group_code,
|
||||
display_value,
|
||||
payload_json,
|
||||
source_type,
|
||||
@@ -280,7 +343,7 @@ export async function createInventoryItems(rows) {
|
||||
metadata_json,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES ($1, $2, $3, $4, $5::jsonb, 'static_import', 'available', '', '{}'::jsonb, $6, $7)
|
||||
) VALUES ($1, $2, $3, $4, $5, $6::jsonb, 'static_import', 'available', '', '{}'::jsonb, $7, $8)
|
||||
ON CONFLICT DO NOTHING
|
||||
RETURNING id
|
||||
`,
|
||||
@@ -288,6 +351,7 @@ export async function createInventoryItems(rows) {
|
||||
row.batchNo || '',
|
||||
row.skuCode,
|
||||
row.credentialType || 'tencent_code',
|
||||
inventoryGroupCode,
|
||||
displayValue,
|
||||
payloadJson,
|
||||
row.createdAt,
|
||||
@@ -303,6 +367,48 @@ export async function createInventoryItems(rows) {
|
||||
return created
|
||||
}
|
||||
|
||||
export async function listInventoryGroupCodes({
|
||||
keyword = '',
|
||||
limit = 100,
|
||||
} = {}) {
|
||||
const params = []
|
||||
const filters = [`inventory_group_code <> ''`]
|
||||
|
||||
if (keyword) {
|
||||
params.push(`%${String(keyword || '').trim()}%`)
|
||||
filters.push(`inventory_group_code ILIKE $${params.length}`)
|
||||
}
|
||||
|
||||
const normalizedLimit = Number.isFinite(Number(limit))
|
||||
? Math.max(1, Math.min(Number(limit), 200))
|
||||
: 100
|
||||
|
||||
params.push(normalizedLimit)
|
||||
const result = await query(
|
||||
`
|
||||
SELECT inventory_group_code
|
||||
FROM inventory_items
|
||||
WHERE ${filters.join(' AND ')}
|
||||
GROUP BY inventory_group_code
|
||||
ORDER BY inventory_group_code ASC
|
||||
LIMIT $${params.length}
|
||||
`,
|
||||
params,
|
||||
)
|
||||
|
||||
return result.rows.map((row) => String(row.inventory_group_code || '').trim()).filter(Boolean)
|
||||
}
|
||||
|
||||
function normalizeInventoryGroupCodes(inventoryGroupCodes) {
|
||||
if (inventoryGroupCodes == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return Array.from(new Set((Array.isArray(inventoryGroupCodes) ? inventoryGroupCodes : [inventoryGroupCodes])
|
||||
.map((value) => String(value || '').trim())
|
||||
.filter(Boolean)))
|
||||
}
|
||||
|
||||
/** @returns {Promise<InventoryItemRow | null>} */
|
||||
export async function releaseReservedInventoryItem(inventoryItemId, updatedAt) {
|
||||
return withTransaction(async (client) => {
|
||||
|
||||
@@ -24,6 +24,7 @@ export async function listTaskInventoryBindingsByTaskId(taskId) {
|
||||
ii.sku_code,
|
||||
ii.batch_no,
|
||||
ii.credential_type,
|
||||
ii.inventory_group_code,
|
||||
ii.display_value,
|
||||
ii.status AS inventory_item_status,
|
||||
ii.invalid_reason
|
||||
@@ -57,6 +58,7 @@ export async function getTaskInventoryBindingById(bindingId) {
|
||||
ii.sku_code,
|
||||
ii.batch_no,
|
||||
ii.credential_type,
|
||||
ii.inventory_group_code,
|
||||
ii.display_value,
|
||||
ii.status AS inventory_item_status,
|
||||
ii.invalid_reason
|
||||
|
||||
Reference in New Issue
Block a user