@hostlink/hostlink-cli 1.0.3 → 1.0.5

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.
Files changed (3) hide show
  1. package/dist/bundle.js +1050 -28
  2. package/index.js +8 -0
  3. package/package.json +1 -1
package/dist/bundle.js CHANGED
@@ -31530,22 +31530,42 @@ var require_clients = __commonJS({
31530
31530
  }
31531
31531
  var CLIENT_FIELDS = `
31532
31532
  client_id
31533
+ client_no
31533
31534
  client_name
31534
31535
  client_email
31535
31536
  client_phone
31536
31537
  client_city
31537
31538
  status
31538
31539
  join_date
31540
+ bill_title
31541
+ bill_first_name
31542
+ bill_last_name
31543
+ bill_department
31544
+ bill_name
31545
+ bill_addr1
31546
+ bill_addr2
31547
+ bill_addr3
31548
+ bill_city
31549
+ bill_phone
31550
+ bill_fax
31551
+ bill_email
31539
31552
  `;
31540
31553
  function register(program2) {
31541
31554
  const clients2 = program2.command("clients").description("Manage clients");
31542
- clients2.command("list").description("List clients").option("-l, --limit <n>", "Max number of clients to return", "50").option("-o, --offset <n>", "Number of clients to skip", "0").option("-s, --search <name>", "Filter by client name").option("--json", "Output as JSON").action(async (options) => {
31555
+ clients2.command("list").description("List clients").option("-l, --limit <n>", "Max number of clients to return", "50").option("-o, --offset <n>", "Number of clients to skip", "0").option("-s, --search <name>", "Filter by client name").option("--sort <field>", "Sort by field: client_no, client_no:desc, client_name, client_name:desc, join_date, join_date:desc", "client_no").option("--json", "Output as JSON").action(async (options) => {
31543
31556
  const client = getClient();
31544
- const filters = options.search ? `filters: { client_name: { contains: ${JSON.stringify(options.search)} } }` : "";
31557
+ const VALID_SORTS = ["client_no", "client_no:desc", "client_name", "client_name:desc", "join_date", "join_date:desc"];
31558
+ if (!VALID_SORTS.includes(options.sort)) {
31559
+ console.error(`Invalid sort value. Must be one of: ${VALID_SORTS.join(", ")}`);
31560
+ process.exit(1);
31561
+ }
31562
+ const args = [];
31563
+ if (options.search) args.push(`filters: { client_name: { contains: ${JSON.stringify(options.search)} } }`);
31564
+ args.push(`sort: ${JSON.stringify(options.sort)}`);
31545
31565
  const pagination = `limit: ${parseInt(options.limit)}, offset: ${parseInt(options.offset)}`;
31546
31566
  const query = gql`
31547
31567
  query {
31548
- listClient${filters ? `(${filters})` : ""} {
31568
+ listClient(${args.join(", ")}) {
31549
31569
  meta { total }
31550
31570
  data(${pagination}) {
31551
31571
  ${CLIENT_FIELDS}
@@ -31563,7 +31583,7 @@ var require_clients = __commonJS({
31563
31583
  console.log("No clients found.");
31564
31584
  } else {
31565
31585
  list.forEach(
31566
- (c) => console.log(`[${c.client_id}] ${c.client_name} | ${c.client_email ?? "-"} | ${c.client_phone ?? "-"} | ${c.status ?? "-"}`)
31586
+ (c) => console.log(`[${c.client_id}] ${c.client_no ? `#${c.client_no} ` : ""}${c.client_name} | ${c.client_email ?? "-"} | ${c.client_phone ?? "-"} | ${c.status ?? "-"}`)
31567
31587
  );
31568
31588
  console.log(`
31569
31589
  Total: ${total}`);
@@ -31723,7 +31743,7 @@ var require_domains = __commonJS({
31723
31743
  const client = getClient();
31724
31744
  const filterParts = [];
31725
31745
  if (options.client) filterParts.push(`client_id: ${parseInt(options.client)}`);
31726
- if (options.search) filterParts.push(`domain_name: ${JSON.stringify(options.search)}`);
31746
+ if (options.search) filterParts.push(`domain_name: { contains: ${JSON.stringify(options.search)} }`);
31727
31747
  const filters = filterParts.length ? `filters: { ${filterParts.join(", ")} }` : "";
31728
31748
  const pagination = `limit: ${parseInt(options.limit)}, offset: ${parseInt(options.offset)}`;
31729
31749
  const query = gql`
@@ -31732,6 +31752,7 @@ var require_domains = __commonJS({
31732
31752
  meta { total }
31733
31753
  data(${pagination}) {
31734
31754
  domain_id
31755
+ domain_no
31735
31756
  domain_name
31736
31757
  client_id
31737
31758
  expiry_date
@@ -31753,7 +31774,7 @@ var require_domains = __commonJS({
31753
31774
  console.log("No domains found.");
31754
31775
  } else {
31755
31776
  list.forEach(
31756
- (d) => console.log(`[${d.domain_id}] ${d.domain_name} | client:${d.client_id} | expires:${d.expiry_date ?? "-"} | ${d.registrar ?? "-"} | ${d.status ?? "-"}`)
31777
+ (d) => console.log(`[${d.domain_id}] ${d.domain_no ? `#${d.domain_no} ` : ""}${d.domain_name} | client:${d.client_id} | expires:${d.expiry_date ?? "-"} | ${d.registrar ?? "-"} | ${d.status ?? "-"}`)
31757
31778
  );
31758
31779
  console.log(`
31759
31780
  Total: ${total}`);
@@ -31860,7 +31881,7 @@ Total: ${total}`);
31860
31881
  process.exit(1);
31861
31882
  }
31862
31883
  });
31863
- domains2.command("update <id>").description("Update a domain by ID").option("--domain-name <value>", "Domain name").option("--client-id <id>", "Client ID").option("--expiry-date <date>", "Expiry date").option("--creation-date <date>", "Creation date").option("--registrar <value>", "Registrar").option("--primary-dns <value>", "Primary DNS").option("--secondary-dns <value>", "Secondary DNS").option("--domain-user-id <value>", "Domain user ID").option("--domain-password <value>", "Domain password").option("--domain-type <n>", "Domain type").option("--remark <value>", "Remark").option("--server-id <n>", "Server ID").option("--status <n>", "Status").action(async (id, options) => {
31884
+ domains2.command("update <id>").description("Update a domain by ID").option("--domain-name <value>", "Domain name").option("--client-id <id>", "Client ID").option("--expiry-date <date>", "Expiry date").option("--creation-date <date>", "Creation date").option("--registrar <value>", "Registrar").option("--primary-dns <value>", "Primary DNS").option("--secondary-dns <value>", "Secondary DNS").option("--domain-user-id <value>", "Domain user ID").option("--domain-password <value>", "Domain password").option("--domain-type <n>", "Domain type").option("--remark <value>", "Remark").option("--server-id <n>", "Server ID").action(async (id, options) => {
31864
31885
  const client = getClient();
31865
31886
  const fieldMap = {
31866
31887
  domainName: ["domain_name", (v) => JSON.stringify(v)],
@@ -31874,8 +31895,7 @@ Total: ${total}`);
31874
31895
  domainPassword: ["domain_password", (v) => JSON.stringify(v)],
31875
31896
  domainType: ["domain_type", (v) => parseInt(v)],
31876
31897
  remark: ["remark", (v) => JSON.stringify(v)],
31877
- serverId: ["server_id", (v) => parseInt(v)],
31878
- status: ["status", (v) => parseInt(v)]
31898
+ serverId: ["server_id", (v) => parseInt(v)]
31879
31899
  };
31880
31900
  const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
31881
31901
  if (fields.length === 0) {
@@ -31927,6 +31947,190 @@ Total: ${total}`);
31927
31947
  }
31928
31948
  });
31929
31949
 
31950
+ // src/domain-passwords/index.js
31951
+ var require_domain_passwords = __commonJS({
31952
+ "src/domain-passwords/index.js"(exports2, module2) {
31953
+ var { GraphQLClient, gql } = require_main();
31954
+ var Conf2 = require_source();
31955
+ var config2 = new Conf2({ projectName: "hostlink-cli" });
31956
+ var ENDPOINT = "https://isapi.hostlink.com.hk/";
31957
+ function getClient() {
31958
+ const token = config2.get("token");
31959
+ if (!token) {
31960
+ console.error("No token found. Run `hostlink set-token <token>` first.");
31961
+ process.exit(1);
31962
+ }
31963
+ return new GraphQLClient(ENDPOINT, {
31964
+ headers: { Authorization: `Bearer ${token}` }
31965
+ });
31966
+ }
31967
+ var DOMAIN_PASSWORD_FIELDS = `
31968
+ domainpassword_id
31969
+ domain_id
31970
+ name
31971
+ username
31972
+ password
31973
+ host
31974
+ port
31975
+ remark
31976
+ `;
31977
+ function register(program2) {
31978
+ const dp = program2.command("domain-passwords").description("Manage domain passwords");
31979
+ dp.command("list <domain_id>").description("List passwords for a domain").option("--json", "Output as JSON").action(async (domain_id, options) => {
31980
+ const client = getClient();
31981
+ const query = gql`
31982
+ query {
31983
+ listDomainPassword(filters: { domain_id: ${parseInt(domain_id)} }) {
31984
+ data {
31985
+ ${DOMAIN_PASSWORD_FIELDS}
31986
+ }
31987
+ }
31988
+ }
31989
+ `;
31990
+ try {
31991
+ const data = await client.request(query);
31992
+ const list = data?.listDomainPassword?.data ?? [];
31993
+ if (options.json) {
31994
+ console.log(JSON.stringify(list, null, 2));
31995
+ } else if (list.length === 0) {
31996
+ console.log("No domain passwords found.");
31997
+ } else {
31998
+ list.forEach(
31999
+ (p) => console.log(`[${p.domainpassword_id}] ${p.name ?? "-"} | ${p.username ?? "-"} | ${p.host ?? "-"}${p.port ? `:${p.port}` : ""}${p.remark ? ` | ${p.remark}` : ""}`)
32000
+ );
32001
+ console.log(`
32002
+ Total: ${list.length}`);
32003
+ }
32004
+ } catch (err) {
32005
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
32006
+ console.error(`Failed to fetch domain passwords: ${message}`);
32007
+ process.exit(1);
32008
+ }
32009
+ });
32010
+ dp.command("get <id>").description("Get a domain password by ID").option("--json", "Output as JSON").action(async (id, options) => {
32011
+ const client = getClient();
32012
+ const query = gql`
32013
+ query {
32014
+ listDomainPassword(filters: { domainpassword_id: ${parseInt(id)} }) {
32015
+ data {
32016
+ ${DOMAIN_PASSWORD_FIELDS}
32017
+ }
32018
+ }
32019
+ }
32020
+ `;
32021
+ try {
32022
+ const data = await client.request(query);
32023
+ const list = data?.listDomainPassword?.data ?? [];
32024
+ if (list.length === 0) {
32025
+ console.error(`Domain password [${id}] not found.`);
32026
+ process.exit(1);
32027
+ }
32028
+ const p = list[0];
32029
+ if (options.json) {
32030
+ console.log(JSON.stringify(p, null, 2));
32031
+ } else {
32032
+ console.log(`ID: ${p.domainpassword_id}`);
32033
+ console.log(`Domain ID:${p.domain_id}`);
32034
+ console.log(`Name: ${p.name ?? "-"}`);
32035
+ console.log(`Username: ${p.username ?? "-"}`);
32036
+ console.log(`Password: ${p.password ?? "-"}`);
32037
+ console.log(`Host: ${p.host ?? "-"}`);
32038
+ console.log(`Port: ${p.port ?? "-"}`);
32039
+ if (p.remark) console.log(`Remark: ${p.remark}`);
32040
+ }
32041
+ } catch (err) {
32042
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
32043
+ console.error(`Failed to fetch domain password: ${message}`);
32044
+ process.exit(1);
32045
+ }
32046
+ });
32047
+ dp.command("add").description("Add a password to a domain").requiredOption("-d, --domain-id <id>", "Domain ID").requiredOption("--name <value>", "Name / label").option("--username <value>", "Username").option("--password <value>", "Password").option("--host <value>", "Host").option("--port <n>", "Port").option("--remark <value>", "Remark").action(async (options) => {
32048
+ const client = getClient();
32049
+ const fieldMap = {
32050
+ domainId: ["domain_id", (v) => parseInt(v)],
32051
+ name: ["name", (v) => JSON.stringify(v)],
32052
+ username: ["username", (v) => JSON.stringify(v)],
32053
+ password: ["password", (v) => JSON.stringify(v)],
32054
+ host: ["host", (v) => JSON.stringify(v)],
32055
+ port: ["port", (v) => parseInt(v)],
32056
+ remark: ["remark", (v) => JSON.stringify(v)]
32057
+ };
32058
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
32059
+ const mutation = gql`
32060
+ mutation {
32061
+ addDomainPassword(data: { ${fields.join(", ")} })
32062
+ }
32063
+ `;
32064
+ try {
32065
+ const data = await client.request(mutation);
32066
+ const newId = data?.addDomainPassword;
32067
+ console.log(`Created domain password [${newId}].`);
32068
+ } catch (err) {
32069
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
32070
+ console.error(`Failed to add domain password: ${message}`);
32071
+ process.exit(1);
32072
+ }
32073
+ });
32074
+ dp.command("update <id>").description("Update a domain password by ID").option("--name <value>", "Name / label").option("--username <value>", "Username").option("--password <value>", "Password").option("--host <value>", "Host").option("--port <n>", "Port").option("--remark <value>", "Remark").action(async (id, options) => {
32075
+ const client = getClient();
32076
+ const fieldMap = {
32077
+ name: ["name", (v) => JSON.stringify(v)],
32078
+ username: ["username", (v) => JSON.stringify(v)],
32079
+ password: ["password", (v) => JSON.stringify(v)],
32080
+ host: ["host", (v) => JSON.stringify(v)],
32081
+ port: ["port", (v) => parseInt(v)],
32082
+ remark: ["remark", (v) => JSON.stringify(v)]
32083
+ };
32084
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
32085
+ if (fields.length === 0) {
32086
+ console.error("No fields to update. Provide at least one option.");
32087
+ process.exit(1);
32088
+ }
32089
+ const mutation = gql`
32090
+ mutation {
32091
+ updateDomainPassword(id: ${parseInt(id)}, data: { ${fields.join(", ")} })
32092
+ }
32093
+ `;
32094
+ try {
32095
+ const data = await client.request(mutation);
32096
+ if (data?.updateDomainPassword) {
32097
+ console.log(`Updated domain password [${id}].`);
32098
+ } else {
32099
+ console.error("Update failed.");
32100
+ process.exit(1);
32101
+ }
32102
+ } catch (err) {
32103
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
32104
+ console.error(`Failed to update domain password: ${message}`);
32105
+ process.exit(1);
32106
+ }
32107
+ });
32108
+ dp.command("delete <id>").description("Delete a domain password by ID").action(async (id) => {
32109
+ const client = getClient();
32110
+ const mutation = gql`
32111
+ mutation {
32112
+ deleteDomainPassword(id: ${parseInt(id)})
32113
+ }
32114
+ `;
32115
+ try {
32116
+ const data = await client.request(mutation);
32117
+ if (data?.deleteDomainPassword) {
32118
+ console.log(`Deleted domain password [${id}].`);
32119
+ } else {
32120
+ console.error("Delete failed.");
32121
+ process.exit(1);
32122
+ }
32123
+ } catch (err) {
32124
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
32125
+ console.error(`Failed to delete domain password: ${message}`);
32126
+ process.exit(1);
32127
+ }
32128
+ });
32129
+ }
32130
+ module2.exports = { register };
32131
+ }
32132
+ });
32133
+
31930
32134
  // src/quotations/index.js
31931
32135
  var require_quotations = __commonJS({
31932
32136
  "src/quotations/index.js"(exports2, module2) {
@@ -65282,29 +65486,15 @@ var require_leave = __commonJS({
65282
65486
  }
65283
65487
  const submitData = await promptLeaveRequest();
65284
65488
  const mutation = gql`
65285
- mutation AddLeave($data: AddStaffLeaveRequestInput!) {
65286
- addStaffLeaveRequest(data: $data) {
65287
- leave_request_id
65288
- type
65289
- from_date
65290
- to_date
65291
- status
65292
- remark
65293
- }
65489
+ mutation AddLeave($data: CreateStaffLeaveInput!) {
65490
+ addStaffLeaveRequest(data: $data)
65294
65491
  }
65295
65492
  `;
65296
65493
  try {
65297
65494
  const result = await client.request(mutation, { data: submitData });
65298
- const req = result?.addStaffLeaveRequest;
65495
+ const leaveRequestId = result?.addStaffLeaveRequest;
65299
65496
  console.log("\n\u2705 Leave request submitted successfully!");
65300
- if (req) {
65301
- console.log(` ID: ${req.leave_request_id}`);
65302
- console.log(` Type: ${LEAVE_TYPE_LABELS[req.type] ?? req.type}`);
65303
- console.log(` From: ${req.from_date}`);
65304
- if (req.to_date) console.log(` To: ${req.to_date}`);
65305
- console.log(` Status: ${req.status}`);
65306
- if (req.remark) console.log(` Remark: ${req.remark}`);
65307
- }
65497
+ console.log(` ID: ${leaveRequestId}`);
65308
65498
  } catch (err) {
65309
65499
  const message = err?.response?.errors?.[0]?.message ?? err.message;
65310
65500
  console.error(`
@@ -65358,12 +65548,836 @@ var require_leave = __commonJS({
65358
65548
  }
65359
65549
  });
65360
65550
 
65551
+ // src/invoices/index.js
65552
+ var require_invoices = __commonJS({
65553
+ "src/invoices/index.js"(exports2, module2) {
65554
+ var { GraphQLClient, gql } = require_main();
65555
+ var Conf2 = require_source();
65556
+ var config2 = new Conf2({ projectName: "hostlink-cli" });
65557
+ var ENDPOINT = "https://isapi.hostlink.com.hk/";
65558
+ function getClient() {
65559
+ const token = config2.get("token");
65560
+ if (!token) {
65561
+ console.error("No token found. Run `hostlink set-token <token>` first.");
65562
+ process.exit(1);
65563
+ }
65564
+ return new GraphQLClient(ENDPOINT, {
65565
+ headers: { Authorization: `Bearer ${token}` }
65566
+ });
65567
+ }
65568
+ function register(program2) {
65569
+ const invoices2 = program2.command("invoices").description("Manage invoices");
65570
+ invoices2.command("list").description("List invoices").option("-c, --client <id>", "Filter by client ID").option("-s, --search <text>", "Filter by client name (contains)").option("--status <value>", "Filter by status").option("-l, --limit <n>", "Max number of invoices to return", "50").option("-o, --offset <n>", "Number of invoices to skip", "0").option("--json", "Output as JSON").action(async (options) => {
65571
+ const client = getClient();
65572
+ const filterParts = [];
65573
+ if (options.client) filterParts.push(`client_id: ${parseInt(options.client)}`);
65574
+ if (options.search) filterParts.push(`client_name: { contains: ${JSON.stringify(options.search)} }`);
65575
+ if (options.status) filterParts.push(`status: ${JSON.stringify(options.status)}`);
65576
+ const filters = filterParts.length ? `filters: { ${filterParts.join(", ")} }` : "";
65577
+ const pagination = `limit: ${parseInt(options.limit)}, offset: ${parseInt(options.offset)}`;
65578
+ const query = gql`
65579
+ query {
65580
+ listInvoice${filters ? `(${filters})` : ""} {
65581
+ meta { total }
65582
+ data(${pagination}) {
65583
+ invoice_id
65584
+ invoice_no
65585
+ invoice_date
65586
+ due_date
65587
+ client_id
65588
+ client_name
65589
+ status
65590
+ total
65591
+ paidTotal
65592
+ }
65593
+ }
65594
+ }
65595
+ `;
65596
+ try {
65597
+ const data = await client.request(query);
65598
+ const list = data?.listInvoice?.data ?? [];
65599
+ const total = data?.listInvoice?.meta?.total ?? list.length;
65600
+ if (options.json) {
65601
+ console.log(JSON.stringify({ total, data: list }, null, 2));
65602
+ } else if (list.length === 0) {
65603
+ console.log("No invoices found.");
65604
+ } else {
65605
+ list.forEach(
65606
+ (inv) => console.log(`[${inv.invoice_id}] #${inv.invoice_no ?? "-"} | ${inv.client_name ?? "-"} | ${inv.invoice_date ?? "-"} | due:${inv.due_date ?? "-"} | ${inv.status ?? "-"} | $${inv.total ?? 0} | paid:$${inv.paidTotal ?? 0}`)
65607
+ );
65608
+ console.log(`
65609
+ Total: ${total}`);
65610
+ }
65611
+ } catch (err) {
65612
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65613
+ console.error(`Failed to fetch invoices: ${message}`);
65614
+ process.exit(1);
65615
+ }
65616
+ });
65617
+ invoices2.command("get <id>").description("Get an invoice by ID").option("--json", "Output as JSON").action(async (id, options) => {
65618
+ const client = getClient();
65619
+ const query = gql`
65620
+ query {
65621
+ listInvoice(filters: { invoice_id: ${parseInt(id)} }) {
65622
+ data {
65623
+ invoice_id
65624
+ invoice_no
65625
+ invoice_date
65626
+ due_date
65627
+ client_id
65628
+ client_name
65629
+ attn
65630
+ phone
65631
+ email
65632
+ fax
65633
+ addr1
65634
+ addr2
65635
+ addr3
65636
+ city
65637
+ status
65638
+ total
65639
+ paidTotal
65640
+ invoice_remark
65641
+ invoice_type
65642
+ invoice_send_via
65643
+ void_date
65644
+ items {
65645
+ paymentperiod_id
65646
+ description
65647
+ unit_price
65648
+ qty
65649
+ discount
65650
+ total
65651
+ remark
65652
+ period_from
65653
+ period_to
65654
+ }
65655
+ }
65656
+ }
65657
+ }
65658
+ `;
65659
+ try {
65660
+ const data = await client.request(query);
65661
+ const list = data?.listInvoice?.data ?? [];
65662
+ if (list.length === 0) {
65663
+ console.error(`Invoice [${id}] not found.`);
65664
+ process.exit(1);
65665
+ }
65666
+ const inv = list[0];
65667
+ if (options.json) {
65668
+ console.log(JSON.stringify(inv, null, 2));
65669
+ } else {
65670
+ console.log(`ID: ${inv.invoice_id}`);
65671
+ console.log(`No: ${inv.invoice_no ?? "-"}`);
65672
+ console.log(`Client ID: ${inv.client_id}`);
65673
+ console.log(`Client: ${inv.client_name ?? "-"}`);
65674
+ console.log(`Attn: ${inv.attn ?? "-"}`);
65675
+ console.log(`Email: ${inv.email ?? "-"}`);
65676
+ console.log(`Phone: ${inv.phone ?? "-"}`);
65677
+ console.log(`Address: ${[inv.addr1, inv.addr2, inv.addr3, inv.city].filter(Boolean).join(", ") || "-"}`);
65678
+ console.log(`Date: ${inv.invoice_date ?? "-"}`);
65679
+ console.log(`Due Date: ${inv.due_date ?? "-"}`);
65680
+ console.log(`Status: ${inv.status ?? "-"}`);
65681
+ console.log(`Total: $${inv.total ?? 0}`);
65682
+ console.log(`Paid: $${inv.paidTotal ?? 0}`);
65683
+ if (inv.void_date) console.log(`Void Date: ${inv.void_date}`);
65684
+ if (inv.invoice_remark) console.log(`Remark: ${inv.invoice_remark}`);
65685
+ if (inv.items?.length) {
65686
+ console.log("\n--- Items ---");
65687
+ inv.items.forEach((item, i) => {
65688
+ console.log(` [${i + 1}] ${item.description ?? "-"} | qty:${item.qty ?? 1} x $${item.unit_price ?? 0} | total:$${item.total ?? 0}${item.remark ? ` | ${item.remark}` : ""}`);
65689
+ });
65690
+ }
65691
+ }
65692
+ } catch (err) {
65693
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65694
+ console.error(`Failed to fetch invoice: ${message}`);
65695
+ process.exit(1);
65696
+ }
65697
+ });
65698
+ invoices2.command("add").description("Add a new invoice").requiredOption("-c, --client-id <id>", "Client ID").option("--client-name <value>", "Client name").option("--invoice-date <date>", "Invoice date (YYYY-MM-DD)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--attn <value>", "Attention (recipient name)").option("--email <value>", "Email").option("--phone <value>", "Phone").option("--addr1 <value>", "Address line 1").option("--addr2 <value>", "Address line 2").option("--addr3 <value>", "Address line 3").option("--city <value>", "City").option("--invoice-remark <value>", "Remark").option("--invoice-type <n>", "Invoice type").option("--invoice-send-via <n>", "Send via (int)").option("--quotation-ids <value>", "Linked quotation IDs").action(async (options) => {
65699
+ const client = getClient();
65700
+ const fieldMap = {
65701
+ clientId: ["client_id", (v) => parseInt(v)],
65702
+ clientName: ["client_name", (v) => JSON.stringify(v)],
65703
+ invoiceDate: ["invoice_date", (v) => JSON.stringify(v)],
65704
+ dueDate: ["due_date", (v) => JSON.stringify(v)],
65705
+ attn: ["attn", (v) => JSON.stringify(v)],
65706
+ email: ["email", (v) => JSON.stringify(v)],
65707
+ phone: ["phone", (v) => JSON.stringify(v)],
65708
+ addr1: ["addr1", (v) => JSON.stringify(v)],
65709
+ addr2: ["addr2", (v) => JSON.stringify(v)],
65710
+ addr3: ["addr3", (v) => JSON.stringify(v)],
65711
+ city: ["city", (v) => JSON.stringify(v)],
65712
+ invoiceRemark: ["invoice_remark", (v) => JSON.stringify(v)],
65713
+ invoiceType: ["invoice_type", (v) => parseInt(v)],
65714
+ invoiceSendVia: ["invoice_send_via", (v) => parseInt(v)],
65715
+ quotationIds: ["quotation_ids", (v) => JSON.stringify(v)]
65716
+ };
65717
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
65718
+ const mutation = gql`
65719
+ mutation {
65720
+ addInvoice(data: { ${fields.join(", ")} })
65721
+ }
65722
+ `;
65723
+ try {
65724
+ const data = await client.request(mutation);
65725
+ const newId = data?.addInvoice;
65726
+ console.log(`Created invoice [${newId}].`);
65727
+ } catch (err) {
65728
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65729
+ console.error(`Failed to add invoice: ${message}`);
65730
+ process.exit(1);
65731
+ }
65732
+ });
65733
+ invoices2.command("update <id>").description("Update an invoice by ID").option("--client-id <id>", "Client ID").option("--client-name <value>", "Client name").option("--invoice-date <date>", "Invoice date (YYYY-MM-DD)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--attn <value>", "Attention (recipient name)").option("--email <value>", "Email").option("--phone <value>", "Phone").option("--addr1 <value>", "Address line 1").option("--addr2 <value>", "Address line 2").option("--addr3 <value>", "Address line 3").option("--city <value>", "City").option("--invoice-remark <value>", "Remark").option("--invoice-type <n>", "Invoice type").option("--invoice-send-via <n>", "Send via (int)").option("--void-date <date>", "Void date (YYYY-MM-DD)").action(async (id, options) => {
65734
+ const client = getClient();
65735
+ const fieldMap = {
65736
+ clientId: ["client_id", (v) => parseInt(v)],
65737
+ clientName: ["client_name", (v) => JSON.stringify(v)],
65738
+ invoiceDate: ["invoice_date", (v) => JSON.stringify(v)],
65739
+ dueDate: ["due_date", (v) => JSON.stringify(v)],
65740
+ attn: ["attn", (v) => JSON.stringify(v)],
65741
+ email: ["email", (v) => JSON.stringify(v)],
65742
+ phone: ["phone", (v) => JSON.stringify(v)],
65743
+ addr1: ["addr1", (v) => JSON.stringify(v)],
65744
+ addr2: ["addr2", (v) => JSON.stringify(v)],
65745
+ addr3: ["addr3", (v) => JSON.stringify(v)],
65746
+ city: ["city", (v) => JSON.stringify(v)],
65747
+ invoiceRemark: ["invoice_remark", (v) => JSON.stringify(v)],
65748
+ invoiceType: ["invoice_type", (v) => parseInt(v)],
65749
+ invoiceSendVia: ["invoice_send_via", (v) => parseInt(v)],
65750
+ voidDate: ["void_date", (v) => JSON.stringify(v)]
65751
+ };
65752
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
65753
+ if (fields.length === 0) {
65754
+ console.error("No fields to update. Provide at least one option.");
65755
+ process.exit(1);
65756
+ }
65757
+ const mutation = gql`
65758
+ mutation {
65759
+ updateInvoice(id: ${parseInt(id)}, data: { ${fields.join(", ")} })
65760
+ }
65761
+ `;
65762
+ try {
65763
+ const data = await client.request(mutation);
65764
+ if (data?.updateInvoice) {
65765
+ console.log(`Updated invoice [${id}].`);
65766
+ } else {
65767
+ console.error("Update failed.");
65768
+ process.exit(1);
65769
+ }
65770
+ } catch (err) {
65771
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65772
+ console.error(`Failed to update invoice: ${message}`);
65773
+ process.exit(1);
65774
+ }
65775
+ });
65776
+ invoices2.command("delete <id>").description("Delete an invoice by ID").action(async (id) => {
65777
+ const client = getClient();
65778
+ const mutation = gql`
65779
+ mutation {
65780
+ deleteInvoice(id: ${parseInt(id)})
65781
+ }
65782
+ `;
65783
+ try {
65784
+ const data = await client.request(mutation);
65785
+ if (data?.deleteInvoice) {
65786
+ console.log(`Deleted invoice [${id}].`);
65787
+ } else {
65788
+ console.error("Delete failed.");
65789
+ process.exit(1);
65790
+ }
65791
+ } catch (err) {
65792
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65793
+ console.error(`Failed to delete invoice: ${message}`);
65794
+ process.exit(1);
65795
+ }
65796
+ });
65797
+ invoices2.command("pdf <id>").description("Get the PDF download link for an invoice").option("-s, --save [filename]", "Download and save PDF to local file").action(async (id, options) => {
65798
+ const client = getClient();
65799
+ const query = gql`
65800
+ query {
65801
+ listInvoice(filters: { invoice_id: ${parseInt(id)} }) {
65802
+ data {
65803
+ invoice_id
65804
+ invoice_no
65805
+ pdfLink
65806
+ downloadUrl
65807
+ }
65808
+ }
65809
+ }
65810
+ `;
65811
+ try {
65812
+ const data = await client.request(query);
65813
+ const list = data?.listInvoice?.data ?? [];
65814
+ if (list.length === 0) {
65815
+ console.error(`Invoice [${id}] not found.`);
65816
+ process.exit(1);
65817
+ }
65818
+ const inv = list[0];
65819
+ const link = inv.pdfLink || inv.downloadUrl;
65820
+ if (!link) {
65821
+ console.error("No PDF link available for this invoice.");
65822
+ process.exit(1);
65823
+ }
65824
+ if (options.save !== void 0) {
65825
+ const https = require("https");
65826
+ const http = require("http");
65827
+ const fs = require("fs");
65828
+ const path = require("path");
65829
+ const filename = typeof options.save === "string" ? options.save : `invoice-${inv.invoice_no ?? id}.pdf`;
65830
+ const filepath = path.resolve(filename);
65831
+ const protocol = link.startsWith("https") ? https : http;
65832
+ const token = config2.get("token");
65833
+ const urlObj = new URL(link);
65834
+ const reqOptions = {
65835
+ hostname: urlObj.hostname,
65836
+ path: urlObj.pathname + urlObj.search,
65837
+ headers: { Authorization: `Bearer ${token}` }
65838
+ };
65839
+ console.log(`Downloading to: ${filepath}`);
65840
+ const file = fs.createWriteStream(filepath);
65841
+ protocol.get(reqOptions, (res) => {
65842
+ if (res.statusCode !== 200) {
65843
+ console.error(`Download failed: HTTP ${res.statusCode}`);
65844
+ fs.unlinkSync(filepath);
65845
+ process.exit(1);
65846
+ }
65847
+ res.pipe(file);
65848
+ file.on("finish", () => {
65849
+ file.close();
65850
+ console.log(`Saved: ${filepath}`);
65851
+ });
65852
+ }).on("error", (err) => {
65853
+ fs.unlinkSync(filepath);
65854
+ console.error(`Download error: ${err.message}`);
65855
+ process.exit(1);
65856
+ });
65857
+ } else {
65858
+ console.log(`Invoice: [${inv.invoice_id}] #${inv.invoice_no ?? ""}`);
65859
+ console.log(`PDF Link: ${link}`);
65860
+ }
65861
+ } catch (err) {
65862
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65863
+ console.error(`Failed to fetch PDF link: ${message}`);
65864
+ process.exit(1);
65865
+ }
65866
+ });
65867
+ }
65868
+ module2.exports = { register };
65869
+ }
65870
+ });
65871
+
65872
+ // src/invoice-items/index.js
65873
+ var require_invoice_items = __commonJS({
65874
+ "src/invoice-items/index.js"(exports2, module2) {
65875
+ var { GraphQLClient, gql } = require_main();
65876
+ var Conf2 = require_source();
65877
+ var config2 = new Conf2({ projectName: "hostlink-cli" });
65878
+ var ENDPOINT = "https://isapi.hostlink.com.hk/";
65879
+ function getClient() {
65880
+ const token = config2.get("token");
65881
+ if (!token) {
65882
+ console.error("No token found. Run `hostlink set-token <token>` first.");
65883
+ process.exit(1);
65884
+ }
65885
+ return new GraphQLClient(ENDPOINT, {
65886
+ headers: { Authorization: `Bearer ${token}` }
65887
+ });
65888
+ }
65889
+ function register(program2) {
65890
+ const items = program2.command("invoice-items").description("Manage invoice items (PaymentPeriod)");
65891
+ items.command("list <invoice_id>").description("List items of an invoice").option("--json", "Output as JSON").action(async (invoice_id, options) => {
65892
+ const client = getClient();
65893
+ const query = gql`
65894
+ query {
65895
+ listPaymentPeriod(filters: { invoice_id: ${parseInt(invoice_id)} }) {
65896
+ data {
65897
+ paymentperiod_id
65898
+ invoice_id
65899
+ sequence
65900
+ description
65901
+ unit_price
65902
+ qty
65903
+ free_month
65904
+ discount
65905
+ subtotal
65906
+ total
65907
+ remark
65908
+ period_from
65909
+ period_to
65910
+ clientservice_id
65911
+ }
65912
+ }
65913
+ }
65914
+ `;
65915
+ try {
65916
+ const data = await client.request(query);
65917
+ const list = data?.listPaymentPeriod?.data ?? [];
65918
+ if (options.json) {
65919
+ console.log(JSON.stringify(list, null, 2));
65920
+ } else if (list.length === 0) {
65921
+ console.log("No items found.");
65922
+ } else {
65923
+ list.forEach(
65924
+ (i) => console.log(`[${i.paymentperiod_id}] #${i.sequence ?? "-"} ${i.description ?? "-"} | qty:${i.qty ?? "-"} x $${i.unit_price ?? 0} | discount:${i.discount ?? 0}% | total:$${i.total ?? 0}${i.remark ? ` | ${i.remark}` : ""}`)
65925
+ );
65926
+ console.log(`
65927
+ Total items: ${list.length}`);
65928
+ }
65929
+ } catch (err) {
65930
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65931
+ console.error(`Failed to fetch invoice items: ${message}`);
65932
+ process.exit(1);
65933
+ }
65934
+ });
65935
+ items.command("get <id>").description("Get an invoice item by ID").option("--json", "Output as JSON").action(async (id, options) => {
65936
+ const client = getClient();
65937
+ const query = gql`
65938
+ query {
65939
+ listPaymentPeriod(filters: { paymentperiod_id: ${parseInt(id)} }) {
65940
+ data {
65941
+ paymentperiod_id
65942
+ invoice_id
65943
+ sequence
65944
+ description
65945
+ unit_price
65946
+ qty
65947
+ unit_month
65948
+ free_month
65949
+ discount
65950
+ subtotal
65951
+ total
65952
+ remark
65953
+ period_from
65954
+ period_to
65955
+ clientservice_id
65956
+ quotation_id
65957
+ }
65958
+ }
65959
+ }
65960
+ `;
65961
+ try {
65962
+ const data = await client.request(query);
65963
+ const list = data?.listPaymentPeriod?.data ?? [];
65964
+ if (list.length === 0) {
65965
+ console.error(`Invoice item [${id}] not found.`);
65966
+ process.exit(1);
65967
+ }
65968
+ const i = list[0];
65969
+ if (options.json) {
65970
+ console.log(JSON.stringify(i, null, 2));
65971
+ } else {
65972
+ console.log(`ID: ${i.paymentperiod_id}`);
65973
+ console.log(`Invoice ID: ${i.invoice_id}`);
65974
+ console.log(`Sequence: ${i.sequence ?? "-"}`);
65975
+ console.log(`Description: ${i.description ?? "-"}`);
65976
+ console.log(`Unit Price: $${i.unit_price ?? 0}`);
65977
+ console.log(`Qty: ${i.qty ?? "-"}`);
65978
+ console.log(`Unit Month: ${i.unit_month ?? 1}`);
65979
+ console.log(`Free Month: ${i.free_month ?? 0}`);
65980
+ console.log(`Discount: ${i.discount ?? 0}%`);
65981
+ console.log(`Subtotal: $${i.subtotal ?? 0}`);
65982
+ console.log(`Total: $${i.total ?? 0}`);
65983
+ console.log(`Period: ${i.period_from ?? "-"} \u2192 ${i.period_to ?? "-"}`);
65984
+ if (i.remark) console.log(`Remark: ${i.remark}`);
65985
+ if (i.clientservice_id) console.log(`Client Svc ID: ${i.clientservice_id}`);
65986
+ if (i.quotation_id) console.log(`Quotation ID: ${i.quotation_id}`);
65987
+ }
65988
+ } catch (err) {
65989
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65990
+ console.error(`Failed to fetch invoice item: ${message}`);
65991
+ process.exit(1);
65992
+ }
65993
+ });
65994
+ items.command("add").description("Add a new item to an invoice").requiredOption("-i, --invoice-id <id>", "Invoice ID").option("--unit-price <n>", "Unit price").option("--qty <n>", "Quantity").option("--unit-month <n>", "Number of months", "1").option("--free-month <n>", "Free months").option("--discount <n>", "Discount percentage").option("--subtotal <n>", "Subtotal (override)").option("--remark <value>", "Remark").option("--sequence <n>", "Display sequence order").option("--period-from <date>", "Service period start (YYYY-MM-DD)").option("--period-to <date>", "Service period end (YYYY-MM-DD)").option("--clientservice-id <n>", "Client service ID").option("--quotation-id <n>", "Linked quotation ID").action(async (options) => {
65995
+ const client = getClient();
65996
+ const fieldMap = {
65997
+ invoiceId: ["invoice_id", (v) => parseInt(v)],
65998
+ unitPrice: ["unit_price", (v) => parseFloat(v)],
65999
+ qty: ["qty", (v) => parseFloat(v)],
66000
+ unitMonth: ["unit_month", (v) => parseInt(v)],
66001
+ freeMonth: ["free_month", (v) => parseInt(v)],
66002
+ discount: ["discount", (v) => parseFloat(v)],
66003
+ subtotal: ["subtotal", (v) => parseFloat(v)],
66004
+ remark: ["remark", (v) => JSON.stringify(v)],
66005
+ sequence: ["sequence", (v) => parseInt(v)],
66006
+ periodFrom: ["period_from", (v) => JSON.stringify(v)],
66007
+ periodTo: ["period_to", (v) => JSON.stringify(v)],
66008
+ clientserviceId: ["clientservice_id", (v) => parseInt(v)],
66009
+ quotationId: ["quotation_id", (v) => parseInt(v)]
66010
+ };
66011
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
66012
+ const mutation = gql`
66013
+ mutation {
66014
+ addPaymentPeriod(data: { ${fields.join(", ")} })
66015
+ }
66016
+ `;
66017
+ try {
66018
+ const data = await client.request(mutation);
66019
+ const newId = data?.addPaymentPeriod;
66020
+ console.log(`Created invoice item [${newId}].`);
66021
+ } catch (err) {
66022
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66023
+ console.error(`Failed to add invoice item: ${message}`);
66024
+ process.exit(1);
66025
+ }
66026
+ });
66027
+ items.command("update <id>").description("Update an invoice item by ID").option("--invoice-id <id>", "Invoice ID").option("--unit-price <n>", "Unit price").option("--qty <n>", "Quantity").option("--unit-month <n>", "Number of months").option("--free-month <n>", "Free months").option("--discount <n>", "Discount percentage").option("--subtotal <n>", "Subtotal (override)").option("--remark <value>", "Remark").option("--sequence <n>", "Display sequence order").option("--period-from <date>", "Service period start (YYYY-MM-DD)").option("--period-to <date>", "Service period end (YYYY-MM-DD)").option("--clientservice-id <n>", "Client service ID").option("--quotation-id <n>", "Linked quotation ID").action(async (id, options) => {
66028
+ const client = getClient();
66029
+ const fieldMap = {
66030
+ invoiceId: ["invoice_id", (v) => parseInt(v)],
66031
+ unitPrice: ["unit_price", (v) => parseFloat(v)],
66032
+ qty: ["qty", (v) => parseFloat(v)],
66033
+ unitMonth: ["unit_month", (v) => parseInt(v)],
66034
+ freeMonth: ["free_month", (v) => parseInt(v)],
66035
+ discount: ["discount", (v) => parseFloat(v)],
66036
+ subtotal: ["subtotal", (v) => parseFloat(v)],
66037
+ remark: ["remark", (v) => JSON.stringify(v)],
66038
+ sequence: ["sequence", (v) => parseInt(v)],
66039
+ periodFrom: ["period_from", (v) => JSON.stringify(v)],
66040
+ periodTo: ["period_to", (v) => JSON.stringify(v)],
66041
+ clientserviceId: ["clientservice_id", (v) => parseInt(v)],
66042
+ quotationId: ["quotation_id", (v) => parseInt(v)]
66043
+ };
66044
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
66045
+ if (fields.length === 0) {
66046
+ console.error("No fields to update. Provide at least one option.");
66047
+ process.exit(1);
66048
+ }
66049
+ const mutation = gql`
66050
+ mutation {
66051
+ updatePaymentPeriod(id: ${parseInt(id)}, data: { ${fields.join(", ")} })
66052
+ }
66053
+ `;
66054
+ try {
66055
+ const data = await client.request(mutation);
66056
+ if (data?.updatePaymentPeriod) {
66057
+ console.log(`Updated invoice item [${id}].`);
66058
+ } else {
66059
+ console.error("Update failed.");
66060
+ process.exit(1);
66061
+ }
66062
+ } catch (err) {
66063
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66064
+ console.error(`Failed to update invoice item: ${message}`);
66065
+ process.exit(1);
66066
+ }
66067
+ });
66068
+ items.command("delete <id>").description("Delete an invoice item by ID").action(async (id) => {
66069
+ const client = getClient();
66070
+ const mutation = gql`
66071
+ mutation {
66072
+ deletePaymentPeriod(id: ${parseInt(id)})
66073
+ }
66074
+ `;
66075
+ try {
66076
+ const data = await client.request(mutation);
66077
+ if (data?.deletePaymentPeriod) {
66078
+ console.log(`Deleted invoice item [${id}].`);
66079
+ } else {
66080
+ console.error("Delete failed.");
66081
+ process.exit(1);
66082
+ }
66083
+ } catch (err) {
66084
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66085
+ console.error(`Failed to delete invoice item: ${message}`);
66086
+ process.exit(1);
66087
+ }
66088
+ });
66089
+ }
66090
+ module2.exports = { register };
66091
+ }
66092
+ });
66093
+
66094
+ // src/client-services/index.js
66095
+ var require_client_services = __commonJS({
66096
+ "src/client-services/index.js"(exports2, module2) {
66097
+ var { GraphQLClient, gql } = require_main();
66098
+ var Conf2 = require_source();
66099
+ var config2 = new Conf2({ projectName: "hostlink-cli" });
66100
+ var ENDPOINT = "https://isapi.hostlink.com.hk/";
66101
+ function getClient() {
66102
+ const token = config2.get("token");
66103
+ if (!token) {
66104
+ console.error("No token found. Run `hostlink set-token <token>` first.");
66105
+ process.exit(1);
66106
+ }
66107
+ return new GraphQLClient(ENDPOINT, {
66108
+ headers: { Authorization: `Bearer ${token}` }
66109
+ });
66110
+ }
66111
+ function register(program2) {
66112
+ const cs = program2.command("client-services").description("Manage client services");
66113
+ cs.command("list").description("List client services").option("-c, --client <id>", "Filter by client ID").option("-s, --search <text>", "Filter by name (contains)").option("--service <id>", "Filter by service ID").option("-l, --limit <n>", "Max records to return", "50").option("-o, --offset <n>", "Records to skip", "0").option("--json", "Output as JSON").action(async (options) => {
66114
+ const client = getClient();
66115
+ const filterParts = [];
66116
+ if (options.client) filterParts.push(`client_id: ${parseInt(options.client)}`);
66117
+ if (options.service) filterParts.push(`service_id: ${parseInt(options.service)}`);
66118
+ if (options.search) filterParts.push(`name: { contains: ${JSON.stringify(options.search)} }`);
66119
+ const filters = filterParts.length ? `filters: { ${filterParts.join(", ")} }` : "";
66120
+ const pagination = `limit: ${parseInt(options.limit)}, offset: ${parseInt(options.offset)}`;
66121
+ const query = gql`
66122
+ query {
66123
+ listClientService${filters ? `(${filters})` : ""} {
66124
+ meta { total }
66125
+ data(${pagination}) {
66126
+ clientservice_id
66127
+ client_id
66128
+ name
66129
+ title
66130
+ unit_price
66131
+ unit_month
66132
+ unit_quantity
66133
+ discount
66134
+ join_date
66135
+ end_date
66136
+ paidPeriodTo
66137
+ paymentPeriodTo
66138
+ no_invoice
66139
+ clientservice_type
66140
+ }
66141
+ }
66142
+ }
66143
+ `;
66144
+ try {
66145
+ const data = await client.request(query);
66146
+ const list = data?.listClientService?.data ?? [];
66147
+ const total = data?.listClientService?.meta?.total ?? list.length;
66148
+ if (options.json) {
66149
+ console.log(JSON.stringify({ total, data: list }, null, 2));
66150
+ } else if (list.length === 0) {
66151
+ console.log("No client services found.");
66152
+ } else {
66153
+ list.forEach(
66154
+ (s) => console.log(`[${s.clientservice_id}] ${s.name ?? s.title ?? "-"} | client:${s.client_id} | $${s.unit_price ?? 0}/${s.unit_month ?? 1}mo | joined:${s.join_date ?? "-"} | paid to:${s.paidPeriodTo ?? "-"}${s.no_invoice ? " [no-invoice]" : ""}`)
66155
+ );
66156
+ console.log(`
66157
+ Total: ${total}`);
66158
+ }
66159
+ } catch (err) {
66160
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66161
+ console.error(`Failed to fetch client services: ${message}`);
66162
+ process.exit(1);
66163
+ }
66164
+ });
66165
+ cs.command("get <id>").description("Get a client service by ID").option("--json", "Output as JSON").action(async (id, options) => {
66166
+ const client = getClient();
66167
+ const query = gql`
66168
+ query {
66169
+ listClientService(filters: { clientservice_id: ${parseInt(id)} }) {
66170
+ data {
66171
+ clientservice_id
66172
+ client_id
66173
+ service_id
66174
+ domain_id
66175
+ quotation_id
66176
+ name
66177
+ title
66178
+ join_date
66179
+ end_date
66180
+ unit_price
66181
+ unit_month
66182
+ unit_quantity
66183
+ discount
66184
+ remark
66185
+ clientservice_type
66186
+ no_invoice
66187
+ renew_day
66188
+ termination_date
66189
+ termination_no
66190
+ termination_reason
66191
+ expect_termination_date
66192
+ paidPeriodTo
66193
+ paymentPeriodTo
66194
+ revenue
66195
+ lastPaymentPeriod {
66196
+ paymentperiod_id
66197
+ period_from
66198
+ period_to
66199
+ total
66200
+ }
66201
+ lastInvoice {
66202
+ invoice_id
66203
+ invoice_no
66204
+ invoice_date
66205
+ status
66206
+ total
66207
+ }
66208
+ }
66209
+ }
66210
+ }
66211
+ `;
66212
+ try {
66213
+ const data = await client.request(query);
66214
+ const list = data?.listClientService?.data ?? [];
66215
+ if (list.length === 0) {
66216
+ console.error(`Client service [${id}] not found.`);
66217
+ process.exit(1);
66218
+ }
66219
+ const s = list[0];
66220
+ if (options.json) {
66221
+ console.log(JSON.stringify(s, null, 2));
66222
+ } else {
66223
+ console.log(`ID: ${s.clientservice_id}`);
66224
+ console.log(`Client ID: ${s.client_id}`);
66225
+ console.log(`Service ID: ${s.service_id ?? "-"}`);
66226
+ console.log(`Domain ID: ${s.domain_id ?? "-"}`);
66227
+ console.log(`Quotation ID: ${s.quotation_id ?? "-"}`);
66228
+ console.log(`Name: ${s.name ?? "-"}`);
66229
+ console.log(`Title: ${s.title ?? "-"}`);
66230
+ console.log(`Join Date: ${s.join_date ?? "-"}`);
66231
+ console.log(`End Date: ${s.end_date ?? "-"}`);
66232
+ console.log(`Unit Price: $${s.unit_price ?? 0}`);
66233
+ console.log(`Unit Month: ${s.unit_month ?? 1}`);
66234
+ console.log(`Unit Quantity: ${s.unit_quantity ?? 1}`);
66235
+ console.log(`Discount: ${s.discount ?? 0}%`);
66236
+ console.log(`Renew Day: ${s.renew_day ?? "-"}`);
66237
+ console.log(`Type: ${s.clientservice_type ?? "-"}`);
66238
+ console.log(`No Invoice: ${s.no_invoice ? "Yes" : "No"}`);
66239
+ console.log(`Paid Period To: ${s.paidPeriodTo ?? "-"}`);
66240
+ console.log(`Payment Period To: ${s.paymentPeriodTo ?? "-"}`);
66241
+ console.log(`Revenue: $${s.revenue ?? 0}`);
66242
+ if (s.remark) console.log(`Remark: ${s.remark}`);
66243
+ if (s.termination_date) {
66244
+ console.log(`Termination Date: ${s.termination_date}`);
66245
+ console.log(`Termination Reason: ${s.termination_reason ?? "-"}`);
66246
+ }
66247
+ if (s.lastInvoice) {
66248
+ const inv = s.lastInvoice;
66249
+ console.log(`
66250
+ Last Invoice: [${inv.invoice_id}] #${inv.invoice_no ?? "-"} | ${inv.invoice_date ?? "-"} | ${inv.status ?? "-"} | $${inv.total ?? 0}`);
66251
+ }
66252
+ if (s.lastPaymentPeriod) {
66253
+ const pp = s.lastPaymentPeriod;
66254
+ console.log(`Last Payment Period: [${pp.paymentperiod_id}] ${pp.period_from ?? "-"} \u2192 ${pp.period_to ?? "-"} | $${pp.total ?? 0}`);
66255
+ }
66256
+ }
66257
+ } catch (err) {
66258
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66259
+ console.error(`Failed to fetch client service: ${message}`);
66260
+ process.exit(1);
66261
+ }
66262
+ });
66263
+ cs.command("add").description("Add a new client service").requiredOption("-c, --client-id <id>", "Client ID").option("--service-id <n>", "Service ID").option("--domain-id <n>", "Domain ID").option("--quotation-id <n>", "Linked quotation ID").option("--quotationitem-id <n>", "Linked quotation item ID").option("--name <value>", "Service name").option("--title <value>", "Display title").option("--join-date <date>", "Start date (YYYY-MM-DD)").option("--end-date <date>", "End date (YYYY-MM-DD)").option("--unit-price <n>", "Unit price").option("--unit-month <n>", "Billing cycle in months").option("--unit-quantity <n>", "Quantity").option("--discount <n>", "Discount percentage").option("--remark <value>", "Remark").option("--clientservice-type <n>", "Service type (int)").option("--renew-day <n>", "Renewal day").option("--no-invoice", "Exclude from invoicing").option("--server-id <n>", "Server ID").action(async (options) => {
66264
+ const client = getClient();
66265
+ const fieldMap = {
66266
+ clientId: ["client_id", (v) => parseInt(v)],
66267
+ serviceId: ["service_id", (v) => parseInt(v)],
66268
+ domainId: ["domain_id", (v) => parseInt(v)],
66269
+ quotationId: ["quotation_id", (v) => parseInt(v)],
66270
+ quotationitemId: ["quotationitem_id", (v) => parseInt(v)],
66271
+ name: ["name", (v) => JSON.stringify(v)],
66272
+ title: ["title", (v) => JSON.stringify(v)],
66273
+ joinDate: ["join_date", (v) => JSON.stringify(v)],
66274
+ endDate: ["end_date", (v) => JSON.stringify(v)],
66275
+ unitPrice: ["unit_price", (v) => parseFloat(v)],
66276
+ unitMonth: ["unit_month", (v) => parseInt(v)],
66277
+ unitQuantity: ["unit_quantity", (v) => parseFloat(v)],
66278
+ discount: ["discount", (v) => parseFloat(v)],
66279
+ remark: ["remark", (v) => JSON.stringify(v)],
66280
+ clientserviceType: ["clientservice_type", (v) => parseInt(v)],
66281
+ renewDay: ["renew_day", (v) => parseInt(v)],
66282
+ serverId: ["server_id", (v) => parseInt(v)]
66283
+ };
66284
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
66285
+ if (options.noInvoice) fields.push("no_invoice: true");
66286
+ const mutation = gql`
66287
+ mutation {
66288
+ addClientService(data: { ${fields.join(", ")} })
66289
+ }
66290
+ `;
66291
+ try {
66292
+ const data = await client.request(mutation);
66293
+ const newId = data?.addClientService;
66294
+ console.log(`Created client service [${newId}].`);
66295
+ } catch (err) {
66296
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66297
+ console.error(`Failed to add client service: ${message}`);
66298
+ process.exit(1);
66299
+ }
66300
+ });
66301
+ cs.command("update <id>").description("Update a client service by ID").option("--client-id <id>", "Client ID").option("--service-id <n>", "Service ID").option("--domain-id <n>", "Domain ID").option("--quotation-id <n>", "Linked quotation ID").option("--name <value>", "Service name").option("--title <value>", "Display title").option("--join-date <date>", "Start date (YYYY-MM-DD)").option("--end-date <date>", "End date (YYYY-MM-DD)").option("--unit-price <n>", "Unit price").option("--unit-month <n>", "Billing cycle in months").option("--unit-quantity <n>", "Quantity").option("--discount <n>", "Discount percentage").option("--remark <value>", "Remark").option("--clientservice-type <n>", "Service type (int)").option("--renew-day <n>", "Renewal day").option("--termination-date <date>", "Termination date (YYYY-MM-DD)").option("--termination-no <n>", "Termination number").option("--termination-reason <value>", "Termination reason").option("--expect-termination-date <date>", "Expected termination date (YYYY-MM-DD)").option("--no-invoice", "Exclude from invoicing").action(async (id, options) => {
66302
+ const client = getClient();
66303
+ const fieldMap = {
66304
+ clientId: ["client_id", (v) => parseInt(v)],
66305
+ serviceId: ["service_id", (v) => parseInt(v)],
66306
+ domainId: ["domain_id", (v) => parseInt(v)],
66307
+ quotationId: ["quotation_id", (v) => parseInt(v)],
66308
+ name: ["name", (v) => JSON.stringify(v)],
66309
+ title: ["title", (v) => JSON.stringify(v)],
66310
+ joinDate: ["join_date", (v) => JSON.stringify(v)],
66311
+ endDate: ["end_date", (v) => JSON.stringify(v)],
66312
+ unitPrice: ["unit_price", (v) => parseFloat(v)],
66313
+ unitMonth: ["unit_month", (v) => parseInt(v)],
66314
+ unitQuantity: ["unit_quantity", (v) => parseFloat(v)],
66315
+ discount: ["discount", (v) => parseFloat(v)],
66316
+ remark: ["remark", (v) => JSON.stringify(v)],
66317
+ clientserviceType: ["clientservice_type", (v) => parseInt(v)],
66318
+ renewDay: ["renew_day", (v) => parseInt(v)],
66319
+ terminationDate: ["termination_date", (v) => JSON.stringify(v)],
66320
+ terminationNo: ["termination_no", (v) => parseInt(v)],
66321
+ terminationReason: ["termination_reason", (v) => JSON.stringify(v)],
66322
+ expectTerminationDate: ["expect_termination_date", (v) => JSON.stringify(v)]
66323
+ };
66324
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
66325
+ if (options.noInvoice) fields.push("no_invoice: true");
66326
+ if (fields.length === 0) {
66327
+ console.error("No fields to update. Provide at least one option.");
66328
+ process.exit(1);
66329
+ }
66330
+ const mutation = gql`
66331
+ mutation {
66332
+ updateClientService(id: ${parseInt(id)}, data: { ${fields.join(", ")} })
66333
+ }
66334
+ `;
66335
+ try {
66336
+ const data = await client.request(mutation);
66337
+ if (data?.updateClientService) {
66338
+ console.log(`Updated client service [${id}].`);
66339
+ } else {
66340
+ console.error("Update failed.");
66341
+ process.exit(1);
66342
+ }
66343
+ } catch (err) {
66344
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66345
+ console.error(`Failed to update client service: ${message}`);
66346
+ process.exit(1);
66347
+ }
66348
+ });
66349
+ cs.command("delete <id>").description("Delete a client service by ID").action(async (id) => {
66350
+ const client = getClient();
66351
+ const mutation = gql`
66352
+ mutation {
66353
+ deleteClientService(id: ${parseInt(id)})
66354
+ }
66355
+ `;
66356
+ try {
66357
+ const data = await client.request(mutation);
66358
+ if (data?.deleteClientService) {
66359
+ console.log(`Deleted client service [${id}].`);
66360
+ } else {
66361
+ console.error("Delete failed.");
66362
+ process.exit(1);
66363
+ }
66364
+ } catch (err) {
66365
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66366
+ console.error(`Failed to delete client service: ${message}`);
66367
+ process.exit(1);
66368
+ }
66369
+ });
66370
+ }
66371
+ module2.exports = { register };
66372
+ }
66373
+ });
66374
+
65361
66375
  // package.json
65362
66376
  var require_package = __commonJS({
65363
66377
  "package.json"(exports2, module2) {
65364
66378
  module2.exports = {
65365
66379
  name: "@hostlink/hostlink-cli",
65366
- version: "1.0.3",
66380
+ version: "1.0.5",
65367
66381
  description: "CLI tool for the HostLink platform",
65368
66382
  main: "index.js",
65369
66383
  bin: {
@@ -65408,10 +66422,14 @@ var { Command } = require_commander();
65408
66422
  var Conf = require_source();
65409
66423
  var clients = require_clients();
65410
66424
  var domains = require_domains();
66425
+ var domainPasswords = require_domain_passwords();
65411
66426
  var quotations = require_quotations();
65412
66427
  var quotationItems = require_quotation_items();
65413
66428
  var me = require_me();
65414
66429
  var leave = require_leave();
66430
+ var invoices = require_invoices();
66431
+ var invoiceItems = require_invoice_items();
66432
+ var clientServices = require_client_services();
65415
66433
  var config = new Conf({ projectName: "hostlink-cli" });
65416
66434
  var program = new Command();
65417
66435
  program.name("hostlink").description("HostLink CLI").version(require_package().version);
@@ -65422,9 +66440,13 @@ program.command("set-token <token>").description("Save your access token").actio
65422
66440
  me.register(program);
65423
66441
  clients.register(program);
65424
66442
  domains.register(program);
66443
+ domainPasswords.register(program);
65425
66444
  quotations.register(program);
65426
66445
  quotationItems.register(program);
65427
66446
  leave.register(program);
66447
+ invoices.register(program);
66448
+ invoiceItems.register(program);
66449
+ clientServices.register(program);
65428
66450
  program.parse(process.argv);
65429
66451
  /*! Bundled license information:
65430
66452
 
package/index.js CHANGED
@@ -4,10 +4,14 @@ const { Command } = require('commander');
4
4
  const Conf = require('conf');
5
5
  const clients = require('./src/clients');
6
6
  const domains = require('./src/domains');
7
+ const domainPasswords = require('./src/domain-passwords');
7
8
  const quotations = require('./src/quotations');
8
9
  const quotationItems = require('./src/quotation-items');
9
10
  const me = require('./src/me');
10
11
  const leave = require('./src/leave');
12
+ const invoices = require('./src/invoices');
13
+ const invoiceItems = require('./src/invoice-items');
14
+ const clientServices = require('./src/client-services');
11
15
 
12
16
  const config = new Conf({ projectName: 'hostlink-cli' });
13
17
 
@@ -29,8 +33,12 @@ program
29
33
  me.register(program);
30
34
  clients.register(program);
31
35
  domains.register(program);
36
+ domainPasswords.register(program);
32
37
  quotations.register(program);
33
38
  quotationItems.register(program);
34
39
  leave.register(program);
40
+ invoices.register(program);
41
+ invoiceItems.register(program);
42
+ clientServices.register(program);
35
43
 
36
44
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hostlink/hostlink-cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "CLI tool for the HostLink platform",
5
5
  "main": "index.js",
6
6
  "bin": {