Merge branch 'feature/intellipay' into release/2023-08-25
This commit is contained in:
@@ -1,49 +0,0 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||
});
|
||||
|
||||
function Test({ bodyshop, setEmailOptions }) {
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEmailOptions({
|
||||
messageOptions: {
|
||||
to: ["patrickwf@gmail.com"],
|
||||
replyTo: bodyshop.email,
|
||||
},
|
||||
template: {
|
||||
name: TemplateList().parts_order.key,
|
||||
variables: {
|
||||
id: "a7c2d4e1-f519-42a9-a071-c48cf0f22979",
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
send email
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
logImEXEvent("IMEXEVENT", { somethignArThare: 5 });
|
||||
}}
|
||||
>
|
||||
Log an ImEX Event.
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Test);
|
||||
63
client/src/components/_test/payment_response.json
Normal file
63
client/src/components/_test/payment_response.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"status": 24201299,
|
||||
"custid": 19607899,
|
||||
"paymentid": 24201299,
|
||||
"response": "A",
|
||||
"authcode": "498680",
|
||||
"declinereason": "Approved",
|
||||
"fee": 0,
|
||||
"invoice": "",
|
||||
"account": "john",
|
||||
"amount": 1000,
|
||||
"amountincludesfee": false,
|
||||
"total": 1000,
|
||||
"paymenttype": "C",
|
||||
"methodhint": "VI ***1111",
|
||||
"cardbrand": "Visa",
|
||||
"cardnumdisplay": "***1111",
|
||||
"receiptelements": {
|
||||
"authcode": "498680",
|
||||
"cust_srv_ph_num": "1-555-555-5555",
|
||||
"rcpt_pg_ftr_txt": "Thank You\nPlease Come Again",
|
||||
"rcpt_currency": "USD",
|
||||
"responsecode": "A",
|
||||
"rcpt_pay_mthd": "Visa",
|
||||
"transid": "C00 915799",
|
||||
"merch_disp_nm": "CP Devel Test",
|
||||
"rcpt_input_mthd": "Keyed",
|
||||
"rcpt_pg_hdr_txt": "Welcome!",
|
||||
"rcpt_tran_time": "Thursday February 23 2023, 11:25:36 pm +08",
|
||||
"rcpt_trans_type": "Normal Transaction (Sale)",
|
||||
"message": "Approved",
|
||||
"rcpt_dba_addr": "1234 Storefront Ave\nSome City, UT 84111",
|
||||
"avsdata": "N",
|
||||
"receiptrequirements": "S",
|
||||
"rcpt_cardnum": "************1111",
|
||||
"cv2result": "M",
|
||||
"rfnd_policy_txt": "<b>No Refunds</b>\nStore Credit Only",
|
||||
"labels": {
|
||||
"tranref": "REF#",
|
||||
"tid": "TID",
|
||||
"validationcode": "ValCode",
|
||||
"emvapplicationid": "AID",
|
||||
"emvatc": "ATC",
|
||||
"rcpt_pay_mthd": "Pay Method",
|
||||
"transid": "TransID",
|
||||
"rcpt_input_mthd": "IMode",
|
||||
"emvtsi": "TSI",
|
||||
"emvac": "AC",
|
||||
"rcpt_trans_type": "TranType",
|
||||
"emvapplicationname": "PApp",
|
||||
"visarewards": "RewardsProg"
|
||||
}
|
||||
},
|
||||
"receipttoken": "H4sIAAAAAAAAACXMTQuCMBgA4P/ynh3tw_3dBI/ipQ8NOtRN53QiblpBRfTfCzo/8LwhxGAdZCCwFYoJJFQjI2kvHdGu74lVkgmrWyWNhASW5jW7cB87yHjKKePGJODnxrrnMl7dDTKmEJlSOqV/_N30XPpyj2Eddq57_KKZ8FLzmh_G1VQnVfhjiXGK1XYTc/h8AVOkf4qUAAAA",
|
||||
"call": "card_payment",
|
||||
"nonce": "488b5568-b5c1-4f38-8b2f-3b050f3abb11P",
|
||||
"hmac": "JyPAJ9Yx0SlYBTtqns1OxAFRt+xF3l2UiLPO5zTDRBE=",
|
||||
"paymentreferenceid": "C19607899P24201299",
|
||||
"cardnum": "...1111",
|
||||
"email": "",
|
||||
"nameOnCard": "John Allen",
|
||||
"cardType": "visa"
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from "react";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
export default function Test() {
|
||||
return (
|
||||
<div>
|
||||
<QboAuthorizeComponent />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
31
client/src/components/_test/test.page.jsx
Normal file
31
client/src/components/_test/test.page.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Button } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setRefundPaymentContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "refund_payment" })),
|
||||
});
|
||||
|
||||
function Test({ setRefundPaymentContext, refundPaymentModal }) {
|
||||
console.log("refundPaymentModal", refundPaymentModal);
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
onClick={() =>
|
||||
setRefundPaymentContext({
|
||||
context: {},
|
||||
})
|
||||
}
|
||||
>
|
||||
Open Modal
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Test);
|
||||
@@ -0,0 +1,281 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Row,
|
||||
Spin,
|
||||
notification,
|
||||
} from "antd";
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
INSERT_PAYMENT_RESPONSE,
|
||||
QUERY_RO_AND_OWNER_BY_JOB_PK,
|
||||
} from "../../graphql/payment_response.queries";
|
||||
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
cardPaymentModal: selectCardPayment,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
|
||||
});
|
||||
|
||||
const CardPaymentModalComponent = ({
|
||||
bodyshop,
|
||||
cardPaymentModal,
|
||||
toggleModalVisible,
|
||||
insertAuditTrail,
|
||||
}) => {
|
||||
const { context } = cardPaymentModal;
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const amount = Form.useWatch("amount", form);
|
||||
const jobid = Form.useWatch("jobid", form);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { data, refetch } = useQuery(QUERY_RO_AND_OWNER_BY_JOB_PK, {
|
||||
variables: { jobid: context?.jobid ?? "" },
|
||||
skip: !context?.jobid,
|
||||
});
|
||||
|
||||
//Initialize the intellipay window.
|
||||
const SetIntellipayCallbackFunctions = () => {
|
||||
console.log("*** Set IntelliPay callback functions.");
|
||||
window.intellipay.runOnClose(() => {
|
||||
//window.intellipay.initialize();
|
||||
});
|
||||
|
||||
window.intellipay.runOnApproval(async function (response) {
|
||||
console.warn("*** Running On Approval Script ***");
|
||||
form.setFieldValue("paymentResponse", response);
|
||||
form.submit();
|
||||
});
|
||||
|
||||
window.intellipay.runOnNonApproval(async function (response) {
|
||||
// Mutate unsuccessful payment
|
||||
await insertPaymentResponse({
|
||||
variables: {
|
||||
paymentResponse: {
|
||||
amount: response.amount,
|
||||
bodyshopid: bodyshop.id,
|
||||
jobid: jobid || context.jobid,
|
||||
declinereason: response.declinereason,
|
||||
ext_paymentid: response.paymentid.toString(),
|
||||
successful: false,
|
||||
response,
|
||||
},
|
||||
},
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: jobid || context?.jobid,
|
||||
operation: AuditTrailMapping.failedpayment(),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
try {
|
||||
const paymentResult = await insertPayment({
|
||||
variables: {
|
||||
paymentInput: {
|
||||
amount: values.amount,
|
||||
transactionid: (values.paymentResponse.paymentid || "").toString(),
|
||||
payer: t("payments.labels.customer"),
|
||||
type: values.paymentResponse.cardbrand,
|
||||
jobid: values.jobid,
|
||||
date: moment(Date.now()),
|
||||
},
|
||||
},
|
||||
refetchQueries: ["GET_JOB_BY_PK"],
|
||||
update(cache, { data }) {
|
||||
cache.modify({
|
||||
id: cache.identify({ id: values.jobid, __typename: "jobs" }),
|
||||
fields: {
|
||||
payments(cachedPayments) {
|
||||
return [...data.insert_payments.returning, ...cachedPayments];
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await insertPaymentResponse({
|
||||
variables: {
|
||||
paymentResponse: {
|
||||
amount: values.amount,
|
||||
bodyshopid: bodyshop.id,
|
||||
paymentid: paymentResult.data.insert_payments.returning[0].id,
|
||||
jobid: values.jobid,
|
||||
declinereason: values.paymentResponse.declinereason,
|
||||
ext_paymentid: values.paymentResponse.paymentid.toString(),
|
||||
successful: true,
|
||||
response: values.paymentResponse,
|
||||
},
|
||||
},
|
||||
});
|
||||
toggleModalVisible();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIntelliPayCharge = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
console.warn("*** Window.Intellipay", !!window.intellipay);
|
||||
|
||||
const response = await axios.post("/intellipay/lightbox_credentials", {
|
||||
bodyshop,
|
||||
refresh: !!window.intellipay,
|
||||
});
|
||||
|
||||
if (window.intellipay) {
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(response.data);
|
||||
SetIntellipayCallbackFunctions();
|
||||
window.intellipay.autoOpen();
|
||||
} else {
|
||||
var rg = document.createRange();
|
||||
let node = rg.createContextualFragment(response.data);
|
||||
document.documentElement.appendChild(node);
|
||||
SetIntellipayCallbackFunctions();
|
||||
window.intellipay.isAutoOpen = true;
|
||||
window.intellipay.initialize();
|
||||
}
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("job_payments.notifications.error.openingip"),
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title="Card Payment">
|
||||
<Spin spinning={loading}>
|
||||
<Form
|
||||
onFinish={handleFinish}
|
||||
form={form}
|
||||
initialValues={{ jobid: context?.jobid }}
|
||||
>
|
||||
<LayoutFormRow grow noDivider>
|
||||
<Form.Item
|
||||
name="jobid"
|
||||
label={t("bills.fields.ro_number")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
// message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<JobSearchSelectComponent
|
||||
disabled={context?.jobid}
|
||||
notExported={false}
|
||||
clm_no
|
||||
onChange={(e) => {
|
||||
refetch({ jobid: e });
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
{/* Lighbox Input amount needs to be hidden */}
|
||||
<Input
|
||||
className="ipayfield"
|
||||
data-ipayname="amount"
|
||||
type="hidden"
|
||||
value={amount}
|
||||
hidden
|
||||
/>
|
||||
<Input
|
||||
className="ipayfield"
|
||||
data-ipayname="account"
|
||||
type="hidden"
|
||||
value={data?.jobs_by_pk.ro_number}
|
||||
hidden
|
||||
/>
|
||||
<Input
|
||||
className="ipayfield"
|
||||
data-ipayname="email"
|
||||
type="hidden"
|
||||
value={data?.jobs_by_pk.owner.ownr_ea}
|
||||
hidden
|
||||
/>
|
||||
{/* Lightbox payment response when it is completed */}
|
||||
<Form.Item name="paymentResponse" hidden>
|
||||
<Input type="hidden" value={amount} />
|
||||
</Form.Item>
|
||||
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item
|
||||
label="Amount"
|
||||
name="amount"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
// message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
|
||||
<Row justify="space-around">
|
||||
<Button
|
||||
type="primary"
|
||||
// data-ipayname="submit"
|
||||
className="ipayfield"
|
||||
disabled={!amount || !jobid}
|
||||
onClick={handleIntelliPayCharge}
|
||||
>
|
||||
{t("job_payments.buttons.proceedtopayment")}
|
||||
</Button>
|
||||
{context?.balance && (
|
||||
<DataLabel
|
||||
valueStyle={{
|
||||
color: context?.balance.getAmount() !== 0 ? "red" : "green",
|
||||
}}
|
||||
label={t("payments.labels.balance")}
|
||||
>
|
||||
{context?.balance.toFormat()}
|
||||
</DataLabel>
|
||||
)}
|
||||
</Row>
|
||||
</LayoutFormRow>
|
||||
</Form>
|
||||
</Spin>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(CardPaymentModalComponent);
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Button, Modal } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CardPaymentModalComponent from "./card-payment-modal.component.";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
cardPaymentModal: selectCardPayment,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
|
||||
});
|
||||
|
||||
function CardPaymentModalContainer({
|
||||
cardPaymentModal,
|
||||
toggleModalVisible,
|
||||
bodyshop,
|
||||
}) {
|
||||
const { visible } = cardPaymentModal;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCancel = () => {
|
||||
toggleModalVisible();
|
||||
};
|
||||
|
||||
const handleOK = () => {
|
||||
toggleModalVisible();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={visible}
|
||||
onOk={handleOK}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<Button key="back" onClick={handleCancel}>
|
||||
{t("job_payments.buttons.goback")}
|
||||
</Button>,
|
||||
]}
|
||||
width="60%"
|
||||
destroyOnClose
|
||||
>
|
||||
<CardPaymentModalComponent />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(CardPaymentModalContainer);
|
||||
@@ -18,7 +18,7 @@ export default function DataLabel({
|
||||
<div {...props} style={{ display: "flex" }}>
|
||||
<div
|
||||
style={{
|
||||
flex: 2,
|
||||
// flex: 2,
|
||||
marginRight: ".2rem",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -70,6 +70,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
setReportCenterContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "reportCenter" })),
|
||||
signOutStart: () => dispatch(signOutStart()),
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||
});
|
||||
|
||||
function Header({
|
||||
@@ -83,6 +85,7 @@ function Header({
|
||||
setPaymentContext,
|
||||
setReportCenterContext,
|
||||
recentItems,
|
||||
setCardPaymentContext,
|
||||
}) {
|
||||
const { Simple_Inventory } = useTreatments(
|
||||
["Simple_Inventory"],
|
||||
@@ -94,6 +97,11 @@ function Header({
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
const { ImEXPay } = useTreatments(
|
||||
["ImEXPay"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -240,6 +248,20 @@ function Header({
|
||||
>
|
||||
{t("menus.header.enterpayment")}
|
||||
</Menu.Item>
|
||||
{ImEXPay.treatment === "on" && (
|
||||
<Menu.Item
|
||||
key="entercardpayments"
|
||||
onClick={() => {
|
||||
setCardPaymentContext({
|
||||
actions: {},
|
||||
context: null,
|
||||
});
|
||||
}}
|
||||
icon={<Icon component={FaCreditCard} />}
|
||||
>
|
||||
{t("menus.header.entercardpayment")}
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Divider key="div5" />
|
||||
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
|
||||
<Link to="/manage/timetickets">
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
import { Button, Card, Space, Table } from "antd";
|
||||
import { EditFilled } from "@ant-design/icons";
|
||||
import { Button, Card, Space, Table } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import {
|
||||
openChatByPhone,
|
||||
setMessage,
|
||||
} from "../../redux/messaging/messaging.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import PaymentExpandedRowComponent from "../payment-expanded-row/payment-expanded-row.component";
|
||||
import PaymentsGenerateLink from "../payments-generate-link/payments-generate-link.component";
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -23,20 +30,34 @@ const mapStateToProps = createStructuredSelector({
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPaymentContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
setMessage: (text) => dispatch(setMessage(text)),
|
||||
});
|
||||
|
||||
export function JobPayments({
|
||||
job,
|
||||
jobRO,
|
||||
bodyshop,
|
||||
setMessage,
|
||||
openChatByPhone,
|
||||
setPaymentContext,
|
||||
setCardPaymentContext,
|
||||
refetch,
|
||||
}) {
|
||||
const { ImEXPay } = useTreatments(
|
||||
["ImEXPay"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: {},
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("payments.fields.date"),
|
||||
@@ -149,6 +170,21 @@ export function JobPayments({
|
||||
title={t("payments.labels.title")}
|
||||
extra={
|
||||
<Space wrap>
|
||||
{ImEXPay.treatment === "on" && (
|
||||
<>
|
||||
<Button
|
||||
onClick={() =>
|
||||
setCardPaymentContext({
|
||||
actions: { refetch },
|
||||
context: { jobid: job.id, balance },
|
||||
})
|
||||
}
|
||||
>
|
||||
{t("menus.header.entercardpayment")}
|
||||
</Button>
|
||||
<PaymentsGenerateLink job={job} />
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
disabled={!job.converted}
|
||||
onClick={() =>
|
||||
@@ -160,6 +196,7 @@ export function JobPayments({
|
||||
>
|
||||
{t("menus.header.enterpayment")}
|
||||
</Button>
|
||||
|
||||
<DataLabel
|
||||
valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }}
|
||||
label={t("payments.labels.balance")}
|
||||
@@ -178,6 +215,11 @@ export function JobPayments({
|
||||
scroll={{
|
||||
x: true,
|
||||
}}
|
||||
expandable={{
|
||||
expandedRowRender: (record) => (
|
||||
<PaymentExpandedRowComponent record={record} bodyshop={bodyshop} />
|
||||
),
|
||||
}}
|
||||
summary={() => (
|
||||
<>
|
||||
<Table.Summary.Row>
|
||||
|
||||
@@ -48,6 +48,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(setModalContext({ context: context, modal: "jobCosting" })),
|
||||
setTimeTicketContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||
});
|
||||
|
||||
export function JobsDetailHeaderActions({
|
||||
@@ -61,6 +63,7 @@ export function JobsDetailHeaderActions({
|
||||
setJobCostingContext,
|
||||
jobRO,
|
||||
setTimeTicketContext,
|
||||
setCardPaymentContext,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
@@ -268,6 +271,18 @@ export function JobsDetailHeaderActions({
|
||||
>
|
||||
{t("menus.header.enterpayment")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="entercardpayments"
|
||||
disabled={!job.converted}
|
||||
onClick={() => {
|
||||
setCardPaymentContext({
|
||||
actions: {},
|
||||
context: { jobid: job.id },
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("menus.header.entercardpayment")}
|
||||
</Menu.Item>
|
||||
<Menu.Item key="cccontract" disabled={jobRO || !job.converted}>
|
||||
<Link
|
||||
to={{
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import React, { useState } from "react";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import {
|
||||
GET_REFUNDABLE_AMOUNT_BY_JOBID,
|
||||
INSERT_PAYMENT_RESPONSE,
|
||||
QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID,
|
||||
} from "../../graphql/payment_response.queries";
|
||||
import { Button, Descriptions, InputNumber, Modal, notification } from "antd";
|
||||
import moment from "moment";
|
||||
import axios from "axios";
|
||||
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const { confirm } = Modal;
|
||||
|
||||
const openNotificationWithIcon = (type, t) => {
|
||||
notification[type]({
|
||||
message: t("job_payments.notifications.error.title"),
|
||||
description: t("job_payments.notifications.error.description"),
|
||||
});
|
||||
};
|
||||
|
||||
const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
|
||||
const [refundAmount, setRefundAmount] = useState(0);
|
||||
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { loading, error, data } = useQuery(
|
||||
QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID,
|
||||
{
|
||||
variables: {
|
||||
paymentid: record.id,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const { data: refundable_amount, refetch } = useQuery(
|
||||
GET_REFUNDABLE_AMOUNT_BY_JOBID,
|
||||
{
|
||||
variables: {
|
||||
jobid: record.jobid,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const insertPayments = async (payment_response, refund_response) => {
|
||||
await insertPayment({
|
||||
variables: {
|
||||
paymentInput: {
|
||||
amount: -refund_response.data.amount,
|
||||
transactionid: payment_response.response.receiptelements.transid,
|
||||
payer: record.payer,
|
||||
type: "Refund",
|
||||
jobid: payment_response.jobid,
|
||||
date: moment(Date.now()),
|
||||
},
|
||||
},
|
||||
update(cache, { data }) {
|
||||
cache.modify({
|
||||
id: cache.identify({
|
||||
id: payment_response.jobid,
|
||||
__typename: "jobs",
|
||||
}),
|
||||
fields: {
|
||||
payments(payments) {
|
||||
return [...data.insert_payments.returning, ...payments];
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await insertPaymentResponse({
|
||||
variables: {
|
||||
paymentResponse: {
|
||||
amount: -refund_response.data.amount,
|
||||
bodyshopid: payment_response.bodyshopid,
|
||||
paymentid: payment_response.paymentid,
|
||||
jobid: payment_response.jobid,
|
||||
declinereason: "Refund",
|
||||
ext_paymentid: payment_response.ext_paymentid,
|
||||
successful: true,
|
||||
response: refund_response.data,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const showConfirm = (payment_response) => {
|
||||
confirm({
|
||||
title: "Do you want to refund payment?",
|
||||
content:
|
||||
"The payment will be refunded. Click OK to confirm and Cancel to dismiss.",
|
||||
async onOk() {
|
||||
const refundResponse = await axios.post("/intellipay/payment_refund", {
|
||||
bodyshop,
|
||||
amount: refundAmount,
|
||||
paymentid: payment_response.ext_paymentid,
|
||||
});
|
||||
|
||||
if (refundResponse.data.status < 0) {
|
||||
openNotificationWithIcon("error", t);
|
||||
return;
|
||||
}
|
||||
|
||||
insertPayments(payment_response, refundResponse);
|
||||
|
||||
// refetch refundable amount
|
||||
refetch();
|
||||
},
|
||||
onCancel() {},
|
||||
});
|
||||
};
|
||||
|
||||
if (loading) return null;
|
||||
|
||||
if (error) return <p>Error loading data. Please Reload</p>;
|
||||
|
||||
const payment_response = data.payment_response[0];
|
||||
const max_refundable_amount =
|
||||
refundable_amount?.payment_response_aggregate.aggregate.sum.amount;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Descriptions
|
||||
title={t("job_payments.titles.descriptions")}
|
||||
contentStyle={{ fontWeight: "600" }}
|
||||
column={4}
|
||||
>
|
||||
<Descriptions.Item label={t("job_payments.titles.payer")}>
|
||||
{record.payer}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t("job_payments.titles.payername")}>
|
||||
{payment_response?.response?.nameOnCard ?? ""}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t("job_payments.titles.amount")}>
|
||||
{record.amount}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t("job_payments.titles.dateOfPayment")}>
|
||||
{moment(record.created_at).format("YYYY-MM-DD HH:mm:ss")}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t("job_payments.titles.transactionid")}>
|
||||
{record.transactionid}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t("job_payments.titles.paymentid")}>
|
||||
{payment_response?.response?.paymentreferenceid ?? ""}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t("job_payments.titles.paymenttype")}>
|
||||
{record.type}
|
||||
</Descriptions.Item>
|
||||
{payment_response && (
|
||||
<Descriptions.Item label={t("job_payments.titles.refundamount")}>
|
||||
<InputNumber
|
||||
onChange={setRefundAmount}
|
||||
max={max_refundable_amount}
|
||||
min={0}
|
||||
/>
|
||||
|
||||
<Button onClick={() => showConfirm(payment_response)}>
|
||||
{t("job_payments.buttons.refundpayment")}
|
||||
</Button>
|
||||
</Descriptions.Item>
|
||||
)}
|
||||
</Descriptions>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentExpandedRowComponent;
|
||||
@@ -0,0 +1,156 @@
|
||||
import { CopyFilled } from "@ant-design/icons";
|
||||
import { Button, Form, Popover, Space, message } from "antd";
|
||||
import axios from "axios";
|
||||
import Dinero from "dinero.js";
|
||||
import { parsePhoneNumber } from "libphonenumber-js";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
openChatByPhone,
|
||||
setMessage,
|
||||
} from "../../redux/messaging/messaging.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
setMessage: (text) => dispatch(setMessage(text)),
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PaymentsGenerateLink);
|
||||
|
||||
export function PaymentsGenerateLink({
|
||||
bodyshop,
|
||||
callback,
|
||||
job,
|
||||
openChatByPhone,
|
||||
setMessage,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [paymentLink, setPaymentLink] = useState(null);
|
||||
|
||||
const handleFinish = async ({ amount }) => {
|
||||
setLoading(true);
|
||||
|
||||
const p = parsePhoneNumber(job.ownr_ph1, "CA");
|
||||
setLoading(true);
|
||||
const response = await axios.post("/intellipay/generate_payment_url", {
|
||||
bodyshop,
|
||||
amount: amount,
|
||||
account: job.ro_number,
|
||||
invoice: job.id,
|
||||
});
|
||||
setLoading(false);
|
||||
setPaymentLink(response.data.shorUrl);
|
||||
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: job.id,
|
||||
});
|
||||
setMessage(
|
||||
t("payments.labels.smspaymentreminder", {
|
||||
shopname: bodyshop.shopname,
|
||||
amount: amount,
|
||||
payment_link: response.data.shorUrl,
|
||||
})
|
||||
);
|
||||
|
||||
//Add in confirmation & errors.
|
||||
if (callback) callback();
|
||||
|
||||
// setVisible(false);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const popContent = (
|
||||
<div>
|
||||
<Form onFinish={handleFinish} layout="vertical" form={form}>
|
||||
<Form.Item
|
||||
label={t("payments.fields.amount")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name="amount"
|
||||
>
|
||||
<CurrencyFormItemComponent />
|
||||
</Form.Item>
|
||||
{paymentLink && (
|
||||
<Space direction="vertical">
|
||||
<Space
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(paymentLink);
|
||||
message.success(t("general.actions.copied"));
|
||||
}}
|
||||
>
|
||||
<div
|
||||
onClick={() => {
|
||||
//Copy the link.
|
||||
}}
|
||||
>
|
||||
{paymentLink}
|
||||
</div>{" "}
|
||||
<CopyFilled />
|
||||
</Space>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const p = parsePhoneNumber(job.ownr_ph1, "CA");
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: job.id,
|
||||
});
|
||||
setMessage(
|
||||
t("payments.labels.smspaymentreminder", {
|
||||
shopname: bodyshop.shopname,
|
||||
amount: Dinero({
|
||||
amount: Math.round(form.getFieldValue("amount") * 100),
|
||||
}).toFormat(),
|
||||
payment_link: paymentLink,
|
||||
})
|
||||
);
|
||||
}}
|
||||
>
|
||||
{t("general.actions.sendbysms")}
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
</Form>
|
||||
<Space>
|
||||
<Button type="primary" onClick={() => form.submit()}>
|
||||
{t("general.actions.submit")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
form.resetFields();
|
||||
setPaymentLink(null);
|
||||
setVisible(false);
|
||||
}}
|
||||
>
|
||||
{t("general.actions.cancel")}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={popContent} visible={visible}>
|
||||
<Button onClick={() => setVisible(true)} loading={loading}>
|
||||
{t("payments.actions.generatepaymentlink")}
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -747,7 +747,6 @@ export const GET_JOB_BY_PK = gql`
|
||||
amount
|
||||
payer
|
||||
created_at
|
||||
stripeid
|
||||
transactionid
|
||||
memo
|
||||
date
|
||||
|
||||
55
client/src/graphql/payment_response.queries.js
Normal file
55
client/src/graphql/payment_response.queries.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const INSERT_PAYMENT_RESPONSE = gql`
|
||||
mutation INSERT_PAYMENT_RESPONSE(
|
||||
$paymentResponse: [payment_response_insert_input!]!
|
||||
) {
|
||||
insert_payment_response(objects: $paymentResponse) {
|
||||
returning {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID = gql`
|
||||
query QUERY_PAYMENT_RESPONSE_BY_PK($paymentid: uuid!) {
|
||||
payment_response(where: { paymentid: { _eq: $paymentid } }) {
|
||||
id
|
||||
jobid
|
||||
bodyshopid
|
||||
paymentid
|
||||
amount
|
||||
declinereason
|
||||
ext_paymentid
|
||||
successful
|
||||
response
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_RO_AND_OWNER_BY_JOB_PK = gql`
|
||||
query QUERY_RO_AND_OWNER_BY_JOB_PK($jobid: uuid!) {
|
||||
jobs_by_pk(id: $jobid) {
|
||||
ro_number
|
||||
owner {
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ea
|
||||
ownr_zip
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_REFUNDABLE_AMOUNT_BY_JOBID = gql`
|
||||
query GET_REFUNDABLE_AMOUNT_BY_JOBID($jobid: uuid!) {
|
||||
payment_response_aggregate(where: { jobid: { _eq: $jobid } }) {
|
||||
aggregate {
|
||||
sum {
|
||||
amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -5,6 +5,15 @@ export const INSERT_NEW_PAYMENT = gql`
|
||||
insert_payments(objects: $paymentInput) {
|
||||
returning {
|
||||
id
|
||||
jobid
|
||||
amount
|
||||
payer
|
||||
created_at
|
||||
transactionid
|
||||
memo
|
||||
date
|
||||
type
|
||||
exportedat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
|
||||
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
|
||||
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
||||
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
|
||||
import TestComponent from "../../components/_test/test.component";
|
||||
import TestComponent from "../../components/_test/test.page";
|
||||
import { requestForToken } from "../../firebase/firebase.utils";
|
||||
import {
|
||||
selectBodyshop,
|
||||
@@ -32,6 +32,10 @@ const ManageRootPage = lazy(() =>
|
||||
);
|
||||
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
||||
|
||||
const CardPaymentModalContainer = lazy(() =>
|
||||
import("../../components/card-payment-modal/card-payment-modal.container.")
|
||||
);
|
||||
|
||||
const JobsDetailPage = lazy(() =>
|
||||
import("../jobs-detail/jobs-detail.page.container")
|
||||
);
|
||||
@@ -196,6 +200,8 @@ export function Manage({ match, conflict, bodyshop }) {
|
||||
>
|
||||
<PaymentModalContainer />
|
||||
|
||||
<CardPaymentModalContainer />
|
||||
|
||||
<BreadCrumbs />
|
||||
<BillEnterModalContainer />
|
||||
<JobCostingModal />
|
||||
|
||||
@@ -25,6 +25,7 @@ const INITIAL_STATE = {
|
||||
contractFinder: { ...baseModal },
|
||||
inventoryUpsert: { ...baseModal },
|
||||
ca_bc_eftTableConvert: { ...baseModal },
|
||||
cardPayment: { ...baseModal },
|
||||
};
|
||||
|
||||
const modalsReducer = (state = INITIAL_STATE, action) => {
|
||||
|
||||
@@ -79,3 +79,8 @@ export const selectCaBcEtfTableConvert = createSelector(
|
||||
[selectModals],
|
||||
(modals) => modals.ca_bc_eftTableConvert
|
||||
);
|
||||
|
||||
export const selectCardPayment = createSelector(
|
||||
[selectModals],
|
||||
(modals) => modals.cardPayment
|
||||
);
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
"scheduledfor": "Scheduled appointment for: ",
|
||||
"severalerrorsfound": "Several jobs have issues which may prevent accurate smart scheduling. Click to expand.",
|
||||
"smartscheduling": "Smart Scheduling",
|
||||
"smspaymentreminder": "",
|
||||
"suggesteddates": "Suggested Dates"
|
||||
},
|
||||
"successes": {
|
||||
@@ -105,7 +104,7 @@
|
||||
"admin_jobunvoid": "ADMIN: Job has been unvoided.",
|
||||
"billposted": "Bill with invoice number {{invoice_number}} posted.",
|
||||
"billupdated": "Bill with invoice number {{invoice_number}} updated.",
|
||||
"failedpayment": "",
|
||||
"failedpayment": "Failed payment",
|
||||
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
|
||||
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
|
||||
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
|
||||
@@ -1015,6 +1014,7 @@
|
||||
"cancel": "Cancel",
|
||||
"clear": "Clear",
|
||||
"close": "Close",
|
||||
"copied": "Copied!",
|
||||
"copylink": "Copy Link",
|
||||
"create": "Create",
|
||||
"delete": "Delete",
|
||||
@@ -1031,6 +1031,7 @@
|
||||
"saveandnew": "Save and New",
|
||||
"selectall": "Select All",
|
||||
"send": "Send",
|
||||
"sendbysms": "Send by SMS",
|
||||
"senderrortosupport": "Send Error to Support",
|
||||
"submit": "Submit",
|
||||
"tryagain": "Try Again",
|
||||
@@ -1191,26 +1192,27 @@
|
||||
},
|
||||
"job_payments": {
|
||||
"buttons": {
|
||||
"goback": "",
|
||||
"proceedtopayment": "",
|
||||
"refundpayment": ""
|
||||
"goback": "Go Back",
|
||||
"proceedtopayment": "Proceed to Payment",
|
||||
"refundpayment": "Refund Payment"
|
||||
},
|
||||
"notifications": {
|
||||
"error": {
|
||||
"description": "",
|
||||
"title": ""
|
||||
"description": "Please try again. Make sure the refund amount does not exceeds the payment amount.",
|
||||
"openingip": "Error connecting to IntelliPay service.",
|
||||
"title": "Error placing refund"
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"amount": "",
|
||||
"dateOfPayment": "",
|
||||
"descriptions": "",
|
||||
"payer": "",
|
||||
"payername": "",
|
||||
"paymentid": "",
|
||||
"paymenttype": "",
|
||||
"refundamount": "",
|
||||
"transactionid": ""
|
||||
"amount": "Amount",
|
||||
"dateOfPayment": "Date of Payment",
|
||||
"descriptions": "Payment Details",
|
||||
"payer": "Payer",
|
||||
"payername": "Payer Name",
|
||||
"paymentid": "Payment Reference ID",
|
||||
"paymenttype": "Payment Type",
|
||||
"refundamount": "Refund Amount",
|
||||
"transactionid": "Transaction ID"
|
||||
}
|
||||
},
|
||||
"joblines": {
|
||||
@@ -1926,7 +1928,7 @@
|
||||
"customers": "Customers",
|
||||
"dashboard": "Dashboard",
|
||||
"enterbills": "Enter Bills",
|
||||
"entercardpayment": "",
|
||||
"entercardpayment": "New Card Charge",
|
||||
"enterpayment": "Enter Payments",
|
||||
"entertimeticket": "Enter Time Tickets",
|
||||
"export": "Export",
|
||||
@@ -1938,7 +1940,6 @@
|
||||
"newjob": "Create New Job",
|
||||
"owners": "Owners",
|
||||
"parts-queue": "Parts Queue",
|
||||
"paymentremindersms": "",
|
||||
"phonebook": "Phonebook",
|
||||
"productionboard": "Production Board - Visual",
|
||||
"productionlist": "Production Board - List",
|
||||
@@ -2200,6 +2201,9 @@
|
||||
}
|
||||
},
|
||||
"payments": {
|
||||
"actions": {
|
||||
"generatepaymentlink": "Generate Payment Link"
|
||||
},
|
||||
"errors": {
|
||||
"exporting": "Error exporting payment(s). {{error}}",
|
||||
"exporting-partner": "Error exporting to partner. Please check the partner interaction log for more errors."
|
||||
@@ -2229,6 +2233,7 @@
|
||||
"markforreexport": "Mark for Re-export",
|
||||
"new": "New Payment",
|
||||
"signup": "Please contact support to sign up for electronic payments.",
|
||||
"smspaymentreminder": "This is {{shopname}} reminding you about your balance of {{amount}}. To pay, click the following link {{payment_link}}.",
|
||||
"title": "Payments",
|
||||
"totalpayments": "Total Payments"
|
||||
},
|
||||
@@ -2411,7 +2416,7 @@
|
||||
"jobs": {
|
||||
"individual_job_note": "Job Note RO: {{ro_number}}",
|
||||
"parts_order": "Parts Order PO: {{ro_number}} - {{name}}",
|
||||
"parts_return_slip":"Parts Return PO: {{ro_number}} - {{name}}",
|
||||
"parts_return_slip": "Parts Return PO: {{ro_number}} - {{name}}",
|
||||
"sublet_order": "Sublet Order PO: {{ro_number}} - {{name}}"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
"scheduledfor": "Cita programada para:",
|
||||
"severalerrorsfound": "",
|
||||
"smartscheduling": "",
|
||||
"smspaymentreminder": "",
|
||||
"suggesteddates": ""
|
||||
},
|
||||
"successes": {
|
||||
@@ -1015,6 +1014,7 @@
|
||||
"cancel": "",
|
||||
"clear": "",
|
||||
"close": "",
|
||||
"copied": "",
|
||||
"copylink": "",
|
||||
"create": "",
|
||||
"delete": "Borrar",
|
||||
@@ -1031,6 +1031,7 @@
|
||||
"saveandnew": "",
|
||||
"selectall": "",
|
||||
"send": "",
|
||||
"sendbysms": "",
|
||||
"senderrortosupport": "",
|
||||
"submit": "",
|
||||
"tryagain": "",
|
||||
@@ -1198,6 +1199,7 @@
|
||||
"notifications": {
|
||||
"error": {
|
||||
"description": "",
|
||||
"openingip": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
@@ -1938,7 +1940,6 @@
|
||||
"newjob": "",
|
||||
"owners": "propietarios",
|
||||
"parts-queue": "",
|
||||
"paymentremindersms": "",
|
||||
"phonebook": "",
|
||||
"productionboard": "",
|
||||
"productionlist": "",
|
||||
@@ -2200,6 +2201,9 @@
|
||||
}
|
||||
},
|
||||
"payments": {
|
||||
"actions": {
|
||||
"generatepaymentlink": ""
|
||||
},
|
||||
"errors": {
|
||||
"exporting": "",
|
||||
"exporting-partner": ""
|
||||
@@ -2229,6 +2233,7 @@
|
||||
"markforreexport": "",
|
||||
"new": "",
|
||||
"signup": "",
|
||||
"smspaymentreminder": "",
|
||||
"title": "",
|
||||
"totalpayments": ""
|
||||
},
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
"scheduledfor": "Rendez-vous prévu pour:",
|
||||
"severalerrorsfound": "",
|
||||
"smartscheduling": "",
|
||||
"smspaymentreminder": "",
|
||||
"suggesteddates": ""
|
||||
},
|
||||
"successes": {
|
||||
@@ -1015,6 +1014,7 @@
|
||||
"cancel": "",
|
||||
"clear": "",
|
||||
"close": "",
|
||||
"copied": "",
|
||||
"copylink": "",
|
||||
"create": "",
|
||||
"delete": "Effacer",
|
||||
@@ -1031,6 +1031,7 @@
|
||||
"saveandnew": "",
|
||||
"selectall": "",
|
||||
"send": "",
|
||||
"sendbysms": "",
|
||||
"senderrortosupport": "",
|
||||
"submit": "",
|
||||
"tryagain": "",
|
||||
@@ -1198,6 +1199,7 @@
|
||||
"notifications": {
|
||||
"error": {
|
||||
"description": "",
|
||||
"openingip": "",
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
@@ -1938,7 +1940,6 @@
|
||||
"newjob": "",
|
||||
"owners": "Propriétaires",
|
||||
"parts-queue": "",
|
||||
"paymentremindersms": "",
|
||||
"phonebook": "",
|
||||
"productionboard": "",
|
||||
"productionlist": "",
|
||||
@@ -2200,6 +2201,9 @@
|
||||
}
|
||||
},
|
||||
"payments": {
|
||||
"actions": {
|
||||
"generatepaymentlink": ""
|
||||
},
|
||||
"errors": {
|
||||
"exporting": "",
|
||||
"exporting-partner": ""
|
||||
@@ -2229,6 +2233,7 @@
|
||||
"markforreexport": "",
|
||||
"new": "",
|
||||
"signup": "",
|
||||
"smspaymentreminder": "",
|
||||
"title": "",
|
||||
"totalpayments": ""
|
||||
},
|
||||
|
||||
@@ -41,7 +41,7 @@ const AuditTrailMapping = {
|
||||
i18n.t("audit_trail.messages.admin_jobmarkforreexport"),
|
||||
admin_jobmarkexported: () =>
|
||||
i18n.t("audit_trail.messages.admin_jobmarkexported"),
|
||||
|
||||
failedpayment: () => i18n.t("audit_trail.messages.failedpayment"),
|
||||
};
|
||||
|
||||
export default AuditTrailMapping;
|
||||
|
||||
Reference in New Issue
Block a user