Skip to content

Commit

Permalink
Merge branch 'main' into fix/31792
Browse files Browse the repository at this point in the history
  • Loading branch information
dukenv0307 committed Nov 30, 2023
2 parents 4ecea54 + e2f4214 commit 61bc06b
Show file tree
Hide file tree
Showing 19 changed files with 131 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ It's crucial to understand the requirements based on your specific QuickBooks su
- An error will occur if you try to export to QuickBooks with a feature enabled that isn't part of your subscription.
- Please be aware that Expensify does not support the Self-Employed subscription in QuickBooks Online.

![QuickBooks Online - Subscription types]({{site.url}}/assets/images/QBO1.png){:width="100%"}

# How to connect to QuickBooks Online

## Step 1: Setup employees in QuickBooks Online
Expand Down Expand Up @@ -79,14 +81,20 @@ This is a single itemized vendor bill for each Expensify report. If the accounti

The submitter will be listed as the vendor in the vendor bill.

![Vendor Bill]({{site.url}}/assets/images/QBO2-Bill.png){:width="100%"}

## Check

This is a single itemized check for each Expensify report. You can mark a check to be printed later in QuickBooks Online.

![Check to print]({{site.url}}/assets/images/QBO3-Checktoprint.png){:width="100%"}

## Journal entry

This is a single itemized journal entry for each Expensify report.

![Journal Entry]({{site.url}}/assets/images/QBO4-JournalEntry.png){:width="100%"}

# Non-reimbursable expenses

Non-reimbursable expenses export to QuickBooks Online as:
Expand All @@ -102,7 +110,9 @@ Using Credit/Debit Card Transactions:
- Each expense will be exported as a bank transaction with its transaction date.
- If you split an expense in Expensify, we'll consolidate it into a single credit card transaction in QuickBooks with multiple line items posted to the corresponding General Ledger accounts.

Pro-Tip: To ensure the payee field in QuickBooks Online reflects the merchant name for Credit Card expenses, ensure there's a matching Vendor in QuickBooks Online. Expensify checks for an exact match during export. If none are found, the payee will be mapped to a vendor we create and labeled as Credit Card Misc. or Debit Card Misc.
Pro-Tip: To ensure the payee field in QuickBooks Online reflects the merchant name for Credit Card expenses, ensure there's a matching Vendor in QuickBooks Online. Expensify checks for an exact match during export. If none are found, the payee will be mapped to a vendor we create and labeled as Credit Card Misc. or Debit Card Misc.

![Expense]({{site.url}}/assets/images/QBO5-Expense.png){:width="100%"}

If you centrally manage your company cards through Domains, you can export expenses from each card to a specific account in QuickBooks.

Expand Down Expand Up @@ -224,6 +234,8 @@ Step 3: Importing Your Credit Card Transactions into QuickBooks Online

- After completing Steps 1 and 2, you can import your credit card transactions into QuickBooks Online. These imported banking transactions will align with the ones brought in from Expensify. QuickBooks Online will guide you through the process of matching these transactions, similar to the example below:

![Transactions]({{site.url}}/assets/images/QBO7-Transactions.png){:width="100%"}

## Tax in QuickBooks Online

If your country applies taxes on sales (like GST, HST, or VAT), you can utilize Expensify's Tax Tracking along with your QuickBooks Online tax rates. Please note: Tax Tracking is not available for Workspaces linked to the US version of QuickBooks Online. If you need assistance applying taxes after reports are exported, contact QuickBooks.
Expand All @@ -247,6 +259,8 @@ When working with QuickBooks Online Multi-Currency, there are some things to rem

In QuickBooks Online, the currency conversion rates are not applied when exporting. All transactions will be exported with a 1:1 conversion rate, so for example, if a vendor's currency is CAD (Canadian Dollar) and the home currency is USD (US Dollar), the export will show these currencies without applying conversion rates.

![Check]({{site.url}}/assets/images/QBO6-Check.png){:width="100%"}

To correct this, you must manually update the conversion rate after the report has been exported to QuickBooks Online.

Specifically for Vendor Bills:
Expand Down
Binary file added docs/assets/images/QBO1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/QBO2-Bill.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/QBO3-Checktoprint.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/QBO4-JournalEntry.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/QBO5-Expense.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/QBO6-Check.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/images/QBO7-Transactions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/Composer/index.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {StyleSheet} from 'react-native';
import _ from 'underscore';
import RNTextInput from '@components/RNTextInput';
import * as ComposerUtils from '@libs/ComposerUtils';
import {useTheme} from '@styles/themes/useTheme';
import useTheme from '@styles/themes/useTheme';
import useThemeStyles from '@styles/useThemeStyles';

const propTypes = {
Expand Down
10 changes: 6 additions & 4 deletions src/components/TextInput/BaseTextInput/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function BaseTextInput(props) {
const styles = useThemeStyles();
const initialValue = props.value || props.defaultValue || '';
const initialActiveLabel = props.forceActiveLabel || initialValue.length > 0 || Boolean(props.prefixCharacter);
const isMultiline = props.multiline || props.autoGrowHeight;

const [isFocused, setIsFocused] = useState(false);
const [passwordHidden, setPasswordHidden] = useState(props.secureTextEntry);
Expand Down Expand Up @@ -172,10 +173,12 @@ function BaseTextInput(props) {
/**
* Set Value & activateLabel
*
* @param {String} value
* @param {String} val
* @memberof BaseTextInput
*/
const setValue = (value) => {
const setValue = (val) => {
const value = isMultiline ? val : val.replace(/\n/g, ' ');

if (props.onInputChange) {
props.onInputChange(value);
}
Expand All @@ -184,7 +187,7 @@ function BaseTextInput(props) {

if (value && value.length > 0) {
hasValueRef.current = true;
// When the componment is uncontrolled, we need to manually activate the label:
// When the component is uncontrolled, we need to manually activate the label
if (props.value === undefined) {
activateLabel();
}
Expand Down Expand Up @@ -227,7 +230,6 @@ function BaseTextInput(props) {
(props.hasError || props.errorText) && styles.borderColorDanger,
props.autoGrowHeight && {scrollPaddingTop: 2 * maxHeight},
]);
const isMultiline = props.multiline || props.autoGrowHeight;

return (
<>
Expand Down
37 changes: 0 additions & 37 deletions src/components/UnorderedList.js

This file was deleted.

26 changes: 26 additions & 0 deletions src/components/UnorderedList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import {View} from 'react-native';
import useThemeStyles from '@styles/useThemeStyles';
import Text from './Text';

type UnorderedListProps = {
/** An array of strings to display as an unordered list */
items?: string[];
};

function UnorderedList({items = []}: UnorderedListProps) {
const styles = useThemeStyles();

return items.map((itemText) => (
<View
key={itemText}
style={[styles.flexRow, styles.alignItemsStart, styles.ml2]}
>
<Text style={[styles.mr2]}>{'\u2022'}</Text>
<Text>{itemText}</Text>
</View>
));
}

UnorderedList.displayName = 'UnorderedList';
export default UnorderedList;
31 changes: 17 additions & 14 deletions src/components/transactionPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,28 @@ export default PropTypes.shape({
modifiedMerchant: PropTypes.string,

/** The comment object on the transaction */
comment: PropTypes.shape({
/** The text of the comment */
comment: PropTypes.string,
comment: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
/** The text of the comment */
comment: PropTypes.string,

/** The waypoints defining the distance request */
waypoints: PropTypes.shape({
/** The latitude of the waypoint */
lat: PropTypes.number,
/** The waypoints defining the distance request */
waypoints: PropTypes.shape({
/** The latitude of the waypoint */
lat: PropTypes.number,

/** The longitude of the waypoint */
lng: PropTypes.number,
/** The longitude of the waypoint */
lng: PropTypes.number,

/** The address of the waypoint */
address: PropTypes.string,
/** The address of the waypoint */
address: PropTypes.string,

/** The name of the waypoint */
name: PropTypes.string,
/** The name of the waypoint */
name: PropTypes.string,
}),
}),
}),
]),

/** The type of transaction */
type: PropTypes.oneOf(_.values(CONST.TRANSACTION.TYPE)),
Expand Down
19 changes: 18 additions & 1 deletion src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {format} from 'date-fns';
import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import Str from 'expensify-common/lib/str';
import {isEmpty} from 'lodash';
import lodashEscape from 'lodash/escape';
import lodashFindLastIndex from 'lodash/findLastIndex';
import lodashIntersection from 'lodash/intersection';
Expand All @@ -14,7 +15,7 @@ import CONST from '@src/CONST';
import {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {Beta, Login, PersonalDetails, Policy, PolicyTags, Report, ReportAction, Transaction} from '@src/types/onyx';
import {Beta, Login, PersonalDetails, Policy, PolicyTags, Report, ReportAction, Session, Transaction} from '@src/types/onyx';
import {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon';
import {ChangeLog, IOUMessage, OriginalMessageActionName} from '@src/types/onyx/OriginalMessage';
import {Message, ReportActions} from '@src/types/onyx/ReportAction';
Expand Down Expand Up @@ -4227,6 +4228,21 @@ function shouldDisableWelcomeMessage(report: OnyxEntry<Report>, policy: OnyxEntr
return isMoneyRequestReport(report) || isArchivedRoom(report) || !isChatRoom(report) || isChatThread(report) || !PolicyUtils.isPolicyAdmin(policy);
}

/**
* Navigates to the appropriate screen based on the presence of a private note for the current user.
*/
function navigateToPrivateNotes(report: Report, session: Session) {
if (isEmpty(report) || isEmpty(session)) {
return;
}
const currentUserPrivateNote = report.privateNotes?.[String(session.accountID)]?.note ?? '';
if (isEmpty(currentUserPrivateNote)) {
Navigation.navigate(ROUTES.PRIVATE_NOTES_EDIT.getRoute(report.reportID, String(session.accountID)));
return;
}
Navigation.navigate(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID));
}

export {
getReportParticipantsTitle,
isReportMessageAttachment,
Expand Down Expand Up @@ -4391,6 +4407,7 @@ export {
getChannelLogMemberMessage,
getRoom,
shouldDisableWelcomeMessage,
navigateToPrivateNotes,
canEditWriteCapability,
};

Expand Down
14 changes: 8 additions & 6 deletions src/pages/PrivateNotes/PrivateNotesEditPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import withLocalize from '@components/withLocalize';
import useLocalize from '@hooks/useLocalize';
import compose from '@libs/compose';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import updateMultilineInputRange from '@libs/UpdateMultilineInputRange';
import withReportAndPrivateNotesOrNotFound from '@pages/home/report/withReportAndPrivateNotesOrNotFound';
import personalDetailsPropType from '@pages/personalDetailsPropType';
Expand Down Expand Up @@ -94,19 +95,21 @@ function PrivateNotesEditPage({route, personalDetailsList, report}) {

const savePrivateNote = () => {
const originalNote = lodashGet(report, ['privateNotes', route.params.accountID, 'note'], '');

let editedNote = '';
if (privateNote.trim() !== originalNote.trim()) {
const editedNote = Report.handleUserDeletedLinksInHtml(privateNote.trim(), parser.htmlToMarkdown(originalNote).trim());
editedNote = Report.handleUserDeletedLinksInHtml(privateNote.trim(), parser.htmlToMarkdown(originalNote).trim());
Report.updatePrivateNotes(report.reportID, route.params.accountID, editedNote);
}

// We want to delete saved private note draft after saving the note
debouncedSavePrivateNote('');

Keyboard.dismiss();

// Take user back to the PrivateNotesView page
Navigation.goBack(ROUTES.PRIVATE_NOTES_VIEW.getRoute(report.reportID, route.params.accountID));
if (!_.some({...report.privateNotes, [route.params.accountID]: {note: editedNote}}, (item) => item.note)) {
ReportUtils.navigateToDetailsPage(report);
} else {
Navigation.goBack(ROUTES.PRIVATE_NOTES_LIST.getRoute(report.reportID));
}
};

return (
Expand All @@ -117,7 +120,6 @@ function PrivateNotesEditPage({route, personalDetailsList, report}) {
>
<HeaderWithBackButton
title={translate('privateNotes.title')}
subtitle={translate('privateNotes.myNote')}
onBackButtonPress={() => Navigation.goBack(ROUTES.PRIVATE_NOTES_VIEW.getRoute(report.reportID, route.params.accountID))}
shouldShowBackButton
onCloseButtonPress={() => Navigation.dismissModal()}
Expand Down
Loading

0 comments on commit 61bc06b

Please sign in to comment.