From 9e899057165e11f67ca87b4aab908337da973386 Mon Sep 17 00:00:00 2001 From: "it@app-quality.com" Date: Fri, 30 Jan 2026 17:43:39 +0100 Subject: [PATCH 01/13] Modified src/reference/openapi.yml --- src/reference/openapi.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/reference/openapi.yml b/src/reference/openapi.yml index 26703c487..cfcaf99f0 100644 --- a/src/reference/openapi.yml +++ b/src/reference/openapi.yml @@ -13419,6 +13419,20 @@ paths: id: 02e8ns5xdhecm security: - JWT: [] + '/campaigns/{campaign}/finance/otherCosts': + parameters: + - schema: + type: string + name: campaign + in: path + required: true + get: + summary: Your GET endpoint + tags: [] + responses: {} + operationId: get-campaigns-campaign-finance-otherCosts + x-stoplight: + id: 9hp8r67rwl59d servers: - url: 'https://api.app-quality.com' tags: From 5be9a8541eb95a59a13b51440e6f754917f594c6 Mon Sep 17 00:00:00 2001 From: "it@app-quality.com" Date: Fri, 30 Jan 2026 17:45:44 +0100 Subject: [PATCH 02/13] Modified src/reference/openapi.yml --- src/reference/openapi.yml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/reference/openapi.yml b/src/reference/openapi.yml index cfcaf99f0..016556737 100644 --- a/src/reference/openapi.yml +++ b/src/reference/openapi.yml @@ -13429,10 +13429,38 @@ paths: get: summary: Your GET endpoint tags: [] - responses: {} + responses: + '200': + description: OK + '403': + description: Forbidden + '404': + description: Not Found + '500': + description: Internal Server Error + content: + application/json: + schema: + type: object + properties: + items: + type: array + x-stoplight: + id: 7k2qykvqlzwfe + items: + x-stoplight: + id: srl106ylr6cpm + type: object + properties: + type: + type: object + x-stoplight: + id: itchthltx575n operationId: get-campaigns-campaign-finance-otherCosts x-stoplight: id: 9hp8r67rwl59d + security: + - JWT: [] servers: - url: 'https://api.app-quality.com' tags: From cd53dfa21589a4de4f05fa13303dc12679036c7a Mon Sep 17 00:00:00 2001 From: "it@app-quality.com" Date: Fri, 30 Jan 2026 17:50:26 +0100 Subject: [PATCH 03/13] Modified src/reference/openapi.yml --- src/reference/openapi.yml | 67 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/reference/openapi.yml b/src/reference/openapi.yml index 016556737..e894d3a24 100644 --- a/src/reference/openapi.yml +++ b/src/reference/openapi.yml @@ -13452,10 +13452,77 @@ paths: id: srl106ylr6cpm type: object properties: + cost_id: + type: number + x-stoplight: + id: 5tq4npphsv3wx type: type: object x-stoplight: id: itchthltx575n + properties: + name: + type: string + x-stoplight: + id: mkzkmmdtonn2u + id: + type: number + x-stoplight: + id: ng1a3g0fc695g + supplier: + type: object + x-stoplight: + id: pewi2gjqq2j1h + properties: + name: + type: string + x-stoplight: + id: eel1e2x9tsc4x + id: + type: number + x-stoplight: + id: ppawzunmvk3qx + description: + type: string + x-stoplight: + id: jlosijekgy2c6 + attachments: + type: array + x-stoplight: + id: jj9p1k4imekme + items: + x-stoplight: + id: w31uej26rl532 + type: object + properties: + id: + type: number + x-stoplight: + id: bz8ydut5na834 + url: + type: string + x-stoplight: + id: nctzb7saomq7c + mimetype: + type: string + x-stoplight: + id: 6tqj2cg96280v + examples: + Example 1: + value: + items: + - type: + name: string + id: 0 + supplier: + name: string + id: 0 + description: string + attachments: + - id: 0 + url: string + mimetype: string + cost_id: 0 operationId: get-campaigns-campaign-finance-otherCosts x-stoplight: id: 9hp8r67rwl59d From 4b30d9e4d4e7d4e05a8bd739b3712044a012556d Mon Sep 17 00:00:00 2001 From: Kariamos Date: Fri, 30 Jan 2026 18:11:23 +0100 Subject: [PATCH 04/13] feat: add endpoint for retrieving other costs associated with a campaign --- src/schema.ts | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/schema.ts b/src/schema.ts index e7ab71112..cdbfa2c57 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -822,6 +822,14 @@ export interface paths { }; }; }; + "/campaigns/{campaign}/finance/otherCosts": { + get: operations["get-campaigns-campaign-finance-otherCosts"]; + parameters: { + path: { + campaign: string; + }; + }; + }; } export interface components { @@ -5606,6 +5614,45 @@ export interface operations { 500: unknown; }; }; + "get-campaigns-campaign-finance-otherCosts": { + parameters: { + path: { + campaign: string; + }; + }; + responses: { + /** OK */ + 200: unknown; + /** Forbidden */ + 403: unknown; + /** Not Found */ + 404: unknown; + /** Internal Server Error */ + 500: { + content: { + "application/json": { + items?: { + cost_id?: number; + type?: { + name?: string; + id?: number; + }; + supplier?: { + name?: string; + id?: number; + }; + description?: string; + attachments?: { + id?: number; + url?: string; + mimetype?: string; + }[]; + }[]; + }; + }; + }; + }; + }; } export interface external {} From 14bba46a90f944f7b8997c64abb447ed95c792c2 Mon Sep 17 00:00:00 2001 From: Kariamos Date: Fri, 30 Jan 2026 18:15:56 +0100 Subject: [PATCH 05/13] fix: reorder response status codes for get campaigns campaign finance other costs endpoint --- src/reference/openapi.yml | 13 +++++++------ src/schema.ts | 14 +++++++------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/reference/openapi.yml b/src/reference/openapi.yml index e894d3a24..15e200366 100644 --- a/src/reference/openapi.yml +++ b/src/reference/openapi.yml @@ -13432,12 +13432,6 @@ paths: responses: '200': description: OK - '403': - description: Forbidden - '404': - description: Not Found - '500': - description: Internal Server Error content: application/json: schema: @@ -13523,6 +13517,13 @@ paths: url: string mimetype: string cost_id: 0 + '403': + description: Forbidden + '404': + description: Not Found + '500': + description: Internal Server Error + operationId: get-campaigns-campaign-finance-otherCosts x-stoplight: id: 9hp8r67rwl59d diff --git a/src/schema.ts b/src/schema.ts index cdbfa2c57..8fca4a1ec 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -5622,13 +5622,7 @@ export interface operations { }; responses: { /** OK */ - 200: unknown; - /** Forbidden */ - 403: unknown; - /** Not Found */ - 404: unknown; - /** Internal Server Error */ - 500: { + 200: { content: { "application/json": { items?: { @@ -5651,6 +5645,12 @@ export interface operations { }; }; }; + /** Forbidden */ + 403: unknown; + /** Not Found */ + 404: unknown; + /** Internal Server Error */ + 500: unknown; }; }; } From 45cdf6ccc12a09280c785522e13fea5886c285d2 Mon Sep 17 00:00:00 2001 From: Kariamos Date: Fri, 30 Jan 2026 18:16:22 +0100 Subject: [PATCH 06/13] wip get --- .../finance/otherCosts/_get/index.spec.ts | 305 ++++++++++++++++++ .../finance/otherCosts/_get/index.ts | 104 ++++++ 2 files changed, 409 insertions(+) create mode 100644 src/routes/campaigns/campaignId/finance/otherCosts/_get/index.spec.ts create mode 100644 src/routes/campaigns/campaignId/finance/otherCosts/_get/index.ts diff --git a/src/routes/campaigns/campaignId/finance/otherCosts/_get/index.spec.ts b/src/routes/campaigns/campaignId/finance/otherCosts/_get/index.spec.ts new file mode 100644 index 000000000..65705ae42 --- /dev/null +++ b/src/routes/campaigns/campaignId/finance/otherCosts/_get/index.spec.ts @@ -0,0 +1,305 @@ +import request from "supertest"; +import app from "@src/app"; +import { tryber } from "@src/features/database"; + +describe("GET /campaigns/campaignId/finance/otherCosts", () => { + beforeAll(async () => { + await tryber.tables.WpAppqEvdProfile.do().insert([ + { + id: 1, + name: "John", + surname: "Doe", + wp_user_id: 1, + email: "", + employment_id: 1, + education_id: 1, + }, + ]); + await tryber.tables.WpUsers.do().insert({ ID: 1 }); + await tryber.tables.WpAppqEvdCampaign.do().insert([ + { + id: 1, + platform_id: 1, + start_date: "2020-01-01", + end_date: "2020-01-01", + title: "This is the title", + page_preview_id: 1, + page_manual_id: 1, + customer_id: 1, + pm_id: 1, + project_id: 1, + customer_title: "", + }, + { + id: 2, + platform_id: 1, + start_date: "2020-01-01", + end_date: "2020-01-01", + title: "Another campaign", + page_preview_id: 1, + page_manual_id: 1, + customer_id: 1, + pm_id: 1, + project_id: 1, + customer_title: "", + }, + ]); + await tryber.tables.WpAppqCampaignOtherCostsType.do().insert([ + { + id: 1, + name: "Type 1", + }, + { + id: 2, + name: "Type 2", + }, + ]); + await tryber.tables.WpAppqCampaignOtherCostsSupplier.do().insert([ + { + id: 1, + name: "Supplier 1", + created_by: 1, + created_on: "2024-01-01 10:00:00", + }, + { + id: 2, + name: "Supplier 2", + created_by: 1, + created_on: "2024-01-02 11:00:00", + }, + ]); + await tryber.tables.WpAppqCampaignOtherCosts.do().insert([ + { + id: 1, + campaign_id: 1, + description: "Cost 1 description", + cost: 100.5, + type_id: 1, + supplier_id: 1, + }, + { + id: 2, + campaign_id: 1, + description: "Cost 2 description", + cost: 200.75, + type_id: 2, + supplier_id: 2, + }, + { + id: 3, + campaign_id: 2, + description: "Cost for other campaign", + cost: 150.0, + type_id: 1, + supplier_id: 1, + }, + ]); + await tryber.tables.WpAppqCampaignOtherCostsAttachment.do().insert([ + { + id: 1, + cost_id: 1, + url: "https://example.com/attachment1.pdf", + mime_type: "application/pdf", + }, + { + id: 2, + cost_id: 1, + url: "https://example.com/attachment2.jpg", + mime_type: "image/jpeg", + }, + { + id: 3, + cost_id: 2, + url: "https://example.com/attachment3.png", + mime_type: "image/png", + }, + ]); + }); + + afterAll(async () => { + await tryber.tables.WpAppqCampaignOtherCostsAttachment.do().delete(); + await tryber.tables.WpAppqCampaignOtherCosts.do().delete(); + await tryber.tables.WpAppqCampaignOtherCostsSupplier.do().delete(); + await tryber.tables.WpAppqCampaignOtherCostsType.do().delete(); + await tryber.tables.WpAppqEvdCampaign.do().delete(); + await tryber.tables.WpUsers.do().delete(); + await tryber.tables.WpAppqEvdProfile.do().delete(); + }); + + it("Should return 403 if logged out", async () => { + const response = await request(app).get("/campaigns/1/finance/otherCosts"); + expect(response.status).toBe(403); + }); + + it("Should return 403 if logged in as not admin user", async () => { + const response = await request(app) + .get("/campaigns/1/finance/otherCosts") + .set("Authorization", "Bearer tester"); + expect(response.status).toBe(403); + }); + + it("Should return 403 if no access to the campaign", async () => { + const response = await request(app) + .get("/campaigns/1/finance/otherCosts") + .set("Authorization", 'Bearer tester"'); + expect(response.status).toBe(403); + }); + + it("Should return 200 if logged in as admin", async () => { + const response = await request(app) + .get("/campaigns/1/finance/otherCosts") + .set("Authorization", "Bearer admin"); + expect(response.status).toBe(200); + }); + + it("Should return finance other costs - admin", async () => { + const response = await request(app) + .get("/campaigns/1/finance/otherCosts") + .set("Authorization", "Bearer admin"); + expect(response.body).toEqual( + expect.objectContaining({ + items: expect.arrayContaining([ + expect.objectContaining({ + cost_id: 1, + type: { + name: "Type 1", + id: 1, + }, + supplier: { + name: "Supplier 1", + id: 1, + }, + description: "Cost 1 description", + attachments: expect.arrayContaining([ + expect.objectContaining({ + id: 1, + url: "https://example.com/attachment1.pdf", + mimetype: "application/pdf", + }), + expect.objectContaining({ + id: 2, + url: "https://example.com/attachment2.jpg", + mimetype: "image/jpeg", + }), + ]), + }), + expect.objectContaining({ + cost_id: 2, + type: { + name: "Type 2", + id: 2, + }, + supplier: { + name: "Supplier 2", + id: 2, + }, + description: "Cost 2 description", + attachments: expect.arrayContaining([ + expect.objectContaining({ + id: 3, + url: "https://example.com/attachment3.png", + mimetype: "image/png", + }), + ]), + }), + ]), + }) + ); + expect(response.body.items).toHaveLength(2); + }); + + it("Should return other costs - olp permissions", async () => { + const response = await request(app) + .get("/campaigns/1/finance/otherCosts") + .set("Authorization", 'Bearer tester olp {"appq_campaign":[1]}'); + expect(response.status).toBe(200); + expect(response.body).toEqual( + expect.objectContaining({ + items: expect.arrayContaining([ + expect.objectContaining({ + cost_id: 1, + type: { + name: "Type 1", + id: 1, + }, + supplier: { + name: "Supplier 1", + id: 1, + }, + description: "Cost 1 description", + }), + expect.objectContaining({ + cost_id: 2, + type: { + name: "Type 2", + id: 2, + }, + supplier: { + name: "Supplier 2", + id: 2, + }, + description: "Cost 2 description", + }), + ]), + }) + ); + expect(response.body.items).toHaveLength(2); + }); + + it("Should return empty items array if no costs exist for campaign", async () => { + await tryber.tables.WpAppqEvdCampaign.do().insert({ + id: 99, + platform_id: 1, + start_date: "2020-01-01", + end_date: "2020-01-01", + title: "Campaign with no costs", + page_preview_id: 1, + page_manual_id: 1, + customer_id: 1, + pm_id: 1, + project_id: 1, + customer_title: "", + }); + + const response = await request(app) + .get("/campaigns/99/finance/otherCosts") + .set("Authorization", "Bearer admin"); + expect(response.status).toBe(200); + expect(response.body).toEqual({ + items: [], + }); + }); + + it("Should not include costs from other campaigns", async () => { + const response = await request(app) + .get("/campaigns/1/finance/otherCosts") + .set("Authorization", "Bearer admin"); + expect(response.status).toBe(200); + expect(response.body.items).toHaveLength(2); + expect( + response.body.items.find((item: any) => item.cost_id === 3) + ).toBeUndefined(); + }); + + it("Should return cost with empty attachments array if cost has no attachments", async () => { + await tryber.tables.WpAppqCampaignOtherCosts.do().insert({ + id: 10, + campaign_id: 1, + description: "Cost without attachments", + cost: 50.0, + type_id: 1, + supplier_id: 1, + }); + + const response = await request(app) + .get("/campaigns/1/finance/otherCosts") + .set("Authorization", "Bearer admin"); + expect(response.status).toBe(200); + + const costWithoutAttachments = response.body.items.find( + (item: any) => item.cost_id === 10 + ); + expect(costWithoutAttachments).toBeDefined(); + expect(costWithoutAttachments.attachments).toEqual([]); + }); +}); diff --git a/src/routes/campaigns/campaignId/finance/otherCosts/_get/index.ts b/src/routes/campaigns/campaignId/finance/otherCosts/_get/index.ts new file mode 100644 index 000000000..e3d48bd92 --- /dev/null +++ b/src/routes/campaigns/campaignId/finance/otherCosts/_get/index.ts @@ -0,0 +1,104 @@ +/** OPENAPI-CLASS: get-campaigns-campaign-finance-otherCosts */ + +import CampaignRoute from "@src/features/routes/CampaignRoute"; +import { tryber } from "@src/features/database"; +import OpenapiError from "@src/features/OpenapiError"; + +type OtherCost = { + cost_id: number; + type: { + name: string; + id: number; + }; + supplier: { + name: string; + id: number; + }; + description: string; + attachments: { + id: number; + url: string; + mimetype: string; + }[]; +}; + +export default class OtherCostsRoute extends CampaignRoute<{ + response: StoplightOperations["get-campaigns-campaign-finance-otherCosts"]["responses"]["200"]["content"]["application/json"]; + parameters: StoplightOperations["get-campaigns-campaign-finance-otherCosts"]["parameters"]["path"]; +}> { + protected async filter(): Promise { + if (!(await super.filter())) return false; + + if (!this.hasAccessToCampaign(this.cp_id)) { + this.setError(403, new OpenapiError("Access denied")); + + return false; + } + return true; + } + + protected async prepare(): Promise { + const costs = await this.getOtherCosts(); + + return this.setSuccess(200, { items: costs }); + } + + private async getOtherCosts(): Promise { + const costs = await tryber.tables.WpAppqCampaignOtherCosts.do() + .select( + tryber + .ref("id") + .withSchema("wp_appq_campaign_other_costs") + .as("cost_id"), + "description", + "type_id", + "supplier_id" + ) + .where("campaign_id", this.cp_id); + + if (!costs.length) return []; + + const typeIds = [...new Set(costs.map((c) => c.type_id))]; + const supplierIds = [...new Set(costs.map((c) => c.supplier_id))]; + const costIds = costs.map((c) => c.cost_id); + + const types = await tryber.tables.WpAppqCampaignOtherCostsType.do() + .select("id", "name") + .whereIn("id", typeIds); + + const suppliers = await tryber.tables.WpAppqCampaignOtherCostsSupplier.do() + .select("id", "name") + .whereIn("id", supplierIds); + + const attachments = + await tryber.tables.WpAppqCampaignOtherCostsAttachment.do() + .select("id", "url", "mime_type", "cost_id") + .whereIn("cost_id", costIds); + + return costs.map((cost) => { + const type = types.find((t) => t.id === cost.type_id); + const supplier = suppliers.find((s) => s.id === cost.supplier_id); + const costAttachments = attachments.filter( + (a) => a.cost_id === cost.cost_id + ); + + return { + cost_id: cost.cost_id, + type: { + name: type?.name || "", + id: type?.id || 0, + }, + supplier: { + name: supplier?.name || "", + id: supplier?.id || 0, + }, + description: cost.description, + attachments: costAttachments.map((a) => ({ + id: a.id, + url: a.url, + mimetype: a.mime_type, + })), + }; + }); + } +} From f9b769ade3fa88f76e3fb285bf4ca3543f358f36 Mon Sep 17 00:00:00 2001 From: "it@app-quality.com" Date: Fri, 30 Jan 2026 18:40:46 +0100 Subject: [PATCH 07/13] Modified src/reference/openapi.yml --- src/reference/openapi.yml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/reference/openapi.yml b/src/reference/openapi.yml index 15e200366..46dd74eb5 100644 --- a/src/reference/openapi.yml +++ b/src/reference/openapi.yml @@ -13299,6 +13299,7 @@ paths: type: object required: - name + - id properties: name: type: string @@ -13312,11 +13313,16 @@ paths: type: integer x-stoplight: id: gcs2p8mvl32gc + id: + type: number + x-stoplight: + id: ek7zfipegepg5 examples: Example 2: value: items: - - name: Respondent + - id: 1 + name: Respondent created_at: '2026-01-01' created_by: 10 '400': @@ -13344,7 +13350,17 @@ paths: responses: '201': description: Created - content: {} + content: + application/json: + schema: + type: object + required: + - supplier_id + properties: + supplier_id: + type: number + x-stoplight: + id: rsrwvlerw0w2j '400': description: Bad Request '403': @@ -13523,7 +13539,6 @@ paths: description: Not Found '500': description: Internal Server Error - operationId: get-campaigns-campaign-finance-otherCosts x-stoplight: id: 9hp8r67rwl59d From ec8857db494c07efdca8e7fc67772e4923bdc97e Mon Sep 17 00:00:00 2001 From: Kariamos Date: Fri, 30 Jan 2026 18:52:18 +0100 Subject: [PATCH 08/13] fix: add missing commas in SQL query and attachment filter From 41583a7403d87df35b79e8d94a8726ebd2e3a9e9 Mon Sep 17 00:00:00 2001 From: Kariamos Date: Fri, 30 Jan 2026 18:52:52 +0100 Subject: [PATCH 09/13] feat: enhance supplier endpoints to include supplier ID in responses and update schema --- .../finance/supplier/_get/index.spec.ts | 4 ++++ .../campaignId/finance/supplier/_get/index.ts | 2 ++ .../finance/supplier/_post/index.spec.ts | 2 ++ .../finance/supplier/_post/index.ts | 24 ++++++++++++------- src/schema.ts | 9 ++++++- 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/routes/campaigns/campaignId/finance/supplier/_get/index.spec.ts b/src/routes/campaigns/campaignId/finance/supplier/_get/index.spec.ts index ee1bf1af0..ab4f40ee6 100644 --- a/src/routes/campaigns/campaignId/finance/supplier/_get/index.spec.ts +++ b/src/routes/campaigns/campaignId/finance/supplier/_get/index.spec.ts @@ -95,11 +95,13 @@ describe("GET /campaigns/campaignId/finance/supplier", () => { expect.objectContaining({ items: expect.arrayContaining([ expect.objectContaining({ + id: 1, name: "Supplier 1", created_by: 1, created_on: "2024-01-01 10:00:00", }), expect.objectContaining({ + id: 2, name: "Supplier 2", created_by: 2, created_on: "2024-01-02 11:00:00", @@ -119,11 +121,13 @@ describe("GET /campaigns/campaignId/finance/supplier", () => { expect.objectContaining({ items: expect.arrayContaining([ expect.objectContaining({ + id: 1, name: "Supplier 1", created_by: 1, created_on: "2024-01-01 10:00:00", }), expect.objectContaining({ + id: 2, name: "Supplier 2", created_by: 2, created_on: "2024-01-02 11:00:00", diff --git a/src/routes/campaigns/campaignId/finance/supplier/_get/index.ts b/src/routes/campaigns/campaignId/finance/supplier/_get/index.ts index 807584851..da33fa92a 100644 --- a/src/routes/campaigns/campaignId/finance/supplier/_get/index.ts +++ b/src/routes/campaigns/campaignId/finance/supplier/_get/index.ts @@ -5,6 +5,7 @@ import { tryber } from "@src/features/database"; import OpenapiError from "@src/features/OpenapiError"; type Supplier = { + id: number; name: string; created_on?: string; created_by?: number; @@ -33,6 +34,7 @@ export default class SupplierRoute extends CampaignRoute<{ private async getSuppliers(): Promise { return await tryber.tables.WpAppqCampaignOtherCostsSupplier.do().select( + "id", "name", "created_on", "created_by" diff --git a/src/routes/campaigns/campaignId/finance/supplier/_post/index.spec.ts b/src/routes/campaigns/campaignId/finance/supplier/_post/index.spec.ts index 0fabdaaf6..649f9ffca 100644 --- a/src/routes/campaigns/campaignId/finance/supplier/_post/index.spec.ts +++ b/src/routes/campaigns/campaignId/finance/supplier/_post/index.spec.ts @@ -101,6 +101,7 @@ describe("POST /campaigns/campaignId/finance/supplier", () => { .send({ name: "New Supplier" }) .set("Authorization", "Bearer admin"); expect(response.status).toBe(201); + expect(response.body).toEqual({ supplier_id: expect.any(Number) }); }); it("Should not add existing supplier", async () => { const response = await request(app) @@ -131,6 +132,7 @@ describe("POST /campaigns/campaignId/finance/supplier", () => { .send({ name: "New Supplier" }) .set("Authorization", 'Bearer tester olp {"appq_campaign":[1]}'); expect(response.status).toBe(201); + expect(response.body).toEqual({ supplier_id: expect.any(Number) }); }); it("Should not add existing supplier", async () => { diff --git a/src/routes/campaigns/campaignId/finance/supplier/_post/index.ts b/src/routes/campaigns/campaignId/finance/supplier/_post/index.ts index 031568347..a6fa15ef7 100644 --- a/src/routes/campaigns/campaignId/finance/supplier/_post/index.ts +++ b/src/routes/campaigns/campaignId/finance/supplier/_post/index.ts @@ -5,7 +5,7 @@ import { tryber } from "@src/features/database"; import OpenapiError from "@src/features/OpenapiError"; export default class SupplierRoute extends CampaignRoute<{ - response: StoplightOperations["post-campaigns-campaign-finance-supplier"]["responses"]["201"]; + response: StoplightOperations["post-campaigns-campaign-finance-supplier"]["responses"]["201"]["content"]["application/json"]; parameters: StoplightOperations["post-campaigns-campaign-finance-supplier"]["parameters"]["path"]; body: StoplightOperations["post-campaigns-campaign-finance-supplier"]["requestBody"]["content"]["application/json"]; }> { @@ -32,8 +32,8 @@ export default class SupplierRoute extends CampaignRoute<{ } try { - await this.createNewSupplier(this.getBody().name); - return this.setSuccess(201, {}); + const supplierId = await this.createNewSupplier(this.getBody().name); + return this.setSuccess(201, { supplier_id: supplierId }); } catch (e) { console.error("Error creating new supplier: ", e); return this.setError( @@ -43,11 +43,19 @@ export default class SupplierRoute extends CampaignRoute<{ } } - private async createNewSupplier(name: string): Promise { - await tryber.tables.WpAppqCampaignOtherCostsSupplier.do().insert({ - name, - created_by: this.getTesterId(), - }); + private async createNewSupplier(name: string): Promise { + const result = await tryber.tables.WpAppqCampaignOtherCostsSupplier.do() + .insert({ + name, + created_by: this.getTesterId(), + }) + .returning("id"); + + const id = result[0]?.id ?? result[0]; + + if (!id) throw new Error("Error creating supplier"); + + return id; } private async checkSupplierExists(name: string): Promise { diff --git a/src/schema.ts b/src/schema.ts index 8fca4a1ec..7fd1eec9b 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -5555,6 +5555,7 @@ export interface operations { name: string; created_at?: string; created_by?: number; + id: number; }[]; }; }; @@ -5575,7 +5576,13 @@ export interface operations { }; responses: { /** Created */ - 201: unknown; + 201: { + content: { + "application/json": { + supplier_id: number; + }; + }; + }; /** Bad Request */ 400: unknown; /** Forbidden */ From 894e27e387a7356127f4f710a560c3028a2d7617 Mon Sep 17 00:00:00 2001 From: "it@app-quality.com" Date: Fri, 30 Jan 2026 18:54:28 +0100 Subject: [PATCH 10/13] Modified src/reference/openapi.yml --- src/reference/openapi.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/reference/openapi.yml b/src/reference/openapi.yml index 46dd74eb5..bfd704803 100644 --- a/src/reference/openapi.yml +++ b/src/reference/openapi.yml @@ -13413,17 +13413,26 @@ paths: x-stoplight: id: r1p9rcymebjpu type: object + required: + - name + - id properties: name: type: string x-stoplight: id: y0keeoutvijjh + id: + type: number + x-stoplight: + id: r546xns1ecc4j examples: Example 1: value: items: - name: Recruiting + id: 1 - name: Survey + id: 2 '403': $ref: '#/components/responses/NotAuthorized' '404': From 3c26801c30e1863bdeabf785cb85d751eb392f8b Mon Sep 17 00:00:00 2001 From: Kariamos Date: Fri, 30 Jan 2026 18:57:15 +0100 Subject: [PATCH 11/13] feat: include ID in finance type responses and update schema --- .../campaigns/campaignId/finance/type/_get/index.spec.ts | 4 ++++ src/routes/campaigns/campaignId/finance/type/_get/index.ts | 5 ++++- src/schema.ts | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/routes/campaigns/campaignId/finance/type/_get/index.spec.ts b/src/routes/campaigns/campaignId/finance/type/_get/index.spec.ts index 3c9580e41..300f8d481 100644 --- a/src/routes/campaigns/campaignId/finance/type/_get/index.spec.ts +++ b/src/routes/campaigns/campaignId/finance/type/_get/index.spec.ts @@ -82,9 +82,11 @@ describe("GET /campaigns/campaignId/finance/type", () => { expect.objectContaining({ items: expect.arrayContaining([ expect.objectContaining({ + id: 1, name: "Type 1", }), expect.objectContaining({ + id: 2, name: "Type 2", }), ]), @@ -102,9 +104,11 @@ describe("GET /campaigns/campaignId/finance/type", () => { expect.objectContaining({ items: expect.arrayContaining([ expect.objectContaining({ + id: 1, name: "Type 1", }), expect.objectContaining({ + id: 2, name: "Type 2", }), ]), diff --git a/src/routes/campaigns/campaignId/finance/type/_get/index.ts b/src/routes/campaigns/campaignId/finance/type/_get/index.ts index 484995ac4..3b8f5e66d 100644 --- a/src/routes/campaigns/campaignId/finance/type/_get/index.ts +++ b/src/routes/campaigns/campaignId/finance/type/_get/index.ts @@ -26,6 +26,9 @@ export default class TypeRoute extends CampaignRoute<{ } private async getTypes() { - return await tryber.tables.WpAppqCampaignOtherCostsType.do().select("name"); + return await tryber.tables.WpAppqCampaignOtherCostsType.do().select( + "name", + "id" + ); } } diff --git a/src/schema.ts b/src/schema.ts index 7fd1eec9b..1a508ac4e 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -5610,7 +5610,8 @@ export interface operations { content: { "application/json": { items: { - name?: string; + name: string; + id: number; }[]; }; }; From 4d1b170b9a41bdcf4f51f088908f094f70b89edb Mon Sep 17 00:00:00 2001 From: Kariamos Date: Mon, 2 Feb 2026 10:33:16 +0100 Subject: [PATCH 12/13] fix: correct cost_id order --- .../campaignId/finance/otherCosts/_get/index.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/campaigns/campaignId/finance/otherCosts/_get/index.spec.ts b/src/routes/campaigns/campaignId/finance/otherCosts/_get/index.spec.ts index 65705ae42..a575d52b6 100644 --- a/src/routes/campaigns/campaignId/finance/otherCosts/_get/index.spec.ts +++ b/src/routes/campaigns/campaignId/finance/otherCosts/_get/index.spec.ts @@ -97,21 +97,21 @@ describe("GET /campaigns/campaignId/finance/otherCosts", () => { await tryber.tables.WpAppqCampaignOtherCostsAttachment.do().insert([ { id: 1, - cost_id: 1, url: "https://example.com/attachment1.pdf", mime_type: "application/pdf", + cost_id: 1, }, { id: 2, - cost_id: 1, url: "https://example.com/attachment2.jpg", mime_type: "image/jpeg", + cost_id: 1, }, { id: 3, - cost_id: 2, url: "https://example.com/attachment3.png", mime_type: "image/png", + cost_id: 2, }, ]); }); From 3cb200180e289c55a4b6f9e0ceb3ac264ac0d11e Mon Sep 17 00:00:00 2001 From: Kariamos Date: Mon, 2 Feb 2026 10:34:54 +0100 Subject: [PATCH 13/13] chore: update @appquality/tryber-database to version 0.46.18 in package.json and package-lock.json --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d6091ad94..d8c15a6e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@appquality/tryber-database": "^0.46.17", + "@appquality/tryber-database": "^0.46.18", "@appquality/wp-auth": "^1.0.7", "@aws-crypto/sha256-js": "^5.2.0", "@aws-sdk/hash-node": "^3.374.0", @@ -108,9 +108,9 @@ } }, "node_modules/@appquality/tryber-database": { - "version": "0.46.17", - "resolved": "https://registry.npmjs.org/@appquality/tryber-database/-/tryber-database-0.46.17.tgz", - "integrity": "sha512-pipi0ypxbkSqkGD69yEx/6M8yYkd3uCevS0Gw7tc97MieSgsGU75X/CIBF7Adr04xbk1tPMLIS7uZ9Q+nJ10vQ==", + "version": "0.46.18", + "resolved": "https://registry.npmjs.org/@appquality/tryber-database/-/tryber-database-0.46.18.tgz", + "integrity": "sha512-7G9GIX3gpWCJJpo/DgdrSXRu/Jm/MJnbhglT9q0SoemEQQPZwSREFnxbGXxqg77URTeG7iNHXRzH1DXSkCVmSg==", "license": "ISC", "dependencies": { "better-sqlite3": "^12.5.0", diff --git a/package.json b/package.json index 1203c771a..9a1103eb5 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "author": "", "license": "ISC", "dependencies": { - "@appquality/tryber-database": "^0.46.17", + "@appquality/tryber-database": "^0.46.18", "@appquality/wp-auth": "^1.0.7", "@aws-crypto/sha256-js": "^5.2.0", "@aws-sdk/hash-node": "^3.374.0",