From 03d4e4dcd133129dc77dfca0e31807ecff20dbdd Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 24 Jan 2024 16:01:48 -0800 Subject: [PATCH 1/4] IO-2543 AR Aging --- .../jobs-admin-remove-ar.component.jsx | 63 +++ .../report-center-modal.component.jsx | 38 +- client/src/graphql/jobs.queries.js | 450 +++++++++--------- .../src/pages/jobs-admin/jobs-admin.page.jsx | 5 +- client/src/translations/en_us/common.json | 3 + client/src/translations/es/common.json | 3 + client/src/translations/fr/common.json | 3 + client/src/utils/AuditTrailMappings.js | 66 +-- client/src/utils/TemplateConstants.js | 10 + 9 files changed, 366 insertions(+), 275 deletions(-) create mode 100644 client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx diff --git a/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx new file mode 100644 index 000000000..c85404f48 --- /dev/null +++ b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx @@ -0,0 +1,63 @@ +import { gql, useMutation } from "@apollo/client"; +import { Form, Switch, notification } from "antd"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { insertAuditTrail } from "../../redux/application/application.actions"; +import AuditTrailMapping from "../../utils/AuditTrailMappings"; + +const mapStateToProps = createStructuredSelector({}); +const mapDispatchToProps = (dispatch) => ({ + insertAuditTrail: ({ jobid, operation }) => + dispatch(insertAuditTrail({ jobid, operation })), +}); + +export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminRemoveAR); + +export function JobsAdminRemoveAR({ insertAuditTrail, job }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [switchValue, setSwitchValue] = useState(job.remove_from_ar); + + const [updateJob] = useMutation(gql` + mutation REMOVE_FROM_AR_JOB($jobId: uuid!, $remove_from_ar: Boolean!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { remove_from_ar: $remove_from_ar } + ) { + id + remove_from_ar + } + } + `); + + const handleChange = async (value) => { + setLoading(true); + const result = await updateJob({ + variables: { jobId: job.id, remove_from_ar: value }, + }); + + if (!result.errors) { + notification["success"]({ message: t("jobs.successes.save") }); + insertAuditTrail({ + jobid: job.id, + operation: AuditTrailMapping.admin_job_remove_from_ar(value), + }); + setSwitchValue(value); + } else { + notification["error"]({ + message: t("jobs.errors.saving", { + error: JSON.stringify(result.errors), + }), + }); + } + setLoading(false); + }; + + return ( + + + + ); +} diff --git a/client/src/components/report-center-modal/report-center-modal.component.jsx b/client/src/components/report-center-modal/report-center-modal.component.jsx index 913d27282..e37e02164 100644 --- a/client/src/components/report-center-modal/report-center-modal.component.jsx +++ b/client/src/components/report-center-modal/report-center-modal.component.jsx @@ -239,20 +239,30 @@ export function ReportCenterModalComponent({ reportCenterModal }) { else return null; }} - - + + {() => { + const key = form.getFieldValue("key"); + const datedisable = Templates[key] && Templates[key].datedisable; + if (datedisable !== true) { + return ( + + + + ); + } else return null; + }} {() => { diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index 4a1c85036..adfc42765 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -545,147 +545,166 @@ export const QUERY_JOB_COSTING_DETAILS = gql` export const GET_JOB_BY_PK = gql` query GET_JOB_BY_PK($id: uuid!) { jobs_by_pk(id: $id) { - updated_at + actual_completion + actual_delivery + actual_in + adjustment_bottom_line + area_of_damage + auto_add_ats + available_jobs { + id + } + alt_transport + ca_bc_pvrt + ca_customer_gst + ca_gst_registrant + category + cccontracts { + agreementnumber + courtesycar { + fleetnumber + id + make + model + plate + year + } + id + scheduledreturn + start + status + } + cieca_ttl + class + clm_no + clm_total + comment + converted + csiinvites { + completedon + id + } + date_estimated + date_exported + date_invoiced + date_last_contacted + date_lost_sale + date_next_contact + date_open + date_rentalresp + date_repairstarted + date_scheduled + date_towin + date_void + ded_amt + ded_note + ded_status + deliverchecklist + depreciation_taxes + driveable + employee_body employee_body_rel { id first_name last_name } - employee_refinish_rel { - id - first_name - last_name - } - employee_prep_rel { - id - first_name - last_name - } + employee_csr employee_csr_rel { id first_name last_name } - employee_csr employee_prep + employee_prep_rel { + id + first_name + last_name + } employee_refinish - employee_body - alt_transport - intakechecklist - invoice_final_note - comment - loss_desc - kmin - kmout - referral_source - referral_source_extra - unit_number - po_number - special_coverage_policy - scheduled_delivery - converted - lbr_adjustments - ro_number - po_number - clm_total + employee_refinish_rel { + id + first_name + last_name + } + est_co_nm + est_ct_fn + est_ct_ln + est_ea + est_ph1 + federal_tax_rate + id inproduction - vehicleid - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - vehicleid - driveable - towin - loss_of_use - lost_sale_reason - vehicle { - id - plate_no - plate_st - v_vin - v_model_yr - v_model_desc - v_make_desc - v_color - notes - v_paint_codes - jobs { - id - ro_number - status - clm_no - } - } - available_jobs { - id - } - ins_co_id - policy_no - loss_date - clm_no - area_of_damage - ins_co_nm ins_addr1 ins_city + ins_co_id + ins_co_nm ins_ct_ln ins_ct_fn ins_ea ins_ph1 - est_co_nm - est_ct_fn - est_ct_ln - est_ph1 - est_ea - selling_dealer - servicing_dealer - selling_dealer_contact - servicing_dealer_contact - regie_number - scheduled_completion - id - ded_amt - ded_status - depreciation_taxes - other_amount_payable - towing_payable - storage_payable - adjustment_bottom_line - federal_tax_rate - state_tax_rate - local_tax_rate - tax_tow_rt - tax_str_rt - tax_paint_mat_rt - tax_shop_mat_rt - tax_sub_rt - tax_lbr_rt - tax_levies_rt - parts_tax_rates - job_totals - ownr_fn - ownr_ln - ownr_co_nm - ownr_ea - ownr_addr1 - ownr_addr2 - ownr_city - ownr_st - ownr_zip - ownr_ctry - ownr_ph1 - ownr_ph2 - production_vars - ca_gst_registrant - ownerid - ded_note - materials - auto_add_ats - rate_ats + intakechecklist + invoice_final_note iouparent + job_totals + joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) { + act_price + ah_detail_line + alt_partm + alt_partno + billlines(limit: 1, order_by: { bill: { date: desc } }) { + actual_cost + actual_price + bill { + id + invoice_number + vendor { + id + name + } + } + joblineid + id + quantity + } + convertedtolbr + critical + db_hrs + db_price + db_ref + id + ioucreated + lbr_amt + lbr_op + line_desc + line_ind + line_no + line_ref + location + manual_line + mod_lb_hrs + mod_lbr_ty + notes + oem_partno + op_code_desc + part_qty + part_type + prt_dsmk_m + prt_dsmk_p + status + tax_part + unq_seq + } + kmin + kmout + labor_rate_desc + lbr_adjustments + local_tax_rate + loss_date + loss_desc + loss_of_use + lost_sale_reason + materials + other_amount_payable owner { id ownr_fn @@ -702,7 +721,40 @@ export const GET_JOB_BY_PK = gql` ownr_ph2 tax_number } - labor_rate_desc + owner_owing + ownerid + ownr_addr1 + ownr_addr2 + ownr_ctry + ownr_city + ownr_co_nm + ownr_ea + ownr_fn + ownr_ln + ownr_ph1 + ownr_ph2 + ownr_st + ownr_zip + parts_tax_rates + payments { + amount + created_at + date + exportedat + id + jobid + memo + payer + paymentnum + transactionid + type + } + plate_no + plate_st + po_number + policy_no + production_vars + rate_ats rate_la1 rate_la2 rate_la3 @@ -726,121 +778,64 @@ export const GET_JOB_BY_PK = gql` rate_mapa rate_mash rate_matd - actual_in - federal_tax_rate - local_tax_rate - state_tax_rate + regie_number + referral_source + referral_source_extra + remove_from_ar + ro_number scheduled_completion - scheduled_in - actual_completion scheduled_delivery - actual_delivery - date_estimated - date_open - date_scheduled - date_invoiced - date_last_contacted - date_lost_sale - date_next_contact - date_towin - date_rentalresp - date_exported - date_repairstarted - date_void + scheduled_in + selling_dealer + servicing_dealer + selling_dealer_contact + servicing_dealer_contact + special_coverage_policy + state_tax_rate status - owner_owing - tax_registration_number - class - category - deliverchecklist - voided - ca_bc_pvrt - ca_customer_gst + storage_payable suspended - joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) { + tax_lbr_rt + tax_levies_rt + tax_paint_mat_rt + tax_registration_number + tax_shop_mat_rt + tax_str_rt + tax_sub_rt + tax_tow_rt + towin + towing_payable + unit_number + updated_at + v_vin + v_model_yr + v_model_desc + v_make_desc + v_color + vehicleid + vehicle { id - alt_partm - line_no - unq_seq - line_ind - line_desc - line_ref - part_type - oem_partno - alt_partno - db_price - act_price - part_qty - mod_lbr_ty - db_hrs - mod_lb_hrs - lbr_op - lbr_amt - op_code_desc - status + jobs { + clm_no + id + ro_number + status + } notes - location - tax_part - db_ref - manual_line - prt_dsmk_p - prt_dsmk_m - ioucreated - convertedtolbr - ah_detail_line - critical - billlines(limit: 1, order_by: { bill: { date: desc } }) { - id - quantity - actual_cost - actual_price - joblineid - bill { - id - invoice_number - vendor { - id - name - } - } - } - } - payments { - id - jobid - amount - payer - paymentnum - created_at - transactionid - memo - date - type - exportedat - } - cccontracts { - id - status - start - scheduledreturn - agreementnumber - courtesycar { - id - make - model - year - plate - fleetnumber - } - } - cieca_ttl - csiinvites { - id - completedon + plate_no + plate_st + v_color + v_make_desc + v_model_desc + v_model_yr + v_paint_codes + v_vin } + voided } } `; + export const GET_JOB_RECONCILIATION_BY_PK = gql` query GET_JOB_RECONCILIATION_BY_PK($id: uuid!) { bills(where: { jobid: { _eq: $id } }) { @@ -905,6 +900,7 @@ export const GET_JOB_RECONCILIATION_BY_PK = gql` } } `; + export const QUERY_JOB_CARD_DETAILS = gql` query QUERY_JOB_CARD_DETAILS($id: uuid!) { jobs_by_pk(id: $id) { diff --git a/client/src/pages/jobs-admin/jobs-admin.page.jsx b/client/src/pages/jobs-admin/jobs-admin.page.jsx index 0d08e5114..0c7eb9a33 100644 --- a/client/src/pages/jobs-admin/jobs-admin.page.jsx +++ b/client/src/pages/jobs-admin/jobs-admin.page.jsx @@ -7,16 +7,16 @@ import { useParams } from "react-router-dom"; import AlertComponent from "../../components/alert/alert.component"; import JobCalculateTotals from "../../components/job-calculate-totals/job-calculate-totals.component"; import ScoreboardAddButton from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component"; +import JobsAdminStatus from "../../components/jobs-admin-change-status/jobs-admin-change.status.component"; import JobsAdminClass from "../../components/jobs-admin-class/jobs-admin-class.component"; import JobsAdminDatesChange from "../../components/jobs-admin-dates/jobs-admin-dates.component"; import JobsAdminDeleteIntake from "../../components/jobs-admin-delete-intake/jobs-admin-delete-intake.component"; import JobsAdminMarkReexport from "../../components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component"; import JobAdminOwnerReassociate from "../../components/jobs-admin-owner-reassociate/jobs-admin-owner-reassociate.component"; +import JobsAdminRemoveAR from "../../components/jobs-admin-remove-ar/jobs-admin-remove-ar.component"; import JobsAdminUnvoid from "../../components/jobs-admin-unvoid/jobs-admin-unvoid.component"; import JobAdminVehicleReassociate from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; -import JobsAdminStatus from "../../components/jobs-admin-change-status/jobs-admin-change.status.component"; - import NotFound from "../../components/not-found/not-found.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import { GET_JOB_BY_PK } from "../../graphql/jobs.queries"; @@ -104,6 +104,7 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) { + diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 919f7e297..1589e9b5f 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -99,6 +99,7 @@ }, "audit_trail": { "messages": { + "admin_job_remove_from_ar": "ADMIN: Remove from AR updated to: {{status}}", "admin_jobmarkexported": "ADMIN: Job marked as exported.", "admin_jobmarkforreexport": "ADMIN: Job marked for re-export.", "admin_jobuninvoice": "ADMIN: Job has been uninvoiced.", @@ -1833,6 +1834,7 @@ }, "reconciliationheader": "Parts & Sublet Reconciliation", "relatedros": "Related ROs", + "remove_from_ar": "Remove from AR", "returntotals": "Return Totals", "rosaletotal": "RO Parts Total", "sale_additional": "Sales - Additional", @@ -2563,6 +2565,7 @@ }, "templates": { "anticipated_revenue": "Anticipated Revenue", + "ar_aging": "AR Aging", "attendance_detail": "Attendance (All Employees)", "attendance_employee": "Employee Attendance", "attendance_summary": "Attendance Summary (All Employees)", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index a798f790c..fc977e7eb 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -99,6 +99,7 @@ }, "audit_trail": { "messages": { + "admin_job_remove_from_ar": "", "admin_jobmarkexported": "", "admin_jobmarkforreexport": "", "admin_jobuninvoice": "", @@ -1833,6 +1834,7 @@ }, "reconciliationheader": "", "relatedros": "", + "remove_from_ar": "", "returntotals": "", "rosaletotal": "", "sale_additional": "", @@ -2563,6 +2565,7 @@ }, "templates": { "anticipated_revenue": "", + "ar_aging": "", "attendance_detail": "", "attendance_employee": "", "attendance_summary": "", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index b65abaf04..76e3ce6a9 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -99,6 +99,7 @@ }, "audit_trail": { "messages": { + "admin_job_remove_from_ar": "", "admin_jobmarkexported": "", "admin_jobmarkforreexport": "", "admin_jobuninvoice": "", @@ -1833,6 +1834,7 @@ }, "reconciliationheader": "", "relatedros": "", + "remove_from_ar": "", "returntotals": "", "rosaletotal": "", "sale_additional": "", @@ -2563,6 +2565,7 @@ }, "templates": { "anticipated_revenue": "", + "ar_aging": "", "attendance_detail": "", "attendance_employee": "", "attendance_summary": "", diff --git a/client/src/utils/AuditTrailMappings.js b/client/src/utils/AuditTrailMappings.js index d7098fa2d..eefbb3a11 100644 --- a/client/src/utils/AuditTrailMappings.js +++ b/client/src/utils/AuditTrailMappings.js @@ -1,54 +1,56 @@ import i18n from "i18next"; const AuditTrailMapping = { - alertToggle: (status) => i18n.t("audit_trail.messages.alerttoggle", { status }), + admin_job_remove_from_ar: (status) => + i18n.t("audit_trail.messages.admin_job_remove_from_ar", { status }), + admin_jobfieldchange: (field, value) => + "ADMIN: " + + i18n.t("audit_trail.messages.jobfieldchanged", { field, value }), + admin_jobmarkexported: () => + i18n.t("audit_trail.messages.admin_jobmarkexported"), + admin_jobmarkforreexport: () => + i18n.t("audit_trail.messages.admin_jobmarkforreexport"), + admin_jobstatuschange: (status) => + "ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }), + admin_jobuninvoice: () => i18n.t("audit_trail.messages.admin_jobuninvoice"), + admin_jobunvoid: () => i18n.t("audit_trail.messages.admin_jobunvoid"), + alertToggle: (status) => + i18n.t("audit_trail.messages.alerttoggle", { status }), appointmentcancel: (lost_sale_reason) => i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }), appointmentinsert: (start) => i18n.t("audit_trail.messages.appointmentinsert", { start }), - jobstatuschange: (status) => - i18n.t("audit_trail.messages.jobstatuschange", { status }), - admin_jobstatuschange: (status) => - "ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }), - jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"), - jobimported: () => i18n.t("audit_trail.messages.jobimported"), - jobinvoiced: () => - i18n.t("audit_trail.messages.jobinvoiced"), - jobconverted: (ro_number) => - i18n.t("audit_trail.messages.jobconverted", { ro_number }), - jobfieldchange: (field, value) => - i18n.t("audit_trail.messages.jobfieldchanged", { field, value }), - admin_jobfieldchange: (field, value) => - "ADMIN: " + - i18n.t("audit_trail.messages.jobfieldchanged", { field, value }), - jobspartsorder: (order_number) => - i18n.t("audit_trail.messages.jobspartsorder", { order_number }), - jobspartsreturn: (order_number) => - i18n.t("audit_trail.messages.jobspartsreturn", { order_number }), - jobmodifylbradj: ({ mod_lbr_ty, hours }) => - i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }), billposted: (invoice_number) => i18n.t("audit_trail.messages.billposted", { invoice_number }), billupdated: (invoice_number) => i18n.t("audit_trail.messages.billupdated", { invoice_number }), + failedpayment: () => i18n.t("audit_trail.messages.failedpayment"), jobassignmentchange: (operation, name) => i18n.t("audit_trail.messages.jobassignmentchange", { operation, name }), jobassignmentremoved: (operation) => i18n.t("audit_trail.messages.jobassignmentremoved", { operation }), - jobinproductionchange: (inproduction) => - i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }), jobchecklist: (type, inproduction, status) => i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }), + jobconverted: (ro_number) => + i18n.t("audit_trail.messages.jobconverted", { ro_number }), + jobfieldchange: (field, value) => + i18n.t("audit_trail.messages.jobfieldchanged", { field, value }), + jobimported: () => i18n.t("audit_trail.messages.jobimported"), + jobinproductionchange: (inproduction) => + i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }), + jobinvoiced: () => i18n.t("audit_trail.messages.jobinvoiced"), + 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"), jobnotedeleted: () => i18n.t("audit_trail.messages.jobnotedeleted"), - admin_jobunvoid: () => i18n.t("audit_trail.messages.admin_jobunvoid"), - admin_jobuninvoice: () => i18n.t("audit_trail.messages.admin_jobuninvoice"), - admin_jobmarkforreexport: () => - i18n.t("audit_trail.messages.admin_jobmarkforreexport"), - admin_jobmarkexported: () => - i18n.t("audit_trail.messages.admin_jobmarkexported"), - failedpayment: () => i18n.t("audit_trail.messages.failedpayment"), + jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"), + jobspartsorder: (order_number) => + i18n.t("audit_trail.messages.jobspartsorder", { order_number }), + jobspartsreturn: (order_number) => + i18n.t("audit_trail.messages.jobspartsreturn", { order_number }), + jobstatuschange: (status) => + i18n.t("audit_trail.messages.jobstatuschange", { status }), + jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"), }; export default AuditTrailMapping; diff --git a/client/src/utils/TemplateConstants.js b/client/src/utils/TemplateConstants.js index eeb937c3a..ad614af3b 100644 --- a/client/src/utils/TemplateConstants.js +++ b/client/src/utils/TemplateConstants.js @@ -2020,6 +2020,7 @@ export const TemplateList = (type, context) => { key: "lost_sales", //idtype: "vendor", disabled: false, + datedisable: true, rangeFilter: { object: i18n.t("reportcenter.labels.objects.jobs"), field: i18n.t("jobs.fields.date_lost_sale"), @@ -2039,6 +2040,15 @@ export const TemplateList = (type, context) => { }, group: "jobs", }, + ar_aging: { + title: i18n.t("reportcenter.templates.ar_aging"), + subject: i18n.t("reportcenter.templates.ar_aging"), + key: "ar_aging", + //idtype: "vendor", + disabled: false, + datedisable: true, + group: "customers", + }, } : {}), ...(!type || type === "courtesycarcontract" From 36dd97394f99db593026281ffff88a2d78c3a94c Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 24 Jan 2024 16:15:32 -0800 Subject: [PATCH 2/4] IO-2543 Adjust for having no dates --- .../report-center-modal/report-center-modal.component.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/report-center-modal/report-center-modal.component.jsx b/client/src/components/report-center-modal/report-center-modal.component.jsx index e37e02164..c4bef11c3 100644 --- a/client/src/components/report-center-modal/report-center-modal.component.jsx +++ b/client/src/components/report-center-modal/report-center-modal.component.jsx @@ -68,8 +68,8 @@ export function ReportCenterModalComponent({ reportCenterModal }) { const handleFinish = async (values) => { setLoading(true); - const start = values.dates[0]; - const end = values.dates[1]; + const start = values.dates ? values.dates[0] : null; + const end = values.dates ? values.dates[1] : null; const { id } = values; await GenerateDocument( From eb8519dc1d0ddb982c58a6df0d180f8fedf1cb07 Mon Sep 17 00:00:00 2001 From: Allan Carr Date: Wed, 24 Jan 2024 18:37:41 -0800 Subject: [PATCH 3/4] IO-2543 Move queries to jobs.queries.js and correct layout --- .../jobs-admin-change.status.component.jsx | 14 ++- .../jobs-admin-delete-intake.component.jsx | 56 ++++----- .../jobs-admin-mark-reexport.component.jsx | 118 +++++++----------- .../jobs-admin-remove-ar.component.jsx | 36 +++--- .../jobs-admin-unvoid.component.jsx | 76 +++-------- client/src/graphql/jobs.queries.js | 117 +++++++++++++++++ 6 files changed, 230 insertions(+), 187 deletions(-) diff --git a/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx b/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx index edef81343..54a020d37 100644 --- a/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx +++ b/client/src/components/jobs-admin-change-status/jobs-admin-change.status.component.jsx @@ -53,12 +53,14 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) { ); return ( - - - + + + + ); } diff --git a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx index 190778437..ec1bd976b 100644 --- a/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx +++ b/client/src/components/jobs-admin-delete-intake/jobs-admin-delete-intake.component.jsx @@ -1,34 +1,18 @@ import { useMutation } from "@apollo/client"; -import { Button, notification } from "antd"; -import { gql } from "@apollo/client"; +import { Button, Space, notification } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; +import { + DELETE_DELIVERY_CHECKLIST, + DELETE_INTAKE_CHECKLIST, +} from "../../graphql/jobs.queries"; + export default function JobAdminDeleteIntake({ job }) { const { t } = useTranslation(); const [loading, setLoading] = useState(false); - const [deleteIntake] = useMutation(gql` - mutation DELETE_INTAKE($jobId: uuid!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { intakechecklist: null } - ) { - id - intakechecklist - } - } - `); - const [DELETE_DELIVERY] = useMutation(gql` - mutation DELETE_DELIVERY($jobId: uuid!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { deliverchecklist: null } - ) { - id - deliverchecklist - } - } - `); + const [deleteIntake] = useMutation(DELETE_INTAKE_CHECKLIST); + const [deleteDelivery] = useMutation(DELETE_DELIVERY_CHECKLIST); const handleDelete = async (values) => { setLoading(true); @@ -50,7 +34,7 @@ export default function JobAdminDeleteIntake({ job }) { const handleDeleteDelivery = async (values) => { setLoading(true); - const result = await DELETE_DELIVERY({ + const result = await deleteDelivery({ variables: { jobId: job.id }, }); @@ -68,12 +52,22 @@ export default function JobAdminDeleteIntake({ job }) { return ( <> - - + + + + ); } diff --git a/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx b/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx index c47c30def..ae193fa72 100644 --- a/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx +++ b/client/src/components/jobs-admin-mark-reexport/jobs-admin-mark-reexport.component.jsx @@ -1,5 +1,5 @@ -import { gql, useMutation } from "@apollo/client"; -import { Button, notification } from "antd"; +import { useMutation } from "@apollo/client"; +import { Button, Space, notification } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -7,6 +7,11 @@ import moment from "moment"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; +import { + MARK_JOB_AS_EXPORTED, + MARK_JOB_AS_UNINVOICED, + MARK_JOB_FOR_REEXPORT, +} from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import { selectBodyshop, @@ -35,58 +40,18 @@ export function JobAdminMarkReexport({ const { t } = useTranslation(); const [loading, setLoading] = useState(false); const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); - const [markJobForReexport] = useMutation(gql` - mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { date_exported: null - status: "${bodyshop.md_ro_statuses.default_invoiced}" - } - ) { - id - date_exported - status - date_invoiced - } - } - `); - const [markJobExported] = useMutation(gql` - mutation MARK_JOB_AS_EXPORTED($jobId: uuid!, $date_exported: timestamptz!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { date_exported: $date_exported - status: "${bodyshop.md_ro_statuses.default_exported}" - } - ) { - id - date_exported - date_invoiced - status - } - } - `); - const [markJobUninvoiced] = useMutation(gql` - mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, ) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { date_exported: null - date_invoiced: null - status: "${bodyshop.md_ro_statuses.default_delivered}" - } - ) { - id - date_exported - date_invoiced - status - } - } - `); + const [markJobForReexport] = useMutation(MARK_JOB_FOR_REEXPORT); + const [markJobExported] = useMutation(MARK_JOB_AS_EXPORTED); + const [markJobUninvoiced] = useMutation(MARK_JOB_AS_UNINVOICED); const handleMarkForExport = async () => { setLoading(true); const result = await markJobForReexport({ - variables: { jobId: job.id }, + variables: { + jobId: job.id, + default_invoiced: bodyshop.md_ro_statuses.default_invoiced, + }, }); if (!result.errors) { @@ -108,7 +73,11 @@ export function JobAdminMarkReexport({ const handleMarkExported = async () => { setLoading(true); const result = await markJobExported({ - variables: { jobId: job.id, date_exported: moment() }, + variables: { + jobId: job.id, + date_exported: moment(), + default_exported: bodyshop.md_ro_statuses.default_exported, + }, }); await insertExportLog({ @@ -144,7 +113,10 @@ export function JobAdminMarkReexport({ const handleUninvoice = async () => { setLoading(true); const result = await markJobUninvoiced({ - variables: { jobId: job.id }, + variables: { + jobId: job.id, + default_delivered: bodyshop.md_ro_statuses.default_delivered, + }, }); if (!result.errors) { @@ -165,27 +137,29 @@ export function JobAdminMarkReexport({ return ( <> - - - + + + + + ); } diff --git a/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx index c85404f48..f1bda15ce 100644 --- a/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx +++ b/client/src/components/jobs-admin-remove-ar/jobs-admin-remove-ar.component.jsx @@ -1,9 +1,10 @@ -import { gql, useMutation } from "@apollo/client"; -import { Form, Switch, notification } from "antd"; +import { useMutation } from "@apollo/client"; +import { Switch, notification } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; +import { UPDATE_REMOVE_FROM_AR } from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import AuditTrailMapping from "../../utils/AuditTrailMappings"; @@ -20,21 +21,11 @@ export function JobsAdminRemoveAR({ insertAuditTrail, job }) { const [loading, setLoading] = useState(false); const [switchValue, setSwitchValue] = useState(job.remove_from_ar); - const [updateJob] = useMutation(gql` - mutation REMOVE_FROM_AR_JOB($jobId: uuid!, $remove_from_ar: Boolean!) { - update_jobs_by_pk( - pk_columns: { id: $jobId } - _set: { remove_from_ar: $remove_from_ar } - ) { - id - remove_from_ar - } - } - `); + const [mutationUpdateRemoveFromAR] = useMutation(UPDATE_REMOVE_FROM_AR); const handleChange = async (value) => { setLoading(true); - const result = await updateJob({ + const result = await mutationUpdateRemoveFromAR({ variables: { jobId: job.id, remove_from_ar: value }, }); @@ -56,8 +47,19 @@ export function JobsAdminRemoveAR({ insertAuditTrail, job }) { }; return ( - - - + <> +
+
+ {t("jobs.labels.remove_from_ar")}: +
+
+ +
+
+ ); } diff --git a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx index 7963fd05f..2094178c4 100644 --- a/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx +++ b/client/src/components/jobs-admin-unvoid/jobs-admin-unvoid.component.jsx @@ -1,9 +1,10 @@ -import { gql, useMutation } from "@apollo/client"; +import { useMutation } from "@apollo/client"; import { Button, notification } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { createStructuredSelector } from "reselect"; +import { UNVOID_JOB } from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions"; import { selectBodyshop, @@ -29,66 +30,17 @@ export function JobsAdminUnvoid({ }) { const { t } = useTranslation(); const [loading, setLoading] = useState(false); - const [updateJob] = useMutation(gql` -mutation UNVOID_JOB($jobId: uuid!) { - update_jobs_by_pk(pk_columns: {id: $jobId}, _set: {voided: false, status: "${ - bodyshop.md_ro_statuses.default_imported - }", date_void: null}) { - id - date_void - voided - status - } - insert_notes(objects: {jobid: $jobId, audit: true, created_by: "${ - currentUser.email - }", text: "${t("jobs.labels.unvoidnote")}"}) { - returning { - id - } - } -} - - `); - - // const result = await voidJob({ - // variables: { - // jobId: job.id, - // job: { - // status: bodyshop.md_ro_statuses.default_void, - // voided: true, - // }, - // note: [ - // { - // jobid: job.id, - // created_by: currentUser.email, - // audit: true, - // text: t("jobs.labels.voidnote", { - // date: moment().format("MM/DD/yyy"), - // time: moment().format("hh:mm a"), - // }), - // }, - // ], - // }, - // }); - - // if (!!!result.errors) { - // notification["success"]({ - // message: t("jobs.successes.voided"), - // }); - // //go back to jobs list. - // history.push(`/manage/`); - // } else { - // notification["error"]({ - // message: t("jobs.errors.voiding", { - // error: JSON.stringify(result.errors), - // }), - // }); - // } + const [mutationUnvoidJob] = useMutation(UNVOID_JOB); const handleUpdate = async (values) => { setLoading(true); - const result = await updateJob({ - variables: { jobId: job.id }, + const result = await mutationUnvoidJob({ + variables: { + jobId: job.id, + default_imported: bodyshop.md_ro_statuses.default_imported, + currentUserEmail: currentUser.email, + text: t("jobs.labels.unvoidnote"), + }, }); if (!result.errors) { @@ -110,8 +62,10 @@ mutation UNVOID_JOB($jobId: uuid!) { }; return ( - + <> + + ); } diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js index adfc42765..57bb935f8 100644 --- a/client/src/graphql/jobs.queries.js +++ b/client/src/graphql/jobs.queries.js @@ -2221,3 +2221,120 @@ export const GET_JOB_LINE_ORDERS = gql` } } `; + +export const UPDATE_REMOVE_FROM_AR = gql` + mutation UPDATE_REMOVE_FROM_AR($jobId: uuid!, $remove_from_ar: Boolean!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { remove_from_ar: $remove_from_ar } + ) { + id + remove_from_ar + } + } +`; + +export const UNVOID_JOB = gql` + mutation UNVOID_JOB( + $jobId: uuid! + $default_imported: String! + $currentUserEmail: String! + $text: String! + ) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { voided: false, status: $default_imported, date_void: null } + ) { + id + date_void + voided + status + } + insert_notes( + objects: { + jobid: $jobId + audit: true + created_by: $currentUserEmail + text: $text + } + ) { + returning { + id + } + } + } +`; + +export const DELETE_INTAKE_CHECKLIST = gql` + mutation DELETE_INTAKE($jobId: uuid!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { intakechecklist: null } + ) { + id + intakechecklist + } + } +`; + +export const DELETE_DELIVERY_CHECKLIST = gql` + mutation DELETE_DELIVERY($jobId: uuid!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { deliverchecklist: null } + ) { + id + deliverchecklist + } + } +`; + +export const MARK_JOB_FOR_REEXPORT = gql` + mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!, $default_invoiced: String!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { date_exported: null, status: $default_invoiced } + ) { + id + date_exported + status + date_invoiced + } + } +`; + +export const MARK_JOB_AS_EXPORTED = gql` + mutation MARK_JOB_AS_EXPORTED( + $jobId: uuid! + $date_exported: timestamptz! + $default_exported: String! + ) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { date_exported: $date_exported, status: $default_exported } + ) { + id + date_exported + date_invoiced + status + } + } +`; + +export const MARK_JOB_AS_UNINVOICED = gql` + mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, $default_delivered: String!) { + update_jobs_by_pk( + pk_columns: { id: $jobId } + _set: { + date_exported: null + date_invoiced: null + status: $default_delivered + } + ) { + id + date_exported + date_invoiced + status + } + } +`; From 50f84d40e1dc13d0143fd68c293b5a695bda8dfb Mon Sep 17 00:00:00 2001 From: Patrick Fic Date: Thu, 25 Jan 2024 10:30:03 -0800 Subject: [PATCH 4/4] Allow negative balance for AR. --- .../1706207204357_run_sql_migration/down.sql | 35 +++++++++++++++++++ .../1706207204357_run_sql_migration/up.sql | 33 +++++++++++++++++ .../1706207267558_run_sql_migration/down.sql | 35 +++++++++++++++++++ .../1706207267558_run_sql_migration/up.sql | 33 +++++++++++++++++ 4 files changed, 136 insertions(+) create mode 100644 hasura/migrations/1706207204357_run_sql_migration/down.sql create mode 100644 hasura/migrations/1706207204357_run_sql_migration/up.sql create mode 100644 hasura/migrations/1706207267558_run_sql_migration/down.sql create mode 100644 hasura/migrations/1706207267558_run_sql_migration/up.sql diff --git a/hasura/migrations/1706207204357_run_sql_migration/down.sql b/hasura/migrations/1706207204357_run_sql_migration/down.sql new file mode 100644 index 000000000..1deabac64 --- /dev/null +++ b/hasura/migrations/1706207204357_run_sql_migration/down.sql @@ -0,0 +1,35 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- CREATE OR REPLACE FUNCTION public.jobs_ar_summary () +-- RETURNS SETOF job_ar_schema +-- LANGUAGE plpgsql +-- STABLE +-- AS $function$ +-- BEGIN +-- +-- RETURN query +-- select +-- j.id, +-- j.ro_number, +-- j.clm_total, +-- coalesce (p.total_payments,0) as total_payments, +-- j.clm_total - coalesce (p.total_payments,0) as balance, +-- j.date_invoiced, +-- j.shopid +-- from +-- jobs j +-- left join ( +-- select +-- p.jobid, +-- coalesce (sum(p.amount),0) as total_payments +-- from +-- payments p +-- group by +-- p.jobid +-- ) p on +-- j.id = p.jobid +-- where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0; +-- +-- +-- END +-- $function$; diff --git a/hasura/migrations/1706207204357_run_sql_migration/up.sql b/hasura/migrations/1706207204357_run_sql_migration/up.sql new file mode 100644 index 000000000..6a42b8d7a --- /dev/null +++ b/hasura/migrations/1706207204357_run_sql_migration/up.sql @@ -0,0 +1,33 @@ +CREATE OR REPLACE FUNCTION public.jobs_ar_summary () + RETURNS SETOF job_ar_schema + LANGUAGE plpgsql + STABLE + AS $function$ +BEGIN + + RETURN query +select + j.id, + j.ro_number, + j.clm_total, + coalesce (p.total_payments,0) as total_payments, + j.clm_total - coalesce (p.total_payments,0) as balance, + j.date_invoiced, + j.shopid +from + jobs j +left join ( + select + p.jobid, + coalesce (sum(p.amount),0) as total_payments + from + payments p + group by + p.jobid + ) p on + j.id = p.jobid +where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0; + + +END +$function$; diff --git a/hasura/migrations/1706207267558_run_sql_migration/down.sql b/hasura/migrations/1706207267558_run_sql_migration/down.sql new file mode 100644 index 000000000..1deabac64 --- /dev/null +++ b/hasura/migrations/1706207267558_run_sql_migration/down.sql @@ -0,0 +1,35 @@ +-- Could not auto-generate a down migration. +-- Please write an appropriate down migration for the SQL below: +-- CREATE OR REPLACE FUNCTION public.jobs_ar_summary () +-- RETURNS SETOF job_ar_schema +-- LANGUAGE plpgsql +-- STABLE +-- AS $function$ +-- BEGIN +-- +-- RETURN query +-- select +-- j.id, +-- j.ro_number, +-- j.clm_total, +-- coalesce (p.total_payments,0) as total_payments, +-- j.clm_total - coalesce (p.total_payments,0) as balance, +-- j.date_invoiced, +-- j.shopid +-- from +-- jobs j +-- left join ( +-- select +-- p.jobid, +-- coalesce (sum(p.amount),0) as total_payments +-- from +-- payments p +-- group by +-- p.jobid +-- ) p on +-- j.id = p.jobid +-- where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0; +-- +-- +-- END +-- $function$; diff --git a/hasura/migrations/1706207267558_run_sql_migration/up.sql b/hasura/migrations/1706207267558_run_sql_migration/up.sql new file mode 100644 index 000000000..6a42b8d7a --- /dev/null +++ b/hasura/migrations/1706207267558_run_sql_migration/up.sql @@ -0,0 +1,33 @@ +CREATE OR REPLACE FUNCTION public.jobs_ar_summary () + RETURNS SETOF job_ar_schema + LANGUAGE plpgsql + STABLE + AS $function$ +BEGIN + + RETURN query +select + j.id, + j.ro_number, + j.clm_total, + coalesce (p.total_payments,0) as total_payments, + j.clm_total - coalesce (p.total_payments,0) as balance, + j.date_invoiced, + j.shopid +from + jobs j +left join ( + select + p.jobid, + coalesce (sum(p.amount),0) as total_payments + from + payments p + group by + p.jobid + ) p on + j.id = p.jobid +where j.remove_from_ar = false and j.date_invoiced is not null and j.clm_total - coalesce (p.total_payments,0) != 0; + + +END +$function$;