@hostlink/hostlink-cli 1.0.3 → 1.0.4

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 +1046 -10
  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) {
@@ -65358,12 +65562,836 @@ var require_leave = __commonJS({
65358
65562
  }
65359
65563
  });
65360
65564
 
65565
+ // src/invoices/index.js
65566
+ var require_invoices = __commonJS({
65567
+ "src/invoices/index.js"(exports2, module2) {
65568
+ var { GraphQLClient, gql } = require_main();
65569
+ var Conf2 = require_source();
65570
+ var config2 = new Conf2({ projectName: "hostlink-cli" });
65571
+ var ENDPOINT = "https://isapi.hostlink.com.hk/";
65572
+ function getClient() {
65573
+ const token = config2.get("token");
65574
+ if (!token) {
65575
+ console.error("No token found. Run `hostlink set-token <token>` first.");
65576
+ process.exit(1);
65577
+ }
65578
+ return new GraphQLClient(ENDPOINT, {
65579
+ headers: { Authorization: `Bearer ${token}` }
65580
+ });
65581
+ }
65582
+ function register(program2) {
65583
+ const invoices2 = program2.command("invoices").description("Manage invoices");
65584
+ 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) => {
65585
+ const client = getClient();
65586
+ const filterParts = [];
65587
+ if (options.client) filterParts.push(`client_id: ${parseInt(options.client)}`);
65588
+ if (options.search) filterParts.push(`client_name: { contains: ${JSON.stringify(options.search)} }`);
65589
+ if (options.status) filterParts.push(`status: ${JSON.stringify(options.status)}`);
65590
+ const filters = filterParts.length ? `filters: { ${filterParts.join(", ")} }` : "";
65591
+ const pagination = `limit: ${parseInt(options.limit)}, offset: ${parseInt(options.offset)}`;
65592
+ const query = gql`
65593
+ query {
65594
+ listInvoice${filters ? `(${filters})` : ""} {
65595
+ meta { total }
65596
+ data(${pagination}) {
65597
+ invoice_id
65598
+ invoice_no
65599
+ invoice_date
65600
+ due_date
65601
+ client_id
65602
+ client_name
65603
+ status
65604
+ total
65605
+ paidTotal
65606
+ }
65607
+ }
65608
+ }
65609
+ `;
65610
+ try {
65611
+ const data = await client.request(query);
65612
+ const list = data?.listInvoice?.data ?? [];
65613
+ const total = data?.listInvoice?.meta?.total ?? list.length;
65614
+ if (options.json) {
65615
+ console.log(JSON.stringify({ total, data: list }, null, 2));
65616
+ } else if (list.length === 0) {
65617
+ console.log("No invoices found.");
65618
+ } else {
65619
+ list.forEach(
65620
+ (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}`)
65621
+ );
65622
+ console.log(`
65623
+ Total: ${total}`);
65624
+ }
65625
+ } catch (err) {
65626
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65627
+ console.error(`Failed to fetch invoices: ${message}`);
65628
+ process.exit(1);
65629
+ }
65630
+ });
65631
+ invoices2.command("get <id>").description("Get an invoice by ID").option("--json", "Output as JSON").action(async (id, options) => {
65632
+ const client = getClient();
65633
+ const query = gql`
65634
+ query {
65635
+ listInvoice(filters: { invoice_id: ${parseInt(id)} }) {
65636
+ data {
65637
+ invoice_id
65638
+ invoice_no
65639
+ invoice_date
65640
+ due_date
65641
+ client_id
65642
+ client_name
65643
+ attn
65644
+ phone
65645
+ email
65646
+ fax
65647
+ addr1
65648
+ addr2
65649
+ addr3
65650
+ city
65651
+ status
65652
+ total
65653
+ paidTotal
65654
+ invoice_remark
65655
+ invoice_type
65656
+ invoice_send_via
65657
+ void_date
65658
+ items {
65659
+ paymentperiod_id
65660
+ description
65661
+ unit_price
65662
+ qty
65663
+ discount
65664
+ total
65665
+ remark
65666
+ period_from
65667
+ period_to
65668
+ }
65669
+ }
65670
+ }
65671
+ }
65672
+ `;
65673
+ try {
65674
+ const data = await client.request(query);
65675
+ const list = data?.listInvoice?.data ?? [];
65676
+ if (list.length === 0) {
65677
+ console.error(`Invoice [${id}] not found.`);
65678
+ process.exit(1);
65679
+ }
65680
+ const inv = list[0];
65681
+ if (options.json) {
65682
+ console.log(JSON.stringify(inv, null, 2));
65683
+ } else {
65684
+ console.log(`ID: ${inv.invoice_id}`);
65685
+ console.log(`No: ${inv.invoice_no ?? "-"}`);
65686
+ console.log(`Client ID: ${inv.client_id}`);
65687
+ console.log(`Client: ${inv.client_name ?? "-"}`);
65688
+ console.log(`Attn: ${inv.attn ?? "-"}`);
65689
+ console.log(`Email: ${inv.email ?? "-"}`);
65690
+ console.log(`Phone: ${inv.phone ?? "-"}`);
65691
+ console.log(`Address: ${[inv.addr1, inv.addr2, inv.addr3, inv.city].filter(Boolean).join(", ") || "-"}`);
65692
+ console.log(`Date: ${inv.invoice_date ?? "-"}`);
65693
+ console.log(`Due Date: ${inv.due_date ?? "-"}`);
65694
+ console.log(`Status: ${inv.status ?? "-"}`);
65695
+ console.log(`Total: $${inv.total ?? 0}`);
65696
+ console.log(`Paid: $${inv.paidTotal ?? 0}`);
65697
+ if (inv.void_date) console.log(`Void Date: ${inv.void_date}`);
65698
+ if (inv.invoice_remark) console.log(`Remark: ${inv.invoice_remark}`);
65699
+ if (inv.items?.length) {
65700
+ console.log("\n--- Items ---");
65701
+ inv.items.forEach((item, i) => {
65702
+ console.log(` [${i + 1}] ${item.description ?? "-"} | qty:${item.qty ?? 1} x $${item.unit_price ?? 0} | total:$${item.total ?? 0}${item.remark ? ` | ${item.remark}` : ""}`);
65703
+ });
65704
+ }
65705
+ }
65706
+ } catch (err) {
65707
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65708
+ console.error(`Failed to fetch invoice: ${message}`);
65709
+ process.exit(1);
65710
+ }
65711
+ });
65712
+ 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) => {
65713
+ const client = getClient();
65714
+ const fieldMap = {
65715
+ clientId: ["client_id", (v) => parseInt(v)],
65716
+ clientName: ["client_name", (v) => JSON.stringify(v)],
65717
+ invoiceDate: ["invoice_date", (v) => JSON.stringify(v)],
65718
+ dueDate: ["due_date", (v) => JSON.stringify(v)],
65719
+ attn: ["attn", (v) => JSON.stringify(v)],
65720
+ email: ["email", (v) => JSON.stringify(v)],
65721
+ phone: ["phone", (v) => JSON.stringify(v)],
65722
+ addr1: ["addr1", (v) => JSON.stringify(v)],
65723
+ addr2: ["addr2", (v) => JSON.stringify(v)],
65724
+ addr3: ["addr3", (v) => JSON.stringify(v)],
65725
+ city: ["city", (v) => JSON.stringify(v)],
65726
+ invoiceRemark: ["invoice_remark", (v) => JSON.stringify(v)],
65727
+ invoiceType: ["invoice_type", (v) => parseInt(v)],
65728
+ invoiceSendVia: ["invoice_send_via", (v) => parseInt(v)],
65729
+ quotationIds: ["quotation_ids", (v) => JSON.stringify(v)]
65730
+ };
65731
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
65732
+ const mutation = gql`
65733
+ mutation {
65734
+ addInvoice(data: { ${fields.join(", ")} })
65735
+ }
65736
+ `;
65737
+ try {
65738
+ const data = await client.request(mutation);
65739
+ const newId = data?.addInvoice;
65740
+ console.log(`Created invoice [${newId}].`);
65741
+ } catch (err) {
65742
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65743
+ console.error(`Failed to add invoice: ${message}`);
65744
+ process.exit(1);
65745
+ }
65746
+ });
65747
+ 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) => {
65748
+ const client = getClient();
65749
+ const fieldMap = {
65750
+ clientId: ["client_id", (v) => parseInt(v)],
65751
+ clientName: ["client_name", (v) => JSON.stringify(v)],
65752
+ invoiceDate: ["invoice_date", (v) => JSON.stringify(v)],
65753
+ dueDate: ["due_date", (v) => JSON.stringify(v)],
65754
+ attn: ["attn", (v) => JSON.stringify(v)],
65755
+ email: ["email", (v) => JSON.stringify(v)],
65756
+ phone: ["phone", (v) => JSON.stringify(v)],
65757
+ addr1: ["addr1", (v) => JSON.stringify(v)],
65758
+ addr2: ["addr2", (v) => JSON.stringify(v)],
65759
+ addr3: ["addr3", (v) => JSON.stringify(v)],
65760
+ city: ["city", (v) => JSON.stringify(v)],
65761
+ invoiceRemark: ["invoice_remark", (v) => JSON.stringify(v)],
65762
+ invoiceType: ["invoice_type", (v) => parseInt(v)],
65763
+ invoiceSendVia: ["invoice_send_via", (v) => parseInt(v)],
65764
+ voidDate: ["void_date", (v) => JSON.stringify(v)]
65765
+ };
65766
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
65767
+ if (fields.length === 0) {
65768
+ console.error("No fields to update. Provide at least one option.");
65769
+ process.exit(1);
65770
+ }
65771
+ const mutation = gql`
65772
+ mutation {
65773
+ updateInvoice(id: ${parseInt(id)}, data: { ${fields.join(", ")} })
65774
+ }
65775
+ `;
65776
+ try {
65777
+ const data = await client.request(mutation);
65778
+ if (data?.updateInvoice) {
65779
+ console.log(`Updated invoice [${id}].`);
65780
+ } else {
65781
+ console.error("Update failed.");
65782
+ process.exit(1);
65783
+ }
65784
+ } catch (err) {
65785
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65786
+ console.error(`Failed to update invoice: ${message}`);
65787
+ process.exit(1);
65788
+ }
65789
+ });
65790
+ invoices2.command("delete <id>").description("Delete an invoice by ID").action(async (id) => {
65791
+ const client = getClient();
65792
+ const mutation = gql`
65793
+ mutation {
65794
+ deleteInvoice(id: ${parseInt(id)})
65795
+ }
65796
+ `;
65797
+ try {
65798
+ const data = await client.request(mutation);
65799
+ if (data?.deleteInvoice) {
65800
+ console.log(`Deleted invoice [${id}].`);
65801
+ } else {
65802
+ console.error("Delete failed.");
65803
+ process.exit(1);
65804
+ }
65805
+ } catch (err) {
65806
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65807
+ console.error(`Failed to delete invoice: ${message}`);
65808
+ process.exit(1);
65809
+ }
65810
+ });
65811
+ 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) => {
65812
+ const client = getClient();
65813
+ const query = gql`
65814
+ query {
65815
+ listInvoice(filters: { invoice_id: ${parseInt(id)} }) {
65816
+ data {
65817
+ invoice_id
65818
+ invoice_no
65819
+ pdfLink
65820
+ downloadUrl
65821
+ }
65822
+ }
65823
+ }
65824
+ `;
65825
+ try {
65826
+ const data = await client.request(query);
65827
+ const list = data?.listInvoice?.data ?? [];
65828
+ if (list.length === 0) {
65829
+ console.error(`Invoice [${id}] not found.`);
65830
+ process.exit(1);
65831
+ }
65832
+ const inv = list[0];
65833
+ const link = inv.pdfLink || inv.downloadUrl;
65834
+ if (!link) {
65835
+ console.error("No PDF link available for this invoice.");
65836
+ process.exit(1);
65837
+ }
65838
+ if (options.save !== void 0) {
65839
+ const https = require("https");
65840
+ const http = require("http");
65841
+ const fs = require("fs");
65842
+ const path = require("path");
65843
+ const filename = typeof options.save === "string" ? options.save : `invoice-${inv.invoice_no ?? id}.pdf`;
65844
+ const filepath = path.resolve(filename);
65845
+ const protocol = link.startsWith("https") ? https : http;
65846
+ const token = config2.get("token");
65847
+ const urlObj = new URL(link);
65848
+ const reqOptions = {
65849
+ hostname: urlObj.hostname,
65850
+ path: urlObj.pathname + urlObj.search,
65851
+ headers: { Authorization: `Bearer ${token}` }
65852
+ };
65853
+ console.log(`Downloading to: ${filepath}`);
65854
+ const file = fs.createWriteStream(filepath);
65855
+ protocol.get(reqOptions, (res) => {
65856
+ if (res.statusCode !== 200) {
65857
+ console.error(`Download failed: HTTP ${res.statusCode}`);
65858
+ fs.unlinkSync(filepath);
65859
+ process.exit(1);
65860
+ }
65861
+ res.pipe(file);
65862
+ file.on("finish", () => {
65863
+ file.close();
65864
+ console.log(`Saved: ${filepath}`);
65865
+ });
65866
+ }).on("error", (err) => {
65867
+ fs.unlinkSync(filepath);
65868
+ console.error(`Download error: ${err.message}`);
65869
+ process.exit(1);
65870
+ });
65871
+ } else {
65872
+ console.log(`Invoice: [${inv.invoice_id}] #${inv.invoice_no ?? ""}`);
65873
+ console.log(`PDF Link: ${link}`);
65874
+ }
65875
+ } catch (err) {
65876
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65877
+ console.error(`Failed to fetch PDF link: ${message}`);
65878
+ process.exit(1);
65879
+ }
65880
+ });
65881
+ }
65882
+ module2.exports = { register };
65883
+ }
65884
+ });
65885
+
65886
+ // src/invoice-items/index.js
65887
+ var require_invoice_items = __commonJS({
65888
+ "src/invoice-items/index.js"(exports2, module2) {
65889
+ var { GraphQLClient, gql } = require_main();
65890
+ var Conf2 = require_source();
65891
+ var config2 = new Conf2({ projectName: "hostlink-cli" });
65892
+ var ENDPOINT = "https://isapi.hostlink.com.hk/";
65893
+ function getClient() {
65894
+ const token = config2.get("token");
65895
+ if (!token) {
65896
+ console.error("No token found. Run `hostlink set-token <token>` first.");
65897
+ process.exit(1);
65898
+ }
65899
+ return new GraphQLClient(ENDPOINT, {
65900
+ headers: { Authorization: `Bearer ${token}` }
65901
+ });
65902
+ }
65903
+ function register(program2) {
65904
+ const items = program2.command("invoice-items").description("Manage invoice items (PaymentPeriod)");
65905
+ items.command("list <invoice_id>").description("List items of an invoice").option("--json", "Output as JSON").action(async (invoice_id, options) => {
65906
+ const client = getClient();
65907
+ const query = gql`
65908
+ query {
65909
+ listPaymentPeriod(filters: { invoice_id: ${parseInt(invoice_id)} }) {
65910
+ data {
65911
+ paymentperiod_id
65912
+ invoice_id
65913
+ sequence
65914
+ description
65915
+ unit_price
65916
+ qty
65917
+ free_month
65918
+ discount
65919
+ subtotal
65920
+ total
65921
+ remark
65922
+ period_from
65923
+ period_to
65924
+ clientservice_id
65925
+ }
65926
+ }
65927
+ }
65928
+ `;
65929
+ try {
65930
+ const data = await client.request(query);
65931
+ const list = data?.listPaymentPeriod?.data ?? [];
65932
+ if (options.json) {
65933
+ console.log(JSON.stringify(list, null, 2));
65934
+ } else if (list.length === 0) {
65935
+ console.log("No items found.");
65936
+ } else {
65937
+ list.forEach(
65938
+ (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}` : ""}`)
65939
+ );
65940
+ console.log(`
65941
+ Total items: ${list.length}`);
65942
+ }
65943
+ } catch (err) {
65944
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
65945
+ console.error(`Failed to fetch invoice items: ${message}`);
65946
+ process.exit(1);
65947
+ }
65948
+ });
65949
+ items.command("get <id>").description("Get an invoice item by ID").option("--json", "Output as JSON").action(async (id, options) => {
65950
+ const client = getClient();
65951
+ const query = gql`
65952
+ query {
65953
+ listPaymentPeriod(filters: { paymentperiod_id: ${parseInt(id)} }) {
65954
+ data {
65955
+ paymentperiod_id
65956
+ invoice_id
65957
+ sequence
65958
+ description
65959
+ unit_price
65960
+ qty
65961
+ unit_month
65962
+ free_month
65963
+ discount
65964
+ subtotal
65965
+ total
65966
+ remark
65967
+ period_from
65968
+ period_to
65969
+ clientservice_id
65970
+ quotation_id
65971
+ }
65972
+ }
65973
+ }
65974
+ `;
65975
+ try {
65976
+ const data = await client.request(query);
65977
+ const list = data?.listPaymentPeriod?.data ?? [];
65978
+ if (list.length === 0) {
65979
+ console.error(`Invoice item [${id}] not found.`);
65980
+ process.exit(1);
65981
+ }
65982
+ const i = list[0];
65983
+ if (options.json) {
65984
+ console.log(JSON.stringify(i, null, 2));
65985
+ } else {
65986
+ console.log(`ID: ${i.paymentperiod_id}`);
65987
+ console.log(`Invoice ID: ${i.invoice_id}`);
65988
+ console.log(`Sequence: ${i.sequence ?? "-"}`);
65989
+ console.log(`Description: ${i.description ?? "-"}`);
65990
+ console.log(`Unit Price: $${i.unit_price ?? 0}`);
65991
+ console.log(`Qty: ${i.qty ?? "-"}`);
65992
+ console.log(`Unit Month: ${i.unit_month ?? 1}`);
65993
+ console.log(`Free Month: ${i.free_month ?? 0}`);
65994
+ console.log(`Discount: ${i.discount ?? 0}%`);
65995
+ console.log(`Subtotal: $${i.subtotal ?? 0}`);
65996
+ console.log(`Total: $${i.total ?? 0}`);
65997
+ console.log(`Period: ${i.period_from ?? "-"} \u2192 ${i.period_to ?? "-"}`);
65998
+ if (i.remark) console.log(`Remark: ${i.remark}`);
65999
+ if (i.clientservice_id) console.log(`Client Svc ID: ${i.clientservice_id}`);
66000
+ if (i.quotation_id) console.log(`Quotation ID: ${i.quotation_id}`);
66001
+ }
66002
+ } catch (err) {
66003
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66004
+ console.error(`Failed to fetch invoice item: ${message}`);
66005
+ process.exit(1);
66006
+ }
66007
+ });
66008
+ 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) => {
66009
+ const client = getClient();
66010
+ const fieldMap = {
66011
+ invoiceId: ["invoice_id", (v) => parseInt(v)],
66012
+ unitPrice: ["unit_price", (v) => parseFloat(v)],
66013
+ qty: ["qty", (v) => parseFloat(v)],
66014
+ unitMonth: ["unit_month", (v) => parseInt(v)],
66015
+ freeMonth: ["free_month", (v) => parseInt(v)],
66016
+ discount: ["discount", (v) => parseFloat(v)],
66017
+ subtotal: ["subtotal", (v) => parseFloat(v)],
66018
+ remark: ["remark", (v) => JSON.stringify(v)],
66019
+ sequence: ["sequence", (v) => parseInt(v)],
66020
+ periodFrom: ["period_from", (v) => JSON.stringify(v)],
66021
+ periodTo: ["period_to", (v) => JSON.stringify(v)],
66022
+ clientserviceId: ["clientservice_id", (v) => parseInt(v)],
66023
+ quotationId: ["quotation_id", (v) => parseInt(v)]
66024
+ };
66025
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
66026
+ const mutation = gql`
66027
+ mutation {
66028
+ addPaymentPeriod(data: { ${fields.join(", ")} })
66029
+ }
66030
+ `;
66031
+ try {
66032
+ const data = await client.request(mutation);
66033
+ const newId = data?.addPaymentPeriod;
66034
+ console.log(`Created invoice item [${newId}].`);
66035
+ } catch (err) {
66036
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66037
+ console.error(`Failed to add invoice item: ${message}`);
66038
+ process.exit(1);
66039
+ }
66040
+ });
66041
+ 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) => {
66042
+ const client = getClient();
66043
+ const fieldMap = {
66044
+ invoiceId: ["invoice_id", (v) => parseInt(v)],
66045
+ unitPrice: ["unit_price", (v) => parseFloat(v)],
66046
+ qty: ["qty", (v) => parseFloat(v)],
66047
+ unitMonth: ["unit_month", (v) => parseInt(v)],
66048
+ freeMonth: ["free_month", (v) => parseInt(v)],
66049
+ discount: ["discount", (v) => parseFloat(v)],
66050
+ subtotal: ["subtotal", (v) => parseFloat(v)],
66051
+ remark: ["remark", (v) => JSON.stringify(v)],
66052
+ sequence: ["sequence", (v) => parseInt(v)],
66053
+ periodFrom: ["period_from", (v) => JSON.stringify(v)],
66054
+ periodTo: ["period_to", (v) => JSON.stringify(v)],
66055
+ clientserviceId: ["clientservice_id", (v) => parseInt(v)],
66056
+ quotationId: ["quotation_id", (v) => parseInt(v)]
66057
+ };
66058
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
66059
+ if (fields.length === 0) {
66060
+ console.error("No fields to update. Provide at least one option.");
66061
+ process.exit(1);
66062
+ }
66063
+ const mutation = gql`
66064
+ mutation {
66065
+ updatePaymentPeriod(id: ${parseInt(id)}, data: { ${fields.join(", ")} })
66066
+ }
66067
+ `;
66068
+ try {
66069
+ const data = await client.request(mutation);
66070
+ if (data?.updatePaymentPeriod) {
66071
+ console.log(`Updated invoice item [${id}].`);
66072
+ } else {
66073
+ console.error("Update failed.");
66074
+ process.exit(1);
66075
+ }
66076
+ } catch (err) {
66077
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66078
+ console.error(`Failed to update invoice item: ${message}`);
66079
+ process.exit(1);
66080
+ }
66081
+ });
66082
+ items.command("delete <id>").description("Delete an invoice item by ID").action(async (id) => {
66083
+ const client = getClient();
66084
+ const mutation = gql`
66085
+ mutation {
66086
+ deletePaymentPeriod(id: ${parseInt(id)})
66087
+ }
66088
+ `;
66089
+ try {
66090
+ const data = await client.request(mutation);
66091
+ if (data?.deletePaymentPeriod) {
66092
+ console.log(`Deleted invoice item [${id}].`);
66093
+ } else {
66094
+ console.error("Delete failed.");
66095
+ process.exit(1);
66096
+ }
66097
+ } catch (err) {
66098
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66099
+ console.error(`Failed to delete invoice item: ${message}`);
66100
+ process.exit(1);
66101
+ }
66102
+ });
66103
+ }
66104
+ module2.exports = { register };
66105
+ }
66106
+ });
66107
+
66108
+ // src/client-services/index.js
66109
+ var require_client_services = __commonJS({
66110
+ "src/client-services/index.js"(exports2, module2) {
66111
+ var { GraphQLClient, gql } = require_main();
66112
+ var Conf2 = require_source();
66113
+ var config2 = new Conf2({ projectName: "hostlink-cli" });
66114
+ var ENDPOINT = "https://isapi.hostlink.com.hk/";
66115
+ function getClient() {
66116
+ const token = config2.get("token");
66117
+ if (!token) {
66118
+ console.error("No token found. Run `hostlink set-token <token>` first.");
66119
+ process.exit(1);
66120
+ }
66121
+ return new GraphQLClient(ENDPOINT, {
66122
+ headers: { Authorization: `Bearer ${token}` }
66123
+ });
66124
+ }
66125
+ function register(program2) {
66126
+ const cs = program2.command("client-services").description("Manage client services");
66127
+ 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) => {
66128
+ const client = getClient();
66129
+ const filterParts = [];
66130
+ if (options.client) filterParts.push(`client_id: ${parseInt(options.client)}`);
66131
+ if (options.service) filterParts.push(`service_id: ${parseInt(options.service)}`);
66132
+ if (options.search) filterParts.push(`name: { contains: ${JSON.stringify(options.search)} }`);
66133
+ const filters = filterParts.length ? `filters: { ${filterParts.join(", ")} }` : "";
66134
+ const pagination = `limit: ${parseInt(options.limit)}, offset: ${parseInt(options.offset)}`;
66135
+ const query = gql`
66136
+ query {
66137
+ listClientService${filters ? `(${filters})` : ""} {
66138
+ meta { total }
66139
+ data(${pagination}) {
66140
+ clientservice_id
66141
+ client_id
66142
+ name
66143
+ title
66144
+ unit_price
66145
+ unit_month
66146
+ unit_quantity
66147
+ discount
66148
+ join_date
66149
+ end_date
66150
+ paidPeriodTo
66151
+ paymentPeriodTo
66152
+ no_invoice
66153
+ clientservice_type
66154
+ }
66155
+ }
66156
+ }
66157
+ `;
66158
+ try {
66159
+ const data = await client.request(query);
66160
+ const list = data?.listClientService?.data ?? [];
66161
+ const total = data?.listClientService?.meta?.total ?? list.length;
66162
+ if (options.json) {
66163
+ console.log(JSON.stringify({ total, data: list }, null, 2));
66164
+ } else if (list.length === 0) {
66165
+ console.log("No client services found.");
66166
+ } else {
66167
+ list.forEach(
66168
+ (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]" : ""}`)
66169
+ );
66170
+ console.log(`
66171
+ Total: ${total}`);
66172
+ }
66173
+ } catch (err) {
66174
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66175
+ console.error(`Failed to fetch client services: ${message}`);
66176
+ process.exit(1);
66177
+ }
66178
+ });
66179
+ cs.command("get <id>").description("Get a client service by ID").option("--json", "Output as JSON").action(async (id, options) => {
66180
+ const client = getClient();
66181
+ const query = gql`
66182
+ query {
66183
+ listClientService(filters: { clientservice_id: ${parseInt(id)} }) {
66184
+ data {
66185
+ clientservice_id
66186
+ client_id
66187
+ service_id
66188
+ domain_id
66189
+ quotation_id
66190
+ name
66191
+ title
66192
+ join_date
66193
+ end_date
66194
+ unit_price
66195
+ unit_month
66196
+ unit_quantity
66197
+ discount
66198
+ remark
66199
+ clientservice_type
66200
+ no_invoice
66201
+ renew_day
66202
+ termination_date
66203
+ termination_no
66204
+ termination_reason
66205
+ expect_termination_date
66206
+ paidPeriodTo
66207
+ paymentPeriodTo
66208
+ revenue
66209
+ lastPaymentPeriod {
66210
+ paymentperiod_id
66211
+ period_from
66212
+ period_to
66213
+ total
66214
+ }
66215
+ lastInvoice {
66216
+ invoice_id
66217
+ invoice_no
66218
+ invoice_date
66219
+ status
66220
+ total
66221
+ }
66222
+ }
66223
+ }
66224
+ }
66225
+ `;
66226
+ try {
66227
+ const data = await client.request(query);
66228
+ const list = data?.listClientService?.data ?? [];
66229
+ if (list.length === 0) {
66230
+ console.error(`Client service [${id}] not found.`);
66231
+ process.exit(1);
66232
+ }
66233
+ const s = list[0];
66234
+ if (options.json) {
66235
+ console.log(JSON.stringify(s, null, 2));
66236
+ } else {
66237
+ console.log(`ID: ${s.clientservice_id}`);
66238
+ console.log(`Client ID: ${s.client_id}`);
66239
+ console.log(`Service ID: ${s.service_id ?? "-"}`);
66240
+ console.log(`Domain ID: ${s.domain_id ?? "-"}`);
66241
+ console.log(`Quotation ID: ${s.quotation_id ?? "-"}`);
66242
+ console.log(`Name: ${s.name ?? "-"}`);
66243
+ console.log(`Title: ${s.title ?? "-"}`);
66244
+ console.log(`Join Date: ${s.join_date ?? "-"}`);
66245
+ console.log(`End Date: ${s.end_date ?? "-"}`);
66246
+ console.log(`Unit Price: $${s.unit_price ?? 0}`);
66247
+ console.log(`Unit Month: ${s.unit_month ?? 1}`);
66248
+ console.log(`Unit Quantity: ${s.unit_quantity ?? 1}`);
66249
+ console.log(`Discount: ${s.discount ?? 0}%`);
66250
+ console.log(`Renew Day: ${s.renew_day ?? "-"}`);
66251
+ console.log(`Type: ${s.clientservice_type ?? "-"}`);
66252
+ console.log(`No Invoice: ${s.no_invoice ? "Yes" : "No"}`);
66253
+ console.log(`Paid Period To: ${s.paidPeriodTo ?? "-"}`);
66254
+ console.log(`Payment Period To: ${s.paymentPeriodTo ?? "-"}`);
66255
+ console.log(`Revenue: $${s.revenue ?? 0}`);
66256
+ if (s.remark) console.log(`Remark: ${s.remark}`);
66257
+ if (s.termination_date) {
66258
+ console.log(`Termination Date: ${s.termination_date}`);
66259
+ console.log(`Termination Reason: ${s.termination_reason ?? "-"}`);
66260
+ }
66261
+ if (s.lastInvoice) {
66262
+ const inv = s.lastInvoice;
66263
+ console.log(`
66264
+ Last Invoice: [${inv.invoice_id}] #${inv.invoice_no ?? "-"} | ${inv.invoice_date ?? "-"} | ${inv.status ?? "-"} | $${inv.total ?? 0}`);
66265
+ }
66266
+ if (s.lastPaymentPeriod) {
66267
+ const pp = s.lastPaymentPeriod;
66268
+ console.log(`Last Payment Period: [${pp.paymentperiod_id}] ${pp.period_from ?? "-"} \u2192 ${pp.period_to ?? "-"} | $${pp.total ?? 0}`);
66269
+ }
66270
+ }
66271
+ } catch (err) {
66272
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66273
+ console.error(`Failed to fetch client service: ${message}`);
66274
+ process.exit(1);
66275
+ }
66276
+ });
66277
+ 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) => {
66278
+ const client = getClient();
66279
+ const fieldMap = {
66280
+ clientId: ["client_id", (v) => parseInt(v)],
66281
+ serviceId: ["service_id", (v) => parseInt(v)],
66282
+ domainId: ["domain_id", (v) => parseInt(v)],
66283
+ quotationId: ["quotation_id", (v) => parseInt(v)],
66284
+ quotationitemId: ["quotationitem_id", (v) => parseInt(v)],
66285
+ name: ["name", (v) => JSON.stringify(v)],
66286
+ title: ["title", (v) => JSON.stringify(v)],
66287
+ joinDate: ["join_date", (v) => JSON.stringify(v)],
66288
+ endDate: ["end_date", (v) => JSON.stringify(v)],
66289
+ unitPrice: ["unit_price", (v) => parseFloat(v)],
66290
+ unitMonth: ["unit_month", (v) => parseInt(v)],
66291
+ unitQuantity: ["unit_quantity", (v) => parseFloat(v)],
66292
+ discount: ["discount", (v) => parseFloat(v)],
66293
+ remark: ["remark", (v) => JSON.stringify(v)],
66294
+ clientserviceType: ["clientservice_type", (v) => parseInt(v)],
66295
+ renewDay: ["renew_day", (v) => parseInt(v)],
66296
+ serverId: ["server_id", (v) => parseInt(v)]
66297
+ };
66298
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
66299
+ if (options.noInvoice) fields.push("no_invoice: true");
66300
+ const mutation = gql`
66301
+ mutation {
66302
+ addClientService(data: { ${fields.join(", ")} })
66303
+ }
66304
+ `;
66305
+ try {
66306
+ const data = await client.request(mutation);
66307
+ const newId = data?.addClientService;
66308
+ console.log(`Created client service [${newId}].`);
66309
+ } catch (err) {
66310
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66311
+ console.error(`Failed to add client service: ${message}`);
66312
+ process.exit(1);
66313
+ }
66314
+ });
66315
+ 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) => {
66316
+ const client = getClient();
66317
+ const fieldMap = {
66318
+ clientId: ["client_id", (v) => parseInt(v)],
66319
+ serviceId: ["service_id", (v) => parseInt(v)],
66320
+ domainId: ["domain_id", (v) => parseInt(v)],
66321
+ quotationId: ["quotation_id", (v) => parseInt(v)],
66322
+ name: ["name", (v) => JSON.stringify(v)],
66323
+ title: ["title", (v) => JSON.stringify(v)],
66324
+ joinDate: ["join_date", (v) => JSON.stringify(v)],
66325
+ endDate: ["end_date", (v) => JSON.stringify(v)],
66326
+ unitPrice: ["unit_price", (v) => parseFloat(v)],
66327
+ unitMonth: ["unit_month", (v) => parseInt(v)],
66328
+ unitQuantity: ["unit_quantity", (v) => parseFloat(v)],
66329
+ discount: ["discount", (v) => parseFloat(v)],
66330
+ remark: ["remark", (v) => JSON.stringify(v)],
66331
+ clientserviceType: ["clientservice_type", (v) => parseInt(v)],
66332
+ renewDay: ["renew_day", (v) => parseInt(v)],
66333
+ terminationDate: ["termination_date", (v) => JSON.stringify(v)],
66334
+ terminationNo: ["termination_no", (v) => parseInt(v)],
66335
+ terminationReason: ["termination_reason", (v) => JSON.stringify(v)],
66336
+ expectTerminationDate: ["expect_termination_date", (v) => JSON.stringify(v)]
66337
+ };
66338
+ const fields = Object.entries(fieldMap).filter(([optKey]) => options[optKey] != null).map(([optKey, [gqlKey, transform]]) => `${gqlKey}: ${transform(options[optKey])}`);
66339
+ if (options.noInvoice) fields.push("no_invoice: true");
66340
+ if (fields.length === 0) {
66341
+ console.error("No fields to update. Provide at least one option.");
66342
+ process.exit(1);
66343
+ }
66344
+ const mutation = gql`
66345
+ mutation {
66346
+ updateClientService(id: ${parseInt(id)}, data: { ${fields.join(", ")} })
66347
+ }
66348
+ `;
66349
+ try {
66350
+ const data = await client.request(mutation);
66351
+ if (data?.updateClientService) {
66352
+ console.log(`Updated client service [${id}].`);
66353
+ } else {
66354
+ console.error("Update failed.");
66355
+ process.exit(1);
66356
+ }
66357
+ } catch (err) {
66358
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66359
+ console.error(`Failed to update client service: ${message}`);
66360
+ process.exit(1);
66361
+ }
66362
+ });
66363
+ cs.command("delete <id>").description("Delete a client service by ID").action(async (id) => {
66364
+ const client = getClient();
66365
+ const mutation = gql`
66366
+ mutation {
66367
+ deleteClientService(id: ${parseInt(id)})
66368
+ }
66369
+ `;
66370
+ try {
66371
+ const data = await client.request(mutation);
66372
+ if (data?.deleteClientService) {
66373
+ console.log(`Deleted client service [${id}].`);
66374
+ } else {
66375
+ console.error("Delete failed.");
66376
+ process.exit(1);
66377
+ }
66378
+ } catch (err) {
66379
+ const message = err?.response?.errors?.[0]?.message ?? err.message;
66380
+ console.error(`Failed to delete client service: ${message}`);
66381
+ process.exit(1);
66382
+ }
66383
+ });
66384
+ }
66385
+ module2.exports = { register };
66386
+ }
66387
+ });
66388
+
65361
66389
  // package.json
65362
66390
  var require_package = __commonJS({
65363
66391
  "package.json"(exports2, module2) {
65364
66392
  module2.exports = {
65365
66393
  name: "@hostlink/hostlink-cli",
65366
- version: "1.0.3",
66394
+ version: "1.0.4",
65367
66395
  description: "CLI tool for the HostLink platform",
65368
66396
  main: "index.js",
65369
66397
  bin: {
@@ -65408,10 +66436,14 @@ var { Command } = require_commander();
65408
66436
  var Conf = require_source();
65409
66437
  var clients = require_clients();
65410
66438
  var domains = require_domains();
66439
+ var domainPasswords = require_domain_passwords();
65411
66440
  var quotations = require_quotations();
65412
66441
  var quotationItems = require_quotation_items();
65413
66442
  var me = require_me();
65414
66443
  var leave = require_leave();
66444
+ var invoices = require_invoices();
66445
+ var invoiceItems = require_invoice_items();
66446
+ var clientServices = require_client_services();
65415
66447
  var config = new Conf({ projectName: "hostlink-cli" });
65416
66448
  var program = new Command();
65417
66449
  program.name("hostlink").description("HostLink CLI").version(require_package().version);
@@ -65422,9 +66454,13 @@ program.command("set-token <token>").description("Save your access token").actio
65422
66454
  me.register(program);
65423
66455
  clients.register(program);
65424
66456
  domains.register(program);
66457
+ domainPasswords.register(program);
65425
66458
  quotations.register(program);
65426
66459
  quotationItems.register(program);
65427
66460
  leave.register(program);
66461
+ invoices.register(program);
66462
+ invoiceItems.register(program);
66463
+ clientServices.register(program);
65428
66464
  program.parse(process.argv);
65429
66465
  /*! Bundled license information:
65430
66466
 
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.4",
4
4
  "description": "CLI tool for the HostLink platform",
5
5
  "main": "index.js",
6
6
  "bin": {