Compare commits
20 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
704543d823 | ||
|
|
fe848b5de4 | ||
|
|
7688f22161 | ||
|
|
efdcd06921 | ||
|
|
c0a37d7c1a | ||
|
|
6759bc5865 | ||
|
|
04732fc6cd | ||
|
|
a65a34ef1f | ||
|
|
1ea7798eeb | ||
|
|
7739d48741 | ||
|
|
074be66b8c | ||
|
|
8db8744782 | ||
|
|
c2d8d78e0a | ||
|
|
71aec6d0c5 | ||
|
|
f89d7865fa | ||
|
|
8fd368ebb4 | ||
|
|
132fc0a20f | ||
|
|
9ea2d83043 | ||
|
|
abad7d5f00 | ||
|
|
c97213bc96 |
@@ -13,6 +13,7 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import dayjs from "../../utils/day";
|
||||
import { buildBillUpdateAuditDetails } from "../../utils/auditTrailDetails";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
||||
@@ -134,10 +135,16 @@ export function BillDetailEditcontainer({ insertAuditTrail, bodyshop }) {
|
||||
|
||||
await Promise.all(updates);
|
||||
|
||||
const details = buildBillUpdateAuditDetails({
|
||||
originalBill: data?.bills_by_pk,
|
||||
bill,
|
||||
billlines
|
||||
});
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: bill.jobid,
|
||||
jobid: bill.jobid ?? data?.bills_by_pk?.jobid,
|
||||
billid: search.billid,
|
||||
operation: AuditTrailMapping.billupdated(bill.invoice_number),
|
||||
operation: AuditTrailMapping.billupdated(bill.invoice_number, details),
|
||||
type: "billupdated"
|
||||
});
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ export function BillFormComponent({
|
||||
const [discount, setDiscount] = useState(0);
|
||||
const notification = useNotification();
|
||||
const jobIdFormWatch = Form.useWatch("jobid", form);
|
||||
const vendorIdFormWatch = Form.useWatch("vendorid", form);
|
||||
|
||||
const {
|
||||
treatments: { Extended_Bill_Posting, ClosingPeriod }
|
||||
@@ -118,6 +119,7 @@ export function BillFormComponent({
|
||||
}
|
||||
}, [
|
||||
form,
|
||||
vendorIdFormWatch,
|
||||
billEdit,
|
||||
loadOutstandingReturns,
|
||||
loadInventory,
|
||||
|
||||
@@ -9,18 +9,20 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
authLevel: selectAuthLevel
|
||||
});
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BillMarkForReexportButton);
|
||||
|
||||
export function BillMarkForReexportButton({ bodyshop, authLevel, bill }) {
|
||||
export function BillMarkForReexportButton({ bodyshop, authLevel, bill, insertAuditTrail }) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const notification = useNotification();
|
||||
@@ -47,6 +49,12 @@ export function BillMarkForReexportButton({ bodyshop, authLevel, bill }) {
|
||||
notification.success({
|
||||
title: t("bills.successes.reexport")
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: bill.jobid,
|
||||
billid: bill.id,
|
||||
operation: AuditTrailMapping.billmarkforreexport(bill.invoice_number),
|
||||
type: "billmarkforreexport"
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
title: t("bills.errors.saving", {
|
||||
|
||||
@@ -14,16 +14,20 @@ import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||
import { buildJobLineInsertAuditDetails, buildJobLineUpdateAuditDetails } from "../../utils/auditTrailDetails.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobLineEditModal: selectJobLineEditModal,
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit"))
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")),
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
});
|
||||
|
||||
function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bodyshop }) {
|
||||
function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bodyshop, insertAuditTrail }) {
|
||||
const {
|
||||
treatments: { CriticalPartsScanning }
|
||||
} = useTreatmentsWithConfig({
|
||||
@@ -74,6 +78,11 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
|
||||
notification.success({
|
||||
title: t("joblines.successes.created")
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: jobLineEditModal.context.jobid,
|
||||
operation: AuditTrailMapping.jobmanuallineinsert(buildJobLineInsertAuditDetails(values)),
|
||||
type: "jobmanuallineinsert"
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
title: t("joblines.errors.creating", {
|
||||
@@ -103,6 +112,17 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
|
||||
notification.success({
|
||||
title: t("joblines.successes.updated")
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: jobLineEditModal.context.jobid,
|
||||
operation: AuditTrailMapping.joblineupdate(
|
||||
values.line_desc || jobLineEditModal.context.line_desc || "manual line",
|
||||
buildJobLineUpdateAuditDetails({
|
||||
originalLine: jobLineEditModal.context,
|
||||
values
|
||||
})
|
||||
),
|
||||
type: "joblineupdate"
|
||||
});
|
||||
} else {
|
||||
notification.success({
|
||||
title: t("joblines.errors.updating", {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { useMutation, useQuery } from "@apollo/client/react";
|
||||
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
||||
import { Button, Form, Modal, Space } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -15,21 +15,27 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import dayjs from "../../utils/day";
|
||||
import TimeTicketsCommitToggleComponent from "../time-tickets-commit-toggle/time-tickets-commit-toggle.component";
|
||||
import TimeTicketModalComponent from "./time-ticket-modal.component";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { buildTimeTicketAuditSummary } from "../../utils/auditTrailDetails.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
timeTicketModal: selectTimeTicket,
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("timeTicket"))
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("timeTicket")),
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
});
|
||||
|
||||
export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible, bodyshop }) {
|
||||
export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible, bodyshop, insertAuditTrail }) {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [enterAgain, setEnterAgain] = useState(false);
|
||||
|
||||
const lastSubmittedRef = useRef(null);
|
||||
|
||||
const [lineTicketRefreshKey, setLineTicketRefreshKey] = useState(0);
|
||||
|
||||
const [insertTicket] = useMutation(INSERT_NEW_TIME_TICKET);
|
||||
@@ -48,47 +54,77 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
const employees = EmployeeAutoCompleteData?.employees ?? [];
|
||||
|
||||
const handleFinish = (values) => {
|
||||
lastSubmittedRef.current = values;
|
||||
setLoading(true);
|
||||
const emps = EmployeeAutoCompleteData?.employees.filter((e) => e.id === values.employeeid);
|
||||
if (timeTicketModal.context.id) {
|
||||
updateTicket({
|
||||
variables: {
|
||||
timeticketId: timeTicketModal.context.id,
|
||||
timeticket: {
|
||||
...values,
|
||||
rate: emps.length === 1 ? emps[0].rates.filter((r) => r.cost_center === values.cost_center)[0]?.rate : null
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(handleMutationSuccess)
|
||||
.catch(handleMutationError);
|
||||
} else {
|
||||
//Get selected employee rate.
|
||||
insertTicket({
|
||||
variables: {
|
||||
timeTicketInput: [
|
||||
{
|
||||
const isEdit = Boolean(timeTicketModal.context.id);
|
||||
const emps = employees.filter((employee) => employee.id === values.employeeid);
|
||||
const mutation = isEdit
|
||||
? updateTicket({
|
||||
variables: {
|
||||
timeticketId: timeTicketModal.context.id,
|
||||
timeticket: {
|
||||
...values,
|
||||
rate:
|
||||
emps.length === 1 ? emps[0].rates.filter((r) => r.cost_center === values.cost_center)[0].rate : null,
|
||||
bodyshopid: bodyshop.id,
|
||||
created_by: timeTicketModal.context.created_by
|
||||
emps.length === 1 ? emps[0].rates.filter((r) => r.cost_center === values.cost_center)[0]?.rate : null
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
.then(handleMutationSuccess)
|
||||
.catch(handleMutationError);
|
||||
}
|
||||
}
|
||||
})
|
||||
: insertTicket({
|
||||
variables: {
|
||||
timeTicketInput: [
|
||||
{
|
||||
...values,
|
||||
rate:
|
||||
emps.length === 1 ? emps[0].rates.filter((r) => r.cost_center === values.cost_center)[0].rate : null,
|
||||
bodyshopid: bodyshop.id,
|
||||
created_by: timeTicketModal.context.created_by
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
mutation.then((result) => handleMutationSuccess(result, isEdit)).catch(handleMutationError);
|
||||
};
|
||||
|
||||
const handleMutationSuccess = () => {
|
||||
const handleMutationSuccess = (result, isEdit) => {
|
||||
notification.success({
|
||||
title: t("timetickets.successes.created")
|
||||
});
|
||||
|
||||
const savedTicket =
|
||||
result?.data?.update_timetickets?.returning?.[0] ?? result?.data?.insert_timetickets?.returning?.[0] ?? {};
|
||||
const originalTicket = timeTicketModal.context?.timeticket ?? {};
|
||||
const submittedValues = {
|
||||
...(lastSubmittedRef.current ?? {}),
|
||||
date: lastSubmittedRef.current?.date ?? savedTicket.date ?? originalTicket.date ?? null,
|
||||
employeeid: lastSubmittedRef.current?.employeeid ?? savedTicket.employeeid ?? originalTicket.employeeid ?? null,
|
||||
jobid:
|
||||
lastSubmittedRef.current?.jobid ??
|
||||
savedTicket.jobid ??
|
||||
timeTicketModal.context.jobId ??
|
||||
originalTicket.job?.id ??
|
||||
originalTicket.jobid ??
|
||||
null
|
||||
};
|
||||
const auditSummary = buildTimeTicketAuditSummary({
|
||||
originalTicket,
|
||||
submittedValues,
|
||||
employees
|
||||
});
|
||||
|
||||
if (auditSummary.jobid) {
|
||||
insertAuditTrail({
|
||||
jobid: auditSummary.jobid,
|
||||
operation: isEdit
|
||||
? AuditTrailMapping.timeticketupdated(auditSummary.employeeName, auditSummary.date, auditSummary.details)
|
||||
: AuditTrailMapping.timeticketcreated(auditSummary.employeeName, auditSummary.date, auditSummary.details),
|
||||
type: isEdit ? "timeticketupdated" : "timeticketcreated"
|
||||
});
|
||||
}
|
||||
|
||||
// Refresh parent screens (Job Labor tab, etc.)
|
||||
if (timeTicketModal.actions.refetch) timeTicketModal.actions.refetch();
|
||||
|
||||
|
||||
@@ -8,13 +8,14 @@ import { createStructuredSelector } from "reselect";
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import { INSERT_NEW_JOB } from "../../graphql/jobs.queries";
|
||||
import { QUERY_OWNER_FOR_JOB_CREATION } from "../../graphql/owners.queries";
|
||||
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||
import { insertAuditTrail, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import JobsCreateComponent from "./jobs-create.component";
|
||||
import JobCreateContext from "./jobs-create.context";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -22,10 +23,11 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
});
|
||||
|
||||
function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, currentUser }) {
|
||||
function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, currentUser, insertAuditTrail }) {
|
||||
const { t } = useTranslation();
|
||||
const notification = useNotification();
|
||||
|
||||
@@ -84,6 +86,11 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, curr
|
||||
newJobId: resp.data.insert_jobs.returning[0].id
|
||||
});
|
||||
logImEXEvent("manual_job_create_completed", {});
|
||||
insertAuditTrail({
|
||||
jobid: resp.data.insert_jobs.returning[0].id,
|
||||
operation: AuditTrailMapping.jobmanualcreate(),
|
||||
type: "jobmanualcreate"
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -120,8 +120,9 @@
|
||||
"appointmentinsert": "Appointment created. Appointment Date: {{start}}.",
|
||||
"assignedlinehours": "Assigned job lines totaling {{hours}} units to {{team}}.",
|
||||
"billdeleted": "Bill with invoice number {{invoice_number}} deleted.",
|
||||
"billmarkforreexport": "Bill with invoice number {{invoice_number}} marked for re-export.",
|
||||
"billposted": "Bill with invoice number {{invoice_number}} posted.",
|
||||
"billupdated": "Bill with invoice number {{invoice_number}} updated.",
|
||||
"billupdated": "Bill with invoice number {{invoice_number}} updated with the following details: {{details}}.",
|
||||
"failedpayment": "Failed payment attempt.",
|
||||
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
|
||||
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
|
||||
@@ -136,6 +137,9 @@
|
||||
"jobintake": "Job intake completed. Status set to {{status}}. Scheduled completion is {{scheduled_completion}}.",
|
||||
"jobinvoiced": "Job has been invoiced.",
|
||||
"jobioucreated": "IOU Created.",
|
||||
"joblineupdate": "Job line {{lineDescription}} updated with the following details: {{details}}.",
|
||||
"jobmanualcreate": "Job manually created.",
|
||||
"jobmanuallineinsert": "Job line manually added with the following details: {{details}}.",
|
||||
"jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.",
|
||||
"jobnoteadded": "Note added to Job.",
|
||||
"jobnotedeleted": "Note deleted from Job.",
|
||||
@@ -151,7 +155,9 @@
|
||||
"tasks_deleted": "Task '{{title}}' deleted by {{deletedBy}}",
|
||||
"tasks_uncompleted": "Task '{{title}}' uncompleted by {{uncompletedBy}}",
|
||||
"tasks_undeleted": "Task '{{title}}' undeleted by {{undeletedBy}}",
|
||||
"tasks_updated": "Task '{{title}}' updated by {{updatedBy}}"
|
||||
"tasks_updated": "Task '{{title}}' updated by {{updatedBy}}",
|
||||
"timeticketcreated": "Time Ticket for {{employee}} on {{date}} created with the following details: {{details}}.",
|
||||
"timeticketupdated": "Time Ticket for {{employee}} on {{date}} updated with the following details: {{details}}"
|
||||
}
|
||||
},
|
||||
"billlines": {
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
"assignedlinehours": "",
|
||||
"billdeleted": "",
|
||||
"billposted": "",
|
||||
"billupdated": "",
|
||||
"billmarkforreexport": "",
|
||||
"failedpayment": "",
|
||||
"jobassignmentchange": "",
|
||||
"jobassignmentremoved": "",
|
||||
|
||||
@@ -120,8 +120,8 @@
|
||||
"appointmentinsert": "",
|
||||
"assignedlinehours": "",
|
||||
"billdeleted": "",
|
||||
"billmarkforreexport": "",
|
||||
"billposted": "",
|
||||
"billupdated": "",
|
||||
"failedpayment": "",
|
||||
"jobassignmentchange": "",
|
||||
"jobassignmentremoved": "",
|
||||
|
||||
@@ -8,8 +8,9 @@ const AuditTrailMapping = {
|
||||
appointmentcancel: (lost_sale_reason) => i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }),
|
||||
appointmentinsert: (start) => i18n.t("audit_trail.messages.appointmentinsert", { start }),
|
||||
billdeleted: (invoice_number) => i18n.t("audit_trail.messages.billdeleted", { invoice_number }),
|
||||
billmarkforreexport: (invoice_number) => i18n.t("audit_trail.messages.billmarkforreexport", { invoice_number }),
|
||||
billposted: (invoice_number) => i18n.t("audit_trail.messages.billposted", { invoice_number }),
|
||||
billupdated: (invoice_number) => i18n.t("audit_trail.messages.billupdated", { invoice_number }),
|
||||
billupdated: (invoice_number, details) => i18n.t("audit_trail.messages.billupdated", { invoice_number, details }),
|
||||
jobassignmentchange: (operation, name) => i18n.t("audit_trail.messages.jobassignmentchange", { operation, name }),
|
||||
jobassignmentremoved: (operation) => i18n.t("audit_trail.messages.jobassignmentremoved", { operation }),
|
||||
jobchecklist: (type, inproduction, status) =>
|
||||
@@ -25,6 +26,10 @@ const AuditTrailMapping = {
|
||||
jobinproductionchange: (inproduction) => i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }),
|
||||
jobinvoiced: () => i18n.t("audit_trail.messages.jobinvoiced"),
|
||||
jobclosedwithbypass: () => i18n.t("audit_trail.messages.jobclosedwithbypass"),
|
||||
joblineupdate: (lineDescription, details) =>
|
||||
i18n.t("audit_trail.messages.joblineupdate", { details, lineDescription }),
|
||||
jobmanualcreate: () => i18n.t("audit_trail.messages.jobmanualcreate"),
|
||||
jobmanuallineinsert: (details) => i18n.t("audit_trail.messages.jobmanuallineinsert", { details }),
|
||||
jobmodifylbradj: ({ mod_lbr_ty, hours }) => i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }),
|
||||
jobnoteadded: () => i18n.t("audit_trail.messages.jobnoteadded"),
|
||||
jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"),
|
||||
@@ -71,7 +76,11 @@ const AuditTrailMapping = {
|
||||
i18n.t("audit_trail.messages.tasks_uncompleted", {
|
||||
title,
|
||||
uncompletedBy
|
||||
})
|
||||
}),
|
||||
timeticketcreated: (employee, date, details) =>
|
||||
i18n.t("audit_trail.messages.timeticketcreated", { employee, date, details }),
|
||||
timeticketupdated: (employee, date, details) =>
|
||||
i18n.t("audit_trail.messages.timeticketupdated", { employee, date, details })
|
||||
};
|
||||
|
||||
export default AuditTrailMapping;
|
||||
|
||||
186
client/src/utils/auditTrailDetails.js
Normal file
186
client/src/utils/auditTrailDetails.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import dayjs from "./day";
|
||||
|
||||
const EMPTY_VALUE = "<<empty>>";
|
||||
const NO_CHANGES = "No changes";
|
||||
|
||||
const BILL_LINE_KEYS = ["line_desc", "quantity", "actual_price", "actual_cost", "cost_center"];
|
||||
const JOB_LINE_SKIP_KEYS = new Set(["ah_detail_line", "prt_dsmk_p"]);
|
||||
const DATE_ONLY_KEYS = new Set(["date"]);
|
||||
const DATE_TIME_KEYS = new Set(["clockon", "clockoff"]);
|
||||
const CURRENCY_KEYS = new Set(["actual_price", "actual_cost", "act_price", "db_price", "rate"]);
|
||||
const HOUR_KEYS = new Set(["productivehrs", "actualhrs", "mod_lb_hrs"]);
|
||||
|
||||
const isBlank = (value) => value == null || value === "";
|
||||
|
||||
const isStructuredValue = (value) => value != null && typeof value === "object" && !dayjs.isDayjs?.(value);
|
||||
|
||||
const formatDate = (value) => (isBlank(value) ? EMPTY_VALUE : dayjs(value).format("YYYY-MM-DD"));
|
||||
|
||||
const formatDateTime = (value) => (isBlank(value) ? EMPTY_VALUE : dayjs(value).format("YYYY-MM-DD HH:mm"));
|
||||
|
||||
const formatNumber = (value, fractionDigits) =>
|
||||
typeof value === "number" ? value.toFixed(fractionDigits) : String(value);
|
||||
|
||||
const compareValue = (key, value) => {
|
||||
if (isBlank(value)) return EMPTY_VALUE;
|
||||
if (DATE_TIME_KEYS.has(key)) return formatDateTime(value);
|
||||
if (DATE_ONLY_KEYS.has(key)) return formatDate(value);
|
||||
if (dayjs.isDayjs?.(value)) return formatDateTime(value);
|
||||
return String(value);
|
||||
};
|
||||
|
||||
const buildFieldChangeDetails = ({ keys, original = {}, updated = {}, displayValue, skippedKeys = new Set() }) =>
|
||||
keys
|
||||
.filter((key) => key !== "__typename" && !skippedKeys.has(key))
|
||||
.filter((key) => !isStructuredValue(original[key]) && !isStructuredValue(updated[key]))
|
||||
.map((key) => {
|
||||
if (compareValue(key, original[key]) === compareValue(key, updated[key])) return null;
|
||||
return `${key}: ${displayValue(key, original[key])} -> ${displayValue(key, updated[key])}`;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
const formatBillValue = (key, value) => {
|
||||
if (isBlank(value)) return EMPTY_VALUE;
|
||||
if (DATE_ONLY_KEYS.has(key)) return formatDate(value);
|
||||
if (CURRENCY_KEYS.has(key)) return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
|
||||
return String(value);
|
||||
};
|
||||
|
||||
const formatJobLineValue = (key, value) => {
|
||||
if (isBlank(value)) return EMPTY_VALUE;
|
||||
if (CURRENCY_KEYS.has(key)) return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
|
||||
if (HOUR_KEYS.has(key)) return formatNumber(value, 1);
|
||||
return String(value);
|
||||
};
|
||||
|
||||
const getEmployeeName = (employeeId, employees = [], fallbackEmployee) => {
|
||||
if (
|
||||
(employeeId == null || fallbackEmployee?.id === employeeId) &&
|
||||
(fallbackEmployee?.first_name || fallbackEmployee?.last_name)
|
||||
) {
|
||||
return [fallbackEmployee.first_name, fallbackEmployee.last_name].filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
const employee = employees.find(({ id }) => id === employeeId);
|
||||
if (employee) {
|
||||
return [employee.first_name, employee.last_name].filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
return employeeId ? String(employeeId) : EMPTY_VALUE;
|
||||
};
|
||||
|
||||
const formatTimeTicketValue = (key, value, { employees = [], fallbackEmployee } = {}) => {
|
||||
if (isBlank(value)) return EMPTY_VALUE;
|
||||
if (key === "employeeid") return getEmployeeName(value, employees, fallbackEmployee);
|
||||
if (DATE_TIME_KEYS.has(key)) return formatDateTime(value);
|
||||
if (DATE_ONLY_KEYS.has(key)) return formatDate(value);
|
||||
if (CURRENCY_KEYS.has(key)) return typeof value === "number" ? `$${value.toFixed(2)}` : String(value);
|
||||
if (HOUR_KEYS.has(key)) return formatNumber(value, 1);
|
||||
if (typeof value === "boolean") return value ? "true" : "false";
|
||||
return String(value);
|
||||
};
|
||||
|
||||
const buildBillLineSummary = (line) =>
|
||||
BILL_LINE_KEYS.map((key) => `${key}: ${formatBillValue(key, line[key])}`).join(", ");
|
||||
|
||||
export function buildBillUpdateAuditDetails({ originalBill = {}, bill = {}, billlines = [] }) {
|
||||
const updatedBill = { ...bill, billlines };
|
||||
const billKeys = Array.from(new Set([...Object.keys(originalBill), ...Object.keys(updatedBill)])).filter(
|
||||
(key) => key !== "billlines"
|
||||
);
|
||||
|
||||
const changed = buildFieldChangeDetails({
|
||||
keys: billKeys,
|
||||
original: originalBill,
|
||||
updated: updatedBill,
|
||||
displayValue: formatBillValue
|
||||
});
|
||||
|
||||
const originalBillLines = originalBill.billlines ?? [];
|
||||
const updatedBillLines = updatedBill.billlines ?? [];
|
||||
|
||||
const addedLines = updatedBillLines
|
||||
.filter((line) => !line.id)
|
||||
.map((line) => `+${line.line_desc || line.description || "new line"} (${buildBillLineSummary(line)})`);
|
||||
|
||||
const removedLines = originalBillLines
|
||||
.filter((line) => !updatedBillLines.some((updatedLine) => updatedLine.id === line.id))
|
||||
.map(
|
||||
(line) => `-${line.line_desc || line.description || line.id || "removed line"} (${buildBillLineSummary(line)})`
|
||||
);
|
||||
|
||||
const modifiedLines = updatedBillLines
|
||||
.filter((line) => line.id)
|
||||
.flatMap((line) => {
|
||||
const originalLine = originalBillLines.find(({ id }) => id === line.id);
|
||||
if (!originalLine) return [];
|
||||
|
||||
const lineChanges = buildFieldChangeDetails({
|
||||
keys: BILL_LINE_KEYS,
|
||||
original: originalLine,
|
||||
updated: line,
|
||||
displayValue: formatBillValue
|
||||
});
|
||||
|
||||
if (!lineChanges.length) return [];
|
||||
|
||||
return [`${line.line_desc || line.description || line.id}: ${lineChanges.join("; ")}`];
|
||||
});
|
||||
|
||||
if (addedLines.length) changed.push(`billlines added: ${addedLines.join(" | ")}`);
|
||||
if (removedLines.length) changed.push(`billlines removed: ${removedLines.join(" | ")}`);
|
||||
if (modifiedLines.length) changed.push(`billlines modified: ${modifiedLines.join(" | ")}`);
|
||||
|
||||
return changed.length ? changed.join("; ") : NO_CHANGES;
|
||||
}
|
||||
|
||||
export function buildJobLineInsertAuditDetails(values = {}) {
|
||||
const details = Object.entries(values)
|
||||
.filter(([key, value]) => !JOB_LINE_SKIP_KEYS.has(key) && !isBlank(value))
|
||||
.map(([key, value]) => `${key}: ${formatJobLineValue(key, value)}`);
|
||||
|
||||
return details.length ? details.join("; ") : NO_CHANGES;
|
||||
}
|
||||
|
||||
export function buildJobLineUpdateAuditDetails({ originalLine = {}, values = {} }) {
|
||||
const details = buildFieldChangeDetails({
|
||||
keys: Object.keys(values),
|
||||
original: originalLine,
|
||||
updated: values,
|
||||
displayValue: formatJobLineValue,
|
||||
skippedKeys: JOB_LINE_SKIP_KEYS
|
||||
});
|
||||
|
||||
return details.length ? details.join("; ") : NO_CHANGES;
|
||||
}
|
||||
|
||||
export function buildTimeTicketAuditSummary({ originalTicket = {}, submittedValues = {}, employees = [] }) {
|
||||
const normalizedOriginal = {
|
||||
...originalTicket,
|
||||
jobid: originalTicket.job?.id ?? originalTicket.jobid ?? null
|
||||
};
|
||||
|
||||
const details = buildFieldChangeDetails({
|
||||
keys: Object.keys(submittedValues),
|
||||
original: normalizedOriginal,
|
||||
updated: submittedValues,
|
||||
displayValue: (key, value) =>
|
||||
formatTimeTicketValue(key, value, {
|
||||
employees,
|
||||
fallbackEmployee: key === "employeeid" ? normalizedOriginal.employee : null
|
||||
})
|
||||
});
|
||||
|
||||
const employeeName = getEmployeeName(
|
||||
submittedValues.employeeid ?? normalizedOriginal.employeeid,
|
||||
employees,
|
||||
normalizedOriginal.employee
|
||||
);
|
||||
|
||||
return {
|
||||
date: formatDate(submittedValues.date ?? normalizedOriginal.date),
|
||||
details: details.length ? details.join("; ") : NO_CHANGES,
|
||||
employeeName,
|
||||
jobid: submittedValues.jobid ?? normalizedOriginal.jobid ?? null
|
||||
};
|
||||
}
|
||||
@@ -24,6 +24,15 @@
|
||||
- name: x-imex-auth
|
||||
value_from_env: DATAPUMP_AUTH
|
||||
comment: Project Mexico
|
||||
- name: Chatter API Data Pump
|
||||
webhook: '{{HASURA_API_URL}}/data/chatter-api'
|
||||
schedule: 45 4 * * *
|
||||
include_in_metadata: true
|
||||
payload: {}
|
||||
headers:
|
||||
- name: x-imex-auth
|
||||
value_from_env: DATAPUMP_AUTH
|
||||
comment: ""
|
||||
- name: Chatter Data Pump
|
||||
webhook: '{{HASURA_API_URL}}/data/chatter'
|
||||
schedule: 45 5 * * *
|
||||
|
||||
@@ -51,7 +51,8 @@ awslocal ses verify-email-identity --email-address noreply@imex.online --region
|
||||
|
||||
# Secrets
|
||||
ensure_secret_file "CHATTER_PRIVATE_KEY" "/tmp/certs/io-ftp-test.key"
|
||||
ensure_secret_string "CHATTER_COMPANY_KEY_6713" "${CHATTER_COMPANY_KEY_6713:-REPLACE_ME}"
|
||||
ensure_secret_string "CHATTER_COMPANY_KEY_6713" "${CHATTER_COMPANY_KEY_6713}"
|
||||
ensure_secret_string "CHATTER_COMPANY_KEY_6746" "${CHATTER_COMPANY_KEY_6746}"
|
||||
|
||||
# Logs
|
||||
ensure_log_group "development"
|
||||
|
||||
@@ -787,7 +787,8 @@ async function RepairOrderChange(socket) {
|
||||
// "DatePromisedUTC": "0001-01-01T00:00:00.0000000Z",
|
||||
DateVehicleCompleted: socket.JobData.actual_completion,
|
||||
// "DateCustomerNotified": "0001-01-01T00:00:00.0000000Z",
|
||||
// "CSR": "String",
|
||||
"CSR": "IMEX", //Hardcoded for now as the only shop that uses this RO posting is RC and they paid for a custom build.
|
||||
Shop: "RB",
|
||||
// "CSRRef": "00000000000000000000000000000000",
|
||||
// "BookingUser": "String",
|
||||
// "BookingUserRef": "00000000000000000000000000000000",
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
|
||||
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||
const { isString, isEmpty } = require("lodash");
|
||||
const { InstanceRegion, InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("../utils/instanceMgr");
|
||||
|
||||
const CHATTER_BASE_URL = process.env.CHATTER_API_BASE_URL || "https://api.chatterresearch.com";
|
||||
const AWS_REGION = process.env.AWS_REGION || "ca-central-1";
|
||||
|
||||
// Configure SecretsManager client with localstack support
|
||||
const secretsClientOptions = {
|
||||
region: AWS_REGION,
|
||||
region: InstanceRegion(),
|
||||
credentials: defaultProvider()
|
||||
};
|
||||
|
||||
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
||||
|
||||
if (isLocal) {
|
||||
secretsClientOptions.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
|
||||
if (InstanceIsLocalStackEnabled()) {
|
||||
secretsClientOptions.endpoint = InstanceLocalStackEndpoint();
|
||||
}
|
||||
|
||||
const secretsClient = new SecretsManagerClient(secretsClientOptions);
|
||||
|
||||
@@ -33,8 +33,6 @@ const createLocation = async (req, res) => {
|
||||
const { logger } = req;
|
||||
const { bodyshopID, googlePlaceID } = req.body;
|
||||
|
||||
console.dir({ body: req.body });
|
||||
|
||||
if (!DEFAULT_COMPANY_ID) {
|
||||
logger.log("chatter-create-location-no-default-company", "warn", null, null, { bodyshopID });
|
||||
return res.json({ success: false, message: "No default company set" });
|
||||
@@ -67,7 +65,7 @@ const createLocation = async (req, res) => {
|
||||
|
||||
const chatterApi = await createChatterClient(DEFAULT_COMPANY_ID);
|
||||
|
||||
const locationIdentifier = `${DEFAULT_COMPANY_ID}-${bodyshop.id}`;
|
||||
const locationIdentifier = bodyshop?.imexshopid ?? `${DEFAULT_COMPANY_ID}-${bodyshop.id}`;
|
||||
|
||||
const locationPayload = {
|
||||
name: bodyshop.shopname,
|
||||
|
||||
@@ -3,7 +3,7 @@ const Dinero = require("dinero.js");
|
||||
const moment = require("moment-timezone");
|
||||
const logger = require("../utils/logger");
|
||||
const InstanceManager = require("../utils/instanceMgr").default;
|
||||
const { isString, isEmpty } = require("lodash");
|
||||
const { InstanceIsLocalStackEnabled } = require("../utils/instanceMgr");
|
||||
const fs = require("fs");
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const { sendServerEmail, sendMexicoBillingEmail } = require("../email/sendemail");
|
||||
@@ -35,10 +35,9 @@ const S3_BUCKET_NAME = InstanceManager({
|
||||
rome: "rome-carfax-uploads"
|
||||
});
|
||||
const region = InstanceManager.InstanceRegion;
|
||||
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
||||
|
||||
const uploadToS3 = (jsonObj, bucketName = S3_BUCKET_NAME) => {
|
||||
const webPath = isLocal
|
||||
const webPath = InstanceIsLocalStackEnabled()
|
||||
? `https://${bucketName}.s3.localhost.localstack.cloud:4566/${jsonObj.filename}`
|
||||
: `https://${bucketName}.s3.${region}.amazonaws.com/${jsonObj.filename}`;
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ const logger = require("../utils/logger");
|
||||
const fs = require("fs");
|
||||
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
|
||||
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||
const { isString, isEmpty } = require("lodash");
|
||||
const { InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("../utils/instanceMgr");
|
||||
|
||||
let Client = require("ssh2-sftp-client");
|
||||
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
@@ -151,10 +152,8 @@ async function getPrivateKey() {
|
||||
credentials: defaultProvider()
|
||||
};
|
||||
|
||||
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
||||
|
||||
if (isLocal) {
|
||||
secretsClientOptions.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
|
||||
if (InstanceIsLocalStackEnabled()) {
|
||||
secretsClientOptions.endpoint = InstanceLocalStackEndpoint();
|
||||
}
|
||||
|
||||
const client = new SecretsManagerClient(secretsClientOptions);
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
const { isString, isEmpty } = require("lodash");
|
||||
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||
const { InstanceRegion } = require("../utils/instanceMgr");
|
||||
const { InstanceRegion, InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("../utils/instanceMgr");
|
||||
const aws = require("@aws-sdk/client-ses");
|
||||
const nodemailer = require("nodemailer");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
||||
|
||||
const sesConfig = {
|
||||
apiVersion: "latest",
|
||||
credentials: defaultProvider(),
|
||||
region: InstanceRegion()
|
||||
};
|
||||
|
||||
if (isLocal) {
|
||||
sesConfig.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
|
||||
if (InstanceIsLocalStackEnabled()) {
|
||||
sesConfig.endpoint = InstanceLocalStackEndpoint();
|
||||
logger.logger.debug(`SES Mailer set to LocalStack end point: ${sesConfig.endpoint}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -334,30 +334,48 @@ async function FortellisSelectedCustomer({ socket, redisHelpers, selectedCustome
|
||||
socket.emit("export-success", JobData.id);
|
||||
} else {
|
||||
//There was something wrong. Throw an error to trigger clean up.
|
||||
//throw new Error("Error posting DMS Batch Transaction");
|
||||
const batchPostError = new Error(DmsBatchTxnPost.sendline || "Error posting DMS Batch Transaction");
|
||||
batchPostError.errorData = { DMSTransHeader, DmsBatchTxnPost };
|
||||
throw batchPostError;
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
//Clean up the transaction and insert a faild error code
|
||||
// //Get the error code
|
||||
CreateFortellisLogEvent(socket, "DEBUG", `{6.1} Getting errors for Transaction ID ${DMSTransHeader.transID}`);
|
||||
|
||||
const DmsError = await QueryDmsErrWip({ socket, redisHelpers, JobData });
|
||||
// //Delete the transaction
|
||||
let dmsErrors = [];
|
||||
try {
|
||||
const DmsError = await QueryDmsErrWip({ socket, redisHelpers, JobData });
|
||||
dmsErrors = Array.isArray(DmsError?.errMsg) ? DmsError.errMsg.filter((e) => e !== null && e !== "") : [];
|
||||
|
||||
dmsErrors.forEach((e) => {
|
||||
CreateFortellisLogEvent(socket, "ERROR", `Error encountered in posting transaction => ${e} `);
|
||||
});
|
||||
} catch (queryError) {
|
||||
CreateFortellisLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`{6.1} Unable to read ErrWIP for Transaction ID ${DMSTransHeader.transID}: ${queryError.message}`
|
||||
);
|
||||
}
|
||||
|
||||
//Delete the transaction, even if querying ErrWIP fails.
|
||||
CreateFortellisLogEvent(socket, "DEBUG", `{6.2} Deleting Transaction ID ${DMSTransHeader.transID}`);
|
||||
try {
|
||||
await DeleteDmsWip({ socket, redisHelpers, JobData });
|
||||
} catch (cleanupError) {
|
||||
CreateFortellisLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`{6.2} Failed cleanup for Transaction ID ${DMSTransHeader.transID}: ${cleanupError.message}`
|
||||
);
|
||||
}
|
||||
|
||||
await DeleteDmsWip({ socket, redisHelpers, JobData });
|
||||
|
||||
DmsError.errMsg.map(
|
||||
(e) =>
|
||||
e !== null &&
|
||||
e !== "" &&
|
||||
CreateFortellisLogEvent(socket, "ERROR", `Error encountered in posting transaction => ${e} `)
|
||||
);
|
||||
await InsertFailedExportLog({
|
||||
socket,
|
||||
JobData,
|
||||
error: DmsError.errMsg
|
||||
});
|
||||
if (!error.errorData || typeof error.errorData !== "object") {
|
||||
error.errorData = {};
|
||||
}
|
||||
error.errorData.issues = dmsErrors.length ? dmsErrors : [error.message];
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -116,6 +116,32 @@ async function TotalsServerSide(req, res) {
|
||||
ret.totals.ttl_tax_adjustment = Dinero({ amount: Math.round(ttlTaxDifference * 100) });
|
||||
ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_tax_adjustment);
|
||||
ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_tax_adjustment);
|
||||
|
||||
if (Math.abs(totalUsTaxes) === 0) {
|
||||
const laborRates = Object.values(job.cieca_pfl)
|
||||
.map((v) => v.lbr_taxp)
|
||||
.filter((v) => v != null);
|
||||
const materialRates = Object.values(job.materials)
|
||||
.map((v) => v.mat_taxp)
|
||||
.filter((v) => v != null);
|
||||
const partsRates = Object.values(job.parts_tax_rates)
|
||||
.map((v) => {
|
||||
const field = v.prt_tax_rt ?? v.part_tax_rt;
|
||||
if (field == null) return null;
|
||||
const raw = typeof field === "object" ? field.parsedValue : field;
|
||||
return raw != null ? raw * 100 : null;
|
||||
})
|
||||
.filter((v) => v != null);
|
||||
const taxRate = Math.max(...laborRates, ...materialRates, ...partsRates);
|
||||
|
||||
const totalTaxes = ret.totals.taxableAmounts.total.multiply(taxRate > 1 ? taxRate / 100 : taxRate);
|
||||
ret.totals.taxableAmounts.total = ret.totals.taxableAmounts.total
|
||||
.multiply(emsTaxTotal)
|
||||
.divide(totalTaxes.toUnit());
|
||||
} else {
|
||||
ret.totals.taxableAmounts.total = ret.totals.taxableAmounts.total.multiply(emsTaxTotal).divide(totalUsTaxes);
|
||||
}
|
||||
|
||||
logger.log("job-totals-USA-ttl-tax-adj", "debug", null, job.id, {
|
||||
adjAmount: ttlTaxDifference
|
||||
});
|
||||
@@ -993,6 +1019,8 @@ function CalculateTaxesTotals(job, otherTotals) {
|
||||
}
|
||||
});
|
||||
|
||||
taxableAmounts.total = Object.values(taxableAmounts).reduce((acc, amount) => acc.add(amount), Dinero({ amount: 0 }));
|
||||
|
||||
// console.log("*** Taxable Amounts***");
|
||||
// console.table(JSON.parse(JSON.stringify(taxableAmounts)));
|
||||
|
||||
@@ -1203,6 +1231,7 @@ function CalculateTaxesTotals(job, otherTotals) {
|
||||
|
||||
let ret = {
|
||||
subtotal: subtotal,
|
||||
taxableAmounts: taxableAmounts,
|
||||
federal_tax: subtotal
|
||||
.percentage((job.federal_tax_rate || 0) * 100)
|
||||
.add(otherTotals.additional.pvrt.percentage((job.federal_tax_rate || 0) * 100)),
|
||||
|
||||
@@ -7,14 +7,24 @@
|
||||
* @property { string | object | function } promanager Return this prop if Rome.
|
||||
* @property { string | object | function } imex Return this prop if Rome.
|
||||
*/
|
||||
const { isString, isEmpty } = require("lodash");
|
||||
|
||||
function InstanceManager({ args, instance, debug, executeFunction, rome, promanager, imex }) {
|
||||
/**
|
||||
* InstanceManager is a utility function that determines which property to return based on the current instance type.
|
||||
* @param param0
|
||||
* @param param0.args
|
||||
* @param param0.instance
|
||||
* @param param0.debug
|
||||
* @param param0.executeFunction
|
||||
* @param param0.rome
|
||||
* @param param0.promanager
|
||||
* @param param0.imex
|
||||
* @returns {*|null}
|
||||
* @constructor
|
||||
*/
|
||||
const InstanceManager = ({ args, instance, debug, executeFunction, rome, promanager, imex }) => {
|
||||
let propToReturn = null;
|
||||
|
||||
//TODO: Remove after debugging.
|
||||
if (promanager) {
|
||||
console.trace("ProManager Prop was used");
|
||||
}
|
||||
switch (instance || process.env.INSTANCE) {
|
||||
case "IMEX":
|
||||
propToReturn = imex;
|
||||
@@ -50,15 +60,42 @@ function InstanceManager({ args, instance, debug, executeFunction, rome, promana
|
||||
}
|
||||
if (executeFunction && typeof propToReturn === "function") return propToReturn(...args);
|
||||
return propToReturn === undefined ? null : propToReturn;
|
||||
}
|
||||
};
|
||||
|
||||
exports.InstanceRegion = () =>
|
||||
/**
|
||||
* Returns the AWS region to be used for the current instance, which is determined by the INSTANCE environment variable.
|
||||
* @returns {*}
|
||||
* @constructor
|
||||
*/
|
||||
const InstanceRegion = () =>
|
||||
InstanceManager({
|
||||
imex: "ca-central-1",
|
||||
rome: "us-east-2"
|
||||
});
|
||||
|
||||
exports.InstanceEndpoints = () =>
|
||||
/**
|
||||
* Checks if the instance is configured to use LocalStack by verifying the presence of the LOCALSTACK_HOSTNAME
|
||||
* environment variable.
|
||||
* @returns {boolean}
|
||||
* @constructor
|
||||
*/
|
||||
const InstanceIsLocalStackEnabled = () =>
|
||||
isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
||||
|
||||
/**
|
||||
* Returns the LocalStack endpoint URL based on the LOCALSTACK_HOSTNAME environment variable.
|
||||
* @returns {`http://${*}:4566`}
|
||||
* @constructor
|
||||
*/
|
||||
const InstanceLocalStackEndpoint = () => `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
|
||||
|
||||
/**
|
||||
* Returns the appropriate endpoints for the current instance, which can be used for making API calls or other network
|
||||
* requests.
|
||||
* @returns {*|null}
|
||||
* @constructor
|
||||
*/
|
||||
const InstanceEndpoints = () =>
|
||||
InstanceManager({
|
||||
imex:
|
||||
process.env?.NODE_ENV === "development"
|
||||
@@ -74,4 +111,11 @@ exports.InstanceEndpoints = () =>
|
||||
: "https://romeonline.io"
|
||||
});
|
||||
|
||||
exports.default = InstanceManager;
|
||||
module.exports = {
|
||||
InstanceManager,
|
||||
InstanceRegion,
|
||||
InstanceIsLocalStackEnabled,
|
||||
InstanceLocalStackEndpoint,
|
||||
InstanceEndpoints,
|
||||
default: InstanceManager
|
||||
};
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
const InstanceManager = require("../utils/instanceMgr").default;
|
||||
const winston = require("winston");
|
||||
const WinstonCloudWatch = require("winston-cloudwatch");
|
||||
const { isString, isEmpty } = require("lodash");
|
||||
const { uploadFileToS3 } = require("./s3");
|
||||
const { v4 } = require("uuid");
|
||||
const { InstanceRegion } = require("./instanceMgr");
|
||||
const { InstanceRegion, InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("./instanceMgr");
|
||||
const getHostNameOrIP = require("./getHostNameOrIP");
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const queries = require("../graphql-client/queries");
|
||||
@@ -48,7 +47,7 @@ const normalizeLevel = (level) => (level ? level.toLowerCase() : LOG_LEVELS.debu
|
||||
|
||||
const createLogger = () => {
|
||||
try {
|
||||
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
||||
const isLocal = InstanceIsLocalStackEnabled();
|
||||
const logGroupName = isLocal ? "development" : process.env.CLOUDWATCH_LOG_GROUP;
|
||||
|
||||
const winstonCloudwatchTransportDefaults = {
|
||||
@@ -60,7 +59,7 @@ const createLogger = () => {
|
||||
};
|
||||
|
||||
if (isLocal) {
|
||||
winstonCloudwatchTransportDefaults.awsOptions.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
|
||||
winstonCloudwatchTransportDefaults.awsOptions.endpoint = InstanceLocalStackEndpoint();
|
||||
}
|
||||
|
||||
const levelFilter = (levels) => {
|
||||
|
||||
@@ -7,8 +7,7 @@ const {
|
||||
CopyObjectCommand
|
||||
} = require("@aws-sdk/client-s3");
|
||||
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||
const { InstanceRegion } = require("./instanceMgr");
|
||||
const { isString, isEmpty } = require("lodash");
|
||||
const { InstanceRegion, InstanceIsLocalStackEnabled, InstanceLocalStackEndpoint } = require("./instanceMgr");
|
||||
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
|
||||
|
||||
const createS3Client = () => {
|
||||
@@ -17,10 +16,8 @@ const createS3Client = () => {
|
||||
credentials: defaultProvider()
|
||||
};
|
||||
|
||||
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
||||
|
||||
if (isLocal) {
|
||||
S3Options.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
|
||||
if (InstanceIsLocalStackEnabled()) {
|
||||
S3Options.endpoint = InstanceLocalStackEndpoint();
|
||||
S3Options.forcePathStyle = true; // Needed for LocalStack to avoid bucket name as hostname
|
||||
}
|
||||
|
||||
@@ -105,7 +102,7 @@ const createS3Client = () => {
|
||||
});
|
||||
const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 360 });
|
||||
return presignedUrl;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
uploadFileToS3,
|
||||
@@ -119,7 +116,4 @@ const createS3Client = () => {
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = createS3Client();
|
||||
|
||||
Reference in New Issue
Block a user