@unifiedcommerce/plugin-marketplace 0.0.1

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 (122) hide show
  1. package/README.md +479 -0
  2. package/dist/analytics-models.d.ts +13 -0
  3. package/dist/analytics-models.d.ts.map +1 -0
  4. package/dist/analytics-models.js +69 -0
  5. package/dist/hooks.d.ts +4 -0
  6. package/dist/hooks.d.ts.map +1 -0
  7. package/dist/hooks.js +187 -0
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +105 -0
  11. package/dist/mcp-tools.d.ts +21 -0
  12. package/dist/mcp-tools.d.ts.map +1 -0
  13. package/dist/mcp-tools.js +183 -0
  14. package/dist/routes/b2b.d.ts +9 -0
  15. package/dist/routes/b2b.d.ts.map +1 -0
  16. package/dist/routes/b2b.js +156 -0
  17. package/dist/routes/commission.d.ts +6 -0
  18. package/dist/routes/commission.d.ts.map +1 -0
  19. package/dist/routes/commission.js +85 -0
  20. package/dist/routes/disputes-returns-reviews.d.ts +10 -0
  21. package/dist/routes/disputes-returns-reviews.d.ts.map +1 -0
  22. package/dist/routes/disputes-returns-reviews.js +179 -0
  23. package/dist/routes/payouts.d.ts +6 -0
  24. package/dist/routes/payouts.d.ts.map +1 -0
  25. package/dist/routes/payouts.js +40 -0
  26. package/dist/routes/sub-orders.d.ts +6 -0
  27. package/dist/routes/sub-orders.d.ts.map +1 -0
  28. package/dist/routes/sub-orders.js +44 -0
  29. package/dist/routes/util.d.ts +23 -0
  30. package/dist/routes/util.d.ts.map +1 -0
  31. package/dist/routes/util.js +41 -0
  32. package/dist/routes/vendor-portal.d.ts +14 -0
  33. package/dist/routes/vendor-portal.d.ts.map +1 -0
  34. package/dist/routes/vendor-portal.js +255 -0
  35. package/dist/routes/vendors.d.ts +11 -0
  36. package/dist/routes/vendors.d.ts.map +1 -0
  37. package/dist/routes/vendors.js +185 -0
  38. package/dist/schema.d.ts +3255 -0
  39. package/dist/schema.d.ts.map +1 -0
  40. package/dist/schema.js +225 -0
  41. package/dist/schemas/b2b.d.ts +1009 -0
  42. package/dist/schemas/b2b.d.ts.map +1 -0
  43. package/dist/schemas/b2b.js +208 -0
  44. package/dist/schemas/commission.d.ts +532 -0
  45. package/dist/schemas/commission.d.ts.map +1 -0
  46. package/dist/schemas/commission.js +113 -0
  47. package/dist/schemas/disputes-returns-reviews.d.ts +1405 -0
  48. package/dist/schemas/disputes-returns-reviews.d.ts.map +1 -0
  49. package/dist/schemas/disputes-returns-reviews.js +270 -0
  50. package/dist/schemas/payouts.d.ts +375 -0
  51. package/dist/schemas/payouts.d.ts.map +1 -0
  52. package/dist/schemas/payouts.js +78 -0
  53. package/dist/schemas/sub-orders.d.ts +303 -0
  54. package/dist/schemas/sub-orders.d.ts.map +1 -0
  55. package/dist/schemas/sub-orders.js +67 -0
  56. package/dist/schemas/vendor-portal.d.ts +1785 -0
  57. package/dist/schemas/vendor-portal.d.ts.map +1 -0
  58. package/dist/schemas/vendor-portal.js +294 -0
  59. package/dist/schemas/vendors.d.ts +1348 -0
  60. package/dist/schemas/vendors.d.ts.map +1 -0
  61. package/dist/schemas/vendors.js +245 -0
  62. package/dist/services/commission.d.ts +81 -0
  63. package/dist/services/commission.d.ts.map +1 -0
  64. package/dist/services/commission.js +98 -0
  65. package/dist/services/contract-price.d.ts +64 -0
  66. package/dist/services/contract-price.d.ts.map +1 -0
  67. package/dist/services/contract-price.js +57 -0
  68. package/dist/services/dispute.d.ts +156 -0
  69. package/dist/services/dispute.d.ts.map +1 -0
  70. package/dist/services/dispute.js +77 -0
  71. package/dist/services/payout.d.ts +126 -0
  72. package/dist/services/payout.d.ts.map +1 -0
  73. package/dist/services/payout.js +130 -0
  74. package/dist/services/return.d.ts +181 -0
  75. package/dist/services/return.d.ts.map +1 -0
  76. package/dist/services/return.js +80 -0
  77. package/dist/services/review.d.ts +70 -0
  78. package/dist/services/review.d.ts.map +1 -0
  79. package/dist/services/review.js +60 -0
  80. package/dist/services/rfq.d.ts +122 -0
  81. package/dist/services/rfq.d.ts.map +1 -0
  82. package/dist/services/rfq.js +60 -0
  83. package/dist/services/sub-order.d.ts +336 -0
  84. package/dist/services/sub-order.d.ts.map +1 -0
  85. package/dist/services/sub-order.js +121 -0
  86. package/dist/services/vendor.d.ts +528 -0
  87. package/dist/services/vendor.d.ts.map +1 -0
  88. package/dist/services/vendor.js +119 -0
  89. package/dist/types.d.ts +67 -0
  90. package/dist/types.d.ts.map +1 -0
  91. package/dist/types.js +13 -0
  92. package/package.json +43 -0
  93. package/src/analytics-models.ts +75 -0
  94. package/src/hooks.ts +215 -0
  95. package/src/index.ts +124 -0
  96. package/src/mcp-tools.ts +210 -0
  97. package/src/routes/b2b.ts +179 -0
  98. package/src/routes/commission.ts +95 -0
  99. package/src/routes/disputes-returns-reviews.ts +209 -0
  100. package/src/routes/payouts.ts +49 -0
  101. package/src/routes/sub-orders.ts +54 -0
  102. package/src/routes/util.ts +42 -0
  103. package/src/routes/vendor-portal.ts +277 -0
  104. package/src/routes/vendors.ts +201 -0
  105. package/src/schema.ts +260 -0
  106. package/src/schemas/b2b.ts +238 -0
  107. package/src/schemas/commission.ts +129 -0
  108. package/src/schemas/disputes-returns-reviews.ts +311 -0
  109. package/src/schemas/payouts.ts +90 -0
  110. package/src/schemas/sub-orders.ts +77 -0
  111. package/src/schemas/vendor-portal.ts +344 -0
  112. package/src/schemas/vendors.ts +281 -0
  113. package/src/services/commission.ts +120 -0
  114. package/src/services/contract-price.ts +80 -0
  115. package/src/services/dispute.ts +92 -0
  116. package/src/services/payout.ts +154 -0
  117. package/src/services/return.ts +92 -0
  118. package/src/services/review.ts +76 -0
  119. package/src/services/rfq.ts +82 -0
  120. package/src/services/sub-order.ts +136 -0
  121. package/src/services/vendor.ts +151 -0
  122. package/src/types.ts +164 -0
@@ -0,0 +1,80 @@
1
+ import { eq, and, desc } from "drizzle-orm";
2
+ import { returnRequests, vendorSubOrders } from "../schema";
3
+ export class ReturnService {
4
+ db;
5
+ constructor(db) {
6
+ this.db = db;
7
+ }
8
+ async request(data) {
9
+ const [req] = await this.db.insert(returnRequests).values({
10
+ subOrderId: data.subOrderId,
11
+ customerId: data.customerId,
12
+ reason: data.reason,
13
+ description: data.description,
14
+ lineItems: data.lineItems,
15
+ }).returning();
16
+ return req;
17
+ }
18
+ async getById(id) {
19
+ const [req] = await this.db.select().from(returnRequests).where(eq(returnRequests.id, id));
20
+ return req ?? null;
21
+ }
22
+ async list(filters) {
23
+ let query = this.db.select().from(returnRequests).$dynamic();
24
+ const conditions = [];
25
+ if (filters?.subOrderId)
26
+ conditions.push(eq(returnRequests.subOrderId, filters.subOrderId));
27
+ if (filters?.status)
28
+ conditions.push(eq(returnRequests.status, filters.status));
29
+ if (conditions.length > 0) {
30
+ query = query.where(conditions.length === 1 ? conditions[0] : and(...conditions));
31
+ }
32
+ return query.orderBy(desc(returnRequests.requestedAt));
33
+ }
34
+ async listByVendor(vendorId) {
35
+ const subOrders = await this.db.select().from(vendorSubOrders)
36
+ .where(eq(vendorSubOrders.vendorId, vendorId));
37
+ const subOrderIds = subOrders.map((s) => s.id);
38
+ if (subOrderIds.length === 0)
39
+ return [];
40
+ const all = await this.db.select().from(returnRequests)
41
+ .orderBy(desc(returnRequests.requestedAt));
42
+ return all.filter((r) => subOrderIds.includes(r.subOrderId));
43
+ }
44
+ async vendorApprove(id, refundAmountCents) {
45
+ const [updated] = await this.db.update(returnRequests).set({
46
+ status: "vendor_approved",
47
+ refundAmountCents,
48
+ }).where(eq(returnRequests.id, id)).returning();
49
+ return updated ?? null;
50
+ }
51
+ async vendorReject(id, notes) {
52
+ const [updated] = await this.db.update(returnRequests).set({
53
+ status: "vendor_rejected",
54
+ vendorNotes: notes,
55
+ resolvedAt: new Date(),
56
+ }).where(eq(returnRequests.id, id)).returning();
57
+ return updated ?? null;
58
+ }
59
+ async shipBack(id, trackingNumber) {
60
+ const [updated] = await this.db.update(returnRequests).set({
61
+ status: "shipped_back",
62
+ trackingNumber,
63
+ }).where(eq(returnRequests.id, id)).returning();
64
+ return updated ?? null;
65
+ }
66
+ async receive(id) {
67
+ const [updated] = await this.db.update(returnRequests).set({
68
+ status: "received",
69
+ }).where(eq(returnRequests.id, id)).returning();
70
+ return updated ?? null;
71
+ }
72
+ async refund(id, amountCents) {
73
+ const [updated] = await this.db.update(returnRequests).set({
74
+ status: "refunded",
75
+ refundAmountCents: amountCents,
76
+ resolvedAt: new Date(),
77
+ }).where(eq(returnRequests.id, id)).returning();
78
+ return updated ?? null;
79
+ }
80
+ }
@@ -0,0 +1,70 @@
1
+ import type { Db, ReviewStatus, MarketplacePluginOptions } from "../types";
2
+ export declare class ReviewService {
3
+ private db;
4
+ private options;
5
+ constructor(db: Db, options: MarketplacePluginOptions);
6
+ create(data: {
7
+ vendorId: string;
8
+ customerId?: string;
9
+ orderId?: string;
10
+ rating: number;
11
+ title?: string;
12
+ body?: string;
13
+ }): Promise<{
14
+ id: string;
15
+ vendorId: string;
16
+ orderId: string | null;
17
+ status: string;
18
+ createdAt: Date;
19
+ rating: number;
20
+ customerId: string | null;
21
+ title: string | null;
22
+ body: string | null;
23
+ vendorResponse: string | null;
24
+ vendorRespondedAt: Date | null;
25
+ } | undefined>;
26
+ getForVendor(vendorId: string, includeUnpublished?: boolean): Promise<{
27
+ id: string;
28
+ vendorId: string;
29
+ customerId: string | null;
30
+ orderId: string | null;
31
+ rating: number;
32
+ title: string | null;
33
+ body: string | null;
34
+ status: string;
35
+ vendorResponse: string | null;
36
+ vendorRespondedAt: Date | null;
37
+ createdAt: Date;
38
+ }[]>;
39
+ getAggregateRating(vendorId: string): Promise<{
40
+ average: number;
41
+ count: number;
42
+ }>;
43
+ respond(reviewId: string, response: string): Promise<{
44
+ id: string;
45
+ vendorId: string;
46
+ customerId: string | null;
47
+ orderId: string | null;
48
+ rating: number;
49
+ title: string | null;
50
+ body: string | null;
51
+ status: string;
52
+ vendorResponse: string | null;
53
+ vendorRespondedAt: Date | null;
54
+ createdAt: Date;
55
+ } | null>;
56
+ moderate(reviewId: string, status: ReviewStatus): Promise<{
57
+ id: string;
58
+ vendorId: string;
59
+ customerId: string | null;
60
+ orderId: string | null;
61
+ rating: number;
62
+ title: string | null;
63
+ body: string | null;
64
+ status: string;
65
+ vendorResponse: string | null;
66
+ vendorRespondedAt: Date | null;
67
+ createdAt: Date;
68
+ } | null>;
69
+ }
70
+ //# sourceMappingURL=review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.d.ts","sourceRoot":"","sources":["../../src/services/review.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAE3E,qBAAa,aAAa;IAEtB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,OAAO;gBADP,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,wBAAwB;IAGrC,MAAM,CAAC,IAAI,EAAE;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;KACf;;;;;;;;;;;;;IAmBK,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,kBAAkB,UAAQ;;;;;;;;;;;;;IAYzD,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAcjF,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;IAQ1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY;;;;;;;;;;;;;CAKtD"}
@@ -0,0 +1,60 @@
1
+ import { eq, and, desc, sql } from "drizzle-orm";
2
+ import { vendorReviews } from "../schema";
3
+ export class ReviewService {
4
+ db;
5
+ options;
6
+ constructor(db, options) {
7
+ this.db = db;
8
+ this.options = options;
9
+ }
10
+ async create(data) {
11
+ if (data.rating < 1 || data.rating > 5) {
12
+ throw new Error("Rating must be between 1 and 5.");
13
+ }
14
+ const status = this.options.reviewModerationEnabled ? "pending" : "published";
15
+ const [review] = await this.db.insert(vendorReviews).values({
16
+ vendorId: data.vendorId,
17
+ customerId: data.customerId,
18
+ orderId: data.orderId,
19
+ rating: data.rating,
20
+ title: data.title,
21
+ body: data.body,
22
+ status,
23
+ }).returning();
24
+ return review;
25
+ }
26
+ async getForVendor(vendorId, includeUnpublished = false) {
27
+ let query = this.db.select().from(vendorReviews).$dynamic();
28
+ if (includeUnpublished) {
29
+ query = query.where(eq(vendorReviews.vendorId, vendorId));
30
+ }
31
+ else {
32
+ query = query.where(and(eq(vendorReviews.vendorId, vendorId), eq(vendorReviews.status, "published")));
33
+ }
34
+ return query.orderBy(desc(vendorReviews.createdAt));
35
+ }
36
+ async getAggregateRating(vendorId) {
37
+ const rows = await this.db.select({
38
+ avg: sql `COALESCE(AVG(${vendorReviews.rating}), 0)`,
39
+ count: sql `COUNT(*)`,
40
+ }).from(vendorReviews)
41
+ .where(and(eq(vendorReviews.vendorId, vendorId), eq(vendorReviews.status, "published")));
42
+ const row = rows[0];
43
+ return {
44
+ average: Math.round((Number(row?.avg) || 0) * 10) / 10,
45
+ count: Number(row?.count) || 0,
46
+ };
47
+ }
48
+ async respond(reviewId, response) {
49
+ const [updated] = await this.db.update(vendorReviews).set({
50
+ vendorResponse: response,
51
+ vendorRespondedAt: new Date(),
52
+ }).where(eq(vendorReviews.id, reviewId)).returning();
53
+ return updated ?? null;
54
+ }
55
+ async moderate(reviewId, status) {
56
+ const [updated] = await this.db.update(vendorReviews).set({ status })
57
+ .where(eq(vendorReviews.id, reviewId)).returning();
58
+ return updated ?? null;
59
+ }
60
+ }
@@ -0,0 +1,122 @@
1
+ import type { Db } from "../types";
2
+ export declare class RFQService {
3
+ private db;
4
+ constructor(db: Db);
5
+ create(data: {
6
+ buyerId?: string;
7
+ title: string;
8
+ description?: string;
9
+ categorySlug?: string;
10
+ quantity?: number;
11
+ budgetCents?: number;
12
+ currency?: string;
13
+ deadlineAt?: Date | undefined;
14
+ metadata?: Record<string, unknown>;
15
+ }): Promise<{
16
+ id: string;
17
+ status: string;
18
+ createdAt: Date;
19
+ description: string | null;
20
+ metadata: Record<string, unknown> | null;
21
+ categorySlug: string | null;
22
+ deadlineAt: Date | null;
23
+ title: string;
24
+ buyerId: string | null;
25
+ quantity: number | null;
26
+ budgetCents: number | null;
27
+ currency: string;
28
+ awardedVendorId: string | null;
29
+ } | undefined>;
30
+ getById(id: string): Promise<{
31
+ id: string;
32
+ buyerId: string | null;
33
+ title: string;
34
+ description: string | null;
35
+ categorySlug: string | null;
36
+ quantity: number | null;
37
+ budgetCents: number | null;
38
+ currency: string;
39
+ deadlineAt: Date | null;
40
+ status: string;
41
+ awardedVendorId: string | null;
42
+ metadata: Record<string, unknown> | null;
43
+ createdAt: Date;
44
+ } | null>;
45
+ list(filters?: {
46
+ status?: string;
47
+ categorySlug?: string;
48
+ }): Promise<{
49
+ id: string;
50
+ buyerId: string | null;
51
+ title: string;
52
+ description: string | null;
53
+ categorySlug: string | null;
54
+ quantity: number | null;
55
+ budgetCents: number | null;
56
+ currency: string;
57
+ deadlineAt: Date | null;
58
+ status: string;
59
+ awardedVendorId: string | null;
60
+ metadata: Record<string, unknown> | null;
61
+ createdAt: Date;
62
+ }[]>;
63
+ respond(rfqId: string, data: {
64
+ vendorId: string;
65
+ unitPriceCents: number;
66
+ totalPriceCents: number;
67
+ leadTimeDays?: number;
68
+ notes?: string;
69
+ }): Promise<{
70
+ id: string;
71
+ vendorId: string;
72
+ status: string;
73
+ createdAt: Date;
74
+ rfqId: string;
75
+ unitPriceCents: number;
76
+ totalPriceCents: number;
77
+ leadTimeDays: number | null;
78
+ notes: string | null;
79
+ } | undefined>;
80
+ getResponses(rfqId: string): Promise<{
81
+ id: string;
82
+ rfqId: string;
83
+ vendorId: string;
84
+ unitPriceCents: number;
85
+ totalPriceCents: number;
86
+ leadTimeDays: number | null;
87
+ notes: string | null;
88
+ status: string;
89
+ createdAt: Date;
90
+ }[]>;
91
+ award(rfqId: string, vendorId: string): Promise<{
92
+ id: string;
93
+ buyerId: string | null;
94
+ title: string;
95
+ description: string | null;
96
+ categorySlug: string | null;
97
+ quantity: number | null;
98
+ budgetCents: number | null;
99
+ currency: string;
100
+ deadlineAt: Date | null;
101
+ status: string;
102
+ awardedVendorId: string | null;
103
+ metadata: Record<string, unknown> | null;
104
+ createdAt: Date;
105
+ } | null>;
106
+ close(rfqId: string): Promise<{
107
+ id: string;
108
+ buyerId: string | null;
109
+ title: string;
110
+ description: string | null;
111
+ categorySlug: string | null;
112
+ quantity: number | null;
113
+ budgetCents: number | null;
114
+ currency: string;
115
+ deadlineAt: Date | null;
116
+ status: string;
117
+ awardedVendorId: string | null;
118
+ metadata: Record<string, unknown> | null;
119
+ createdAt: Date;
120
+ } | null>;
121
+ }
122
+ //# sourceMappingURL=rfq.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rfq.d.ts","sourceRoot":"","sources":["../../src/services/rfq.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAgC,MAAM,UAAU,CAAC;AAEjE,qBAAa,UAAU;IACT,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,EAAE;IAEpB,MAAM,CAAC,IAAI,EAAE;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;QAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC;;;;;;;;;;;;;;;IAKK,OAAO,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;IAKlB,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;;;;IAWzD,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE;QACjC,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB;;;;;;;;;;;IAQK,YAAY,CAAC,KAAK,EAAE,MAAM;;;;;;;;;;;IAM1B,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;IAiBrC,KAAK,CAAC,KAAK,EAAE,MAAM;;;;;;;;;;;;;;;CAM1B"}
@@ -0,0 +1,60 @@
1
+ import { eq, and, desc } from "drizzle-orm";
2
+ import { rfqs, rfqResponses } from "../schema";
3
+ export class RFQService {
4
+ db;
5
+ constructor(db) {
6
+ this.db = db;
7
+ }
8
+ async create(data) {
9
+ const [rfq] = await this.db.insert(rfqs).values(data).returning();
10
+ return rfq;
11
+ }
12
+ async getById(id) {
13
+ const [rfq] = await this.db.select().from(rfqs).where(eq(rfqs.id, id));
14
+ return rfq ?? null;
15
+ }
16
+ async list(filters) {
17
+ let query = this.db.select().from(rfqs).$dynamic();
18
+ const conditions = [];
19
+ if (filters?.status)
20
+ conditions.push(eq(rfqs.status, filters.status));
21
+ if (filters?.categorySlug)
22
+ conditions.push(eq(rfqs.categorySlug, filters.categorySlug));
23
+ if (conditions.length > 0) {
24
+ query = query.where(conditions.length === 1 ? conditions[0] : and(...conditions));
25
+ }
26
+ return query.orderBy(desc(rfqs.createdAt));
27
+ }
28
+ async respond(rfqId, data) {
29
+ const [response] = await this.db.insert(rfqResponses).values({
30
+ rfqId,
31
+ ...data,
32
+ }).returning();
33
+ return response;
34
+ }
35
+ async getResponses(rfqId) {
36
+ return this.db.select().from(rfqResponses)
37
+ .where(eq(rfqResponses.rfqId, rfqId))
38
+ .orderBy(desc(rfqResponses.createdAt));
39
+ }
40
+ async award(rfqId, vendorId) {
41
+ // Mark RFQ as awarded
42
+ const [updated] = await this.db.update(rfqs).set({
43
+ status: "awarded",
44
+ awardedVendorId: vendorId,
45
+ }).where(eq(rfqs.id, rfqId)).returning();
46
+ // Mark winning response as accepted, others as rejected
47
+ const responses = await this.getResponses(rfqId);
48
+ for (const r of responses) {
49
+ const status = r.vendorId === vendorId ? "accepted" : "rejected";
50
+ await this.db.update(rfqResponses).set({ status }).where(eq(rfqResponses.id, r.id));
51
+ }
52
+ return updated ?? null;
53
+ }
54
+ async close(rfqId) {
55
+ const [updated] = await this.db.update(rfqs).set({
56
+ status: "closed",
57
+ }).where(eq(rfqs.id, rfqId)).returning();
58
+ return updated ?? null;
59
+ }
60
+ }