@questwork/q-inventory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.storybook/main.js +23 -0
  2. package/.storybook/preview.js +17 -0
  3. package/README.md +16 -0
  4. package/app/qInventory/App.vue +164 -0
  5. package/app/qInventory/index.html +12 -0
  6. package/app/qInventory/main.ts +4 -0
  7. package/app/qInventory/styles.scss +59 -0
  8. package/dist/components/InventoryAssetDetail.vue.d.ts +8 -0
  9. package/dist/components/InventoryAssetDetail.vue.d.ts.map +1 -0
  10. package/dist/components/InventoryLocationAdmin.vue.d.ts +10 -0
  11. package/dist/components/InventoryLocationAdmin.vue.d.ts.map +1 -0
  12. package/dist/components/InventoryLocationDetail.vue.d.ts +8 -0
  13. package/dist/components/InventoryLocationDetail.vue.d.ts.map +1 -0
  14. package/dist/components/InventoryOverview.vue.d.ts +15 -0
  15. package/dist/components/InventoryOverview.vue.d.ts.map +1 -0
  16. package/dist/components/InventoryScanner.vue.d.ts +21 -0
  17. package/dist/components/InventoryScanner.vue.d.ts.map +1 -0
  18. package/dist/components/QInventory.vue.d.ts +24 -0
  19. package/dist/components/QInventory.vue.d.ts.map +1 -0
  20. package/dist/index.d.ts +16 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/mixins/CSSMixin.d.ts +7 -0
  23. package/dist/mixins/CSSMixin.d.ts.map +1 -0
  24. package/dist/models/index.d.ts +62 -0
  25. package/dist/models/index.d.ts.map +1 -0
  26. package/dist/models/inventoryAssetRepoAxios.d.ts +18 -0
  27. package/dist/models/inventoryAssetRepoAxios.d.ts.map +1 -0
  28. package/dist/models/inventoryAuthRepoAxios.d.ts +26 -0
  29. package/dist/models/inventoryAuthRepoAxios.d.ts.map +1 -0
  30. package/dist/models/inventoryAxiosBase.d.ts +41 -0
  31. package/dist/models/inventoryAxiosBase.d.ts.map +1 -0
  32. package/dist/models/inventoryLocationRepoAxios.d.ts +13 -0
  33. package/dist/models/inventoryLocationRepoAxios.d.ts.map +1 -0
  34. package/dist/models/inventoryMovementRepoAxios.d.ts +13 -0
  35. package/dist/models/inventoryMovementRepoAxios.d.ts.map +1 -0
  36. package/dist/q-inventory.esm.js +961 -0
  37. package/dist/q-inventory.esm.js.map +1 -0
  38. package/dist/q-inventory.min.cjs +2 -0
  39. package/dist/q-inventory.min.cjs.map +1 -0
  40. package/dist/q-inventory.min.css +1 -0
  41. package/dist/q-inventory.min.js +2 -0
  42. package/dist/q-inventory.min.js.map +1 -0
  43. package/dist-app/qInventory/assets/q-inventory-app.css +1 -0
  44. package/dist-app/qInventory/assets/q-inventory-app.js +18 -0
  45. package/dist-app/qInventory/index.html +13 -0
  46. package/package.json +58 -0
  47. package/src/components/InventoryAssetDetail.vue +55 -0
  48. package/src/components/InventoryLocationAdmin.vue +56 -0
  49. package/src/components/InventoryLocationDetail.vue +51 -0
  50. package/src/components/InventoryOverview.vue +81 -0
  51. package/src/components/InventoryScanner.vue +315 -0
  52. package/src/components/QInventory.vue +117 -0
  53. package/src/index.ts +36 -0
  54. package/src/mixins/CSSMixin.ts +14 -0
  55. package/src/models/index.ts +114 -0
  56. package/src/models/inventoryAssetRepoAxios.ts +38 -0
  57. package/src/models/inventoryAuthRepoAxios.ts +48 -0
  58. package/src/models/inventoryAxiosBase.ts +149 -0
  59. package/src/models/inventoryLocationRepoAxios.ts +25 -0
  60. package/src/models/inventoryMovementRepoAxios.ts +24 -0
  61. package/src/styles/index.scss +148 -0
  62. package/stories/stories.scss +9 -0
  63. package/stories/stories.ts +362 -0
  64. package/tsconfig.json +29 -0
  65. package/vite.app.config.js +37 -0
  66. package/vite.config.js +68 -0
  67. package/vitest.config.js +11 -0
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <div class="q-inventory">
3
+ <div class="q-inventory__toolbar">
4
+ <button
5
+ v-for="item in tabs"
6
+ :key="item.key"
7
+ class="q-inventory__tab"
8
+ :class="{ 'q-inventory__tab--active': activeTab === item.key }"
9
+ type="button"
10
+ @click="activeTab = item.key"
11
+ >
12
+ {{ item.label }}
13
+ </button>
14
+ </div>
15
+ <inventory-scanner
16
+ v-show="activeTab === 'scanner'"
17
+ :asset-api-client="assetApiClient"
18
+ :assets="assets"
19
+ :current-user-code="currentUserCode"
20
+ @moved="onMoved"
21
+ @scan="onScan"
22
+ />
23
+ <inventory-overview
24
+ v-if="activeTab === 'overview'"
25
+ :asset-api-client="assetApiClient"
26
+ :assets="assets"
27
+ :locations="locations"
28
+ @select-asset="onSelectAsset"
29
+ @select-location="onSelectLocation"
30
+ />
31
+ <inventory-asset-detail
32
+ v-if="activeTab === 'asset'"
33
+ :movement-api-client="movementApiClient"
34
+ :asset="selectedAsset"
35
+ />
36
+ <inventory-location-detail
37
+ v-if="activeTab === 'location'"
38
+ :asset-api-client="assetApiClient"
39
+ :location="selectedLocation"
40
+ />
41
+ <inventory-location-admin
42
+ v-if="activeTab === 'locations'"
43
+ :locations="locations"
44
+ @save-location="onSaveLocation"
45
+ />
46
+ </div>
47
+ </template>
48
+
49
+ <script setup lang="ts">
50
+ import { computed, ref } from 'vue'
51
+ import InventoryScanner from './InventoryScanner.vue'
52
+ import InventoryOverview from './InventoryOverview.vue'
53
+ import InventoryAssetDetail from './InventoryAssetDetail.vue'
54
+ import InventoryLocationDetail from './InventoryLocationDetail.vue'
55
+ import InventoryLocationAdmin from './InventoryLocationAdmin.vue'
56
+ import type {
57
+ InventoryAssetApiClient,
58
+ InventoryLocationApiClient,
59
+ InventoryMovementApiClient,
60
+ } from '../models/index.js'
61
+
62
+ defineOptions({
63
+ name: 'q-inventory',
64
+ })
65
+
66
+ const props = defineProps<{
67
+ assetApiClient?: InventoryAssetApiClient
68
+ locationApiClient?: InventoryLocationApiClient
69
+ movementApiClient?: InventoryMovementApiClient
70
+ assets?: unknown[]
71
+ currentUserCode?: string
72
+ locations?: unknown[]
73
+ }>()
74
+
75
+ const emit = defineEmits<{
76
+ moved: [payload: unknown]
77
+ saveLocation: [payload: unknown]
78
+ scan: [payload: unknown]
79
+ selectAsset: [payload: unknown]
80
+ selectLocation: [payload: unknown]
81
+ }>()
82
+
83
+ const activeTab = ref('scanner')
84
+ const selectedAsset = ref<unknown>(null)
85
+ const selectedLocation = ref<unknown>(null)
86
+ const tabs = computed(() => [
87
+ { key: 'scanner', label: 'Scan' },
88
+ { key: 'overview', label: 'Overview' },
89
+ { key: 'asset', label: 'Asset' },
90
+ { key: 'location', label: 'Location' },
91
+ { key: 'locations', label: 'Locations' },
92
+ ])
93
+
94
+ function onMoved(payload: unknown) {
95
+ emit('moved', payload)
96
+ }
97
+
98
+ function onScan(payload: unknown) {
99
+ emit('scan', payload)
100
+ }
101
+
102
+ function onSaveLocation(payload: unknown) {
103
+ emit('saveLocation', payload)
104
+ }
105
+
106
+ function onSelectAsset(payload: unknown) {
107
+ selectedAsset.value = payload
108
+ activeTab.value = 'asset'
109
+ emit('selectAsset', payload)
110
+ }
111
+
112
+ function onSelectLocation(payload: unknown) {
113
+ selectedLocation.value = payload
114
+ activeTab.value = 'location'
115
+ emit('selectLocation', payload)
116
+ }
117
+ </script>
package/src/index.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { App } from 'vue'
2
+ import QInventory from './components/QInventory.vue'
3
+ import InventoryScanner from './components/InventoryScanner.vue'
4
+ import InventoryOverview from './components/InventoryOverview.vue'
5
+ import InventoryAssetDetail from './components/InventoryAssetDetail.vue'
6
+ import InventoryLocationDetail from './components/InventoryLocationDetail.vue'
7
+ import InventoryLocationAdmin from './components/InventoryLocationAdmin.vue'
8
+ import './styles/index.scss'
9
+
10
+ export * from '@questwork/q-inventory-model'
11
+ export * from './models/index.js'
12
+
13
+ function install(app: App) {
14
+ app.component('q-inventory', QInventory)
15
+ app.component('inventory-scanner', InventoryScanner)
16
+ app.component('inventory-overview', InventoryOverview)
17
+ app.component('inventory-asset-detail', InventoryAssetDetail)
18
+ app.component('inventory-location-detail', InventoryLocationDetail)
19
+ app.component('inventory-location-admin', InventoryLocationAdmin)
20
+ }
21
+
22
+ QInventory.install = install
23
+
24
+ export {
25
+ install,
26
+ InventoryAssetDetail,
27
+ InventoryLocationAdmin,
28
+ InventoryLocationDetail,
29
+ InventoryOverview,
30
+ InventoryScanner,
31
+ QInventory,
32
+ }
33
+
34
+ export default {
35
+ install,
36
+ }
@@ -0,0 +1,14 @@
1
+ export interface CssBlock {
2
+ block?: string
3
+ element?: string
4
+ modifier?: string
5
+ }
6
+
7
+ export function getCssBlock(base: string, css?: CssBlock): string[] {
8
+ return [
9
+ base,
10
+ css?.block,
11
+ css?.element,
12
+ css?.modifier,
13
+ ].filter(Boolean) as string[]
14
+ }
@@ -0,0 +1,114 @@
1
+ import { InventoryAssetRepoAxios } from './inventoryAssetRepoAxios.js'
2
+ import { InventoryLocationRepoAxios } from './inventoryLocationRepoAxios.js'
3
+ import { InventoryMovementRepoAxios } from './inventoryMovementRepoAxios.js'
4
+ import type { InventoryRepoAxiosOptions } from './inventoryAxiosBase.js'
5
+
6
+ export interface InventoryAssetApiClient {
7
+ findAll?: (query?: Record<string, unknown>) => Promise<unknown[]>
8
+ findOne?: (id: string) => Promise<unknown[]>
9
+ saveOne?: (doc: Record<string, unknown>) => Promise<unknown>
10
+ move?: (payload: InventoryMovePayload) => Promise<unknown>
11
+ }
12
+
13
+ export interface InventoryLocationApiClient {
14
+ findAll?: (query?: Record<string, unknown>) => Promise<unknown[]>
15
+ findOne?: (id: string) => Promise<unknown[]>
16
+ saveOne?: (doc: Record<string, unknown>) => Promise<unknown>
17
+ }
18
+
19
+ export interface InventoryMovementApiClient {
20
+ findAll?: (query?: Record<string, unknown>) => Promise<unknown[]>
21
+ }
22
+
23
+ // Backward-compatible union type for consumers that still expect one object
24
+ export type InventoryApiClient = InventoryAssetApiClient & InventoryLocationApiClient & InventoryMovementApiClient
25
+
26
+ export interface InventoryMovePayload {
27
+ assetTag: string
28
+ fromLocationCode?: string
29
+ fromLocationKind?: string
30
+ movedBy?: string
31
+ toLocationCode: string
32
+ toLocationKind?: string
33
+ movementType?: string
34
+ }
35
+
36
+ export interface InventoryMoveWarning {
37
+ actualLocationCode?: string
38
+ assetTag?: string
39
+ code: string
40
+ message: string
41
+ requestedFromLocationCode?: string
42
+ requestedFromLocationKind?: string
43
+ }
44
+
45
+ export interface InventoryMoveResult {
46
+ warnings?: InventoryMoveWarning[]
47
+ }
48
+
49
+ export interface InventoryDisplayLocation {
50
+ code: string
51
+ label: string
52
+ type?: string
53
+ }
54
+
55
+ export * from './inventoryAxiosBase.js'
56
+ export * from './inventoryAssetRepoAxios.js'
57
+ export * from './inventoryAuthRepoAxios.js'
58
+ export * from './inventoryLocationRepoAxios.js'
59
+ export * from './inventoryMovementRepoAxios.js'
60
+
61
+ // Backward-compat wrapper so existing callers don't break immediately.
62
+ // Internally it delegates to the focused repos above.
63
+ export class InventoryRepoAxios implements InventoryApiClient {
64
+ private assetRepo: InventoryAssetRepoAxios
65
+ private locationRepo: InventoryLocationRepoAxios
66
+ private movementRepo: InventoryMovementRepoAxios
67
+
68
+ constructor(options: InventoryRepoAxiosOptions = {}) {
69
+ this.assetRepo = new InventoryAssetRepoAxios(options)
70
+ this.locationRepo = new InventoryLocationRepoAxios(options)
71
+ this.movementRepo = new InventoryMovementRepoAxios(options)
72
+ }
73
+
74
+ async findAll(query?: Record<string, unknown>) {
75
+ return this.assetRepo.findAll(query)
76
+ }
77
+
78
+ async findOne(id: string) {
79
+ return this.assetRepo.findOne(id)
80
+ }
81
+
82
+ async saveOne(doc: Record<string, unknown>) {
83
+ return this.assetRepo.saveOne(doc)
84
+ }
85
+
86
+ async move(payload: InventoryMovePayload) {
87
+ return this.assetRepo.move(payload)
88
+ }
89
+
90
+ // Legacy alias for asset findAll
91
+ async findAssets(query?: Record<string, unknown>) {
92
+ return this.assetRepo.findAll(query)
93
+ }
94
+
95
+ // Legacy alias for location findAll
96
+ async findLocations(query?: Record<string, unknown>) {
97
+ return this.locationRepo.findAll(query)
98
+ }
99
+
100
+ // Legacy alias — location assets are just assets filtered by currentLocationCode
101
+ async findLocationAssets(locationCode: string) {
102
+ return this.assetRepo.findAll({ currentLocationCode: locationCode })
103
+ }
104
+
105
+ // Legacy alias for movement history
106
+ async findAssetHistory(assetTag: string) {
107
+ return this.movementRepo.findAll({ assetTag })
108
+ }
109
+
110
+ // Legacy alias for move
111
+ async moveAsset(payload: InventoryMovePayload) {
112
+ return this.assetRepo.move(payload)
113
+ }
114
+ }
@@ -0,0 +1,38 @@
1
+ import { InventoryRepoAxiosBase, type InventoryRepoAxiosOptions } from './inventoryAxiosBase.js'
2
+ import type { InventoryMovePayload } from './index.js'
3
+
4
+ export interface InventoryAssetFindAllQuery extends Record<string, unknown> {
5
+ assetTag?: string
6
+ assetTags?: string[]
7
+ category?: string
8
+ currentLocationCode?: string
9
+ currentLocationKind?: string
10
+ currentLocationType?: string
11
+ }
12
+
13
+ export class InventoryAssetRepoAxios extends InventoryRepoAxiosBase {
14
+ constructor(options: InventoryRepoAxiosOptions = {}) {
15
+ super(options)
16
+ }
17
+
18
+ async findAll(query: InventoryAssetFindAllQuery = {}) {
19
+ return this.get('/qInventoryAsset', this.withDefaults(query))
20
+ }
21
+
22
+ async findOne(assetTag: string) {
23
+ return this.get(`/qInventoryAsset/${encodeURIComponent(assetTag)}`, this.withDefaults())
24
+ }
25
+
26
+ async saveOne(doc: Record<string, unknown>) {
27
+ return this.post('/qInventoryAsset', this.withDefaults(doc))
28
+ }
29
+
30
+ async move(payload: InventoryMovePayload) {
31
+ const assetTag = String(payload.assetTag || '')
32
+ const movedBy = payload.movedBy || this.movedBy
33
+ return this.post(`/qInventoryAsset/${encodeURIComponent(assetTag)}/move`, this.withDefaults({
34
+ ...payload,
35
+ movedBy,
36
+ }))
37
+ }
38
+ }
@@ -0,0 +1,48 @@
1
+ import { InventoryRepoAxiosBase, type InventoryRepoAxiosOptions } from './inventoryAxiosBase.js'
2
+
3
+ export interface InventoryTokenResult {
4
+ token: string
5
+ tokenExpiry: number
6
+ }
7
+
8
+ export interface InventoryLoginLinkResult {
9
+ email: string
10
+ loginUrl: string
11
+ sent: boolean
12
+ }
13
+
14
+ export interface InventoryUserTokenResult {
15
+ email: string
16
+ tenantCode: string
17
+ userCode: string
18
+ userToken: string
19
+ }
20
+
21
+ export class InventoryAuthRepoAxios extends InventoryRepoAxiosBase {
22
+ constructor(options: InventoryRepoAxiosOptions = {}) {
23
+ super(options)
24
+ }
25
+
26
+ async getBrowserToken() {
27
+ const result = await this.post('/qInventory/token', {
28
+ clientAppId: 'qInventoryBrowser',
29
+ clientAppName: 'qInventoryBrowser',
30
+ }) as { data?: InventoryTokenResult[] }
31
+ return result.data?.[0]
32
+ }
33
+
34
+ async requestLoginLink({ email, tenantCode = this.tenantCode }: { email: string, tenantCode?: string }) {
35
+ const result = await this.post('/qInventory/login-link', {
36
+ email,
37
+ tenantCode,
38
+ }) as { data?: InventoryLoginLinkResult[] }
39
+ return result.data?.[0]
40
+ }
41
+
42
+ async verifyLoginToken(loginToken: string) {
43
+ const result = await this.post('/qInventory/login-token/verify', {
44
+ loginToken,
45
+ }) as { data?: InventoryUserTokenResult[] }
46
+ return result.data?.[0]
47
+ }
48
+ }
@@ -0,0 +1,149 @@
1
+ export interface InventoryAxiosLike {
2
+ get: (url: string, config?: { headers?: Record<string, string>, params?: Record<string, unknown> }) => Promise<{ data: unknown }>
3
+ post: (url: string, body?: Record<string, unknown>, config?: { headers?: Record<string, string> }) => Promise<{ data: unknown }>
4
+ }
5
+
6
+ export interface InventoryRepoAxiosOptions {
7
+ axios?: InventoryAxiosLike
8
+ authenticatorTokenProvider?: () => Promise<string> | string
9
+ baseURL?: string
10
+ movedBy?: string
11
+ qRepo?: string
12
+ tenantCode?: string
13
+ userTokenProvider?: () => Promise<string> | string
14
+ }
15
+
16
+ export abstract class InventoryRepoAxiosBase {
17
+ axios?: InventoryAxiosLike
18
+ authenticatorTokenProvider?: () => Promise<string> | string
19
+ baseURL: string
20
+ movedBy: string
21
+ qRepo?: string
22
+ tenantCode: string
23
+ userTokenProvider?: () => Promise<string> | string
24
+
25
+ constructor(options: InventoryRepoAxiosOptions = {}) {
26
+ this.axios = options.axios
27
+ this.authenticatorTokenProvider = options.authenticatorTokenProvider
28
+ this.baseURL = (options.baseURL || '').replace(/\/$/, '')
29
+ this.movedBy = options.movedBy || 'currentUser'
30
+ this.qRepo = options.qRepo
31
+ this.tenantCode = options.tenantCode || 'tenantCode'
32
+ this.userTokenProvider = options.userTokenProvider
33
+ }
34
+
35
+ protected async get(path: string, query: Record<string, unknown> = {}) {
36
+ const headers = await this.getAuthHeaders()
37
+ if (this.axios) {
38
+ const response = await this.axios.get(this.getUrl(path), { headers, params: query })
39
+ return unwrapQSystemData(response.data)
40
+ }
41
+ const url = new URL(this.getUrl(path), getUrlBase())
42
+ Object.entries(query).forEach(([key, value]) => {
43
+ if (typeof value !== 'undefined' && value !== null && value !== '') {
44
+ url.searchParams.set(key, String(value))
45
+ }
46
+ })
47
+ const response = await fetch(this.getFetchUrl(url), { headers })
48
+ return unwrapQSystemData(await response.json(), response)
49
+ }
50
+
51
+ protected async post(path: string, body: Record<string, unknown> = {}) {
52
+ const headers = {
53
+ 'Content-Type': 'application/json',
54
+ ...await this.getAuthHeaders(),
55
+ }
56
+ if (this.axios) {
57
+ const response = await this.axios.post(this.getUrl(path), body, { headers })
58
+ return unwrapQSystemResponse(response.data)
59
+ }
60
+ const response = await fetch(this.getUrl(path), {
61
+ body: JSON.stringify(body),
62
+ headers,
63
+ method: 'POST',
64
+ })
65
+ return unwrapQSystemResponse(await response.json(), response)
66
+ }
67
+
68
+ protected getUrl(path: string) {
69
+ return `${this.baseURL}/api/v1${path}`
70
+ }
71
+
72
+ protected getFetchUrl(url: URL) {
73
+ return this.baseURL ? url.toString() : `${url.pathname}${url.search}`
74
+ }
75
+
76
+ protected withDefaults(obj: Record<string, unknown> = {}) {
77
+ return removeEmptyValues({
78
+ ...obj,
79
+ qRepo: obj.qRepo || this.qRepo,
80
+ tenantCode: obj.tenantCode || this.tenantCode,
81
+ })
82
+ }
83
+
84
+ private async getAuthHeaders() {
85
+ const headers: Record<string, string> = {}
86
+ const authenticatorToken = await resolveTokenProvider(this.authenticatorTokenProvider)
87
+ const userToken = await resolveTokenProvider(this.userTokenProvider)
88
+ if (authenticatorToken) {
89
+ headers['X-Q-Authenticator'] = authenticatorToken
90
+ }
91
+ if (userToken) {
92
+ headers['X-Q-Inventory-User-Token'] = userToken
93
+ }
94
+ return headers
95
+ }
96
+ }
97
+
98
+ async function resolveTokenProvider(provider?: () => Promise<string> | string) {
99
+ if (!provider) {
100
+ return ''
101
+ }
102
+ return provider()
103
+ }
104
+
105
+ function unwrapQSystemResponse(json: unknown, response?: Response) {
106
+ const data = json as Record<string, unknown>
107
+ const err = data?.err as Record<string, unknown> | undefined
108
+ const errors = data?.errors as Record<string, unknown> | undefined
109
+ if ((response && !response.ok) || err?.hasError || errors) {
110
+ throw new Error(getResponseErrorMessage({ data, err, errors, response }))
111
+ }
112
+ return data
113
+ }
114
+
115
+ function unwrapQSystemData(json: unknown, response?: Response): unknown[] {
116
+ const data = unwrapQSystemResponse(json, response)
117
+ return Array.isArray(data?.data) ? data.data : []
118
+ }
119
+
120
+ function getUrlBase() {
121
+ if (typeof window !== 'undefined') {
122
+ return window.location.origin
123
+ }
124
+ return 'http://localhost'
125
+ }
126
+
127
+ function getResponseErrorMessage({ data, err, errors, response }: {
128
+ data: Record<string, unknown>
129
+ err?: Record<string, unknown>
130
+ errors?: Record<string, unknown>
131
+ response?: Response
132
+ }) {
133
+ const code = String(response?.status || err?.statusCode || err?.code || errors?.name || data?.code || 'REQUEST_ERROR')
134
+ const message = formatErrorMessage(err?.message || errors?.message || data?.message || 'QInventory request failed')
135
+ return `${code}: ${message}`
136
+ }
137
+
138
+ function formatErrorMessage(value: unknown) {
139
+ if (Array.isArray(value)) {
140
+ return value.join('; ')
141
+ }
142
+ return String(value)
143
+ }
144
+
145
+ function removeEmptyValues(obj: Record<string, unknown>) {
146
+ return Object.fromEntries(
147
+ Object.entries(obj).filter(([, value]) => typeof value !== 'undefined' && value !== null && value !== ''),
148
+ )
149
+ }
@@ -0,0 +1,25 @@
1
+ import { InventoryRepoAxiosBase, type InventoryRepoAxiosOptions } from './inventoryAxiosBase.js'
2
+
3
+ export interface InventoryLocationFindAllQuery extends Record<string, unknown> {
4
+ locationCode?: string
5
+ locationType?: string
6
+ parentLocationCode?: string
7
+ }
8
+
9
+ export class InventoryLocationRepoAxios extends InventoryRepoAxiosBase {
10
+ constructor(options: InventoryRepoAxiosOptions = {}) {
11
+ super(options)
12
+ }
13
+
14
+ async findAll(query: InventoryLocationFindAllQuery = {}) {
15
+ return this.get('/qInventoryLocation', this.withDefaults(query))
16
+ }
17
+
18
+ async findOne(locationCode: string) {
19
+ return this.get(`/qInventoryLocation/${encodeURIComponent(locationCode)}`, this.withDefaults())
20
+ }
21
+
22
+ async saveOne(doc: Record<string, unknown>) {
23
+ return this.post('/qInventoryLocation', this.withDefaults(doc))
24
+ }
25
+ }
@@ -0,0 +1,24 @@
1
+ import { InventoryRepoAxiosBase, type InventoryRepoAxiosOptions } from './inventoryAxiosBase.js'
2
+
3
+ export interface InventoryMovementFindAllQuery extends Record<string, unknown> {
4
+ assetTag?: string
5
+ fromLocationCode?: string
6
+ toLocationCode?: string
7
+ movedBy?: string
8
+ movementType?: string
9
+ }
10
+
11
+ export class InventoryMovementRepoAxios extends InventoryRepoAxiosBase {
12
+ constructor(options: InventoryRepoAxiosOptions = {}) {
13
+ super(options)
14
+ }
15
+
16
+ async findAll(query: InventoryMovementFindAllQuery = {}) {
17
+ const assetTag = String(query.assetTag || '')
18
+ if (!assetTag) {
19
+ throw new Error('assetTag is required for movement findAll')
20
+ }
21
+ const { assetTag: _omit, ...rest } = query
22
+ return this.get(`/qInventoryAsset/${encodeURIComponent(assetTag)}/movements`, this.withDefaults(rest))
23
+ }
24
+ }