refaktoryzacja service api.

wydzielenie mockow.
dodanie parsowania wyszukiwania
This commit is contained in:
2026-05-02 05:49:24 +02:00
parent 045c65c363
commit 93778065ce
20 changed files with 820 additions and 275 deletions

View File

@@ -9,7 +9,7 @@
"scripts": {
"lint": "eslint -c ./eslint.config.js \"./src*/**/*.{js,cjs,mjs,vue}\"",
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test": "echo \"No test specified\" && exit 0",
"test": "node --test \"src/**/*.test.js\"",
"dev": "quasar dev",
"build": "quasar build",
"postinstall": "quasar prepare"
@@ -41,4 +41,4 @@
"yarn": ">= 1.21.1",
"pnpm": ">= 10.0.0"
}
}
}

View File

@@ -0,0 +1,19 @@
import mockDictionaries from 'src/mocks/dictionaries.json'
import { waitForMockApi } from 'src/services/apiMode'
export async function fetchDictionaries() {
await waitForMockApi()
return normalizeDictionaries(mockDictionaries)
}
function normalizeDictionaries(data) {
return {
models: data.models ?? [],
clients: data.clients ?? [],
finishes: data.finishes ?? [],
productionLists: data.productionLists ?? [],
operations: data.operations ?? [],
colors: data.colors ?? [],
}
}

View File

@@ -0,0 +1,43 @@
import mockSpecifications from 'src/mocks/specifications.json'
import { waitForMockApi } from 'src/services/apiMode'
export async function fetchProductSpecification(productId) {
await waitForMockApi()
return normalizeSpecificationResponse(getMockSpecification(productId))
}
export async function refreshProductSpecification(productId) {
await waitForMockApi()
return normalizeSpecificationResponse({
...getMockSpecification(productId),
lastFetchedAt: new Date().toISOString(),
})
}
function getMockSpecification(productId) {
return (
mockSpecifications[productId] ?? {
productId,
orderId: null,
sourceUrl: null,
lastFetchedAt: null,
specification: {
sections: [],
},
diff: [],
}
)
}
function normalizeSpecificationResponse(data) {
return {
productId: data.productId,
orderId: data.orderId ?? null,
sourceUrl: data.sourceUrl ?? null,
lastFetchedAt: data.lastFetchedAt ?? null,
sections: data.specification?.sections ?? data.sections ?? [],
diff: data.diff ?? [],
}
}

View File

@@ -0,0 +1,81 @@
import mockTimelines from 'src/mocks/timelines.json'
import { waitForMockApi } from 'src/services/apiMode'
const timelinesByProductId = structuredClone(mockTimelines)
export async function fetchProductTimeline(productId) {
await waitForMockApi()
return getMockTimeline(productId)
}
export async function createProductTimelineEvent(productId, payload) {
await waitForMockApi()
const timeline = getMockTimeline(productId)
const event = createMockEvent(productId, payload)
timeline.events.push(event)
timeline.timelinePreview = createTimelinePreview(timeline.events)
return {
event,
timelinePreview: timeline.timelinePreview,
}
}
function getMockTimeline(productId) {
if (!timelinesByProductId[productId]) {
timelinesByProductId[productId] = {
productId,
events: [],
timelinePreview: {
body: [],
neck: [],
},
}
}
return timelinesByProductId[productId]
}
function createMockEvent(productId, payload) {
const now = Date.now()
return {
id: now,
productId,
partId: payload.partId ?? null,
partType: normalizePartType(payload.partType),
type: payload.type ?? 'operation',
operationId: payload.operationId ?? null,
operationCode: payload.operationCode ?? payload.code ?? 'NEW',
operationName: payload.operationName ?? payload.label ?? 'Nowa operacja',
date: payload.date ?? new Date().toISOString().slice(0, 10),
note: payload.note ?? null,
photosCount: payload.photosCount ?? 0,
}
}
function createTimelinePreview(events) {
return {
body: createPartPreview(events, 'BODY'),
neck: createPartPreview(events, 'NECK'),
}
}
function createPartPreview(events, partType) {
return events
.filter((event) => event.type === 'operation' && event.partType === partType)
.map((event) => ({
id: event.id,
code: event.operationCode,
label: event.operationName,
date: event.date,
status: 'done',
}))
}
function normalizePartType(partType) {
return String(partType ?? 'BODY').toUpperCase()
}

View File

@@ -0,0 +1,83 @@
import mockProducts from 'src/mocks/products.json'
import { waitForMockApi } from 'src/services/apiMode'
const mockItems = [...mockProducts.items]
export async function fetchProducts(params) {
await waitForMockApi()
const filteredItems = filterMockProducts(mockItems, params)
const offset = Number(params?.offset ?? 0)
const limit = Number(params?.limit ?? filteredItems.length)
const items = filteredItems.slice(offset, offset + limit)
return {
items,
pageInfo: {
limit,
offset,
hasMore: offset + limit < filteredItems.length,
total: filteredItems.length,
},
}
}
function filterMockProducts(items, params = {}) {
return items.filter((product) => {
if (!matchesOrderSearch(product, params)) {
return false
}
if (params.finish && product.finish !== params.finish) {
return false
}
if (params.year && product.orderYear !== Number(params.year)) {
return false
}
if (params.productionList && !product.productionLists.includes(params.productionList)) {
return false
}
return true
})
}
function matchesOrderSearch(product, params = {}) {
const search = params.search?.trim()
const orderSearch = params.orderSearch ?? []
if (!search) {
return true
}
if (!orderSearch.length) {
return false
}
return orderSearch.some((condition) => matchesOrderCondition(product, condition))
}
function matchesOrderCondition(product, condition) {
const productOrderNumber = Number(product.orderNumber)
const productOrderNumberText = String(productOrderNumber)
if (condition.type === 'orderNumberPrefix') {
return productOrderNumberText.startsWith(condition.orderNumberPrefix)
}
if (condition.orderNumber !== null && productOrderNumber !== condition.orderNumber) {
return false
}
if (condition.orderYear !== null && product.orderYear !== condition.orderYear) {
return false
}
if (condition.orderIndex !== null && product.orderIndex !== condition.orderIndex) {
return false
}
return true
}

View File

@@ -78,7 +78,7 @@ import { useProductsStore } from 'src/stores/productsStore'
const productsStore = useProductsStore()
const uiStore = useUiStore()
const searchQuery = ref(productsStore.filters.search)
const searchQuery = ref(productsStore.searchQuery)
const activeMonth = ref('all')
const monthFilters = [
@@ -98,7 +98,7 @@ onMounted(() => {
})
watch(searchQuery, (value) => {
productsStore.setFilters({ search: value })
productsStore.setSearchQuery(value)
})
function openAdvancedSearch() {
@@ -135,7 +135,7 @@ function openProductionStatuses({ product, partType } = {}) {
}
function applySearch() {
productsStore.applyFilters({ search: searchQuery.value })
productsStore.applySearch(searchQuery.value)
}
function selectMonth(month) {

View File

@@ -1,26 +1,5 @@
import { api } from 'src/boot/axios'
import mockDictionaries from 'src/mocks/dictionaries.json'
import { USE_MOCK_API, waitForMockApi } from 'src/services/apiMode'
import { fetchDictionaries as fetchDictionariesFromHttp } from 'src/services/dictionariesHttpApi'
import { fetchDictionaries as fetchDictionariesFromMock } from 'src/mocks/api/dictionariesMockApi'
import { USE_MOCK_API } from 'src/services/apiMode'
export async function fetchDictionaries() {
if (USE_MOCK_API) {
await waitForMockApi()
return normalizeDictionaries(mockDictionaries)
}
const response = await api.get('/mayo-api/dictionaries')
return normalizeDictionaries(response.data)
}
function normalizeDictionaries(data) {
return {
models: data.models ?? [],
clients: data.clients ?? [],
finishes: data.finishes ?? [],
productionLists: data.productionLists ?? [],
operations: data.operations ?? [],
colors: data.colors ?? [],
}
}
export const fetchDictionaries = USE_MOCK_API ? fetchDictionariesFromMock : fetchDictionariesFromHttp

View File

@@ -0,0 +1,18 @@
import { api } from 'src/boot/axios'
export async function fetchDictionaries() {
const response = await api.get('/mayo-api/dictionaries')
return normalizeDictionaries(response.data)
}
function normalizeDictionaries(data) {
return {
models: data.models ?? [],
clients: data.clients ?? [],
finishes: data.finishes ?? [],
productionLists: data.productionLists ?? [],
operations: data.operations ?? [],
colors: data.colors ?? [],
}
}

View File

@@ -1,56 +1,17 @@
import { api } from 'src/boot/axios'
import mockSpecifications from 'src/mocks/specifications.json'
import { USE_MOCK_API, waitForMockApi } from 'src/services/apiMode'
import {
fetchProductSpecification as fetchProductSpecificationFromHttp,
refreshProductSpecification as refreshProductSpecificationFromHttp,
} from 'src/services/productSpecificationHttpApi'
import {
fetchProductSpecification as fetchProductSpecificationFromMock,
refreshProductSpecification as refreshProductSpecificationFromMock,
} from 'src/mocks/api/productSpecificationMockApi'
import { USE_MOCK_API } from 'src/services/apiMode'
function normalizeSpecificationResponse(data) {
return {
productId: data.productId,
orderId: data.orderId ?? null,
sourceUrl: data.sourceUrl ?? null,
lastFetchedAt: data.lastFetchedAt ?? null,
sections: data.specification?.sections ?? data.sections ?? [],
diff: data.diff ?? [],
}
}
export const fetchProductSpecification = USE_MOCK_API
? fetchProductSpecificationFromMock
: fetchProductSpecificationFromHttp
export async function fetchProductSpecification(productId) {
if (USE_MOCK_API) {
await waitForMockApi()
return normalizeSpecificationResponse(getMockSpecification(productId))
}
const response = await api.get(`/mayo-api/products/${productId}/specification`)
return normalizeSpecificationResponse(response.data)
}
export async function refreshProductSpecification(productId) {
if (USE_MOCK_API) {
await waitForMockApi()
return normalizeSpecificationResponse({
...getMockSpecification(productId),
lastFetchedAt: new Date().toISOString(),
})
}
const response = await api.post(`/mayo-api/products/${productId}/specification/refresh`)
return normalizeSpecificationResponse(response.data)
}
function getMockSpecification(productId) {
return (
mockSpecifications[productId] ?? {
productId,
orderId: null,
sourceUrl: null,
lastFetchedAt: null,
specification: {
sections: [],
},
diff: [],
}
)
}
export const refreshProductSpecification = USE_MOCK_API
? refreshProductSpecificationFromMock
: refreshProductSpecificationFromHttp

View File

@@ -0,0 +1,24 @@
import { api } from 'src/boot/axios'
export async function fetchProductSpecification(productId) {
const response = await api.get(`/mayo-api/products/${productId}/specification`)
return normalizeSpecificationResponse(response.data)
}
export async function refreshProductSpecification(productId) {
const response = await api.post(`/mayo-api/products/${productId}/specification/refresh`)
return normalizeSpecificationResponse(response.data)
}
function normalizeSpecificationResponse(data) {
return {
productId: data.productId,
orderId: data.orderId ?? null,
sourceUrl: data.sourceUrl ?? null,
lastFetchedAt: data.lastFetchedAt ?? null,
sections: data.specification?.sections ?? data.sections ?? [],
diff: data.diff ?? [],
}
}

View File

@@ -1,104 +1,17 @@
import { api } from 'src/boot/axios'
import mockTimelines from 'src/mocks/timelines.json'
import { USE_MOCK_API, waitForMockApi } from 'src/services/apiMode'
import {
createProductTimelineEvent as createProductTimelineEventFromHttp,
fetchProductTimeline as fetchProductTimelineFromHttp,
} from 'src/services/productTimelineHttpApi'
import {
createProductTimelineEvent as createProductTimelineEventFromMock,
fetchProductTimeline as fetchProductTimelineFromMock,
} from 'src/mocks/api/productTimelineMockApi'
import { USE_MOCK_API } from 'src/services/apiMode'
const timelinesByProductId = structuredClone(mockTimelines)
export const fetchProductTimeline = USE_MOCK_API
? fetchProductTimelineFromMock
: fetchProductTimelineFromHttp
export async function fetchProductTimeline(productId) {
if (USE_MOCK_API) {
await waitForMockApi()
return timelinesByProductId[productId] ?? {
productId,
events: [],
timelinePreview: {
body: [],
neck: [],
},
}
}
const response = await api.get(`/mayo-api/products/${productId}/timeline`)
return response.data
}
export async function createProductTimelineEvent(productId, payload) {
if (USE_MOCK_API) {
await waitForMockApi()
const timeline = ensureMockTimeline(productId)
const event = createMockEvent(productId, payload)
timeline.events.push(event)
timeline.timelinePreview = createTimelinePreview(timeline.events)
return {
event,
timelinePreview: timeline.timelinePreview,
}
}
const response = await api.post(`/mayo-api/products/${productId}/timeline/events`, payload)
return {
event: response.data.event ?? response.data,
timelinePreview: response.data.timelinePreview,
}
}
function ensureMockTimeline(productId) {
if (!timelinesByProductId[productId]) {
timelinesByProductId[productId] = {
productId,
events: [],
timelinePreview: {
body: [],
neck: [],
},
}
}
return timelinesByProductId[productId]
}
function createMockEvent(productId, payload) {
const now = Date.now()
return {
id: now,
productId,
partId: payload.partId ?? null,
partType: normalizePartType(payload.partType),
type: payload.type ?? 'operation',
operationId: payload.operationId ?? null,
operationCode: payload.operationCode ?? payload.code ?? 'NEW',
operationName: payload.operationName ?? payload.label ?? 'Nowa operacja',
date: payload.date ?? new Date().toISOString().slice(0, 10),
note: payload.note ?? null,
photosCount: payload.photosCount ?? 0,
}
}
function createTimelinePreview(events) {
return {
body: createPartPreview(events, 'BODY'),
neck: createPartPreview(events, 'NECK'),
}
}
function createPartPreview(events, partType) {
return events
.filter((event) => event.type === 'operation' && event.partType === partType)
.map((event) => ({
id: event.id,
code: event.operationCode,
label: event.operationName,
date: event.date,
status: 'done',
}))
}
function normalizePartType(partType) {
return String(partType ?? 'BODY').toUpperCase()
}
export const createProductTimelineEvent = USE_MOCK_API
? createProductTimelineEventFromMock
: createProductTimelineEventFromHttp

View File

@@ -0,0 +1,16 @@
import { api } from 'src/boot/axios'
export async function fetchProductTimeline(productId) {
const response = await api.get(`/mayo-api/products/${productId}/timeline`)
return response.data
}
export async function createProductTimelineEvent(productId, payload) {
const response = await api.post(`/mayo-api/products/${productId}/timeline/events`, payload)
return {
event: response.data.event ?? response.data,
timelinePreview: response.data.timelinePreview,
}
}

View File

@@ -1,64 +1,5 @@
import { api } from 'src/boot/axios'
import mockProducts from 'src/mocks/products.json'
import { USE_MOCK_API, waitForMockApi } from 'src/services/apiMode'
import { fetchProducts as fetchProductsFromHttp } from 'src/services/productsHttpApi'
import { fetchProducts as fetchProductsFromMock } from 'src/mocks/api/productsMockApi'
import { USE_MOCK_API } from 'src/services/apiMode'
const mockItems = [...mockProducts.items]
export async function fetchProducts(params) {
if (USE_MOCK_API) {
await waitForMockApi()
const filteredItems = filterMockProducts(mockItems, params)
const offset = Number(params?.offset ?? 0)
const limit = Number(params?.limit ?? filteredItems.length)
const items = filteredItems.slice(offset, offset + limit)
return {
items,
pageInfo: {
limit,
offset,
hasMore: offset + limit < filteredItems.length,
total: filteredItems.length,
},
}
}
const response = await api.get('/mayo-api/products', { params })
return {
items: response.data.items ?? response.data,
pageInfo: response.data.pageInfo ?? {},
}
}
function filterMockProducts(items, params = {}) {
return items.filter((product) => {
const search = params.search?.toLowerCase()
const productionList = params.productionList
if (search && !matchesSearch(product, search)) {
return false
}
if (params.finish && product.finish !== params.finish) {
return false
}
if (params.year && product.orderYear !== Number(params.year)) {
return false
}
if (productionList && !product.productionLists.includes(productionList)) {
return false
}
return true
})
}
function matchesSearch(product, search) {
return [product.orderId, product.model, product.client, product.finish]
.filter(Boolean)
.some((value) => value.toLowerCase().includes(search))
}
export const fetchProducts = USE_MOCK_API ? fetchProductsFromMock : fetchProductsFromHttp

View File

@@ -0,0 +1,19 @@
import { api } from 'src/boot/axios'
export async function fetchProducts(params) {
const response = await api.get('/mayo-api/products', {
params: serializeProductsParams(params),
})
return {
items: response.data.items ?? response.data,
pageInfo: response.data.pageInfo ?? {},
}
}
function serializeProductsParams(params = {}) {
return {
...params,
orderSearch: params.orderSearch?.length ? JSON.stringify(params.orderSearch) : undefined,
}
}

View File

@@ -1,12 +1,12 @@
import { computed, ref } from 'vue'
import { acceptHMRUpdate, defineStore } from 'pinia'
import { fetchProducts } from 'src/services/productsApi'
import { parseOrderSearchQuery } from 'src/utils/orderSearchParser'
const DEFAULT_LIMIT = 30
function createDefaultFilters() {
return {
search: '',
modelId: null,
clientId: null,
finish: null,
@@ -19,6 +19,8 @@ export const useProductsStore = defineStore('products', () => {
const ids = ref([])
const byId = ref({})
const filters = ref(createDefaultFilters())
const searchQuery = ref('')
const orderSearch = ref([])
const limit = ref(DEFAULT_LIMIT)
const offset = ref(0)
const total = ref(null)
@@ -33,7 +35,8 @@ export const useProductsStore = defineStore('products', () => {
return {
limit: limit.value,
offset: offset.value,
search: filters.value.search || undefined,
search: searchQuery.value || undefined,
orderSearch: orderSearch.value.length ? orderSearch.value : undefined,
model: filters.value.modelId || undefined,
client: filters.value.clientId || undefined,
finish: filters.value.finish || undefined,
@@ -95,11 +98,21 @@ export const useProductsStore = defineStore('products', () => {
}
}
function setSearchQuery(value) {
searchQuery.value = value
orderSearch.value = parseOrderSearchQuery(value)
}
async function applyFilters(nextFilters) {
setFilters(nextFilters)
await fetchFirstPage()
}
async function applySearch(value) {
setSearchQuery(value)
await fetchFirstPage()
}
function updateProduct(productId, patch) {
const current = byId.value[productId]
@@ -133,6 +146,8 @@ export const useProductsStore = defineStore('products', () => {
ids,
byId,
filters,
searchQuery,
orderSearch,
limit,
offset,
total,
@@ -144,7 +159,9 @@ export const useProductsStore = defineStore('products', () => {
fetchFirstPage,
fetchNextPage,
setFilters,
setSearchQuery,
applyFilters,
applySearch,
updateProduct,
applyTimelinePreviewUpdate,
clear,

View File

@@ -0,0 +1,141 @@
const TOKEN_SEPARATOR_PATTERN = /[\s,.]+/
const ORDER_NUMBER_PATTERN = /^\d{1,4}$/
const YEAR_PATTERN = /^\d{4}$/
const ORDER_INDEX_PATTERN = /^\d{1,2}$/
const MIN_ORDER_YEAR = 2021
export function parseOrderSearchQuery(query) {
return tokenizeSearchQuery(query).map(parseOrderSearchToken).filter(Boolean)
}
export function tokenizeSearchQuery(query) {
return String(query ?? '')
.trim()
.split(TOKEN_SEPARATOR_PATTERN)
.map((token) => token.trim())
.filter(Boolean)
}
export function parseOrderSearchToken(token) {
const parts = token.split('/').map((part) => part.trim())
if (parts.length === 1) {
return parseOrderNumberOnly(parts[0], token)
}
if (parts.length === 2) {
return parseTwoPartOrderToken(parts, token)
}
if (parts.length === 3) {
return parseFullOrderToken(parts, token)
}
return null
}
function parseOrderNumberOnly(orderNumberText, raw) {
if (!isValidOrderNumber(orderNumberText)) {
return null
}
const normalizedOrderNumber = normalizeNumber(orderNumberText)
const hasLeadingZeros = orderNumberText.length > 1 && orderNumberText.startsWith('0')
const isFullWidthOrderNumber = orderNumberText.length === 4
if (hasLeadingZeros || isFullWidthOrderNumber) {
return {
raw,
type: 'orderNumber',
match: 'exact',
orderNumber: normalizedOrderNumber,
orderNumberPrefix: null,
orderYear: null,
orderIndex: null,
}
}
return {
raw,
type: 'orderNumberPrefix',
match: 'prefix',
orderNumber: null,
orderNumberPrefix: orderNumberText,
orderYear: null,
orderIndex: null,
}
}
function parseTwoPartOrderToken(parts, raw) {
const [orderNumberText, secondPart] = parts
if (!isValidOrderNumber(orderNumberText)) {
return null
}
if (isValidOrderYear(secondPart)) {
return {
raw,
type: 'orderNumberYear',
match: 'exact',
orderNumber: normalizeNumber(orderNumberText),
orderNumberPrefix: null,
orderYear: normalizeNumber(secondPart),
orderIndex: null,
}
}
if (isValidOrderIndex(secondPart)) {
return {
raw,
type: 'orderNumberIndex',
match: 'exact',
orderNumber: normalizeNumber(orderNumberText),
orderNumberPrefix: null,
orderYear: null,
orderIndex: normalizeNumber(secondPart),
}
}
return null
}
function parseFullOrderToken(parts, raw) {
const [orderNumberText, orderYearText, orderIndexText] = parts
if (
!isValidOrderNumber(orderNumberText) ||
!isValidOrderYear(orderYearText) ||
!isValidOrderIndex(orderIndexText)
) {
return null
}
return {
raw,
type: 'fullOrderId',
match: 'exact',
orderNumber: normalizeNumber(orderNumberText),
orderNumberPrefix: null,
orderYear: normalizeNumber(orderYearText),
orderIndex: normalizeNumber(orderIndexText),
}
}
function isValidOrderNumber(value) {
return ORDER_NUMBER_PATTERN.test(value) && normalizeNumber(value) >= 1
}
function isValidOrderYear(value) {
return YEAR_PATTERN.test(value) && normalizeNumber(value) >= MIN_ORDER_YEAR
}
function isValidOrderIndex(value) {
const normalized = normalizeNumber(value)
return ORDER_INDEX_PATTERN.test(value) && normalized >= 1 && normalized <= 99
}
function normalizeNumber(value) {
return Number.parseInt(value, 10)
}

View File

@@ -0,0 +1,134 @@
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import {
parseOrderSearchQuery,
parseOrderSearchToken,
tokenizeSearchQuery,
} from './orderSearchParser.js'
describe('tokenizeSearchQuery', () => {
it('splits tokens by spaces, commas and dots', () => {
assert.deepEqual(tokenizeSearchQuery('123/1 0200/2,333/2025/3.444/2024'), [
'123/1',
'0200/2',
'333/2025/3',
'444/2024',
])
})
it('ignores empty input', () => {
assert.deepEqual(tokenizeSearchQuery(' , . '), [])
})
})
describe('parseOrderSearchToken', () => {
it('parses full order id', () => {
assert.deepEqual(parseOrderSearchToken('0012/2025/01'), {
raw: '0012/2025/01',
type: 'fullOrderId',
match: 'exact',
orderNumber: 12,
orderNumberPrefix: null,
orderYear: 2025,
orderIndex: 1,
})
})
it('parses padded order number as exact order number', () => {
assert.deepEqual(parseOrderSearchToken('0012'), {
raw: '0012',
type: 'orderNumber',
match: 'exact',
orderNumber: 12,
orderNumberPrefix: null,
orderYear: null,
orderIndex: null,
})
})
it('parses unpadded order number as prefix search', () => {
assert.deepEqual(parseOrderSearchToken('12'), {
raw: '12',
type: 'orderNumberPrefix',
match: 'prefix',
orderNumber: null,
orderNumberPrefix: '12',
orderYear: null,
orderIndex: null,
})
})
it('parses order number with year', () => {
assert.deepEqual(parseOrderSearchToken('444/2024'), {
raw: '444/2024',
type: 'orderNumberYear',
match: 'exact',
orderNumber: 444,
orderNumberPrefix: null,
orderYear: 2024,
orderIndex: null,
})
})
it('parses order number with product index', () => {
assert.deepEqual(parseOrderSearchToken('12/01'), {
raw: '12/01',
type: 'orderNumberIndex',
match: 'exact',
orderNumber: 12,
orderNumberPrefix: null,
orderYear: null,
orderIndex: 1,
})
})
it('rejects invalid values', () => {
assert.equal(parseOrderSearchToken('0000'), null)
assert.equal(parseOrderSearchToken('12/2019/1'), null)
assert.equal(parseOrderSearchToken('12/2025/100'), null)
assert.equal(parseOrderSearchToken('12/2025/1/2'), null)
})
})
describe('parseOrderSearchQuery', () => {
it('parses many order numbers in one query', () => {
assert.deepEqual(parseOrderSearchQuery('123/1 0200/2 333/2025/3 444/2024'), [
{
raw: '123/1',
type: 'orderNumberIndex',
match: 'exact',
orderNumber: 123,
orderNumberPrefix: null,
orderYear: null,
orderIndex: 1,
},
{
raw: '0200/2',
type: 'orderNumberIndex',
match: 'exact',
orderNumber: 200,
orderNumberPrefix: null,
orderYear: null,
orderIndex: 2,
},
{
raw: '333/2025/3',
type: 'fullOrderId',
match: 'exact',
orderNumber: 333,
orderNumberPrefix: null,
orderYear: 2025,
orderIndex: 3,
},
{
raw: '444/2024',
type: 'orderNumberYear',
match: 'exact',
orderNumber: 444,
orderNumberPrefix: null,
orderYear: 2024,
orderIndex: null,
},
])
})
})