@bergetai/odoo-cli 0.4.2 → 0.5.2
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 +8 -0
- package/dist/cli.js +72 -1
- package/dist/commands/activities.js +95 -0
- package/dist/commands/partners.js +244 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,6 +24,13 @@ odoo auth status --json
|
|
|
24
24
|
# Unauthenticated connectivity check
|
|
25
25
|
odoo version
|
|
26
26
|
|
|
27
|
+
# Partners (companies and contacts — res.partner)
|
|
28
|
+
odoo partners list --company --json
|
|
29
|
+
odoo partners show 51
|
|
30
|
+
odoo partners create --name "Acme Corp" --is-company true --email info@acme.example
|
|
31
|
+
odoo partners update 51 --email new@acme.example --dry-run
|
|
32
|
+
odoo partners delete 54 --dry-run
|
|
33
|
+
|
|
27
34
|
# List and inspect records
|
|
28
35
|
odoo crm leads list --open --limit 20
|
|
29
36
|
odoo crm leads show 42
|
|
@@ -59,6 +66,7 @@ Note: `odoo --version` prints the CLI's own version; `odoo version` queries the
|
|
|
59
66
|
## Features
|
|
60
67
|
|
|
61
68
|
- Read-only CRM surface (`crm leads list/show`) — default columns, `--fields`/`--all-fields`, `--limit`/`--offset`/`--order`, `--domain`/`--filter` with `fields_get` type checking, reference resolution (`--stage`/`--team`/`--assignee`/`--tag`), table/JSON output with `search_count` envelope
|
|
69
|
+
- Partners surface (`partners list/show/create/update/delete`) — `res.partner` CRUD with `--company`/`--individual` filters, `--archived` (include inactive), first-class flags for `--name`, `--email`, `--phone`, `--website`, `--parent` (m2o), `--is-company`, `--street`, `--city`, `--zip`, `--country` (m2o), `--comment`, full `--dry-run` support, `--fields`/`--all-fields`, table/JSON output
|
|
62
70
|
- Sales teams surface (`crm teams list/show/create/update/delete`) — `--archived` (include inactive teams), `--team-leader` (team leader, login/email/name), `--active`/`--use-leads`/`--use-opportunities`/`--invoiced-target`/`--color`/`--sequence`, full `--dry-run` support, `--fields`/`--all-fields`, table/JSON output
|
|
63
71
|
- Read-only Helpdesk surface (`helpdesk tickets list/show`) — `--open`/`--closed` via stage `fold`, strict team-scoped stage resolution, module-not-installed error (exit 4)
|
|
64
72
|
- Read-only Project surface (`project list/show`) — `--user` (project manager) and `--tag` filters, archived projects excluded by default, module-not-installed error (exit 4)
|
package/dist/cli.js
CHANGED
|
@@ -5,10 +5,11 @@ import { leadsCreateCommand, leadsDeleteCommand, leadsListCommand, leadsShowComm
|
|
|
5
5
|
import { teamsCreateCommand, teamsDeleteCommand, teamsListCommand, teamsShowCommand, teamsUpdateCommand, crmTeams, } from "./commands/teams.js";
|
|
6
6
|
import { ticketsCreateCommand, ticketsDeleteCommand, ticketsListCommand, ticketsShowCommand, ticketsUpdateCommand, helpdeskTickets, } from "./commands/helpdesk.js";
|
|
7
7
|
import { addNoteCommand, listMessagesCommand, } from "./commands/notes.js";
|
|
8
|
-
import { activitiesListCommand, activityDoneCommand, scheduleCommand, } from "./commands/activities.js";
|
|
8
|
+
import { activitiesListCommand, activityDoneCommand, activityUpdateCommand, scheduleCommand, } from "./commands/activities.js";
|
|
9
9
|
import { createTaskCommand, projectListCommand, projectShowCommand, tasksListCommand, tasksShowCommand, } from "./commands/project.js";
|
|
10
10
|
import { documentsDocuments, documentsListCommand, foldersListCommand, documentsShowCommand, documentsCreateCommand, documentsUpdateCommand, documentsDeleteCommand, treeCommand, uploadCommand, } from "./commands/documents.js";
|
|
11
11
|
import { versionCommand } from "./commands/version.js";
|
|
12
|
+
import { partnersCreateCommand, partnersDeleteCommand, partnersListCommand, partnersShowCommand, partnersUpdateCommand, resPartner, } from "./commands/partners.js";
|
|
12
13
|
import { CliError } from "./errors.js";
|
|
13
14
|
import { applyGlobalOptions, enforceTransportSecurity, DEFAULT_TIMEOUT, } from "./global-options.js";
|
|
14
15
|
import { printError } from "./output.js";
|
|
@@ -226,6 +227,30 @@ Examples:
|
|
|
226
227
|
.action(async (ids) => {
|
|
227
228
|
await run((g) => activityDoneCommand(ids, g), program);
|
|
228
229
|
});
|
|
230
|
+
leads
|
|
231
|
+
.command("activity-update")
|
|
232
|
+
.description("update activities — deadline, summary, note, type, assignee (preview with --dry-run)")
|
|
233
|
+
.argument("<id...>", "activity ids")
|
|
234
|
+
.option("--deadline <yyyy-mm-dd>", "new deadline")
|
|
235
|
+
.option("--summary <text>", "new summary")
|
|
236
|
+
.option("--note <text>", "new note")
|
|
237
|
+
.option("--type <name|id>", "new activity type (e.g. To-Do, Call, Meeting)")
|
|
238
|
+
.option("--assignee <name|id>", "new assignee (login, email, or name)")
|
|
239
|
+
.addHelpText("after", `
|
|
240
|
+
Updates mail.activity records directly via write. At least one field is required.
|
|
241
|
+
|
|
242
|
+
Examples:
|
|
243
|
+
# Push an activity deadline to tomorrow
|
|
244
|
+
$ odoo crm records activity-update 736 --deadline 2026-08-28
|
|
245
|
+
|
|
246
|
+
# Update summary + deadline for two activities
|
|
247
|
+
$ odoo crm records activity-update 736 731 --deadline 2026-08-28 --summary "Follow-up"
|
|
248
|
+
|
|
249
|
+
# Dry run
|
|
250
|
+
$ odoo crm records activity-update 736 --deadline 2026-08-28 --dry-run`)
|
|
251
|
+
.action(async (ids, opts) => {
|
|
252
|
+
await run((g) => activityUpdateCommand(ids, opts, g), program);
|
|
253
|
+
});
|
|
229
254
|
const teams = crm.command("teams").description("sales teams (crm.team)");
|
|
230
255
|
teams
|
|
231
256
|
.command("list")
|
|
@@ -552,6 +577,52 @@ Examples:
|
|
|
552
577
|
.action(async (opts) => {
|
|
553
578
|
await run((g) => uploadCommand(opts, g), program);
|
|
554
579
|
});
|
|
580
|
+
const partners = program
|
|
581
|
+
.command("partners")
|
|
582
|
+
.description("Contacts and companies (res.partner)");
|
|
583
|
+
partners
|
|
584
|
+
.command("list")
|
|
585
|
+
.description("search and list partners with curated default columns")
|
|
586
|
+
.option("--archived", "include archived partners (active = false) instead of active only")
|
|
587
|
+
.option("--company", "only companies (is_company = true)")
|
|
588
|
+
.option("--individual", "only individuals (is_company = false)")
|
|
589
|
+
.option("--filter <f=v>", "equality filter; repeatable, AND-ed", collect, [])
|
|
590
|
+
.option("--domain <json>", "Odoo domain as a JSON array (mutually exclusive with --filter)")
|
|
591
|
+
.option("--fields <a,b,c>", "columns to display")
|
|
592
|
+
.option("--limit <n>", "max records (default: 50)", "50")
|
|
593
|
+
.option("--offset <n>", "skip the first n records", "0")
|
|
594
|
+
.option("--order <order>", 'sort order, e.g. "name asc"', undefined)
|
|
595
|
+
.action(async (opts) => {
|
|
596
|
+
await run((g) => partnersListCommand(opts, g), program);
|
|
597
|
+
});
|
|
598
|
+
partners
|
|
599
|
+
.command("show")
|
|
600
|
+
.description("read one partner")
|
|
601
|
+
.argument("<id>", "record id")
|
|
602
|
+
.option("--fields <a,b,c>", "fields to display")
|
|
603
|
+
.option("--all-fields", "display every readable field")
|
|
604
|
+
.action(async (id, opts) => {
|
|
605
|
+
await run((g) => partnersShowCommand(id, opts, g), program);
|
|
606
|
+
});
|
|
607
|
+
addMutationFlags(partners
|
|
608
|
+
.command("create")
|
|
609
|
+
.description("create a partner (company or individual; preview with --dry-run)"), resPartner).action(async (opts) => {
|
|
610
|
+
await run((g) => partnersCreateCommand(opts, g), program);
|
|
611
|
+
});
|
|
612
|
+
addMutationFlags(partners
|
|
613
|
+
.command("update")
|
|
614
|
+
.description("update partners (preview with --dry-run)"), resPartner)
|
|
615
|
+
.argument("<id...>", "record ids")
|
|
616
|
+
.action(async (ids, opts) => {
|
|
617
|
+
await run((g) => partnersUpdateCommand(ids, opts, g), program);
|
|
618
|
+
});
|
|
619
|
+
partners
|
|
620
|
+
.command("delete")
|
|
621
|
+
.description("delete partners (preview with --dry-run; unknown ids are skipped)")
|
|
622
|
+
.argument("<id...>", "record ids")
|
|
623
|
+
.action(async (ids) => {
|
|
624
|
+
await run((g) => partnersDeleteCommand(ids, g), program);
|
|
625
|
+
});
|
|
555
626
|
return program;
|
|
556
627
|
}
|
|
557
628
|
async function run(fn, program) {
|
|
@@ -180,6 +180,101 @@ export async function activityDoneCommand(activityIds, globals) {
|
|
|
180
180
|
console.log(`✓ Marked ${ids.length} activity(ies) as done: ${ids.join(", ")}`);
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Update one or more activities (mail.activity) — deadline, summary, note,
|
|
185
|
+
* type, or assignee. Uses `write` directly on mail.activity.
|
|
186
|
+
*/
|
|
187
|
+
export async function activityUpdateCommand(activityIds, opts, globals) {
|
|
188
|
+
const ids = activityIds.map((raw) => {
|
|
189
|
+
const n = Number.parseInt(raw, 10);
|
|
190
|
+
if (Number.isNaN(n) || n <= 0) {
|
|
191
|
+
throw usageError(`invalid activity id: ${raw}`, "provide a positive integer");
|
|
192
|
+
}
|
|
193
|
+
return n;
|
|
194
|
+
});
|
|
195
|
+
if (!opts.deadline && !opts.summary && opts.note === undefined && !opts.type && !opts.assignee) {
|
|
196
|
+
throw usageError("no fields to update", "provide at least one of --deadline, --summary, --note, --type, --assignee");
|
|
197
|
+
}
|
|
198
|
+
const client = await buildClient(globals);
|
|
199
|
+
// Verify the activities exist
|
|
200
|
+
const rows = (await client.executeKw("mail.activity", "search_read", [[["id", "in", ids]]], {
|
|
201
|
+
fields: ["id", "summary", "date_deadline", "state", "activity_type_id", "user_id"],
|
|
202
|
+
limit: ids.length,
|
|
203
|
+
}));
|
|
204
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
205
|
+
const missing = ids.filter((id) => !byId.has(id));
|
|
206
|
+
if (missing.length > 0) {
|
|
207
|
+
throw notFoundError("record_not_found", `no mail.activity record(s) with id(s): ${missing.join(", ")}`, "no activities were modified — aborts the whole batch when any id is missing");
|
|
208
|
+
}
|
|
209
|
+
const values = {};
|
|
210
|
+
if (opts.deadline) {
|
|
211
|
+
values.date_deadline = parseDate(opts.deadline, "--deadline");
|
|
212
|
+
}
|
|
213
|
+
if (opts.summary) {
|
|
214
|
+
values.summary = opts.summary;
|
|
215
|
+
}
|
|
216
|
+
if (opts.note !== undefined) {
|
|
217
|
+
values.note = opts.note;
|
|
218
|
+
}
|
|
219
|
+
if (opts.type) {
|
|
220
|
+
const activityType = await resolveReference(client, "mail.activity.type", opts.type, {
|
|
221
|
+
label: "activity type",
|
|
222
|
+
});
|
|
223
|
+
values.activity_type_id = activityType.id;
|
|
224
|
+
}
|
|
225
|
+
if (opts.assignee) {
|
|
226
|
+
const user = await resolveReference(client, "res.users", opts.assignee, {
|
|
227
|
+
label: "user",
|
|
228
|
+
searchFields: ["login", "email", "name"],
|
|
229
|
+
});
|
|
230
|
+
values.user_id = user.id;
|
|
231
|
+
}
|
|
232
|
+
if (globals.verbose) {
|
|
233
|
+
logVerbose(`Updating ${ids.length} activity(ies): ${JSON.stringify(values)}`, globals.verbose);
|
|
234
|
+
}
|
|
235
|
+
if (globals.dryRun) {
|
|
236
|
+
if (globals.json) {
|
|
237
|
+
printJson({
|
|
238
|
+
model: "mail.activity",
|
|
239
|
+
action: "update",
|
|
240
|
+
dry_run: true,
|
|
241
|
+
ids,
|
|
242
|
+
values,
|
|
243
|
+
activities: rows.map((r) => ({
|
|
244
|
+
id: r.id,
|
|
245
|
+
summary: r.summary,
|
|
246
|
+
date_deadline: r.date_deadline,
|
|
247
|
+
state: r.state,
|
|
248
|
+
})),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
console.log(`[DRY RUN] Would update ${ids.length} activity(ies):`);
|
|
253
|
+
for (const r of rows) {
|
|
254
|
+
console.log(` #${r.id} — ${r.summary} (deadline: ${r.date_deadline ?? "none"}, state: ${r.state})`);
|
|
255
|
+
}
|
|
256
|
+
console.log(` New values: ${JSON.stringify(values, null, 2)}`);
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
await client.executeKw("mail.activity", "write", [ids, values]);
|
|
261
|
+
if (globals.json) {
|
|
262
|
+
printJson({
|
|
263
|
+
model: "mail.activity",
|
|
264
|
+
action: "update",
|
|
265
|
+
dry_run: false,
|
|
266
|
+
updated: ids,
|
|
267
|
+
values,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
console.log(`✓ Updated ${ids.length} activity(ies): ${ids.join(", ")}`);
|
|
272
|
+
if (opts.deadline)
|
|
273
|
+
console.log(` New deadline: ${values.date_deadline}`);
|
|
274
|
+
if (opts.summary)
|
|
275
|
+
console.log(` New summary: ${values.summary}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
183
278
|
/**
|
|
184
279
|
* List activities for a crm.lead record.
|
|
185
280
|
*/
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { ModelFields } from "../model.js";
|
|
2
|
+
import { authenticatedClient, showCommand, } from "../resource.js";
|
|
3
|
+
import { createCommand, deleteCommand, updateCommand, } from "../mutation.js";
|
|
4
|
+
import { buildFilterClauses, parseDomain } from "../filter.js";
|
|
5
|
+
import { usageError } from "../errors.js";
|
|
6
|
+
import { colorEnabled, printJson } from "../output.js";
|
|
7
|
+
import { formatTable, humanCell } from "../table.js";
|
|
8
|
+
function parseYesNoFlag(raw, flagName) {
|
|
9
|
+
const v = raw.toLowerCase();
|
|
10
|
+
if (v === "true" || v === "1" || v === "yes")
|
|
11
|
+
return true;
|
|
12
|
+
if (v === "false" || v === "0" || v === "no")
|
|
13
|
+
return false;
|
|
14
|
+
throw usageError(`invalid ${flagName} value ${JSON.stringify(raw)}`, "expected: true or false");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Mutation flags for res.partner. Keeps a focused first-class surface — the
|
|
18
|
+
* full partner model has hundreds of fields; `--set` and `--values` cover the
|
|
19
|
+
* long tail. `--parent` resolves the parent company (many2one), and
|
|
20
|
+
* `--is-company` is a boolean flag matching the Odoo `is_company` selection.
|
|
21
|
+
*/
|
|
22
|
+
const resPartnerMutation = {
|
|
23
|
+
flags: [
|
|
24
|
+
{
|
|
25
|
+
flags: "--name <name>",
|
|
26
|
+
option: "name",
|
|
27
|
+
field: "name",
|
|
28
|
+
help: "Partner name",
|
|
29
|
+
requiredOnCreate: true,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
flags: "--email <address>",
|
|
33
|
+
option: "email",
|
|
34
|
+
field: "email",
|
|
35
|
+
help: "Email",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
flags: "--phone <number>",
|
|
39
|
+
option: "phone",
|
|
40
|
+
field: "phone",
|
|
41
|
+
help: "Phone",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
flags: "--website <url>",
|
|
45
|
+
option: "website",
|
|
46
|
+
field: "website",
|
|
47
|
+
help: "Website",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
flags: "--parent <name|id>",
|
|
51
|
+
option: "parent",
|
|
52
|
+
field: "parent_id",
|
|
53
|
+
help: "Parent company (name or id)",
|
|
54
|
+
reference: {
|
|
55
|
+
model: "res.partner",
|
|
56
|
+
label: "partner",
|
|
57
|
+
searchFields: ["name", "email"],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
flags: "--is-company <true|false>",
|
|
62
|
+
option: "isCompany",
|
|
63
|
+
field: "is_company",
|
|
64
|
+
help: "Is a company (default: false on create)",
|
|
65
|
+
parse: parseYesNoFlag,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
flags: "--active <true|false>",
|
|
69
|
+
option: "active",
|
|
70
|
+
field: "active",
|
|
71
|
+
help: "Active flag (default: true on create)",
|
|
72
|
+
parse: parseYesNoFlag,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
flags: "--street <text>",
|
|
76
|
+
option: "street",
|
|
77
|
+
field: "street",
|
|
78
|
+
help: "Street",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
flags: "--city <text>",
|
|
82
|
+
option: "city",
|
|
83
|
+
field: "city",
|
|
84
|
+
help: "City",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
flags: "--zip <code>",
|
|
88
|
+
option: "zip",
|
|
89
|
+
field: "zip",
|
|
90
|
+
help: "Postal / ZIP code",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
flags: "--country <name|id>",
|
|
94
|
+
option: "country",
|
|
95
|
+
field: "country_id",
|
|
96
|
+
help: "Country (name or id)",
|
|
97
|
+
reference: {
|
|
98
|
+
model: "res.country",
|
|
99
|
+
label: "country",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
flags: "--comment <text>",
|
|
104
|
+
option: "comment",
|
|
105
|
+
field: "comment",
|
|
106
|
+
help: "Internal comment",
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
createDefaultValues: {
|
|
110
|
+
is_company: false,
|
|
111
|
+
active: true,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
export const resPartner = {
|
|
115
|
+
model: "res.partner",
|
|
116
|
+
showCommand: "odoo partners show",
|
|
117
|
+
defaultListFields: [
|
|
118
|
+
"id",
|
|
119
|
+
"name",
|
|
120
|
+
"is_company",
|
|
121
|
+
"email",
|
|
122
|
+
"phone",
|
|
123
|
+
"parent_id",
|
|
124
|
+
"active",
|
|
125
|
+
"create_date",
|
|
126
|
+
],
|
|
127
|
+
showExtraFields: [
|
|
128
|
+
"website",
|
|
129
|
+
"street",
|
|
130
|
+
"city",
|
|
131
|
+
"zip",
|
|
132
|
+
"country_id",
|
|
133
|
+
"comment",
|
|
134
|
+
"company_id",
|
|
135
|
+
"write_date",
|
|
136
|
+
],
|
|
137
|
+
resolutionHints: {
|
|
138
|
+
parent_id: "--parent",
|
|
139
|
+
country_id: "--country",
|
|
140
|
+
},
|
|
141
|
+
priorityLabels: {},
|
|
142
|
+
pinnedDomain: [],
|
|
143
|
+
references: {
|
|
144
|
+
assignee: {
|
|
145
|
+
model: "res.partner",
|
|
146
|
+
label: "partner",
|
|
147
|
+
field: "parent_id",
|
|
148
|
+
searchFields: ["name", "email"],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
mutation: resPartnerMutation,
|
|
152
|
+
};
|
|
153
|
+
function parseCount(raw, flag) {
|
|
154
|
+
if (!/^\d+$/.test(raw)) {
|
|
155
|
+
throw usageError(`invalid ${flag} value ${JSON.stringify(raw)}`, "expected a non-negative integer");
|
|
156
|
+
}
|
|
157
|
+
return Number(raw);
|
|
158
|
+
}
|
|
159
|
+
function parseFieldList(raw) {
|
|
160
|
+
const fields = raw
|
|
161
|
+
.split(",")
|
|
162
|
+
.map((f) => f.trim())
|
|
163
|
+
.filter((f) => f !== "");
|
|
164
|
+
if (fields.length === 0) {
|
|
165
|
+
throw usageError("--fields requires at least one field name");
|
|
166
|
+
}
|
|
167
|
+
return fields;
|
|
168
|
+
}
|
|
169
|
+
export async function partnersListCommand(opts, globals) {
|
|
170
|
+
const client = await authenticatedClient(globals);
|
|
171
|
+
if (opts.domain && opts.filter && opts.filter.length > 0) {
|
|
172
|
+
throw usageError("--domain and --filter are mutually exclusive", "pick one filtering form");
|
|
173
|
+
}
|
|
174
|
+
if (opts.company && opts.individual) {
|
|
175
|
+
throw usageError("--company and --individual are mutually exclusive", "pick one (or neither to list both)");
|
|
176
|
+
}
|
|
177
|
+
const limit = parseCount(opts.limit, "--limit");
|
|
178
|
+
const offset = parseCount(opts.offset, "--offset");
|
|
179
|
+
const fieldsMeta = await ModelFields.load(client, "res.partner");
|
|
180
|
+
const domain = [];
|
|
181
|
+
if (opts.archived) {
|
|
182
|
+
domain.push(["active", "=", false]);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
domain.push(["active", "=", true]);
|
|
186
|
+
}
|
|
187
|
+
if (opts.company)
|
|
188
|
+
domain.push(["is_company", "=", true]);
|
|
189
|
+
if (opts.individual)
|
|
190
|
+
domain.push(["is_company", "=", false]);
|
|
191
|
+
if (opts.domain) {
|
|
192
|
+
domain.push(...parseDomain(opts.domain));
|
|
193
|
+
}
|
|
194
|
+
else if (opts.filter && opts.filter.length > 0) {
|
|
195
|
+
domain.push(...buildFilterClauses(opts.filter, fieldsMeta, {
|
|
196
|
+
parent_id: "--parent",
|
|
197
|
+
country_id: "--country",
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
const fields = opts.fields
|
|
201
|
+
? parseFieldList(opts.fields)
|
|
202
|
+
: resPartner.defaultListFields;
|
|
203
|
+
for (const field of fields) {
|
|
204
|
+
if (field !== "id" && !fieldsMeta.get(field)) {
|
|
205
|
+
throw usageError(`unknown field ${JSON.stringify(field)} on res.partner`, `run \`odoo partners show <id> --all-fields\` to see available fields`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const order = opts.order ?? "id asc";
|
|
209
|
+
// active_test:false so Odoo returns both active and archived rows; the
|
|
210
|
+
// domain above is the authoritative filter.
|
|
211
|
+
const kwargs = {
|
|
212
|
+
fields,
|
|
213
|
+
limit,
|
|
214
|
+
offset,
|
|
215
|
+
order,
|
|
216
|
+
context: { active_test: false },
|
|
217
|
+
};
|
|
218
|
+
const [records, count] = await Promise.all([
|
|
219
|
+
client.executeKw("res.partner", "search_read", [domain], kwargs),
|
|
220
|
+
client.executeKw("res.partner", "search_count", [domain], {
|
|
221
|
+
context: { active_test: false },
|
|
222
|
+
}),
|
|
223
|
+
]);
|
|
224
|
+
if (globals.json) {
|
|
225
|
+
printJson({ model: "res.partner", records, count, limit, offset });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const headers = fields.map((f) => fieldsMeta.get(f)?.string ?? f);
|
|
229
|
+
const rows = records.map((rec) => fields.map((f) => humanCell(rec[f])));
|
|
230
|
+
const color = colorEnabled(globals.noColor);
|
|
231
|
+
process.stdout.write(formatTable(headers, rows, color) + "\n");
|
|
232
|
+
}
|
|
233
|
+
export function partnersShowCommand(idArg, opts, globals) {
|
|
234
|
+
return showCommand(resPartner, idArg, opts, globals);
|
|
235
|
+
}
|
|
236
|
+
export function partnersCreateCommand(opts, globals) {
|
|
237
|
+
return createCommand(resPartner, opts, globals);
|
|
238
|
+
}
|
|
239
|
+
export function partnersUpdateCommand(idArgs, opts, globals) {
|
|
240
|
+
return updateCommand(resPartner, idArgs, opts, globals);
|
|
241
|
+
}
|
|
242
|
+
export function partnersDeleteCommand(idArgs, globals) {
|
|
243
|
+
return deleteCommand(resPartner, idArgs, globals);
|
|
244
|
+
}
|