task-fic-display
This commit is contained in:
@@ -17,6 +17,8 @@ export interface CustomerRecord {
|
||||
id: string;
|
||||
organizationId: string;
|
||||
branchId: string | null;
|
||||
branchName: string | null;
|
||||
branchCode: string | null;
|
||||
code: string;
|
||||
name: string;
|
||||
abbr: string | null;
|
||||
@@ -47,7 +49,9 @@ export interface CustomerRecord {
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
createdBy: string;
|
||||
createdByName: string | null;
|
||||
updatedBy: string;
|
||||
updatedByName: string | null;
|
||||
}
|
||||
|
||||
export interface CustomerListItem extends CustomerRecord {
|
||||
@@ -230,4 +234,3 @@ export interface MutationSuccessResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -301,8 +301,8 @@ export function CustomerDetail({
|
||||
<CardDescription>ข้อมูลประกอบการติดตามและตรวจสอบของรายการลูกค้านี้</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-4'>
|
||||
<FieldItem label='Created By' value={customer.createdBy} />
|
||||
<FieldItem label='Updated By' value={customer.updatedBy} />
|
||||
<FieldItem label='Created By' value={customer.createdByName ?? customer.createdBy} />
|
||||
<FieldItem label='Updated By' value={customer.updatedByName ?? customer.updatedBy} />
|
||||
<FieldItem label='Created At' value={formatDateTime(customer.createdAt)} />
|
||||
<FieldItem label='Updated At' value={formatDateTime(customer.updatedAt)} />
|
||||
</CardContent>
|
||||
|
||||
@@ -34,6 +34,10 @@ import {
|
||||
getUserBranches,
|
||||
} from "@/features/foundation/branch-scope/service";
|
||||
import { generateNextDocumentCode } from "@/features/foundation/document-sequence/service";
|
||||
import {
|
||||
resolveBranchDisplays,
|
||||
resolveUserDisplays,
|
||||
} from "@/features/foundation/display/server/display-resolver";
|
||||
import { getActiveOptionsByCategory } from "@/features/foundation/master-options/service";
|
||||
import {
|
||||
canAccessContact,
|
||||
@@ -64,6 +68,13 @@ type CustomerRecordSource = Omit<
|
||||
ownerAssignedByName?: string | null;
|
||||
};
|
||||
|
||||
type CustomerDisplayOptions = {
|
||||
branchName?: string | null;
|
||||
branchCode?: string | null;
|
||||
createdByName?: string | null;
|
||||
updatedByName?: string | null;
|
||||
};
|
||||
|
||||
let customerSubGroupColumnAvailable: boolean | undefined;
|
||||
|
||||
export type CustomerAccessContext = CrmSecurityContext;
|
||||
@@ -154,11 +165,16 @@ function toOptionalIsoDateTime(value: Date | string | null | undefined) {
|
||||
return value ? toIsoDateTime(value) : null;
|
||||
}
|
||||
|
||||
function mapCustomerRecord(row: CustomerRecordSource): CustomerRecord {
|
||||
function mapCustomerRecord(
|
||||
row: CustomerRecordSource,
|
||||
options?: CustomerDisplayOptions,
|
||||
): CustomerRecord {
|
||||
return {
|
||||
id: row.id,
|
||||
organizationId: row.organizationId,
|
||||
branchId: row.branchId,
|
||||
branchName: options?.branchName ?? null,
|
||||
branchCode: options?.branchCode ?? null,
|
||||
code: row.code,
|
||||
name: row.name,
|
||||
abbr: row.abbr,
|
||||
@@ -189,7 +205,9 @@ function mapCustomerRecord(row: CustomerRecordSource): CustomerRecord {
|
||||
updatedAt: toIsoDateTime(row.updatedAt),
|
||||
deletedAt: toOptionalIsoDateTime(row.deletedAt),
|
||||
createdBy: row.createdBy,
|
||||
createdByName: options?.createdByName ?? null,
|
||||
updatedBy: row.updatedBy,
|
||||
updatedByName: options?.updatedByName ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -956,26 +974,35 @@ export async function getCustomerDetail(
|
||||
organizationId,
|
||||
accessContext,
|
||||
);
|
||||
const relatedUserIds = [
|
||||
customer.ownerUserId,
|
||||
customer.ownerAssignedBy,
|
||||
].filter(Boolean) as string[];
|
||||
const userRows = relatedUserIds.length
|
||||
? await db
|
||||
.select({ id: users.id, name: users.name })
|
||||
.from(users)
|
||||
.where(inArray(users.id, relatedUserIds))
|
||||
: [];
|
||||
const userMap = new Map(userRows.map((row) => [row.id, row.name]));
|
||||
const [userDisplayMap, branchDisplayMap] = await Promise.all([
|
||||
resolveUserDisplays([
|
||||
customer.ownerUserId,
|
||||
customer.ownerAssignedBy,
|
||||
customer.createdBy,
|
||||
customer.updatedBy,
|
||||
]),
|
||||
resolveBranchDisplays({
|
||||
organizationId,
|
||||
branchIds: [customer.branchId],
|
||||
}),
|
||||
]);
|
||||
const branchDisplay = customer.branchId
|
||||
? (branchDisplayMap.get(customer.branchId) ?? null)
|
||||
: null;
|
||||
|
||||
return mapCustomerRecord({
|
||||
...customer,
|
||||
ownerName: customer.ownerUserId
|
||||
? (userMap.get(customer.ownerUserId) ?? null)
|
||||
? (userDisplayMap.get(customer.ownerUserId)?.displayName ?? null)
|
||||
: null,
|
||||
ownerAssignedByName: customer.ownerAssignedBy
|
||||
? (userMap.get(customer.ownerAssignedBy) ?? null)
|
||||
? (userDisplayMap.get(customer.ownerAssignedBy)?.displayName ?? null)
|
||||
: null,
|
||||
}, {
|
||||
branchName: branchDisplay?.name ?? null,
|
||||
branchCode: branchDisplay?.code ?? null,
|
||||
createdByName: userDisplayMap.get(customer.createdBy)?.displayName ?? null,
|
||||
updatedByName: userDisplayMap.get(customer.updatedBy)?.displayName ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1615,4 +1642,3 @@ export async function resolveCustomerOptionLabel(
|
||||
|
||||
return row?.label ?? null;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,8 @@ export interface QuotationRecord {
|
||||
id: string;
|
||||
organizationId: string;
|
||||
branchId: string | null;
|
||||
branchName: string | null;
|
||||
branchCode: string | null;
|
||||
code: string;
|
||||
opportunityId: string | null;
|
||||
customerId: string;
|
||||
@@ -91,6 +93,7 @@ export interface QuotationRecord {
|
||||
isHotProject: boolean;
|
||||
competitor: string | null;
|
||||
salesmanId: string | null;
|
||||
salesmanName: string | null;
|
||||
isSent: boolean;
|
||||
sentAt: string | null;
|
||||
sentVia: string | null;
|
||||
@@ -109,7 +112,9 @@ export interface QuotationRecord {
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
createdBy: string;
|
||||
createdByName: string | null;
|
||||
updatedBy: string;
|
||||
updatedByName: string | null;
|
||||
}
|
||||
|
||||
export interface QuotationListItem extends QuotationRecord {
|
||||
|
||||
@@ -1716,8 +1716,8 @@ export function QuotationDetail({
|
||||
<CardDescription>Operational metadata for this quotation row.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-4'>
|
||||
<FieldItem label='Created By' value={quotation.createdBy} />
|
||||
<FieldItem label='Updated By' value={quotation.updatedBy} />
|
||||
<FieldItem label='Created By' value={quotation.createdByName ?? quotation.createdBy} />
|
||||
<FieldItem label='Updated By' value={quotation.updatedByName ?? quotation.updatedBy} />
|
||||
<FieldItem
|
||||
label='Created At'
|
||||
value={formatDateTime(quotation.createdAt)}
|
||||
|
||||
@@ -24,6 +24,10 @@ import {
|
||||
canAccessScopedCrmRecord
|
||||
} from '@/features/crm/security/server/service';
|
||||
import { generateNextDocumentCode } from '@/features/foundation/document-sequence/service';
|
||||
import {
|
||||
resolveBranchDisplays,
|
||||
resolveUserDisplays
|
||||
} from '@/features/foundation/display/server/display-resolver';
|
||||
import { getActiveOptionsByCategory } from '@/features/foundation/master-options/service';
|
||||
import { syncOpportunityPipelineStageFromQuotationStatus } from '@/features/crm/opportunities/server/service';
|
||||
import {
|
||||
@@ -130,12 +134,21 @@ function mapApprovedArtifactSummary(
|
||||
|
||||
function mapQuotationRecord(
|
||||
row: typeof crmQuotations.$inferSelect,
|
||||
options?: { approvedArtifact?: QuotationApprovedArtifactSummary | null }
|
||||
options?: {
|
||||
approvedArtifact?: QuotationApprovedArtifactSummary | null;
|
||||
branchName?: string | null;
|
||||
branchCode?: string | null;
|
||||
salesmanName?: string | null;
|
||||
createdByName?: string | null;
|
||||
updatedByName?: string | null;
|
||||
}
|
||||
): QuotationRecord {
|
||||
return {
|
||||
id: row.id,
|
||||
organizationId: row.organizationId,
|
||||
branchId: row.branchId,
|
||||
branchName: options?.branchName ?? null,
|
||||
branchCode: options?.branchCode ?? null,
|
||||
code: row.code,
|
||||
opportunityId: row.opportunityId,
|
||||
customerId: row.customerId,
|
||||
@@ -164,6 +177,7 @@ function mapQuotationRecord(
|
||||
isHotProject: row.isHotProject,
|
||||
competitor: row.competitor,
|
||||
salesmanId: row.salesmanId,
|
||||
salesmanName: options?.salesmanName ?? null,
|
||||
isSent: row.isSent,
|
||||
sentAt: row.sentAt?.toISOString() ?? null,
|
||||
sentVia: row.sentVia,
|
||||
@@ -185,7 +199,9 @@ function mapQuotationRecord(
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
deletedAt: row.deletedAt?.toISOString() ?? null,
|
||||
createdBy: row.createdBy,
|
||||
updatedBy: row.updatedBy
|
||||
createdByName: options?.createdByName ?? null,
|
||||
updatedBy: row.updatedBy,
|
||||
updatedByName: options?.updatedByName ?? null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1211,6 +1227,27 @@ export async function getQuotationDetail(
|
||||
accessContext?: QuotationAccessContext
|
||||
): Promise<QuotationRecord> {
|
||||
const quotation = await assertQuotationBelongsToOrganization(id, organizationId, accessContext);
|
||||
const [userDisplayMap, branchDisplayMap] = await Promise.all([
|
||||
resolveUserDisplays([
|
||||
quotation.salesmanId,
|
||||
quotation.createdBy,
|
||||
quotation.updatedBy
|
||||
]),
|
||||
resolveBranchDisplays({
|
||||
organizationId,
|
||||
branchIds: [quotation.branchId]
|
||||
})
|
||||
]);
|
||||
const branchDisplay = quotation.branchId ? (branchDisplayMap.get(quotation.branchId) ?? null) : null;
|
||||
const baseDisplayOptions = {
|
||||
branchName: branchDisplay?.name ?? null,
|
||||
branchCode: branchDisplay?.code ?? null,
|
||||
salesmanName: quotation.salesmanId
|
||||
? (userDisplayMap.get(quotation.salesmanId)?.displayName ?? null)
|
||||
: null,
|
||||
createdByName: userDisplayMap.get(quotation.createdBy)?.displayName ?? null,
|
||||
updatedByName: userDisplayMap.get(quotation.updatedBy)?.displayName ?? null
|
||||
};
|
||||
const approvedArtifactId =
|
||||
quotation.approvedArtifactId ??
|
||||
(quotation.approvedPdfUrl?.startsWith('artifact:')
|
||||
@@ -1218,7 +1255,7 @@ export async function getQuotationDetail(
|
||||
: null);
|
||||
|
||||
if (!approvedArtifactId) {
|
||||
return mapQuotationRecord(quotation);
|
||||
return mapQuotationRecord(quotation, baseDisplayOptions);
|
||||
}
|
||||
|
||||
const [artifact] = await db
|
||||
@@ -1237,19 +1274,15 @@ export async function getQuotationDetail(
|
||||
return mapQuotationRecord(quotation);
|
||||
}
|
||||
|
||||
const relatedUserIds = [artifact.generatedBy, artifact.lockedBy].filter(Boolean) as string[];
|
||||
const actorRows = relatedUserIds.length
|
||||
? await db
|
||||
.select({ id: users.id, name: users.name })
|
||||
.from(users)
|
||||
.where(inArray(users.id, relatedUserIds))
|
||||
: [];
|
||||
const actorMap = new Map(actorRows.map((row) => [row.id, row.name]));
|
||||
const artifactActorMap = await resolveUserDisplays([artifact.generatedBy, artifact.lockedBy]);
|
||||
|
||||
return mapQuotationRecord(quotation, {
|
||||
...baseDisplayOptions,
|
||||
approvedArtifact: mapApprovedArtifactSummary(artifact, {
|
||||
generatedByName: actorMap.get(artifact.generatedBy) ?? null,
|
||||
lockedByName: artifact.lockedBy ? (actorMap.get(artifact.lockedBy) ?? null) : null
|
||||
generatedByName: artifactActorMap.get(artifact.generatedBy)?.displayName ?? null,
|
||||
lockedByName: artifact.lockedBy
|
||||
? (artifactActorMap.get(artifact.lockedBy)?.displayName ?? null)
|
||||
: null
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user