@bergetai/odoo-cli 0.13.1 → 0.14.1

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/README.md CHANGED
@@ -107,6 +107,8 @@ odoo expenses create --name "Taxi Arlanda" --date 2026-10-14 --amount 249.50 \
107
107
  --category "Travel & Accommodation" --attach receipt.pdf
108
108
  odoo expenses attach 137 --file kvitto.pdf
109
109
  odoo expenses delete 137 --dry-run
110
+ odoo expenses report create --name "Utlägg sept 2026" --expense 137 --expense 138 --submit --dry-run
111
+ odoo expenses report create --name "Utlägg sept 2026" --expense 137 --expense 138 --submit
110
112
 
111
113
  # Marketing (mailing.*) — email is the key; trace deletion is always explicit
112
114
  odoo marketing lists list
package/dist/cli.js CHANGED
@@ -12,7 +12,7 @@ import { versionCommand } from "./commands/version.js";
12
12
  import { partnersCreateCommand, partnersDeleteCommand, partnersListCommand, partnersShowCommand, partnersTagsCommand, partnersUpdateCommand, resPartner, } from "./commands/partners.js";
13
13
  import { usersDeleteCommand, usersShowCommand, } from "./commands/users.js";
14
14
  import { attachmentsDeleteCommand, attachmentsDownloadCommand, attachmentsListCommand, attachmentsShowCommand, } from "./commands/attachments.js";
15
- import { expensesAttachCommand, expensesCategoriesCommand, expensesCreateCommand, expensesDeleteCommand, expensesListCommand, expensesShowCommand, } from "./commands/expenses.js";
15
+ import { expensesAttachCommand, expensesCategoriesCommand, expensesCreateCommand, expensesDeleteCommand, expensesListCommand, expensesReportCreateCommand, expensesShowCommand, } from "./commands/expenses.js";
16
16
  import { signTemplatesCreateFromDocumentCommand, signTemplatesListCommand, signTemplatesShowCommand, signItemsAddCommand, signRequestsCreateCommand, signRequestsListCommand, signRequestsCancelCommand, } from "./commands/sign.js";
17
17
  import { invoicesPrecedentCommand, linesAddCommand, linesUpdateCommand, ordersCreateCommand, ordersListCommand, ordersReadCommand, ordersUpdateCommand, pdfCommand, plansListCommand, plansPricingCreateCommand, plansPricingListCommand, plansPricingUpdateCommand, } from "./commands/sales.js";
18
18
  import { messagesDeleteCommand, messagesListCommand, messagesScrubCommand, messagesShowCommand, } from "./commands/messages.js";
@@ -886,6 +886,29 @@ Examples:
886
886
  .action(async (opts) => {
887
887
  await run((g) => expensesCreateCommand(opts, g), program);
888
888
  });
889
+ const expensesReport = expenses
890
+ .command("report")
891
+ .description("expense reports (hr.expense.sheet) — collect and submit");
892
+ expensesReport
893
+ .command("create")
894
+ .description("create an expense report from draft expenses; add --submit to send it for approval")
895
+ .requiredOption("--name <text>", "report name, e.g. 'Utlägg september 2026'")
896
+ .requiredOption("--expense <id>", "expense id to include (draft, not yet reported); repeatable", collect, [])
897
+ .option("--submit", "submit the report for approval immediately (state: Submitted)", false)
898
+ .option("--employee <name|id>", "report owner (default: employee linked to the authenticated user)")
899
+ .addHelpText("after", `
900
+ Creates an hr.expense.sheet and links the given expenses to it (same as
901
+ "Create Report" in the web UI). Approval (action_approve_sheet) is NOT
902
+ supported — that step belongs to the approver in the Odoo UI.
903
+
904
+ Examples:
905
+ $ odoo expenses report create --name "Utlägg sept 2026" \\
906
+ --expense 138 --expense 139 --dry-run
907
+ $ odoo expenses report create --name "Utlägg sept 2026" \\
908
+ --expense 138 --expense 139 --submit --json`)
909
+ .action(async (opts) => {
910
+ await run((g) => expensesReportCreateCommand(opts, g), program);
911
+ });
889
912
  expenses
890
913
  .command("attach")
891
914
  .description("attach a receipt file to an existing expense")
@@ -222,6 +222,75 @@ export async function expensesCreateCommand(opts, globals) {
222
222
  const line = `Created hr.expense ${id} (${opts.name})${attSuffix}`;
223
223
  process.stdout.write((color ? `\x1b[32m${line}\x1b[0m` : line) + "\n");
224
224
  }
225
+ export async function expensesReportCreateCommand(opts, globals) {
226
+ const client = await authenticatedClient(globals);
227
+ const ids = opts.expense.map(parseId);
228
+ if (ids.length === 0) {
229
+ throw usageError("pass at least one --expense <id>");
230
+ }
231
+ const rows = (await client.executeKw("hr.expense", "read", [ids], {
232
+ fields: ["id", "name", "state", "sheet_id", "employee_id", "total_amount", "currency_id"],
233
+ }));
234
+ if (rows.length !== ids.length) {
235
+ const found = new Set(rows.map((r) => r.id));
236
+ const missing = ids.filter((id) => !found.has(id));
237
+ throw notFoundError("record_not_found", `no hr.expense record(s) with id(s): ${missing.join(", ")}`, "expenses are only visible to their owner unless you are an Expense Manager");
238
+ }
239
+ const reported = rows.filter((r) => r.sheet_id !== false);
240
+ if (reported.length > 0) {
241
+ throw usageError(`expense(s) already on a report: ${reported.map((r) => `${r.id} (${r.name})`).join(", ")}`);
242
+ }
243
+ const employeeId = opts.employee !== undefined
244
+ ? (await resolveReference(client, "hr.employee", opts.employee, {
245
+ label: "employee",
246
+ })).id
247
+ : await currentEmployee(client);
248
+ const values = {
249
+ name: opts.name,
250
+ employee_id: employeeId,
251
+ expense_line_ids: [[6, 0, ids]],
252
+ };
253
+ if (globals.dryRun) {
254
+ const preview = { ...values, submit: opts.submit === true };
255
+ if (globals.json) {
256
+ printJson({ model: "hr.expense.sheet", action: "create", dry_run: true, values: preview });
257
+ return;
258
+ }
259
+ process.stdout.write(`Would create hr.expense.sheet:\n` +
260
+ Object.entries(preview)
261
+ .map(([k, v]) => ` ${k}: ${JSON.stringify(v)}`)
262
+ .join("\n") +
263
+ "\n");
264
+ return;
265
+ }
266
+ const id = (await client.executeKw("hr.expense.sheet", "create", [values]));
267
+ let state = "draft";
268
+ if (opts.submit === true) {
269
+ await client.executeKw("hr.expense.sheet", "action_submit_sheet", [[id]]);
270
+ state = "submit";
271
+ }
272
+ const [created] = (await client.executeKw("hr.expense.sheet", "read", [[id]], {
273
+ fields: ["total_amount", "currency_id"],
274
+ }));
275
+ if (globals.json) {
276
+ printJson({
277
+ model: "hr.expense.sheet",
278
+ action: "create",
279
+ dry_run: false,
280
+ id,
281
+ name: opts.name,
282
+ expense_ids: ids,
283
+ state,
284
+ total_amount: created.total_amount,
285
+ currency: created.currency_id?.[1],
286
+ });
287
+ return;
288
+ }
289
+ const color = colorEnabled(false) && process.stdout.isTTY === true;
290
+ const line = `Created hr.expense.sheet ${id} (${opts.name}) with ${ids.length} expense(s)` +
291
+ (opts.submit === true ? " — submitted" : "");
292
+ process.stdout.write((color ? `\x1b[32m${line}\x1b[0m` : line) + "\n");
293
+ }
225
294
  export async function expensesAttachCommand(idArg, opts, globals) {
226
295
  const id = parseId(idArg);
227
296
  const client = await authenticatedClient(globals);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bergetai/odoo-cli",
3
- "version": "0.13.1",
3
+ "version": "0.14.1",
4
4
  "description": "Agent-first CLI for the Odoo External API",
5
5
  "publishConfig": {
6
6
  "access": "public"