@voyantjs/suppliers 0.2.0 → 0.3.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-operations.d.ts","sourceRoot":"","sources":["../src/service-operations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,157 @@
1
+ import { and, asc, desc, eq, gte, lte } from "drizzle-orm";
2
+ import { supplierAvailability, supplierContracts, supplierNotes, supplierRates, supplierServices, } from "./schema.js";
3
+ import { ensureSupplierExists } from "./service-shared.js";
4
+ export function listServices(db, supplierId) {
5
+ return db
6
+ .select()
7
+ .from(supplierServices)
8
+ .where(eq(supplierServices.supplierId, supplierId))
9
+ .orderBy(supplierServices.createdAt);
10
+ }
11
+ export async function createService(db, supplierId, data) {
12
+ const supplier = await ensureSupplierExists(db, supplierId);
13
+ if (!supplier) {
14
+ return null;
15
+ }
16
+ const [row] = await db
17
+ .insert(supplierServices)
18
+ .values({ ...data, supplierId })
19
+ .returning();
20
+ return row ?? null;
21
+ }
22
+ export async function updateService(db, serviceId, data) {
23
+ const [row] = await db
24
+ .update(supplierServices)
25
+ .set({ ...data, updatedAt: new Date() })
26
+ .where(eq(supplierServices.id, serviceId))
27
+ .returning();
28
+ return row ?? null;
29
+ }
30
+ export async function deleteService(db, serviceId) {
31
+ const [row] = await db
32
+ .delete(supplierServices)
33
+ .where(eq(supplierServices.id, serviceId))
34
+ .returning({ id: supplierServices.id });
35
+ return row ?? null;
36
+ }
37
+ export function listRates(db, serviceId) {
38
+ return db
39
+ .select()
40
+ .from(supplierRates)
41
+ .where(eq(supplierRates.serviceId, serviceId))
42
+ .orderBy(supplierRates.createdAt);
43
+ }
44
+ export async function createRate(db, serviceId, data) {
45
+ const [service] = await db
46
+ .select({ id: supplierServices.id })
47
+ .from(supplierServices)
48
+ .where(eq(supplierServices.id, serviceId))
49
+ .limit(1);
50
+ if (!service) {
51
+ return null;
52
+ }
53
+ const [row] = await db
54
+ .insert(supplierRates)
55
+ .values({ ...data, serviceId })
56
+ .returning();
57
+ return row ?? null;
58
+ }
59
+ export async function updateRate(db, rateId, data) {
60
+ const [row] = await db
61
+ .update(supplierRates)
62
+ .set(data)
63
+ .where(eq(supplierRates.id, rateId))
64
+ .returning();
65
+ return row ?? null;
66
+ }
67
+ export async function deleteRate(db, rateId) {
68
+ const [row] = await db
69
+ .delete(supplierRates)
70
+ .where(eq(supplierRates.id, rateId))
71
+ .returning({ id: supplierRates.id });
72
+ return row ?? null;
73
+ }
74
+ export function listNotes(db, supplierId) {
75
+ return db
76
+ .select()
77
+ .from(supplierNotes)
78
+ .where(eq(supplierNotes.supplierId, supplierId))
79
+ .orderBy(supplierNotes.createdAt);
80
+ }
81
+ export async function createNote(db, supplierId, userId, data) {
82
+ const supplier = await ensureSupplierExists(db, supplierId);
83
+ if (!supplier) {
84
+ return null;
85
+ }
86
+ const [row] = await db
87
+ .insert(supplierNotes)
88
+ .values({
89
+ supplierId,
90
+ authorId: userId,
91
+ content: data.content,
92
+ })
93
+ .returning();
94
+ return row ?? null;
95
+ }
96
+ export async function listAvailability(db, supplierId, query) {
97
+ const conditions = [eq(supplierAvailability.supplierId, supplierId)];
98
+ if (query.from) {
99
+ conditions.push(gte(supplierAvailability.date, query.from));
100
+ }
101
+ if (query.to) {
102
+ conditions.push(lte(supplierAvailability.date, query.to));
103
+ }
104
+ return db
105
+ .select()
106
+ .from(supplierAvailability)
107
+ .where(and(...conditions))
108
+ .orderBy(asc(supplierAvailability.date));
109
+ }
110
+ export async function createAvailability(db, supplierId, entries) {
111
+ const supplier = await ensureSupplierExists(db, supplierId);
112
+ if (!supplier) {
113
+ return null;
114
+ }
115
+ return db
116
+ .insert(supplierAvailability)
117
+ .values(entries.map((entry) => ({
118
+ supplierId,
119
+ date: entry.date,
120
+ available: entry.available,
121
+ notes: entry.notes ?? null,
122
+ })))
123
+ .returning();
124
+ }
125
+ export function listContracts(db, supplierId) {
126
+ return db
127
+ .select()
128
+ .from(supplierContracts)
129
+ .where(eq(supplierContracts.supplierId, supplierId))
130
+ .orderBy(desc(supplierContracts.createdAt));
131
+ }
132
+ export async function createContract(db, supplierId, data) {
133
+ const supplier = await ensureSupplierExists(db, supplierId);
134
+ if (!supplier) {
135
+ return null;
136
+ }
137
+ const [row] = await db
138
+ .insert(supplierContracts)
139
+ .values({ ...data, supplierId })
140
+ .returning();
141
+ return row ?? null;
142
+ }
143
+ export async function updateContract(db, contractId, data) {
144
+ const [row] = await db
145
+ .update(supplierContracts)
146
+ .set({ ...data, updatedAt: new Date() })
147
+ .where(eq(supplierContracts.id, contractId))
148
+ .returning();
149
+ return row ?? null;
150
+ }
151
+ export async function deleteContract(db, contractId) {
152
+ const [row] = await db
153
+ .delete(supplierContracts)
154
+ .where(eq(supplierContracts.id, contractId))
155
+ .returning({ id: supplierContracts.id });
156
+ return row ?? null;
157
+ }
@@ -0,0 +1,43 @@
1
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
2
+ import type { z } from "zod";
3
+ import type { Supplier } from "./schema.js";
4
+ import type { availabilityQuerySchema, insertAvailabilitySchema, insertContractSchema, insertRateSchema, insertServiceSchema, insertSupplierNoteSchema, insertSupplierSchema, supplierListQuerySchema, updateContractSchema, updateRateSchema, updateServiceSchema, updateSupplierSchema } from "./validation.js";
5
+ export type SupplierListQuery = z.infer<typeof supplierListQuerySchema>;
6
+ export type AvailabilityQuery = z.infer<typeof availabilityQuerySchema>;
7
+ export type CreateSupplierInput = z.infer<typeof insertSupplierSchema>;
8
+ export type UpdateSupplierInput = z.infer<typeof updateSupplierSchema>;
9
+ export type CreateServiceInput = z.infer<typeof insertServiceSchema>;
10
+ export type UpdateServiceInput = z.infer<typeof updateServiceSchema>;
11
+ export type CreateRateInput = z.infer<typeof insertRateSchema>;
12
+ export type UpdateRateInput = z.infer<typeof updateRateSchema>;
13
+ export type CreateSupplierNoteInput = z.infer<typeof insertSupplierNoteSchema>;
14
+ export type CreateAvailabilityInput = z.infer<typeof insertAvailabilitySchema>;
15
+ export type CreateContractInput = z.infer<typeof insertContractSchema>;
16
+ export type UpdateContractInput = z.infer<typeof updateContractSchema>;
17
+ export declare const supplierEntityType = "supplier";
18
+ export declare const supplierBaseIdentitySource = "suppliers.base";
19
+ export declare const supplierPrimaryNamedContactSource = "suppliers.primary_contact";
20
+ export type SupplierIdentityInput = Pick<CreateSupplierInput, "email" | "phone" | "website" | "address" | "city" | "country" | "contactName" | "contactEmail" | "contactPhone">;
21
+ export type SupplierHydratedFields = {
22
+ email: string | null;
23
+ phone: string | null;
24
+ website: string | null;
25
+ address: string | null;
26
+ city: string | null;
27
+ country: string | null;
28
+ contactName: string | null;
29
+ contactEmail: string | null;
30
+ contactPhone: string | null;
31
+ };
32
+ export type HydratedSupplier = Supplier & SupplierHydratedFields;
33
+ export declare function ensureSupplierExists(db: PostgresJsDatabase, supplierId: string): Promise<{
34
+ id: string;
35
+ } | null>;
36
+ export declare function syncSupplierIdentity(db: PostgresJsDatabase, supplierId: string, data: SupplierIdentityInput): Promise<void>;
37
+ export declare function hydrateSuppliers<T extends {
38
+ id: string;
39
+ contactName?: string | null;
40
+ contactEmail?: string | null;
41
+ contactPhone?: string | null;
42
+ }>(db: PostgresJsDatabase, rows: T[]): Promise<Array<T & SupplierHydratedFields>>;
43
+ //# sourceMappingURL=service-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-shared.d.ts","sourceRoot":"","sources":["../src/service-shared.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,212 @@
1
+ import { identityAddresses, identityContactPoints, identityNamedContacts, } from "@voyantjs/identity/schema";
2
+ import { identityService } from "@voyantjs/identity/service";
3
+ import { and, eq, inArray } from "drizzle-orm";
4
+ import { suppliers } from "./schema.js";
5
+ export const supplierEntityType = "supplier";
6
+ export const supplierBaseIdentitySource = "suppliers.base";
7
+ export const supplierPrimaryNamedContactSource = "suppliers.primary_contact";
8
+ function normalizeContactValue(kind, value) {
9
+ if (kind === "email" || kind === "website") {
10
+ return value.trim().toLowerCase();
11
+ }
12
+ return value.trim();
13
+ }
14
+ function isManagedBySource(metadata, source) {
15
+ return metadata?.managedBy === source;
16
+ }
17
+ function toNullableTrimmed(value) {
18
+ const trimmed = value?.trim();
19
+ return trimmed ? trimmed : null;
20
+ }
21
+ function formatAddress(address) {
22
+ if (address.fullText) {
23
+ return address.fullText;
24
+ }
25
+ const parts = [
26
+ address.line1,
27
+ address.line2,
28
+ address.city,
29
+ address.region,
30
+ address.postalCode,
31
+ address.country,
32
+ ].filter(Boolean);
33
+ return parts.length > 0 ? parts.join(", ") : null;
34
+ }
35
+ export async function ensureSupplierExists(db, supplierId) {
36
+ const [supplier] = await db
37
+ .select({ id: suppliers.id })
38
+ .from(suppliers)
39
+ .where(eq(suppliers.id, supplierId))
40
+ .limit(1);
41
+ return supplier ?? null;
42
+ }
43
+ export async function syncSupplierIdentity(db, supplierId, data) {
44
+ const existingContactPoints = await identityService.listContactPointsForEntity(db, supplierEntityType, supplierId);
45
+ const existingAddresses = await identityService.listAddressesForEntity(db, supplierEntityType, supplierId);
46
+ const existingNamedContacts = await identityService.listNamedContactsForEntity(db, supplierEntityType, supplierId);
47
+ const managedContactPoints = existingContactPoints.filter((point) => isManagedBySource(point.metadata, supplierBaseIdentitySource));
48
+ const managedAddress = existingAddresses.find((address) => isManagedBySource(address.metadata, supplierBaseIdentitySource));
49
+ const managedPrimaryContact = existingNamedContacts.find((contact) => isManagedBySource(contact.metadata, supplierPrimaryNamedContactSource));
50
+ for (const [kind, rawValue] of Object.entries({
51
+ email: data.email,
52
+ phone: data.phone,
53
+ website: data.website,
54
+ })) {
55
+ const value = toNullableTrimmed(rawValue);
56
+ const existing = managedContactPoints.find((point) => point.kind === kind) ??
57
+ existingContactPoints.find((point) => point.kind === kind && point.isPrimary);
58
+ if (!value) {
59
+ if (existing) {
60
+ await identityService.deleteContactPoint(db, existing.id);
61
+ }
62
+ continue;
63
+ }
64
+ const payload = {
65
+ entityType: supplierEntityType,
66
+ entityId: supplierId,
67
+ kind,
68
+ label: kind === "website" ? "website" : "primary",
69
+ value,
70
+ normalizedValue: normalizeContactValue(kind, value),
71
+ isPrimary: true,
72
+ metadata: {
73
+ managedBy: supplierBaseIdentitySource,
74
+ },
75
+ };
76
+ if (existing) {
77
+ await identityService.updateContactPoint(db, existing.id, payload);
78
+ }
79
+ else {
80
+ await identityService.createContactPoint(db, payload);
81
+ }
82
+ }
83
+ const addressLine = toNullableTrimmed(data.address);
84
+ const city = toNullableTrimmed(data.city);
85
+ const country = toNullableTrimmed(data.country);
86
+ const hasAddress = Boolean(addressLine || city || country);
87
+ if (!hasAddress) {
88
+ if (managedAddress) {
89
+ await identityService.deleteAddress(db, managedAddress.id);
90
+ }
91
+ }
92
+ else {
93
+ const addressPayload = {
94
+ entityType: supplierEntityType,
95
+ entityId: supplierId,
96
+ label: "primary",
97
+ fullText: addressLine,
98
+ line1: addressLine,
99
+ city,
100
+ country,
101
+ isPrimary: true,
102
+ metadata: {
103
+ managedBy: supplierBaseIdentitySource,
104
+ },
105
+ };
106
+ if (managedAddress) {
107
+ await identityService.updateAddress(db, managedAddress.id, addressPayload);
108
+ }
109
+ else {
110
+ await identityService.createAddress(db, addressPayload);
111
+ }
112
+ }
113
+ const contactName = toNullableTrimmed(data.contactName);
114
+ const contactEmail = toNullableTrimmed(data.contactEmail);
115
+ const contactPhone = toNullableTrimmed(data.contactPhone);
116
+ const hasPrimaryContact = Boolean(contactName || contactEmail || contactPhone);
117
+ if (!hasPrimaryContact) {
118
+ if (managedPrimaryContact) {
119
+ await identityService.deleteNamedContact(db, managedPrimaryContact.id);
120
+ }
121
+ return;
122
+ }
123
+ const namedContactPayload = {
124
+ entityType: supplierEntityType,
125
+ entityId: supplierId,
126
+ role: "primary",
127
+ name: contactName ?? contactEmail ?? contactPhone ?? "Primary contact",
128
+ email: contactEmail,
129
+ phone: contactPhone,
130
+ isPrimary: true,
131
+ metadata: {
132
+ managedBy: supplierPrimaryNamedContactSource,
133
+ },
134
+ };
135
+ if (managedPrimaryContact) {
136
+ await identityService.updateNamedContact(db, managedPrimaryContact.id, namedContactPayload);
137
+ }
138
+ else {
139
+ await identityService.createNamedContact(db, namedContactPayload);
140
+ }
141
+ }
142
+ export async function hydrateSuppliers(db, rows) {
143
+ if (rows.length === 0) {
144
+ return rows.map((row) => ({
145
+ ...row,
146
+ email: null,
147
+ phone: null,
148
+ website: null,
149
+ address: null,
150
+ city: null,
151
+ country: null,
152
+ contactName: null,
153
+ contactEmail: null,
154
+ contactPhone: null,
155
+ }));
156
+ }
157
+ const ids = rows.map((row) => row.id);
158
+ const [contactPoints, addresses, namedContacts] = await Promise.all([
159
+ db
160
+ .select()
161
+ .from(identityContactPoints)
162
+ .where(and(eq(identityContactPoints.entityType, supplierEntityType), inArray(identityContactPoints.entityId, ids))),
163
+ db
164
+ .select()
165
+ .from(identityAddresses)
166
+ .where(and(eq(identityAddresses.entityType, supplierEntityType), inArray(identityAddresses.entityId, ids))),
167
+ db
168
+ .select()
169
+ .from(identityNamedContacts)
170
+ .where(and(eq(identityNamedContacts.entityType, supplierEntityType), inArray(identityNamedContacts.entityId, ids))),
171
+ ]);
172
+ const contactPointMap = new Map();
173
+ const addressMap = new Map();
174
+ const namedContactMap = new Map();
175
+ for (const point of contactPoints) {
176
+ const bucket = contactPointMap.get(point.entityId) ?? [];
177
+ bucket.push(point);
178
+ contactPointMap.set(point.entityId, bucket);
179
+ }
180
+ for (const address of addresses) {
181
+ const bucket = addressMap.get(address.entityId) ?? [];
182
+ bucket.push(address);
183
+ addressMap.set(address.entityId, bucket);
184
+ }
185
+ for (const contact of namedContacts) {
186
+ const bucket = namedContactMap.get(contact.entityId) ?? [];
187
+ bucket.push(contact);
188
+ namedContactMap.set(contact.entityId, bucket);
189
+ }
190
+ return rows.map((row) => {
191
+ const entityContactPoints = contactPointMap.get(row.id) ?? [];
192
+ const entityAddresses = addressMap.get(row.id) ?? [];
193
+ const entityNamedContacts = namedContactMap.get(row.id) ?? [];
194
+ const pickContactValue = (kind) => entityContactPoints.find((point) => point.kind === kind && point.isPrimary)?.value ??
195
+ entityContactPoints.find((point) => point.kind === kind)?.value ??
196
+ null;
197
+ const primaryAddress = entityAddresses.find((address) => address.isPrimary) ?? entityAddresses[0] ?? null;
198
+ const primaryContact = entityNamedContacts.find((contact) => contact.isPrimary) ?? entityNamedContacts[0] ?? null;
199
+ return {
200
+ ...row,
201
+ email: pickContactValue("email"),
202
+ phone: pickContactValue("phone"),
203
+ website: pickContactValue("website"),
204
+ address: primaryAddress ? formatAddress(primaryAddress) : null,
205
+ city: primaryAddress?.city ?? null,
206
+ country: primaryAddress?.country ?? null,
207
+ contactName: primaryContact?.name ?? null,
208
+ contactEmail: primaryContact?.email ?? null,
209
+ contactPhone: primaryContact?.phone ?? null,
210
+ };
211
+ });
212
+ }