const urlBuilder = require("./qbo").urlBuilder;
const StandardizeName = require("./qbo").StandardizeName;

const logger = require("../../utils/logger");
const apiGqlClient = require("../../graphql-client/graphql-client").client;
const queries = require("../../graphql-client/queries");
const { refresh: refreshOauthToken } = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const CreateInvoiceLines = require("../qb-receivables-lines").default;
const moment = require("moment-timezone");
const { generateOwnerTier } = require("../qbxml/qbxml-utils");
const { createMultiQbPayerLines } = require("../qb-receivables-lines");

exports.default = async (req, res) => {
  const oauthClient = new OAuthClient({
    clientId: process.env.QBO_CLIENT_ID,
    clientSecret: process.env.QBO_SECRET,
    environment: process.env.NODE_ENV === "production" ? "production" : "sandbox",
    redirectUri: process.env.QBO_REDIRECT_URI
  });

  try {
    //Fetch the API Access Tokens & Set them for the session.
    const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
      email: req.user.email
    });
    const { qbo_realmId } = response.associations[0];
    if (!qbo_realmId) {
      res.status(401).json({ error: "No company associated." });
      return;
    }
    oauthClient.setToken(response.associations[0].qbo_auth);

    await refreshOauthToken(oauthClient, req);

    const { jobIds, elgen } = req.body;
    //Query Job Info

    const BearerToken = req.BearerToken;
    const client = req.userGraphQLClient;

    logger.log("qbo-receivable-create", "DEBUG", req.user.email, jobIds);

    const result = await client
      .setHeaders({ Authorization: BearerToken })
      .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, {
        ids: jobIds
      });
    const { jobs, bodyshops } = result;
    const bodyshop = bodyshops[0];

    const ret = [];
    for (const job of jobs) {
      //const job = jobs[0];
      try {
        const isThreeTier = bodyshop.accountingconfig.tiers === 3;
        const twoTierPref = bodyshop.accountingconfig.twotierpref;

        //Replace this with a for-each loop to check every single Job that's included in the list.

        let insCoCustomerTier, ownerCustomerTier, jobTier;
        if (isThreeTier || (!isThreeTier && twoTierPref === "source")) {
          //Insert the insurance company tier.
          //Query for top level customer, the insurance company name.
          insCoCustomerTier = await QueryInsuranceCo(oauthClient, qbo_realmId, req, job);
          if (!insCoCustomerTier) {
            //Creating the Insurance Customer.
            insCoCustomerTier = await InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop);
          }
        }

        if (isThreeTier || (!isThreeTier && twoTierPref === "name")) {
          //Insert the name/owner and account for whether the source should be the ins co in 3 tier..
          ownerCustomerTier = await QueryOwner(oauthClient, qbo_realmId, req, job, insCoCustomerTier);
          //Query for the owner itself.
          if (!ownerCustomerTier) {
            ownerCustomerTier = await InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, insCoCustomerTier);
          }
        }

        //Query for the Job or Create it.
        jobTier = await QueryJob(
          oauthClient,
          qbo_realmId,
          req,
          job,
          isThreeTier ? ownerCustomerTier : twoTierPref === "source" ? insCoCustomerTier : ownerCustomerTier
        );

        // Need to validate that the job tier is associated to the right individual?

        if (!jobTier) {
          jobTier = await InsertJob(
            oauthClient,
            qbo_realmId,
            req,
            job,

            ownerCustomerTier || insCoCustomerTier
          );
        }

        if (!req.body.custDataOnly) {
          await InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, jobTier);

          if (job.qb_multiple_payers?.length > 0) {
            for (const [index, payer] of job.qb_multiple_payers.entries()) {
              //do the thing.

              //Create the source level.
              let insCoCustomerTier, jobTier;

              //Insert the insurance company tier.
              //Query for top level customer, the insurance company name.
              insCoCustomerTier = await QueryInsuranceCo(oauthClient, qbo_realmId, req, {
                ...job,
                ins_co_nm: payer.name
              });
              if (!insCoCustomerTier) {
                //Creating the Insurance Customer.
                insCoCustomerTier = await InsertInsuranceCo(
                  oauthClient,
                  qbo_realmId,
                  req,
                  { ...job, ins_co_nm: payer.name },
                  bodyshop
                );
              }
              //Query for the Job or Create it.
              jobTier = await QueryJob(oauthClient, qbo_realmId, req, job, insCoCustomerTier);
              // Need to validate that the job tier is associated to the right individual?
              if (!jobTier) {
                jobTier = await InsertJob(oauthClient, qbo_realmId, req, job, insCoCustomerTier);
              }

              //Create the RO level

              await InsertInvoiceMultiPayerInvoice(
                oauthClient,
                qbo_realmId,
                req,
                job,
                bodyshop,
                jobTier,
                payer,
                `-${index + 1}`
              );
            }
          }

          // //No error. Mark the job exported & insert export log.
          if (elgen) {
            await client.setHeaders({ Authorization: BearerToken }).request(queries.QBO_MARK_JOB_EXPORTED, {
              jobId: job.id,
              job: {
                status: bodyshop.md_ro_statuses.default_exported || "Exported*",
                date_exported: moment().tz(bodyshop.timezone)
              },
              logs: [
                {
                  bodyshopid: bodyshop.id,
                  jobid: job.id,
                  successful: true,
                  useremail: req.user.email
                }
              ]
            });
          }
        }
        ret.push({ jobid: job.id, success: true });
      } catch (error) {
        ret.push({
          jobid: job.id,
          success: false,
          errorMessage:
            error?.authResponse?.body ||
            error?.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
            error?.response?.data ||
            error?.message
        });
        //console.log(error);
        logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
          error: error.message,
          stack: error.stack
        });
        //Add the export log error.
        if (elgen) {
          await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_EXPORT_LOG, {
            logs: [
              {
                bodyshopid: bodyshop.id,
                jobid: job.id,
                successful: false,
                message: JSON.stringify([error?.authResponse?.body || error?.message]),
                useremail: req.user.email
              }
            ]
          });
        }
      }
    }

    res.status(200).json(ret);
  } catch (error) {
    //console.log(error);
    logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
      error: error.message,
      stack: error.stack
    });
    res.status(400).json(error);
  }
};

async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
  try {
    const url = urlBuilder(
      qbo_realmId,
      "query",
      `select * From Customer where DisplayName = '${StandardizeName(job.ins_co_nm.trim())}' and Active = true`
    );
    const result = await oauthClient.makeApiCall({
      url: url,
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      }
    });
    logger.LogIntegrationCall({
      platform: "QBO",
      method: "POST",
      name: "QueryCustomer",
      status: result.status,
      bodyshopid: job.shopid,
      jobid: job.id,
      email: req.user.email
    });
    logger.log("qbo-receivables-query", "DEBUG", req.user.email, job.id, {
      method: "QueryInsuranceCo",
      call: url,
      result: result.json
    });

    return result.json?.QueryResponse?.Customer?.[0];
  } catch (error) {
    logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
      error,
      method: "QueryInsuranceCo"
    });
    throw error;
  }
}

exports.QueryInsuranceCo = QueryInsuranceCo;

async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
  const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm);

  if (!insCo) {
    throw new Error(
      `Insurance Company '${job.ins_co_nm}' not found in shop configuration. Please make sure it exists or change the insurance company name on the job to one that exists.`
    );
  }
  const Customer = {
    DisplayName: job.ins_co_nm.trim(),
    BillWithParent: true,
    BillAddr: {
      City: job.ownr_city,
      Line1: insCo.street1,
      Line2: insCo.street2,
      PostalCode: insCo.zip,
      CountrySubDivisionCode: insCo.state
    }
  };
  try {
    const url = urlBuilder(qbo_realmId, "customer");
    const result = await oauthClient.makeApiCall({
      url: url,
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(Customer)
    });
    logger.LogIntegrationCall({
      platform: "QBO",
      method: "POST",
      name: "InsertCustomer",
      status: result.status,
      bodyshopid: job.shopid,
      jobid: job.id,
      email: req.user.email
    });
    logger.log("qbo-receivables-insert", "DEBUG", req.user.email, job.id, {
      method: "InsertInsuranceCo",
      call: url,
      customerObj: Customer,
      result: result.json
    });

    return result.json?.Customer;
  } catch (error) {
    logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
      error,
      method: "InsertInsuranceCo"
    });
    throw error;
  }
}

exports.InsertInsuranceCo = InsertInsuranceCo;

async function QueryOwner(oauthClient, qbo_realmId, req, job, parentTierRef) {
  const ownerName = generateOwnerTier(job, true, null);
  const url = urlBuilder(
    qbo_realmId,
    "query",
    `select * From Customer where DisplayName = '${StandardizeName(ownerName)}' and Active = true`
  );
  const result = await oauthClient.makeApiCall({
    url: url,
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    }
  });
  logger.LogIntegrationCall({
    platform: "QBO",
    method: "POST",
    name: "QueryCustomer",
    status: result.status,
    bodyshopid: job.shopid,
    jobid: job.id,
    email: req.user.email
  });
  logger.log("qbo-receivables-query", "DEBUG", req.user.email, job.id, {
    method: "QueryOwner",
    call: url,
    result: result.json
  });

  return result.json?.QueryResponse?.Customer?.find((x) => x.ParentRef?.value === parentTierRef?.Id);
}

exports.QueryOwner = QueryOwner;

async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, parentTierRef) {
  const ownerName = generateOwnerTier(job, true, null);
  const Customer = {
    DisplayName: ownerName,
    BillWithParent: true,
    BillAddr: {
      City: job.ownr_city,
      Line1: job.ownr_addr1,
      Line2: job.ownr_addr2,
      PostalCode: job.ownr_zip,
      CountrySubDivisionCode: job.ownr_st
    },
    ...(job.ownr_ea ? { PrimaryEmailAddr: { Address: job.ownr_ea.trim() } } : {}),
    ...(isThreeTier
      ? {
          Job: true,
          ParentRef: {
            value: parentTierRef.Id
          }
        }
      : {})
  };
  try {
    const url = urlBuilder(qbo_realmId, "customer");
    const result = await oauthClient.makeApiCall({
      url: url,
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(Customer)
    });
    logger.LogIntegrationCall({
      platform: "QBO",
      method: "POST",
      name: "InsertCustomer",
      status: result.status,
      bodyshopid: job.shopid,
      jobid: job.id,
      email: req.user.email
    });
    logger.log("qbo-receivables-insert", "DEBUG", req.user.email, job.id, {
      method: "InsertOwner",
      call: url,
      customerObj: Customer,
      result: result.json
    });

    return result.json?.Customer;
  } catch (error) {
    logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
      error,
      method: "InsertOwner"
    });
    throw error;
  }
}

exports.InsertOwner = InsertOwner;

async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
  const url = urlBuilder(
    qbo_realmId,
    "query",
    `select * From Customer where DisplayName = '${job.ro_number}' and Active = true`
  );
  const result = await oauthClient.makeApiCall({
    url: url,
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    }
  });
  logger.LogIntegrationCall({
    platform: "QBO",
    method: "POST",
    name: "QueryCustomer",
    status: result.status,
    bodyshopid: job.shopid,
    jobid: job.id,
    email: req.user.email
  });
  logger.log("qbo-receivables-query", "DEBUG", req.user.email, job.id, {
    method: "QueryJob",
    call: url,
    result: result.json
  });

  const customers = result.json?.QueryResponse?.Customer;
  return customers && (parentTierRef ? customers.find((x) => x.ParentRef.value === parentTierRef.Id) : customers[0]);
}

exports.QueryJob = QueryJob;

async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
  const Customer = {
    DisplayName: job.ro_number,
    BillWithParent: true,
    BillAddr: {
      City: job.ownr_city,
      Line1: job.ownr_addr1,
      Line2: job.ownr_addr2,
      PostalCode: job.ownr_zip,
      CountrySubDivisionCode: job.ownr_st
    },
    ...(job.ownr_ea ? { PrimaryEmailAddr: { Address: job.ownr_ea.trim() } } : {}),
    Job: true,
    ParentRef: {
      value: parentTierRef.Id
    }
  };
  try {
    const url = urlBuilder(qbo_realmId, "customer");
    const result = await oauthClient.makeApiCall({
      url: url,
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(Customer)
    });
    logger.LogIntegrationCall({
      platform: "QBO",
      method: "POST",
      name: "InsertCustomer",
      status: result.status,
      bodyshopid: job.shopid,
      jobid: job.id,
      email: req.user.email
    });
    logger.log("qbo-receivables-insert", "DEBUG", req.user.email, job.id, {
      method: "InsertJob",
      call: url,
      customerObj: Customer,
      result: result.json
    });

    if (result.status >= 400) {
      throw new Error(JSON.stringify(result.json.Fault));
    }
    if (result.status === 200) return result?.json.Customer;
  } catch (error) {
    logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
      method: "InsertOwner",
      validationError: error.message,
      stack: error.stack
    });
    throw error;
  }
}

exports.InsertJob = InsertJob;

async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) {
  const items = await oauthClient.makeApiCall({
    url: urlBuilder(qbo_realmId, "query", `select * From Item where active = true maxresults 1000`),
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    }
  });
  logger.LogIntegrationCall({
    platform: "QBO",
    method: "POST",
    name: "QueryItems",
    status: items.status,
    bodyshopid,
    jobid: jobid,
    email: req.user.email
  });

  const taxCodes = await oauthClient.makeApiCall({
    url: urlBuilder(qbo_realmId, "query", `select * From TaxCode where active = true`),
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    }
  });
  logger.LogIntegrationCall({
    platform: "QBO",
    method: "POST",
    name: "QueryTaxCodes",
    status: taxCodes.status,
    bodyshopid,
    jobid: jobid,
    email: req.user.email
  });
  const classes = await oauthClient.makeApiCall({
    url: urlBuilder(qbo_realmId, "query", `select * From Class`),
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    }
  });
  logger.LogIntegrationCall({
    platform: "QBO",
    method: "POST",
    name: "QueryClasses",
    status: classes.status,
    bodyshopid,
    jobid: jobid,
    email: req.user.email
  });

  const taxCodeMapping = Object.fromEntries((taxCodes.json?.QueryResponse?.TaxCode || []).map((t) => [t.Name, t.Id]));

  const itemMapping = Object.fromEntries((items.json?.QueryResponse?.Item || []).map((item) => [item.Name, item.Id]));

  const classMapping = Object.fromEntries((classes.json?.QueryResponse?.Class || []).map((c) => [c.Name, c.Id]));

  return {
    items: itemMapping,
    taxCodes: taxCodeMapping,
    classes: classMapping
  };
}

async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, parentTierRef) {
  const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req, job.shopid, job.id);
  const InvoiceLineAdd = CreateInvoiceLines({
    bodyshop,
    jobs_by_pk: job,
    qbo: true,
    items,
    taxCodes,
    classes
  });

  const invoiceObj = {
    Line: InvoiceLineAdd,
    TxnDate: moment(job.date_invoiced).tz(bodyshop.timezone).format("YYYY-MM-DD"),
    DocNumber: job.ro_number,
    ...(job.class ? { ClassRef: { value: classes[job.class] } } : {}),
    CustomerMemo: {
      value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${
        job.po_number ? `PO No: ${job.po_number}` : ``
      } Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
        job.v_model_desc || ""
      } ${job.v_vin || ""} ${job.plate_no || ""} `.trim()
    },
    CustomerRef: {
      value: parentTierRef?.Id
    },
    ...(bodyshop.accountingconfig.qbo_departmentid?.trim() !== "" && {
      DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid }
    }),
    CustomField: [
      ...(bodyshop.accountingconfig.ReceivableCustomField1
        ? [
            {
              DefinitionId: "1",
              StringValue: job[bodyshop.accountingconfig.ReceivableCustomField1],
              Type: "StringType"
            }
          ]
        : []),
      ...(bodyshop.accountingconfig.ReceivableCustomField2
        ? [
            {
              DefinitionId: "2",
              StringValue: job[bodyshop.accountingconfig.ReceivableCustomField2],
              Type: "StringType"
            }
          ]
        : []),
      ...(bodyshop.accountingconfig.ReceivableCustomField3
        ? [
            {
              DefinitionId: "3",
              StringValue: job[bodyshop.accountingconfig.ReceivableCustomField3],
              Type: "StringType"
            }
          ]
        : [])
    ],
    ...(bodyshop.accountingconfig?.qbo &&
      bodyshop.accountingconfig?.qbo_usa && {
        TxnTaxDetail: {
          TxnTaxCodeRef: {
            value: taxCodes[bodyshop.md_responsibility_centers.taxes.state.accountitem]
          }
        }
      }),

    ...(bodyshop.accountingconfig.printlater ? { PrintStatus: "NeedToPrint" } : {}),
    ...(bodyshop.accountingconfig.emaillater && job.ownr_ea ? { EmailStatus: "NeedToSend" } : {}),
    BillAddr: {
      Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${job.ownr_zip || ""}`.trim(),
      Line2: job.ownr_addr1 || "",
      Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${job.ownr_co_nm || ""}`
    },
    ...(job.ownr_ea ? { BillEmail: { Address: job.ownr_ea.trim() } } : {})
  };

  logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {
    invoiceObj
  });

  try {
    const result = await oauthClient.makeApiCall({
      url: urlBuilder(qbo_realmId, "invoice"),
      method: "POST",

      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(invoiceObj)
    });
    logger.LogIntegrationCall({
      platform: "QBO",
      method: "POST",
      name: "InsertInvoice",
      status: result.status,
      bodyshopid: job.shopid,
      jobid: job.id,
      email: req.user.email
    });

    if (result.status >= 400) {
      throw new Error(JSON.stringify(result.json.Fault));
    }
    if (result.status === 200) return result?.json;
  } catch (error) {
    logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
      method: "InsertInvoice",
      validationError: error.message,
      accountmeta: JSON.stringify({ items, taxCodes, classes }),
      stack: error.stack
    });
    throw error;
  }
}

async function InsertInvoiceMultiPayerInvoice(
  oauthClient,
  qbo_realmId,
  req,
  job,
  bodyshop,
  parentTierRef,
  payer,
  suffix
) {
  const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req, job.shopid);
  const InvoiceLineAdd = createMultiQbPayerLines({
    bodyshop,
    jobs_by_pk: job,
    qbo: true,
    items,
    taxCodes,
    classes,
    payer,
    suffix
  });

  const invoiceObj = {
    Line: InvoiceLineAdd,
    TxnDate: moment(job.date_invoiced).tz(bodyshop.timezone).format("YYYY-MM-DD"),
    DocNumber: job.ro_number + suffix,
    ...(job.class ? { ClassRef: { value: classes[job.class] } } : {}),
    CustomerMemo: {
      value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${
        job.po_number ? `PO No: ${job.po_number}` : ``
      } Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
        job.v_model_desc || ""
      } ${job.v_vin || ""} ${job.plate_no || ""} `.trim()
    },
    CustomerRef: {
      value: parentTierRef.Id
    },
    ...(bodyshop.accountingconfig.qbo_departmentid?.trim() !== "" && {
      DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid }
    }),
    CustomField: [
      ...(bodyshop.accountingconfig.ReceivableCustomField1
        ? [
            {
              DefinitionId: "1",
              StringValue: job[bodyshop.accountingconfig.ReceivableCustomField1],
              Type: "StringType"
            }
          ]
        : []),
      ...(bodyshop.accountingconfig.ReceivableCustomField2
        ? [
            {
              DefinitionId: "2",
              StringValue: job[bodyshop.accountingconfig.ReceivableCustomField2],
              Type: "StringType"
            }
          ]
        : []),
      ...(bodyshop.accountingconfig.ReceivableCustomField3
        ? [
            {
              DefinitionId: "3",
              StringValue: job[bodyshop.accountingconfig.ReceivableCustomField3],
              Type: "StringType"
            }
          ]
        : [])
    ],
    ...(bodyshop.accountingconfig?.qbo &&
      bodyshop.accountingconfig?.qbo_usa &&
      bodyshop.region_config.includes("CA_") && {
        TxnTaxDetail: {
          TxnTaxCodeRef: {
            value: taxCodes[bodyshop.md_responsibility_centers.taxes.state.accountitem]
          }
        }
      }),

    ...(bodyshop.accountingconfig.printlater ? { PrintStatus: "NeedToPrint" } : {}),
    ...(bodyshop.accountingconfig.emaillater && job.ownr_ea ? { EmailStatus: "NeedToSend" } : {}),
    BillAddr: {
      Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${job.ownr_zip || ""}`.trim(),
      Line2: job.ownr_addr1 || "",
      Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${job.ownr_co_nm || ""}`
    },
    ...(job.ownr_ea ? { BillEmail: { Address: job.ownr_ea.trim() } } : {})
  };

  logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {
    invoiceObj
  });

  try {
    const result = await oauthClient.makeApiCall({
      url: urlBuilder(qbo_realmId, "invoice"),
      method: "POST",

      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(invoiceObj)
    });
    logger.LogIntegrationCall({
      platform: "QBO",
      method: "POST",
      name: "InsertInvoiceMultiPayerInvoice",
      status: result.status,
      bodyshopid: job.shopid,
      jobid: job.id,
      email: req.user.email
    });

    if (result.status >= 400) {
      throw new Error(JSON.stringify(result.json.Fault));
    }
    if (result.status === 200) return result?.json;
  } catch (error) {
    logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
      method: "InsertInvoiceMultiPayerInvoice",
      validationError: error.message,
      accountmeta: JSON.stringify({ items, taxCodes, classes }),
      stack: error.stack
    });
    throw error;
  }
}
