@profullstack/coinpay 0.17.0 → 0.19.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.
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Projects, time tracking, AR aging, invoice settings and client portal links.
3
+ *
4
+ * An API key is locked to its own business, so `businessId` is optional when
5
+ * the client was created with one.
6
+ */
7
+
8
+ const q = (params) => {
9
+ const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== null && v !== '');
10
+ return entries.length ? '?' + new URLSearchParams(entries.map(([k, v]) => [k, String(v)])).toString() : '';
11
+ };
12
+ const json = (method, body) => ({ method, body: JSON.stringify(body) });
13
+
14
+ /** Open invoices by days past due, per currency and per client. */
15
+ export async function getArAging(client, { businessId } = {}) {
16
+ return client.request('/invoices/aging' + q({ business_id: businessId }));
17
+ }
18
+
19
+ /** Reminder schedule, late fee and branding. */
20
+ export async function getInvoiceSettings(client, { businessId } = {}) {
21
+ const data = await client.request('/invoice-settings' + q({ business_id: businessId }));
22
+ return data.settings;
23
+ }
24
+
25
+ /**
26
+ * Update invoice settings. Fields use the API's names: reminders_enabled,
27
+ * reminder_days (e.g. [-3, 0, 7]), late_fee_type ('none'|'percent'|'fixed'),
28
+ * late_fee_value, late_fee_after_days, brand_color, logo_url, invoice_footer.
29
+ */
30
+ export async function updateInvoiceSettings(client, { businessId, ...fields }) {
31
+ const data = await client.request('/invoice-settings', json('PUT', { business_id: businessId, ...fields }));
32
+ return data.settings;
33
+ }
34
+
35
+ /** Create (or replace) a client's portal link; the old one stops working. */
36
+ export async function createClientPortalLink(client, clientId) {
37
+ const data = await client.request(`/clients/${encodeURIComponent(clientId)}/portal-link`, { method: 'POST' });
38
+ return data.url;
39
+ }
40
+
41
+ export async function revokeClientPortalLink(client, clientId) {
42
+ return client.request(`/clients/${encodeURIComponent(clientId)}/portal-link`, { method: 'DELETE' });
43
+ }
44
+
45
+ export async function listProjects(client, { businessId, includeArchived } = {}) {
46
+ const data = await client.request('/projects' + q({ business_id: businessId, include_archived: includeArchived ? 'true' : undefined }));
47
+ return data.projects ?? [];
48
+ }
49
+
50
+ /** A project with its hours, unbilled, billed and paid totals. */
51
+ export async function getProject(client, id) {
52
+ return client.request('/projects/' + encodeURIComponent(id));
53
+ }
54
+
55
+ export async function createProject(client, { businessId, name, clientId, hourlyRate, currency, budgetHours, notes }) {
56
+ const body = { name };
57
+ if (businessId) body.business_id = businessId;
58
+ if (clientId) body.client_id = clientId;
59
+ if (hourlyRate !== undefined) body.hourly_rate = hourlyRate;
60
+ if (currency) body.currency = currency;
61
+ if (budgetHours !== undefined) body.budget_hours = budgetHours;
62
+ if (notes) body.notes = notes;
63
+ const data = await client.request('/projects', json('POST', body));
64
+ return data.project;
65
+ }
66
+
67
+ /** Log finished time on a project. */
68
+ export async function logTime(client, { projectId, minutes, description, billable, hourlyRate, startedAt }) {
69
+ const body = { project_id: projectId, minutes };
70
+ if (description) body.description = description;
71
+ if (billable !== undefined) body.billable = billable;
72
+ if (hourlyRate !== undefined) body.hourly_rate = hourlyRate;
73
+ if (startedAt) body.started_at = startedAt;
74
+ const data = await client.request('/time-entries', json('POST', body));
75
+ return data.time_entry;
76
+ }
77
+
78
+ /** Start a running timer (one per person). */
79
+ export async function startTimer(client, { projectId, description }) {
80
+ const data = await client.request('/time-entries', json('POST', { project_id: projectId, start: true, ...(description ? { description } : {}) }));
81
+ return data.time_entry;
82
+ }
83
+
84
+ /** Stop a running timer; with no id, stops yours wherever it runs. */
85
+ export async function stopTimer(client, { entryId, businessId } = {}) {
86
+ let id = entryId;
87
+ if (!id) {
88
+ const data = await client.request('/time-entries' + q({ business_id: businessId, running: 'true' }));
89
+ id = data.time_entries?.[0]?.id;
90
+ if (!id) throw new Error('No timer is running');
91
+ }
92
+ const data = await client.request('/time-entries/' + encodeURIComponent(id), json('PATCH', { stop: true }));
93
+ return data.time_entry;
94
+ }
95
+
96
+ export async function listTimeEntries(client, { projectId, businessId, unbilled } = {}) {
97
+ const data = await client.request(
98
+ '/time-entries' + q({ project_id: projectId, business_id: businessId, unbilled: unbilled ? 'true' : undefined })
99
+ );
100
+ return data.time_entries ?? [];
101
+ }
102
+
103
+ /** Bill a project's unbilled billable time as a draft itemised invoice. */
104
+ export async function invoiceProjectTime(client, projectId, { entryIds } = {}) {
105
+ return client.request(`/projects/${encodeURIComponent(projectId)}/invoice`, json('POST', entryIds ? { entry_ids: entryIds } : {}));
106
+ }