task-d.5.7
This commit is contained in:
@@ -26,6 +26,15 @@ import {
|
||||
import { generateNextDocumentCode } from '@/features/foundation/document-sequence/service';
|
||||
import { getActiveOptionsByCategory } from '@/features/foundation/master-options/service';
|
||||
import { syncOpportunityPipelineStageFromQuotationStatus } from '@/features/crm/opportunities/server/service';
|
||||
import {
|
||||
syncOpportunityFromQuotationApproval,
|
||||
syncOpportunityFromQuotationCreated
|
||||
} from '@/features/crm/opportunities/server/quotation-sync.service';
|
||||
import {
|
||||
assertOpportunityLinkIntegrity,
|
||||
assertRevisionOpportunityLinkIntegrity,
|
||||
auditQuotationOpportunityLink
|
||||
} from './opportunity-link.service';
|
||||
import type {
|
||||
QuotationActivityRecord,
|
||||
QuotationAttachmentMutationPayload,
|
||||
@@ -1297,12 +1306,11 @@ export async function createQuotation(
|
||||
payload.status
|
||||
);
|
||||
|
||||
const opportunity = payload.opportunityId
|
||||
? await assertOpportunityBelongsToOrganization(payload.opportunityId, organizationId)
|
||||
: null;
|
||||
if (opportunity) {
|
||||
assertOpportunityOpenForQuotation(opportunity);
|
||||
}
|
||||
const opportunity = await assertOpportunityLinkIntegrity({
|
||||
organizationId,
|
||||
opportunityId: payload.opportunityId,
|
||||
customerId: payload.customerId
|
||||
});
|
||||
const documentCode = await generateNextDocumentCode({
|
||||
organizationId,
|
||||
documentType: 'quotation',
|
||||
@@ -1362,6 +1370,19 @@ export async function createQuotation(
|
||||
);
|
||||
|
||||
if (created.opportunityId) {
|
||||
await auditQuotationOpportunityLink({
|
||||
organizationId,
|
||||
userId,
|
||||
quotationId: created.id,
|
||||
opportunityId: created.opportunityId,
|
||||
reason: 'quotation_created'
|
||||
});
|
||||
await syncOpportunityFromQuotationCreated({
|
||||
organizationId,
|
||||
userId,
|
||||
opportunityId: created.opportunityId,
|
||||
quotationId: created.id
|
||||
});
|
||||
await syncOpportunityPipelineStageFromQuotationStatus(
|
||||
created.opportunityId,
|
||||
organizationId,
|
||||
@@ -1387,9 +1408,11 @@ export async function updateQuotation(
|
||||
payload.status
|
||||
);
|
||||
const existing = await assertQuotationBelongsToOrganization(id, organizationId, accessContext);
|
||||
const opportunity = payload.opportunityId
|
||||
? await assertOpportunityBelongsToOrganization(payload.opportunityId, organizationId)
|
||||
: null;
|
||||
const opportunity = await assertOpportunityLinkIntegrity({
|
||||
organizationId,
|
||||
opportunityId: payload.opportunityId,
|
||||
customerId: payload.customerId
|
||||
});
|
||||
|
||||
const updated = await db.transaction(async (tx) => {
|
||||
const [row] = await tx
|
||||
@@ -1435,11 +1458,26 @@ export async function updateQuotation(
|
||||
);
|
||||
|
||||
if (row.opportunityId) {
|
||||
await auditQuotationOpportunityLink({
|
||||
organizationId,
|
||||
userId,
|
||||
quotationId: row.id,
|
||||
opportunityId: row.opportunityId,
|
||||
previousOpportunityId: existing.opportunityId,
|
||||
reason: 'quotation_updated'
|
||||
});
|
||||
await syncOpportunityPipelineStageFromQuotationStatus(
|
||||
row.opportunityId,
|
||||
organizationId,
|
||||
quotationStatusCode
|
||||
);
|
||||
await syncOpportunityFromQuotationApproval({
|
||||
organizationId,
|
||||
userId,
|
||||
opportunityId: row.opportunityId,
|
||||
quotationId: row.id,
|
||||
quotationStatusCode
|
||||
});
|
||||
}
|
||||
|
||||
return row;
|
||||
@@ -2125,6 +2163,7 @@ export async function createQuotationRevision(
|
||||
revisionRemark?: string
|
||||
) {
|
||||
const parent = await assertQuotationBelongsToOrganization(quotationId, organizationId);
|
||||
await assertRevisionOpportunityLinkIntegrity({ organizationId, quotationId });
|
||||
const statusCode = await resolveOptionCodeById(
|
||||
organizationId,
|
||||
QUOTATION_OPTION_CATEGORIES.status,
|
||||
@@ -2273,6 +2312,23 @@ export async function createQuotationRevision(
|
||||
}
|
||||
}
|
||||
|
||||
if (created.opportunityId) {
|
||||
await auditQuotationOpportunityLink({
|
||||
organizationId,
|
||||
userId,
|
||||
quotationId: created.id,
|
||||
opportunityId: created.opportunityId,
|
||||
previousOpportunityId: parent.opportunityId,
|
||||
reason: 'quotation_revision_created'
|
||||
});
|
||||
await syncOpportunityFromQuotationCreated({
|
||||
organizationId,
|
||||
userId,
|
||||
opportunityId: created.opportunityId,
|
||||
quotationId: created.id
|
||||
});
|
||||
}
|
||||
|
||||
return created;
|
||||
});
|
||||
}
|
||||
@@ -2292,14 +2348,21 @@ export async function listQuotationRevisions(id: string, organizationId: string)
|
||||
)
|
||||
.orderBy(asc(crmQuotations.revision), asc(crmQuotations.createdAt));
|
||||
|
||||
return rows.map<QuotationRelationItem>((row) => ({
|
||||
id: row.id,
|
||||
code: row.code,
|
||||
status: row.status,
|
||||
quotationType: row.quotationType,
|
||||
totalAmount: row.totalAmount,
|
||||
updatedAt: row.updatedAt.toISOString()
|
||||
}));
|
||||
return rows.map<QuotationRelationItem>((row) => ({
|
||||
id: row.id,
|
||||
code: row.code,
|
||||
status: row.status,
|
||||
revision: row.revision,
|
||||
quotationType: row.quotationType,
|
||||
totalAmount: row.totalAmount,
|
||||
currency: row.currency,
|
||||
validUntil: row.validUntil?.toISOString() ?? null,
|
||||
approvedAt: row.approvedAt?.toISOString() ?? null,
|
||||
acceptedAt: row.acceptedAt?.toISOString() ?? null,
|
||||
rejectedAt: row.rejectedAt?.toISOString() ?? null,
|
||||
rejectionReason: row.rejectionReason,
|
||||
updatedAt: row.updatedAt.toISOString()
|
||||
}));
|
||||
}
|
||||
|
||||
export async function listOpportunityQuotationRelations(
|
||||
@@ -2318,14 +2381,21 @@ export async function listOpportunityQuotationRelations(
|
||||
)
|
||||
.orderBy(desc(crmQuotations.updatedAt));
|
||||
|
||||
return rows.map((row) => ({
|
||||
id: row.id,
|
||||
code: row.code,
|
||||
status: row.status,
|
||||
quotationType: row.quotationType,
|
||||
totalAmount: row.totalAmount,
|
||||
updatedAt: row.updatedAt.toISOString()
|
||||
}));
|
||||
return rows.map((row) => ({
|
||||
id: row.id,
|
||||
code: row.code,
|
||||
status: row.status,
|
||||
revision: row.revision,
|
||||
quotationType: row.quotationType,
|
||||
totalAmount: row.totalAmount,
|
||||
currency: row.currency,
|
||||
validUntil: row.validUntil?.toISOString() ?? null,
|
||||
approvedAt: row.approvedAt?.toISOString() ?? null,
|
||||
acceptedAt: row.acceptedAt?.toISOString() ?? null,
|
||||
rejectedAt: row.rejectedAt?.toISOString() ?? null,
|
||||
rejectionReason: row.rejectionReason,
|
||||
updatedAt: row.updatedAt.toISOString()
|
||||
}));
|
||||
}
|
||||
|
||||
export async function listCustomerQuotationRelations(
|
||||
@@ -2351,8 +2421,15 @@ export async function listCustomerQuotationRelations(
|
||||
id: row.id,
|
||||
code: row.code,
|
||||
status: row.status,
|
||||
revision: row.revision,
|
||||
quotationType: row.quotationType,
|
||||
totalAmount: row.totalAmount,
|
||||
currency: row.currency,
|
||||
validUntil: row.validUntil?.toISOString() ?? null,
|
||||
approvedAt: row.approvedAt?.toISOString() ?? null,
|
||||
acceptedAt: row.acceptedAt?.toISOString() ?? null,
|
||||
rejectedAt: row.rejectedAt?.toISOString() ?? null,
|
||||
rejectionReason: row.rejectionReason,
|
||||
updatedAt: row.updatedAt.toISOString()
|
||||
}));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user