diff --git a/app/components/form/DueDate.jsx b/app/components/form/DueDate.jsx
index 58e7e2d1..683ebc89 100644
--- a/app/components/form/DueDate.jsx
+++ b/app/components/form/DueDate.jsx
@@ -4,16 +4,8 @@ import PropTypes from 'prop-types';
// Custom Components
import { Section } from '../shared/Section';
-
-// React Dates
-import { SingleDatePicker } from 'react-dates';
-import moment from 'moment';
-
-// Styles
-import styled from 'styled-components';
-const DueDateContent = styled.div`
- display: flex;
-`;
+import DueDatePicker from './DueDatePicker';
+import DueDateTerms from './DueDateTerms';
// Animation
import _withFadeInAnimation from '../shared/hoc/_withFadeInAnimation';
@@ -22,60 +14,86 @@ import _withFadeInAnimation from '../shared/hoc/_withFadeInAnimation';
export class DueDate extends Component {
constructor(props) {
super(props);
- this.state = { focused: false };
- this.onFocusChange = this.onFocusChange.bind(this);
- this.onDateChange = this.onDateChange.bind(this);
- this.clearDate = this.clearDate.bind(this);
+ this.state = this.props.dueDate;
+ this.toggleDatePicker = this.toggleDatePicker.bind(this);
+ this.updateCustomDate = this.updateCustomDate.bind(this);
+ this.updatePaymentTerm = this.updatePaymentTerm.bind(this);
+ this.updateDueDate = this.updateDueDate.bind(this);
}
- shouldComponentUpdate(nextProps, nextState) {
- return this.state !== nextState || this.props.dueDate != nextProps.dueDate;
+ // Handle Clear Form
+ componentWillReceiveProps(nextProps) {
+ const { selectedDate, paymentTerm, useCustom } = nextProps.dueDate;
+ if (selectedDate === null && paymentTerm === null && useCustom === true) {
+ this.setState(nextProps.dueDate);
+ }
}
- onFocusChange() {
- this.setState({ focused: !this.state.focused });
+ toggleDatePicker() {
+ this.setState({ useCustom: !this.state.useCustom }, () => {
+ this.updateDueDate(this.state);
+ });
}
- onDateChange(date) {
- const selectedDate = date === null ? null : moment(date).toObject();
- this.props.updateFieldData('dueDate', { selectedDate });
+ updateCustomDate(selectedDate) {
+ this.setState({ selectedDate }, () => {
+ this.updateDueDate(this.state);
+ });
}
- clearDate() {
- this.onDateChange(null);
+ updatePaymentTerm(paymentTerm) {
+ this.setState({ paymentTerm }, () => {
+ this.updateDueDate(this.state);
+ });
+ }
+
+ updateDueDate(data) {
+ this.props.updateFieldData('dueDate', data);
}
render() {
- const { t, dueDate } = this.props;
- const selectedDate = dueDate.selectedDate
- ? moment(dueDate.selectedDate)
- : null;
+ const { t } = this.props;
+ const { selectedDate, paymentTerm } = this.state;
return (
{t('form:fields:dueDate:name')}
-
- this.onDateChange(newDate)}
+ {this.state.useCustom ? (
+
+ ) : (
+
- {selectedDate !== null && (
-
-
-
- )}
-
+ )}
+
);
}
diff --git a/app/components/form/DueDatePicker.jsx b/app/components/form/DueDatePicker.jsx
new file mode 100644
index 00000000..d7e53932
--- /dev/null
+++ b/app/components/form/DueDatePicker.jsx
@@ -0,0 +1,78 @@
+// Libraries
+import React, { PureComponent } from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+
+// React Dates
+import { SingleDatePicker } from 'react-dates';
+
+// Styles
+import _withFadeInAnimation from '../shared/hoc/_withFadeInAnimation';
+import styled from 'styled-components';
+const Container = styled.div`
+ display: flex;
+ margin-bottom: 20px;
+`;
+
+// Component
+export class DueDatePicker extends PureComponent {
+ constructor(props) {
+ super(props);
+ this.state = { focused: false };
+ this.onFocusChange = this.onFocusChange.bind(this);
+ this.onDateChange = this.onDateChange.bind(this);
+ this.clearDate = this.clearDate.bind(this);
+ }
+
+ onFocusChange() {
+ this.setState({ focused: !this.state.focused });
+ }
+
+ onDateChange(date) {
+ const selectedDate = date === null ? null : moment(date).toObject();
+ this.props.updateCustomDate(selectedDate);
+ }
+
+ clearDate() {
+ this.onDateChange(null);
+ }
+
+ render() {
+ const { t, selectedDate } = this.props;
+ const dueDate = selectedDate === null ? null : moment(selectedDate);
+ return (
+
+ this.onDateChange(newDate)}
+ />
+ {selectedDate !== null && (
+
+
+
+ )}
+
+ );
+ }
+}
+
+DueDatePicker.propTypes = {
+ selectedDate: PropTypes.object,
+ t: PropTypes.func.isRequired,
+ updateCustomDate: PropTypes.func.isRequired,
+};
+
+DueDatePicker.defaultProps = {
+ selectedDate: null,
+};
+
+// Export
+export default _withFadeInAnimation(DueDatePicker);
diff --git a/app/components/form/DueDateTerms.jsx b/app/components/form/DueDateTerms.jsx
new file mode 100644
index 00000000..76311f25
--- /dev/null
+++ b/app/components/form/DueDateTerms.jsx
@@ -0,0 +1,68 @@
+// Libraries
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+// Styles
+import _withFadeInAnimation from '../shared/hoc/_withFadeInAnimation';
+import styled from 'styled-components';
+const Container = styled.div`
+ display: flex;
+ margin-bottom: 20px;
+`;
+
+// Payment Terms
+import { paymentTerms } from '../../../libs/paymentTerms';
+
+// Component
+export class DueDateTerms extends Component {
+ constructor(props) {
+ super(props);
+ this.handleInputChange = this.handleInputChange.bind(this);
+ }
+
+ componentWillMount() {
+ if (this.props.paymentTerm === null) {
+ this.updateSelectedTerm(paymentTerms[0]);
+ }
+ }
+
+ handleInputChange(event) {
+ this.updateSelectedTerm(event.target.value);
+ }
+
+ updateSelectedTerm(term) {
+ const { updatePaymentTerm } = this.props;
+ updatePaymentTerm(term);
+ }
+
+ render() {
+ const { t, paymentTerm } = this.props;
+ const termOptions = paymentTerms.map(term => (
+
+ { t(`form:fields:dueDate:paymentTerms:${term}:label`) }
+ {' - '}
+ { t(`form:fields:dueDate:paymentTerms:${term}:description`) }
+
+ ));
+ return (
+
+
+ { termOptions }
+
+
+ );
+ }
+}
+
+DueDateTerms.propTypes = {
+ paymentTerm: PropTypes.string,
+ t: PropTypes.func.isRequired,
+ updatePaymentTerm: PropTypes.func.isRequired,
+};
+
+// Export
+export default _withFadeInAnimation(DueDateTerms);
+
diff --git a/app/components/form/__tests__/DueDate.spec.jsx b/app/components/form/__tests__/DueDate.spec.jsx
index bb734049..98c6749b 100644
--- a/app/components/form/__tests__/DueDate.spec.jsx
+++ b/app/components/form/__tests__/DueDate.spec.jsx
@@ -34,52 +34,52 @@ describe('Renders correctly to the DOM', () => {
// TODO
it('will not rerender if threre is no change in Props', () => {});
- it('receives correct props', () => {
- expect(wrapper.prop('dueDate')).toEqual(dueDate);
- expect(wrapper.prop('dueDate')).not.toEqual({});
- expect(wrapper.prop('updateFieldData')).toEqual(updateFieldData);
- });
-
- it('renders necessary element', () => {
- expect(wrapper.find('label')).toHaveLength(1);
- expect(wrapper.find('label')).not.toHaveLength(2);
- expect(wrapper.find(SingleDatePicker)).toHaveLength(1);
- expect(wrapper.find(SingleDatePicker)).not.toHaveLength(2);
- });
-
- it('call clearDate when click clearDate Button', () => {
- // Setup
- const spy = jest.spyOn(DueDate.prototype, 'clearDate');
- const wrap = mount(
-
- );
- const clearDateBtn = wrap.find('.clearDateBtn');
- // Execute
- clearDateBtn.simulate('click');
- // Assertion
- expect(spy).toHaveBeenCalled();
- });
-
- it('clearDate clears selectedDate', () => {
- // Setup
- const instance = wrapper.instance();
- const spy = jest.spyOn(instance, 'clearDate');
- // Execute
- instance.clearDate();
- // Assertion
- expect(spy).toHaveBeenCalled();
- expect(updateFieldData).toHaveBeenCalled();
- expect(updateFieldData).toHaveBeenCalledWith('dueDate', {
- selectedDate: null,
- });
- });
-
- it('matches snapshot', () => {
- const tree = renderer
- .create(
-
- )
- .toJSON();
- expect(tree).toMatchSnapshot();
- });
+ // it('receives correct props', () => {
+ // expect(wrapper.prop('dueDate')).toEqual(dueDate);
+ // expect(wrapper.prop('dueDate')).not.toEqual({});
+ // expect(wrapper.prop('updateFieldData')).toEqual(updateFieldData);
+ // });
+ //
+ // it('renders necessary element', () => {
+ // expect(wrapper.find('label')).toHaveLength(1);
+ // expect(wrapper.find('label')).not.toHaveLength(2);
+ // expect(wrapper.find(SingleDatePicker)).toHaveLength(1);
+ // expect(wrapper.find(SingleDatePicker)).not.toHaveLength(2);
+ // });
+ //
+ // it('call clearDate when click clearDate Button', () => {
+ // // Setup
+ // const spy = jest.spyOn(DueDate.prototype, 'clearDate');
+ // const wrap = mount(
+ //
+ // );
+ // const clearDateBtn = wrap.find('.clearDateBtn');
+ // // Execute
+ // clearDateBtn.simulate('click');
+ // // Assertion
+ // expect(spy).toHaveBeenCalled();
+ // });
+ //
+ // it('clearDate clears selectedDate', () => {
+ // // Setup
+ // const instance = wrapper.instance();
+ // const spy = jest.spyOn(instance, 'clearDate');
+ // // Execute
+ // instance.clearDate();
+ // // Assertion
+ // expect(spy).toHaveBeenCalled();
+ // expect(updateFieldData).toHaveBeenCalled();
+ // expect(updateFieldData).toHaveBeenCalledWith('dueDate', {
+ // selectedDate: null,
+ // });
+ // });
+ //
+ // it('matches snapshot', () => {
+ // const tree = renderer
+ // .create(
+ //
+ // )
+ // .toJSON();
+ // expect(tree).toMatchSnapshot();
+ // });
});
diff --git a/app/components/form/__tests__/DueDatePicker.spec.jsx b/app/components/form/__tests__/DueDatePicker.spec.jsx
new file mode 100644
index 00000000..2c1959c7
--- /dev/null
+++ b/app/components/form/__tests__/DueDatePicker.spec.jsx
@@ -0,0 +1,78 @@
+// Libs
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+import { SingleDatePicker } from 'react-dates';
+
+// Component
+import { DueDatePicker } from '../DueDatePicker.jsx';
+
+// Mocks
+const t = jest.fn();
+const updateCustomDate = jest.fn();
+const selectedDate = {
+ milliseconds: 0,
+ seconds: 0,
+ minutes: 0,
+ hours: 12,
+ date: 20,
+ months: 10,
+ years: 2017,
+};
+
+describe('Renders correctly to the DOM', () => {
+ let wrapper;
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ });
+
+ // TODO
+ it('will not rerender if threre is no change in Props', () => {});
+
+ it('receives correct props', () => {
+ expect(wrapper.prop('selectedDate')).toEqual(selectedDate);
+ expect(wrapper.prop('selectedDate')).not.toEqual({});
+ expect(wrapper.prop('updateCustomDate')).toEqual(updateCustomDate);
+ });
+
+ it('renders necessary element', () => {
+ expect(wrapper.find(SingleDatePicker)).toHaveLength(1);
+ expect(wrapper.find(SingleDatePicker)).not.toHaveLength(2);
+ });
+
+ it('call clearDate when click clearDate Button', () => {
+ // Setup
+ const spy = jest.spyOn(DueDatePicker.prototype, 'clearDate');
+ const wrap = mount(
+
+ );
+ const clearDateBtn = wrap.find('.clearDateBtn');
+ // Execute
+ clearDateBtn.simulate('click');
+ // Assertion
+ expect(spy).toHaveBeenCalled();
+ });
+
+ it('clearDate clears selectedDate', () => {
+ // Setup
+ const instance = wrapper.instance();
+ const spy = jest.spyOn(instance, 'clearDate');
+ // Execute
+ instance.clearDate();
+ // Assertion
+ expect(spy).toHaveBeenCalled();
+ expect(updateCustomDate).toHaveBeenCalled();
+ expect(updateCustomDate).toHaveBeenCalledWith(null);
+ });
+
+ it('matches snapshot', () => {
+ const tree = renderer
+ .create(
+
+ )
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/app/components/form/__tests__/DueDateTerms.spec.jsx b/app/components/form/__tests__/DueDateTerms.spec.jsx
new file mode 100644
index 00000000..658aeb1c
--- /dev/null
+++ b/app/components/form/__tests__/DueDateTerms.spec.jsx
@@ -0,0 +1,59 @@
+// Libs
+import React from 'react';
+import renderer from 'react-test-renderer';
+import { mount } from 'enzyme';
+
+// Component
+import { DueDateTerms } from '../DueDateTerms.jsx';
+import { paymentTerms } from '../../../../libs/paymentTerms';
+
+// Mocks
+const t = jest.fn();
+const updatePaymentTerm = jest.fn();
+const paymentTerm = 'net10';
+
+describe('Renders correctly to the DOM', () => {
+ let wrapper;
+ beforeEach(() => {
+ wrapper = mount(
+
+ );
+ });
+
+ // TODO
+ it('will not rerender if threre is no change in Props', () => {});
+
+ it('receives correct props', () => {
+ expect(wrapper.prop('paymentTerm')).toEqual(paymentTerm);
+ expect(wrapper.prop('paymentTerm')).not.toEqual({});
+ expect(wrapper.prop('updatePaymentTerm')).toEqual(updatePaymentTerm);
+ });
+
+ it('renders necessary element', () => {
+ expect(wrapper.find('select')).toHaveLength(1);
+ expect(wrapper.find('select')).not.toHaveLength(2);
+ expect(wrapper.find('option')).toHaveLength(paymentTerms.length);
+ });
+
+ it('handle input change correctly', () => {
+ // Setup
+ const spy = jest.spyOn(DueDateTerms.prototype, 'handleInputChange');
+ const wrap = mount(
+
+ );
+ // Execute
+ const select = wrap.find('select');
+ select.simulate('change', { target: { value: 'net90' } });
+ // Assertion
+ expect(spy).toHaveBeenCalled();
+ });
+
+ it('matches snapshot', () => {
+ const tree = renderer
+ .create(
+
+ )
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/app/components/form/__tests__/__snapshots__/DueDate.spec.jsx.snap b/app/components/form/__tests__/__snapshots__/DueDate.spec.jsx.snap
deleted file mode 100644
index ed3b777f..00000000
--- a/app/components/form/__tests__/__snapshots__/DueDate.spec.jsx.snap
+++ /dev/null
@@ -1,67 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Renders correctly to the DOM matches snapshot 1`] = `
-
-
-
-
-
-
-
-
-
- Press the down arrow key to interact with the calendar and
- select a date. Press the question mark key to get the keyboard shortcuts for changing dates.
-
-
- 20/11/2017
-
-
-
-
-
-
-
-
-
-
-`;
diff --git a/app/components/form/__tests__/__snapshots__/DueDatePicker.spec.jsx.snap b/app/components/form/__tests__/__snapshots__/DueDatePicker.spec.jsx.snap
new file mode 100644
index 00000000..456e8309
--- /dev/null
+++ b/app/components/form/__tests__/__snapshots__/DueDatePicker.spec.jsx.snap
@@ -0,0 +1,60 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Renders correctly to the DOM matches snapshot 1`] = `
+
+
+
+
+
+
+
+ Press the down arrow key to interact with the calendar and
+ select a date. Press the question mark key to get the keyboard shortcuts for changing dates.
+
+
+ 20/11/2017
+
+
+
+
+
+
+
+
+
+`;
diff --git a/app/components/form/__tests__/__snapshots__/DueDateTerms.spec.jsx.snap b/app/components/form/__tests__/__snapshots__/DueDateTerms.spec.jsx.snap
new file mode 100644
index 00000000..ee036ea7
--- /dev/null
+++ b/app/components/form/__tests__/__snapshots__/DueDateTerms.spec.jsx.snap
@@ -0,0 +1,38 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Renders correctly to the DOM matches snapshot 1`] = `
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+`;
diff --git a/app/components/invoices/Invoice.jsx b/app/components/invoices/Invoice.jsx
index 88cd7bc4..a2dda676 100644
--- a/app/components/invoices/Invoice.jsx
+++ b/app/components/invoices/Invoice.jsx
@@ -8,6 +8,7 @@ const ipc = require('electron').ipcRenderer;
// Helper
import { formatNumber } from '../../../helpers/formatNumber';
+import { calTermDate } from '../../../helpers/date';
// Custom Components
import Button from '../shared/Button';
@@ -204,6 +205,25 @@ class Invoice extends PureComponent {
}
}
+ renderDueDate() {
+ const { t, invoice } = this.props;
+ const { dueDate, configs } = invoice;
+ const { useCustom, paymentTerm, selectedDate } = dueDate;
+ const dateFormat = configs ? configs.dateFormat : this.props.dateFormat;
+ // If it's a custom date then return selectedDate
+ if (useCustom === true) {
+ return moment(selectedDate).format(dateFormat);
+ }
+ // If it's a payment term, calculate the term date and print out
+ const paymentTermDate = calTermDate(invoice.created_at, paymentTerm);
+ return `
+ ${ t(`form:fields:dueDate:paymentTerms:${paymentTerm}:label`) }
+ (
+ ${ moment(paymentTermDate).format(dateFormat) }
+ )
+ `;
+ }
+
render() {
const { invoice, setInvoiceStatus, t } = this.props;
const { recipient, status, configs } = invoice;
@@ -275,9 +295,7 @@ class Invoice extends PureComponent {
{t('invoices:fields:dueDate')}
- {invoice.dueDate
- ? moment(invoice.dueDate).format(dateFormat)
- : '--'}
+ { invoice.dueDate && this.renderDueDate() }
diff --git a/app/components/invoices/__tests__/__snapshots__/Invoice.spec.js.snap b/app/components/invoices/__tests__/__snapshots__/Invoice.spec.js.snap
index 38c42634..5876bb23 100644
--- a/app/components/invoices/__tests__/__snapshots__/Invoice.spec.js.snap
+++ b/app/components/invoices/__tests__/__snapshots__/Invoice.spec.js.snap
@@ -95,9 +95,7 @@ exports[`Renders correctly to the DOM matches snapshot 1`] = `
Pending
-
- --
-
+
diff --git a/app/helpers/__tests__/form.spec.js b/app/helpers/__tests__/form.spec.js
index 8936623b..bff311ad 100644
--- a/app/helpers/__tests__/form.spec.js
+++ b/app/helpers/__tests__/form.spec.js
@@ -54,7 +54,11 @@ describe('getInvoiceData', () => {
quantity: faker.random.number(100),
},
],
- dueDate: {},
+ dueDate: {
+ selectedDate: null,
+ paymentTerm: null,
+ useCustom: true,
+ },
currency: {
code: 'USD',
placement: 'before',
@@ -151,6 +155,8 @@ describe('getInvoiceData', () => {
months: 9,
years: 2017,
},
+ useCustom: true,
+ paymentTerm: null,
},
settings: Object.assign({}, formData.settings, {
required_fields: Object.assign({}, formData.settings.required_fields, {
@@ -160,9 +166,13 @@ describe('getInvoiceData', () => {
});
const invoiceData = getInvoiceData(newFormData);
expect(invoiceData.dueDate).toEqual({
- date: 20,
- months: 9,
- years: 2017,
+ selectedDate: {
+ date: 20,
+ months: 9,
+ years: 2017,
+ },
+ useCustom: true,
+ paymentTerm: null,
});
expect(invoiceData.dueDate).not.toEqual({
date: 2,
@@ -284,6 +294,8 @@ describe('validateFormData', () => {
],
dueDate: {
selectedDate: faker.date.future(),
+ useCustom: true,
+ paymentTerm: null,
},
currency: {
code: 'USD',
@@ -549,7 +561,9 @@ describe('validateRows', () => {
describe('validateDueDate', () => {
it('should validate selectedDate', () => {
const dueDate = {
+ useCustom: true,
selectedDate: null,
+ paymentTerm: null,
};
const validation = validateDueDate(true, dueDate);
expect(validation).toEqual(false);
diff --git a/app/helpers/form.js b/app/helpers/form.js
index 4c396759..fd19f286 100644
--- a/app/helpers/form.js
+++ b/app/helpers/form.js
@@ -60,19 +60,19 @@ function getInvoiceData(formData) {
}
// Set InvoiceID
if (required_fields.invoiceID) invoiceData.invoiceID = invoiceID;
- // Set Invoice DueDate
- if (required_fields.dueDate) invoiceData.dueDate = dueDate.selectedDate;
- // Set Invoice Currency
+ // Set DueDate
+ if (required_fields.dueDate) invoiceData.dueDate = dueDate;
+ // Set Currency
if (required_fields.currency) {
invoiceData.currency = currency;
} else {
invoiceData.currency = appConfig.get('invoice.currency');
}
- // Set Invoice Discount
+ // Set Discount
if (required_fields.discount) invoiceData.discount = discount;
- // Set Invoice Tax
+ // Set Tax
if (required_fields.tax) invoiceData.tax = tax;
- // Set Invoice Note
+ // Set Note
if (required_fields.note) invoiceData.note = note.content;
// Return final value
@@ -167,15 +167,19 @@ function validateRows(rows) {
}
function validateDueDate(isRequired, dueDate) {
- const { selectedDate } = dueDate;
+ const { selectedDate,paymentTerm, useCustom } = dueDate;
if (isRequired) {
- if (!selectedDate || selectedDate === null) {
- openDialog({
- type: 'warning',
- title: i18n.t('dialog:validation:dueDate:title'),
- message: i18n.t('dialog:validation:dueDate:message'),
- });
- return false;
+ // If use customDate
+ if (useCustom) {
+ if (!selectedDate || selectedDate === null) {
+ openDialog({
+ type: 'warning',
+ title: i18n.t('dialog:validation:dueDate:title'),
+ message: i18n.t('dialog:validation:dueDate:message'),
+ });
+ return false;
+ }
+ return true;
}
return true;
}
diff --git a/app/helpers/pouchDB.js b/app/helpers/pouchDB.js
index e282f913..a519ccc2 100644
--- a/app/helpers/pouchDB.js
+++ b/app/helpers/pouchDB.js
@@ -72,6 +72,17 @@ const invoicesMigrations = {
}
});
return newDoc;
+ },
+ 3: doc => {
+ if (!doc.dueDate) {
+ return doc;
+ }
+ return Object.assign({}, doc, {
+ dueDate: {
+ selectedDate: doc.dueDate,
+ useCustom: true,
+ }
+ });
}
};
diff --git a/app/reducers/FormReducer.jsx b/app/reducers/FormReducer.jsx
index 4d86395e..1ead4d83 100644
--- a/app/reducers/FormReducer.jsx
+++ b/app/reducers/FormReducer.jsx
@@ -16,7 +16,11 @@ const initialState = {
new: {},
},
rows: [],
- dueDate: {},
+ dueDate: {
+ selectedDate: null,
+ paymentTerm: null,
+ useCustom: true,
+ },
discount: {},
note: {},
invoiceID: "",
@@ -137,12 +141,7 @@ const FormReducer = handleActions(
currency: currency !== undefined ? currency : state.currency,
discount: discount !== undefined ? discount : state.discount,
tax: tax !== undefined ? tax : state.tax,
- dueDate:
- dueDate !== undefined
- ? Object.assign({}, state.dueDate, {
- selectedDate: dueDate,
- })
- : state.dueDate,
+ dueDate: dueDate !== undefined ? dueDate : state.dueDate,
note:
note !== undefined
? Object.assign({}, state.note, {
diff --git a/app/reducers/__tests__/FormReducer.spec.js b/app/reducers/__tests__/FormReducer.spec.js
index bc2c35a0..c4b9c505 100644
--- a/app/reducers/__tests__/FormReducer.spec.js
+++ b/app/reducers/__tests__/FormReducer.spec.js
@@ -20,7 +20,11 @@ describe('Form Reducer should handle', () => {
{ id: 'Tyrion Lannister' },
{ id: 'Arya Stark' },
],
- dueDate: {},
+ dueDate: {
+ selectedDate: null,
+ paymentTerm: null,
+ useCustom: true,
+ },
note: {},
currency: {
code: 'USD',
@@ -102,7 +106,11 @@ describe('Form Reducer should handle', () => {
new: {},
});
expect(newState.rows).toHaveLength(0);
- expect(newState.dueDate).toEqual({});
+ expect(newState.dueDate).toEqual({
+ selectedDate: null,
+ paymentTerm: null,
+ useCustom: true,
+ });
expect(newState.note).toEqual({});
expect(newState.currency).toEqual({
code: 'USD',
@@ -484,7 +492,7 @@ describe('Form Reducer should handle Invoice Edit', () => {
// Discount
expect(newState.discount).toEqual(invoiceData.discount);
// DueDate
- expect(newState.dueDate.selectedDate).toEqual(invoiceData.dueDate);
+ expect(newState.dueDate).toEqual(invoiceData.dueDate);
});
it('toggle optional field if necessary', () => {
diff --git a/helpers/date.js b/helpers/date.js
new file mode 100644
index 00000000..1b54dce6
--- /dev/null
+++ b/helpers/date.js
@@ -0,0 +1,30 @@
+function calTermDate(createdAt, adjustment) {
+ const createdDate = new Date(createdAt);
+ let numberOfDaysToAdd;
+ switch (adjustment) {
+ case 'net10': {
+ numberOfDaysToAdd = 10;
+ break;
+ }
+ case 'net30': {
+ numberOfDaysToAdd = 30;
+ break;
+ }
+ case 'net60': {
+ numberOfDaysToAdd = 60;
+ break;
+ }
+ case 'net90': {
+ numberOfDaysToAdd = 90;
+ break;
+ }
+ default: {
+ numberOfDaysToAdd = 7;
+ break;
+ }
+ }
+ const paymentTermDate = new Date();
+ return paymentTermDate.setDate(createdDate.getDate() + numberOfDaysToAdd);
+}
+
+export { calTermDate };
diff --git a/i18n/en/form.json b/i18n/en/form.json
index 96cfd27d..6e23d02c 100644
--- a/i18n/en/form.json
+++ b/i18n/en/form.json
@@ -32,7 +32,30 @@
},
"dueDate": {
"name": "Due Date",
- "placeHolder": "Select a Date"
+ "placeHolder": "Select a custom date",
+ "paymentTerms": {
+ "name": "Select a Payment Term",
+ "net7": {
+ "label": "Net 7",
+ "description": "Payment seven days after invoice date"
+ },
+ "net10": {
+ "label": "Net 10",
+ "description": "Payment ten days after invoice date"
+ },
+ "net30": {
+ "label": "Net 30",
+ "description": "Payment 30 days after invoice date"
+ },
+ "net60": {
+ "label": "Net 60",
+ "description": "Payment 60 days after invoice date"
+ },
+ "net90": {
+ "label": "Net 90",
+ "description": "Payment 90 days after invoice date"
+ }
+ }
},
"tax": {
"name": "Tax",
diff --git a/libs/paymentTerms.js b/libs/paymentTerms.js
new file mode 100644
index 00000000..7c491ee7
--- /dev/null
+++ b/libs/paymentTerms.js
@@ -0,0 +1 @@
+export const paymentTerms = ['net7', 'net10', 'net30', 'net60', 'net90'];
diff --git a/preview/containers/SideBar.jsx b/preview/containers/SideBar.jsx
index 40f11097..73db8c21 100644
--- a/preview/containers/SideBar.jsx
+++ b/preview/containers/SideBar.jsx
@@ -77,16 +77,16 @@ class SideBar extends Component {
} = configs;
return (
-
-
{configs.showRecipient && (
- { t('preview:common:billedTo', {lng: currentLanguage}) }
+ {t('preview:common:billedTo', { lng: currentLanguage })}
{recipient.company}
{recipient.fullname}
{recipient.email}
@@ -83,26 +86,50 @@ function Header({ t, invoice, profile, configs }) {
)}
- { t('preview:common:invoice', {lng: currentLanguage}) }
+
+ {t('preview:common:invoice', { lng: currentLanguage })}
+
#
- { invoice.invoiceID
+ {invoice.invoiceID
? invoice.invoiceID
: truncate(invoice._id, {
length: 8,
- omission: '', })
- }
+ omission: '',
+ })}
- { t('preview:common:created', {lng: currentLanguage}) }:
- {' '}
- {moment(invoice.created_at).lang(currentLanguage).format(configs.dateFormat)}
-
-
- { t('preview:common:due', {lng: currentLanguage}) }:
- {' '}
- {moment(invoice.dueDate).lang(currentLanguage).format(configs.dateFormat)}
+ {t('preview:common:created', { lng: currentLanguage })}:{' '}
+ {moment(invoice.created_at)
+ .lang(currentLanguage)
+ .format(configs.dateFormat)}
+ {invoice.dueDate && [
+
+ {t('preview:common:due', { lng: currentLanguage })}:{' '}
+ {invoice.dueDate.useCustom
+ ? moment(invoice.dueDate.selectedDate)
+ .lang(currentLanguage)
+ .format(configs.dateFormat)
+ : moment(
+ calTermDate(invoice.created_at, invoice.dueDate.paymentTerm)
+ )
+ .lang(currentLanguage)
+ .format(configs.dateFormat)}
+
,
+
+ {!invoice.dueDate.useCustom &&
+ `
+ (
+ ${t(
+ `form:fields:dueDate:paymentTerms:${
+ invoice.dueDate.paymentTerm
+ }:description`
+ )}
+ )
+ `}
+
,
+ ]}
);
diff --git a/preview/templates/minimal/components/Header.jsx b/preview/templates/minimal/components/Header.jsx
index 16b50cdc..bb4a59d7 100644
--- a/preview/templates/minimal/components/Header.jsx
+++ b/preview/templates/minimal/components/Header.jsx
@@ -3,6 +3,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { truncate } from 'lodash';
const moment = require('moment');
+import { calTermDate } from '../../../../helpers/date';
// Styles
import styled from 'styled-components';
@@ -52,13 +53,32 @@ function Header({ t, invoice, profile, configs }) {
{' '}
{moment(invoice.created_at).lang(currentLanguage).format(configs.dateFormat)}
- {invoice.dueDate && (
-
- {t('preview:common:due', {lng: currentLanguage})}:
- {' '}
- {moment(invoice.dueDate).lang(currentLanguage).format(configs.dateFormat)}
-
- )}
+ {invoice.dueDate && [
+
+ {t('preview:common:due', { lng: currentLanguage })}:{' '}
+ {invoice.dueDate.useCustom
+ ? moment(invoice.dueDate.selectedDate)
+ .lang(currentLanguage)
+ .format(configs.dateFormat)
+ : moment(
+ calTermDate(invoice.created_at, invoice.dueDate.paymentTerm)
+ )
+ .lang(currentLanguage)
+ .format(configs.dateFormat)}
+
,
+
+ {!invoice.dueDate.useCustom &&
+ `
+ (
+ ${t(
+ `form:fields:dueDate:paymentTerms:${
+ invoice.dueDate.paymentTerm
+ }:description`
+ )}
+ )
+ `}
+
,
+ ]}
{configs.showLogo && (