@open-mercato/core 0.4.5-develop-0f0e676c72 → 0.4.5-develop-8f98466993
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/entities/customer_deal/index.js +4 -0
- package/dist/generated/entities/customer_deal/index.js.map +2 -2
- package/dist/generated/entities/customer_pipeline/index.js +17 -0
- package/dist/generated/entities/customer_pipeline/index.js.map +7 -0
- package/dist/generated/entities/customer_pipeline_stage/index.js +19 -0
- package/dist/generated/entities/customer_pipeline_stage/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +2 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +4 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/customers/acl.js +2 -0
- package/dist/modules/customers/acl.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/route.js +4 -0
- package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +12 -0
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -1
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js +69 -0
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +7 -0
- package/dist/modules/customers/api/pipeline-stages/route.js +275 -0
- package/dist/modules/customers/api/pipeline-stages/route.js.map +7 -0
- package/dist/modules/customers/api/pipelines/route.js +245 -0
- package/dist/modules/customers/api/pipelines/route.js.map +7 -0
- package/dist/modules/customers/backend/config/customers/page.js +2 -0
- package/dist/modules/customers/backend/config/customers/page.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +439 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +7 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +17 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +19 -1
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +35 -1
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +102 -74
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/cli.js +28 -2
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +34 -2
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/index.js +2 -0
- package/dist/modules/customers/commands/index.js.map +2 -2
- package/dist/modules/customers/commands/pipeline-stages.js +126 -0
- package/dist/modules/customers/commands/pipeline-stages.js.map +7 -0
- package/dist/modules/customers/commands/pipelines.js +87 -0
- package/dist/modules/customers/commands/pipelines.js.map +7 -0
- package/dist/modules/customers/components/DictionarySettings.js +0 -5
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +474 -0
- package/dist/modules/customers/components/PipelineSettings.js.map +7 -0
- package/dist/modules/customers/components/detail/DealForm.js +84 -12
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/data/entities.js +78 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +44 -0
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260218191730.js +77 -0
- package/dist/modules/customers/migrations/Migration20260218191730.js.map +7 -0
- package/dist/modules/customers/setup.js +7 -3
- package/dist/modules/customers/setup.js.map +2 -2
- package/dist/modules/workflows/migrations/Migration20260222205305.js +14 -0
- package/dist/modules/workflows/migrations/Migration20260222205305.js.map +7 -0
- package/generated/entities/customer_deal/index.ts +2 -0
- package/generated/entities/customer_pipeline/index.ts +7 -0
- package/generated/entities/customer_pipeline_stage/index.ts +8 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +4 -0
- package/package.json +2 -2
- package/src/modules/customers/acl.ts +2 -0
- package/src/modules/customers/api/deals/[id]/route.ts +4 -0
- package/src/modules/customers/api/deals/route.ts +12 -0
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +21 -1
- package/src/modules/customers/api/pipeline-stages/reorder/route.ts +71 -0
- package/src/modules/customers/api/pipeline-stages/route.ts +296 -0
- package/src/modules/customers/api/pipelines/route.ts +261 -0
- package/src/modules/customers/backend/config/customers/page.tsx +2 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +13 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +512 -0
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +21 -1
- package/src/modules/customers/backend/customers/deals/page.tsx +33 -1
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +119 -79
- package/src/modules/customers/cli.ts +29 -1
- package/src/modules/customers/commands/deals.ts +44 -1
- package/src/modules/customers/commands/index.ts +2 -0
- package/src/modules/customers/commands/pipeline-stages.ts +156 -0
- package/src/modules/customers/commands/pipelines.ts +105 -0
- package/src/modules/customers/components/DictionarySettings.tsx +0 -5
- package/src/modules/customers/components/PipelineSettings.tsx +570 -0
- package/src/modules/customers/components/detail/DealForm.tsx +89 -11
- package/src/modules/customers/data/entities.ts +64 -0
- package/src/modules/customers/data/validators.ts +57 -0
- package/src/modules/customers/i18n/de.json +4 -0
- package/src/modules/customers/i18n/en.json +4 -0
- package/src/modules/customers/i18n/es.json +4 -0
- package/src/modules/customers/i18n/pl.json +5 -1
- package/src/modules/customers/migrations/Migration20260218191730.ts +84 -0
- package/src/modules/customers/setup.ts +5 -1
- package/src/modules/workflows/migrations/Migration20260222205305.ts +13 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { seedCustomerDictionaries, seedCurrencyDictionary, seedCustomerExamples } from "./cli.js";
|
|
1
|
+
import { seedCustomerDictionaries, seedCurrencyDictionary, seedCustomerExamples, seedDefaultPipeline } from "./cli.js";
|
|
2
2
|
const setup = {
|
|
3
3
|
seedDefaults: async (ctx) => {
|
|
4
4
|
const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
|
|
5
5
|
await seedCustomerDictionaries(ctx.em, scope);
|
|
6
6
|
await seedCurrencyDictionary(ctx.em, scope);
|
|
7
|
+
await seedDefaultPipeline(ctx.em, scope);
|
|
7
8
|
},
|
|
8
9
|
seedExamples: async (ctx) => {
|
|
9
10
|
const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId };
|
|
@@ -17,14 +18,17 @@ const setup = {
|
|
|
17
18
|
"customers.companies.view",
|
|
18
19
|
"customers.companies.manage",
|
|
19
20
|
"customers.deals.view",
|
|
20
|
-
"customers.deals.manage"
|
|
21
|
+
"customers.deals.manage",
|
|
22
|
+
"customers.pipelines.view",
|
|
23
|
+
"customers.pipelines.manage"
|
|
21
24
|
],
|
|
22
25
|
employee: [
|
|
23
26
|
"customers.*",
|
|
24
27
|
"customers.people.view",
|
|
25
28
|
"customers.people.manage",
|
|
26
29
|
"customers.companies.view",
|
|
27
|
-
"customers.companies.manage"
|
|
30
|
+
"customers.companies.manage",
|
|
31
|
+
"customers.pipelines.view"
|
|
28
32
|
]
|
|
29
33
|
}
|
|
30
34
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/customers/setup.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport { seedCustomerDictionaries, seedCurrencyDictionary, seedCustomerExamples } from './cli'\n\nexport const setup: ModuleSetupConfig = {\n seedDefaults: async (ctx) => {\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await seedCustomerDictionaries(ctx.em, scope)\n await seedCurrencyDictionary(ctx.em, scope)\n },\n\n seedExamples: async (ctx) => {\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await seedCustomerExamples(ctx.em, ctx.container, scope)\n },\n\n defaultRoleFeatures: {\n admin: [\n 'customers.*',\n 'customers.people.view',\n 'customers.people.manage',\n 'customers.companies.view',\n 'customers.companies.manage',\n 'customers.deals.view',\n 'customers.deals.manage',\n ],\n employee: [\n 'customers.*',\n 'customers.people.view',\n 'customers.people.manage',\n 'customers.companies.view',\n 'customers.companies.manage',\n ],\n },\n}\n\nexport default setup\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,0BAA0B,wBAAwB,
|
|
4
|
+
"sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport { seedCustomerDictionaries, seedCurrencyDictionary, seedCustomerExamples, seedDefaultPipeline } from './cli'\n\nexport const setup: ModuleSetupConfig = {\n seedDefaults: async (ctx) => {\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await seedCustomerDictionaries(ctx.em, scope)\n await seedCurrencyDictionary(ctx.em, scope)\n await seedDefaultPipeline(ctx.em, scope)\n },\n\n seedExamples: async (ctx) => {\n const scope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId }\n await seedCustomerExamples(ctx.em, ctx.container, scope)\n },\n\n defaultRoleFeatures: {\n admin: [\n 'customers.*',\n 'customers.people.view',\n 'customers.people.manage',\n 'customers.companies.view',\n 'customers.companies.manage',\n 'customers.deals.view',\n 'customers.deals.manage',\n 'customers.pipelines.view',\n 'customers.pipelines.manage',\n ],\n employee: [\n 'customers.*',\n 'customers.people.view',\n 'customers.people.manage',\n 'customers.companies.view',\n 'customers.companies.manage',\n 'customers.pipelines.view',\n ],\n },\n}\n\nexport default setup\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,0BAA0B,wBAAwB,sBAAsB,2BAA2B;AAErG,MAAM,QAA2B;AAAA,EACtC,cAAc,OAAO,QAAQ;AAC3B,UAAM,QAAQ,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAC3E,UAAM,yBAAyB,IAAI,IAAI,KAAK;AAC5C,UAAM,uBAAuB,IAAI,IAAI,KAAK;AAC1C,UAAM,oBAAoB,IAAI,IAAI,KAAK;AAAA,EACzC;AAAA,EAEA,cAAc,OAAO,QAAQ;AAC3B,UAAM,QAAQ,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAC3E,UAAM,qBAAqB,IAAI,IAAI,IAAI,WAAW,KAAK;AAAA,EACzD;AAAA,EAEA,qBAAqB;AAAA,IACnB,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Migration } from "@mikro-orm/migrations";
|
|
2
|
+
class Migration20260222205305 extends Migration {
|
|
3
|
+
async up() {
|
|
4
|
+
this.addSql(`create table if not exists "workflow_event_triggers" ("id" uuid not null default gen_random_uuid(), "name" varchar(255) not null, "description" text null, "workflow_definition_id" uuid not null, "event_pattern" varchar(255) not null, "config" jsonb null, "enabled" boolean not null default true, "priority" int not null default 0, "tenant_id" uuid not null, "organization_id" uuid not null, "created_by" varchar(255) null, "updated_by" varchar(255) null, "created_at" timestamptz not null, "updated_at" timestamptz not null, "deleted_at" timestamptz null, constraint "workflow_event_triggers_pkey" primary key ("id"));`);
|
|
5
|
+
this.addSql(`create index if not exists "workflow_event_triggers_enabled_priority_idx" on "workflow_event_triggers" ("enabled", "priority");`);
|
|
6
|
+
this.addSql(`create index if not exists "workflow_event_triggers_tenant_org_idx" on "workflow_event_triggers" ("tenant_id", "organization_id");`);
|
|
7
|
+
this.addSql(`create index if not exists "workflow_event_triggers_definition_idx" on "workflow_event_triggers" ("workflow_definition_id");`);
|
|
8
|
+
this.addSql(`create index if not exists "workflow_event_triggers_event_pattern_idx" on "workflow_event_triggers" ("event_pattern", "enabled");`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
Migration20260222205305
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=Migration20260222205305.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/workflows/migrations/Migration20260222205305.ts"],
|
|
4
|
+
"sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260222205305 extends Migration {\n\n override async up(): Promise<void> {\n this.addSql(`create table if not exists \"workflow_event_triggers\" (\"id\" uuid not null default gen_random_uuid(), \"name\" varchar(255) not null, \"description\" text null, \"workflow_definition_id\" uuid not null, \"event_pattern\" varchar(255) not null, \"config\" jsonb null, \"enabled\" boolean not null default true, \"priority\" int not null default 0, \"tenant_id\" uuid not null, \"organization_id\" uuid not null, \"created_by\" varchar(255) null, \"updated_by\" varchar(255) null, \"created_at\" timestamptz not null, \"updated_at\" timestamptz not null, \"deleted_at\" timestamptz null, constraint \"workflow_event_triggers_pkey\" primary key (\"id\"));`);\n this.addSql(`create index if not exists \"workflow_event_triggers_enabled_priority_idx\" on \"workflow_event_triggers\" (\"enabled\", \"priority\");`);\n this.addSql(`create index if not exists \"workflow_event_triggers_tenant_org_idx\" on \"workflow_event_triggers\" (\"tenant_id\", \"organization_id\");`);\n this.addSql(`create index if not exists \"workflow_event_triggers_definition_idx\" on \"workflow_event_triggers\" (\"workflow_definition_id\");`);\n this.addSql(`create index if not exists \"workflow_event_triggers_event_pattern_idx\" on \"workflow_event_triggers\" (\"event_pattern\", \"enabled\");`);\n }\n\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,gCAAgC,UAAU;AAAA,EAErD,MAAe,KAAoB;AACjC,SAAK,OAAO,4mBAA4mB;AACxnB,SAAK,OAAO,iIAAiI;AAC7I,SAAK,OAAO,oIAAoI;AAChJ,SAAK,OAAO,8HAA8H;AAC1I,SAAK,OAAO,mIAAmI;AAAA,EACjJ;AAEF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -5,6 +5,8 @@ export const title = 'title'
|
|
|
5
5
|
export const description = 'description'
|
|
6
6
|
export const status = 'status'
|
|
7
7
|
export const pipeline_stage = 'pipeline_stage'
|
|
8
|
+
export const pipeline_id = 'pipeline_id'
|
|
9
|
+
export const pipeline_stage_id = 'pipeline_stage_id'
|
|
8
10
|
export const value_amount = 'value_amount'
|
|
9
11
|
export const value_currency = 'value_currency'
|
|
10
12
|
export const probability = 'probability'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const id = 'id'
|
|
2
|
+
export const organization_id = 'organization_id'
|
|
3
|
+
export const tenant_id = 'tenant_id'
|
|
4
|
+
export const pipeline_id = 'pipeline_id'
|
|
5
|
+
export const name = 'name'
|
|
6
|
+
export const position = 'position'
|
|
7
|
+
export const created_at = 'created_at'
|
|
8
|
+
export const updated_at = 'updated_at'
|
|
@@ -63,6 +63,8 @@ export const E = {
|
|
|
63
63
|
"customer_tag": "customers:customer_tag",
|
|
64
64
|
"customer_tag_assignment": "customers:customer_tag_assignment",
|
|
65
65
|
"customer_dictionary_entry": "customers:customer_dictionary_entry",
|
|
66
|
+
"customer_pipeline": "customers:customer_pipeline",
|
|
67
|
+
"customer_pipeline_stage": "customers:customer_pipeline_stage",
|
|
66
68
|
"customer_todo_link": "customers:customer_todo_link"
|
|
67
69
|
},
|
|
68
70
|
"perspectives": {
|
|
@@ -35,6 +35,8 @@ import * as customer_deal_person_link from './entities/customer_deal_person_link
|
|
|
35
35
|
import * as customer_dictionary_entry from './entities/customer_dictionary_entry/index'
|
|
36
36
|
import * as customer_entity from './entities/customer_entity/index'
|
|
37
37
|
import * as customer_person_profile from './entities/customer_person_profile/index'
|
|
38
|
+
import * as customer_pipeline from './entities/customer_pipeline/index'
|
|
39
|
+
import * as customer_pipeline_stage from './entities/customer_pipeline_stage/index'
|
|
38
40
|
import * as customer_settings from './entities/customer_settings/index'
|
|
39
41
|
import * as customer_tag from './entities/customer_tag/index'
|
|
40
42
|
import * as customer_tag_assignment from './entities/customer_tag_assignment/index'
|
|
@@ -169,6 +171,8 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
|
|
|
169
171
|
customer_dictionary_entry,
|
|
170
172
|
customer_entity,
|
|
171
173
|
customer_person_profile,
|
|
174
|
+
customer_pipeline,
|
|
175
|
+
customer_pipeline_stage,
|
|
172
176
|
customer_settings,
|
|
173
177
|
customer_tag,
|
|
174
178
|
customer_tag_assignment,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.5-develop-
|
|
3
|
+
"version": "0.4.5-develop-8f98466993",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dependencies": {
|
|
210
|
-
"@open-mercato/shared": "0.4.5-develop-
|
|
210
|
+
"@open-mercato/shared": "0.4.5-develop-8f98466993",
|
|
211
211
|
"@types/semver": "^7.5.8",
|
|
212
212
|
"@xyflow/react": "^12.6.0",
|
|
213
213
|
"ai": "^6.0.0",
|
|
@@ -8,6 +8,8 @@ export const features = [
|
|
|
8
8
|
{ id: 'customers.activities.view', title: 'View activities', module: 'customers' },
|
|
9
9
|
{ id: 'customers.activities.manage', title: 'Manage activities', module: 'customers' },
|
|
10
10
|
{ id: 'customers.settings.manage', title: 'Manage customer settings', module: 'customers' },
|
|
11
|
+
{ id: 'customers.pipelines.view', title: 'View pipelines', module: 'customers' },
|
|
12
|
+
{ id: 'customers.pipelines.manage', title: 'Manage pipelines', module: 'customers' },
|
|
11
13
|
{ id: 'customers.widgets.todos', title: 'Use customer todos widget', module: 'customers' },
|
|
12
14
|
{ id: 'customers.widgets.next-interactions', title: 'Use customer next interactions widget', module: 'customers' },
|
|
13
15
|
{ id: 'customers.widgets.new-customers', title: 'Use customer new customers widget', module: 'customers' },
|
|
@@ -212,6 +212,8 @@ export async function GET(request: Request, context: { params?: Record<string, u
|
|
|
212
212
|
description: deal.description ?? null,
|
|
213
213
|
status: deal.status ?? null,
|
|
214
214
|
pipelineStage: deal.pipelineStage ?? null,
|
|
215
|
+
pipelineId: deal.pipelineId ?? null,
|
|
216
|
+
pipelineStageId: deal.pipelineStageId ?? null,
|
|
215
217
|
valueAmount: deal.valueAmount ?? null,
|
|
216
218
|
valueCurrency: deal.valueCurrency ?? null,
|
|
217
219
|
probability: deal.probability ?? null,
|
|
@@ -241,6 +243,8 @@ const dealDetailResponseSchema = z.object({
|
|
|
241
243
|
description: z.string().nullable().optional(),
|
|
242
244
|
status: z.string().nullable().optional(),
|
|
243
245
|
pipelineStage: z.string().nullable().optional(),
|
|
246
|
+
pipelineId: z.string().uuid().nullable().optional(),
|
|
247
|
+
pipelineStageId: z.string().uuid().nullable().optional(),
|
|
244
248
|
valueAmount: z.number().nullable().optional(),
|
|
245
249
|
valueCurrency: z.string().nullable().optional(),
|
|
246
250
|
probability: z.number().nullable().optional(),
|
|
@@ -25,6 +25,8 @@ const listSchema = z
|
|
|
25
25
|
search: z.string().optional(),
|
|
26
26
|
status: z.string().optional(),
|
|
27
27
|
pipelineStage: z.string().optional(),
|
|
28
|
+
pipelineId: z.string().uuid().optional(),
|
|
29
|
+
pipelineStageId: z.string().uuid().optional(),
|
|
28
30
|
sortField: z.string().optional(),
|
|
29
31
|
sortDir: z.enum(['asc', 'desc']).optional(),
|
|
30
32
|
personEntityId: z.string().uuid().optional(),
|
|
@@ -98,6 +100,8 @@ const crud = makeCrudRoute<unknown, unknown, DealListQuery>({
|
|
|
98
100
|
'description',
|
|
99
101
|
'status',
|
|
100
102
|
'pipeline_stage',
|
|
103
|
+
'pipeline_id',
|
|
104
|
+
'pipeline_stage_id',
|
|
101
105
|
'value_amount',
|
|
102
106
|
'value_currency',
|
|
103
107
|
'probability',
|
|
@@ -129,6 +133,12 @@ const crud = makeCrudRoute<unknown, unknown, DealListQuery>({
|
|
|
129
133
|
if (query.pipelineStage) {
|
|
130
134
|
filters.pipeline_stage = { $eq: query.pipelineStage }
|
|
131
135
|
}
|
|
136
|
+
if (query.pipelineId) {
|
|
137
|
+
filters.pipeline_id = { $eq: query.pipelineId }
|
|
138
|
+
}
|
|
139
|
+
if (query.pipelineStageId) {
|
|
140
|
+
filters.pipeline_stage_id = { $eq: query.pipelineStageId }
|
|
141
|
+
}
|
|
132
142
|
return filters
|
|
133
143
|
},
|
|
134
144
|
},
|
|
@@ -394,6 +404,8 @@ const dealListItemSchema = z
|
|
|
394
404
|
description: z.string().nullable().optional(),
|
|
395
405
|
status: z.string().nullable().optional(),
|
|
396
406
|
pipeline_stage: z.string().nullable().optional(),
|
|
407
|
+
pipeline_id: z.string().uuid().nullable().optional(),
|
|
408
|
+
pipeline_stage_id: z.string().uuid().nullable().optional(),
|
|
397
409
|
value_amount: z.number().nullable().optional(),
|
|
398
410
|
value_currency: z.string().nullable().optional(),
|
|
399
411
|
probability: z.number().nullable().optional(),
|
|
@@ -4,7 +4,8 @@ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
|
4
4
|
import type { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
5
5
|
import type { CommandExecuteResult } from '@open-mercato/shared/lib/commands/types'
|
|
6
6
|
import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
|
|
7
|
-
import { CustomerDictionaryEntry } from '../../../data/entities'
|
|
7
|
+
import { CustomerDictionaryEntry, CustomerPipelineStage } from '../../../data/entities'
|
|
8
|
+
import { ensureDictionaryEntry } from '../../../commands/shared'
|
|
8
9
|
import { mapDictionaryKind, resolveDictionaryRouteContext } from '../context'
|
|
9
10
|
import { createDictionaryCacheKey, createDictionaryCacheTags, invalidateDictionaryCache, DICTIONARY_CACHE_TTL_MS } from '../cache'
|
|
10
11
|
import { z } from 'zod'
|
|
@@ -48,6 +49,25 @@ export async function GET(req: Request, ctx: { params?: { kind?: string } }) {
|
|
|
48
49
|
{ orderBy: { label: 'asc' } }
|
|
49
50
|
)
|
|
50
51
|
|
|
52
|
+
if (mappedKind === 'pipeline_stage' && organizationId) {
|
|
53
|
+
const existingNormalized = new Set(entries.map((e) => e.normalizedValue))
|
|
54
|
+
const pipelineStages = await em.find(CustomerPipelineStage, { organizationId, tenantId })
|
|
55
|
+
for (const stage of pipelineStages) {
|
|
56
|
+
if (!existingNormalized.has(stage.label.trim().toLowerCase())) {
|
|
57
|
+
const created = await ensureDictionaryEntry(em, {
|
|
58
|
+
tenantId,
|
|
59
|
+
organizationId,
|
|
60
|
+
kind: 'pipeline_stage',
|
|
61
|
+
value: stage.label,
|
|
62
|
+
})
|
|
63
|
+
if (created) {
|
|
64
|
+
entries.push(created)
|
|
65
|
+
existingNormalized.add(created.normalizedValue)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
const byValue = new Map<string, { entry: CustomerDictionaryEntry; isInherited: boolean; order: number }>()
|
|
52
72
|
for (const entry of entries) {
|
|
53
73
|
const normalized = entry.normalizedValue
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
6
|
+
import type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'
|
|
7
|
+
import { pipelineStageReorderSchema, type PipelineStageReorderInput } from '../../../data/validators'
|
|
8
|
+
import { withScopedPayload } from '../../utils'
|
|
9
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
10
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
11
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
12
|
+
|
|
13
|
+
export const metadata = {
|
|
14
|
+
POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function POST(req: Request) {
|
|
18
|
+
try {
|
|
19
|
+
const container = await createRequestContainer()
|
|
20
|
+
const auth = await getAuthFromRequest(req)
|
|
21
|
+
const { translate } = await resolveTranslations()
|
|
22
|
+
if (!auth) throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })
|
|
23
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
24
|
+
const ctx: CommandRuntimeContext = {
|
|
25
|
+
container,
|
|
26
|
+
auth,
|
|
27
|
+
organizationScope: scope,
|
|
28
|
+
selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,
|
|
29
|
+
organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),
|
|
30
|
+
request: req,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const body = await req.json().catch(() => ({}))
|
|
34
|
+
const scoped = withScopedPayload(body, ctx, translate)
|
|
35
|
+
|
|
36
|
+
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
37
|
+
await commandBus.execute<PipelineStageReorderInput, void>(
|
|
38
|
+
'customers.pipeline-stages.reorder',
|
|
39
|
+
{ input: pipelineStageReorderSchema.parse(scoped), ctx },
|
|
40
|
+
)
|
|
41
|
+
return NextResponse.json({ ok: true })
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (err instanceof CrudHttpError) {
|
|
44
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
45
|
+
}
|
|
46
|
+
console.error('customers.pipeline-stages.reorder failed', err)
|
|
47
|
+
return NextResponse.json({ error: 'Failed to reorder pipeline stages' }, { status: 400 })
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const reorderOkResponseSchema = z.object({ ok: z.boolean() })
|
|
52
|
+
const reorderErrorSchema = z.object({ error: z.string() })
|
|
53
|
+
|
|
54
|
+
export const openApi: OpenApiRouteDoc = {
|
|
55
|
+
tag: 'Customers',
|
|
56
|
+
summary: 'Reorder pipeline stages',
|
|
57
|
+
methods: {
|
|
58
|
+
POST: {
|
|
59
|
+
summary: 'Reorder pipeline stages',
|
|
60
|
+
description: 'Updates the order of pipeline stages in bulk.',
|
|
61
|
+
requestBody: { contentType: 'application/json', schema: pipelineStageReorderSchema },
|
|
62
|
+
responses: [
|
|
63
|
+
{ status: 200, description: 'Stages reordered', schema: reorderOkResponseSchema },
|
|
64
|
+
],
|
|
65
|
+
errors: [
|
|
66
|
+
{ status: 400, description: 'Validation failed', schema: reorderErrorSchema },
|
|
67
|
+
{ status: 401, description: 'Unauthorized', schema: reorderErrorSchema },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
6
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
7
|
+
import type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'
|
|
8
|
+
import { CustomerPipelineStage, CustomerDictionaryEntry } from '../../data/entities'
|
|
9
|
+
import {
|
|
10
|
+
pipelineStageCreateSchema,
|
|
11
|
+
pipelineStageUpdateSchema,
|
|
12
|
+
pipelineStageDeleteSchema,
|
|
13
|
+
type PipelineStageCreateInput,
|
|
14
|
+
type PipelineStageUpdateInput,
|
|
15
|
+
type PipelineStageDeleteInput,
|
|
16
|
+
} from '../../data/validators'
|
|
17
|
+
import { withScopedPayload } from '../utils'
|
|
18
|
+
import { ensureDictionaryEntry } from '../../commands/shared'
|
|
19
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
20
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
21
|
+
import { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'
|
|
22
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
23
|
+
|
|
24
|
+
export const metadata = {
|
|
25
|
+
GET: { requireAuth: true, requireFeatures: ['customers.pipelines.view'] },
|
|
26
|
+
POST: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },
|
|
27
|
+
PUT: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },
|
|
28
|
+
DELETE: { requireAuth: true, requireFeatures: ['customers.pipelines.manage'] },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function buildContext(
|
|
32
|
+
req: Request
|
|
33
|
+
): Promise<{ ctx: CommandRuntimeContext; organizationId: string | null; tenantId: string | null }> {
|
|
34
|
+
const container = await createRequestContainer()
|
|
35
|
+
const auth = await getAuthFromRequest(req)
|
|
36
|
+
const { translate } = await resolveTranslations()
|
|
37
|
+
if (!auth) throw new CrudHttpError(401, { error: translate('customers.errors.unauthorized', 'Unauthorized') })
|
|
38
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
39
|
+
const ctx: CommandRuntimeContext = {
|
|
40
|
+
container,
|
|
41
|
+
auth,
|
|
42
|
+
organizationScope: scope,
|
|
43
|
+
selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,
|
|
44
|
+
organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),
|
|
45
|
+
request: req,
|
|
46
|
+
}
|
|
47
|
+
const organizationId = scope?.selectedId ?? auth.orgId ?? null
|
|
48
|
+
const tenantId = auth.tenantId ?? null
|
|
49
|
+
return { ctx, organizationId, tenantId }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function GET(req: Request) {
|
|
53
|
+
try {
|
|
54
|
+
const { ctx, organizationId, tenantId } = await buildContext(req)
|
|
55
|
+
if (!organizationId || !tenantId) {
|
|
56
|
+
return NextResponse.json({ error: 'Organization and tenant context required' }, { status: 400 })
|
|
57
|
+
}
|
|
58
|
+
const url = new URL(req.url)
|
|
59
|
+
const pipelineId = url.searchParams.get('pipelineId')
|
|
60
|
+
|
|
61
|
+
const em = (ctx.container.resolve('em') as EntityManager)
|
|
62
|
+
const where: Record<string, unknown> = { organizationId, tenantId }
|
|
63
|
+
if (pipelineId) where.pipelineId = pipelineId
|
|
64
|
+
|
|
65
|
+
const stages = await em.find(CustomerPipelineStage, where, { orderBy: { order: 'ASC' } })
|
|
66
|
+
|
|
67
|
+
const stageLabels = stages.map((s) => s.label.trim().toLowerCase())
|
|
68
|
+
const dictEntries = stageLabels.length
|
|
69
|
+
? await em.find(CustomerDictionaryEntry, {
|
|
70
|
+
organizationId,
|
|
71
|
+
tenantId,
|
|
72
|
+
kind: 'pipeline_stage',
|
|
73
|
+
normalizedValue: { $in: stageLabels },
|
|
74
|
+
})
|
|
75
|
+
: []
|
|
76
|
+
const dictByNormalized = new Map<string, CustomerDictionaryEntry>()
|
|
77
|
+
dictEntries.forEach((entry) => dictByNormalized.set(entry.normalizedValue, entry))
|
|
78
|
+
|
|
79
|
+
const missingStages = stages.filter((s) => !dictByNormalized.has(s.label.trim().toLowerCase()))
|
|
80
|
+
if (missingStages.length) {
|
|
81
|
+
for (const stage of missingStages) {
|
|
82
|
+
const created = await ensureDictionaryEntry(em, {
|
|
83
|
+
tenantId,
|
|
84
|
+
organizationId,
|
|
85
|
+
kind: 'pipeline_stage',
|
|
86
|
+
value: stage.label,
|
|
87
|
+
})
|
|
88
|
+
if (created) dictByNormalized.set(created.normalizedValue, created)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const items = stages.map((stage) => {
|
|
93
|
+
const dictEntry = dictByNormalized.get(stage.label.trim().toLowerCase())
|
|
94
|
+
return {
|
|
95
|
+
id: stage.id,
|
|
96
|
+
pipelineId: stage.pipelineId,
|
|
97
|
+
label: stage.label,
|
|
98
|
+
order: stage.order,
|
|
99
|
+
color: dictEntry?.color ?? null,
|
|
100
|
+
icon: dictEntry?.icon ?? null,
|
|
101
|
+
organizationId: stage.organizationId,
|
|
102
|
+
tenantId: stage.tenantId,
|
|
103
|
+
createdAt: stage.createdAt,
|
|
104
|
+
updatedAt: stage.updatedAt,
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
return NextResponse.json({ items, total: items.length })
|
|
108
|
+
} catch (err) {
|
|
109
|
+
if (err instanceof CrudHttpError) {
|
|
110
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
111
|
+
}
|
|
112
|
+
console.error('customers.pipeline-stages GET failed', err)
|
|
113
|
+
return NextResponse.json({ error: 'Failed to load pipeline stages' }, { status: 500 })
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function POST(req: Request) {
|
|
118
|
+
try {
|
|
119
|
+
const { ctx } = await buildContext(req)
|
|
120
|
+
const body = await req.json().catch(() => ({}))
|
|
121
|
+
const { translate } = await resolveTranslations()
|
|
122
|
+
const scoped = withScopedPayload(body, ctx, translate)
|
|
123
|
+
|
|
124
|
+
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
125
|
+
const { result, logEntry } = await commandBus.execute<PipelineStageCreateInput, { stageId: string }>(
|
|
126
|
+
'customers.pipeline-stages.create',
|
|
127
|
+
{ input: pipelineStageCreateSchema.parse(scoped), ctx },
|
|
128
|
+
)
|
|
129
|
+
const response = NextResponse.json({ id: result?.stageId ?? null }, { status: 201 })
|
|
130
|
+
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
131
|
+
response.headers.set(
|
|
132
|
+
'x-om-operation',
|
|
133
|
+
serializeOperationMetadata({
|
|
134
|
+
id: logEntry.id,
|
|
135
|
+
undoToken: logEntry.undoToken,
|
|
136
|
+
commandId: logEntry.commandId,
|
|
137
|
+
actionLabel: logEntry.actionLabel ?? null,
|
|
138
|
+
resourceKind: logEntry.resourceKind ?? 'customers.pipelineStage',
|
|
139
|
+
resourceId: logEntry.resourceId ?? result?.stageId ?? null,
|
|
140
|
+
executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,
|
|
141
|
+
})
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
return response
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if (err instanceof CrudHttpError) {
|
|
147
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
148
|
+
}
|
|
149
|
+
console.error('customers.pipeline-stages POST failed', err)
|
|
150
|
+
return NextResponse.json({ error: 'Failed to create pipeline stage' }, { status: 400 })
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function PUT(req: Request) {
|
|
155
|
+
try {
|
|
156
|
+
const { ctx } = await buildContext(req)
|
|
157
|
+
const body = await req.json().catch(() => ({}))
|
|
158
|
+
const { translate } = await resolveTranslations()
|
|
159
|
+
const scoped = withScopedPayload(body, ctx, translate)
|
|
160
|
+
|
|
161
|
+
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
162
|
+
const { logEntry } = await commandBus.execute<PipelineStageUpdateInput, void>(
|
|
163
|
+
'customers.pipeline-stages.update',
|
|
164
|
+
{ input: pipelineStageUpdateSchema.parse(scoped), ctx },
|
|
165
|
+
)
|
|
166
|
+
const response = NextResponse.json({ ok: true })
|
|
167
|
+
if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
|
|
168
|
+
response.headers.set(
|
|
169
|
+
'x-om-operation',
|
|
170
|
+
serializeOperationMetadata({
|
|
171
|
+
id: logEntry.id,
|
|
172
|
+
undoToken: logEntry.undoToken,
|
|
173
|
+
commandId: logEntry.commandId,
|
|
174
|
+
actionLabel: logEntry.actionLabel ?? null,
|
|
175
|
+
resourceKind: logEntry.resourceKind ?? 'customers.pipelineStage',
|
|
176
|
+
resourceId: logEntry.resourceId ?? null,
|
|
177
|
+
executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,
|
|
178
|
+
})
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
return response
|
|
182
|
+
} catch (err) {
|
|
183
|
+
if (err instanceof CrudHttpError) {
|
|
184
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
185
|
+
}
|
|
186
|
+
console.error('customers.pipeline-stages PUT failed', err)
|
|
187
|
+
return NextResponse.json({ error: 'Failed to update pipeline stage' }, { status: 400 })
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export async function DELETE(req: Request) {
|
|
192
|
+
try {
|
|
193
|
+
const { ctx } = await buildContext(req)
|
|
194
|
+
const body = await req.json().catch(() => ({}))
|
|
195
|
+
const { translate } = await resolveTranslations()
|
|
196
|
+
const scoped = withScopedPayload(body, ctx, translate)
|
|
197
|
+
|
|
198
|
+
const commandBus = (ctx.container.resolve('commandBus') as CommandBus)
|
|
199
|
+
await commandBus.execute<PipelineStageDeleteInput, void>(
|
|
200
|
+
'customers.pipeline-stages.delete',
|
|
201
|
+
{ input: pipelineStageDeleteSchema.parse(scoped), ctx },
|
|
202
|
+
)
|
|
203
|
+
return NextResponse.json({ ok: true })
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (err instanceof CrudHttpError) {
|
|
206
|
+
return NextResponse.json(err.body, { status: err.status })
|
|
207
|
+
}
|
|
208
|
+
console.error('customers.pipeline-stages DELETE failed', err)
|
|
209
|
+
return NextResponse.json({ error: 'Failed to delete pipeline stage' }, { status: 400 })
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const stageItemSchema = z.object({
|
|
214
|
+
id: z.string().uuid(),
|
|
215
|
+
pipelineId: z.string().uuid(),
|
|
216
|
+
label: z.string(),
|
|
217
|
+
order: z.number(),
|
|
218
|
+
color: z.string().nullable(),
|
|
219
|
+
icon: z.string().nullable(),
|
|
220
|
+
organizationId: z.string().uuid(),
|
|
221
|
+
tenantId: z.string().uuid(),
|
|
222
|
+
createdAt: z.date(),
|
|
223
|
+
updatedAt: z.date(),
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
const stageListResponseSchema = z.object({
|
|
227
|
+
items: z.array(stageItemSchema),
|
|
228
|
+
total: z.number(),
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
const stageCreateResponseSchema = z.object({
|
|
232
|
+
id: z.string().uuid().nullable(),
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
const stageOkResponseSchema = z.object({
|
|
236
|
+
ok: z.boolean(),
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
const stageErrorSchema = z.object({
|
|
240
|
+
error: z.string(),
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
export const openApi: OpenApiRouteDoc = {
|
|
244
|
+
tag: 'Customers',
|
|
245
|
+
summary: 'Manage pipeline stages',
|
|
246
|
+
methods: {
|
|
247
|
+
GET: {
|
|
248
|
+
summary: 'List pipeline stages',
|
|
249
|
+
description: 'Returns pipeline stages for the authenticated organization, optionally filtered by pipelineId.',
|
|
250
|
+
query: z.object({ pipelineId: z.string().uuid().optional() }),
|
|
251
|
+
responses: [
|
|
252
|
+
{ status: 200, description: 'Stage list', schema: stageListResponseSchema },
|
|
253
|
+
],
|
|
254
|
+
errors: [
|
|
255
|
+
{ status: 401, description: 'Unauthorized', schema: stageErrorSchema },
|
|
256
|
+
{ status: 400, description: 'Invalid request', schema: stageErrorSchema },
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
POST: {
|
|
260
|
+
summary: 'Create pipeline stage',
|
|
261
|
+
description: 'Creates a new pipeline stage.',
|
|
262
|
+
requestBody: { contentType: 'application/json', schema: pipelineStageCreateSchema },
|
|
263
|
+
responses: [
|
|
264
|
+
{ status: 201, description: 'Stage created', schema: stageCreateResponseSchema },
|
|
265
|
+
],
|
|
266
|
+
errors: [
|
|
267
|
+
{ status: 400, description: 'Validation failed', schema: stageErrorSchema },
|
|
268
|
+
{ status: 401, description: 'Unauthorized', schema: stageErrorSchema },
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
PUT: {
|
|
272
|
+
summary: 'Update pipeline stage',
|
|
273
|
+
description: 'Updates an existing pipeline stage.',
|
|
274
|
+
requestBody: { contentType: 'application/json', schema: pipelineStageUpdateSchema },
|
|
275
|
+
responses: [
|
|
276
|
+
{ status: 200, description: 'Stage updated', schema: stageOkResponseSchema },
|
|
277
|
+
],
|
|
278
|
+
errors: [
|
|
279
|
+
{ status: 400, description: 'Validation failed', schema: stageErrorSchema },
|
|
280
|
+
{ status: 404, description: 'Stage not found', schema: stageErrorSchema },
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
DELETE: {
|
|
284
|
+
summary: 'Delete pipeline stage',
|
|
285
|
+
description: 'Deletes a pipeline stage. Returns 409 if active deals use this stage.',
|
|
286
|
+
requestBody: { contentType: 'application/json', schema: pipelineStageDeleteSchema },
|
|
287
|
+
responses: [
|
|
288
|
+
{ status: 200, description: 'Stage deleted', schema: stageOkResponseSchema },
|
|
289
|
+
],
|
|
290
|
+
errors: [
|
|
291
|
+
{ status: 409, description: 'Stage has active deals', schema: stageErrorSchema },
|
|
292
|
+
{ status: 404, description: 'Stage not found', schema: stageErrorSchema },
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
}
|