Compare commits
237 Commits
feature/IO
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17905fa844 | ||
|
|
74a0b78a71 | ||
|
|
797a423702 | ||
|
|
2fce8c9644 | ||
|
|
9cd39c1c3e | ||
|
|
6264a2f45c | ||
|
|
94e47d14ad | ||
|
|
9319f492dd | ||
|
|
8f04c5a12c | ||
|
|
436a41405d | ||
|
|
a2150009db | ||
|
|
e1c785322f | ||
|
|
c1d71720ab | ||
|
|
89ff7740e2 | ||
|
|
4e69fe819e | ||
|
|
a8cc3fa190 | ||
|
|
10fceb7ddf | ||
|
|
6c1a0cff8d | ||
|
|
d92d2cca9a | ||
|
|
ea54820bc0 | ||
|
|
62a800a2c0 | ||
|
|
9c408d8bf5 | ||
|
|
45ad09c100 | ||
|
|
dd5cafcd42 | ||
|
|
eb48b56f47 | ||
|
|
a879e99e77 | ||
|
|
ddd816e7ca | ||
|
|
d646e5f285 | ||
|
|
c10517a11b | ||
|
|
ba683a2e8a | ||
|
|
6b66b76f84 | ||
|
|
c3fe763261 | ||
|
|
5209c12b89 | ||
|
|
bf7aa17f65 | ||
|
|
cd6e0dcde3 | ||
|
|
a2822f5592 | ||
|
|
ca129fa4a0 | ||
|
|
cbe0c78553 | ||
|
|
2e763f1dd5 | ||
|
|
b8942c320e | ||
|
|
eee135f4ef | ||
|
|
de92b2d47e | ||
|
|
5d7384aa8b | ||
|
|
bf18e687da | ||
|
|
e69e844568 | ||
|
|
2b5268fb77 | ||
|
|
eebe7edba8 | ||
|
|
1a5c74dc79 | ||
|
|
be62ab5ff9 | ||
|
|
85497eb815 | ||
|
|
5724d0129c | ||
|
|
fd579fc509 | ||
|
|
6e21b1bdf6 | ||
|
|
a7ad18fae2 | ||
|
|
8bfa879485 | ||
|
|
ea774ff22b | ||
|
|
88101b0252 | ||
|
|
60ec76701d | ||
|
|
6b52723ba9 | ||
|
|
910c2a0f9b | ||
|
|
6c93e600c4 | ||
|
|
e70edaec7c | ||
|
|
acaba96e3b | ||
|
|
12d1613b04 | ||
|
|
df878672fc | ||
|
|
076115253f | ||
|
|
6f58528de2 | ||
|
|
3defe7201f | ||
|
|
4ce75ead52 | ||
|
|
6de7ec00fe | ||
|
|
90ea2cd699 | ||
|
|
800552210b | ||
|
|
80abea56b4 | ||
|
|
480f081c40 | ||
|
|
9529335c96 | ||
|
|
4334b3f419 | ||
|
|
94353bb342 | ||
|
|
5aad7acdd5 | ||
|
|
cd4f7ffb9c | ||
|
|
400dc79ed6 | ||
|
|
1dfb309223 | ||
|
|
29c9fb37a1 | ||
|
|
41d6f0a4bc | ||
|
|
af70c80e09 | ||
|
|
b02d4e0fdd | ||
|
|
27bf8d9ed6 | ||
|
|
582ad03e05 | ||
|
|
babdfe4cc5 | ||
|
|
8a7a94dd70 | ||
|
|
2654519277 | ||
|
|
02eddcbbf4 | ||
|
|
b967bb6d4e | ||
|
|
f60870a087 | ||
|
|
39aa21d985 | ||
|
|
512bb5e013 | ||
|
|
7581b8634e | ||
|
|
78f041a34f | ||
|
|
2c456cbf03 | ||
|
|
da76021802 | ||
|
|
6de06e084b | ||
|
|
cb49c91983 | ||
|
|
b0df5fa91c | ||
|
|
0652404334 | ||
|
|
bd7d8068df | ||
|
|
4dd868130c | ||
|
|
71860cf899 | ||
|
|
3512905264 | ||
|
|
2c072a9e7a | ||
|
|
fee5bee569 | ||
|
|
0a1cdbdfe3 | ||
|
|
8af79989ff | ||
|
|
5d2bdc7ee1 | ||
|
|
255d65e47d | ||
|
|
f0805e0a79 | ||
|
|
c875ade35c | ||
|
|
31b4f4e561 | ||
|
|
9b485bfe45 | ||
|
|
91279c27fe | ||
|
|
2c1844fb13 | ||
|
|
3812a0650e | ||
|
|
b1c5bbb01f | ||
|
|
bd59e40761 | ||
|
|
f440a2b022 | ||
|
|
6262b3ff83 | ||
|
|
0652114013 | ||
|
|
307c77b30c | ||
|
|
3150647ff6 | ||
|
|
f1d7a98fe8 | ||
|
|
be259317f9 | ||
|
|
046d104bfa | ||
|
|
e2258bb91f | ||
|
|
9c693a2b74 | ||
|
|
2be0f3de09 | ||
|
|
3dd4b3dd77 | ||
|
|
de8c2cd5a2 | ||
|
|
b791f9846f | ||
|
|
daa7631056 | ||
|
|
d2f7585ea5 | ||
|
|
14b8a2daef | ||
|
|
90b38d817d | ||
|
|
ace48e2890 | ||
|
|
fb810be5d5 | ||
|
|
50230e9f50 | ||
|
|
86e14967ca | ||
|
|
c45c3b4037 | ||
|
|
c4c11528b9 | ||
|
|
1f9c4e92f1 | ||
|
|
371e148e09 | ||
|
|
33af544ded | ||
|
|
6b8d0ec91c | ||
|
|
5a3ddfad0f | ||
|
|
043c44ed51 | ||
|
|
6d5dbf3145 | ||
|
|
6d8463265c | ||
|
|
0669282432 | ||
|
|
1af511be2f | ||
|
|
bf4dc7e158 | ||
|
|
14b38604a3 | ||
|
|
5de2036fdb | ||
|
|
1629663e15 | ||
|
|
e25f2db2b1 | ||
|
|
cbf5d268ea | ||
|
|
a92a95a9fa | ||
|
|
0be7bf2c8e | ||
|
|
56b810dd40 | ||
|
|
33cfa531b8 | ||
|
|
40c7b706aa | ||
|
|
000ded6649 | ||
|
|
72181e1ff7 | ||
|
|
d73b1d2220 | ||
|
|
88c03ce655 | ||
|
|
8645b434c8 | ||
|
|
38a13bd082 | ||
|
|
3bc5f5d626 | ||
|
|
86a2351316 | ||
|
|
de62e994bd | ||
|
|
d5b1496898 | ||
|
|
d0d8354395 | ||
|
|
6d2c3c81c7 | ||
|
|
3486e16d4e | ||
|
|
e2e02945cc | ||
|
|
404efcaf05 | ||
|
|
9b96460e4f | ||
|
|
618eff4973 | ||
|
|
109c34bebd | ||
|
|
1aab5aa740 | ||
|
|
484cb8e39e | ||
|
|
1ca483d4b0 | ||
|
|
3641363d3d | ||
|
|
094160ebf3 | ||
|
|
fde13436c9 | ||
|
|
e7be4c6e61 | ||
|
|
708fe63852 | ||
|
|
3f579f49b9 | ||
|
|
c15e69f079 | ||
|
|
ddb919e2cc | ||
|
|
27e0f497bb | ||
|
|
b9a9f07d7b | ||
|
|
3a06e813a8 | ||
|
|
c2aaf8844f | ||
|
|
488f79ddc8 | ||
|
|
5704e54e48 | ||
|
|
3c4902f71f | ||
|
|
5fb62aa16b | ||
|
|
f4473d11a8 | ||
|
|
dd469bad12 | ||
|
|
a716535795 | ||
|
|
c2ec476324 | ||
|
|
3ecd29c640 | ||
|
|
01443c478d | ||
|
|
209245187f | ||
|
|
3a9e989d70 | ||
|
|
8839fc0293 | ||
|
|
028bf3c7a0 | ||
|
|
ed05754368 | ||
|
|
7c3043988b | ||
|
|
a16f0df7de | ||
|
|
2562151f6e | ||
|
|
5e96ccdd99 | ||
|
|
965af6da5f | ||
|
|
c5c47e9bfc | ||
|
|
d66fdfb2e0 | ||
|
|
fb5c5561e9 | ||
|
|
4bc8ff26d2 | ||
|
|
a5e3985745 | ||
|
|
88ee4f13e1 | ||
|
|
fa05d0b401 | ||
|
|
cf017fb80b | ||
|
|
56c366e9e8 | ||
|
|
07b7394fec | ||
|
|
0617d79d19 | ||
|
|
885e9c6958 | ||
|
|
6bf5f2fe77 | ||
|
|
a3cc5c2324 | ||
|
|
a44ed3c406 | ||
|
|
aa5110ae13 | ||
|
|
c8ee9ca5a7 |
@@ -1009,27 +1009,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>smspaymentreminder</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>suggesteddates</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -1474,6 +1453,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>admin_jobuninvoice</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>admin_jobunvoid</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -3571,6 +3571,27 @@
|
||||
<folder_node>
|
||||
<name>validation</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>closingperiod</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>inventoryquantity</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -4205,6 +4226,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>closingperiod</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>country</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -4318,6 +4360,48 @@
|
||||
<folder_node>
|
||||
<name>dms</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>apcontrol</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>appostingaccount</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>cashierid</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -6622,6 +6706,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>void</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
@@ -14004,6 +14109,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>scheduledintoday</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>scheduledouttoday</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
</children>
|
||||
@@ -16060,6 +16207,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>copied</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>copylink</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -16396,6 +16564,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>sendbysms</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>senderrortosupport</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -19108,6 +19297,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>openingip</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>title</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -23584,6 +23794,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>date_void</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ded_amt</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -23844,6 +24075,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>dms_unsold</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>dms_wip_acctnumber</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -28343,6 +28595,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>closingperiod</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>contracts</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -32753,27 +33026,6 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>paymentremindersms</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>phonebook</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -36978,6 +37230,32 @@
|
||||
<folder_node>
|
||||
<name>payments</name>
|
||||
<children>
|
||||
<folder_node>
|
||||
<name>actions</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>generatepaymentlink</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>errors</name>
|
||||
<children>
|
||||
@@ -37411,6 +37689,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>markexported</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>markforreexport</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>new</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -37453,6 +37773,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>smspaymentreminder</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>title</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -37542,6 +37883,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>markreexported</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>payment</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -39355,6 +39717,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>mpi_final_repair_acct_sheet</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>paint_grid</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -40433,6 +40816,27 @@
|
||||
<folder_node>
|
||||
<name>jobs</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>individual_job_note</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>parts_order</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -40454,6 +40858,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>parts_return_slip</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>sublet_order</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -43276,6 +43701,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>jobs_scheduled_completion</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>lag_time</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -44284,6 +44730,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>work_in_progress_jobs</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>work_in_progress_labour</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -44378,6 +44845,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>estimators</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ins_co_nm_filter</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"react-i18next": "^12.2.0",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-intersection-observer": "^9.4.3",
|
||||
"react-number-format": "^5.1.3",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-resizable": "^3.0.4",
|
||||
|
||||
@@ -145,7 +145,11 @@
|
||||
|
||||
//Update row highlighting on production board.
|
||||
.ant-table-tbody > tr.ant-table-row:hover > td {
|
||||
background: #eaeaea !important;
|
||||
background: #e7f3ff !important;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr.ant-table-row-selected > td {
|
||||
background: #e6f7ff !important;
|
||||
}
|
||||
|
||||
.job-line-manual {
|
||||
@@ -156,3 +160,11 @@
|
||||
td.ant-table-column-sort {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.rowWithColor > td {
|
||||
background-color: var(--bgColor) !important;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useApolloClient, useMutation } from "@apollo/client";
|
||||
import { Button, Checkbox, Form, Modal, notification, Space } from "antd";
|
||||
import { Button, Checkbox, Form, Modal, Space, notification } from "antd";
|
||||
import _ from "lodash";
|
||||
import React, { useEffect, useState, useMemo } from "react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_NEW_BILL } from "../../graphql/bills.queries";
|
||||
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
||||
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
||||
import {
|
||||
QUERY_JOB_LBR_ADJUSTMENTS,
|
||||
UPDATE_JOB,
|
||||
} from "../../graphql/jobs.queries";
|
||||
import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries";
|
||||
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
||||
@@ -20,15 +20,15 @@ import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import confirmDialog from "../../utils/asyncConfirm";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import confirmDialog from "../../utils/asyncConfirm";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
billEnterModal: selectBillEnterModal,
|
||||
@@ -126,6 +126,17 @@ function BillEnterModalContainer({
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
lbr_adjustment,
|
||||
joblineid: i.joblineid === "noline" ? null : i.joblineid,
|
||||
applicable_taxes: {
|
||||
federal:
|
||||
(i.applicable_taxes && i.applicable_taxes.federal) ||
|
||||
false,
|
||||
state:
|
||||
(i.applicable_taxes && i.applicable_taxes.state) ||
|
||||
false,
|
||||
local:
|
||||
(i.applicable_taxes && i.applicable_taxes.local) ||
|
||||
false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Icon, { UploadOutlined } from "@ant-design/icons";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { MdOpenInNew } from "react-icons/md";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import {
|
||||
Alert,
|
||||
Divider,
|
||||
@@ -12,14 +12,17 @@ import {
|
||||
Switch,
|
||||
Upload,
|
||||
} from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MdOpenInNew } from "react-icons/md";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
@@ -28,8 +31,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||
import BillFormLines from "./bill-form.lines.component";
|
||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -58,6 +59,11 @@ export function BillFormComponent({
|
||||
{},
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
const { ClosingPeriod } = useTreatments(
|
||||
["ClosingPeriod"],
|
||||
{},
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const handleVendorSelect = (props, opt) => {
|
||||
setDiscount(opt.discount);
|
||||
@@ -259,6 +265,37 @@ export function BillFormComponent({
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
validator(rule, value) {
|
||||
if (
|
||||
ClosingPeriod.treatment === "on" &&
|
||||
bodyshop.accountingconfig.ClosingPeriod
|
||||
) {
|
||||
if (
|
||||
moment(value)
|
||||
.startOf("day")
|
||||
.isSameOrAfter(
|
||||
moment(
|
||||
bodyshop.accountingconfig.ClosingPeriod[0]
|
||||
).startOf("day")
|
||||
) &&
|
||||
moment(value)
|
||||
.startOf("day")
|
||||
.isSameOrBefore(
|
||||
moment(
|
||||
bodyshop.accountingconfig.ClosingPeriod[1]
|
||||
).endOf("day")
|
||||
)
|
||||
) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(t("bills.validation.closingperiod"));
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<FormDatePicker disabled={disabled} />
|
||||
|
||||
@@ -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);
|
||||
@@ -1,12 +1,19 @@
|
||||
import { Badge, List, Tag } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
AutoSizer,
|
||||
CellMeasurer,
|
||||
CellMeasurerCache,
|
||||
List as VirtualizedList,
|
||||
} from "react-virtualized";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
|
||||
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
||||
import { TimeAgoFormatter } from "../../utils/DateFormatter";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
import "./chat-conversation-list.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -18,59 +25,95 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(setSelectedConversation(conversationId)),
|
||||
});
|
||||
|
||||
export function ChatConversationListComponent({
|
||||
function ChatConversationListComponent({
|
||||
conversationList,
|
||||
selectedConversation,
|
||||
setSelectedConversation,
|
||||
loadMoreConversations,
|
||||
}) {
|
||||
const cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 60,
|
||||
});
|
||||
|
||||
const rowRenderer = ({ index, key, style, parent }) => {
|
||||
const item = conversationList[index];
|
||||
|
||||
return (
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
cache={cache}
|
||||
parent={parent}
|
||||
columnIndex={0}
|
||||
rowIndex={index}
|
||||
>
|
||||
<List.Item
|
||||
onClick={() => setSelectedConversation(item.id)}
|
||||
className={`chat-list-item ${
|
||||
item.id === selectedConversation
|
||||
? "chat-list-selected-conversation"
|
||||
: null
|
||||
}`}
|
||||
style={style}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
{item.label && <div className="chat-name">{item.label}</div>}
|
||||
{item.job_conversations.length > 0 ? (
|
||||
<div className="chat-name">
|
||||
{item.job_conversations.map((j, idx) => (
|
||||
<div key={idx}>
|
||||
<OwnerNameDisplay ownerObject={j.job} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: "inline-block" }}>
|
||||
<div>
|
||||
{item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => (
|
||||
<Tag key={idx} className="ro-number-tag">
|
||||
{j.job.ro_number}
|
||||
</Tag>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||
</div>
|
||||
<Badge count={item.messages_aggregate.aggregate.count || 0} />
|
||||
</List.Item>
|
||||
</CellMeasurer>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-list-container">
|
||||
<List
|
||||
bordered
|
||||
dataSource={conversationList}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
key={item.id}
|
||||
onClick={() => setSelectedConversation(item.id)}
|
||||
className={`chat-list-item ${
|
||||
item.id === selectedConversation
|
||||
? "chat-list-selected-conversation"
|
||||
: null
|
||||
}`}
|
||||
>
|
||||
<div sryle={{ display: "inline-block" }}>
|
||||
{item.label && <div className="chat-name">{item.label}</div>}
|
||||
{item.job_conversations.length > 0 ? (
|
||||
<div className="chat-name">
|
||||
{item.job_conversations.map((j, idx) => (
|
||||
<div key={idx}>
|
||||
<OwnerNameDisplay ownerObject={j.job} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
|
||||
)}
|
||||
</div>
|
||||
<div sryle={{ display: "inline-block" }}>
|
||||
<div>
|
||||
{item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => (
|
||||
<Tag key={idx} className="ro-number-tag">
|
||||
{j.job.ro_number}
|
||||
</Tag>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||
</div>
|
||||
<Badge count={item.messages_aggregate.aggregate.count || 0} />
|
||||
</List.Item>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<VirtualizedList
|
||||
height={height}
|
||||
width={width}
|
||||
rowCount={conversationList.length}
|
||||
rowHeight={cache.rowHeight}
|
||||
rowRenderer={rowRenderer}
|
||||
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
|
||||
if (scrollTop + clientHeight === scrollHeight) {
|
||||
loadMoreConversations();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</AutoSizer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
}
|
||||
.chat-list-container {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
border: 1px solid gainsboro;
|
||||
}
|
||||
|
||||
.chat-list-item {
|
||||
@@ -21,4 +22,6 @@
|
||||
.ro-number-tag {
|
||||
align-self: baseline;
|
||||
}
|
||||
padding: 12px 24px;
|
||||
border-bottom: 1px solid gainsboro;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import {
|
||||
ShrinkOutlined,
|
||||
SyncOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -47,12 +47,13 @@ export function ChatPopupComponent({
|
||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||
});
|
||||
|
||||
const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !chatVisible,
|
||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||
});
|
||||
const [getConversations, { loading, data, refetch, fetchMore }] =
|
||||
useLazyQuery(CONVERSATION_LIST_QUERY, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !chatVisible,
|
||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||
});
|
||||
|
||||
const fcmToken = sessionStorage.getItem("fcmtoken");
|
||||
|
||||
@@ -65,15 +66,22 @@ export function ChatPopupComponent({
|
||||
}, [fcmToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (called && chatVisible) refetch();
|
||||
}, [chatVisible, called, refetch]);
|
||||
if (chatVisible)
|
||||
getConversations({
|
||||
variables: {
|
||||
offset: 0,
|
||||
},
|
||||
});
|
||||
}, [chatVisible, getConversations]);
|
||||
|
||||
// const unreadCount = data
|
||||
// ? data.conversations.reduce(
|
||||
// (acc, val) => val.messages_aggregate.aggregate.count + acc,
|
||||
// 0
|
||||
// )
|
||||
// : 0;
|
||||
const loadMoreConversations = useCallback(() => {
|
||||
if (data)
|
||||
fetchMore({
|
||||
variables: {
|
||||
offset: data.conversations.length,
|
||||
},
|
||||
});
|
||||
}, [data, fetchMore]);
|
||||
|
||||
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
|
||||
|
||||
@@ -110,6 +118,7 @@ export function ChatPopupComponent({
|
||||
) : (
|
||||
<ChatConversationListComponent
|
||||
conversationList={data ? data.conversations : []}
|
||||
loadMoreConversations={loadMoreConversations}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
|
||||
@@ -59,6 +59,14 @@ export default function ContractsCarsComponent({
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "model" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("courtesycars.fields.color"),
|
||||
dataIndex: "color",
|
||||
key: "color",
|
||||
sorter: (a, b) => alphaSort(a.color, b.color),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "color" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("courtesycars.fields.plate"),
|
||||
dataIndex: "plate",
|
||||
@@ -93,6 +101,9 @@ export default function ContractsCarsComponent({
|
||||
(cc.model || "")
|
||||
.toLowerCase()
|
||||
.includes(state.search.toLowerCase()) ||
|
||||
(cc.color || "")
|
||||
.toLowerCase()
|
||||
.includes(state.search.toLowerCase()) ||
|
||||
(cc.plate || "").toLowerCase().includes(state.search.toLowerCase())
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
import {
|
||||
BranchesOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Space, Table, Tooltip } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
});
|
||||
if (!data) return null;
|
||||
if (!data.scheduled_in_today)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const appt = []; // Flatten Data
|
||||
data.scheduled_in_today.forEach((item) => {
|
||||
if (item.job) {
|
||||
var i = {
|
||||
canceled: item.canceled,
|
||||
id: item.id,
|
||||
alt_transport: item.job.alt_transport,
|
||||
clm_no: item.job.clm_no,
|
||||
jobid: item.job.jobid,
|
||||
ins_co_nm: item.job.ins_co_nm,
|
||||
iouparent: item.job.iouparent,
|
||||
ownerid: item.job.ownerid,
|
||||
ownr_co_nm: item.job.ownr_co_nm,
|
||||
ownr_ea: item.job.ownr_ea,
|
||||
ownr_fn: item.job.ownr_fn,
|
||||
ownr_ln: item.job.ownr_ln,
|
||||
ownr_ph1: item.job.ownr_ph1,
|
||||
ownr_ph2: item.job.ownr_ph2,
|
||||
production_vars: item.job.production_vars,
|
||||
ro_number: item.job.ro_number,
|
||||
suspended: item.job.suspended,
|
||||
v_make_desc: item.job.v_make_desc,
|
||||
v_model_desc: item.job.v_model_desc,
|
||||
v_model_yr: item.job.v_model_yr,
|
||||
v_vin: item.job.v_vin,
|
||||
vehicleid: item.job.vehicleid,
|
||||
note: item.note,
|
||||
start: moment(item.start).format("hh:mm a"),
|
||||
title: item.title,
|
||||
};
|
||||
appt.push(i);
|
||||
}
|
||||
});
|
||||
appt.sort(function (a, b) {
|
||||
return new moment(a.start) - new moment(b.start);
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||
)}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ea"),
|
||||
dataIndex: "ownr_ea",
|
||||
key: "ownr_ea",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.time"),
|
||||
dataIndex: "start",
|
||||
key: "start",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (sorter) => {
|
||||
setState({ ...state, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.scheduledintoday", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
})}
|
||||
{...cardProps}
|
||||
>
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||
rowKey="id"
|
||||
style={{ height: "85%" }}
|
||||
dataSource={appt}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardScheduledInTodayGql = `
|
||||
scheduled_in_today: appointments(where: {start: {_gte: "${moment()
|
||||
.startOf("day")
|
||||
.toISOString()}", _lte: "${moment()
|
||||
.endOf("day")
|
||||
.toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) {
|
||||
canceled
|
||||
id
|
||||
job {
|
||||
alt_transport
|
||||
clm_no
|
||||
jobid: id
|
||||
ins_co_nm
|
||||
iouparent
|
||||
ownerid
|
||||
ownr_co_nm
|
||||
ownr_ea
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
production_vars
|
||||
ro_number
|
||||
suspended
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
vehicleid
|
||||
}
|
||||
note
|
||||
start
|
||||
title
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,210 @@
|
||||
import {
|
||||
BranchesOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Space, Table, Tooltip } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
});
|
||||
if (!data) return null;
|
||||
if (!data.scheduled_out_today)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
data.scheduled_out_today.forEach((item) => {
|
||||
item.scheduled_completion= moment(item.scheduled_completion).format("hh:mm a")
|
||||
});
|
||||
data.scheduled_out_today.sort(function (a, b) {
|
||||
return new Date(a.scheduled_completion) - new Date(b.scheduled_completion);
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||
)}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ea"),
|
||||
dataIndex: "ownr_ea",
|
||||
key: "ownr_ea",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.scheduled_completion"),
|
||||
dataIndex: "scheduled_completion",
|
||||
key: "scheduled_completion",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (sorter) => {
|
||||
setState({ ...state, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.scheduledouttoday", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
})}
|
||||
{...cardProps}
|
||||
>
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||
rowKey="id"
|
||||
style={{ height: "85%" }}
|
||||
dataSource={data.scheduled_out_today}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardScheduledOutTodayGql = `
|
||||
scheduled_out_today: jobs(where: {
|
||||
date_invoiced: {_is_null: true},
|
||||
ro_number: {_is_null: false},
|
||||
voided: {_eq: false},
|
||||
scheduled_completion: {_gte: "${moment().startOf("day").toISOString()}",
|
||||
_lte: "${moment().endOf("day").toISOString()}"}}) {
|
||||
alt_transport
|
||||
clm_no
|
||||
jobid: id
|
||||
ins_co_nm
|
||||
iouparent
|
||||
ownerid
|
||||
ownr_co_nm
|
||||
ownr_ea
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
production_vars
|
||||
ro_number
|
||||
scheduled_completion
|
||||
suspended
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
vehicleid
|
||||
|
||||
}
|
||||
`;
|
||||
@@ -1,6 +1,6 @@
|
||||
import Icon, { SyncOutlined } from "@ant-design/icons";
|
||||
import { gql, useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Dropdown, Menu, notification, PageHeader, Space } from "antd";
|
||||
import { Button, Dropdown, Menu, PageHeader, Space, notification } from "antd";
|
||||
import i18next from "i18next";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
@@ -37,6 +37,12 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
//Combination of the following:
|
||||
// /node_modules/react-grid-layout/css/styles.css
|
||||
// /node_modules/react-resizable/css/styles.css
|
||||
import DashboardScheduledInToday, {
|
||||
DashboardScheduledInTodayGql,
|
||||
} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component";
|
||||
import DashboardScheduledOutToday, {
|
||||
DashboardScheduledOutTodayGql,
|
||||
} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component";
|
||||
import "./dashboard-grid.styles.scss";
|
||||
import { GenerateDashboardData } from "./dashboard-grid.utils";
|
||||
|
||||
@@ -268,6 +274,28 @@ const componentList = {
|
||||
w: 2,
|
||||
h: 2,
|
||||
},
|
||||
ScheduleInToday: {
|
||||
label: i18next.t("dashboard.titles.scheduledintoday", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
}),
|
||||
component: DashboardScheduledInToday,
|
||||
gqlFragment: DashboardScheduledInTodayGql,
|
||||
minW: 10,
|
||||
minH: 2,
|
||||
w: 10,
|
||||
h: 2,
|
||||
},
|
||||
ScheduleOutToday: {
|
||||
label: i18next.t("dashboard.titles.scheduledouttoday", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
}),
|
||||
component: DashboardScheduledOutToday,
|
||||
gqlFragment: DashboardScheduledOutTodayGql,
|
||||
minW: 10,
|
||||
minH: 2,
|
||||
w: 10,
|
||||
h: 2,
|
||||
},
|
||||
};
|
||||
|
||||
const createDashboardQuery = (state) => {
|
||||
@@ -283,8 +311,12 @@ const createDashboardQuery = (state) => {
|
||||
monthly_sales: jobs(where: {_and: [
|
||||
{ voided: {_eq: false}},
|
||||
{date_invoiced: {_gte: "${moment()
|
||||
.startOf("month").startOf('day').toISOString()}"}}, {date_invoiced: {_lte: "${moment()
|
||||
.endOf("month").endOf('day').toISOString()}"}}]}) {
|
||||
.startOf("month")
|
||||
.startOf("day")
|
||||
.toISOString()}"}}, {date_invoiced: {_lte: "${moment()
|
||||
.endOf("month")
|
||||
.endOf("day")
|
||||
.toISOString()}"}}]}) {
|
||||
id
|
||||
ro_number
|
||||
date_invoiced
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function DataLabel({
|
||||
<div {...props} style={{ display: "flex" }}>
|
||||
<div
|
||||
style={{
|
||||
flex: 2,
|
||||
// flex: 2,
|
||||
marginRight: ".2rem",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -66,7 +66,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
||||
key: "status",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
title: t("bills.fields.invoice_number"),
|
||||
dataIndex: ["Posting", "Reference"],
|
||||
key: "reference",
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Select,
|
||||
Space,
|
||||
Statistic,
|
||||
Switch,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
@@ -183,6 +184,13 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
<Space>
|
||||
<DmsCdkMakes form={form} socket={socket} job={job} />
|
||||
<DmsCdkMakesRefetch />
|
||||
<Form.Item
|
||||
name="dms_unsold"
|
||||
label={t("jobs.fields.dms.dms_unsold")}
|
||||
initialValue={false}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
Menu,
|
||||
Select,
|
||||
Space,
|
||||
Tabs,
|
||||
Upload,
|
||||
Space,
|
||||
Menu,
|
||||
Dropdown,
|
||||
Button,
|
||||
} from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import EmailDocumentsComponent from "../email-documents/email-documents.component";
|
||||
import _ from "lodash";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectEmailConfig } from "../../redux/email/email.selectors";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
|
||||
import { selectEmailConfig } from "../../redux/email/email.selectors";
|
||||
import EmailDocumentsComponent from "../email-documents/email-documents.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -54,6 +54,15 @@ export function EmailOverlayComponent({
|
||||
]),
|
||||
});
|
||||
};
|
||||
const handle_CC_Click = ({ item, key, keyPath }) => {
|
||||
const email = item.props.value;
|
||||
form.setFieldsValue({
|
||||
cc: _.uniq([
|
||||
...(form.getFieldValue("cc") || ""),
|
||||
...(typeof email === "string" ? [email] : email),
|
||||
]),
|
||||
});
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
@@ -74,6 +83,25 @@ export function EmailOverlayComponent({
|
||||
</div>
|
||||
);
|
||||
|
||||
const menuCC = (
|
||||
<div>
|
||||
<Menu onClick={handle_CC_Click}>
|
||||
{bodyshop.employees
|
||||
.filter((e) => e.user_email)
|
||||
.map((e, idx) => (
|
||||
<Menu.Item value={e.user_email} key={idx}>
|
||||
{`${e.first_name} ${e.last_name}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
{bodyshop.md_to_emails.map((e, idx) => (
|
||||
<Menu.Item value={e.emails} key={idx + "group"}>
|
||||
{e.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form.Item
|
||||
@@ -122,7 +150,23 @@ export function EmailOverlayComponent({
|
||||
>
|
||||
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("emails.fields.cc")} name="cc">
|
||||
<Form.Item
|
||||
label={
|
||||
<Space>
|
||||
{t("emails.fields.cc")}
|
||||
<Dropdown overlay={menuCC}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
<UserAddOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
}
|
||||
name="cc"
|
||||
>
|
||||
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import Icon, {
|
||||
BankFilled,
|
||||
BarChartOutlined,
|
||||
CarFilled,
|
||||
ClockCircleFilled,
|
||||
CheckCircleOutlined,
|
||||
ClockCircleFilled,
|
||||
DashboardFilled,
|
||||
DollarCircleFilled,
|
||||
ExportOutlined,
|
||||
@@ -26,6 +25,7 @@ import Icon, {
|
||||
UnorderedListOutlined,
|
||||
UserOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Layout, Menu } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -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">
|
||||
@@ -252,7 +274,11 @@ function Header({
|
||||
onClick={() => {
|
||||
setTimeTicketContext({
|
||||
actions: {},
|
||||
context: {},
|
||||
context: {
|
||||
created_by: currentUser.displayName
|
||||
? currentUser.email.concat(" | ", currentUser.displayName)
|
||||
: currentUser.email,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -311,7 +337,9 @@ function Header({
|
||||
icon={<SettingOutlined />}
|
||||
>
|
||||
<Menu.Item key="shop" icon={<Icon component={GiSettingsKnobs} />}>
|
||||
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
|
||||
<Link to="/manage/shop?tab=info">
|
||||
{t("menus.header.shop_config")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="dashboard" icon={<DashboardFilled />}>
|
||||
<Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR } from "../../graphql/vendors.queries";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
@@ -13,13 +14,14 @@ import VendorSearchSelect from "../vendor-search-select/vendor-search-select.com
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal);
|
||||
|
||||
export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
|
||||
export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
@@ -212,7 +214,9 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
|
||||
]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
|
||||
{!technician ? (
|
||||
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
|
||||
) : null}
|
||||
<Radio value={"p"}>{t("parts_orders.labels.print")}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
@@ -29,11 +29,11 @@ import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
||||
import ScheduleAtChange from "./job-at-change.component";
|
||||
import ScheduleEventColor from "./schedule-event.color.component";
|
||||
import ScheduleEventNote from "./schedule-event.note.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -208,46 +208,56 @@ export function ScheduleEventComponent({
|
||||
<Button>{t("appointments.actions.sendreminder")}</Button>
|
||||
</Dropdown>
|
||||
) : null}
|
||||
<Popover
|
||||
trigger="click"
|
||||
disabled={event.arrived}
|
||||
content={
|
||||
<Form
|
||||
layout="vertical"
|
||||
onFinish={({ lost_sale_reason }) => {
|
||||
handleCancel({ id: event.id, lost_sale_reason });
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="lost_sale_reason"
|
||||
label={t("jobs.fields.lost_sale_reason")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
|
||||
label: lsr,
|
||||
value: lsr,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button htmlType="submit">
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
</Form>
|
||||
}
|
||||
>
|
||||
{event.arrived ? (
|
||||
<Button
|
||||
// onClick={() => handleCancel(event.id)}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
</Popover>
|
||||
) : (
|
||||
<Popover
|
||||
trigger="click"
|
||||
disabled={event.arrived}
|
||||
content={
|
||||
<Form
|
||||
layout="vertical"
|
||||
onFinish={({ lost_sale_reason }) => {
|
||||
handleCancel({ id: event.id, lost_sale_reason });
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="lost_sale_reason"
|
||||
label={t("jobs.fields.lost_sale_reason")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
|
||||
label: lsr,
|
||||
value: lsr,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button htmlType="submit">
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
</Form>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
// onClick={() => handleCancel(event.id)}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
{event.isintake ? (
|
||||
<Button
|
||||
disabled={event.arrived}
|
||||
|
||||
@@ -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"),
|
||||
@@ -99,7 +120,7 @@ export function JobPayments({
|
||||
render: (text, record) => (
|
||||
<Space wrap>
|
||||
<Button
|
||||
disabled={record.exportedat}
|
||||
// disabled={record.exportedat}
|
||||
onClick={() => {
|
||||
setPaymentContext({
|
||||
actions: { refetch: refetch },
|
||||
@@ -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>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Form, notification } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import moment from "moment";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -38,8 +38,8 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
||||
setLoading(true);
|
||||
const result = await updateJob({
|
||||
variables: { jobId: job.id, job: values },
|
||||
refetchQueries: ['GET_JOB_BY_PK'],
|
||||
awaitRefetchQueries:true
|
||||
refetchQueries: ["GET_JOB_BY_PK"],
|
||||
awaitRefetchQueries: true,
|
||||
});
|
||||
|
||||
const changedAuditFields = form.getFieldsValue(
|
||||
@@ -126,7 +126,10 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
||||
<Form.Item label={t("jobs.fields.actual_in")} name="actual_in">
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.date_repairstarted")} name="date_repairstarted">
|
||||
<Form.Item
|
||||
label={t("jobs.fields.date_repairstarted")}
|
||||
name="date_repairstarted"
|
||||
>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
@@ -173,6 +176,9 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
||||
>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</Form>
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import { gql } from "@apollo/client";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import moment from "moment";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import moment from "moment";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -150,6 +149,10 @@ export function JobAdminMarkReexport({
|
||||
|
||||
if (!result.errors) {
|
||||
notification["success"]({ message: t("jobs.successes.save") });
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.admin_jobuninvoice(),
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
|
||||
@@ -33,8 +33,9 @@ export function JobsAdminUnvoid({
|
||||
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
|
||||
}
|
||||
|
||||
@@ -4,16 +4,15 @@ import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -173,6 +172,13 @@ export function JobsCloseExportButton({
|
||||
});
|
||||
}
|
||||
}
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
notification.open({
|
||||
type: "success",
|
||||
key: "jobsuccessexport",
|
||||
message: t("jobs.successes.exported"),
|
||||
});
|
||||
}
|
||||
if (setSelectedJobs) {
|
||||
setSelectedJobs((selectedJobs) => {
|
||||
return selectedJobs.filter((i) => i !== jobId);
|
||||
|
||||
@@ -141,6 +141,10 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
<Form.Item label={t("jobs.fields.date_exported")} name="date_exported">
|
||||
<DateTimePicker disabled={true || jobRO} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
||||
<DateTimePicker disabled={true || jobRO} />
|
||||
</Form.Item>
|
||||
</FormRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
Dropdown,
|
||||
Form,
|
||||
Menu,
|
||||
notification,
|
||||
Popconfirm,
|
||||
Popover,
|
||||
Select,
|
||||
notification,
|
||||
} from "antd";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent";
|
||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
|
||||
@@ -47,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({
|
||||
@@ -60,6 +63,7 @@ export function JobsDetailHeaderActions({
|
||||
setJobCostingContext,
|
||||
jobRO,
|
||||
setTimeTicketContext,
|
||||
setCardPaymentContext,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
@@ -136,63 +140,67 @@ export function JobsDetailHeaderActions({
|
||||
<Menu.Item
|
||||
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
|
||||
>
|
||||
<Popover
|
||||
trigger="click"
|
||||
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
|
||||
content={
|
||||
<Form
|
||||
layout="vertical"
|
||||
onFinish={async ({ lost_sale_reason }) => {
|
||||
const jobUpdate = await cancelAllAppointments({
|
||||
variables: {
|
||||
jobid: job.id,
|
||||
job: {
|
||||
date_scheduled: null,
|
||||
scheduled_in: null,
|
||||
scheduled_completion: null,
|
||||
lost_sale_reason,
|
||||
status: bodyshop.md_ro_statuses.default_imported,
|
||||
{job.status !== bodyshop.md_ro_statuses.default_scheduled ? (
|
||||
t("menus.jobsactions.cancelallappointments")
|
||||
) : (
|
||||
<Popover
|
||||
trigger="click"
|
||||
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
|
||||
content={
|
||||
<Form
|
||||
layout="vertical"
|
||||
onFinish={async ({ lost_sale_reason }) => {
|
||||
const jobUpdate = await cancelAllAppointments({
|
||||
variables: {
|
||||
jobid: job.id,
|
||||
job: {
|
||||
date_scheduled: null,
|
||||
scheduled_in: null,
|
||||
scheduled_completion: null,
|
||||
lost_sale_reason,
|
||||
status: bodyshop.md_ro_statuses.default_imported,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!jobUpdate.errors) {
|
||||
notification["success"]({
|
||||
message: t("appointments.successes.canceled"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="lost_sale_reason"
|
||||
label={t("jobs.fields.lost_sale_reason")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
if (!jobUpdate.errors) {
|
||||
notification["success"]({
|
||||
message: t("appointments.successes.canceled"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
|
||||
label: lsr,
|
||||
value: lsr,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
disabled={
|
||||
job.status !== bodyshop.md_ro_statuses.default_scheduled
|
||||
}
|
||||
>
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
</Form>
|
||||
}
|
||||
>
|
||||
{t("menus.jobsactions.cancelallappointments")}
|
||||
</Popover>
|
||||
<Form.Item
|
||||
name="lost_sale_reason"
|
||||
label={t("jobs.fields.lost_sale_reason")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
|
||||
label: lsr,
|
||||
value: lsr,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button
|
||||
htmlType="submit"
|
||||
disabled={
|
||||
job.status !== bodyshop.md_ro_statuses.default_scheduled
|
||||
}
|
||||
>
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
</Form>
|
||||
}
|
||||
>
|
||||
{t("menus.jobsactions.cancelallappointments")}
|
||||
</Popover>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
disabled={
|
||||
@@ -238,7 +246,12 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
setTimeTicketContext({
|
||||
actions: {},
|
||||
context: { jobId: job.id },
|
||||
context: {
|
||||
jobId: job.id,
|
||||
created_by: currentUser.displayName
|
||||
? currentUser.email.concat(" | ", currentUser.displayName)
|
||||
: currentUser.email,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -258,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={{
|
||||
@@ -461,54 +486,57 @@ export function JobsDetailHeaderActions({
|
||||
)}
|
||||
<JobsDetailHeaderActionsAddevent jobid={job.id} />
|
||||
{!jobRO && job.converted && (
|
||||
<Menu.Item>
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.voidjob")}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onConfirm={async () => {
|
||||
//delete the job.
|
||||
const result = await voidJob({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: {
|
||||
status: bodyshop.md_ro_statuses.default_void,
|
||||
voided: true,
|
||||
scheduled_in: null,
|
||||
scheduled_completion: null,
|
||||
inproduction: false,
|
||||
},
|
||||
note: [
|
||||
{
|
||||
jobid: job.id,
|
||||
created_by: currentUser.email,
|
||||
audit: true,
|
||||
text: t("jobs.labels.voidnote"),
|
||||
<RbacWrapper action="jobs:void" noauth>
|
||||
<Menu.Item>
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.voidjob")}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onConfirm={async () => {
|
||||
//delete the job.
|
||||
const result = await voidJob({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: {
|
||||
status: bodyshop.md_ro_statuses.default_void,
|
||||
voided: true,
|
||||
scheduled_in: null,
|
||||
scheduled_completion: null,
|
||||
inproduction: false,
|
||||
date_void: new Date(),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
note: [
|
||||
{
|
||||
jobid: job.id,
|
||||
created_by: currentUser.email,
|
||||
audit: true,
|
||||
text: t("jobs.labels.voidnote"),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
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),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}}
|
||||
getPopupContainer={(trigger) => trigger.parentNode}
|
||||
>
|
||||
{t("menus.jobsactions.void")}
|
||||
</Popconfirm>
|
||||
</Menu.Item>
|
||||
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),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}}
|
||||
getPopupContainer={(trigger) => trigger.parentNode}
|
||||
>
|
||||
{t("menus.jobsactions.void")}
|
||||
</Popconfirm>
|
||||
</Menu.Item>
|
||||
</RbacWrapper>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
@@ -169,14 +169,20 @@ export function JobsExportAllButton({
|
||||
});
|
||||
}
|
||||
}
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
notification.open({
|
||||
type: "success",
|
||||
key: "jobsuccessexport",
|
||||
message: t("jobs.successes.exported"),
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
||||
|
||||
if (!!completedCallback) completedCallback([]);
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -112,7 +112,9 @@ export function JobsList({ bodyshop }) {
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sorter: (a, b) =>
|
||||
parseInt((a.ro_number || "0").replace(/\D/g, "")) -
|
||||
parseInt((b.ro_number || "0").replace(/\D/g, "")),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||
import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -15,6 +16,15 @@ const mapStateToProps = createStructuredSelector({
|
||||
function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedJobs, setSelectedJobs] = useState([]);
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" },
|
||||
});
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
@@ -26,6 +36,9 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
</Link>
|
||||
),
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
@@ -46,11 +59,17 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
||||
title: t("jobs.fields.clm_no"),
|
||||
dataIndex: "clm_no",
|
||||
key: "clm_no",
|
||||
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -60,6 +79,9 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
|
||||
),
|
||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -80,6 +102,7 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
||||
scroll={{ x: true }}
|
||||
rowKey="id"
|
||||
dataSource={owner.jobs}
|
||||
onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelect: (record, selected, selectedRows) => {
|
||||
setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []);
|
||||
|
||||
@@ -113,6 +113,8 @@ export function PartsOrderListTableComponent({
|
||||
id: pol.id,
|
||||
line_desc: pol.line_desc,
|
||||
quantity: pol.quantity,
|
||||
act_price: pol.act_price,
|
||||
oem_partno: pol.oem_partno,
|
||||
};
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -79,6 +79,20 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.oem_partno")}
|
||||
key={`${index}oem_partno`}
|
||||
name={[field.name, "oem_partno"]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.act_price")}
|
||||
key={`${index}act_price`}
|
||||
name={[field.name, "act_price"]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.location")}
|
||||
key={`${index}location`}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import axios from "axios";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { UPDATE_BILLS } from "../../graphql/bills.queries";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import _ from "lodash";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -164,6 +163,13 @@ export function PayableExportAll({
|
||||
});
|
||||
}
|
||||
}
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
notification.open({
|
||||
type: "success",
|
||||
key: "billsuccessexport",
|
||||
message: t("bills.successes.exported"),
|
||||
});
|
||||
}
|
||||
}
|
||||
})()
|
||||
);
|
||||
@@ -173,7 +179,6 @@ export function PayableExportAll({
|
||||
if (!!completedCallback) completedCallback([]);
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,16 +4,15 @@ import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { UPDATE_BILLS } from "../../graphql/bills.queries";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -167,7 +166,13 @@ export function PayableExportButton({
|
||||
});
|
||||
}
|
||||
}
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
notification.open({
|
||||
type: "success",
|
||||
key: "billsuccessexport",
|
||||
message: t("bills.successes.exported"),
|
||||
});
|
||||
}
|
||||
|
||||
if (setSelectedBills) {
|
||||
setSelectedBills((selectedBills) => {
|
||||
@@ -177,6 +182,7 @@ export function PayableExportButton({
|
||||
}
|
||||
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -172,8 +172,15 @@ export function PaymentExportButton({
|
||||
});
|
||||
}
|
||||
}
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
notification.open({
|
||||
type: "success",
|
||||
key: "paymentsuccessexport",
|
||||
message: t("payments.successes.exported"),
|
||||
});
|
||||
}
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import React from "react";
|
||||
import { Button, notification } from "antd";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { connect } from "react-redux";
|
||||
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
|
||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPaymentContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||
});
|
||||
|
||||
const PaymentMarkForExportButton = ({
|
||||
bodyshop,
|
||||
payment,
|
||||
refetch,
|
||||
setPaymentContext,
|
||||
currentUser,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [insertExportLog, { loading: exportLogLoading }] =
|
||||
useMutation(INSERT_EXPORT_LOG);
|
||||
const [updatePayment, { loading: updatePaymentLoading }] =
|
||||
useMutation(UPDATE_PAYMENT);
|
||||
|
||||
const handleClick = async () => {
|
||||
const today = new Date();
|
||||
|
||||
await insertExportLog({
|
||||
variables: {
|
||||
logs: [
|
||||
{
|
||||
bodyshopid: bodyshop.id,
|
||||
paymentid: payment.id,
|
||||
successful: true,
|
||||
useremail: currentUser.email,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const paymentUpdateResponse = await updatePayment({
|
||||
variables: {
|
||||
paymentId: payment.id,
|
||||
payment: {
|
||||
exportedat: today,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!!!paymentUpdateResponse.errors) {
|
||||
notification.open({
|
||||
type: "success",
|
||||
key: "paymentsuccessmarkforexport",
|
||||
message: t("payments.successes.markexported"),
|
||||
});
|
||||
|
||||
if (refetch) refetch();
|
||||
|
||||
setPaymentContext({
|
||||
actions: {
|
||||
refetch,
|
||||
},
|
||||
context: {
|
||||
...payment,
|
||||
exportedat: today,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("payments.errors.exporting", {
|
||||
error: JSON.stringify(paymentUpdateResponse.error),
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
loading={exportLogLoading || updatePaymentLoading}
|
||||
disabled={!!payment.exportedat}
|
||||
>
|
||||
{t("payments.labels.markexported")}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PaymentMarkForExportButton);
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
|
||||
import { Button, Form, Modal, notification } from "antd";
|
||||
import { Button, Form, Modal, notification, Space } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import PaymentForm from "../payment-form/payment-form.component";
|
||||
import PaymentReexportButton from "../payment-reexport-button/payment-reexport-button.component";
|
||||
import PaymentMarkForExportButton from "../payment-mark-export-button/payment-mark-export-button-component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
paymentModal: selectPayment,
|
||||
@@ -176,12 +178,24 @@ function PaymentModalContainer({
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{!context || (context && !context.id) ? null : (
|
||||
<Space>
|
||||
<PaymentReexportButton payment={context} refetch={actions.refetch} />
|
||||
<PaymentMarkForExportButton
|
||||
bodyshop={bodyshop}
|
||||
payment={context}
|
||||
refetch={actions.refetch}
|
||||
/>
|
||||
</Space>
|
||||
)}
|
||||
|
||||
<Form
|
||||
onFinish={handleFinish}
|
||||
autoComplete={"off"}
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={context || {}}
|
||||
disabled={context?.exportedat}
|
||||
>
|
||||
<PaymentForm form={form} />
|
||||
</Form>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import { Button, notification } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPaymentContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||
});
|
||||
|
||||
const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => {
|
||||
const { t } = useTranslation();
|
||||
const [updatePayment, { loading }] = useMutation(UPDATE_PAYMENT);
|
||||
|
||||
const handleClick = async () => {
|
||||
const paymentUpdateResponse = await updatePayment({
|
||||
variables: {
|
||||
paymentId: payment.id,
|
||||
payment: {
|
||||
exportedat: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!!!paymentUpdateResponse.errors) {
|
||||
notification.open({
|
||||
type: "success",
|
||||
key: "paymentsuccessexport",
|
||||
message: t("payments.successes.markreexported"),
|
||||
});
|
||||
|
||||
if (refetch) refetch();
|
||||
|
||||
setPaymentContext({
|
||||
actions: {
|
||||
refetch,
|
||||
},
|
||||
context: {
|
||||
...payment,
|
||||
exportedat: null,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("payments.errors.exporting", {
|
||||
error: JSON.stringify(paymentUpdateResponse.error),
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleClick}
|
||||
loading={loading}
|
||||
disabled={!payment.exportedat}
|
||||
>
|
||||
{t("payments.labels.markforreexport")}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(null, mapDispatchToProps)(PaymentReexportButton);
|
||||
@@ -25,7 +25,7 @@ export function PaymentsExportAllButton({
|
||||
disabled,
|
||||
loadingCallback,
|
||||
completedCallback,
|
||||
refetch
|
||||
refetch,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updatePayments] = useMutation(UPDATE_PAYMENTS);
|
||||
@@ -150,6 +150,13 @@ export function PaymentsExportAllButton({
|
||||
});
|
||||
}
|
||||
}
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
notification.open({
|
||||
type: "success",
|
||||
key: "paymentsuccessexport",
|
||||
message: t("payments.successes.exported"),
|
||||
});
|
||||
}
|
||||
}
|
||||
})()
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -156,7 +156,7 @@ export function PaymentsListPaginated({
|
||||
render: (text, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
disabled={record.exportedat}
|
||||
// disabled={record.exportedat}
|
||||
onClick={async () => {
|
||||
let apolloResults;
|
||||
if (search.search) {
|
||||
|
||||
@@ -5,11 +5,13 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import { selectPrintCenter } from "../../redux/modals/modals.selectors";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
printCenterModal: selectPrintCenter,
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||
@@ -22,6 +24,7 @@ export function PrintCenterItemComponent({
|
||||
id,
|
||||
bodyshop,
|
||||
disabled,
|
||||
technician,
|
||||
}) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { context } = printCenterModal;
|
||||
@@ -44,19 +47,24 @@ export function PrintCenterItemComponent({
|
||||
<Space wrap>
|
||||
{item.title}
|
||||
<PrinterOutlined onClick={renderToNewWindow} />
|
||||
<MailOutlined
|
||||
onClick={() => {
|
||||
GenerateDocument(
|
||||
{
|
||||
name: item.key,
|
||||
variables: { id: id },
|
||||
},
|
||||
{ to: context.job && context.job.ownr_ea, subject: item.subject },
|
||||
"e",
|
||||
id
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{!technician ? (
|
||||
<MailOutlined
|
||||
onClick={() => {
|
||||
GenerateDocument(
|
||||
{
|
||||
name: item.key,
|
||||
variables: { id: id },
|
||||
},
|
||||
{
|
||||
to: context.job && context.job.ownr_ea,
|
||||
subject: item.subject,
|
||||
},
|
||||
"e",
|
||||
id
|
||||
);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{loading && <Spin />}
|
||||
</Space>
|
||||
</li>
|
||||
|
||||
@@ -23,17 +23,34 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
|
||||
const { id: jobId, job } = printCenterModal.context;
|
||||
const tempList = TemplateList("job", {});
|
||||
const { t } = useTranslation();
|
||||
const JobsReportsList = Object.keys(tempList)
|
||||
.map((key) => {
|
||||
return tempList[key];
|
||||
})
|
||||
.filter(
|
||||
(temp) =>
|
||||
!temp.regions ||
|
||||
(temp.regions && temp.regions[bodyshop.region_config]) ||
|
||||
(temp.regions &&
|
||||
bodyshop.region_config.includes(Object.keys(temp.regions)) === true)
|
||||
);
|
||||
|
||||
const JobsReportsList =
|
||||
bodyshop.cdk_dealerid === null && bodyshop.pbs_serialnumber === null
|
||||
? Object.keys(tempList)
|
||||
.map((key) => {
|
||||
return tempList[key];
|
||||
})
|
||||
.filter(
|
||||
(temp) =>
|
||||
(!temp.regions ||
|
||||
(temp.regions && temp.regions[bodyshop.region_config]) ||
|
||||
(temp.regions &&
|
||||
bodyshop.region_config.includes(Object.keys(temp.regions)) ===
|
||||
true)) &&
|
||||
(!temp.dms || temp.dms === false)
|
||||
)
|
||||
: Object.keys(tempList)
|
||||
.map((key) => {
|
||||
return tempList[key];
|
||||
})
|
||||
.filter(
|
||||
(temp) =>
|
||||
!temp.regions ||
|
||||
(temp.regions && temp.regions[bodyshop.region_config]) ||
|
||||
(temp.regions &&
|
||||
bodyshop.region_config.includes(Object.keys(temp.regions)) ===
|
||||
true)
|
||||
);
|
||||
|
||||
const filteredJobsReportsList =
|
||||
search !== ""
|
||||
|
||||
@@ -25,6 +25,7 @@ export default function ProductionListDate({
|
||||
// }
|
||||
|
||||
//e.stopPropagation();
|
||||
|
||||
updateAlert({
|
||||
variables: {
|
||||
jobId: record.id,
|
||||
@@ -32,6 +33,11 @@ export default function ProductionListDate({
|
||||
[field]: date,
|
||||
},
|
||||
},
|
||||
optimisticResponse: {
|
||||
update_jobs: {
|
||||
[field]: date,
|
||||
},
|
||||
},
|
||||
}).then(() => {
|
||||
if (record.refetch) record.refetch();
|
||||
if (!time) {
|
||||
@@ -49,9 +55,11 @@ export default function ProductionListDate({
|
||||
(moment().add(1, "day").isSame(moment(record[field]), "day") &&
|
||||
"production-completion-soon"));
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
//trigger={["click"]}
|
||||
trigger={["click"]}
|
||||
onVisibleChange={(v) => setVisible(v)}
|
||||
visible={visible}
|
||||
style={{
|
||||
height: "19px",
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
import { PrinterFilled } from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Descriptions, Drawer, Space, PageHeader, Button } from "antd";
|
||||
import { Button, Descriptions, Drawer, PageHeader, Space } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
||||
import JobAtChange from "../job-at-change/job-at-change.component";
|
||||
import JobDetailCardsDocumentsComponent from "../job-detail-cards/job-detail-cards.documents.component";
|
||||
import JobDetailCardsNotesComponent from "../job-detail-cards/job-detail-cards.notes.component";
|
||||
import JobDetailCardsPartsComponent from "../job-detail-cards/job-detail-cards.parts.component";
|
||||
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
|
||||
import JobAtChange from "../job-at-change/job-at-change.component";
|
||||
import { PrinterFilled } from "@ant-design/icons";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPrintCenterContext: (context) =>
|
||||
@@ -40,6 +42,7 @@ export function ProductionListDetail({
|
||||
bodyshop,
|
||||
jobs,
|
||||
setPrintCenterContext,
|
||||
technician,
|
||||
}) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useHistory();
|
||||
@@ -66,7 +69,9 @@ export function ProductionListDetail({
|
||||
title={theJob.ro_number}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<ProductionRemoveButton jobId={theJob.id} />{" "}
|
||||
{!technician ? (
|
||||
<ProductionRemoveButton jobId={theJob.id} />
|
||||
) : null}
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPrintCenterContext({
|
||||
@@ -82,7 +87,9 @@ export function ProductionListDetail({
|
||||
<PrinterFilled />
|
||||
{t("jobs.actions.printCenter")}
|
||||
</Button>
|
||||
<ScoreboardAddButton job={data ? data.jobs_by_pk : {}} />
|
||||
{!technician ? (
|
||||
<ScoreboardAddButton job={data ? data.jobs_by_pk : {}} />
|
||||
) : null}
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -246,11 +246,21 @@ export function ProductionListTable({
|
||||
(x) => x.status === record.status
|
||||
);
|
||||
|
||||
if (!color) return null;
|
||||
if (!color) {
|
||||
if (index % 2 === 0)
|
||||
return {
|
||||
style: {
|
||||
backgroundColor: `rgb(236, 236, 236)`,
|
||||
},
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
className: "rowWithColor",
|
||||
style: {
|
||||
backgroundColor: `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
|
||||
"--bgColor": `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -26,6 +26,8 @@ const ret = {
|
||||
"jobs:partsqueue": 4,
|
||||
"jobs:checklist-view": 2,
|
||||
"jobs:list-ready": 1,
|
||||
"jobs:void": 5,
|
||||
|
||||
"bills:enter": 2,
|
||||
"bills:view": 2,
|
||||
"bills:list": 2,
|
||||
|
||||
@@ -12,7 +12,8 @@ import "./schedule-calendar.styles.scss";
|
||||
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
|
||||
import { selectProblemJobs } from "../../redux/application/application.selectors";
|
||||
import { Alert, Collapse } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -66,10 +67,21 @@ export function ScheduleCalendarWrapperComponent({
|
||||
<Alert
|
||||
key={problem.id}
|
||||
type="error"
|
||||
message={t("appointments.labels.dataconsistency", {
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
})}
|
||||
message={
|
||||
<Trans
|
||||
i18nKey="appointments.labels.dataconsistency"
|
||||
components={[
|
||||
<Link
|
||||
to={`/manage/jobs/${problem.id}`}
|
||||
target="_blank"
|
||||
/>,
|
||||
]}
|
||||
values={{
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Collapse.Panel>
|
||||
@@ -79,10 +91,18 @@ export function ScheduleCalendarWrapperComponent({
|
||||
<Alert
|
||||
key={problem.id}
|
||||
type="error"
|
||||
message={t("appointments.labels.dataconsistency", {
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
})}
|
||||
message={
|
||||
<Trans
|
||||
i18nKey="appointments.labels.dataconsistency"
|
||||
components={[
|
||||
<Link to={`/manage/jobs/${problem.id}`} target="_blank" />,
|
||||
]}
|
||||
values={{
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -21,6 +21,7 @@ import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verif
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import _ from "lodash";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
@@ -39,9 +40,39 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
|
||||
employeevacation: true,
|
||||
ins_co_nm: null,
|
||||
});
|
||||
const [estimatorsFilter, setEstimatiorsFilter] = useLocalStorage(
|
||||
"estimators",
|
||||
[]
|
||||
);
|
||||
|
||||
const estimators = useMemo(() => {
|
||||
return _.uniq([
|
||||
...data
|
||||
.filter((d) => d.__typename === "appointments")
|
||||
.map((app) =>
|
||||
`${app.job?.est_ct_fn || ""} ${app.job?.est_ct_ln || ""}`.trim()
|
||||
)
|
||||
.filter((e) => e.length > 0),
|
||||
...bodyshop.md_estimators.map((e) =>
|
||||
`${e.est_ct_fn || ""} ${e.est_ct_ln || ""}`.trim()
|
||||
),
|
||||
]);
|
||||
}, [data, bodyshop.md_estimators]);
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
return data.filter(
|
||||
(d) =>
|
||||
return data.filter((d) => {
|
||||
const estFilter =
|
||||
d.__typename === "appointments"
|
||||
? estimatorsFilter.length === 0
|
||||
? true
|
||||
: !!estimatorsFilter.find(
|
||||
(e) =>
|
||||
e ===
|
||||
`${d.job?.est_ct_fn || ""} ${d.job?.est_ct_ln || ""}`.trim()
|
||||
)
|
||||
: true;
|
||||
|
||||
return (
|
||||
(d.block ||
|
||||
(filter.intake && d.isintake) ||
|
||||
(filter.manual && !d.isintake && d.block === false) ||
|
||||
@@ -50,9 +81,11 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
|
||||
!!d.employee)) &&
|
||||
(filter.ins_co_nm && filter.ins_co_nm.length > 0
|
||||
? filter.ins_co_nm.includes(d.job?.ins_co_nm)
|
||||
: true)
|
||||
);
|
||||
}, [data, filter]);
|
||||
: true) &&
|
||||
estFilter
|
||||
);
|
||||
});
|
||||
}, [data, filter, estimatorsFilter]);
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
@@ -63,6 +96,21 @@ export function ScheduleCalendarComponent({ data, refetch, bodyshop }) {
|
||||
extra={
|
||||
<Space wrap>
|
||||
<ScheduleAtsSummary appointments={filteredData} />
|
||||
<Select
|
||||
style={{ minWidth: "15rem" }}
|
||||
mode="multiple"
|
||||
placeholder={t("schedule.labels.estimators")}
|
||||
allowClear
|
||||
onClear={() => setEstimatiorsFilter([])}
|
||||
value={[...estimatorsFilter]}
|
||||
onChange={(e) => {
|
||||
setEstimatiorsFilter(e);
|
||||
}}
|
||||
options={estimators.map((e) => ({
|
||||
label: e,
|
||||
value: e,
|
||||
}))}
|
||||
/>
|
||||
<Select
|
||||
style={{ minWidth: "15rem" }}
|
||||
mode="multiple"
|
||||
|
||||
@@ -59,11 +59,12 @@ export function ScheduleManualEvent({ bodyshop, event }) {
|
||||
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"],
|
||||
});
|
||||
}
|
||||
form.resetFields();
|
||||
setVisibility(false);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setVisibility(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -47,9 +47,7 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
||||
bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs,
|
||||
painthrs: dayAcc.painthrs + dayVal.painthrs,
|
||||
sales:
|
||||
dayAcc.painthrs +
|
||||
dayVal.job.job_totals.totals.subtotal.amount / 100 +
|
||||
2500,
|
||||
dayAcc.sales + dayVal.job.job_totals.totals.subtotal.amount / 100,
|
||||
};
|
||||
},
|
||||
{ bodyhrs: 0, painthrs: 0, sales: 0 }
|
||||
|
||||
@@ -15,6 +15,8 @@ import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycen
|
||||
import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
|
||||
import ShopInfoSchedulingComponent from "./shop-info.scheduling.component";
|
||||
import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import queryString from "query-string";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -31,6 +33,10 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const search = queryString.parse(location.search);
|
||||
|
||||
return (
|
||||
<Card
|
||||
extra={
|
||||
@@ -43,7 +49,14 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Tabs>
|
||||
<Tabs
|
||||
defaultActiveKey={search.subtab}
|
||||
onChange={(key) =>
|
||||
history.push({
|
||||
search: `?tab=${search.tab}&subtab=${key}`,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Tabs.TabPane key="general" tab={t("bodyshop.labels.shopinfo")}>
|
||||
<ShopInfoGeneral form={form} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ShopInfoComponent from "./shop-info.component";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Form, notification } from "antd";
|
||||
import { useQuery, useMutation } from "@apollo/client";
|
||||
import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import FormsFieldChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import moment from "moment";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import ShopInfoComponent from "./shop-info.component";
|
||||
export default function ShopInfoContainer() {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
@@ -52,13 +52,28 @@ export default function ShopInfoContainer() {
|
||||
onFinish={handleFinish}
|
||||
initialValues={
|
||||
data
|
||||
? {
|
||||
...data.bodyshops[0],
|
||||
schedule_start_time: moment(
|
||||
data.bodyshops[0].schedule_start_time
|
||||
),
|
||||
schedule_end_time: moment(data.bodyshops[0].schedule_end_time),
|
||||
}
|
||||
? data.bodyshops[0].accountingconfig.ClosingPeriod
|
||||
? {
|
||||
...data.bodyshops[0],
|
||||
accountingconfig: {
|
||||
...data.bodyshops[0].accountingconfig,
|
||||
ClosingPeriod: [
|
||||
moment(data.bodyshops[0].accountingconfig.ClosingPeriod[0]),
|
||||
moment(data.bodyshops[0].accountingconfig.ClosingPeriod[1]),
|
||||
],
|
||||
},
|
||||
schedule_start_time: moment(
|
||||
data.bodyshops[0].schedule_start_time
|
||||
),
|
||||
schedule_end_time: moment(data.bodyshops[0].schedule_end_time),
|
||||
}
|
||||
: {
|
||||
...data.bodyshops[0],
|
||||
schedule_start_time: moment(
|
||||
data.bodyshops[0].schedule_start_time
|
||||
),
|
||||
schedule_end_time: moment(data.bodyshops[0].schedule_end_time),
|
||||
}
|
||||
: null
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import {
|
||||
Button,
|
||||
DatePicker,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
@@ -9,8 +11,13 @@ import {
|
||||
Space,
|
||||
Switch,
|
||||
} from "antd";
|
||||
import momentTZ from "moment-timezone";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import DatePickerRanges from "../../utils/DatePickerRanges";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||
import PhoneFormItem, {
|
||||
@@ -18,15 +25,29 @@ import PhoneFormItem, {
|
||||
} from "../form-items-formatted/phone-form-item.component";
|
||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
import momentTZ from "moment-timezone";
|
||||
const timeZonesList = momentTZ.tz.names();
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral);
|
||||
|
||||
export default function ShopInfoGeneral({ form }) {
|
||||
export function ShopInfoGeneral({ form, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const { ClosingPeriod } = useTreatments(
|
||||
["ClosingPeriod"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LayoutFormRow header={t("bodyshop.labels.businessinformation")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.businessinformation")}
|
||||
id="businessinformation"
|
||||
>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.shopname")}
|
||||
name="shopname"
|
||||
@@ -155,7 +176,10 @@ export default function ShopInfoGeneral({ form }) {
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.accountingsetup")}
|
||||
id="accountingsetup"
|
||||
>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.qbo")}
|
||||
valuePropName="checked"
|
||||
@@ -385,8 +409,25 @@ export default function ShopInfoGeneral({ form }) {
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
{ClosingPeriod.treatment === "on" && (
|
||||
<>
|
||||
<Form.Item
|
||||
allowClear
|
||||
name={["accountingconfig", "ClosingPeriod"]}
|
||||
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
|
||||
>
|
||||
<DatePicker.RangePicker
|
||||
format="MM/DD/YYYY"
|
||||
ranges={DatePickerRanges}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.scoreboardsetup")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.scoreboardsetup")}
|
||||
id="scoreboardsetup"
|
||||
>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dailypainttarget")}
|
||||
name={["scoreboard_target", "dailyPaintTarget"]}
|
||||
@@ -445,7 +486,10 @@ export default function ShopInfoGeneral({ form }) {
|
||||
<InputNumber min={1} precision={1} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.systemsettings")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.systemsettings")}
|
||||
id="systemsettings"
|
||||
>
|
||||
<Form.Item
|
||||
name={["md_referral_sources"]}
|
||||
label={t("bodyshop.fields.md_referral_sources")}
|
||||
@@ -589,6 +633,20 @@ export default function ShopInfoGeneral({ form }) {
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["md_email_cc", "parts_return_slip"]}
|
||||
label={t("bodyshop.fields.md_email_cc", {
|
||||
template: "parts_return_slip",
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
//message: t("general.validation.required"),
|
||||
type: "array",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["tt_allow_post_to_invoiced"]}
|
||||
label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
|
||||
@@ -655,7 +713,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.messagingpresets")}
|
||||
id="messagingpresets"
|
||||
>
|
||||
<Form.List name={["md_messaging_presets"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -720,7 +782,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.notespresets")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.notespresets")}
|
||||
id="notespresets"
|
||||
>
|
||||
<Form.List name={["md_notes_presets"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -785,7 +851,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.partslocations")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.partslocations")}
|
||||
id="partslocations"
|
||||
>
|
||||
<Form.List name={["md_parts_locations"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -839,7 +909,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.insurancecos")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.insurancecos")}
|
||||
id="insurancecos"
|
||||
>
|
||||
<Form.List name={["md_ins_cos"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -935,7 +1009,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.estimators")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.estimators")}
|
||||
id="estimators"
|
||||
>
|
||||
<Form.List name={["md_estimators"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -1024,7 +1102,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.filehandlers")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.filehandlers")}
|
||||
id="filehandlers"
|
||||
>
|
||||
<Form.List name={["md_filehandlers"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -1106,7 +1188,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.fields.md_ccc_rates")}
|
||||
id="md_ccc_rates"
|
||||
>
|
||||
<Form.List name={["md_ccc_rates"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -1223,7 +1309,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.fields.md_jobline_presets")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.fields.md_jobline_presets")}
|
||||
id="md_jobline_presets"
|
||||
>
|
||||
<Form.List name={["md_jobline_presets"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -1404,7 +1494,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.fields.md_parts_order_comment")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.fields.md_parts_order_comment")}
|
||||
id="md_parts_order_comment"
|
||||
>
|
||||
<Form.List name={["md_parts_order_comment"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -1470,7 +1564,11 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.md_to_emails")}>
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.labels.md_to_emails")}
|
||||
id="md_to_emails"
|
||||
>
|
||||
<Form.List name={["md_to_emails"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
|
||||
@@ -20,7 +20,10 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
||||
const TemplateListGenerated = TemplateList();
|
||||
return (
|
||||
<div>
|
||||
<LayoutFormRow header={t("bodyshop.labels.intakechecklist")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.intakechecklist")}
|
||||
id="intakechecklist"
|
||||
>
|
||||
<Form.List name={["intakechecklist", "form"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -188,7 +191,10 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
||||
</Form.Item>
|
||||
</SelectorDiv>
|
||||
|
||||
<LayoutFormRow header={t("bodyshop.labels.deliverchecklist")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.deliverchecklist")}
|
||||
id="deliverchecklist"
|
||||
>
|
||||
<Form.List name={["deliverchecklist", "form"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Form, InputNumber } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
@@ -316,6 +316,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.void")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:void"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.enter")}
|
||||
rules={[
|
||||
|
||||
@@ -95,7 +95,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
{form.getFieldValue("pbs_serialnumber")}
|
||||
</DataLabel>
|
||||
)}
|
||||
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dms.default_journal")}
|
||||
@@ -163,6 +162,32 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
)}
|
||||
{bodyshop.pbs_serialnumber && (
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dms.appostingaccount")}
|
||||
name={["pbs_configuration", "appostingaccount"]}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: "wip", label: "WIP" },
|
||||
{ value: "cogs", label: "COGS" },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
{bodyshop.pbs_serialnumber && (
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dms.apcontrol")}
|
||||
name={["pbs_configuration", "apcontrol"]}
|
||||
>
|
||||
<Select
|
||||
options={[
|
||||
{ value: "ro", label: "RO Number" },
|
||||
{ value: "vendordmsid", label: "Vendor DMS ID" },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}>
|
||||
<Form.List name={["cdk_configuration", "payers"]}>
|
||||
@@ -315,7 +340,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</DataLabel>
|
||||
</>
|
||||
)}
|
||||
<LayoutFormRow header={t("bodyshop.labels.responsibilitycenters.costs")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.responsibilitycenters.costs")}
|
||||
id="costs"
|
||||
>
|
||||
<Form.List name={["md_responsibility_centers", "costs"]}>
|
||||
{(fields, { add, remove }) => {
|
||||
return (
|
||||
@@ -462,6 +490,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.responsibilitycenters.profits")}
|
||||
id="profits"
|
||||
>
|
||||
<Form.List name={["md_responsibility_centers", "profits"]}>
|
||||
{(fields, { add, remove }) => {
|
||||
@@ -601,7 +630,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<div>
|
||||
<LayoutFormRow>
|
||||
<LayoutFormRow id="mappingname">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dms.mappingname")}
|
||||
key={`${index}name`}
|
||||
@@ -631,6 +660,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.defaultcostsmapping")}
|
||||
id="defaultcostsmapping"
|
||||
>
|
||||
<Form.Item
|
||||
label={t(
|
||||
@@ -4088,6 +4118,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.labels.responsibilitycenters.tax_accounts")}
|
||||
id="tax_accounts"
|
||||
>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.federal_tax")}
|
||||
@@ -4202,7 +4233,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
{DmsAp.treatment === "on" && (
|
||||
<LayoutFormRow>
|
||||
<LayoutFormRow id="federal_tax_itc">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.federal_tax_itc")}
|
||||
rules={[
|
||||
@@ -4316,7 +4347,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
)}
|
||||
<LayoutFormRow>
|
||||
<LayoutFormRow id="state_tax">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.state_tax")}
|
||||
rules={[
|
||||
@@ -4414,7 +4445,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow>
|
||||
<LayoutFormRow id="local_tax">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.local_tax")}
|
||||
rules={[
|
||||
@@ -4512,7 +4543,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={<div>AR</div>}>
|
||||
<LayoutFormRow header={<div>AR</div>} id="AR">
|
||||
{/* <Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.ar")}
|
||||
rules={[
|
||||
@@ -4576,7 +4607,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</LayoutFormRow>
|
||||
|
||||
{DmsAp.treatment === "on" && (
|
||||
<LayoutFormRow header={t("bodyshop.fields.responsibilitycenters.ap")}>
|
||||
<LayoutFormRow
|
||||
header={t("bodyshop.fields.responsibilitycenters.ap")}
|
||||
id="ap"
|
||||
>
|
||||
{/* <Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.ap")}
|
||||
rules={[
|
||||
@@ -4639,7 +4673,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
)}
|
||||
<LayoutFormRow header={<div>Refund</div>}>
|
||||
<LayoutFormRow header={<div>Refund</div>} id="refund">
|
||||
{/* <Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.refund")}
|
||||
rules={[
|
||||
@@ -4702,7 +4736,10 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
{Qb_Multi_Ar.treatment === "on" && (
|
||||
<LayoutFormRow header={<div>Multiple Payers Item</div>}>
|
||||
<LayoutFormRow
|
||||
header={<div>Multiple Payers Item</div>}
|
||||
id="accountitem"
|
||||
>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
|
||||
rules={[
|
||||
@@ -4730,7 +4767,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<LayoutFormRow>
|
||||
<LayoutFormRow id="sales_tax_codes">
|
||||
<Form.Item
|
||||
label={t(
|
||||
"bodyshop.fields.responsibilitycenters.sales_tax_codes.description"
|
||||
|
||||
@@ -44,7 +44,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<SelectorDiv>
|
||||
<SelectorDiv id="jobstatus">
|
||||
<Form.Item
|
||||
name={["md_ro_statuses", "statuses"]}
|
||||
label={t("bodyshop.labels.alljobstatuses")}
|
||||
@@ -322,6 +322,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.fields.statuses.production_colors")}
|
||||
id="production_colors"
|
||||
>
|
||||
<Form.List name={["md_ro_statuses", "production_colors"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
|
||||
@@ -92,7 +92,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Divider orientation="left">{t("bodyshop.labels.workingdays")}</Divider>
|
||||
<Space wrap size="large">
|
||||
<Space wrap size="large" id="workingdays">
|
||||
<Form.Item
|
||||
label={t("general.labels.sunday")}
|
||||
name={["workingdays", "sunday"]}
|
||||
@@ -143,7 +143,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
<LayoutFormRow header={t("bodyshop.labels.apptcolors")}>
|
||||
<LayoutFormRow header={t("bodyshop.labels.apptcolors")} id="apptcolors">
|
||||
<Form.List name={["appt_colors"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
@@ -208,7 +208,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.ssbuckets")}>
|
||||
<LayoutFormRow header={t("bodyshop.labels.ssbuckets")} id="ssbuckets">
|
||||
<Form.List name={["ssbuckets"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
|
||||
@@ -33,6 +33,7 @@ export function TechClockInComponent({ form, bodyshop, technician }) {
|
||||
<JobSearchSelect
|
||||
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
|
||||
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
||||
notInvoiced={!bodyshop.tt_allow_post_to_invoiced}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Form, notification, Space } 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_NEW_TIME_TICKET } from "../../graphql/timetickets.queries";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import TechClockInComponent from "./tech-job-clock-in-form.component";
|
||||
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
|
||||
import moment from "moment";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
|
||||
import TechClockInComponent from "./tech-job-clock-in-form.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
technician: selectTechnician,
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setTimeTicketContext: (context) =>
|
||||
@@ -25,9 +29,10 @@ export function TechClockInContainer({
|
||||
setTimeTicketContext,
|
||||
technician,
|
||||
bodyshop,
|
||||
currentUser,
|
||||
}) {
|
||||
console.log(
|
||||
"🚀 ~ file: tech-job-clock-in-form.container.jsx:29 ~ technician:",
|
||||
"🚀 ~ file: tech-job-clock-in-form.container.jsx:30 ~ technician:",
|
||||
technician
|
||||
);
|
||||
const [form] = Form.useForm();
|
||||
@@ -66,6 +71,12 @@ export function TechClockInContainer({
|
||||
values.cost_center
|
||||
);
|
||||
}),
|
||||
created_by: currentUser.email.concat(
|
||||
" | ",
|
||||
technician.employee_number
|
||||
.concat(" ", technician.first_name, " ", technician.last_name)
|
||||
.trim()
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -100,13 +111,24 @@ export function TechClockInContainer({
|
||||
employeeid: technician.id,
|
||||
flat_rate: emps.flat_rate,
|
||||
},
|
||||
created_by: currentUser.email.concat(
|
||||
" | ",
|
||||
technician.employee_number
|
||||
.concat(
|
||||
" ",
|
||||
technician.first_name,
|
||||
" ",
|
||||
technician.last_name
|
||||
)
|
||||
.trim()
|
||||
),
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("timetickets.actions.enter")}
|
||||
</Button>
|
||||
<TechJobPrintTickets />
|
||||
<TechJobPrintTickets attendacePrint={false} />
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => form.submit()}
|
||||
|
||||
@@ -5,10 +5,10 @@ import {
|
||||
Col,
|
||||
Form,
|
||||
InputNumber,
|
||||
notification,
|
||||
Popover,
|
||||
Row,
|
||||
Select,
|
||||
notification,
|
||||
} from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
@@ -16,13 +16,14 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
|
||||
import { UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
import TechJobClockoutDelete from "../tech-job-clock-out-delete/tech-job-clock-out-delete.component";
|
||||
import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component";
|
||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -40,6 +41,7 @@ export function TechClockOffButton({
|
||||
}) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET);
|
||||
const [updateJobStatus] = useMutation(UPDATE_JOB_STATUS);
|
||||
const [form] = Form.useForm();
|
||||
const { queryLoading, data: lineTicketData } = useQuery(
|
||||
GET_LINE_TICKET_BY_PK,
|
||||
@@ -59,7 +61,8 @@ export function TechClockOffButton({
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
logImEXEvent("tech_clock_out_job");
|
||||
|
||||
const status = values.status;
|
||||
delete values.status;
|
||||
setLoading(true);
|
||||
const result = await updateTimeticket({
|
||||
variables: {
|
||||
@@ -98,6 +101,26 @@ export function TechClockOffButton({
|
||||
message: t("timetickets.successes.clockedout"),
|
||||
});
|
||||
}
|
||||
if (!isShiftTicket) {
|
||||
const job_update_result = await updateJobStatus({
|
||||
variables: {
|
||||
jobId: jobId,
|
||||
status: status,
|
||||
},
|
||||
});
|
||||
|
||||
if (!!job_update_result.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.updating", {
|
||||
message: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.updated"),
|
||||
});
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
if (completedCallback) completedCallback();
|
||||
};
|
||||
@@ -195,7 +218,6 @@ export function TechClockOffButton({
|
||||
</Form.Item>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Form.Item
|
||||
name="cost_center"
|
||||
label={t("timetickets.fields.cost_center")}
|
||||
@@ -228,6 +250,29 @@ export function TechClockOffButton({
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{isShiftTicket ? (
|
||||
<div></div>
|
||||
) : (
|
||||
<Form.Item
|
||||
name="status"
|
||||
label={t("jobs.fields.status")}
|
||||
initialValue={
|
||||
lineTicketData && lineTicketData.jobs_by_pk.status
|
||||
}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
{bodyshop.md_ro_statuses.production_statuses.map((item) => (
|
||||
<Select.Option key={item}></Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Card, DatePicker, Form, Popover, Space } from "antd";
|
||||
import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -21,12 +21,13 @@ export default connect(
|
||||
mapDispatchToProps
|
||||
)(TechJobPrintTickets);
|
||||
|
||||
export function TechJobPrintTickets({ technician, event }) {
|
||||
export function TechJobPrintTickets({ technician, event, attendacePrint }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [visibility, setVisibility] = useState(false);
|
||||
const Templates = TemplateList("report_center");
|
||||
|
||||
useEffect(() => {
|
||||
if (visibility && event) {
|
||||
@@ -44,7 +45,10 @@ export function TechJobPrintTickets({ technician, event }) {
|
||||
try {
|
||||
await GenerateDocument(
|
||||
{
|
||||
name: TemplateList().timetickets_employee.key,
|
||||
name:
|
||||
attendacePrint === true
|
||||
? Templates.attendance_employee.key
|
||||
: Templates.timetickets_employee.key,
|
||||
variables: {
|
||||
...(start
|
||||
? { start: moment(start).startOf("day").format("YYYY-MM-DD") }
|
||||
@@ -60,9 +64,12 @@ export function TechJobPrintTickets({ technician, event }) {
|
||||
},
|
||||
{
|
||||
to: technician.email,
|
||||
subject: TemplateList().timetickets_employee.subject,
|
||||
subject:
|
||||
attendacePrint === true
|
||||
? Templates.attendance_employee.subject
|
||||
: Templates.timetickets_employee.subject,
|
||||
},
|
||||
"p"
|
||||
values.sendby // === "email" ? "e" : "p"
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -92,10 +99,25 @@ export function TechJobPrintTickets({ technician, event }) {
|
||||
format={"MM/DD/YYYY"}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item dependencies={["dates"]}>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("general.labels.sendby")}
|
||||
name="sendby"
|
||||
initialValue="p"
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value="e">{t("general.labels.email")}</Radio>
|
||||
<Radio value="p">{t("general.labels.print")}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Space wrap>
|
||||
<Button type="primary" onClick={() => form.submit()}>
|
||||
{t("general.actions.print")}
|
||||
{t("reportcenter.actions.generate")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -118,7 +140,7 @@ export function TechJobPrintTickets({ technician, event }) {
|
||||
return (
|
||||
<Popover content={overlay} visible={visibility}>
|
||||
<Button loading={loading} onClick={handleClick}>
|
||||
{t("general.actions.print")}
|
||||
{t("general.labels.reports")}
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
@@ -9,9 +9,10 @@ import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
selectAuthLevel,
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import RbacWrapper, {
|
||||
HasRbacAccess,
|
||||
@@ -20,6 +21,7 @@ import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
authLevel: selectAuthLevel,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -29,6 +31,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketList);
|
||||
export function TimeTicketList({
|
||||
bodyshop,
|
||||
authLevel,
|
||||
currentUser,
|
||||
disabled,
|
||||
loading,
|
||||
timetickets,
|
||||
@@ -193,7 +196,15 @@ export function TimeTicketList({
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: t("timetickets.fields.created_by"),
|
||||
dataIndex: "created_by",
|
||||
key: "created_by",
|
||||
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
|
||||
render: (text, record) => record.created_by,
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
@@ -254,7 +265,12 @@ export function TimeTicketList({
|
||||
(techConsole ? null : (
|
||||
<TimeTicketEnterButton
|
||||
actions={{ refetch }}
|
||||
context={{ jobId: jobId }}
|
||||
context={{
|
||||
jobId: jobId,
|
||||
created_by: currentUser.displayName
|
||||
? currentUser.email.concat(" | ", currentUser.displayName)
|
||||
: currentUser.email,
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
{t("timetickets.actions.enter")}
|
||||
|
||||
@@ -96,8 +96,9 @@ export function TimeTicketModalComponent({
|
||||
]}
|
||||
>
|
||||
<JobSearchSelect
|
||||
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
|
||||
convertedOnly={true}
|
||||
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
||||
notInvoiced={!bodyshop.tt_allow_post_to_invoiced}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Form, Modal, notification, PageHeader, Space } from "antd";
|
||||
import { Button, Form, Modal, PageHeader, Space, notification } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -77,6 +77,7 @@ export function TimeTicketModalContainer({
|
||||
)[0].rate
|
||||
: null,
|
||||
bodyshopid: bodyshop.id,
|
||||
created_by: timeTicketModal.context.created_by,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -4,48 +4,67 @@ import { useTranslation } from "react-i18next";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component";
|
||||
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
|
||||
|
||||
export default function TimeTicketShiftActive({ timetickets, refetch }) {
|
||||
export default function TimeTicketShiftActive({
|
||||
timetickets,
|
||||
refetch,
|
||||
isTechConsole,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{timetickets.length > 0 ? (
|
||||
<div>
|
||||
<Typography.Title level={2}>
|
||||
{t("timetickets.labels.shiftalreadyclockedon")}
|
||||
</Typography.Title>
|
||||
<List
|
||||
grid={{
|
||||
gutter: 32,
|
||||
xs: 1,
|
||||
sm: 2,
|
||||
md: 3,
|
||||
lg: 4,
|
||||
xl: 5,
|
||||
xxl: 6,
|
||||
}}
|
||||
dataSource={timetickets || []}
|
||||
renderItem={(ticket) => (
|
||||
<List.Item>
|
||||
<Card
|
||||
title={t(ticket.memo)}
|
||||
actions={[
|
||||
<TechClockOffButton
|
||||
jobId={ticket.jobid}
|
||||
timeTicketId={ticket.id}
|
||||
completedCallback={refetch}
|
||||
isShiftTicket
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<DataLabel label={t("timetickets.fields.clockon")}>
|
||||
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
|
||||
</DataLabel>
|
||||
</Card>
|
||||
</List.Item>
|
||||
)}
|
||||
></List>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<Typography.Title level={2}>
|
||||
{t("timetickets.labels.shiftalreadyclockedon")}
|
||||
</Typography.Title>
|
||||
{isTechConsole ? (
|
||||
<TechJobPrintTickets attendacePrint={true} />
|
||||
) : null}
|
||||
</div>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
<List
|
||||
grid={{
|
||||
gutter: 32,
|
||||
xs: 1,
|
||||
sm: 2,
|
||||
md: 3,
|
||||
lg: 4,
|
||||
xl: 5,
|
||||
xxl: 6,
|
||||
}}
|
||||
dataSource={timetickets || []}
|
||||
renderItem={(ticket) => (
|
||||
<List.Item>
|
||||
<Card
|
||||
title={t(ticket.memo)}
|
||||
actions={[
|
||||
<TechClockOffButton
|
||||
jobId={ticket.jobid}
|
||||
timeTicketId={ticket.id}
|
||||
completedCallback={refetch}
|
||||
isShiftTicket
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<DataLabel label={t("timetickets.fields.clockon")}>
|
||||
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
|
||||
</DataLabel>
|
||||
</Card>
|
||||
</List.Item>
|
||||
)}
|
||||
></List>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Form, notification } from "antd";
|
||||
import { Button, Form, Space, notification } from "antd";
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
import React, { useMemo, useState } from "react";
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
|
||||
import TimeTicektShiftComponent from "./time-ticket-shift-form.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -29,6 +30,10 @@ export function TimeTicektShiftContainer({
|
||||
isTechConsole,
|
||||
checkIfAlreadyClocked,
|
||||
}) {
|
||||
console.log(
|
||||
"🚀 ~ file: time-ticket-shift-form.container.jsx:28 ~ technician:",
|
||||
technician
|
||||
);
|
||||
const [form] = Form.useForm();
|
||||
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET);
|
||||
const { t } = useTranslation();
|
||||
@@ -65,6 +70,21 @@ export function TimeTicektShiftContainer({
|
||||
clockon: theTime,
|
||||
date: theTime,
|
||||
memo: values.memo,
|
||||
created_by: isTechConsole
|
||||
? currentUser.email.concat(
|
||||
" | ",
|
||||
technician.employee_number
|
||||
.concat(
|
||||
" ",
|
||||
technician.first_name,
|
||||
" ",
|
||||
technician.last_name
|
||||
)
|
||||
.trim()
|
||||
)
|
||||
: currentUser.displayName
|
||||
? currentUser.email.concat(" | ", currentUser.displayName)
|
||||
: currentUser.email,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -98,9 +118,14 @@ export function TimeTicektShiftContainer({
|
||||
initialValues={{ cost_center: t("timetickets.labels.shift") }}
|
||||
>
|
||||
<TimeTicektShiftComponent form={form} />
|
||||
<Button htmlType="submit" loading={loading}>
|
||||
{t("timetickets.actions.clockin")}
|
||||
</Button>
|
||||
<Space wrap>
|
||||
<Button htmlType="submit" loading={loading} type="primary">
|
||||
{t("timetickets.actions.clockin")}
|
||||
</Button>
|
||||
{isTechConsole === true ? (
|
||||
<TechJobPrintTickets attendacePrint={true} />
|
||||
) : null}
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -76,6 +76,7 @@ export function TimeTicketShiftContainer({
|
||||
<TimeTicketShiftActive
|
||||
timetickets={data ? data.timetickets : []}
|
||||
refetch={refetch}
|
||||
isTechConsole={isTechConsole}
|
||||
/>
|
||||
) : (
|
||||
<TimeTicketShiftFormContainer
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Alert, Button, Col, Row, Space } from "antd";
|
||||
import i18n from "i18next";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectUpdateAvailable } from "../../redux/application/application.selectors";
|
||||
import { AlertOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { setUpdateAvailable } from "../../redux/application/application.actions";
|
||||
import { store } from "../../redux/store";
|
||||
import * as serviceWorkerRegistration from "../../serviceWorkerRegistration";
|
||||
|
||||
let globalRegistration;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
updateAvailable: selectUpdateAvailable,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UpdateAlert);
|
||||
|
||||
export function UpdateAlert({ updateAvailable }) {
|
||||
const { t } = useTranslation();
|
||||
if (!updateAvailable) return null;
|
||||
return (
|
||||
<Alert
|
||||
message={t("general.messages.newversiontitle")}
|
||||
showIcon
|
||||
icon={<AlertOutlined />}
|
||||
description={
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col sm={24} md={16} lg={18}>
|
||||
{t("general.messages.newversionmessage")}
|
||||
</Col>
|
||||
<Col sm={24} md={8} lg={6}>
|
||||
<Space wrap>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
window.open("https://imex-online.noticeable.news/", "_blank");
|
||||
}}
|
||||
>
|
||||
{i18n.t("general.actions.viewreleasenotes")}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={async () => {
|
||||
if (globalRegistration && globalRegistration.waiting) {
|
||||
await globalRegistration.unregister();
|
||||
// Makes Workbox call skipWaiting()
|
||||
globalRegistration.waiting.postMessage({
|
||||
type: "SKIP_WAITING",
|
||||
});
|
||||
// Once the service worker is unregistered, we can reload the page to let
|
||||
// the browser download a fresh copy of our app (invalidating the cache)
|
||||
window.location.reload();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{i18n.t("general.actions.refresh")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
}
|
||||
closable={false}
|
||||
type="warning"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const onServiceWorkerUpdate = (registration) => {
|
||||
console.log("onServiceWorkerUpdate", registration);
|
||||
globalRegistration = registration;
|
||||
store.dispatch(setUpdateAvailable(true));
|
||||
};
|
||||
|
||||
serviceWorkerRegistration.register({ onUpdate: onServiceWorkerUpdate });
|
||||
@@ -6,8 +6,9 @@ import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
|
||||
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -16,6 +17,14 @@ const mapStateToProps = createStructuredSelector({
|
||||
export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedJobs, setSelectedJobs] = useState([]);
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" },
|
||||
});
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@@ -28,6 +37,9 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
</Link>
|
||||
),
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
@@ -43,11 +55,17 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
|
||||
title: t("jobs.fields.clm_no"),
|
||||
dataIndex: "clm_no",
|
||||
key: "clm_no",
|
||||
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
},
|
||||
|
||||
{
|
||||
@@ -57,6 +75,9 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
|
||||
),
|
||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -76,6 +97,7 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
|
||||
rowKey="id"
|
||||
scroll={{ x: true }}
|
||||
dataSource={vehicle.jobs}
|
||||
onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelect: (record, selected, selectedRows) => {
|
||||
setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []);
|
||||
|
||||
@@ -57,6 +57,8 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
|
||||
v_model_yr
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
est_ct_fn
|
||||
est_ct_ln
|
||||
labhrs: joblines_aggregate(
|
||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||
) {
|
||||
|
||||
@@ -1,36 +1,5 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
// export const CONVERSATION_LIST_SUBSCRIPTION = gql`
|
||||
// subscription CONVERSATION_LIST_SUBSCRIPTION {
|
||||
// conversations(
|
||||
// order_by: { updated_at: desc }
|
||||
// limit: 50
|
||||
// where: { archived: { _eq: false } }
|
||||
// ) {
|
||||
// phone_num
|
||||
// id
|
||||
// updated_at
|
||||
// unreadcnt
|
||||
// messages_aggregate(
|
||||
// where: { read: { _eq: false }, isoutbound: { _eq: false } }
|
||||
// ) {
|
||||
// aggregate {
|
||||
// count
|
||||
// }
|
||||
// }
|
||||
// job_conversations {
|
||||
// job {
|
||||
// id
|
||||
// ro_number
|
||||
// ownr_fn
|
||||
// ownr_ln
|
||||
// ownr_co_nm
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// `;
|
||||
|
||||
export const UNREAD_CONVERSATION_COUNT = gql`
|
||||
query UNREAD_CONVERSATION_COUNT {
|
||||
messages_aggregate(
|
||||
@@ -44,10 +13,11 @@ export const UNREAD_CONVERSATION_COUNT = gql`
|
||||
`;
|
||||
|
||||
export const CONVERSATION_LIST_QUERY = gql`
|
||||
query CONVERSATION_LIST_QUERY {
|
||||
query CONVERSATION_LIST_QUERY($offset: Int!) {
|
||||
conversations(
|
||||
order_by: { updated_at: desc }
|
||||
limit: 50
|
||||
offset: $offset
|
||||
where: { archived: { _eq: false } }
|
||||
) {
|
||||
phone_num
|
||||
|
||||
@@ -34,6 +34,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
id
|
||||
lbr_adjustments
|
||||
converted
|
||||
status
|
||||
}
|
||||
joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) {
|
||||
id
|
||||
@@ -56,6 +57,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
actualhrs
|
||||
ciecacode
|
||||
cost_center
|
||||
created_by
|
||||
date
|
||||
id
|
||||
jobid
|
||||
|
||||
@@ -682,6 +682,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
date_rentalresp
|
||||
date_exported
|
||||
date_repairstarted
|
||||
date_void
|
||||
status
|
||||
owner_owing
|
||||
tax_registration_number
|
||||
@@ -746,7 +747,6 @@ export const GET_JOB_BY_PK = gql`
|
||||
amount
|
||||
payer
|
||||
created_at
|
||||
stripeid
|
||||
transactionid
|
||||
memo
|
||||
date
|
||||
@@ -1075,6 +1075,10 @@ export const UPDATE_JOB = gql`
|
||||
lbr_adjustments
|
||||
suspended
|
||||
queued_for_parts
|
||||
scheduled_completion
|
||||
actual_in
|
||||
date_repairstarted
|
||||
date_void
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1122,6 +1126,7 @@ export const VOID_JOB = gql`
|
||||
update_jobs_by_pk(_set: $job, pk_columns: { id: $jobId }) {
|
||||
id
|
||||
date_exported
|
||||
date_void
|
||||
status
|
||||
alt_transport
|
||||
ro_number
|
||||
@@ -1216,10 +1221,10 @@ export const ACTIVE_JOBS_FOR_AUTOCOMPLETE = gql`
|
||||
query ACTIVE_JOBS_FOR_AUTOCOMPLETE($statuses: [String!]!) {
|
||||
jobs(where: { status: { _in: $statuses } }) {
|
||||
id
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ro_number
|
||||
|
||||
vehicleid
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
@@ -1247,6 +1252,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
|
||||
}
|
||||
) {
|
||||
id
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ro_number
|
||||
@@ -1263,6 +1269,7 @@ export const SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE = gql`
|
||||
query SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
id
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ro_number
|
||||
@@ -1281,6 +1288,7 @@ export const SEARCH_FOR_JOBS = gql`
|
||||
search_jobs(args: { search: $search }, limit: 25) {
|
||||
id
|
||||
ro_number
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export const QUERY_OWNER_BY_ID = gql`
|
||||
preferred_contact
|
||||
note
|
||||
tax_number
|
||||
jobs {
|
||||
jobs(order_by: { date_open: desc }) {
|
||||
id
|
||||
ro_number
|
||||
clm_no
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql`
|
||||
clockon
|
||||
cost_center
|
||||
created_at
|
||||
created_by
|
||||
date
|
||||
id
|
||||
rate
|
||||
@@ -80,6 +81,7 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
|
||||
clockon
|
||||
cost_center
|
||||
created_at
|
||||
created_by
|
||||
date
|
||||
id
|
||||
rate
|
||||
@@ -112,6 +114,7 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
|
||||
clockon
|
||||
cost_center
|
||||
created_at
|
||||
created_by
|
||||
date
|
||||
id
|
||||
rate
|
||||
@@ -151,6 +154,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
|
||||
clockon
|
||||
cost_center
|
||||
created_at
|
||||
created_by
|
||||
date
|
||||
id
|
||||
rate
|
||||
@@ -181,6 +185,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
|
||||
clockon
|
||||
cost_center
|
||||
created_at
|
||||
created_by
|
||||
date
|
||||
id
|
||||
rate
|
||||
@@ -210,6 +215,7 @@ export const INSERT_NEW_TIME_TICKET = gql`
|
||||
insert_timetickets(objects: $timeTicketInput) {
|
||||
returning {
|
||||
id
|
||||
created_by
|
||||
clockon
|
||||
clockoff
|
||||
employeeid
|
||||
|
||||
@@ -28,11 +28,10 @@ export const QUERY_VEHICLE_BY_ID = gql`
|
||||
updated_at
|
||||
trim_color
|
||||
notes
|
||||
jobs {
|
||||
jobs(order_by: { date_open: desc }) {
|
||||
id
|
||||
ro_number
|
||||
ownr_fn
|
||||
|
||||
ownr_ln
|
||||
owner {
|
||||
id
|
||||
|
||||
@@ -13,7 +13,7 @@ import queryString from "query-string";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useHistory, useLocation, Link } from "react-router-dom";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import SocketIO from "socket.io-client";
|
||||
import AlertComponent from "../../components/alert/alert.component";
|
||||
@@ -22,6 +22,7 @@ import DmsCustomerSelector from "../../components/dms-customer-selector/dms-cust
|
||||
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
|
||||
import DmsPostForm from "../../components/dms-post-form/dms-post-form.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries";
|
||||
import {
|
||||
@@ -29,7 +30,6 @@ import {
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -46,6 +46,7 @@ export const socket = SocketIO(
|
||||
process.env.NODE_ENV === "production"
|
||||
? process.env.REACT_APP_AXIOS_BASE_API_URL
|
||||
: window.location.origin,
|
||||
// "http://localhost:4000", // for dev testing,
|
||||
{
|
||||
path: "/ws",
|
||||
withCredentials: true,
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
notification,
|
||||
PageHeader,
|
||||
Popconfirm,
|
||||
Row,
|
||||
@@ -17,12 +16,14 @@ import {
|
||||
Statistic,
|
||||
Switch,
|
||||
Typography,
|
||||
notification,
|
||||
} from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
//import { useHistory } from "react-router-dom";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import Dinero from "dinero.js";
|
||||
import moment from "moment";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -37,7 +38,6 @@ import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.qu
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import Dinero from "dinero.js";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -55,6 +55,11 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
const { ClosingPeriod } = useTreatments(
|
||||
["ClosingPeriod"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const handleFinish = async ({ removefromproduction, ...values }) => {
|
||||
setLoading(true);
|
||||
@@ -254,12 +259,40 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
||||
if (!value || moment(value).isSameOrAfter(moment(), "day")) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject(
|
||||
new Error(t("jobs.labels.dms.invoicedatefuture"))
|
||||
);
|
||||
},
|
||||
}),
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (
|
||||
ClosingPeriod.treatment === "on" &&
|
||||
bodyshop.accountingconfig.ClosingPeriod
|
||||
) {
|
||||
if (
|
||||
moment(value).isSameOrAfter(
|
||||
moment(
|
||||
bodyshop.accountingconfig.ClosingPeriod[0]
|
||||
).startOf("day")
|
||||
) &&
|
||||
moment(value).isSameOrBefore(
|
||||
moment(
|
||||
bodyshop.accountingconfig.ClosingPeriod[1]
|
||||
).endOf("day")
|
||||
)
|
||||
) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(
|
||||
new Error(t("jobs.labels.closingperiod"))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<DateTimePicker
|
||||
|
||||
@@ -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,
|
||||
@@ -25,12 +25,17 @@ import {
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
import "./manage.page.styles.scss";
|
||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
|
||||
const ManageRootPage = lazy(() =>
|
||||
import("../manage-root/manage-root.page.container")
|
||||
);
|
||||
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")
|
||||
);
|
||||
@@ -195,6 +200,8 @@ export function Manage({ match, conflict, bodyshop }) {
|
||||
>
|
||||
<PaymentModalContainer />
|
||||
|
||||
<CardPaymentModalContainer />
|
||||
|
||||
<BreadCrumbs />
|
||||
<BillEnterModalContainer />
|
||||
<JobCostingModal />
|
||||
@@ -394,6 +401,7 @@ export function Manage({ match, conflict, bodyshop }) {
|
||||
<>
|
||||
<ChatAffixContainer />
|
||||
<Layout className="layout-container">
|
||||
<UpdateAlert />
|
||||
<HeaderContainer />
|
||||
|
||||
<Content className="content-container">
|
||||
|
||||
@@ -6,7 +6,7 @@ import AlertComponent from "../../components/alert/alert.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
|
||||
import { setBodyshop } from "../../redux/user/user.actions";
|
||||
import "../../utils/RegisterSw";
|
||||
//import "../../utils/RegisterSw";
|
||||
import ManagePage from "./manage.page.component";
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Tabs } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import queryString from "query-string";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container";
|
||||
import ShopInfoContainer from "../../components/shop-info/shop-info.container";
|
||||
@@ -24,6 +26,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const search = queryString.parse(useLocation().search);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t("titles.shop");
|
||||
setSelectedHeader("shop");
|
||||
@@ -35,9 +40,16 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
|
||||
]);
|
||||
}, [t, setSelectedHeader, setBreadcrumbs, bodyshop.shopname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!search.tab) history.push({ search: "?tab=info" });
|
||||
}, [history, search]);
|
||||
|
||||
return (
|
||||
<RbacWrapper action="shop:config">
|
||||
<Tabs>
|
||||
<Tabs
|
||||
defaultActiveKey={search.tab}
|
||||
onChange={(key) => history.push({ search: `?tab=${key}` })}
|
||||
>
|
||||
<Tabs.TabPane tab={t("bodyshop.labels.shopinfo")} key="info">
|
||||
<ShopInfoContainer />
|
||||
</Tabs.TabPane>
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import { BackTop, Layout } from "antd";
|
||||
import React, { lazy, Suspense, useEffect } from "react";
|
||||
import React, { Suspense, lazy, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Redirect, Route, Switch } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
|
||||
|
||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import TechHeader from "../../components/tech-header/tech-header.component";
|
||||
import TechSider from "../../components/tech-sider/tech-sider.component";
|
||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import "./tech.page.styles.scss";
|
||||
const TimeTicketModalContainer = lazy(() =>
|
||||
import("../../components/time-ticket-modal/time-ticket-modal.container")
|
||||
);
|
||||
const EmailOverlayContainer = lazy(() =>
|
||||
import("../../components/email-overlay/email-overlay.container.jsx")
|
||||
);
|
||||
const PrintCenterModalContainer = lazy(() =>
|
||||
import("../../components/print-center-modal/print-center-modal.container")
|
||||
);
|
||||
@@ -56,7 +60,9 @@ export function TechPage({ technician, match }) {
|
||||
<TechSider />
|
||||
<Layout>
|
||||
{technician ? null : <Redirect to={`${match.path}/login`} />}
|
||||
<UpdateAlert />
|
||||
<TechHeader />
|
||||
|
||||
<Content className="tech-content-container">
|
||||
<ErrorBoundary>
|
||||
<Suspense
|
||||
@@ -66,6 +72,7 @@ export function TechPage({ technician, match }) {
|
||||
>
|
||||
<FeatureWrapper featureName="tech-console">
|
||||
<TimeTicketModalContainer />
|
||||
<EmailOverlayContainer />
|
||||
<PrintCenterModalContainer />
|
||||
<Switch>
|
||||
<Route
|
||||
|
||||
@@ -9,7 +9,7 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import "../../utils/RegisterSw";
|
||||
//import "../../utils/RegisterSw";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
|
||||
@@ -62,3 +62,8 @@ export const setProblemJobs = (problemJobs) => ({
|
||||
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
|
||||
payload: problemJobs,
|
||||
});
|
||||
|
||||
export const setUpdateAvailable = (isUpdateAvailable) => ({
|
||||
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
|
||||
payload: isUpdateAvailable,
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import ApplicationActionTypes from "./application.types";
|
||||
const INITIAL_STATE = {
|
||||
loading: false,
|
||||
online: true,
|
||||
updateAvailable: false,
|
||||
breadcrumbs: [],
|
||||
recentItems: [],
|
||||
selectedHeader: "home",
|
||||
@@ -18,6 +19,11 @@ const INITIAL_STATE = {
|
||||
|
||||
const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case ApplicationActionTypes.SET_UPDATE_AVAILABLE:
|
||||
return {
|
||||
...state,
|
||||
updateAvailable: action.payload,
|
||||
};
|
||||
case ApplicationActionTypes.SET_SELECTED_HEADER:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -48,3 +48,7 @@ export const selectProblemJobs = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.problemJobs
|
||||
);
|
||||
export const selectUpdateAvailable = createSelector(
|
||||
[selectApplication],
|
||||
(application) => application.updateAvailable
|
||||
);
|
||||
|
||||
@@ -12,5 +12,6 @@ const ApplicationActionTypes = {
|
||||
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
|
||||
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
|
||||
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
|
||||
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE"
|
||||
};
|
||||
export default ApplicationActionTypes;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"blocked": "Blocked",
|
||||
"cancelledappointment": "Canceled appointment for: ",
|
||||
"completingjobs": "Completing Jobs",
|
||||
"dataconsistency": "{{ro_number}} has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.",
|
||||
"dataconsistency": "<0>{{ro_number}}</0> has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.",
|
||||
"expectedjobs": "Expected Jobs in Production: ",
|
||||
"expectedprodhrs": "Expected Production Hours:",
|
||||
"history": "History",
|
||||
@@ -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": {
|
||||
@@ -101,10 +100,11 @@
|
||||
"messages": {
|
||||
"admin_jobmarkexported": "ADMIN: Job marked as exported.",
|
||||
"admin_jobmarkforreexport": "ADMIN: Job marked for re-export.",
|
||||
"admin_jobuninvoice": "ADMIN: Job has been uninvoiced.",
|
||||
"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}}.",
|
||||
@@ -222,6 +222,7 @@
|
||||
"reexport": "Bill marked for re-export."
|
||||
},
|
||||
"validation": {
|
||||
"closingperiod": "This Bill Date is outside of the Closing Period.",
|
||||
"inventoryquantity": "Quantity must be greater than or equal to what has been added to inventory ({{number}}).",
|
||||
"manualinhouse": "Manual posting to the in house vendor is restricted. ",
|
||||
"unique_invoice_number": "This invoice number has already been entered for this vendor."
|
||||
@@ -261,6 +262,7 @@
|
||||
"bill_local_tax_rate": "Bill - Provincial/State Tax Rate %",
|
||||
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %",
|
||||
"city": "City",
|
||||
"closingperiod": "Closing Period",
|
||||
"country": "Country",
|
||||
"dailybodytarget": "Scoreboard - Daily Body Target",
|
||||
"dailypainttarget": "Scoreboard - Daily Paint Target",
|
||||
@@ -269,6 +271,8 @@
|
||||
"templates": "Delivery Templates"
|
||||
},
|
||||
"dms": {
|
||||
"apcontrol": "AP Control Number",
|
||||
"appostingaccount": "AP Posting Account",
|
||||
"cashierid": "Cashier ID",
|
||||
"default_journal": "Default Journal",
|
||||
"disablebillwip": "Disable bill WIP for A/P Posting",
|
||||
@@ -405,7 +409,8 @@
|
||||
"list-active": "Jobs -> List Active",
|
||||
"list-all": "Jobs -> List All",
|
||||
"list-ready": "Jobs -> List Ready",
|
||||
"partsqueue": "Jobs -> Parts Queue"
|
||||
"partsqueue": "Jobs -> Parts Queue",
|
||||
"void": "Jobs -> Void"
|
||||
},
|
||||
"owners": {
|
||||
"detail": "Owners -> Detail",
|
||||
@@ -857,7 +862,9 @@
|
||||
"prodhrssummary": "Production Hours Summary",
|
||||
"productiondollars": "Total dollars in Production",
|
||||
"productionhours": "Total hours in Production",
|
||||
"projectedmonthlysales": "Projected Monthly Sales"
|
||||
"projectedmonthlysales": "Projected Monthly Sales",
|
||||
"scheduledintoday": "Sheduled In Today: {{date}}",
|
||||
"scheduledouttoday": "Sheduled Out Today: {{date}}"
|
||||
}
|
||||
},
|
||||
"dms": {
|
||||
@@ -1007,6 +1014,7 @@
|
||||
"cancel": "Cancel",
|
||||
"clear": "Clear",
|
||||
"close": "Close",
|
||||
"copied": "Copied!",
|
||||
"copylink": "Copy Link",
|
||||
"create": "Create",
|
||||
"delete": "Delete",
|
||||
@@ -1023,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",
|
||||
@@ -1084,6 +1093,7 @@
|
||||
"passwordsdonotmatch": "The passwords you have entered do not match.",
|
||||
"print": "Print",
|
||||
"refresh": "Refresh",
|
||||
"reports": "Reports",
|
||||
"required": "Required",
|
||||
"saturday": "Saturday",
|
||||
"search": "Search...",
|
||||
@@ -1117,7 +1127,7 @@
|
||||
},
|
||||
"messages": {
|
||||
"exception": "$t(titles.app) has encountered an error. Please try again. If the problem persists, please submit a support ticket or contact us.",
|
||||
"newversionmessage": "Click refresh below to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.",
|
||||
"newversionmessage": "Click refresh to update to the latest available version of ImEX Online. Please make sure all other tabs and windows are closed.",
|
||||
"newversiontitle": "New version of ImEX Online Available",
|
||||
"noacctfilepath": "There is no accounting file path set. You will not be able to export any items.",
|
||||
"nofeatureaccess": "You do not have access to this feature of ImEX Online. Please contact support to request a license for this feature.",
|
||||
@@ -1183,26 +1193,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": {
|
||||
@@ -1434,6 +1445,7 @@
|
||||
"date_repairstarted": "Repairs Started",
|
||||
"date_scheduled": "Scheduled",
|
||||
"date_towin": "Towed In",
|
||||
"date_void": "Void",
|
||||
"ded_amt": "Deductible",
|
||||
"ded_note": "Deductible Note",
|
||||
"ded_status": "Deductible Status",
|
||||
@@ -1449,6 +1461,7 @@
|
||||
"cost_dms_acctnumber": "Cost DMS Acct #",
|
||||
"dms_make": "DMS Make",
|
||||
"dms_model": "DMS Model",
|
||||
"dms_unsold": "New, Unsold Vehicle",
|
||||
"dms_wip_acctnumber": "Cost WIP DMS Acct #",
|
||||
"id": "DMS ID",
|
||||
"inservicedate": "In Service Date",
|
||||
@@ -1680,6 +1693,7 @@
|
||||
"checklists": "Checklists",
|
||||
"closeconfirm": "Are you sure you want to close this job? This cannot be easily undone.",
|
||||
"closejob": "Close Job {{ro_number}}",
|
||||
"closingperiod": "This Invoice Date is outside of the Closing Period.",
|
||||
"contracts": "CC Contracts",
|
||||
"convertedtolabor": "Lines Converted to Labor",
|
||||
"cost": "Cost",
|
||||
@@ -1915,7 +1929,7 @@
|
||||
"customers": "Customers",
|
||||
"dashboard": "Dashboard",
|
||||
"enterbills": "Enter Bills",
|
||||
"entercardpayment": "",
|
||||
"entercardpayment": "New Card Charge",
|
||||
"enterpayment": "Enter Payments",
|
||||
"entertimeticket": "Enter Time Tickets",
|
||||
"export": "Export",
|
||||
@@ -1927,7 +1941,6 @@
|
||||
"newjob": "Create New Job",
|
||||
"owners": "Owners",
|
||||
"parts-queue": "Parts Queue",
|
||||
"paymentremindersms": "",
|
||||
"phonebook": "Phonebook",
|
||||
"productionboard": "Production Board - Visual",
|
||||
"productionlist": "Production Board - List",
|
||||
@@ -2189,6 +2202,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."
|
||||
@@ -2214,14 +2230,18 @@
|
||||
"external": "External",
|
||||
"findermodal": "ICBC Payment Finder",
|
||||
"insurance": "Insurance",
|
||||
"markexported": "Mark Exported",
|
||||
"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"
|
||||
},
|
||||
"successes": {
|
||||
"exported": "Payment(s) exported successfully.",
|
||||
"markexported": "Payment(s) marked exported.",
|
||||
"markreexported": "Payment marked for re-export successfully",
|
||||
"payment": "Payment created successfully. ",
|
||||
"stripe": "Credit card transaction charged successfully."
|
||||
}
|
||||
@@ -2332,7 +2352,8 @@
|
||||
"mechanical_authorization": "Mechanical Authorization",
|
||||
"mpi_animal_checklist": "MPI - Animal Checklist",
|
||||
"mpi_eglass_auth": "MPI - eGlass Auth",
|
||||
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet",
|
||||
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet (Direct Repair)",
|
||||
"mpi_final_repair_acct_sheet": "MPI - Final Accounting Sheet",
|
||||
"paint_grid": "Paint Grid",
|
||||
"parts_invoice_label_single": "Parts Label Single",
|
||||
"parts_label_multiple": "Parts Label - Multi",
|
||||
@@ -2396,6 +2417,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}}",
|
||||
"sublet_order": "Sublet Order PO: {{ro_number}} - {{name}}"
|
||||
}
|
||||
},
|
||||
@@ -2563,6 +2585,7 @@
|
||||
"jobs_completed_not_invoiced": "Jobs Completed not Invoiced",
|
||||
"jobs_invoiced_not_exported": "Jobs Invoiced not Exported",
|
||||
"jobs_reconcile": "Parts/Sublet/Labor Reconciliation",
|
||||
"jobs_scheduled_completion": "Jobs Scheduled Completion",
|
||||
"lag_time": "Lag Time",
|
||||
"open_orders": "Open Orders by Date",
|
||||
"open_orders_csr": "Open Orders by CSR",
|
||||
@@ -2611,6 +2634,7 @@
|
||||
"timetickets_summary": "Time Tickets Summary",
|
||||
"unclaimed_hrs": "Unclaimed Hours",
|
||||
"void_ros": "Void ROs",
|
||||
"work_in_progress_jobs": "Work in Progress - Jobs",
|
||||
"work_in_progress_labour": "Work in Progress - Labor",
|
||||
"work_in_progress_payables": "Work in Progress - Payables"
|
||||
}
|
||||
@@ -2619,6 +2643,7 @@
|
||||
"labels": {
|
||||
"atssummary": "ATS Summary",
|
||||
"employeevacation": "Employee Vacations",
|
||||
"estimators": "Filter by Writer/Customer Rep.",
|
||||
"ins_co_nm_filter": "Filter by Insurance Company",
|
||||
"intake": "Intake Events",
|
||||
"manual": "Manual Events",
|
||||
@@ -2711,6 +2736,7 @@
|
||||
"clockon": "Clocked In",
|
||||
"committed": "",
|
||||
"cost_center": "Cost Center",
|
||||
"created_by": "Created By",
|
||||
"date": "Ticket Date",
|
||||
"efficiency": "Efficiency",
|
||||
"employee": "Employee",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user