feat(uat-b1): ship Wave A-E of Bucket 1 audit findings
Wave A (Interest+EOI form quick wins): - Auto-select yacht after inline-create from interest form - EOI generate dialog: "View EOI" action toast - Interest form berth picker: formatBerthRange compact label - Remove "Generate EOI" button from Documents tab (clean removal) - Interest auto-assign: only sales_agent/sales_manager auto-claim ownership on create (explicit role check via user_port_roles join) - LinkedBerthRowItem dims: drop "D" suffix + "L × W" format - ExternalEoiUploadDialog: prefillSignatories prop threaded from active EOI signers - EOI signature progress on Overview milestone card footer Wave B (a11y + i18n sweeps): - aria-live on supplemental-info error state - text-[10px] -> text-xs in client-pipeline-summary - Currency formatter: locale default removed (Intl uses runtime) - en-US/en-GB hardcoded toLocaleString swept across 13 components Wave C (Primary berth always in EOI bundle): - Service guard strengthened on update path - Migration 0083 backfills historical primary rows Wave D (Onboarding super_admin discoverability): - /api/v1/admin/onboarding/status endpoint + shared service - Topbar OnboardingBanner (super_admin, session-dismissible) - OnboardingTile dashboard widget (rail group, self-hides at 100%) - Celebration toast + invalidate of shared status on last tick Wave E (Branded post-completion email idempotency): - Verified handleDocumentCompleted already owns the email fan-out - Added regression test for the polling path + idempotency Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import { berthReservations } from '@/lib/db/schema/reservations';
|
||||
import { yachts } from '@/lib/db/schema/yachts';
|
||||
import { companyMemberships } from '@/lib/db/schema/companies';
|
||||
import { tags } from '@/lib/db/schema/system';
|
||||
import { userProfiles } from '@/lib/db/schema/users';
|
||||
import { userProfiles, userPortRoles, roles } from '@/lib/db/schema/users';
|
||||
import { createAuditLog, type AuditMeta } from '@/lib/audit';
|
||||
import { activeInterestsWhere } from '@/lib/services/active-interest';
|
||||
import { getPortReminderConfig } from '@/lib/services/port-config';
|
||||
@@ -755,14 +755,25 @@ export async function createInterest(portId: string, data: CreateInterestInput,
|
||||
if (v?.userId) {
|
||||
resolvedAssignedTo = v.userId;
|
||||
} else {
|
||||
// Tier 3: auto-assign to creator unless they're a super-admin.
|
||||
// Tier 3: auto-assign to creator only when their role is a working
|
||||
// sales rep — super_admin / director / residential_partner / viewer
|
||||
// intentionally skip (they create on behalf of others or shouldn't
|
||||
// own interests at all).
|
||||
const AUTO_ASSIGN_ROLES = new Set(['sales_agent', 'sales_manager']);
|
||||
const [profile] = await db
|
||||
.select({ isSuperAdmin: userProfiles.isSuperAdmin })
|
||||
.from(userProfiles)
|
||||
.where(eq(userProfiles.userId, meta.userId))
|
||||
.limit(1);
|
||||
if (profile && !profile.isSuperAdmin) {
|
||||
resolvedAssignedTo = meta.userId;
|
||||
const userRoles = await db
|
||||
.select({ name: roles.name })
|
||||
.from(userPortRoles)
|
||||
.innerJoin(roles, eq(userPortRoles.roleId, roles.id))
|
||||
.where(and(eq(userPortRoles.userId, meta.userId), eq(userPortRoles.portId, portId)));
|
||||
if (userRoles.some((r) => AUTO_ASSIGN_ROLES.has(r.name))) {
|
||||
resolvedAssignedTo = meta.userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user