后端迁移更多仓储模块
This commit is contained in:
+70
-16
@@ -1,5 +1,43 @@
|
|||||||
import { query, withTransaction } from '../db/client.js'
|
import { query, withTransaction } from '../db/client.js'
|
||||||
|
|
||||||
|
type AdminUserRow = {
|
||||||
|
id: number
|
||||||
|
username: string
|
||||||
|
password_hash: string
|
||||||
|
role: string
|
||||||
|
status: string
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
inventory_group_codes: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminUserCreateInput = {
|
||||||
|
username: string
|
||||||
|
passwordHash: string
|
||||||
|
role: string
|
||||||
|
status: string
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminUserPatch = Partial<Pick<
|
||||||
|
AdminUserRow,
|
||||||
|
'username' | 'password_hash' | 'role' | 'status' | 'updated_at'
|
||||||
|
>>
|
||||||
|
|
||||||
|
type AdminUserListInput = {
|
||||||
|
page?: number
|
||||||
|
pageSize?: number
|
||||||
|
username?: string
|
||||||
|
role?: string
|
||||||
|
status?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminUserListResult = {
|
||||||
|
items: AdminUserRow[]
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|
||||||
const ADMIN_USER_SELECT = `
|
const ADMIN_USER_SELECT = `
|
||||||
SELECT
|
SELECT
|
||||||
au.*,
|
au.*,
|
||||||
@@ -12,24 +50,24 @@ const ADMIN_USER_SELECT = `
|
|||||||
) bindings ON TRUE
|
) bindings ON TRUE
|
||||||
`
|
`
|
||||||
|
|
||||||
export async function getAdminUserById(userId) {
|
export async function getAdminUserById(userId: number | string): Promise<AdminUserRow | null> {
|
||||||
const result = await query(
|
const result = await query<AdminUserRow>(
|
||||||
`${ADMIN_USER_SELECT} WHERE au.id = $1 LIMIT 1`,
|
`${ADMIN_USER_SELECT} WHERE au.id = $1 LIMIT 1`,
|
||||||
[Number(userId)],
|
[Number(userId)],
|
||||||
)
|
)
|
||||||
return result.rows[0] || null
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAdminUserByUsername(username) {
|
export async function getAdminUserByUsername(username: string): Promise<AdminUserRow | null> {
|
||||||
const result = await query(
|
const result = await query<AdminUserRow>(
|
||||||
`${ADMIN_USER_SELECT} WHERE au.username = $1 LIMIT 1`,
|
`${ADMIN_USER_SELECT} WHERE au.username = $1 LIMIT 1`,
|
||||||
[String(username || '').trim().toLowerCase()],
|
[String(username || '').trim().toLowerCase()],
|
||||||
)
|
)
|
||||||
return result.rows[0] || null
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAdminUser(input) {
|
export async function createAdminUser(input: AdminUserCreateInput): Promise<AdminUserRow | null> {
|
||||||
const result = await query(
|
const result = await query<{ [column: string]: unknown, id: number }>(
|
||||||
`
|
`
|
||||||
INSERT INTO admin_users (
|
INSERT INTO admin_users (
|
||||||
username,
|
username,
|
||||||
@@ -54,14 +92,17 @@ export async function createAdminUser(input) {
|
|||||||
return getAdminUserById(result.rows[0]?.id || 0)
|
return getAdminUserById(result.rows[0]?.id || 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateAdminUser(userId, patch) {
|
export async function updateAdminUser(
|
||||||
|
userId: number | string,
|
||||||
|
patch: AdminUserPatch,
|
||||||
|
): Promise<AdminUserRow | null> {
|
||||||
const current = await getAdminUserById(userId)
|
const current = await getAdminUserById(userId)
|
||||||
if (!current) {
|
if (!current) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = { ...current, ...patch }
|
const next = { ...current, ...patch }
|
||||||
const result = await query(
|
const result = await query<{ [column: string]: unknown, id: number }>(
|
||||||
`
|
`
|
||||||
UPDATE admin_users
|
UPDATE admin_users
|
||||||
SET
|
SET
|
||||||
@@ -86,10 +127,16 @@ export async function updateAdminUser(userId, patch) {
|
|||||||
return getAdminUserById(result.rows[0]?.id || 0)
|
return getAdminUserById(result.rows[0]?.id || 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listAdminUsers({ page = 1, pageSize = 20, username = '', role = '', status = '' } = {}) {
|
export async function listAdminUsers({
|
||||||
|
page = 1,
|
||||||
|
pageSize = 20,
|
||||||
|
username = '',
|
||||||
|
role = '',
|
||||||
|
status = '',
|
||||||
|
}: AdminUserListInput = {}): Promise<AdminUserListResult> {
|
||||||
const offset = (page - 1) * pageSize
|
const offset = (page - 1) * pageSize
|
||||||
const filters = []
|
const filters: string[] = []
|
||||||
const params = []
|
const params: unknown[] = []
|
||||||
|
|
||||||
if (username) {
|
if (username) {
|
||||||
params.push(`%${username}%`)
|
params.push(`%${username}%`)
|
||||||
@@ -107,11 +154,14 @@ export async function listAdminUsers({ page = 1, pageSize = 20, username = '', r
|
|||||||
}
|
}
|
||||||
|
|
||||||
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
||||||
const totalResult = await query(`SELECT COUNT(*)::int AS total FROM admin_users ${whereClause}`, params)
|
const totalResult = await query<{ [column: string]: unknown, total: number }>(
|
||||||
|
`SELECT COUNT(*)::int AS total FROM admin_users ${whereClause}`,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
|
||||||
params.push(pageSize)
|
params.push(pageSize)
|
||||||
params.push(offset)
|
params.push(offset)
|
||||||
const itemsResult = await query(
|
const itemsResult = await query<AdminUserRow>(
|
||||||
`
|
`
|
||||||
${ADMIN_USER_SELECT}
|
${ADMIN_USER_SELECT}
|
||||||
${whereClause}
|
${whereClause}
|
||||||
@@ -127,14 +177,18 @@ export async function listAdminUsers({ page = 1, pageSize = 20, username = '', r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function countActiveAdminUsers() {
|
export async function countActiveAdminUsers(): Promise<number> {
|
||||||
const result = await query(
|
const result = await query<{ [column: string]: unknown, total: number }>(
|
||||||
`SELECT COUNT(*)::int AS total FROM admin_users WHERE role = 'admin' AND status = 'active'`,
|
`SELECT COUNT(*)::int AS total FROM admin_users WHERE role = 'admin' AND status = 'active'`,
|
||||||
)
|
)
|
||||||
return Number(result.rows[0]?.total || 0)
|
return Number(result.rows[0]?.total || 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function replaceAdminUserInventoryGroupBindings(userId, inventoryGroupCodes = [], timestamp) {
|
export async function replaceAdminUserInventoryGroupBindings(
|
||||||
|
userId: number | string,
|
||||||
|
inventoryGroupCodes: unknown[] = [],
|
||||||
|
timestamp: string,
|
||||||
|
): Promise<AdminUserRow | null> {
|
||||||
const normalizedUserId = Number(userId)
|
const normalizedUserId = Number(userId)
|
||||||
const normalizedCodes = Array.from(new Set((Array.isArray(inventoryGroupCodes) ? inventoryGroupCodes : [])
|
const normalizedCodes = Array.from(new Set((Array.isArray(inventoryGroupCodes) ? inventoryGroupCodes : [])
|
||||||
.map((value) => String(value || '').trim())
|
.map((value) => String(value || '').trim())
|
||||||
+117
-13
@@ -1,15 +1,106 @@
|
|||||||
import { query, withTransaction } from '../db/client.js'
|
import { query, withTransaction } from '../db/client.js'
|
||||||
|
|
||||||
export async function getFulfillmentProfileByKey(profileKey) {
|
type FulfillmentProfileRow = {
|
||||||
const result = await query(
|
id: number
|
||||||
|
profile_key: string
|
||||||
|
name: string
|
||||||
|
executor_key: string
|
||||||
|
requires_claim: boolean
|
||||||
|
auto_dispatch: boolean
|
||||||
|
inventory_strategy: string
|
||||||
|
config_json: string | Record<string, unknown>
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FulfillmentProfileRequirementRow = {
|
||||||
|
id: number
|
||||||
|
profile_id: number
|
||||||
|
role_key: string
|
||||||
|
roleKey?: string
|
||||||
|
credential_type: string
|
||||||
|
credentialType?: string
|
||||||
|
quantity_per_unit: number
|
||||||
|
is_required: boolean
|
||||||
|
config_json: string | Record<string, unknown>
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SkuFulfillmentBindingRow = {
|
||||||
|
id: number
|
||||||
|
sku_code: string
|
||||||
|
provider: string
|
||||||
|
platform: string
|
||||||
|
shop_id: string
|
||||||
|
profile_id: number
|
||||||
|
enabled: boolean
|
||||||
|
priority: number
|
||||||
|
config_json: string | Record<string, unknown>
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
profile_key?: string
|
||||||
|
name?: string
|
||||||
|
profile_name?: string
|
||||||
|
executor_key?: string
|
||||||
|
requires_claim?: boolean
|
||||||
|
auto_dispatch?: boolean
|
||||||
|
inventory_strategy?: string
|
||||||
|
profile_config_json?: string | Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
type FulfillmentProfileUpsertInput = {
|
||||||
|
profileKey: string
|
||||||
|
name: string
|
||||||
|
executorKey: string
|
||||||
|
requiresClaim?: boolean
|
||||||
|
autoDispatch?: boolean
|
||||||
|
inventoryStrategy?: string
|
||||||
|
configJson?: string | Record<string, unknown>
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FulfillmentProfileRequirementInput = {
|
||||||
|
roleKey: string
|
||||||
|
credentialType: string
|
||||||
|
quantityPerUnit?: number | string
|
||||||
|
isRequired?: boolean
|
||||||
|
configJson?: string | Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
type SkuFulfillmentBindingUpsertInput = {
|
||||||
|
skuCode: string
|
||||||
|
provider?: string
|
||||||
|
platform?: string
|
||||||
|
shopId?: string
|
||||||
|
profileId: number | string
|
||||||
|
enabled?: boolean
|
||||||
|
priority?: number | string
|
||||||
|
configJson?: string | Record<string, unknown>
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type FulfillmentBindingResolveInput = {
|
||||||
|
skuCode: string
|
||||||
|
provider?: string
|
||||||
|
platform?: string
|
||||||
|
shopId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFulfillmentProfileByKey(profileKey: string): Promise<FulfillmentProfileRow | null> {
|
||||||
|
const result = await query<FulfillmentProfileRow>(
|
||||||
'SELECT * FROM fulfillment_profiles WHERE profile_key = $1 LIMIT 1',
|
'SELECT * FROM fulfillment_profiles WHERE profile_key = $1 LIMIT 1',
|
||||||
[String(profileKey || '').trim()],
|
[String(profileKey || '').trim()],
|
||||||
)
|
)
|
||||||
return result.rows[0] || null
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function upsertFulfillmentProfile(input) {
|
export async function upsertFulfillmentProfile(
|
||||||
const result = await query(
|
input: FulfillmentProfileUpsertInput,
|
||||||
|
): Promise<FulfillmentProfileRow | null> {
|
||||||
|
const result = await query<FulfillmentProfileRow>(
|
||||||
`
|
`
|
||||||
INSERT INTO fulfillment_profiles (
|
INSERT INTO fulfillment_profiles (
|
||||||
profile_key,
|
profile_key,
|
||||||
@@ -49,7 +140,11 @@ export async function upsertFulfillmentProfile(input) {
|
|||||||
return result.rows[0] || null
|
return result.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function replaceFulfillmentProfileRequirements(profileId, requirements = [], timestamp) {
|
export async function replaceFulfillmentProfileRequirements(
|
||||||
|
profileId: number | string,
|
||||||
|
requirements: FulfillmentProfileRequirementInput[] = [],
|
||||||
|
timestamp: string,
|
||||||
|
): Promise<void> {
|
||||||
await withTransaction(async (client) => {
|
await withTransaction(async (client) => {
|
||||||
await client.query(
|
await client.query(
|
||||||
'DELETE FROM fulfillment_profile_requirements WHERE profile_id = $1',
|
'DELETE FROM fulfillment_profile_requirements WHERE profile_id = $1',
|
||||||
@@ -85,8 +180,10 @@ export async function replaceFulfillmentProfileRequirements(profileId, requireme
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listFulfillmentProfileRequirements(profileId) {
|
export async function listFulfillmentProfileRequirements(
|
||||||
const result = await query(
|
profileId: number | string,
|
||||||
|
): Promise<FulfillmentProfileRequirementRow[]> {
|
||||||
|
const result = await query<FulfillmentProfileRequirementRow>(
|
||||||
`
|
`
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM fulfillment_profile_requirements
|
FROM fulfillment_profile_requirements
|
||||||
@@ -99,8 +196,10 @@ export async function listFulfillmentProfileRequirements(profileId) {
|
|||||||
return result.rows
|
return result.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function upsertSkuFulfillmentBinding(input) {
|
export async function upsertSkuFulfillmentBinding(
|
||||||
const existing = await query(
|
input: SkuFulfillmentBindingUpsertInput,
|
||||||
|
): Promise<SkuFulfillmentBindingRow | null> {
|
||||||
|
const existing = await query<SkuFulfillmentBindingRow>(
|
||||||
`
|
`
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM sku_fulfillment_bindings
|
FROM sku_fulfillment_bindings
|
||||||
@@ -111,7 +210,7 @@ export async function upsertSkuFulfillmentBinding(input) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (!existing.rows[0]) {
|
if (!existing.rows[0]) {
|
||||||
const inserted = await query(
|
const inserted = await query<SkuFulfillmentBindingRow>(
|
||||||
`
|
`
|
||||||
INSERT INTO sku_fulfillment_bindings (
|
INSERT INTO sku_fulfillment_bindings (
|
||||||
sku_code,
|
sku_code,
|
||||||
@@ -144,7 +243,7 @@ export async function upsertSkuFulfillmentBinding(input) {
|
|||||||
return inserted.rows[0] || null
|
return inserted.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await query(
|
const updated = await query<SkuFulfillmentBindingRow>(
|
||||||
`
|
`
|
||||||
UPDATE sku_fulfillment_bindings
|
UPDATE sku_fulfillment_bindings
|
||||||
SET
|
SET
|
||||||
@@ -169,8 +268,13 @@ export async function upsertSkuFulfillmentBinding(input) {
|
|||||||
return updated.rows[0] || null
|
return updated.rows[0] || null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function resolveFulfillmentBinding({ skuCode, provider = '', platform = '', shopId = '' }) {
|
export async function resolveFulfillmentBinding({
|
||||||
const result = await query(
|
skuCode,
|
||||||
|
provider = '',
|
||||||
|
platform = '',
|
||||||
|
shopId = '',
|
||||||
|
}: FulfillmentBindingResolveInput): Promise<SkuFulfillmentBindingRow | null> {
|
||||||
|
const result = await query<SkuFulfillmentBindingRow>(
|
||||||
`
|
`
|
||||||
SELECT
|
SELECT
|
||||||
sfb.*,
|
sfb.*,
|
||||||
+50
-4
@@ -1,7 +1,53 @@
|
|||||||
import { query } from '../db/client.js'
|
import { query } from '../db/client.js'
|
||||||
|
|
||||||
export async function upsertProductMatchRule(input) {
|
type ProductMatchRuleRow = {
|
||||||
const result = await query(
|
id: number
|
||||||
|
provider: string
|
||||||
|
platform: string
|
||||||
|
shop_id: string
|
||||||
|
external_item_id: string
|
||||||
|
external_sku_code: string
|
||||||
|
external_sku_name: string
|
||||||
|
external_sku_name_normalized: string
|
||||||
|
resolved_sku_code: string
|
||||||
|
enabled: boolean
|
||||||
|
priority: number
|
||||||
|
config_json: string | Record<string, unknown>
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
matched_by?: string
|
||||||
|
match_score?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductMatchRuleUpsertInput = {
|
||||||
|
provider?: string
|
||||||
|
platform?: string
|
||||||
|
shopId?: string
|
||||||
|
externalItemId?: string
|
||||||
|
externalSkuCode?: string
|
||||||
|
externalSkuName?: string
|
||||||
|
externalSkuNameNormalized?: string
|
||||||
|
resolvedSkuCode: string
|
||||||
|
enabled?: boolean
|
||||||
|
priority?: number | string
|
||||||
|
configJson?: string | Record<string, unknown>
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductMatchRuleResolveInput = {
|
||||||
|
provider?: string
|
||||||
|
platform?: string
|
||||||
|
shopId?: string
|
||||||
|
externalItemId?: string
|
||||||
|
externalSkuCode?: string
|
||||||
|
externalSkuNameNormalized?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertProductMatchRule(
|
||||||
|
input: ProductMatchRuleUpsertInput,
|
||||||
|
): Promise<ProductMatchRuleRow | null> {
|
||||||
|
const result = await query<ProductMatchRuleRow>(
|
||||||
`
|
`
|
||||||
INSERT INTO product_match_rules (
|
INSERT INTO product_match_rules (
|
||||||
provider,
|
provider,
|
||||||
@@ -64,8 +110,8 @@ export async function resolveProductMatchRule({
|
|||||||
externalItemId = '',
|
externalItemId = '',
|
||||||
externalSkuCode = '',
|
externalSkuCode = '',
|
||||||
externalSkuNameNormalized = '',
|
externalSkuNameNormalized = '',
|
||||||
}) {
|
}: ProductMatchRuleResolveInput): Promise<ProductMatchRuleRow | null> {
|
||||||
const result = await query(
|
const result = await query<ProductMatchRuleRow>(
|
||||||
`
|
`
|
||||||
SELECT
|
SELECT
|
||||||
pmr.*,
|
pmr.*,
|
||||||
@@ -224,12 +224,16 @@
|
|||||||
- `src/repositories/message-delivery-repo.ts`
|
- `src/repositories/message-delivery-repo.ts`
|
||||||
- `src/repositories/webhook-event-repo.ts`
|
- `src/repositories/webhook-event-repo.ts`
|
||||||
13. `MessageDeliveryRow` 与列表查询结果已进入共享 repository row 类型
|
13. `MessageDeliveryRow` 与列表查询结果已进入共享 repository row 类型
|
||||||
|
14. 小型 repository 第三批已迁移到 `.ts`:
|
||||||
|
- `src/repositories/admin-user-repo.ts`
|
||||||
|
- `src/repositories/product-match-rule-repo.ts`
|
||||||
|
- `src/repositories/fulfillment-profile-repo.ts`
|
||||||
|
|
||||||
## 下一步建议
|
## 下一步建议
|
||||||
|
|
||||||
第一批继续推进时,建议按这个顺序:
|
第一批继续推进时,建议按这个顺序:
|
||||||
|
|
||||||
1. 继续迁移剩余 repository:`admin-user-repo`、`inventory-repo`、`order-repo`、`task-repo`
|
1. 继续迁移剩余核心 repository:`inventory-repo`、`order-repo`、`task-repo`
|
||||||
2. 拆分并迁移 `runtime.js`,把 env 解析、默认配置加载、配置合并分开
|
2. 拆分并迁移 `runtime.js`,把 env 解析、默认配置加载、配置合并分开
|
||||||
3. 为 webhook、库存换码、自动发货补测试
|
3. 为 webhook、库存换码、自动发货补测试
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user