@managemint-solutions/sdk 0.65.0 → 0.67.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.
- package/dist/audit/index.d.ts +1 -1
- package/dist/audit/index.js +14 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +14 -0
- package/dist/documents/content.js +6 -1
- package/dist/documents/document-templates.js +15 -5
- package/dist/documents/documents.d.ts +14 -5
- package/dist/documents/documents.js +14 -2
- package/dist/documents/dto.d.ts +12 -2
- package/dist/documents/dto.js +61 -1
- package/dist/documents/render/html.d.ts +5 -0
- package/dist/documents/render/html.js +10 -3
- package/dist/documents/render/index.d.ts +1 -1
- package/dist/documents/render/index.js +3 -1
- package/dist/documents/render/values.d.ts +12 -5
- package/dist/documents/render/values.js +16 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +32 -3
- package/dist/inventory/dto.d.ts +134 -0
- package/dist/inventory/dto.js +684 -0
- package/dist/inventory/index.d.ts +7 -0
- package/dist/inventory/index.js +30 -0
- package/dist/inventory/items.d.ts +65 -0
- package/dist/inventory/items.js +216 -0
- package/dist/inventory/levels.d.ts +52 -0
- package/dist/inventory/levels.js +157 -0
- package/dist/inventory/locations.d.ts +29 -0
- package/dist/inventory/locations.js +158 -0
- package/dist/inventory/movements.d.ts +36 -0
- package/dist/inventory/movements.js +103 -0
- package/dist/inventory/stocktakes.d.ts +36 -0
- package/dist/inventory/stocktakes.js +70 -0
- package/dist/inventory/transfers.d.ts +73 -0
- package/dist/inventory/transfers.js +134 -0
- package/dist/organizations/dto.d.ts +3 -1
- package/dist/organizations/dto.js +12 -0
- package/dist/organizations/index.js +2 -0
- package/dist/permissions/dto.d.ts +4 -0
- package/dist/permissions/dto.js +20 -0
- package/dist/service-client/index.d.ts +6 -0
- package/dist/service-client/index.js +10 -0
- package/dist/users/paths.d.ts +5 -6
- package/dist/users/paths.js +12 -12
- package/package.json +2 -2
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InventoryLocationsResource = void 0;
|
|
4
|
+
const enum_1 = require("@managemint-solutions/entities/audit-trail/enum");
|
|
5
|
+
const audit_1 = require("../audit");
|
|
6
|
+
const errors_1 = require("../errors");
|
|
7
|
+
const transforms_1 = require("../transforms");
|
|
8
|
+
const LOCATION_SELECT = `
|
|
9
|
+
mms_id,
|
|
10
|
+
created_at,
|
|
11
|
+
created_by:user_profiles!created_by ${transforms_1.USER_PROFILE_EMBED},
|
|
12
|
+
updated_at,
|
|
13
|
+
last_updated_by:user_profiles!last_updated_by ${transforms_1.USER_PROFILE_EMBED},
|
|
14
|
+
name,
|
|
15
|
+
kind,
|
|
16
|
+
address,
|
|
17
|
+
latitude,
|
|
18
|
+
longitude,
|
|
19
|
+
is_default,
|
|
20
|
+
archived,
|
|
21
|
+
archived_at,
|
|
22
|
+
archived_by:user_profiles!archived_by ${transforms_1.USER_PROFILE_EMBED},
|
|
23
|
+
unarchived_at,
|
|
24
|
+
unarchived_by:user_profiles!unarchived_by ${transforms_1.USER_PROFILE_EMBED}
|
|
25
|
+
`;
|
|
26
|
+
/** `latitude` and `longitude` are numeric columns, which PostgREST sends as strings. */
|
|
27
|
+
const asLocation = (raw) => {
|
|
28
|
+
const row = raw;
|
|
29
|
+
return {
|
|
30
|
+
...row,
|
|
31
|
+
latitude: row.latitude === null ? null : Number(row.latitude),
|
|
32
|
+
longitude: row.longitude === null ? null : Number(row.longitude),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Where stock is kept. A handful of rows per organization, read whole (no paging). One of them
|
|
37
|
+
* is the default: `is_default` moves between rows and is never cleared, so an organization
|
|
38
|
+
* with any location has one (the api stars the first location added; nothing is seeded).
|
|
39
|
+
*/
|
|
40
|
+
class InventoryLocationsResource {
|
|
41
|
+
supabase;
|
|
42
|
+
auth;
|
|
43
|
+
table;
|
|
44
|
+
audit;
|
|
45
|
+
constructor(supabase, auth, audit) {
|
|
46
|
+
this.supabase = supabase;
|
|
47
|
+
this.auth = auth;
|
|
48
|
+
this.table = (0, audit_1.auditedTable)(supabase, auth, audit, {
|
|
49
|
+
table: 'inventory_locations',
|
|
50
|
+
entityType: enum_1.AuditTrailEntityType.INVENTORY_LOCATIONS,
|
|
51
|
+
});
|
|
52
|
+
this.audit = new audit_1.EntityAuditTrail(supabase, auth, enum_1.AuditTrailEntityType.INVENTORY_LOCATIONS);
|
|
53
|
+
}
|
|
54
|
+
/** The organization's locations, the default first, then by name. */
|
|
55
|
+
async list(archive) {
|
|
56
|
+
const { data, error } = await this.supabase
|
|
57
|
+
.from('inventory_locations')
|
|
58
|
+
.select(LOCATION_SELECT)
|
|
59
|
+
.eq('organization_id', this.auth.organization_id)
|
|
60
|
+
.eq('archived', archive)
|
|
61
|
+
.order('is_default', { ascending: false })
|
|
62
|
+
.order('name');
|
|
63
|
+
if (error)
|
|
64
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
65
|
+
return (data ?? []).map(asLocation);
|
|
66
|
+
}
|
|
67
|
+
async getById(locationId) {
|
|
68
|
+
const { data, error } = await this.supabase
|
|
69
|
+
.from('inventory_locations')
|
|
70
|
+
.select(LOCATION_SELECT)
|
|
71
|
+
.eq('organization_id', this.auth.organization_id)
|
|
72
|
+
.eq('mms_id', locationId)
|
|
73
|
+
.single();
|
|
74
|
+
if (error)
|
|
75
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
76
|
+
return asLocation(data);
|
|
77
|
+
}
|
|
78
|
+
/** The default location's id; null while the organization has no location yet. */
|
|
79
|
+
async defaultId() {
|
|
80
|
+
const { data, error } = await this.supabase
|
|
81
|
+
.from('inventory_locations')
|
|
82
|
+
.select('mms_id')
|
|
83
|
+
.eq('organization_id', this.auth.organization_id)
|
|
84
|
+
.eq('is_default', true)
|
|
85
|
+
.maybeSingle();
|
|
86
|
+
if (error)
|
|
87
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
88
|
+
return data?.mms_id ?? null;
|
|
89
|
+
}
|
|
90
|
+
async create(input) {
|
|
91
|
+
if (input.is_default)
|
|
92
|
+
await this.clearDefault();
|
|
93
|
+
const row = await this.table.insert({
|
|
94
|
+
name: input.name,
|
|
95
|
+
kind: input.kind,
|
|
96
|
+
address: input.address ?? '',
|
|
97
|
+
latitude: input.latitude ?? null,
|
|
98
|
+
longitude: input.longitude ?? null,
|
|
99
|
+
is_default: input.is_default === true,
|
|
100
|
+
organization_id: this.auth.organization_id,
|
|
101
|
+
created_by: this.auth.mms_id,
|
|
102
|
+
created_at: new Date().toISOString(),
|
|
103
|
+
});
|
|
104
|
+
return this.getById(row.mms_id);
|
|
105
|
+
}
|
|
106
|
+
async update(locationId, input) {
|
|
107
|
+
// The star moves; it is never taken away. Making another location the default clears this one.
|
|
108
|
+
if (input.is_default === false) {
|
|
109
|
+
const current = await this.getById(locationId);
|
|
110
|
+
if (current.is_default) {
|
|
111
|
+
throw new errors_1.SupabaseClientError({
|
|
112
|
+
status: 400,
|
|
113
|
+
error: 'Database Error',
|
|
114
|
+
message: 'Make another location the default first',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (input.is_default)
|
|
119
|
+
await this.clearDefault(locationId);
|
|
120
|
+
await this.table.update(locationId, {
|
|
121
|
+
name: input.name,
|
|
122
|
+
kind: input.kind,
|
|
123
|
+
address: input.address ?? '',
|
|
124
|
+
latitude: input.latitude ?? null,
|
|
125
|
+
longitude: input.longitude ?? null,
|
|
126
|
+
...(input.is_default !== undefined && { is_default: input.is_default }),
|
|
127
|
+
last_updated_by: this.auth.mms_id,
|
|
128
|
+
updated_at: new Date().toISOString(),
|
|
129
|
+
});
|
|
130
|
+
return this.getById(locationId);
|
|
131
|
+
}
|
|
132
|
+
async archive(locationId) {
|
|
133
|
+
await this.table.transition(locationId, enum_1.AuditTrailAction.ARCHIVE, (0, transforms_1.archivedPatch)(this.auth.mms_id));
|
|
134
|
+
return this.getById(locationId);
|
|
135
|
+
}
|
|
136
|
+
async unarchive(locationId) {
|
|
137
|
+
await this.table.transition(locationId, enum_1.AuditTrailAction.UNARCHIVE, (0, transforms_1.unarchivedPatch)(this.auth.mms_id));
|
|
138
|
+
return this.getById(locationId);
|
|
139
|
+
}
|
|
140
|
+
/** Hard delete. The levels' and transfers' `on delete restrict` keys refuse a location still in use (23503 -> 409). */
|
|
141
|
+
async remove(locationId) {
|
|
142
|
+
await this.table.remove(locationId);
|
|
143
|
+
}
|
|
144
|
+
/** Takes the star off the current default (other than `except`), so the unique index lets the next one have it. */
|
|
145
|
+
async clearDefault(except) {
|
|
146
|
+
let query = this.supabase
|
|
147
|
+
.from('inventory_locations')
|
|
148
|
+
.update({ is_default: false })
|
|
149
|
+
.eq('organization_id', this.auth.organization_id)
|
|
150
|
+
.eq('is_default', true);
|
|
151
|
+
if (except)
|
|
152
|
+
query = query.neq('mms_id', except);
|
|
153
|
+
const { error } = await query;
|
|
154
|
+
if (error)
|
|
155
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
exports.InventoryLocationsResource = InventoryLocationsResource;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { MmsList } from '@managemint-solutions/entities/common';
|
|
2
|
+
import type { InventoryMovementEntity } from '@managemint-solutions/entities/inventory';
|
|
3
|
+
import type { InventoryMovementType, InventoryReferenceType } from '@managemint-solutions/entities/inventory/enum';
|
|
4
|
+
import type { GetInventoryMovementsDto } from '@managemint-solutions/entities/inventory/dto';
|
|
5
|
+
import { type AuditActor, type AuditResource } from '../audit';
|
|
6
|
+
import type { TypedSupabaseClient } from '../client';
|
|
7
|
+
/** One ledger row as written: the signed quantity and what caused it. */
|
|
8
|
+
export type CreateInventoryMovementRow = {
|
|
9
|
+
item_id: string;
|
|
10
|
+
location_id: string;
|
|
11
|
+
type: InventoryMovementType;
|
|
12
|
+
quantity: number;
|
|
13
|
+
unit_cost_minor?: number | null;
|
|
14
|
+
reason?: string;
|
|
15
|
+
reference_type?: InventoryReferenceType | null;
|
|
16
|
+
reference_id?: string | null;
|
|
17
|
+
supplier?: string;
|
|
18
|
+
supplier_reference?: string;
|
|
19
|
+
notes?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The ledger. Insert and read only: the table has no update or delete grant, so a mistake is
|
|
23
|
+
* reversed by an opposite movement and both stay on record. The balance it moves is the level
|
|
24
|
+
* row, which the api updates right after the insert.
|
|
25
|
+
*/
|
|
26
|
+
export declare class InventoryMovementsResource {
|
|
27
|
+
private readonly supabase;
|
|
28
|
+
private readonly auth;
|
|
29
|
+
private readonly table;
|
|
30
|
+
constructor(supabase: TypedSupabaseClient, auth: AuditActor, audit: AuditResource);
|
|
31
|
+
/** A page of movements, newest first, narrowed by item, location or type. */
|
|
32
|
+
list(query: GetInventoryMovementsDto): Promise<MmsList<InventoryMovementEntity>>;
|
|
33
|
+
/** Whether the item has any history - the guard in front of deleting it. */
|
|
34
|
+
existsForItem(itemId: string): Promise<boolean>;
|
|
35
|
+
create(input: CreateInventoryMovementRow): Promise<string>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InventoryMovementsResource = void 0;
|
|
4
|
+
const enum_1 = require("@managemint-solutions/entities/audit-trail/enum");
|
|
5
|
+
const audit_1 = require("../audit");
|
|
6
|
+
const errors_1 = require("../errors");
|
|
7
|
+
const transforms_1 = require("../transforms");
|
|
8
|
+
const items_1 = require("./items");
|
|
9
|
+
const MOVEMENT_SELECT = `
|
|
10
|
+
mms_id,
|
|
11
|
+
created_at,
|
|
12
|
+
created_by:user_profiles!created_by ${transforms_1.USER_PROFILE_EMBED},
|
|
13
|
+
item:inventory_items!item_id (${items_1.ITEM_IDENTITY_SELECT}),
|
|
14
|
+
location:inventory_locations!location_id (mms_id, name),
|
|
15
|
+
type,
|
|
16
|
+
quantity,
|
|
17
|
+
unit_cost_minor,
|
|
18
|
+
reason,
|
|
19
|
+
reference_type,
|
|
20
|
+
reference_id,
|
|
21
|
+
supplier,
|
|
22
|
+
supplier_reference,
|
|
23
|
+
notes
|
|
24
|
+
`;
|
|
25
|
+
/**
|
|
26
|
+
* The ledger. Insert and read only: the table has no update or delete grant, so a mistake is
|
|
27
|
+
* reversed by an opposite movement and both stay on record. The balance it moves is the level
|
|
28
|
+
* row, which the api updates right after the insert.
|
|
29
|
+
*/
|
|
30
|
+
class InventoryMovementsResource {
|
|
31
|
+
supabase;
|
|
32
|
+
auth;
|
|
33
|
+
table;
|
|
34
|
+
constructor(supabase, auth, audit) {
|
|
35
|
+
this.supabase = supabase;
|
|
36
|
+
this.auth = auth;
|
|
37
|
+
this.table = (0, audit_1.auditedTable)(supabase, auth, audit, {
|
|
38
|
+
table: 'inventory_movements',
|
|
39
|
+
entityType: enum_1.AuditTrailEntityType.INVENTORY_MOVEMENTS,
|
|
40
|
+
related: { entityType: enum_1.AuditTrailEntityType.INVENTORY_ITEMS, column: 'item_id' },
|
|
41
|
+
identity: ['item_id', 'location_id', 'type', 'quantity'],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/** A page of movements, newest first, narrowed by item, location or type. */
|
|
45
|
+
async list(query) {
|
|
46
|
+
const page = query.page;
|
|
47
|
+
const pageSize = query.page_size;
|
|
48
|
+
const offset = (page - 1) * pageSize;
|
|
49
|
+
let movementsQuery = this.supabase
|
|
50
|
+
.from('inventory_movements')
|
|
51
|
+
.select(MOVEMENT_SELECT, { count: 'estimated' })
|
|
52
|
+
.eq('organization_id', this.auth.organization_id)
|
|
53
|
+
.order('created_at', { ascending: false })
|
|
54
|
+
.range(offset, offset + pageSize - 1);
|
|
55
|
+
if (query.item_id)
|
|
56
|
+
movementsQuery = movementsQuery.eq('item_id', query.item_id);
|
|
57
|
+
if (query.location_id)
|
|
58
|
+
movementsQuery = movementsQuery.eq('location_id', query.location_id);
|
|
59
|
+
if (query.type)
|
|
60
|
+
movementsQuery = movementsQuery.eq('type', query.type);
|
|
61
|
+
const { data, error, count } = await movementsQuery;
|
|
62
|
+
if (error)
|
|
63
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
64
|
+
const rows = (data ?? []).map((row) => ({
|
|
65
|
+
...row,
|
|
66
|
+
quantity: Number(row.quantity),
|
|
67
|
+
}));
|
|
68
|
+
return (0, transforms_1.toMmsList)(rows, page, pageSize, count);
|
|
69
|
+
}
|
|
70
|
+
/** Whether the item has any history - the guard in front of deleting it. */
|
|
71
|
+
async existsForItem(itemId) {
|
|
72
|
+
const { data, error } = await this.supabase
|
|
73
|
+
.from('inventory_movements')
|
|
74
|
+
.select('mms_id')
|
|
75
|
+
.eq('organization_id', this.auth.organization_id)
|
|
76
|
+
.eq('item_id', itemId)
|
|
77
|
+
.limit(1)
|
|
78
|
+
.maybeSingle();
|
|
79
|
+
if (error)
|
|
80
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
81
|
+
return data !== null;
|
|
82
|
+
}
|
|
83
|
+
async create(input) {
|
|
84
|
+
const row = await this.table.insert({
|
|
85
|
+
item_id: input.item_id,
|
|
86
|
+
location_id: input.location_id,
|
|
87
|
+
type: input.type,
|
|
88
|
+
quantity: input.quantity,
|
|
89
|
+
unit_cost_minor: input.unit_cost_minor ?? null,
|
|
90
|
+
reason: input.reason ?? '',
|
|
91
|
+
reference_type: input.reference_type ?? null,
|
|
92
|
+
reference_id: input.reference_id ?? null,
|
|
93
|
+
supplier: input.supplier ?? '',
|
|
94
|
+
supplier_reference: input.supplier_reference ?? '',
|
|
95
|
+
notes: input.notes ?? '',
|
|
96
|
+
organization_id: this.auth.organization_id,
|
|
97
|
+
created_by: this.auth.mms_id,
|
|
98
|
+
created_at: new Date().toISOString(),
|
|
99
|
+
});
|
|
100
|
+
return row.mms_id;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.InventoryMovementsResource = InventoryMovementsResource;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { MmsList } from '@managemint-solutions/entities/common';
|
|
2
|
+
import type { InventoryLocationIdentity, InventoryStocktakeLine } from '@managemint-solutions/entities/inventory';
|
|
3
|
+
import type { GetInventoryStocktakesDto } from '@managemint-solutions/entities/inventory/dto';
|
|
4
|
+
import type { UserIdentity } from '@managemint-solutions/entities/users';
|
|
5
|
+
import { type AuditActor, type AuditResource } from '../audit';
|
|
6
|
+
import type { TypedSupabaseClient } from '../client';
|
|
7
|
+
/** A stocktake as stored: its lines name items by id; the api reads the items alongside. */
|
|
8
|
+
export type InventoryStocktakeRow = {
|
|
9
|
+
mms_id: string;
|
|
10
|
+
created_at: string;
|
|
11
|
+
created_by: UserIdentity | null;
|
|
12
|
+
location: InventoryLocationIdentity;
|
|
13
|
+
lines: InventoryStocktakeLine[];
|
|
14
|
+
has_differences: boolean;
|
|
15
|
+
notes: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The record of each count of a location, matches included. Insert and read only: what was
|
|
19
|
+
* found on a day is never edited. The count movements it posts are the api's.
|
|
20
|
+
*/
|
|
21
|
+
export declare class InventoryStocktakesResource {
|
|
22
|
+
private readonly supabase;
|
|
23
|
+
private readonly auth;
|
|
24
|
+
private readonly table;
|
|
25
|
+
constructor(supabase: TypedSupabaseClient, auth: AuditActor, audit: AuditResource);
|
|
26
|
+
list(query: GetInventoryStocktakesDto & {
|
|
27
|
+
organization_id?: string | null;
|
|
28
|
+
}): Promise<MmsList<InventoryStocktakeRow>>;
|
|
29
|
+
getById(stocktakeId: string): Promise<InventoryStocktakeRow>;
|
|
30
|
+
create(input: {
|
|
31
|
+
location_id: string;
|
|
32
|
+
lines: InventoryStocktakeLine[];
|
|
33
|
+
has_differences: boolean;
|
|
34
|
+
notes: string;
|
|
35
|
+
}): Promise<InventoryStocktakeRow>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InventoryStocktakesResource = void 0;
|
|
4
|
+
const enum_1 = require("@managemint-solutions/entities/audit-trail/enum");
|
|
5
|
+
const audit_1 = require("../audit");
|
|
6
|
+
const errors_1 = require("../errors");
|
|
7
|
+
const tenancy_1 = require("../tenancy");
|
|
8
|
+
const transforms_1 = require("../transforms");
|
|
9
|
+
const STOCKTAKE_SELECT = `
|
|
10
|
+
mms_id,
|
|
11
|
+
created_at,
|
|
12
|
+
created_by:user_profiles!created_by ${transforms_1.USER_PROFILE_EMBED},
|
|
13
|
+
location:inventory_locations!location_id (mms_id, name),
|
|
14
|
+
lines,
|
|
15
|
+
has_differences,
|
|
16
|
+
notes
|
|
17
|
+
`;
|
|
18
|
+
/**
|
|
19
|
+
* The record of each count of a location, matches included. Insert and read only: what was
|
|
20
|
+
* found on a day is never edited. The count movements it posts are the api's.
|
|
21
|
+
*/
|
|
22
|
+
class InventoryStocktakesResource {
|
|
23
|
+
supabase;
|
|
24
|
+
auth;
|
|
25
|
+
table;
|
|
26
|
+
constructor(supabase, auth, audit) {
|
|
27
|
+
this.supabase = supabase;
|
|
28
|
+
this.auth = auth;
|
|
29
|
+
this.table = (0, audit_1.auditedTable)(supabase, auth, audit, {
|
|
30
|
+
table: 'inventory_stocktakes',
|
|
31
|
+
entityType: enum_1.AuditTrailEntityType.INVENTORY_STOCKTAKES,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async list(query) {
|
|
35
|
+
const page = query.page;
|
|
36
|
+
const pageSize = query.page_size;
|
|
37
|
+
const offset = (page - 1) * pageSize;
|
|
38
|
+
let stocktakesQuery = (0, tenancy_1.scopeToOrganization)(this.supabase
|
|
39
|
+
.from('inventory_stocktakes')
|
|
40
|
+
.select((0, tenancy_1.withOrganization)(STOCKTAKE_SELECT, this.auth), { count: 'estimated' }), this.auth)
|
|
41
|
+
.order('created_at', { ascending: false })
|
|
42
|
+
.range(offset, offset + pageSize - 1);
|
|
43
|
+
if (query.organization_id)
|
|
44
|
+
stocktakesQuery = stocktakesQuery.eq('organization_id', query.organization_id);
|
|
45
|
+
if (query.location_id)
|
|
46
|
+
stocktakesQuery = stocktakesQuery.eq('location_id', query.location_id);
|
|
47
|
+
const { data, error, count } = await stocktakesQuery;
|
|
48
|
+
if (error)
|
|
49
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
50
|
+
return (0, transforms_1.toMmsList)(data, page, pageSize, count);
|
|
51
|
+
}
|
|
52
|
+
async getById(stocktakeId) {
|
|
53
|
+
const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('inventory_stocktakes').select((0, tenancy_1.withOrganization)(STOCKTAKE_SELECT, this.auth)), this.auth)
|
|
54
|
+
.eq('mms_id', stocktakeId)
|
|
55
|
+
.single();
|
|
56
|
+
if (error)
|
|
57
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
58
|
+
return data;
|
|
59
|
+
}
|
|
60
|
+
async create(input) {
|
|
61
|
+
const row = await this.table.insert({
|
|
62
|
+
...input,
|
|
63
|
+
organization_id: this.auth.organization_id,
|
|
64
|
+
created_by: this.auth.mms_id,
|
|
65
|
+
created_at: new Date().toISOString(),
|
|
66
|
+
});
|
|
67
|
+
return this.getById(row.mms_id);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.InventoryStocktakesResource = InventoryStocktakesResource;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { MmsList } from '@managemint-solutions/entities/common';
|
|
2
|
+
import type { InventoryLocationIdentity, InventoryTransferLine } from '@managemint-solutions/entities/inventory';
|
|
3
|
+
import { InventoryTransferStatus } from '@managemint-solutions/entities/inventory/enum';
|
|
4
|
+
import type { GetInventoryTransfersDto } from '@managemint-solutions/entities/inventory/dto';
|
|
5
|
+
import type { UserIdentity } from '@managemint-solutions/entities/users';
|
|
6
|
+
import { EntityAuditTrail, type AuditActor, type AuditResource } from '../audit';
|
|
7
|
+
import type { TypedSupabaseClient } from '../client';
|
|
8
|
+
/** A transfer as stored: its lines name items by id; the api reads the items alongside. */
|
|
9
|
+
export type InventoryTransferRow = {
|
|
10
|
+
mms_id: string;
|
|
11
|
+
created_at: string;
|
|
12
|
+
created_by: UserIdentity | null;
|
|
13
|
+
updated_at: string | null;
|
|
14
|
+
last_updated_by: UserIdentity | null;
|
|
15
|
+
from_location: InventoryLocationIdentity;
|
|
16
|
+
to_location: InventoryLocationIdentity;
|
|
17
|
+
status: InventoryTransferStatus;
|
|
18
|
+
lines: InventoryTransferLine[];
|
|
19
|
+
notes: string;
|
|
20
|
+
sent_at: string | null;
|
|
21
|
+
sent_by: UserIdentity | null;
|
|
22
|
+
received_at: string | null;
|
|
23
|
+
received_by: UserIdentity | null;
|
|
24
|
+
cancelled_at: string | null;
|
|
25
|
+
cancelled_by: UserIdentity | null;
|
|
26
|
+
};
|
|
27
|
+
/** What a write may change: the lines, the notes, the status and its stamps. */
|
|
28
|
+
export type UpdateInventoryTransferRow = Partial<{
|
|
29
|
+
from_location_id: string;
|
|
30
|
+
to_location_id: string;
|
|
31
|
+
lines: InventoryTransferLine[];
|
|
32
|
+
notes: string;
|
|
33
|
+
status: InventoryTransferStatus;
|
|
34
|
+
sent_at: string;
|
|
35
|
+
sent_by: string | null;
|
|
36
|
+
received_at: string | null;
|
|
37
|
+
received_by: string | null;
|
|
38
|
+
cancelled_at: string;
|
|
39
|
+
cancelled_by: string | null;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Stock on its way between two locations. The lifecycle (draft -> in transit -> received, or
|
|
43
|
+
* cancelled) and the ledger rows each step posts are the api's; this is the row.
|
|
44
|
+
*/
|
|
45
|
+
export declare class InventoryTransfersResource {
|
|
46
|
+
private readonly supabase;
|
|
47
|
+
private readonly auth;
|
|
48
|
+
private readonly table;
|
|
49
|
+
readonly audit: EntityAuditTrail;
|
|
50
|
+
constructor(supabase: TypedSupabaseClient, auth: AuditActor, audit: AuditResource);
|
|
51
|
+
list(query: GetInventoryTransfersDto & {
|
|
52
|
+
organization_id?: string | null;
|
|
53
|
+
}): Promise<MmsList<InventoryTransferRow>>;
|
|
54
|
+
/** Every transfer in transit: what is incoming where. */
|
|
55
|
+
listInTransit(): Promise<InventoryTransferRow[]>;
|
|
56
|
+
/** Whether any transfer still names the location - the guard in front of deleting it. */
|
|
57
|
+
existsForLocation(locationId: string): Promise<boolean>;
|
|
58
|
+
getById(transferId: string): Promise<InventoryTransferRow>;
|
|
59
|
+
create(input: {
|
|
60
|
+
from_location_id: string;
|
|
61
|
+
to_location_id: string;
|
|
62
|
+
lines: InventoryTransferLine[];
|
|
63
|
+
notes: string;
|
|
64
|
+
}): Promise<InventoryTransferRow>;
|
|
65
|
+
update(transferId: string, patch: UpdateInventoryTransferRow): Promise<InventoryTransferRow>;
|
|
66
|
+
/**
|
|
67
|
+
* A status move that only lands while the transfer still reads `from`: two people sending the
|
|
68
|
+
* same draft at once post its stock out once. Resolves null when it had already moved on.
|
|
69
|
+
*/
|
|
70
|
+
updateWhere(transferId: string, from: InventoryTransferStatus, patch: UpdateInventoryTransferRow): Promise<InventoryTransferRow | null>;
|
|
71
|
+
/** Hard delete; the api allows it on a draft only. */
|
|
72
|
+
remove(transferId: string): Promise<void>;
|
|
73
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InventoryTransfersResource = void 0;
|
|
4
|
+
const enum_1 = require("@managemint-solutions/entities/audit-trail/enum");
|
|
5
|
+
const enum_2 = require("@managemint-solutions/entities/inventory/enum");
|
|
6
|
+
const audit_1 = require("../audit");
|
|
7
|
+
const errors_1 = require("../errors");
|
|
8
|
+
const tenancy_1 = require("../tenancy");
|
|
9
|
+
const transforms_1 = require("../transforms");
|
|
10
|
+
const TRANSFER_SELECT = `
|
|
11
|
+
mms_id,
|
|
12
|
+
created_at,
|
|
13
|
+
created_by:user_profiles!created_by ${transforms_1.USER_PROFILE_EMBED},
|
|
14
|
+
updated_at,
|
|
15
|
+
last_updated_by:user_profiles!last_updated_by ${transforms_1.USER_PROFILE_EMBED},
|
|
16
|
+
from_location:inventory_locations!from_location_id (mms_id, name),
|
|
17
|
+
to_location:inventory_locations!to_location_id (mms_id, name),
|
|
18
|
+
status,
|
|
19
|
+
lines,
|
|
20
|
+
notes,
|
|
21
|
+
sent_at,
|
|
22
|
+
sent_by:user_profiles!sent_by ${transforms_1.USER_PROFILE_EMBED},
|
|
23
|
+
received_at,
|
|
24
|
+
received_by:user_profiles!received_by ${transforms_1.USER_PROFILE_EMBED},
|
|
25
|
+
cancelled_at,
|
|
26
|
+
cancelled_by:user_profiles!cancelled_by ${transforms_1.USER_PROFILE_EMBED}
|
|
27
|
+
`;
|
|
28
|
+
/**
|
|
29
|
+
* Stock on its way between two locations. The lifecycle (draft -> in transit -> received, or
|
|
30
|
+
* cancelled) and the ledger rows each step posts are the api's; this is the row.
|
|
31
|
+
*/
|
|
32
|
+
class InventoryTransfersResource {
|
|
33
|
+
supabase;
|
|
34
|
+
auth;
|
|
35
|
+
table;
|
|
36
|
+
audit;
|
|
37
|
+
constructor(supabase, auth, audit) {
|
|
38
|
+
this.supabase = supabase;
|
|
39
|
+
this.auth = auth;
|
|
40
|
+
this.table = (0, audit_1.auditedTable)(supabase, auth, audit, {
|
|
41
|
+
table: 'inventory_transfers',
|
|
42
|
+
entityType: enum_1.AuditTrailEntityType.INVENTORY_TRANSFERS,
|
|
43
|
+
});
|
|
44
|
+
this.audit = new audit_1.EntityAuditTrail(supabase, auth, enum_1.AuditTrailEntityType.INVENTORY_TRANSFERS);
|
|
45
|
+
}
|
|
46
|
+
async list(query) {
|
|
47
|
+
const page = query.page;
|
|
48
|
+
const pageSize = query.page_size;
|
|
49
|
+
const offset = (page - 1) * pageSize;
|
|
50
|
+
let transfersQuery = (0, tenancy_1.scopeToOrganization)(this.supabase
|
|
51
|
+
.from('inventory_transfers')
|
|
52
|
+
.select((0, tenancy_1.withOrganization)(TRANSFER_SELECT, this.auth), { count: 'estimated' }), this.auth)
|
|
53
|
+
.order('created_at', { ascending: false })
|
|
54
|
+
.range(offset, offset + pageSize - 1);
|
|
55
|
+
if (query.organization_id)
|
|
56
|
+
transfersQuery = transfersQuery.eq('organization_id', query.organization_id);
|
|
57
|
+
if (query.status)
|
|
58
|
+
transfersQuery = transfersQuery.eq('status', query.status);
|
|
59
|
+
// The ids are `@IsUUID('7')`-validated by the DTO, so interpolating one is safe.
|
|
60
|
+
if (query.location_id) {
|
|
61
|
+
transfersQuery = transfersQuery.or(`from_location_id.eq.${query.location_id},to_location_id.eq.${query.location_id}`);
|
|
62
|
+
}
|
|
63
|
+
const { data, error, count } = await transfersQuery;
|
|
64
|
+
if (error)
|
|
65
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
66
|
+
return (0, transforms_1.toMmsList)(data, page, pageSize, count);
|
|
67
|
+
}
|
|
68
|
+
/** Every transfer in transit: what is incoming where. */
|
|
69
|
+
async listInTransit() {
|
|
70
|
+
return (0, transforms_1.allRows)((from, to) => this.supabase
|
|
71
|
+
.from('inventory_transfers')
|
|
72
|
+
.select(TRANSFER_SELECT)
|
|
73
|
+
.eq('organization_id', this.auth.organization_id)
|
|
74
|
+
.eq('status', enum_2.InventoryTransferStatus.IN_TRANSIT)
|
|
75
|
+
.order('mms_id')
|
|
76
|
+
.range(from, to));
|
|
77
|
+
}
|
|
78
|
+
/** Whether any transfer still names the location - the guard in front of deleting it. */
|
|
79
|
+
async existsForLocation(locationId) {
|
|
80
|
+
const { data, error } = await this.supabase
|
|
81
|
+
.from('inventory_transfers')
|
|
82
|
+
.select('mms_id')
|
|
83
|
+
.eq('organization_id', this.auth.organization_id)
|
|
84
|
+
.or(`from_location_id.eq.${locationId},to_location_id.eq.${locationId}`)
|
|
85
|
+
.limit(1)
|
|
86
|
+
.maybeSingle();
|
|
87
|
+
if (error)
|
|
88
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
89
|
+
return data !== null;
|
|
90
|
+
}
|
|
91
|
+
async getById(transferId) {
|
|
92
|
+
const { data, error } = await (0, tenancy_1.scopeToOrganization)(this.supabase.from('inventory_transfers').select((0, tenancy_1.withOrganization)(TRANSFER_SELECT, this.auth)), this.auth)
|
|
93
|
+
.eq('mms_id', transferId)
|
|
94
|
+
.single();
|
|
95
|
+
if (error)
|
|
96
|
+
throw (0, errors_1.mapPostgrestError)(error);
|
|
97
|
+
return data;
|
|
98
|
+
}
|
|
99
|
+
async create(input) {
|
|
100
|
+
const row = await this.table.insert({
|
|
101
|
+
...input,
|
|
102
|
+
status: enum_2.InventoryTransferStatus.DRAFT,
|
|
103
|
+
organization_id: this.auth.organization_id,
|
|
104
|
+
created_by: this.auth.mms_id,
|
|
105
|
+
created_at: new Date().toISOString(),
|
|
106
|
+
});
|
|
107
|
+
return this.getById(row.mms_id);
|
|
108
|
+
}
|
|
109
|
+
async update(transferId, patch) {
|
|
110
|
+
await this.table.update(transferId, {
|
|
111
|
+
...patch,
|
|
112
|
+
last_updated_by: this.auth.mms_id,
|
|
113
|
+
updated_at: new Date().toISOString(),
|
|
114
|
+
});
|
|
115
|
+
return this.getById(transferId);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A status move that only lands while the transfer still reads `from`: two people sending the
|
|
119
|
+
* same draft at once post its stock out once. Resolves null when it had already moved on.
|
|
120
|
+
*/
|
|
121
|
+
async updateWhere(transferId, from, patch) {
|
|
122
|
+
const row = await this.table.updateWhere(transferId, (query) => query.eq('status', from), {
|
|
123
|
+
...patch,
|
|
124
|
+
last_updated_by: this.auth.mms_id,
|
|
125
|
+
updated_at: new Date().toISOString(),
|
|
126
|
+
});
|
|
127
|
+
return row ? this.getById(transferId) : null;
|
|
128
|
+
}
|
|
129
|
+
/** Hard delete; the api allows it on a draft only. */
|
|
130
|
+
async remove(transferId) {
|
|
131
|
+
await this.table.remove(transferId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.InventoryTransfersResource = InventoryTransfersResource;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CreateOrganizationDto as CreateOrganizationDtoType, GetOrganizationsDto as GetOrganizationsDtoType, OrganizationIdDto as OrganizationIdDtoType, UpdateOrganizationDto as UpdateOrganizationDtoType } from '@managemint-solutions/entities/organization/dto';
|
|
2
|
-
import { OrganizationTypes } from '@managemint-solutions/entities/organization/enum';
|
|
2
|
+
import { OrganizationTypes, StockIssueTrigger } from '@managemint-solutions/entities/organization/enum';
|
|
3
3
|
export declare class CreateOrganizationDto implements CreateOrganizationDtoType {
|
|
4
4
|
readonly org_type: OrganizationTypes;
|
|
5
5
|
readonly org_name: string;
|
|
@@ -60,6 +60,8 @@ export declare class UpdateOrganizationDto implements UpdateOrganizationDtoType
|
|
|
60
60
|
readonly marketing_consent?: boolean;
|
|
61
61
|
readonly currency?: string;
|
|
62
62
|
readonly leave_calendar_start_month?: number;
|
|
63
|
+
readonly stock_issue_on?: StockIssueTrigger;
|
|
64
|
+
readonly allow_negative_stock?: boolean;
|
|
63
65
|
}
|
|
64
66
|
export declare class OrganizationIdDto implements OrganizationIdDtoType {
|
|
65
67
|
readonly organizationId: string;
|
|
@@ -226,6 +226,8 @@ class UpdateOrganizationDto {
|
|
|
226
226
|
marketing_consent;
|
|
227
227
|
currency;
|
|
228
228
|
leave_calendar_start_month;
|
|
229
|
+
stock_issue_on;
|
|
230
|
+
allow_negative_stock;
|
|
229
231
|
}
|
|
230
232
|
exports.UpdateOrganizationDto = UpdateOrganizationDto;
|
|
231
233
|
__decorate([
|
|
@@ -450,6 +452,16 @@ __decorate([
|
|
|
450
452
|
(0, class_validator_1.Max)(12, { message: 'Leave year start month must be between 1 and 12' }),
|
|
451
453
|
__metadata("design:type", Number)
|
|
452
454
|
], UpdateOrganizationDto.prototype, "leave_calendar_start_month", void 0);
|
|
455
|
+
__decorate([
|
|
456
|
+
(0, class_validator_1.IsOptional)(),
|
|
457
|
+
(0, class_validator_1.IsEnum)(enum_1.StockIssueTrigger, { message: 'Stock leaves on issue or on payment' }),
|
|
458
|
+
__metadata("design:type", String)
|
|
459
|
+
], UpdateOrganizationDto.prototype, "stock_issue_on", void 0);
|
|
460
|
+
__decorate([
|
|
461
|
+
(0, class_validator_1.IsOptional)(),
|
|
462
|
+
(0, class_validator_1.IsBoolean)({ message: 'Allow negative stock must be true or false' }),
|
|
463
|
+
__metadata("design:type", Boolean)
|
|
464
|
+
], UpdateOrganizationDto.prototype, "allow_negative_stock", void 0);
|
|
453
465
|
// No tenant route takes one - a tenant only ever reaches their own organization - so this is the
|
|
454
466
|
// admin api's `/organizations/:organizationId`.
|
|
455
467
|
class OrganizationIdDto {
|
|
@@ -36,6 +36,10 @@ export declare class UpdatePermissionsDto implements UpdatePermissionsDtoType {
|
|
|
36
36
|
readonly read_leave_requests: boolean;
|
|
37
37
|
readonly update_leave_requests: boolean;
|
|
38
38
|
readonly delete_leave_requests: boolean;
|
|
39
|
+
readonly create_inventory: boolean;
|
|
40
|
+
readonly read_inventory: boolean;
|
|
41
|
+
readonly update_inventory: boolean;
|
|
42
|
+
readonly delete_inventory: boolean;
|
|
39
43
|
readonly read_invoices: boolean;
|
|
40
44
|
readonly create_users: boolean;
|
|
41
45
|
readonly read_users: boolean;
|