308 lines
7.7 KiB
TypeScript
308 lines
7.7 KiB
TypeScript
import { query, withTransaction } from '../db/client.js'
|
|
|
|
type FulfillmentProfileRow = {
|
|
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
|
|
}
|
|
|
|
export 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',
|
|
[String(profileKey || '').trim()],
|
|
)
|
|
return result.rows[0] || null
|
|
}
|
|
|
|
export async function upsertFulfillmentProfile(
|
|
input: FulfillmentProfileUpsertInput,
|
|
): Promise<FulfillmentProfileRow | null> {
|
|
const result = await query<FulfillmentProfileRow>(
|
|
`
|
|
INSERT INTO fulfillment_profiles (
|
|
profile_key,
|
|
name,
|
|
executor_key,
|
|
requires_claim,
|
|
auto_dispatch,
|
|
inventory_strategy,
|
|
config_json,
|
|
created_at,
|
|
updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8, $9)
|
|
ON CONFLICT (profile_key) DO UPDATE
|
|
SET
|
|
name = EXCLUDED.name,
|
|
executor_key = EXCLUDED.executor_key,
|
|
requires_claim = EXCLUDED.requires_claim,
|
|
auto_dispatch = EXCLUDED.auto_dispatch,
|
|
inventory_strategy = EXCLUDED.inventory_strategy,
|
|
config_json = EXCLUDED.config_json,
|
|
updated_at = EXCLUDED.updated_at
|
|
RETURNING *
|
|
`,
|
|
[
|
|
input.profileKey,
|
|
input.name,
|
|
input.executorKey,
|
|
Boolean(input.requiresClaim),
|
|
Boolean(input.autoDispatch),
|
|
input.inventoryStrategy || 'static_pool',
|
|
input.configJson || '{}',
|
|
input.createdAt,
|
|
input.updatedAt,
|
|
],
|
|
)
|
|
|
|
return result.rows[0] || null
|
|
}
|
|
|
|
export async function replaceFulfillmentProfileRequirements(
|
|
profileId: number | string,
|
|
requirements: FulfillmentProfileRequirementInput[] = [],
|
|
timestamp: string,
|
|
): Promise<void> {
|
|
await withTransaction(async (client) => {
|
|
await client.query(
|
|
'DELETE FROM fulfillment_profile_requirements WHERE profile_id = $1',
|
|
[Number(profileId)],
|
|
)
|
|
|
|
for (const requirement of requirements) {
|
|
await client.query(
|
|
`
|
|
INSERT INTO fulfillment_profile_requirements (
|
|
profile_id,
|
|
role_key,
|
|
credential_type,
|
|
quantity_per_unit,
|
|
is_required,
|
|
config_json,
|
|
created_at,
|
|
updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7, $8)
|
|
`,
|
|
[
|
|
Number(profileId),
|
|
requirement.roleKey,
|
|
requirement.credentialType,
|
|
Number(requirement.quantityPerUnit || 1),
|
|
requirement.isRequired !== false,
|
|
requirement.configJson || '{}',
|
|
timestamp,
|
|
timestamp,
|
|
],
|
|
)
|
|
}
|
|
})
|
|
}
|
|
|
|
export async function listFulfillmentProfileRequirements(
|
|
profileId: number | string,
|
|
): Promise<FulfillmentProfileRequirementRow[]> {
|
|
const result = await query<FulfillmentProfileRequirementRow>(
|
|
`
|
|
SELECT *
|
|
FROM fulfillment_profile_requirements
|
|
WHERE profile_id = $1
|
|
ORDER BY id ASC
|
|
`,
|
|
[Number(profileId)],
|
|
)
|
|
|
|
return result.rows
|
|
}
|
|
|
|
export async function upsertSkuFulfillmentBinding(
|
|
input: SkuFulfillmentBindingUpsertInput,
|
|
): Promise<SkuFulfillmentBindingRow | null> {
|
|
const existing = await query<SkuFulfillmentBindingRow>(
|
|
`
|
|
SELECT *
|
|
FROM sku_fulfillment_bindings
|
|
WHERE sku_code = $1 AND provider = $2 AND platform = $3 AND shop_id = $4
|
|
LIMIT 1
|
|
`,
|
|
[input.skuCode, input.provider || '', input.platform || '', input.shopId || ''],
|
|
)
|
|
|
|
if (!existing.rows[0]) {
|
|
const inserted = await query<SkuFulfillmentBindingRow>(
|
|
`
|
|
INSERT INTO sku_fulfillment_bindings (
|
|
sku_code,
|
|
provider,
|
|
platform,
|
|
shop_id,
|
|
profile_id,
|
|
enabled,
|
|
priority,
|
|
config_json,
|
|
created_at,
|
|
updated_at
|
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::jsonb, $9, $10)
|
|
RETURNING *
|
|
`,
|
|
[
|
|
input.skuCode,
|
|
input.provider || '',
|
|
input.platform || '',
|
|
input.shopId || '',
|
|
Number(input.profileId),
|
|
input.enabled !== false,
|
|
Number(input.priority || 100),
|
|
input.configJson || '{}',
|
|
input.createdAt,
|
|
input.updatedAt,
|
|
],
|
|
)
|
|
|
|
return inserted.rows[0] || null
|
|
}
|
|
|
|
const updated = await query<SkuFulfillmentBindingRow>(
|
|
`
|
|
UPDATE sku_fulfillment_bindings
|
|
SET
|
|
profile_id = $1,
|
|
enabled = $2,
|
|
priority = $3,
|
|
config_json = $4::jsonb,
|
|
updated_at = $5
|
|
WHERE id = $6
|
|
RETURNING *
|
|
`,
|
|
[
|
|
Number(input.profileId),
|
|
input.enabled !== false,
|
|
Number(input.priority || 100),
|
|
input.configJson || '{}',
|
|
input.updatedAt,
|
|
Number(existing.rows[0].id),
|
|
],
|
|
)
|
|
|
|
return updated.rows[0] || null
|
|
}
|
|
|
|
export async function resolveFulfillmentBinding({
|
|
skuCode,
|
|
provider = '',
|
|
platform = '',
|
|
shopId = '',
|
|
}: FulfillmentBindingResolveInput): Promise<SkuFulfillmentBindingRow | null> {
|
|
const result = await query<SkuFulfillmentBindingRow>(
|
|
`
|
|
SELECT
|
|
sfb.*,
|
|
fp.profile_key,
|
|
fp.name AS profile_name,
|
|
fp.executor_key,
|
|
fp.requires_claim,
|
|
fp.auto_dispatch,
|
|
fp.inventory_strategy,
|
|
fp.config_json AS profile_config_json
|
|
FROM sku_fulfillment_bindings sfb
|
|
JOIN fulfillment_profiles fp ON fp.id = sfb.profile_id
|
|
WHERE sfb.enabled = TRUE
|
|
AND sfb.sku_code = $1
|
|
AND (sfb.provider = '' OR sfb.provider = $2)
|
|
AND (sfb.platform = '' OR sfb.platform = $3)
|
|
AND (sfb.shop_id = '' OR sfb.shop_id = $4)
|
|
ORDER BY
|
|
CASE WHEN sfb.shop_id = '' THEN 1 ELSE 0 END,
|
|
CASE WHEN sfb.platform = '' THEN 1 ELSE 0 END,
|
|
CASE WHEN sfb.provider = '' THEN 1 ELSE 0 END,
|
|
sfb.priority ASC,
|
|
sfb.id ASC
|
|
LIMIT 1
|
|
`,
|
|
[skuCode, provider, platform, shopId],
|
|
)
|
|
|
|
return result.rows[0] || null
|
|
}
|