@purveyors/cli 0.2.1 → 0.3.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/commands/catalog.d.ts +4 -56
- package/dist/commands/catalog.d.ts.map +1 -1
- package/dist/commands/catalog.js +14 -81
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/inventory.d.ts +2 -25
- package/dist/commands/inventory.d.ts.map +1 -1
- package/dist/commands/inventory.js +47 -147
- package/dist/commands/inventory.js.map +1 -1
- package/dist/commands/roast.d.ts +2 -41
- package/dist/commands/roast.d.ts.map +1 -1
- package/dist/commands/roast.js +138 -125
- package/dist/commands/roast.js.map +1 -1
- package/dist/commands/sales.d.ts +2 -10
- package/dist/commands/sales.d.ts.map +1 -1
- package/dist/commands/sales.js +30 -109
- package/dist/commands/sales.js.map +1 -1
- package/dist/commands/tasting.d.ts +4 -38
- package/dist/commands/tasting.d.ts.map +1 -1
- package/dist/commands/tasting.js +18 -108
- package/dist/commands/tasting.js.map +1 -1
- package/dist/lib/artisan/db.d.ts +37 -0
- package/dist/lib/artisan/db.d.ts.map +1 -0
- package/dist/lib/artisan/db.js +51 -0
- package/dist/lib/artisan/db.js.map +1 -0
- package/dist/lib/artisan/import.d.ts +16 -0
- package/dist/lib/artisan/import.d.ts.map +1 -0
- package/dist/lib/artisan/import.js +447 -0
- package/dist/lib/artisan/import.js.map +1 -0
- package/dist/lib/artisan/index.d.ts +9 -0
- package/dist/lib/artisan/index.d.ts.map +1 -0
- package/dist/lib/artisan/index.js +7 -0
- package/dist/lib/artisan/index.js.map +1 -0
- package/dist/lib/artisan/parser.d.ts +19 -0
- package/dist/lib/artisan/parser.d.ts.map +1 -0
- package/dist/lib/artisan/parser.js +376 -0
- package/dist/lib/artisan/parser.js.map +1 -0
- package/dist/lib/artisan/temperature.d.ts +52 -0
- package/dist/lib/artisan/temperature.d.ts.map +1 -0
- package/dist/lib/artisan/temperature.js +101 -0
- package/dist/lib/artisan/temperature.js.map +1 -0
- package/dist/lib/artisan/types.d.ts +195 -0
- package/dist/lib/artisan/types.d.ts.map +1 -0
- package/dist/lib/artisan/types.js +35 -0
- package/dist/lib/artisan/types.js.map +1 -0
- package/dist/lib/artisan/validator.d.ts +14 -0
- package/dist/lib/artisan/validator.d.ts.map +1 -0
- package/dist/lib/artisan/validator.js +228 -0
- package/dist/lib/artisan/validator.js.map +1 -0
- package/dist/lib/catalog.d.ts +87 -0
- package/dist/lib/catalog.d.ts.map +1 -0
- package/dist/lib/catalog.js +111 -0
- package/dist/lib/catalog.js.map +1 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +11 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/inventory.d.ts +80 -0
- package/dist/lib/inventory.d.ts.map +1 -0
- package/dist/lib/inventory.js +205 -0
- package/dist/lib/inventory.js.map +1 -0
- package/dist/lib/roast.d.ts +127 -0
- package/dist/lib/roast.d.ts.map +1 -0
- package/dist/lib/roast.js +284 -0
- package/dist/lib/roast.js.map +1 -0
- package/dist/lib/sales.d.ts +53 -0
- package/dist/lib/sales.d.ts.map +1 -0
- package/dist/lib/sales.js +155 -0
- package/dist/lib/sales.js.map +1 -0
- package/dist/lib/tasting.d.ts +76 -0
- package/dist/lib/tasting.d.ts.map +1 -0
- package/dist/lib/tasting.js +136 -0
- package/dist/lib/tasting.js.map +1 -0
- package/package.json +13 -2
package/dist/commands/roast.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { access, readFile } from 'fs/promises';
|
|
3
|
+
import { basename } from 'path';
|
|
2
4
|
import { createAuthenticatedClient } from '../lib/supabase.js';
|
|
3
5
|
import { outputData, info, success } from '../lib/output.js';
|
|
4
6
|
import { withErrorHandling, AuthError, PrvrsError } from '../lib/errors.js';
|
|
5
7
|
import { confirm, todayIso } from '../lib/prompts.js';
|
|
8
|
+
import { listRoasts, getRoast, createRoast, deleteRoast, importRoastFromFile, } from '../lib/roast.js';
|
|
6
9
|
// ─── Command builder ──────────────────────────────────────────────────────────
|
|
7
10
|
/**
|
|
8
11
|
* `purvey roast` — Browse and manage your roast profiles.
|
|
@@ -23,18 +26,11 @@ export function buildRoastCommand() {
|
|
|
23
26
|
if (!user) {
|
|
24
27
|
throw new AuthError('Not logged in. Run `purvey auth login` first.');
|
|
25
28
|
}
|
|
26
|
-
|
|
27
|
-
.
|
|
28
|
-
.
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
query = query.eq('coffee_id', parseInt(opts.coffeeId, 10));
|
|
32
|
-
}
|
|
33
|
-
const limit = Math.max(1, parseInt(opts.limit, 10));
|
|
34
|
-
const { data, error } = await query.order('roast_date', { ascending: false }).limit(limit);
|
|
35
|
-
if (error)
|
|
36
|
-
throw error;
|
|
37
|
-
if (!data || data.length === 0) {
|
|
29
|
+
const data = await listRoasts(supabase, user.id, {
|
|
30
|
+
coffeeId: opts.coffeeId !== undefined ? parseInt(opts.coffeeId, 10) : undefined,
|
|
31
|
+
limit: Math.max(1, parseInt(opts.limit, 10)),
|
|
32
|
+
});
|
|
33
|
+
if (data.length === 0) {
|
|
38
34
|
info('No roast profiles found.');
|
|
39
35
|
return;
|
|
40
36
|
}
|
|
@@ -53,44 +49,11 @@ export function buildRoastCommand() {
|
|
|
53
49
|
if (!user) {
|
|
54
50
|
throw new AuthError('Not logged in. Run `purvey auth login` first.');
|
|
55
51
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.eq('roast_id', roastId)
|
|
62
|
-
.eq('user', user.id)
|
|
63
|
-
.single();
|
|
64
|
-
if (profileError) {
|
|
65
|
-
if (profileError.code === 'PGRST116') {
|
|
66
|
-
throw new AuthError(`Roast profile ${id} not found or does not belong to you.`);
|
|
67
|
-
}
|
|
68
|
-
throw profileError;
|
|
69
|
-
}
|
|
70
|
-
const result = { ...profile };
|
|
71
|
-
// Optionally fetch temperature curve
|
|
72
|
-
if (opts.includeTemps) {
|
|
73
|
-
const { data: temps, error: tempError } = await supabase
|
|
74
|
-
.from('roast_temperatures')
|
|
75
|
-
.select('roast_id, time_seconds, bean_temp, environmental_temp')
|
|
76
|
-
.eq('roast_id', roastId)
|
|
77
|
-
.order('time_seconds', { ascending: true });
|
|
78
|
-
if (tempError)
|
|
79
|
-
throw tempError;
|
|
80
|
-
result.temperatures = temps ?? [];
|
|
81
|
-
}
|
|
82
|
-
// Optionally fetch roast events
|
|
83
|
-
if (opts.includeEvents) {
|
|
84
|
-
const { data: events, error: eventsError } = await supabase
|
|
85
|
-
.from('roast_events')
|
|
86
|
-
.select('roast_id, time_seconds, event_type, event_value')
|
|
87
|
-
.eq('roast_id', roastId)
|
|
88
|
-
.order('time_seconds', { ascending: true });
|
|
89
|
-
if (eventsError)
|
|
90
|
-
throw eventsError;
|
|
91
|
-
result.events = events ?? [];
|
|
92
|
-
}
|
|
93
|
-
outputData(result, globalOpts);
|
|
52
|
+
const data = await getRoast(supabase, user.id, parseInt(id, 10), {
|
|
53
|
+
includeTemps: Boolean(opts.includeTemps),
|
|
54
|
+
includeEvents: Boolean(opts.includeEvents),
|
|
55
|
+
});
|
|
56
|
+
outputData(data, globalOpts);
|
|
94
57
|
}));
|
|
95
58
|
// ── roast create ──────────────────────────────────────────────────────────
|
|
96
59
|
roast
|
|
@@ -113,62 +76,27 @@ export function buildRoastCommand() {
|
|
|
113
76
|
if (isNaN(coffeeId)) {
|
|
114
77
|
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --coffee-id: "${opts.coffeeId}".`);
|
|
115
78
|
}
|
|
116
|
-
|
|
117
|
-
const { data: invItem, error: invError } = await supabase
|
|
118
|
-
.from('green_coffee_inv')
|
|
119
|
-
.select('id, coffee_catalog!catalog_id (name)')
|
|
120
|
-
.eq('id', coffeeId)
|
|
121
|
-
.eq('user', user.id)
|
|
122
|
-
.single();
|
|
123
|
-
if (invError || !invItem) {
|
|
124
|
-
throw new AuthError(`Inventory item ${coffeeId} not found or does not belong to you.`);
|
|
125
|
-
}
|
|
126
|
-
const roastDate = opts.roastDate ?? todayIso();
|
|
127
|
-
// Default batch name: coffee name + roast date
|
|
128
|
-
let batchName = opts.batchName;
|
|
129
|
-
if (!batchName) {
|
|
130
|
-
const catalogRaw = invItem.coffee_catalog;
|
|
131
|
-
const catalog = Array.isArray(catalogRaw) ? (catalogRaw[0] ?? null) : catalogRaw;
|
|
132
|
-
const coffeeName = catalog?.name ?? `Coffee #${coffeeId}`;
|
|
133
|
-
batchName = `${coffeeName} — ${roastDate}`;
|
|
134
|
-
}
|
|
135
|
-
const insertPayload = {
|
|
136
|
-
user: user.id,
|
|
137
|
-
coffee_id: coffeeId,
|
|
138
|
-
batch_name: batchName,
|
|
139
|
-
roast_date: roastDate,
|
|
140
|
-
};
|
|
79
|
+
let ozIn;
|
|
141
80
|
if (opts.ozIn !== undefined) {
|
|
142
|
-
|
|
81
|
+
ozIn = parseFloat(opts.ozIn);
|
|
143
82
|
if (isNaN(ozIn) || ozIn <= 0)
|
|
144
83
|
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --oz-in: "${opts.ozIn}".`);
|
|
145
|
-
insertPayload.oz_in = ozIn;
|
|
146
84
|
}
|
|
85
|
+
let ozOut;
|
|
147
86
|
if (opts.ozOut !== undefined) {
|
|
148
|
-
|
|
87
|
+
ozOut = parseFloat(opts.ozOut);
|
|
149
88
|
if (isNaN(ozOut) || ozOut <= 0)
|
|
150
89
|
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --oz-out: "${opts.ozOut}".`);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.
|
|
158
|
-
.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (insertError)
|
|
162
|
-
throw insertError;
|
|
163
|
-
// Re-fetch the full row
|
|
164
|
-
const { data, error } = await supabase
|
|
165
|
-
.from('roast_profiles')
|
|
166
|
-
.select('roast_id, batch_name, coffee_id, coffee_name, roast_date, oz_in, oz_out, weight_loss_percent, roast_notes, roaster_type, total_roast_time, data_source, last_updated')
|
|
167
|
-
.eq('roast_id', inserted.roast_id)
|
|
168
|
-
.single();
|
|
169
|
-
if (error)
|
|
170
|
-
throw error;
|
|
171
|
-
success(`Roast profile ${inserted.roast_id} created.`);
|
|
90
|
+
}
|
|
91
|
+
const data = await createRoast(supabase, user.id, {
|
|
92
|
+
coffeeId,
|
|
93
|
+
batchName: opts.batchName,
|
|
94
|
+
ozIn,
|
|
95
|
+
ozOut,
|
|
96
|
+
roastDate: opts.roastDate ?? todayIso(),
|
|
97
|
+
notes: opts.notes,
|
|
98
|
+
});
|
|
99
|
+
success(`Roast profile ${data.roast_id} created.`);
|
|
172
100
|
outputData(data, globalOpts);
|
|
173
101
|
}));
|
|
174
102
|
// ── roast delete <id> ─────────────────────────────────────────────────────
|
|
@@ -177,7 +105,7 @@ export function buildRoastCommand() {
|
|
|
177
105
|
.description('Delete a roast profile (must be yours)')
|
|
178
106
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
179
107
|
.action(withErrorHandling(async (id, opts, cmd) => {
|
|
180
|
-
void cmd;
|
|
108
|
+
void cmd;
|
|
181
109
|
const supabase = await createAuthenticatedClient();
|
|
182
110
|
const { data: { user }, } = await supabase.auth.getUser();
|
|
183
111
|
if (!user) {
|
|
@@ -187,39 +115,124 @@ export function buildRoastCommand() {
|
|
|
187
115
|
if (isNaN(roastId)) {
|
|
188
116
|
throw new PrvrsError('INVALID_ARGUMENT', `Invalid roast ID: "${id}".`);
|
|
189
117
|
}
|
|
190
|
-
// Verify ownership
|
|
191
|
-
const { data: existing, error: fetchError } = await supabase
|
|
192
|
-
.from('roast_profiles')
|
|
193
|
-
.select('roast_id, batch_name')
|
|
194
|
-
.eq('roast_id', roastId)
|
|
195
|
-
.eq('user', user.id)
|
|
196
|
-
.single();
|
|
197
|
-
if (fetchError || !existing) {
|
|
198
|
-
throw new AuthError(`Roast profile ${id} not found or does not belong to you.`);
|
|
199
|
-
}
|
|
200
118
|
if (!opts.yes) {
|
|
201
|
-
const
|
|
202
|
-
const ok = await confirm(`Delete roast profile ${label}?`);
|
|
119
|
+
const ok = await confirm(`Delete roast profile #${roastId}?`);
|
|
203
120
|
if (!ok) {
|
|
204
121
|
info('Aborted.');
|
|
205
122
|
return;
|
|
206
123
|
}
|
|
207
124
|
}
|
|
208
|
-
|
|
209
|
-
.from('roast_profiles')
|
|
210
|
-
.delete()
|
|
211
|
-
.eq('roast_id', roastId)
|
|
212
|
-
.eq('user', user.id);
|
|
213
|
-
if (deleteError)
|
|
214
|
-
throw deleteError;
|
|
125
|
+
await deleteRoast(supabase, user.id, roastId);
|
|
215
126
|
success(`Roast profile ${roastId} deleted.`);
|
|
216
127
|
}));
|
|
217
|
-
// ── roast import
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
128
|
+
// ── roast import <file> ───────────────────────────────────────────────────
|
|
129
|
+
roast
|
|
130
|
+
.command('import <file>')
|
|
131
|
+
.description('Import an Artisan .alog file and create a new roast profile')
|
|
132
|
+
.requiredOption('--coffee-id <id>', 'green_coffee_inv ID for this roast')
|
|
133
|
+
.option('--batch-name <name>', 'Batch name (auto-generated from coffee name + date if omitted)')
|
|
134
|
+
.option('--oz-in <oz>', 'Green weight in ounces (extracted from .alog if omitted)')
|
|
135
|
+
.option('--roast-notes <notes>', 'Additional roast notes')
|
|
136
|
+
.action(withErrorHandling(async (file, opts, cmd) => {
|
|
137
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
138
|
+
// 1. Validate file exists and is readable
|
|
139
|
+
try {
|
|
140
|
+
await access(file);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
throw new PrvrsError('INVALID_ARGUMENT', `File not found or not readable: "${file}"`);
|
|
144
|
+
}
|
|
145
|
+
// 2. Read file content
|
|
146
|
+
const fileContent = await readFile(file, 'utf-8');
|
|
147
|
+
const fileName = basename(file);
|
|
148
|
+
// 3. Authenticate
|
|
149
|
+
const supabase = await createAuthenticatedClient();
|
|
150
|
+
const { data: { user }, } = await supabase.auth.getUser();
|
|
151
|
+
if (!user) {
|
|
152
|
+
throw new AuthError('Not logged in. Run `purvey auth login` first.');
|
|
153
|
+
}
|
|
154
|
+
// 4. Parse --coffee-id
|
|
155
|
+
const coffeeId = parseInt(opts.coffeeId, 10);
|
|
156
|
+
if (isNaN(coffeeId) || coffeeId <= 0) {
|
|
157
|
+
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --coffee-id: "${opts.coffeeId}".`);
|
|
158
|
+
}
|
|
159
|
+
// 5. Parse --oz-in if provided
|
|
160
|
+
let ozIn;
|
|
161
|
+
if (opts.ozIn !== undefined) {
|
|
162
|
+
ozIn = parseFloat(opts.ozIn);
|
|
163
|
+
if (isNaN(ozIn) || ozIn <= 0) {
|
|
164
|
+
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --oz-in: "${opts.ozIn}".`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// 6. Run the import
|
|
168
|
+
const result = await importRoastFromFile(supabase, user.id, {
|
|
169
|
+
fileContent,
|
|
170
|
+
fileName,
|
|
171
|
+
coffeeId,
|
|
172
|
+
batchName: opts.batchName,
|
|
173
|
+
ozIn,
|
|
174
|
+
roastNotes: opts.roastNotes,
|
|
175
|
+
});
|
|
176
|
+
// 7. Output
|
|
177
|
+
if (globalOpts.pretty) {
|
|
178
|
+
printImportPretty(result, coffeeId);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
success(`Roast profile ${result.roast_id} imported from ${fileName}.`);
|
|
182
|
+
outputData(result, globalOpts);
|
|
183
|
+
}
|
|
184
|
+
}));
|
|
223
185
|
return roast;
|
|
224
186
|
}
|
|
187
|
+
// ─── Pretty-print helper ──────────────────────────────────────────────────────
|
|
188
|
+
function formatTime(seconds) {
|
|
189
|
+
const m = Math.floor(seconds / 60);
|
|
190
|
+
const s = Math.round(seconds % 60);
|
|
191
|
+
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
192
|
+
}
|
|
193
|
+
function printImportPretty(result, coffeeId) {
|
|
194
|
+
const { milestones, phases } = result;
|
|
195
|
+
console.log('');
|
|
196
|
+
console.log('✓ Roast imported successfully');
|
|
197
|
+
console.log('');
|
|
198
|
+
console.log(` Roast ID: ${result.roast_id}`);
|
|
199
|
+
console.log(` Bean: ${result.coffee_name} (#${coffeeId})`);
|
|
200
|
+
console.log(` Batch: ${result.batch_name}`);
|
|
201
|
+
const tempCount = result.message.match(/(\d+) data points/)?.[1] ?? '?';
|
|
202
|
+
console.log(` Data points: ${tempCount} temperatures, ${result.milestone_events} milestones, ${result.control_events} control events`);
|
|
203
|
+
// Milestones
|
|
204
|
+
if (Object.keys(milestones).length > 0) {
|
|
205
|
+
console.log('');
|
|
206
|
+
console.log(' Milestones:');
|
|
207
|
+
const labels = {
|
|
208
|
+
charge: 'Charge',
|
|
209
|
+
dry_end: 'Dry End',
|
|
210
|
+
fc_start: 'FC Start',
|
|
211
|
+
fc_end: 'FC End',
|
|
212
|
+
sc_start: 'SC Start',
|
|
213
|
+
drop: 'Drop',
|
|
214
|
+
cool: 'Cool',
|
|
215
|
+
};
|
|
216
|
+
for (const [key, label] of Object.entries(labels)) {
|
|
217
|
+
const t = milestones[key];
|
|
218
|
+
if (t !== undefined && t > 0) {
|
|
219
|
+
console.log(` ${label.padEnd(10)}${formatTime(t)}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Phases
|
|
224
|
+
const { drying_percent, maillard_percent, development_percent, total_time_seconds } = phases;
|
|
225
|
+
if (total_time_seconds > 0) {
|
|
226
|
+
console.log('');
|
|
227
|
+
console.log(' Phases:');
|
|
228
|
+
if (drying_percent > 0)
|
|
229
|
+
console.log(` Drying ${Math.round(drying_percent)}%`);
|
|
230
|
+
if (maillard_percent > 0)
|
|
231
|
+
console.log(` Maillard ${Math.round(maillard_percent)}%`);
|
|
232
|
+
if (development_percent > 0)
|
|
233
|
+
console.log(` Development ${Math.round(development_percent)}%`);
|
|
234
|
+
console.log(` Total ${formatTime(total_time_seconds)}`);
|
|
235
|
+
}
|
|
236
|
+
console.log('');
|
|
237
|
+
}
|
|
225
238
|
//# sourceMappingURL=roast.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roast.js","sourceRoot":"","sources":["../../src/commands/roast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"roast.js","sourceRoot":"","sources":["../../src/commands/roast.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/sales.d.ts
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
roast_id: number | null;
|
|
5
|
-
oz_sold: number | null;
|
|
6
|
-
sale_price: number | null;
|
|
7
|
-
buyer: string | null;
|
|
8
|
-
sell_date: string | null;
|
|
9
|
-
user: string;
|
|
10
|
-
last_updated: string;
|
|
11
|
-
}
|
|
2
|
+
import type { Sale } from '../lib/sales.js';
|
|
3
|
+
export type { Sale };
|
|
12
4
|
/**
|
|
13
5
|
* `purvey sales` — Record and manage coffee sales.
|
|
14
6
|
* Requires authentication.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sales.d.ts","sourceRoot":"","sources":["../../src/commands/sales.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"sales.d.ts","sourceRoot":"","sources":["../../src/commands/sales.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAI5C,YAAY,EAAE,IAAI,EAAE,CAAC;AAIrB;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CA+L3C"}
|
package/dist/commands/sales.js
CHANGED
|
@@ -3,8 +3,7 @@ import { createAuthenticatedClient } from '../lib/supabase.js';
|
|
|
3
3
|
import { outputData, info, success } from '../lib/output.js';
|
|
4
4
|
import { withErrorHandling, AuthError, PrvrsError } from '../lib/errors.js';
|
|
5
5
|
import { confirm, todayIso } from '../lib/prompts.js';
|
|
6
|
-
|
|
7
|
-
const SALE_SELECT = 'id, roast_id, oz_sold, sale_price, buyer, sell_date, user, last_updated';
|
|
6
|
+
import { listSales, recordSale, updateSale, deleteSale } from '../lib/sales.js';
|
|
8
7
|
// ─── Command builder ──────────────────────────────────────────────────────────
|
|
9
8
|
/**
|
|
10
9
|
* `purvey sales` — Record and manage coffee sales.
|
|
@@ -24,17 +23,10 @@ export function buildSalesCommand() {
|
|
|
24
23
|
if (!user) {
|
|
25
24
|
throw new AuthError('Not logged in. Run `purvey auth login` first.');
|
|
26
25
|
}
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.select(`${SALE_SELECT}, roast_profiles!roast_id (batch_name, coffee_name)`)
|
|
32
|
-
.eq('user', user.id)
|
|
33
|
-
.order('sell_date', { ascending: false })
|
|
34
|
-
.limit(limit);
|
|
35
|
-
if (error)
|
|
36
|
-
throw error;
|
|
37
|
-
if (!data || data.length === 0) {
|
|
26
|
+
const data = await listSales(supabase, user.id, {
|
|
27
|
+
limit: Math.max(1, parseInt(opts.limit, 10)),
|
|
28
|
+
});
|
|
29
|
+
if (data.length === 0) {
|
|
38
30
|
info('No sales found.');
|
|
39
31
|
return;
|
|
40
32
|
}
|
|
@@ -68,42 +60,14 @@ export function buildSalesCommand() {
|
|
|
68
60
|
if (isNaN(price) || price < 0) {
|
|
69
61
|
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --price: "${opts.price}". Must be a non-negative number.`);
|
|
70
62
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
throw new AuthError(`Roast profile ${roastId} not found or does not belong to you.`);
|
|
80
|
-
}
|
|
81
|
-
const insertPayload = {
|
|
82
|
-
user: user.id,
|
|
83
|
-
roast_id: roastId,
|
|
84
|
-
oz_sold: oz,
|
|
85
|
-
sale_price: price,
|
|
86
|
-
sell_date: opts.sellDate ?? todayIso(),
|
|
87
|
-
};
|
|
88
|
-
if (opts.buyer !== undefined) {
|
|
89
|
-
insertPayload.buyer = opts.buyer;
|
|
90
|
-
}
|
|
91
|
-
const { data: inserted, error: insertError } = await supabase
|
|
92
|
-
.from('sales')
|
|
93
|
-
.insert(insertPayload)
|
|
94
|
-
.select('id')
|
|
95
|
-
.single();
|
|
96
|
-
if (insertError)
|
|
97
|
-
throw insertError;
|
|
98
|
-
// Re-fetch the full row with roast join
|
|
99
|
-
const { data, error } = await supabase
|
|
100
|
-
.from('sales')
|
|
101
|
-
.select(`${SALE_SELECT}, roast_profiles!roast_id (batch_name, coffee_name)`)
|
|
102
|
-
.eq('id', inserted.id)
|
|
103
|
-
.single();
|
|
104
|
-
if (error)
|
|
105
|
-
throw error;
|
|
106
|
-
success(`Sale ${inserted.id} recorded.`);
|
|
63
|
+
const data = await recordSale(supabase, user.id, {
|
|
64
|
+
roastId,
|
|
65
|
+
oz,
|
|
66
|
+
price,
|
|
67
|
+
buyer: opts.buyer,
|
|
68
|
+
sellDate: opts.sellDate ?? todayIso(),
|
|
69
|
+
});
|
|
70
|
+
success(`Sale ${data.id} recorded.`);
|
|
107
71
|
outputData(data, globalOpts);
|
|
108
72
|
}));
|
|
109
73
|
// ── sales update <id> ─────────────────────────────────────────────────────
|
|
@@ -125,54 +89,30 @@ export function buildSalesCommand() {
|
|
|
125
89
|
if (isNaN(saleId)) {
|
|
126
90
|
throw new PrvrsError('INVALID_ARGUMENT', `Invalid sale ID: "${id}".`);
|
|
127
91
|
}
|
|
128
|
-
|
|
129
|
-
const { data: existing, error: fetchError } = await supabase
|
|
130
|
-
.from('sales')
|
|
131
|
-
.select('id')
|
|
132
|
-
.eq('id', saleId)
|
|
133
|
-
.eq('user', user.id)
|
|
134
|
-
.single();
|
|
135
|
-
if (fetchError || !existing) {
|
|
136
|
-
throw new AuthError(`Sale ${id} not found or does not belong to you.`);
|
|
137
|
-
}
|
|
138
|
-
// Build partial update
|
|
139
|
-
const updates = {};
|
|
92
|
+
let oz;
|
|
140
93
|
if (opts.oz !== undefined) {
|
|
141
|
-
|
|
94
|
+
oz = parseFloat(opts.oz);
|
|
142
95
|
if (isNaN(oz) || oz <= 0)
|
|
143
96
|
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --oz: "${opts.oz}".`);
|
|
144
|
-
updates.oz_sold = oz;
|
|
145
97
|
}
|
|
98
|
+
let price;
|
|
146
99
|
if (opts.price !== undefined) {
|
|
147
|
-
|
|
100
|
+
price = parseFloat(opts.price);
|
|
148
101
|
if (isNaN(price) || price < 0)
|
|
149
102
|
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --price: "${opts.price}".`);
|
|
150
|
-
updates.sale_price = price;
|
|
151
103
|
}
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
updates.sell_date = opts.sellDate;
|
|
157
|
-
}
|
|
158
|
-
if (Object.keys(updates).length === 0) {
|
|
104
|
+
if (oz === undefined &&
|
|
105
|
+
price === undefined &&
|
|
106
|
+
opts.buyer === undefined &&
|
|
107
|
+
opts.sellDate === undefined) {
|
|
159
108
|
throw new PrvrsError('INVALID_ARGUMENT', 'No update fields provided. Pass at least one of: --oz, --price, --buyer, --sell-date.');
|
|
160
109
|
}
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
.
|
|
165
|
-
.
|
|
166
|
-
|
|
167
|
-
throw updateError;
|
|
168
|
-
// Re-fetch the updated row
|
|
169
|
-
const { data, error } = await supabase
|
|
170
|
-
.from('sales')
|
|
171
|
-
.select(`${SALE_SELECT}, roast_profiles!roast_id (batch_name, coffee_name)`)
|
|
172
|
-
.eq('id', saleId)
|
|
173
|
-
.single();
|
|
174
|
-
if (error)
|
|
175
|
-
throw error;
|
|
110
|
+
const data = await updateSale(supabase, user.id, saleId, {
|
|
111
|
+
oz,
|
|
112
|
+
price,
|
|
113
|
+
buyer: opts.buyer,
|
|
114
|
+
sellDate: opts.sellDate,
|
|
115
|
+
});
|
|
176
116
|
success(`Sale ${saleId} updated.`);
|
|
177
117
|
outputData(data, globalOpts);
|
|
178
118
|
}));
|
|
@@ -182,7 +122,7 @@ export function buildSalesCommand() {
|
|
|
182
122
|
.description('Delete a sale (must be yours)')
|
|
183
123
|
.option('-y, --yes', 'Skip confirmation prompt')
|
|
184
124
|
.action(withErrorHandling(async (id, opts, cmd) => {
|
|
185
|
-
void cmd;
|
|
125
|
+
void cmd;
|
|
186
126
|
const supabase = await createAuthenticatedClient();
|
|
187
127
|
const { data: { user }, } = await supabase.auth.getUser();
|
|
188
128
|
if (!user) {
|
|
@@ -192,33 +132,14 @@ export function buildSalesCommand() {
|
|
|
192
132
|
if (isNaN(saleId)) {
|
|
193
133
|
throw new PrvrsError('INVALID_ARGUMENT', `Invalid sale ID: "${id}".`);
|
|
194
134
|
}
|
|
195
|
-
// Verify ownership
|
|
196
|
-
const { data: existing, error: fetchError } = await supabase
|
|
197
|
-
.from('sales')
|
|
198
|
-
.select('id, sell_date, oz_sold')
|
|
199
|
-
.eq('id', saleId)
|
|
200
|
-
.eq('user', user.id)
|
|
201
|
-
.single();
|
|
202
|
-
if (fetchError || !existing) {
|
|
203
|
-
throw new AuthError(`Sale ${id} not found or does not belong to you.`);
|
|
204
|
-
}
|
|
205
135
|
if (!opts.yes) {
|
|
206
|
-
const
|
|
207
|
-
? `from ${existing.sell_date} (${existing.oz_sold} oz)`
|
|
208
|
-
: `#${saleId}`;
|
|
209
|
-
const ok = await confirm(`Delete sale ${label}?`);
|
|
136
|
+
const ok = await confirm(`Delete sale #${saleId}?`);
|
|
210
137
|
if (!ok) {
|
|
211
138
|
info('Aborted.');
|
|
212
139
|
return;
|
|
213
140
|
}
|
|
214
141
|
}
|
|
215
|
-
|
|
216
|
-
.from('sales')
|
|
217
|
-
.delete()
|
|
218
|
-
.eq('id', saleId)
|
|
219
|
-
.eq('user', user.id);
|
|
220
|
-
if (deleteError)
|
|
221
|
-
throw deleteError;
|
|
142
|
+
await deleteSale(supabase, user.id, saleId);
|
|
222
143
|
success(`Sale ${saleId} deleted.`);
|
|
223
144
|
}));
|
|
224
145
|
return sales;
|