@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.
Files changed (44) hide show
  1. package/dist/audit/index.d.ts +1 -1
  2. package/dist/audit/index.js +14 -0
  3. package/dist/client.d.ts +7 -0
  4. package/dist/client.js +14 -0
  5. package/dist/documents/content.js +6 -1
  6. package/dist/documents/document-templates.js +15 -5
  7. package/dist/documents/documents.d.ts +14 -5
  8. package/dist/documents/documents.js +14 -2
  9. package/dist/documents/dto.d.ts +12 -2
  10. package/dist/documents/dto.js +61 -1
  11. package/dist/documents/render/html.d.ts +5 -0
  12. package/dist/documents/render/html.js +10 -3
  13. package/dist/documents/render/index.d.ts +1 -1
  14. package/dist/documents/render/index.js +3 -1
  15. package/dist/documents/render/values.d.ts +12 -5
  16. package/dist/documents/render/values.js +16 -6
  17. package/dist/index.d.ts +3 -1
  18. package/dist/index.js +32 -3
  19. package/dist/inventory/dto.d.ts +134 -0
  20. package/dist/inventory/dto.js +684 -0
  21. package/dist/inventory/index.d.ts +7 -0
  22. package/dist/inventory/index.js +30 -0
  23. package/dist/inventory/items.d.ts +65 -0
  24. package/dist/inventory/items.js +216 -0
  25. package/dist/inventory/levels.d.ts +52 -0
  26. package/dist/inventory/levels.js +157 -0
  27. package/dist/inventory/locations.d.ts +29 -0
  28. package/dist/inventory/locations.js +158 -0
  29. package/dist/inventory/movements.d.ts +36 -0
  30. package/dist/inventory/movements.js +103 -0
  31. package/dist/inventory/stocktakes.d.ts +36 -0
  32. package/dist/inventory/stocktakes.js +70 -0
  33. package/dist/inventory/transfers.d.ts +73 -0
  34. package/dist/inventory/transfers.js +134 -0
  35. package/dist/organizations/dto.d.ts +3 -1
  36. package/dist/organizations/dto.js +12 -0
  37. package/dist/organizations/index.js +2 -0
  38. package/dist/permissions/dto.d.ts +4 -0
  39. package/dist/permissions/dto.js +20 -0
  40. package/dist/service-client/index.d.ts +6 -0
  41. package/dist/service-client/index.js +10 -0
  42. package/dist/users/paths.d.ts +5 -6
  43. package/dist/users/paths.js +12 -12
  44. 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 {
@@ -83,6 +83,8 @@ const ORGANIZATION_SELECT = `
83
83
  billing_post_code,
84
84
  billing_country,
85
85
  leave_calendar_start_month,
86
+ stock_issue_on,
87
+ allow_negative_stock,
86
88
  billing_cycle_locked,
87
89
  modules,
88
90
  payment_frequency,
@@ -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;