Files
optima/src/api/sales/[id]/updateNote.ts
T
HoloPanio d7b374f8ab feat: sales activities, forecast products, catalog categories, member cache, procurement filters, and comprehensive tests
New features:
- ActivityController and manager for CW sales activities (CRUD)
- ForecastProductController for opportunity forecast/product lines
- CW member cache with dual-layer (in-memory + Redis) resolution
- Catalog category/subcategory/ecosystem taxonomy module
- Quote statuses type definitions with CW mapping
- User-defined fields (UDF) module with cache and event refresh
- Company sites CW module with serialization
- Procurement manager filters (category, ecosystem, manufacturer, price, stock)
- Opportunity notes CRUD and product line management via CW API
- Opportunity type definitions endpoint

Updates:
- OpportunityController: CW refresh, company hydration, activities, custom fields
- UserController: cwIdentifier field for CW member linking
- CatalogItemController: category/subcategory fields from CW
- PermissionNodes: sales note/product CRUD nodes, subCategories, collectPermissions
- API routes: procurement categories/filters, sales notes/products, opportunity types
- Global events: UDF and member refresh intervals on startup

Tests (414 passing):
- ActivityController, ForecastProductController, OpportunityController unit tests
- UserController cwIdentifier tests
- catalogCategories, companySites, memberCache, procurement module tests
- activityTypes, opportunityTypes, quoteStatuses type tests
- permissionNodes subCategories and getAllPermissionNodes tests
- Updated test setup with redis mock, API method mocks, and builder helpers
2026-03-01 13:19:00 -06:00

58 lines
1.9 KiB
TypeScript

import { createRoute } from "../../../modules/api-utils/createRoute";
import { opportunities } from "../../../managers/opportunities";
import { apiResponse } from "../../../modules/api-utils/apiResponse";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { authMiddleware } from "../../middleware/authorization";
import GenericError from "../../../Errors/GenericError";
import { resolveMember } from "../../../modules/cw-utils/members/memberCache";
import { z } from "zod";
/* PATCH /v1/sales/opportunities/:identifier/notes/:noteId */
export default createRoute(
"patch",
["/opportunities/:identifier/notes/:noteId"],
async (c) => {
const identifier = c.req.param("identifier");
const noteId = Number(c.req.param("noteId"));
if (isNaN(noteId))
throw new GenericError({
status: 400,
name: "InvalidNoteId",
message: "Note ID must be a number",
});
const body = await c.req.json();
const schema = z
.object({
text: z.string().min(1).optional(),
flagged: z.boolean().optional(),
})
.refine((d) => d.text !== undefined || d.flagged !== undefined, {
message: "At least one of 'text' or 'flagged' must be provided",
});
const data = schema.parse(body);
const item = await opportunities.fetchItem(identifier);
const updated = await item.updateNote(noteId, data);
const response = apiResponse.successful(
"Opportunity note updated successfully!",
{
id: updated.id,
text: updated.text,
type: updated.type
? { id: updated.type.id, name: updated.type.name }
: null,
flagged: updated.flagged,
enteredBy: await resolveMember(updated.enteredBy),
},
);
return c.json(response, response.status as ContentfulStatusCode);
},
authMiddleware({ permissions: ["sales.opportunity.note.update"] }),
);