@opencxh/domain 1.276.0 → 1.278.0
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/entities/business-entity/types.d.ts +16 -7
- package/dist/entities/catalog/pricing.d.ts +12 -1
- package/dist/entities/catalog/types.d.ts +5 -0
- package/dist/entities/company/types.d.ts +5 -0
- package/dist/entities/contact/index.d.ts +3 -0
- package/dist/entities/contact/lifecycle.d.ts +9 -0
- package/dist/entities/contact/name.d.ts +3 -0
- package/dist/entities/contact/segment.d.ts +32 -0
- package/dist/entities/contact/types.d.ts +6 -0
- package/dist/entities/custom-field-def/types.d.ts +3 -3
- package/dist/entities/saved-view/index.d.ts +1 -0
- package/dist/entities/saved-view/types.d.ts +30 -0
- package/dist/entities/transaction/convert.d.ts +16 -0
- package/dist/entities/transaction/document.d.ts +2 -0
- package/dist/entities/transaction/keys.d.ts +15 -0
- package/dist/entities/transaction/kinds.d.ts +25 -4
- package/dist/entities/transaction/payment-term.d.ts +0 -8
- package/dist/entities/transaction/types.d.ts +21 -1
- package/dist/entities/work/index.d.ts +2 -0
- package/dist/entities/work/ladder.d.ts +3 -0
- package/dist/entities/work/recurrence.d.ts +26 -0
- package/dist/entities/work/recurrence.test.d.ts +1 -0
- package/dist/entities/work/types.d.ts +27 -7
- package/dist/entities/work/view.d.ts +27 -0
- package/dist/index.cjs +20 -20
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1666 -1438
- package/package.json +1 -1
|
@@ -63,12 +63,21 @@ export type BusinessEntityLike = Pick<BusinessEntity, "name" | "address" | "vatN
|
|
|
63
63
|
/**
|
|
64
64
|
* Does this row belong to this entity?
|
|
65
65
|
*
|
|
66
|
-
* Master data is owned by **exactly one** entity
|
|
67
|
-
*
|
|
68
|
-
* backfill: every product written before entities existed keeps showing up where it always did,
|
|
69
|
-
* and the first save stamps it for real.
|
|
70
|
-
*
|
|
71
|
-
* The check runs in memory because the rows are loaded whole anyway, and because "the field is
|
|
72
|
-
* unset" is not expressible as a filter on this platform — there is no `$exists`.
|
|
66
|
+
* Master data is owned by **exactly one** entity. An empty owner means the **default** entity, so
|
|
67
|
+
* rows written before entities existed stay where they were without a backfill.
|
|
73
68
|
*/
|
|
74
69
|
export declare function belongsToEntity(rowEntityId: string | undefined, entityId: string, isDefaultEntity: boolean): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* The `businessEntityId` values that belong to this entity, as a query filter:
|
|
72
|
+
* `{ businessEntityId: { $in: entityOwnerIds(entity) } }`. Same rule as {@link belongsToEntity};
|
|
73
|
+
* it works as a filter because every sales schema defaults the column to `""`, never unset.
|
|
74
|
+
*/
|
|
75
|
+
export declare function entityOwnerIds(entity: Pick<BusinessEntity, "id" | "isDefault">): string[];
|
|
76
|
+
/**
|
|
77
|
+
* A shared table with per-entity overrides: rows with an empty `businessEntityId` first, then the
|
|
78
|
+
* entity's own over them by `key`. How VAT rates and payment terms both work — a Belgian sister
|
|
79
|
+
* needs `low` at 6, not `low` hidden.
|
|
80
|
+
*/
|
|
81
|
+
export declare function shadowByKey<T extends {
|
|
82
|
+
businessEntityId?: string;
|
|
83
|
+
}>(rows: readonly T[], key: keyof T, businessEntityId?: string): T[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Cents } from '../money/money';
|
|
2
|
+
import { TransactionLine } from '../transaction/types';
|
|
2
3
|
import { PriceRule, Product } from './types';
|
|
3
4
|
/**
|
|
4
5
|
* The ladder: the most specific rule wins, whole. No sum, no stacking.
|
|
@@ -10,7 +11,9 @@ import { PriceRule, Product } from './types';
|
|
|
10
11
|
* | 3 | customer + category | `customer_category` |
|
|
11
12
|
* | 4 | segment + product | `segment` |
|
|
12
13
|
* | 5 | segment + category | `segment_category` |
|
|
13
|
-
* | 6 |
|
|
14
|
+
* | 6 | everyone + product | `everyone` |
|
|
15
|
+
* | 7 | everyone + category | `everyone_category` |
|
|
16
|
+
* | 8 | the product's list price | `list` |
|
|
14
17
|
*
|
|
15
18
|
* A tier (`minQuantity`) is not a rung of its own but a tie-break within one: of the rules that
|
|
16
19
|
* pass, the highest threshold that the quantity reaches wins, and the source then reads `tier`
|
|
@@ -38,3 +41,11 @@ export interface ResolvedPrice {
|
|
|
38
41
|
* specific its customer is: a discount on "everything" is a decision, not a price rule.
|
|
39
42
|
*/
|
|
40
43
|
export declare function resolvePrice(product: Product, ctx: PriceContext, rules: readonly PriceRule[]): ResolvedPrice;
|
|
44
|
+
/** The `priceSourceKey` of a price somebody typed themselves. The ladder never overwrites it. */
|
|
45
|
+
export declare const MANUAL_PRICE_SOURCE = "manual";
|
|
46
|
+
/**
|
|
47
|
+
* The ladder again, for lines already on a document: after the quantity or the customer changed,
|
|
48
|
+
* a tier or an agreement may now apply. A typed price stays, and so does a line whose product
|
|
49
|
+
* is no longer in `products`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function repriceLines(lines: readonly TransactionLine[], products: readonly Product[], rules: readonly PriceRule[], ctx: Omit<PriceContext, "quantity">): TransactionLine[];
|
|
@@ -32,6 +32,11 @@ export interface Product {
|
|
|
32
32
|
* types "Services" and another "services " cannot carry that.
|
|
33
33
|
*/
|
|
34
34
|
categoryKey?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Which revenue account a line books to, copied onto the line. Not `categoryKey`: that one
|
|
37
|
+
* groups the offering for price agreements, and "discount on services" names no account.
|
|
38
|
+
*/
|
|
39
|
+
ledgerCategoryKey?: string;
|
|
35
40
|
/** References to this product elsewhere (`moneybird:412`). A reference, never a copy. */
|
|
36
41
|
externalIds?: string[];
|
|
37
42
|
/** See {@link FIXED_UNITS}. Drives the form and the word after the quantity. */
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { PostalAddress } from '../organization/types';
|
|
2
2
|
import { OwnerScope } from '../scope/types';
|
|
3
|
+
/** Same meaning as {@link ContactOrigin}: `shadow` is a lazily cached hit, a landed row is `own`. */
|
|
4
|
+
export type CompanyOrigin = "own" | "shadow";
|
|
3
5
|
/** Where a company row came from. `local` is a row somebody created by hand. */
|
|
4
6
|
export interface CompanySource {
|
|
5
7
|
providerId: string;
|
|
@@ -40,6 +42,9 @@ export interface Company {
|
|
|
40
42
|
externalIds: string[];
|
|
41
43
|
ownerScope: OwnerScope;
|
|
42
44
|
source: CompanySource;
|
|
45
|
+
origin?: CompanyOrigin;
|
|
46
|
+
/** The group this company belongs to. One level up per row; a cycle is refused on write. */
|
|
47
|
+
parentCompanyId?: string;
|
|
43
48
|
/**
|
|
44
49
|
* Where the company sits. Needed by anything that puts this company on paper — a quote's
|
|
45
50
|
* recipient block is the first, and the issue gate refuses a document with a hole in it.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a person stands with us. `lead` has not been qualified yet; qualifying (a deal is made)
|
|
3
|
+
* moves it to `opportunity`. `customer` and `former` are set by hand or by a rule.
|
|
4
|
+
*/
|
|
5
|
+
export declare const LIFECYCLE_STAGES: readonly ["lead", "opportunity", "customer", "former"];
|
|
6
|
+
export type LifecycleStage = (typeof LIFECYCLE_STAGES)[number];
|
|
7
|
+
/** How far a lead is worked; only meaningful while the stage is `lead`. */
|
|
8
|
+
export declare const LEAD_STATUSES: readonly ["new", "working", "unqualified"];
|
|
9
|
+
export type LeadStatus = (typeof LEAD_STATUSES)[number];
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { SavedView, SavedViewSort } from '../saved-view/types';
|
|
2
|
+
/**
|
|
3
|
+
* A crm segment: a {@link SavedView} over contacts or companies. It stores the question, never
|
|
4
|
+
* the rows — whoever matches later is in it. One filter shape for both lists; `SEGMENT_FILTER_KEYS`
|
|
5
|
+
* says which keys a kind reads, and the server drops the rest.
|
|
6
|
+
*/
|
|
7
|
+
export type SegmentKind = "contact" | "company";
|
|
8
|
+
/** Multi-value filters: empty or absent = no filter on that axis. */
|
|
9
|
+
export declare const SEGMENT_LIST_FILTERS: readonly ["stages", "leadStatuses", "sources", "scopes", "cities", "parentCompanyIds"];
|
|
10
|
+
export type SegmentListFilter = (typeof SEGMENT_LIST_FILTERS)[number];
|
|
11
|
+
/** Yes/no filters that need another store to answer: open deals in work, people at a company. */
|
|
12
|
+
export declare const SEGMENT_PRESENCE_FILTERS: readonly ["openDeal", "hasContacts"];
|
|
13
|
+
export type SegmentPresenceFilter = (typeof SEGMENT_PRESENCE_FILTERS)[number];
|
|
14
|
+
export type SegmentFilters = {
|
|
15
|
+
search?: string;
|
|
16
|
+
/** Created in the last N days; relative, so a saved segment stays current. */
|
|
17
|
+
createdWithinDays?: number;
|
|
18
|
+
/** Select custom fields: field key → accepted values. */
|
|
19
|
+
fields?: Record<string, string[]>;
|
|
20
|
+
} & Partial<Record<SegmentListFilter, string[]>> & Partial<Record<SegmentPresenceFilter, "yes" | "no">>;
|
|
21
|
+
/** Which filters each list offers. `sources` holds provider ids (`local`, `hubspot`). */
|
|
22
|
+
export declare const SEGMENT_FILTER_KEYS: Record<SegmentKind, readonly string[]>;
|
|
23
|
+
export declare const SEGMENT_SORT_FIELDS: readonly ["updatedAt", "createdAt", "name"];
|
|
24
|
+
export type SegmentSortField = (typeof SEGMENT_SORT_FIELDS)[number];
|
|
25
|
+
export type SegmentSort = SavedViewSort<SegmentSortField>;
|
|
26
|
+
export declare const SEGMENT_LAYOUTS: readonly ["list"];
|
|
27
|
+
export type SegmentLayout = (typeof SEGMENT_LAYOUTS)[number];
|
|
28
|
+
export type CrmSegment = SavedView<SegmentFilters, SegmentLayout, SegmentSortField> & {
|
|
29
|
+
kind: SegmentKind;
|
|
30
|
+
};
|
|
31
|
+
/** True when a filter narrows anything; an empty segment is the plain list. */
|
|
32
|
+
export declare function segmentIsEmpty(filters: SegmentFilters): boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CommunicationScheme } from '../../platform/communication';
|
|
2
2
|
import { OwnerScope } from '../scope/types';
|
|
3
|
+
import { LeadStatus, LifecycleStage } from './lifecycle';
|
|
3
4
|
export interface ContactEndpoint {
|
|
4
5
|
scheme: CommunicationScheme;
|
|
5
6
|
resource: string;
|
|
@@ -69,6 +70,11 @@ export interface Contact {
|
|
|
69
70
|
*/
|
|
70
71
|
locale?: string;
|
|
71
72
|
origin?: ContactOrigin;
|
|
73
|
+
jobTitle?: string;
|
|
74
|
+
lifecycleStage?: LifecycleStage;
|
|
75
|
+
leadStatus?: LeadStatus;
|
|
76
|
+
/** When a deal was first made for this person (`POST /contact/:id/qualify`). */
|
|
77
|
+
qualifiedAt?: number;
|
|
72
78
|
/** When a shadow row last saw its source. There is no refresh job; touching refreshes. */
|
|
73
79
|
syncedAt?: number;
|
|
74
80
|
}
|
|
@@ -53,9 +53,9 @@ export interface CustomFieldDef {
|
|
|
53
53
|
*/
|
|
54
54
|
projectIds?: string[];
|
|
55
55
|
/**
|
|
56
|
-
* When true,
|
|
57
|
-
*
|
|
58
|
-
* high-cardinality dimensions.
|
|
56
|
+
* When true, the owning app reports on it: a `select` becomes a dimension (`comms.cf.<key>`,
|
|
57
|
+
* `work.cf.<key>`), and in work a `currency` becomes an amount summed over what closed. Opt-in
|
|
58
|
+
* to avoid turning free-text fields into high-cardinality dimensions.
|
|
59
59
|
*/
|
|
60
60
|
reportable?: boolean;
|
|
61
61
|
required?: boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './types';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { OwnerScope } from '../scope/types';
|
|
2
|
+
/**
|
|
3
|
+
* A saved view on any list: a filter, an order, a grouping and a layout, under a name.
|
|
4
|
+
*
|
|
5
|
+
* The shape is shared, the store is not: each app keeps its own views next to the rows they
|
|
6
|
+
* filter (`work_view` in work), because only the owning app knows what its filters mean. The
|
|
7
|
+
* routes come from `savedViewRoutes` in platform-api, so an app joins with its filter shape and
|
|
8
|
+
* nothing else.
|
|
9
|
+
*
|
|
10
|
+
* Personal (`ownerScope.kind: "personal"`) or shared with a team or the organisation; whoever may
|
|
11
|
+
* write that scope may change the view. It stores what the screen chooses, never the rows.
|
|
12
|
+
*/
|
|
13
|
+
export interface SavedView<TFilters extends object = Record<string, unknown>, TLayout extends string = string, TSortField extends string = string> {
|
|
14
|
+
id: string;
|
|
15
|
+
organizationId: string;
|
|
16
|
+
name: string;
|
|
17
|
+
ownerScope: OwnerScope;
|
|
18
|
+
filters: TFilters;
|
|
19
|
+
sort?: SavedViewSort<TSortField>;
|
|
20
|
+
/** A grouping the owning list knows; free text here, validated nowhere but there. */
|
|
21
|
+
groupBy?: string;
|
|
22
|
+
layout: TLayout;
|
|
23
|
+
createdBy: string;
|
|
24
|
+
createdAt?: number;
|
|
25
|
+
updatedAt?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface SavedViewSort<TField extends string = string> {
|
|
28
|
+
field: TField;
|
|
29
|
+
dir: "asc" | "desc";
|
|
30
|
+
}
|
|
@@ -13,8 +13,24 @@ export declare function isFrozen(transaction: Pick<Transaction, "issuedAt">): bo
|
|
|
13
13
|
* Everything else is a `BadRequestError`. Whoever wants to change a sent quote gets v2.
|
|
14
14
|
*/
|
|
15
15
|
export declare const FROZEN_WRITABLE_FIELDS: readonly (keyof Transaction)[];
|
|
16
|
+
/**
|
|
17
|
+
* What a save may set on a draft. `status` is not here: it moves through `issue` and the status
|
|
18
|
+
* route, which check the transition and announce it.
|
|
19
|
+
*/
|
|
20
|
+
export declare const DRAFT_FIELDS: readonly ["companyId", "contactId", "dealItemId", "title", "reference", "currency", "taxOverrideKey", "introText", "termsText", "expiresAt", "deliveredAt", "periodFrom", "periodTo", "paymentTerm", "lines"];
|
|
21
|
+
/**
|
|
22
|
+
* The draft fields a save may empty, named in `clear`. A list and not `null` in the body:
|
|
23
|
+
* `invoke` strips `null`, so "empty this" never reaches the server any other way.
|
|
24
|
+
*/
|
|
25
|
+
export declare const CLEARABLE_FIELDS: readonly ["contactId", "dealItemId", "title", "reference", "taxOverrideKey", "introText", "termsText", "expiresAt", "deliveredAt", "periodFrom", "periodTo"];
|
|
26
|
+
export type ClearableField = (typeof CLEARABLE_FIELDS)[number];
|
|
16
27
|
/** The fields a draft carries over; the server owns id, organisation and author. */
|
|
17
28
|
export type TransactionDraft = Omit<Transaction, "id" | "organizationId" | "createdBy" | "createdAt" | "updatedAt">;
|
|
29
|
+
/**
|
|
30
|
+
* Whether `source` may become a `kindKey`: offered, a step the source's kind allows, and for a
|
|
31
|
+
* correcting kind (a credit note) only from the issued document it corrects.
|
|
32
|
+
*/
|
|
33
|
+
export declare function canConvert(source: Pick<Transaction, "kindKey" | "issuedAt">, kindKey: string): boolean;
|
|
18
34
|
/**
|
|
19
35
|
* The conversion: quote -> order -> invoice -> credit note, as one function.
|
|
20
36
|
*
|
|
@@ -24,6 +24,8 @@ export interface TransactionContextSources {
|
|
|
24
24
|
organizationName?: string;
|
|
25
25
|
/** Who the recipient reads; drives which template row is picked. */
|
|
26
26
|
locale?: string;
|
|
27
|
+
/** The number of the document a credit note corrects. */
|
|
28
|
+
creditedNumber?: string;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* The tokens: `{customer.name}`, `{quote.number}`, `{totals.gross}`.
|
|
@@ -12,6 +12,21 @@ export declare const PRODUCT_SCOPE_KIND = "product";
|
|
|
12
12
|
/** `invoice:412`. The kind of a transaction *is* its kind key — no mapping table. */
|
|
13
13
|
export declare function transactionScopeKey(kindKey: string, id: string): string;
|
|
14
14
|
export declare function productScopeKey(id: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* The shell path of a document, under the administration it belongs to.
|
|
17
|
+
*
|
|
18
|
+
* `fallbackEntityId` is for a row that predates administrations (`""` = the default one); without
|
|
19
|
+
* either there is no screen to point at, and no link beats a dead one.
|
|
20
|
+
*/
|
|
21
|
+
export declare function transactionHref(row: {
|
|
22
|
+
id: string;
|
|
23
|
+
kindKey: string;
|
|
24
|
+
businessEntityId?: string;
|
|
25
|
+
}, fallbackEntityId?: string): string | undefined;
|
|
26
|
+
export declare function productHref(row: {
|
|
27
|
+
id: string;
|
|
28
|
+
businessEntityId?: string;
|
|
29
|
+
}, fallbackEntityId?: string): string | undefined;
|
|
15
30
|
/** Is this scopeKey one of ours, and which id does it point at? */
|
|
16
31
|
export declare function transactionIdFromScope(scopeKey: string): string | undefined;
|
|
17
32
|
/**
|
|
@@ -13,8 +13,12 @@ import { TransactionStatus } from './types';
|
|
|
13
13
|
*/
|
|
14
14
|
export interface TransactionKind {
|
|
15
15
|
key: string;
|
|
16
|
-
/** i18n key, not text. */
|
|
16
|
+
/** i18n key, not text. For a screen that has `t()`. */
|
|
17
17
|
label: string;
|
|
18
|
+
/** Dutch, for what the server writes itself: an approval title, a related row, a card. */
|
|
19
|
+
name: string;
|
|
20
|
+
/** What the seeded template row is called, when the paper has another name than the kind. */
|
|
21
|
+
paperName?: string;
|
|
18
22
|
/** Gets a number from a series when issued. */
|
|
19
23
|
numbered: boolean;
|
|
20
24
|
/** Invert amounts: a credit note is an invoice with a minus sign. */
|
|
@@ -35,6 +39,19 @@ export interface TransactionKind {
|
|
|
35
39
|
* invoice or a credit note.
|
|
36
40
|
*/
|
|
37
41
|
issuedStatus: TransactionStatus;
|
|
42
|
+
/**
|
|
43
|
+
* The answers a person may give after issuing, per status. `draft -> issuedStatus` is not in
|
|
44
|
+
* here: that move is `issue`, with its gate and its number. Walking back one step is allowed —
|
|
45
|
+
* a mis-click on "accepted" must not need a new version.
|
|
46
|
+
*/
|
|
47
|
+
transitions: Partial<Record<TransactionStatus, readonly TransactionStatus[]>>;
|
|
48
|
+
/** What this kind may become through `convert`. A revision of the same kind is `version`. */
|
|
49
|
+
convertsTo: readonly string[];
|
|
50
|
+
/**
|
|
51
|
+
* The kind this one corrects. Made only from an issued document of that kind, which the paper
|
|
52
|
+
* names: a credit note without its invoice number is not a credit note.
|
|
53
|
+
*/
|
|
54
|
+
credits?: string;
|
|
38
55
|
}
|
|
39
56
|
export declare const TRANSACTION_KINDS: readonly TransactionKind[];
|
|
40
57
|
/**
|
|
@@ -45,9 +62,8 @@ export declare const TRANSACTION_KINDS: readonly TransactionKind[];
|
|
|
45
62
|
* A product decision, like the kinds themselves — §12.4: *"transactiesoorten zijn van ons, niet
|
|
46
63
|
* van de organisatie"*.
|
|
47
64
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* worse than one you cannot create at all.
|
|
65
|
+
* A credit note credits one issued invoice, whole or in part: the lines come over negated and a
|
|
66
|
+
* person lowers or removes what is not credited.
|
|
51
67
|
*/
|
|
52
68
|
export declare const ENABLED_TRANSACTION_KINDS: readonly string[];
|
|
53
69
|
/** The kinds a reader can browse, one per route segment. A credit note files with the invoices. */
|
|
@@ -60,3 +76,8 @@ export declare function listedTransactionKinds(): TransactionKind[];
|
|
|
60
76
|
* amounts on a guess is worse than showing a document with a strange label.
|
|
61
77
|
*/
|
|
62
78
|
export declare function transactionKind(key: string): TransactionKind;
|
|
79
|
+
/** Dutch status names, for the same server-side readers as {@link TransactionKind.name}. */
|
|
80
|
+
export declare const STATUS_NAMES: Record<TransactionStatus, string>;
|
|
81
|
+
/** Where a person may move this document from its current status. Empty on a draft's issue. */
|
|
82
|
+
export declare function nextStatuses(kindKey: string, from: TransactionStatus): readonly TransactionStatus[];
|
|
83
|
+
export declare function canTransition(kindKey: string, from: TransactionStatus, to: TransactionStatus): boolean;
|
|
@@ -44,14 +44,6 @@ export interface PaymentTerm {
|
|
|
44
44
|
* would make the answer depend on which timezone the server happened to be in.
|
|
45
45
|
*/
|
|
46
46
|
export declare function dueDateFrom(issuedAt: number, dueDays: number): number;
|
|
47
|
-
/**
|
|
48
|
-
* The term that applies, out of the shared table and one administration's own.
|
|
49
|
-
*
|
|
50
|
-
* Shared rows first, then the administration's over them, by code — the same `shadowByKey` the VAT
|
|
51
|
-
* table uses. Kept in domain rather than in the app because both the server and the screen have to
|
|
52
|
-
* land on the same answer.
|
|
53
|
-
*/
|
|
54
|
-
export declare function shadowTermsByCode(rows: readonly PaymentTerm[], businessEntityId?: string): PaymentTerm[];
|
|
55
47
|
/**
|
|
56
48
|
* The condition as it was copied onto a document.
|
|
57
49
|
*
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { DocumentBlock } from '../../platform/document-blocks';
|
|
2
|
+
import { DocumentSender } from '../document-template/types';
|
|
1
3
|
import { Cents } from '../money/money';
|
|
4
|
+
import { TaxRateKind } from '../money/tax';
|
|
2
5
|
import { OwnerScope } from '../scope/types';
|
|
3
6
|
import { TransactionPaymentTerm } from './payment-term';
|
|
4
7
|
/**
|
|
@@ -95,6 +98,12 @@ export interface Transaction {
|
|
|
95
98
|
expiresAt?: number;
|
|
96
99
|
/** Only on a numbered kind, and only at the moment of issuing. */
|
|
97
100
|
number?: string;
|
|
101
|
+
/**
|
|
102
|
+
* The paper as it went out: blocks and letterhead, filled at issue. A frozen document shows
|
|
103
|
+
* this and never re-renders — the customer's address, the sender's IBAN, a VAT rate and the
|
|
104
|
+
* template may all change afterwards, and an invoice must keep saying what it said.
|
|
105
|
+
*/
|
|
106
|
+
issuedPaper?: IssuedPaper;
|
|
98
107
|
/** Posted — to whichever set of books. Set by the push, never by a person. */
|
|
99
108
|
postedAt?: number;
|
|
100
109
|
/** The rendered PDF in `apps/storage`. A reference, never the content. */
|
|
@@ -126,7 +135,15 @@ export interface Transaction {
|
|
|
126
135
|
* A union and not a free string: kinds and their statuses are **ours**, not the organisation's.
|
|
127
136
|
* Labels follow from the key as `status_<key>`.
|
|
128
137
|
*/
|
|
129
|
-
export type TransactionStatus = "draft" | "sent" | "accepted" | "rejected" | "expired" | "confirmed" | "delivered" | "open" | "paid" | "overdue" | "settled" | "cancelled"
|
|
138
|
+
export type TransactionStatus = "draft" | "sent" | "accepted" | "rejected" | "expired" | "confirmed" | "delivered" | "open" | "paid" | "overdue" | "settled" | "cancelled"
|
|
139
|
+
/** A newer version of this document went out. Set by the server, never by a person. */
|
|
140
|
+
| "superseded";
|
|
141
|
+
export interface IssuedPaper {
|
|
142
|
+
blocks: DocumentBlock[];
|
|
143
|
+
sender: DocumentSender;
|
|
144
|
+
templateId?: string;
|
|
145
|
+
customerName?: string;
|
|
146
|
+
}
|
|
130
147
|
export interface TransactionLine {
|
|
131
148
|
/** Stable within the transaction, so a version diff can follow a line. */
|
|
132
149
|
id: string;
|
|
@@ -178,6 +195,9 @@ export interface TransactionTotals {
|
|
|
178
195
|
/** Per rate, because that is what the invoice and the bookkeeping both need. */
|
|
179
196
|
byRate: {
|
|
180
197
|
taxRateKey: string;
|
|
198
|
+
/** As it was when priced. Absent on rows priced before it was stored. */
|
|
199
|
+
percent?: number;
|
|
200
|
+
kind?: TaxRateKind;
|
|
181
201
|
netCents: Cents;
|
|
182
202
|
taxCents: Cents;
|
|
183
203
|
}[];
|
|
@@ -104,3 +104,6 @@ export declare function validateStatuses(statuses: readonly WorkStatus[]): Statu
|
|
|
104
104
|
* move nobody can unblock.
|
|
105
105
|
*/
|
|
106
106
|
export declare function statusRequiresApproval(statusKey: string, ladder: readonly WorkStatus[]): boolean;
|
|
107
|
+
/** How an item ended, read off its project's resolution flag. `undefined` = open or neither. */
|
|
108
|
+
export type WorkOutcome = "won" | "lost";
|
|
109
|
+
export declare function outcomeOf(resolution: string | undefined, project?: WorkProject | null): WorkOutcome | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A repeating item: closing it makes the next one, the Asana "on completion" model. No schedule
|
|
3
|
+
* and no cron — the next occurrence exists the moment this one is done, so nothing can be missed
|
|
4
|
+
* by a job that did not run.
|
|
5
|
+
*/
|
|
6
|
+
export interface WorkRecurrence {
|
|
7
|
+
every: "day" | "week" | "month";
|
|
8
|
+
/** Every `interval` units; 1 = every week, 2 = every other week. */
|
|
9
|
+
interval: number;
|
|
10
|
+
}
|
|
11
|
+
/** A recurrence as a body sent it, or `undefined` when it is not one. */
|
|
12
|
+
export declare function parseRecurrence(raw: unknown): WorkRecurrence | undefined;
|
|
13
|
+
/** `ms` moved on by one recurrence step, in local calendar terms (a month is a month). */
|
|
14
|
+
export declare function stepDate(ms: number, recurrence: WorkRecurrence): number;
|
|
15
|
+
/**
|
|
16
|
+
* The next occurrence's dates. From the old due date when it had one (a weekly report stays on
|
|
17
|
+
* Friday even when finished on Monday), else from when it was closed. The start keeps its
|
|
18
|
+
* distance to the due date.
|
|
19
|
+
*/
|
|
20
|
+
export declare function nextOccurrenceDates(item: {
|
|
21
|
+
dueDate?: number;
|
|
22
|
+
startDate?: number;
|
|
23
|
+
}, recurrence: WorkRecurrence, closedAt: number): {
|
|
24
|
+
dueDate: number;
|
|
25
|
+
startDate?: number;
|
|
26
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WorkRecurrence } from './recurrence';
|
|
1
2
|
import { Relation } from '../../platform/resource-source';
|
|
2
3
|
import { OwnerScope } from '../scope/types';
|
|
3
4
|
/**
|
|
@@ -61,15 +62,16 @@ export interface WorkStatus {
|
|
|
61
62
|
/**
|
|
62
63
|
* A resolution: *how* something ended, apart from where it sits.
|
|
63
64
|
*
|
|
64
|
-
*
|
|
65
|
-
* "
|
|
66
|
-
*
|
|
67
|
-
* `positive?: boolean` belongs here.
|
|
65
|
+
* A free label: a law firm distinguishes "settled" from "judgment", a sales team "won" from
|
|
66
|
+
* "lost", and neither fits an enum we invent. `positive` is the one meaning a query may read —
|
|
67
|
+
* whether it ended well — so a win rate never guesses which label means good.
|
|
68
68
|
*/
|
|
69
69
|
export interface WorkResolution {
|
|
70
70
|
key: string;
|
|
71
71
|
label: string;
|
|
72
72
|
order: number;
|
|
73
|
+
/** `true` ended well, `false` ended badly, absent says neither (postponed, transferred). */
|
|
74
|
+
positive?: boolean;
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
75
77
|
* An item kind: an icon with a label, never a workflow.
|
|
@@ -227,18 +229,32 @@ export interface WorkCycle {
|
|
|
227
229
|
committedSeconds?: number;
|
|
228
230
|
/** Written once, when the cycle is completed. See {@link WorkCycleSummary}. */
|
|
229
231
|
summary?: WorkCycleSummary;
|
|
232
|
+
/** One point per day while it runs, for the burndown. See {@link WorkCycleDay}. */
|
|
233
|
+
series?: WorkCycleDay[];
|
|
230
234
|
/** Ids of this cycle in source systems. Empty on one created here. */
|
|
231
235
|
externalIds?: string[];
|
|
232
236
|
createdBy: string;
|
|
233
237
|
createdAt?: number;
|
|
234
238
|
updatedAt?: number;
|
|
235
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Where a running cycle stood on one day. The hourly job overwrites today's point, so the last
|
|
242
|
+
* one of a day is what that day ended on; a day the job missed is simply absent (a gap, never an
|
|
243
|
+
* invented value). Embedded on the cycle: a sprint has a few dozen days, never thousands.
|
|
244
|
+
*/
|
|
245
|
+
export interface WorkCycleDay {
|
|
246
|
+
/** Start of the day, epoch ms (UTC). */
|
|
247
|
+
day: number;
|
|
248
|
+
open: number;
|
|
249
|
+
openSeconds: number;
|
|
250
|
+
done: number;
|
|
251
|
+
}
|
|
236
252
|
/**
|
|
237
253
|
* What a cycle delivered, frozen at completion.
|
|
238
254
|
*
|
|
239
|
-
* One row written once
|
|
240
|
-
*
|
|
241
|
-
*
|
|
255
|
+
* One row written once: "what did this team get done in the last eight cycles" needs eight rows
|
|
256
|
+
* and no rollup. The day-by-day view is {@link WorkCycle.series}. Compare against
|
|
257
|
+
* {@link WorkCycle.committedCount}, which was counted at the start.
|
|
242
258
|
*/
|
|
243
259
|
export interface WorkCycleSummary {
|
|
244
260
|
completed: number;
|
|
@@ -389,6 +405,10 @@ export interface WorkItem {
|
|
|
389
405
|
dueDate?: number;
|
|
390
406
|
/** Set as soon as the status moves to category `done`, cleared when it comes back. */
|
|
391
407
|
closedAt?: number;
|
|
408
|
+
/** Closing makes the next one; see `WorkRecurrence`. */
|
|
409
|
+
recurrence?: WorkRecurrence;
|
|
410
|
+
/** The occurrence this one's closing made: set once, so a reopen-and-close makes no second. */
|
|
411
|
+
nextOccurrenceId?: string;
|
|
392
412
|
estimateSeconds?: number;
|
|
393
413
|
/**
|
|
394
414
|
* Keys of the still-open items this one is `blocked_by`. **A read-time projection**, never
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SavedView, SavedViewSort } from '../saved-view/types';
|
|
2
|
+
/**
|
|
3
|
+
* Work's saved view: a {@link SavedView} over work items, optionally pinned to one project.
|
|
4
|
+
* The filter keys are declared once, here; the filter bar, the server's cleaning and the
|
|
5
|
+
* stored shape all derive from these two lists, so a new filter is one edit, not four.
|
|
6
|
+
*/
|
|
7
|
+
/** Multi-value filters: empty or absent = no filter on that axis. */
|
|
8
|
+
export declare const WORK_VIEW_LIST_FILTERS: readonly ["statusKeys", "typeKeys", "labels", "priority", "assignees"];
|
|
9
|
+
export type WorkViewListFilter = (typeof WORK_VIEW_LIST_FILTERS)[number];
|
|
10
|
+
/**
|
|
11
|
+
* On/off filters. `watchingOnly` and `reportedByMe` are about the viewer, so the server resolves
|
|
12
|
+
* them; the bar only shows `blockedOnly`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const WORK_VIEW_FLAGS: readonly ["blockedOnly", "watchingOnly", "reportedByMe"];
|
|
15
|
+
export type WorkViewFlag = (typeof WORK_VIEW_FLAGS)[number];
|
|
16
|
+
export type WorkViewFilters = {
|
|
17
|
+
search?: string;
|
|
18
|
+
} & Partial<Record<WorkViewListFilter, string[]>> & Partial<Record<WorkViewFlag, boolean>>;
|
|
19
|
+
export declare const WORK_SORT_FIELDS: readonly ["priority", "dueDate", "updatedAt", "createdAt", "title", "estimateSeconds"];
|
|
20
|
+
export type WorkSortField = (typeof WORK_SORT_FIELDS)[number];
|
|
21
|
+
export type WorkViewSort = SavedViewSort<WorkSortField>;
|
|
22
|
+
export declare const WORK_VIEW_LAYOUTS: readonly ["list", "board", "calendar", "workload"];
|
|
23
|
+
export type WorkViewLayout = (typeof WORK_VIEW_LAYOUTS)[number];
|
|
24
|
+
export type WorkView = SavedView<WorkViewFilters, WorkViewLayout, WorkSortField> & {
|
|
25
|
+
/** Pinned to one project; absent = across everything the viewer can see. */
|
|
26
|
+
projectId?: string;
|
|
27
|
+
};
|