@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 +2 -0
- package/dist/cli.js +24 -1
- package/dist/commands/expenses.js +69 -0
- package/package.json +1 -1
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);
|