@things-factory/sales 10.1.203 → 10.1.205
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-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/opportunity/opportunity-service.js +1 -1
- package/dist-server/service/opportunity/opportunity-service.js.map +1 -1
- package/dist-server/service/quotation/quotation-service.js +1 -1
- package/dist-server/service/quotation/quotation-service.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
|
@@ -87,7 +87,7 @@ async function closeOpportunity(tx, domain, user, id, outcome, { closedOn, reaso
|
|
|
87
87
|
throw new shell_1.Refusal('opportunity-stage-move-invalid', {}, 'The opportunity cannot move to that stage');
|
|
88
88
|
if (outcome === opportunity_rules_1.OpportunityStage.LOST && !reason?.trim())
|
|
89
89
|
throw new shell_1.Refusal('opportunity-reason-required', {}, 'Say why it was lost');
|
|
90
|
-
const day = closedOn || (0,
|
|
90
|
+
const day = closedOn || (0, shell_1.calendarDay)(domain.timezone);
|
|
91
91
|
if (!(0, invoice_1.validDay)(day))
|
|
92
92
|
throw new shell_1.Refusal('opportunity-closed-on-invalid', {}, 'Give the closing day as YYYY-MM-DD');
|
|
93
93
|
const from = opportunity.stage;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opportunity-service.js","sourceRoot":"","sources":["../../../server/service/opportunity/opportunity-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDomainAssignees } from '@things-factory/auth-base'\nimport { Counterparty } from '@things-factory/biz-base'\nimport { calendarDay, currencyDigits, validDay } from '@things-factory/invoice'\nimport { Domain, Refusal } from '@things-factory/shell'\nimport { EntityManager } from 'typeorm'\n\nimport { recordSalesEvent } from '../sales-event'\nimport { Opportunity } from './opportunity'\nimport { OpportunityEvent } from './opportunity-event'\nimport { isOpen, OpportunityStage, probabilityAfter, stageMove, validMonth, validProbability } from './opportunity-rules'\nimport { NewOpportunity } from './opportunity-type'\n\nexport async function opportunityInDomain(tx: EntityManager, domain: Domain, id: string) {\n const opportunity = await tx.getRepository(Opportunity).findOne({ where: { id, domain: { id: domain.id } } })\n if (!opportunity) throw new Refusal('opportunity-not-found', {}, 'Opportunity not found')\n return opportunity\n}\n\n/**\n * Create an opportunity, or change one a person may still change. An open one takes any open stage; a lost one is\n * opened again by giving it an open stage. Closing is `closeOpportunity`'s.\n */\nexport async function saveOpportunity(tx: EntityManager, domain: Domain, user: any, input: NewOpportunity, id?: string) {\n const old = id ? await opportunityInDomain(tx, domain, id) : undefined\n const stage = input.stage ?? old?.stage ?? OpportunityStage.LEAD\n /* A closed one changes only by being opened again, and only a lost one may be. */\n if (old && !isOpen(old.stage) && !(isOpen(stage) && stageMove(old.stage, stage))) throw new Refusal('opportunity-closed', {}, 'A closed opportunity is not changed')\n if (!isOpen(stage)) throw new Refusal('opportunity-stage-open-required', {}, 'Give an open stage; won and lost are closings')\n\n const name = input.name?.trim()\n if (!name) throw new Refusal('opportunity-name-required', {}, 'Name the opportunity')\n if (!Number.isFinite(input.amount) || input.amount < 0) throw new Refusal('opportunity-amount-invalid', {}, 'The expected amount is zero or more')\n const currency = (input.currency?.trim() || 'KRW').toUpperCase()\n currencyDigits(currency)\n if (!validMonth(input.expectedCloseMonth)) throw new Refusal('opportunity-close-month-invalid', {}, 'Give the expected month as YYYY-MM')\n if (input.probability !== undefined && input.probability !== null && !validProbability(input.probability))\n throw new Refusal('opportunity-probability-invalid', {}, 'The probability is a whole number from 0 to 100')\n\n const counterparties = tx.getRepository(Counterparty)\n const counterparty = input.counterpartyId ? await counterparties.findOne({ where: { id: input.counterpartyId, domain: { id: domain.id } } }) : null\n if (!counterparty) throw new Refusal('opportunity-counterparty-required', {}, 'Choose the counterparty of this domain')\n const endCustomer = input.endCustomerId ? await counterparties.findOne({ where: { id: input.endCustomerId, domain: { id: domain.id } } }) : null\n if (input.endCustomerId && !endCustomer) throw new Refusal('opportunity-end-customer-invalid', {}, 'Choose the end customer among the counterparties of this domain')\n\n const ownerId = input.ownerId || old?.ownerId || user.id\n /* The people work here may be given to — auth-base's one answer, the same a license's recipient gets. */\n if (ownerId !== user.id && ownerId !== old?.ownerId && !(await getDomainAssignees(domain, tx)).some(one => one.id === ownerId))\n throw new Refusal('opportunity-owner-invalid', {}, 'Put it in the charge of a member of this domain')\n\n const staged = !!old && old.stage !== stage\n /* The probability follows the stage unless the person gave one; with the stage unchanged it stays as it was. */\n const probability = !old || staged ? probabilityAfter(stage, input.probability) : input.probability ?? old.probability\n\n const repository = tx.getRepository(Opportunity)\n const row = await repository.save(\n repository.create({\n ...(old ? { id: old.id, creator: old.creatorId ? ({ id: old.creatorId } as any) : undefined } : { creator: user }),\n domain,\n name,\n counterparty,\n endCustomer: endCustomer ?? null,\n amount: input.amount,\n currency,\n expectedCloseMonth: input.expectedCloseMonth,\n stage,\n probability,\n owner: { id: ownerId } as any,\n /* A lost opportunity opened again is no longer closed. */\n closedOn: null,\n lostReason: null,\n description: input.description?.trim() || null,\n updater: user\n })\n )\n const saved = await opportunityInDomain(tx, domain, row.id)\n await recordOpportunityEvent(tx, domain, user, saved, !old ? 'CREATED' : staged ? 'STAGED' : 'UPDATED', staged ? old.stage : undefined)\n return saved\n}\n\n/**\n * Close an open opportunity — lost, with the reason, by a person; won by the application that turned it into its own\n * record (Office's contract, ADR-0138 decision 8). The day is the domain's today unless one is given.\n */\nexport async function closeOpportunity(\n tx: EntityManager,\n domain: Domain,\n user: any,\n id: string,\n outcome: OpportunityStage.WON | OpportunityStage.LOST,\n { closedOn, reason }: { closedOn?: string; reason?: string } = {}\n) {\n const opportunity = await opportunityInDomain(tx, domain, id)\n if (!stageMove(opportunity.stage, outcome)) throw new Refusal('opportunity-stage-move-invalid', {}, 'The opportunity cannot move to that stage')\n if (outcome === OpportunityStage.LOST && !reason?.trim()) throw new Refusal('opportunity-reason-required', {}, 'Say why it was lost')\n const day = closedOn || calendarDay(domain.timezone)\n if (!validDay(day)) throw new Refusal('opportunity-closed-on-invalid', {}, 'Give the closing day as YYYY-MM-DD')\n\n const from = opportunity.stage\n await tx.getRepository(Opportunity).update(\n { id: opportunity.id },\n { stage: outcome, probability: probabilityAfter(outcome), closedOn: day, lostReason: outcome === OpportunityStage.LOST ? reason!.trim() : null, updater: user }\n )\n const closed = await opportunityInDomain(tx, domain, id)\n await recordOpportunityEvent(tx, domain, user, closed, outcome, from, outcome === OpportunityStage.LOST ? closed.lostReason : undefined)\n return closed\n}\n\nfunction recordOpportunityEvent(tx: EntityManager, domain: Domain, user: any, opportunity: Opportunity, action: string, fromStage?: OpportunityStage, reason?: string) {\n return recordSalesEvent(\n tx,\n OpportunityEvent,\n { opportunity, fromStage: fromStage ?? null, stage: opportunity.stage, probability: opportunity.probability },\n { domain, user, action, reason, snapshot: snapshotOf(opportunity) }\n )\n}\n\n/** The opportunity as it stood. */\nfunction snapshotOf(opportunity: Opportunity) {\n const keys = ['id', 'name', 'counterpartyId', 'endCustomerId', 'amount', 'currency', 'expectedCloseMonth', 'stage', 'probability', 'ownerId', 'closedOn', 'lostReason', 'description']\n return Object.fromEntries(keys.map(key => [key, opportunity[key] ?? null]))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"opportunity-service.js","sourceRoot":"","sources":["../../../server/service/opportunity/opportunity-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDomainAssignees } from '@things-factory/auth-base'\nimport { Counterparty } from '@things-factory/biz-base'\nimport { currencyDigits, validDay } from '@things-factory/invoice'\nimport { Domain, Refusal, calendarDay } from '@things-factory/shell'\nimport { EntityManager } from 'typeorm'\n\nimport { recordSalesEvent } from '../sales-event'\nimport { Opportunity } from './opportunity'\nimport { OpportunityEvent } from './opportunity-event'\nimport { isOpen, OpportunityStage, probabilityAfter, stageMove, validMonth, validProbability } from './opportunity-rules'\nimport { NewOpportunity } from './opportunity-type'\n\nexport async function opportunityInDomain(tx: EntityManager, domain: Domain, id: string) {\n const opportunity = await tx.getRepository(Opportunity).findOne({ where: { id, domain: { id: domain.id } } })\n if (!opportunity) throw new Refusal('opportunity-not-found', {}, 'Opportunity not found')\n return opportunity\n}\n\n/**\n * Create an opportunity, or change one a person may still change. An open one takes any open stage; a lost one is\n * opened again by giving it an open stage. Closing is `closeOpportunity`'s.\n */\nexport async function saveOpportunity(tx: EntityManager, domain: Domain, user: any, input: NewOpportunity, id?: string) {\n const old = id ? await opportunityInDomain(tx, domain, id) : undefined\n const stage = input.stage ?? old?.stage ?? OpportunityStage.LEAD\n /* A closed one changes only by being opened again, and only a lost one may be. */\n if (old && !isOpen(old.stage) && !(isOpen(stage) && stageMove(old.stage, stage))) throw new Refusal('opportunity-closed', {}, 'A closed opportunity is not changed')\n if (!isOpen(stage)) throw new Refusal('opportunity-stage-open-required', {}, 'Give an open stage; won and lost are closings')\n\n const name = input.name?.trim()\n if (!name) throw new Refusal('opportunity-name-required', {}, 'Name the opportunity')\n if (!Number.isFinite(input.amount) || input.amount < 0) throw new Refusal('opportunity-amount-invalid', {}, 'The expected amount is zero or more')\n const currency = (input.currency?.trim() || 'KRW').toUpperCase()\n currencyDigits(currency)\n if (!validMonth(input.expectedCloseMonth)) throw new Refusal('opportunity-close-month-invalid', {}, 'Give the expected month as YYYY-MM')\n if (input.probability !== undefined && input.probability !== null && !validProbability(input.probability))\n throw new Refusal('opportunity-probability-invalid', {}, 'The probability is a whole number from 0 to 100')\n\n const counterparties = tx.getRepository(Counterparty)\n const counterparty = input.counterpartyId ? await counterparties.findOne({ where: { id: input.counterpartyId, domain: { id: domain.id } } }) : null\n if (!counterparty) throw new Refusal('opportunity-counterparty-required', {}, 'Choose the counterparty of this domain')\n const endCustomer = input.endCustomerId ? await counterparties.findOne({ where: { id: input.endCustomerId, domain: { id: domain.id } } }) : null\n if (input.endCustomerId && !endCustomer) throw new Refusal('opportunity-end-customer-invalid', {}, 'Choose the end customer among the counterparties of this domain')\n\n const ownerId = input.ownerId || old?.ownerId || user.id\n /* The people work here may be given to — auth-base's one answer, the same a license's recipient gets. */\n if (ownerId !== user.id && ownerId !== old?.ownerId && !(await getDomainAssignees(domain, tx)).some(one => one.id === ownerId))\n throw new Refusal('opportunity-owner-invalid', {}, 'Put it in the charge of a member of this domain')\n\n const staged = !!old && old.stage !== stage\n /* The probability follows the stage unless the person gave one; with the stage unchanged it stays as it was. */\n const probability = !old || staged ? probabilityAfter(stage, input.probability) : input.probability ?? old.probability\n\n const repository = tx.getRepository(Opportunity)\n const row = await repository.save(\n repository.create({\n ...(old ? { id: old.id, creator: old.creatorId ? ({ id: old.creatorId } as any) : undefined } : { creator: user }),\n domain,\n name,\n counterparty,\n endCustomer: endCustomer ?? null,\n amount: input.amount,\n currency,\n expectedCloseMonth: input.expectedCloseMonth,\n stage,\n probability,\n owner: { id: ownerId } as any,\n /* A lost opportunity opened again is no longer closed. */\n closedOn: null,\n lostReason: null,\n description: input.description?.trim() || null,\n updater: user\n })\n )\n const saved = await opportunityInDomain(tx, domain, row.id)\n await recordOpportunityEvent(tx, domain, user, saved, !old ? 'CREATED' : staged ? 'STAGED' : 'UPDATED', staged ? old.stage : undefined)\n return saved\n}\n\n/**\n * Close an open opportunity — lost, with the reason, by a person; won by the application that turned it into its own\n * record (Office's contract, ADR-0138 decision 8). The day is the domain's today unless one is given.\n */\nexport async function closeOpportunity(\n tx: EntityManager,\n domain: Domain,\n user: any,\n id: string,\n outcome: OpportunityStage.WON | OpportunityStage.LOST,\n { closedOn, reason }: { closedOn?: string; reason?: string } = {}\n) {\n const opportunity = await opportunityInDomain(tx, domain, id)\n if (!stageMove(opportunity.stage, outcome)) throw new Refusal('opportunity-stage-move-invalid', {}, 'The opportunity cannot move to that stage')\n if (outcome === OpportunityStage.LOST && !reason?.trim()) throw new Refusal('opportunity-reason-required', {}, 'Say why it was lost')\n const day = closedOn || calendarDay(domain.timezone)\n if (!validDay(day)) throw new Refusal('opportunity-closed-on-invalid', {}, 'Give the closing day as YYYY-MM-DD')\n\n const from = opportunity.stage\n await tx.getRepository(Opportunity).update(\n { id: opportunity.id },\n { stage: outcome, probability: probabilityAfter(outcome), closedOn: day, lostReason: outcome === OpportunityStage.LOST ? reason!.trim() : null, updater: user }\n )\n const closed = await opportunityInDomain(tx, domain, id)\n await recordOpportunityEvent(tx, domain, user, closed, outcome, from, outcome === OpportunityStage.LOST ? closed.lostReason : undefined)\n return closed\n}\n\nfunction recordOpportunityEvent(tx: EntityManager, domain: Domain, user: any, opportunity: Opportunity, action: string, fromStage?: OpportunityStage, reason?: string) {\n return recordSalesEvent(\n tx,\n OpportunityEvent,\n { opportunity, fromStage: fromStage ?? null, stage: opportunity.stage, probability: opportunity.probability },\n { domain, user, action, reason, snapshot: snapshotOf(opportunity) }\n )\n}\n\n/** The opportunity as it stood. */\nfunction snapshotOf(opportunity: Opportunity) {\n const keys = ['id', 'name', 'counterpartyId', 'endCustomerId', 'amount', 'currency', 'expectedCloseMonth', 'stage', 'probability', 'ownerId', 'closedOn', 'lostReason', 'description']\n return Object.fromEntries(keys.map(key => [key, opportunity[key] ?? null]))\n}\n"]}
|
|
@@ -53,7 +53,7 @@ async function saveQuotation(tx, domain, user, input, id) {
|
|
|
53
53
|
async function transitionQuotation(tx, domain, user, id, action, reason) {
|
|
54
54
|
await (0, invoice_1.lockDocumentDomain)(tx, domain);
|
|
55
55
|
const quotation = await quotationInDomain(tx, domain, id);
|
|
56
|
-
const next = (0, quotation_rules_1.quotationTransition)(quotation.status, action, quotation.validUntil, (0,
|
|
56
|
+
const next = (0, quotation_rules_1.quotationTransition)(quotation.status, action, quotation.validUntil, (0, shell_1.calendarDay)(domain.timezone));
|
|
57
57
|
if (!next)
|
|
58
58
|
throw new shell_1.Refusal('quotation-transition-invalid', {}, 'The quotation status or validity does not allow this');
|
|
59
59
|
if (next === quotation_1.QuotationStatus.REJECTED && !reason?.trim())
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quotation-service.js","sourceRoot":"","sources":["../../../server/service/quotation/quotation-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { calculateDocument, calendarDay, currencyDigits, HEADER_KEYS, lockDocumentDomain, normalizedHeader, TAX_ROUNDING, validDay } from '@things-factory/invoice'\nimport { Domain, getRepository, Refusal } from '@things-factory/shell'\nimport { EntityManager } from 'typeorm'\n\nimport { Opportunity } from '../opportunity/opportunity'\nimport { recordSalesEvent } from '../sales-event'\nimport { Quotation, QuotationStatus } from './quotation'\nimport { QuotationEvent } from './quotation-event'\nimport { QuotationItem } from './quotation-item'\nimport { QuotationAction, quotationTransition, revisable } from './quotation-rules'\nimport { NewQuotation } from './quotation-type'\n\nexport async function quotationInDomain(tx: EntityManager, domain: Domain, id: string) {\n const quotation = await tx.getRepository(Quotation).findOne({ where: { id, domain: { id: domain.id } }, relations: ['items', 'creator'] })\n if (!quotation) throw new Refusal('quotation-not-found', {}, 'Quotation not found')\n quotation.items.sort((a, b) => a.position - b.position)\n return quotation\n}\n\nexport async function saveQuotation(tx: EntityManager, domain: Domain, user: any, input: NewQuotation, id?: string) {\n await lockDocumentDomain(tx, domain)\n const old = id ? await quotationInDomain(tx, domain, id) : undefined\n if (old && old.status !== QuotationStatus.DRAFT) throw new Refusal('quotation-draft-required', {}, 'Revise a sent quotation to change it')\n\n const header = normalizedHeader(input)\n if (!validDay(input.validUntil)) throw new Refusal('quotation-valid-until', {}, 'A valid last day is required')\n /* A revision answers the quotation it revises: it keeps the number. */\n if (old && old.version > 1 && header.number !== old.number) throw new Refusal('quotation-revision-number', {}, 'A revision keeps its quotation number')\n\n const repository = tx.getRepository(Quotation)\n const version = old?.version ?? 1\n const duplicate = await repository.findOne({ where: { domain: { id: domain.id }, number: header.number, version } })\n if (duplicate && duplicate.id !== id) throw new Refusal('quotation-number-duplicate', {}, 'Quotation number already exists')\n\n const opportunity = input.opportunityId ? await tx.getRepository(Opportunity).findOne({ where: { id: input.opportunityId, domain: { id: domain.id } } }) : null\n if (input.opportunityId && !opportunity) throw new Refusal('quotation-opportunity-invalid', {}, 'Choose an opportunity of this domain')\n\n const { items, supplyAmount, taxAmount, totalAmount } = calculateDocument(input.items, header.currency)\n const quotation = await repository.save(\n repository.create({ ...old, ...header, version, validUntil: input.validUntil, opportunity, supplyAmount, taxAmount, totalAmount, domain, creator: old?.creator ?? user, updater: user, items: undefined })\n )\n const lines = tx.getRepository(QuotationItem)\n if (old) await lines.softDelete({ quotation: { id: quotation.id } })\n await lines.save(items.map(({ receivedTaxAmount, ...item }) => lines.create({ ...item, quotation, domain, creator: user, updater: user })))\n\n const saved = await quotationInDomain(tx, domain, quotation.id)\n await recordQuotationEvent(tx, domain, user, saved, old ? 'UPDATED' : 'CREATED')\n return saved\n}\n\nexport async function transitionQuotation(tx: EntityManager, domain: Domain, user: any, id: string, action: QuotationAction, reason?: string) {\n await lockDocumentDomain(tx, domain)\n const quotation = await quotationInDomain(tx, domain, id)\n const next = quotationTransition(quotation.status, action, quotation.validUntil, calendarDay(domain.timezone))\n if (!next) throw new Refusal('quotation-transition-invalid', {}, 'The quotation status or validity does not allow this')\n if (next === QuotationStatus.REJECTED && !reason?.trim()) throw new Refusal('quotation-reason-required', {}, 'Say why the buyer declined')\n if (next === QuotationStatus.SENT) {\n if (!quotation.issueDate) throw new Refusal('quotation-issue-date-required', {}, 'A quotation is sent with its issue date')\n /* What the amounts were reckoned with stays on the quotation; a later rule never recomputes them. */\n quotation.taxRounding = TAX_ROUNDING\n quotation.currencyDigits = currencyDigits(quotation.currency)\n }\n quotation.status = next\n if (next === QuotationStatus.REJECTED) quotation.outcomeReason = reason!.trim()\n quotation.updater = user\n const { items, ...head } = quotation\n await tx.getRepository(Quotation).save(head)\n await recordQuotationEvent(tx, domain, user, quotation, action, reason)\n return quotationInDomain(tx, domain, id)\n}\n\n/** A new draft version of a sent quotation, with the same number; the version it replaces is superseded. */\nexport async function reviseQuotation(tx: EntityManager, domain: Domain, user: any, id: string) {\n await lockDocumentDomain(tx, domain)\n const old = await quotationInDomain(tx, domain, id)\n if (!revisable(old.status)) throw new Refusal('quotation-revision-invalid', {}, 'Only a sent quotation nobody accepted can be revised')\n const versions = await tx.getRepository(Quotation).find({ where: { domain: { id: domain.id }, number: old.number } })\n if (old.version !== Math.max(...versions.map(one => one.version))) throw new Refusal('quotation-latest-required', {}, 'Revise the latest version')\n\n const repository = tx.getRepository(Quotation)\n const { id: _id, items, createdAt, updatedAt, creator, updater, status, taxRounding, currencyDigits: digits, outcomeReason, opportunityId, ...document } = old\n /* A revision answers the same opportunity as the version it replaces. */\n const opportunity = opportunityId ? ({ id: opportunityId } as Opportunity) : null\n const revision = await repository.save(repository.create({ ...document, opportunity, version: old.version + 1, status: QuotationStatus.DRAFT, domain, creator: user, updater: user }))\n const lines = tx.getRepository(QuotationItem)\n await lines.save(\n items.map(({ id: _line, quotation: _parent, quotationId, createdAt, updatedAt, deletedAt, creator, updater, ...line }) =>\n lines.create({ ...line, quotation: revision, domain, creator: user, updater: user })\n )\n )\n\n old.status = QuotationStatus.SUPERSEDED\n old.updater = user\n const { items: _items, ...head } = old\n await repository.save(head)\n await recordQuotationEvent(tx, domain, user, old, 'SUPERSEDED')\n const saved = await quotationInDomain(tx, domain, revision.id)\n await recordQuotationEvent(tx, domain, user, saved, 'REVISED')\n return saved\n}\n\nfunction recordQuotationEvent(tx: EntityManager, domain: Domain, user: any, quotation: Quotation, action: string, reason?: string) {\n return recordSalesEvent(tx, QuotationEvent, { quotation }, { domain, user, action, reason, snapshot: snapshotOf(quotation) })\n}\n\n/** The quotation as printed. */\nexport function snapshotOf(quotation: Quotation) {\n const keys = ['id', ...HEADER_KEYS, 'version', 'status', 'validUntil', 'opportunityId', 'supplyAmount', 'taxAmount', 'totalAmount', 'taxRounding', 'currencyDigits', 'outcomeReason']\n return {\n ...Object.fromEntries(keys.map(key => [key, quotation[key] ?? null])),\n items: quotation.items.map(({ position, description, quantity, unitPrice, discount, taxKind, taxRate, supplyAmount }) => ({\n position,\n description,\n quantity,\n unitPrice,\n discount,\n taxKind,\n taxRate,\n supplyAmount\n }))\n }\n}\n\n/** The quotations that answer an opportunity, every version, by number then version. */\nexport function quotationsOfOpportunity(tx: EntityManager | undefined, domain: Domain, opportunityId: string) {\n const repository = tx ? tx.getRepository(Quotation) : getRepository(Quotation)\n return repository.find({ where: { domain: { id: domain.id }, opportunity: { id: opportunityId } }, order: { number: 'ASC', version: 'ASC' } })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"quotation-service.js","sourceRoot":"","sources":["../../../server/service/quotation/quotation-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { calculateDocument, currencyDigits, HEADER_KEYS, lockDocumentDomain, normalizedHeader, TAX_ROUNDING, validDay } from '@things-factory/invoice'\nimport { Domain, getRepository, Refusal, calendarDay } from '@things-factory/shell'\nimport { EntityManager } from 'typeorm'\n\nimport { Opportunity } from '../opportunity/opportunity'\nimport { recordSalesEvent } from '../sales-event'\nimport { Quotation, QuotationStatus } from './quotation'\nimport { QuotationEvent } from './quotation-event'\nimport { QuotationItem } from './quotation-item'\nimport { QuotationAction, quotationTransition, revisable } from './quotation-rules'\nimport { NewQuotation } from './quotation-type'\n\nexport async function quotationInDomain(tx: EntityManager, domain: Domain, id: string) {\n const quotation = await tx.getRepository(Quotation).findOne({ where: { id, domain: { id: domain.id } }, relations: ['items', 'creator'] })\n if (!quotation) throw new Refusal('quotation-not-found', {}, 'Quotation not found')\n quotation.items.sort((a, b) => a.position - b.position)\n return quotation\n}\n\nexport async function saveQuotation(tx: EntityManager, domain: Domain, user: any, input: NewQuotation, id?: string) {\n await lockDocumentDomain(tx, domain)\n const old = id ? await quotationInDomain(tx, domain, id) : undefined\n if (old && old.status !== QuotationStatus.DRAFT) throw new Refusal('quotation-draft-required', {}, 'Revise a sent quotation to change it')\n\n const header = normalizedHeader(input)\n if (!validDay(input.validUntil)) throw new Refusal('quotation-valid-until', {}, 'A valid last day is required')\n /* A revision answers the quotation it revises: it keeps the number. */\n if (old && old.version > 1 && header.number !== old.number) throw new Refusal('quotation-revision-number', {}, 'A revision keeps its quotation number')\n\n const repository = tx.getRepository(Quotation)\n const version = old?.version ?? 1\n const duplicate = await repository.findOne({ where: { domain: { id: domain.id }, number: header.number, version } })\n if (duplicate && duplicate.id !== id) throw new Refusal('quotation-number-duplicate', {}, 'Quotation number already exists')\n\n const opportunity = input.opportunityId ? await tx.getRepository(Opportunity).findOne({ where: { id: input.opportunityId, domain: { id: domain.id } } }) : null\n if (input.opportunityId && !opportunity) throw new Refusal('quotation-opportunity-invalid', {}, 'Choose an opportunity of this domain')\n\n const { items, supplyAmount, taxAmount, totalAmount } = calculateDocument(input.items, header.currency)\n const quotation = await repository.save(\n repository.create({ ...old, ...header, version, validUntil: input.validUntil, opportunity, supplyAmount, taxAmount, totalAmount, domain, creator: old?.creator ?? user, updater: user, items: undefined })\n )\n const lines = tx.getRepository(QuotationItem)\n if (old) await lines.softDelete({ quotation: { id: quotation.id } })\n await lines.save(items.map(({ receivedTaxAmount, ...item }) => lines.create({ ...item, quotation, domain, creator: user, updater: user })))\n\n const saved = await quotationInDomain(tx, domain, quotation.id)\n await recordQuotationEvent(tx, domain, user, saved, old ? 'UPDATED' : 'CREATED')\n return saved\n}\n\nexport async function transitionQuotation(tx: EntityManager, domain: Domain, user: any, id: string, action: QuotationAction, reason?: string) {\n await lockDocumentDomain(tx, domain)\n const quotation = await quotationInDomain(tx, domain, id)\n const next = quotationTransition(quotation.status, action, quotation.validUntil, calendarDay(domain.timezone))\n if (!next) throw new Refusal('quotation-transition-invalid', {}, 'The quotation status or validity does not allow this')\n if (next === QuotationStatus.REJECTED && !reason?.trim()) throw new Refusal('quotation-reason-required', {}, 'Say why the buyer declined')\n if (next === QuotationStatus.SENT) {\n if (!quotation.issueDate) throw new Refusal('quotation-issue-date-required', {}, 'A quotation is sent with its issue date')\n /* What the amounts were reckoned with stays on the quotation; a later rule never recomputes them. */\n quotation.taxRounding = TAX_ROUNDING\n quotation.currencyDigits = currencyDigits(quotation.currency)\n }\n quotation.status = next\n if (next === QuotationStatus.REJECTED) quotation.outcomeReason = reason!.trim()\n quotation.updater = user\n const { items, ...head } = quotation\n await tx.getRepository(Quotation).save(head)\n await recordQuotationEvent(tx, domain, user, quotation, action, reason)\n return quotationInDomain(tx, domain, id)\n}\n\n/** A new draft version of a sent quotation, with the same number; the version it replaces is superseded. */\nexport async function reviseQuotation(tx: EntityManager, domain: Domain, user: any, id: string) {\n await lockDocumentDomain(tx, domain)\n const old = await quotationInDomain(tx, domain, id)\n if (!revisable(old.status)) throw new Refusal('quotation-revision-invalid', {}, 'Only a sent quotation nobody accepted can be revised')\n const versions = await tx.getRepository(Quotation).find({ where: { domain: { id: domain.id }, number: old.number } })\n if (old.version !== Math.max(...versions.map(one => one.version))) throw new Refusal('quotation-latest-required', {}, 'Revise the latest version')\n\n const repository = tx.getRepository(Quotation)\n const { id: _id, items, createdAt, updatedAt, creator, updater, status, taxRounding, currencyDigits: digits, outcomeReason, opportunityId, ...document } = old\n /* A revision answers the same opportunity as the version it replaces. */\n const opportunity = opportunityId ? ({ id: opportunityId } as Opportunity) : null\n const revision = await repository.save(repository.create({ ...document, opportunity, version: old.version + 1, status: QuotationStatus.DRAFT, domain, creator: user, updater: user }))\n const lines = tx.getRepository(QuotationItem)\n await lines.save(\n items.map(({ id: _line, quotation: _parent, quotationId, createdAt, updatedAt, deletedAt, creator, updater, ...line }) =>\n lines.create({ ...line, quotation: revision, domain, creator: user, updater: user })\n )\n )\n\n old.status = QuotationStatus.SUPERSEDED\n old.updater = user\n const { items: _items, ...head } = old\n await repository.save(head)\n await recordQuotationEvent(tx, domain, user, old, 'SUPERSEDED')\n const saved = await quotationInDomain(tx, domain, revision.id)\n await recordQuotationEvent(tx, domain, user, saved, 'REVISED')\n return saved\n}\n\nfunction recordQuotationEvent(tx: EntityManager, domain: Domain, user: any, quotation: Quotation, action: string, reason?: string) {\n return recordSalesEvent(tx, QuotationEvent, { quotation }, { domain, user, action, reason, snapshot: snapshotOf(quotation) })\n}\n\n/** The quotation as printed. */\nexport function snapshotOf(quotation: Quotation) {\n const keys = ['id', ...HEADER_KEYS, 'version', 'status', 'validUntil', 'opportunityId', 'supplyAmount', 'taxAmount', 'totalAmount', 'taxRounding', 'currencyDigits', 'outcomeReason']\n return {\n ...Object.fromEntries(keys.map(key => [key, quotation[key] ?? null])),\n items: quotation.items.map(({ position, description, quantity, unitPrice, discount, taxKind, taxRate, supplyAmount }) => ({\n position,\n description,\n quantity,\n unitPrice,\n discount,\n taxKind,\n taxRate,\n supplyAmount\n }))\n }\n}\n\n/** The quotations that answer an opportunity, every version, by number then version. */\nexport function quotationsOfOpportunity(tx: EntityManager | undefined, domain: Domain, opportunityId: string) {\n const repository = tx ? tx.getRepository(Quotation) : getRepository(Quotation)\n return repository.find({ where: { domain: { id: domain.id }, opportunity: { id: opportunityId } }, order: { number: 'ASC', version: 'ASC' } })\n}\n"]}
|