Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ interface ReimbursementRequestFormViewProps {
isLeadershipApproved?: boolean;
onSubmitToFinance?: (data: ReimbursementRequestFormInput) => void;
isSubmitting?: boolean;
applySplitShippingToProducts: (totalShipping?: number) => void;
}

const ReimbursementRequestFormView: React.FC<ReimbursementRequestFormViewProps> = ({
Expand All @@ -112,7 +113,8 @@ const ReimbursementRequestFormView: React.FC<ReimbursementRequestFormViewProps>
isEditing = false,
isLeadershipApproved = false,
onSubmitToFinance,
isSubmitting = false
isSubmitting = false,
applySplitShippingToProducts
}) => {
const [datePickerOpen, setDatePickerOpen] = useState(false);
const [showAddRefundSourceModal, setShowAddRefundSourceModal] = useState(false);
Expand Down Expand Up @@ -888,6 +890,54 @@ const ReimbursementRequestFormView: React.FC<ReimbursementRequestFormViewProps>
)}
/>
</FormControl>
{/* Total Shipping */}
<FormControl sx={{ borderRadius: '25px', width: '100%' }}>
<FormLabel
sx={{
color: '#dd524c',
textShadow: '1.5px 0 #dd524c',
letterSpacing: '0.5px',
textDecoration: 'underline',
textUnderlineOffset: '3.5px',
textDecorationThickness: '0.6px',
paddingBottom: '2px',
fontSize: 'x-large',
fontWeight: 'bold'
}}
>
Total Shipping
</FormLabel>

<Controller
name="splitShipping"
control={control}
render={({ field: { onChange, value } }) => (
<TextField
value={value ?? ''}
onChange={(e) => {
onChange(e);
}}
onBlur={() => applySplitShippingToProducts(Number(value))}
placeholder="Enter total shipping cost"
type="number"
inputProps={{ min: 0, step: 0.01 }}
size="small"
fullWidth
sx={{
'& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button': {
WebkitAppearance: 'none',
margin: 0
},
'& input[type=number]': {
MozAppearance: 'textfield'
}
}}
/>
)}
/>

<FormHelperText error>{errors.splitShipping?.message}</FormHelperText>
</FormControl>
</Stack>
</Grid>
</Grid>
Expand All @@ -913,6 +963,7 @@ const ReimbursementRequestFormView: React.FC<ReimbursementRequestFormViewProps>
firstRefundSourceName={firstRefundSource.name}
secondRefundSourceName={secondRefundSource.name}
allProjects={allProjects}
applySplitShippingToProducts={applySplitShippingToProducts}
/>
<FormHelperText error>{errors.reimbursementProducts?.message}</FormHelperText>
</FormControl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ interface ReimbursementProductTableProps {
firstRefundSourceName?: string;
secondRefundSourceName?: string;
allProjects: ProjectPreview[];
applySplitShippingToProducts: (totalShipping?: number) => void;
}

const ListItem = styled('li')(({ theme }) => ({
Expand Down Expand Up @@ -156,7 +157,8 @@ const ReimbursementProductTable: React.FC<ReimbursementProductTableProps> = ({
firstRefundSourceName,
secondRefundSourceName,
watch,
allProjects
allProjects,
applySplitShippingToProducts
}) => {
const uniqueWbsElementsWithProducts = new Map<
string,
Expand All @@ -181,6 +183,7 @@ const ReimbursementProductTable: React.FC<ReimbursementProductTableProps> = ({
setValue(`reimbursementProducts.${index}.refundSources`, [{ indexCode: firstRefundSourceIndexCode, amount: value }]);
}
};
const totalShipping = watch('splitShipping');

const userTheme = useTheme();
const hoverColor = userTheme.palette.action.hover;
Expand Down Expand Up @@ -409,6 +412,7 @@ const ReimbursementProductTable: React.FC<ReimbursementProductTableProps> = ({
cost: 0,
refundSources: []
});
setTimeout(() => applySplitShippingToProducts(Number(totalShipping)), 0);
}
}}
value={null}
Expand All @@ -434,6 +438,7 @@ const ReimbursementProductTable: React.FC<ReimbursementProductTableProps> = ({
cost: 0,
refundSources: []
});
setTimeout(() => applySplitShippingToProducts(Number(totalShipping)), 0);
}
}}
value={null}
Expand Down Expand Up @@ -805,7 +810,15 @@ const ReimbursementProductTable: React.FC<ReimbursementProductTableProps> = ({
backgroundColor: hoverColor
}
}}
onClick={() => removeProduct(product.index)}
onClick={() => {
const isShippingProduct = product.name === 'Split Shipping';

removeProduct(product.index);

if (!isShippingProduct) {
setTimeout(() => applySplitShippingToProducts(Number(totalShipping)), 0);
}
}}
>
<RemoveCircleOutline />
</IconButton>
Expand Down Expand Up @@ -840,6 +853,7 @@ const ReimbursementProductTable: React.FC<ReimbursementProductTableProps> = ({
cost: 0,
refundSources: []
});
setTimeout(() => applySplitShippingToProducts(Number(totalShipping)), 0);
}
e.currentTarget.blur();
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface ReimbursementRequestInformation {
}
export interface ReimbursementRequestFormInput extends ReimbursementRequestInformation {
reimbursementProducts: ReimbursementProductFormArgs[];
splitShipping?: number;
}

export interface ReimbursementRequestDataSubmission extends ReimbursementRequestInformation {
Expand All @@ -57,6 +58,13 @@ const RECEIPTS_REQUIRED = import.meta.env.VITE_RR_RECEIPT_REQUIREMENT || 'disabl

const schema = yup.object().shape({
vendorId: yup.string().required('Vendor is required'),
splitShipping: yup
.number()
.transform((value, originalValue) => {
return originalValue === '' || originalValue === undefined ? undefined : value;
})
.optional()
.min(0.01, 'Split shipping must be greater than 0'),
indexCodeId: yup.string().required('Refund source is required'),
secondaryAccount: yup.string().test('required-if-split', 'Second refund source is required', function (value) {
if (!this.parent.$hasConfirmedFinance) return true;
Expand Down Expand Up @@ -163,7 +171,8 @@ const ReimbursementRequestForm: React.FC<ReimbursementRequestFormProps> = ({
accountCodeId: defaultValues?.accountCodeId ?? '',
description: defaultValues?.description?.trim() || '',
reimbursementProducts: defaultValues?.reimbursementProducts ?? ([] as ReimbursementProductFormArgs[]),
receiptFiles: defaultValues?.receiptFiles ?? ([] as ReimbursementReceiptUploadArgs[])
receiptFiles: defaultValues?.receiptFiles ?? ([] as ReimbursementReceiptUploadArgs[]),
splitShipping: defaultValues?.splitShipping ?? undefined
}
});

Expand All @@ -181,12 +190,62 @@ const ReimbursementRequestForm: React.FC<ReimbursementRequestFormProps> = ({
const {
fields: reimbursementProducts,
prepend: reimbursementProductPrepend,
remove: reimbursementProductRemove
remove: reimbursementProductRemove,
replace: reimbursementProductReplace
} = useFieldArray({
control,
name: 'reimbursementProducts'
});

const applySplitShippingToProducts = (totalShipping?: number) => {
const currentProducts = watch('reimbursementProducts') ?? [];

const nonShippingProducts = currentProducts.filter((product) => product.name !== 'Split Shipping');

if (!totalShipping || totalShipping <= 0 || nonShippingProducts.length === 0) {
reimbursementProductReplace(nonShippingProducts);
return;
}

const groupedProducts = new Map<string, ReimbursementProductFormArgs[]>();

nonShippingProducts.forEach((product) => {
const key =
'otherProductReasonId' in product.reason
? `other-${product.reason.otherProductReasonId}`
: `wbs-${product.reason.carNumber}-${product.reason.projectNumber}`;

if (!groupedProducts.has(key)) {
groupedProducts.set(key, []);
}

groupedProducts.get(key)!.push(product);
});

const groupedEntries = Array.from(groupedProducts.values());

const totalShippingCents = Math.round(totalShipping * 100);
const baseShippingCents = Math.floor(totalShippingCents / groupedEntries.length);
const remainderCents = totalShippingCents % groupedEntries.length;

const updatedProducts: ReimbursementProductFormArgs[] = [];

groupedEntries.forEach((productsInGroup, index) => {
productsInGroup.forEach((product) => updatedProducts.push(product));

const shippingCents = baseShippingCents + (index < remainderCents ? 1 : 0);

updatedProducts.push({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rn if we add two products to the same project it adds two shipping costs, I think Nathan wanted the cost split evenly over projects (and if it was huge difference in # of products they could fix $ manually)

name: 'Split Shipping',
reason: productsInGroup[0].reason,
cost: shippingCents / 100,
refundSources: []
});
});

reimbursementProductReplace(updatedProducts);
};

const {
isLoading: allVendorsIsLoading,
isError: allVendorsIsError,
Expand Down Expand Up @@ -230,13 +289,11 @@ const ReimbursementRequestForm: React.FC<ReimbursementRequestFormProps> = ({
checkSecureSettingsIsLoading
)
return <LoadingIndicator />;

const onSubmitWrapper = async (data: ReimbursementRequestFormInput) => {
try {
//total cost, firstSourceAmount and secondSourceAmount is tracked in cents
const totalCost = Math.round(data.reimbursementProducts.reduce((acc, curr) => acc + curr.cost, 0) * 100);
// For each product, if multiple refund sources are enabled, the `cost` field represents
// the total amount from the first refund source amount (firstSourceAmount) and second refund source (secondSourceAmount) of that product.
// If only one refund source is present, the `cost` reflects the refund source amount for that product, and firstSourceAmount and secondSourceAmount are left as 0 since they will not needed for this scenario.

const reimbursementProducts = data.reimbursementProducts.map((product: ReimbursementProductFormArgs) => {
const anyNonZero = product.refundSources.some((rs) => Number(rs.amount) > 0);
Expand Down Expand Up @@ -371,6 +428,7 @@ const ReimbursementRequestForm: React.FC<ReimbursementRequestFormProps> = ({
isLeadershipApproved={isLeadershipApproved}
onSubmitToFinance={onSubmitToFinanceWrapper}
isSubmitting={isSubmitting}
applySplitShippingToProducts={applySplitShippingToProducts}
/>
);
};
Expand Down
Loading