<commit_before>import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Container, Row, Col, Form} from 'react-bootstrap';
import {t, formatDate} from '../../utils';
import EditInvoiceLines from './invoice-lines/EditInvoiceLines';
import InvoiceNotVerifiedAlert from './InvoiceNotVerifiedAlert';
import {EditInvoiceSaveButtons} from './EditInvoiceSaveButtons';
import {invoiceAction} from '../../../actions/index';
import {EditInvoiceClient} from './EditInvoiceClient';
import InvoiceModel from '../models/InvoiceModel';
import {ConfigModel} from '../../config/models/ConfigModel';
import {ClientModel} from '../../client/models/ClientModels';
import {ConfacState} from '../../../reducers/app-state';
import {EditInvoiceDetails} from './EditInvoiceDetails';
import {StickyFooter} from '../../controls/skeleton/StickyFooter';
import {DownloadInvoiceButton} from './DownloadInvoiceButton';
import {EmailModal, EmailModalTitle} from '../../controls/email/EmailModal';
import {EmailModel} from '../../controls/email/EmailModels';
import {sendEmail} from '../../../actions/emailActions';
import {Button} from '../../controls/form-controls/Button';
import {getNewInvoice} from '../models/getNewInvoice';
import {getDocumentTitle} from '../../hooks/useDocumentTitle';
import {InvoiceAttachmentsForm} from '../controls/InvoiceAttachmentsForm';
import {ConsultantModel} from '../../consultant/models/ConsultantModel';
import {projectMonthResolve} from '../../project/ProjectMonthsLists';
import {FullProjectMonthModel} from '../../project/models/FullProjectMonthModel';
import {invoiceReplacements} from '../invoice-replacements';
import {ProjectMonthSelect} from '../../project/controls/ProjectMonthSelect';
import {EditInvoiceBadges} from './EditInvoiceBadges';


type EditInvoiceProps = {
  invoices: InvoiceModel[],
  config: ConfigModel,
  app: { isLoaded: boolean },
  clients: ClientModel[],
  consultants: ConsultantModel[],
  fullProjectMonths: FullProjectMonthModel[],
  invoiceAction: Function,
  match: {
    params: {
      id: string
    }
  },
  renavigationKey: string,
  sendEmail: (invoice: InvoiceModel, email: EmailModel, fullProjectMonth?: FullProjectMonthModel) => void,
}

type EditInvoiceState = {
  invoice: InvoiceModel,
  renavigationKey: string,
  showEmailModal: boolean,
}


export class EditInvoice extends Component<EditInvoiceProps, EditInvoiceState> {
  constructor(props: EditInvoiceProps) {
    super(props);
    this.state = {
      invoice: this.createModel(props),
      renavigationKey: '',
      showEmailModal: false,
    };
  }

  componentDidMount() {
    // TODO: When converting this to a functional component, also update the title:
    // "#Nr clientName (consultantName) YYYY-MM" (YYYY-MM of projectMonth.date)
    let docTitle: string;
    if (this.state.invoice._id) {
      const name = t(this.isQuotation ? 'quotation.pdfName' : 'invoice.invoice');
      docTitle = `${name} #${this.state.invoice.number}`;
    } else {
      docTitle = t(this.isQuotation ? 'titles.quotationNew' : 'titles.invoiceNew');
    }
    document.title = getDocumentTitle(docTitle);
    window.scrollTo(0, 0);
  }

  // eslint-disable-next-line camelcase
  UNSAFE_componentWillReceiveProps(nextProps: EditInvoiceProps) {
    if (nextProps.app.isLoaded !== this.props.app.isLoaded
      || (nextProps.match.params.id !== this.props.match.params.id)
      || nextProps.invoices !== this.props.invoices // Changing this? Check confac-back::invoices.js
      || nextProps.renavigationKey !== this.state.renavigationKey) {

      this.setState({invoice: this.createModel(nextProps), renavigationKey: nextProps.renavigationKey});
    }
  }

  get isQuotation(): boolean {
    return window.location.pathname.startsWith('/quotations/');
  }

  get type(): 'quotation' | 'invoice' {
    return this.isQuotation ? 'quotation' : 'invoice';
  }

  createModel(props: EditInvoiceProps): InvoiceModel {
    if (props.match.params.id) {
      // Existing invoice / quotation
      const invoicesOrQuotations = this.isQuotation
        ? props.invoices.filter(x => x.isQuotation)
        : props.invoices.filter(x => !x.isQuotation);

      const invoice = invoicesOrQuotations.find(i => i.number === parseInt(props.match.params.id, 10));
      return new InvoiceModel(props.config, invoice);
    }

    const invoice = {
      isQuotation: this.isQuotation,
    };

    return getNewInvoice(props.config, props.invoices, props.clients, invoice);
  }

  updateInvoice(key: string, value: any, calcMoneys = false) {
    // Naughty naughty: We are manipulating state directly!
    // To fix this: state should be a regular object, and a
    // InvoiceModel should be created in the render
    this.state.invoice.updateField(key, value, calcMoneys);
    this.forceUpdate();
  }

  render() {
    const {invoice} = this.state;

    const fullProjectMonth = this.props.fullProjectMonths.find(x => x.invoice && x.invoice._id === invoice._id);

    const getDefaultEmailValue = (i: InvoiceModel, config: ConfigModel): EmailModel => {
      const defaultEmail = config.email;
      if (!i.client || !i.client.email) {
        return defaultEmail;
      }

      const emailValues = Object.keys(i.client.email).reduce((acc: EmailModel, cur: string) => {
        if (i.client.email[cur]) {
          acc[cur] = i.client.email[cur];
          return acc;
        }
        return acc;
      }, {} as EmailModel);

      const finalValues = {...defaultEmail, ...emailValues};
      finalValues.subject = invoiceReplacements(finalValues.subject, i, fullProjectMonth);
      if (i.lastEmail && config.emailReminder) {
        finalValues.body = config.emailReminder;
      }
      finalValues.body = invoiceReplacements(finalValues.body, i, fullProjectMonth);
      finalValues.body += config.emailSignature;

      return finalValues;
    };

    return (
      <Container className="edit-container">
        <Form>
          <Row>
            <Col sm={12} style={{marginBottom: 20, display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}>
              <div style={{display: 'inline-flex', alignItems: 'flex-start'}}>
                <h1 style={{width: 'unset'}}>
                  {invoice._id ? (
                    <>
                      {t(`${this.type}.editTitle`)}
                      <small className="created-on">
                        {`${t('createdOn')} ${formatDate(invoice.createdOn)}`}
                      </small>
                    </>
                  ) : t(`${this.type}.createTitle`)}
                </h1>
                <div>
                  <EditInvoiceBadges invoice={invoice} />
                </div>
              </div>
              <div>
                {invoice._id && <DownloadInvoiceButton invoice={invoice} />}
              </div>
            </Col>
            <Col sm={12}>
              <InvoiceNotVerifiedAlert invoice={invoice} />
            </Col>
          </Row>

          <Row>
            <Col sm={6}>
              <EditInvoiceClient
                invoice={invoice}
                onChange={val => this.setState({invoice: invoice.setClient(val)})}
              />
            </Col>

            <Col sm={6}>
              <Row>
                <EditInvoiceDetails
                  invoice={invoice}
                  onChange={(fieldName: string, value: any) => this.updateInvoice(fieldName, value)}
                />
              </Row>
              <Row>
                <Col sm={12}>
                  <ProjectMonthSelect
                    label={t('projectMonth.selectLabel')}
                    value={invoice.projectMonthId ? invoice.projectMonthId : ''}
                    onChange={fpm => {
                      this.updateInvoice('projectMonthId', fpm._id);
                      this.updateInvoice('consultantId', fpm.consultant._id);
                    }}
                    invoice={invoice}
                  />
                </Col>
              </Row>
            </Col>
          </Row><commit_msg>editinvoice: bugfix discount: show changes to money calc directly<commit_after>import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Container, Row, Col, Form} from 'react-bootstrap';
import {t, formatDate} from '../../utils';
import EditInvoiceLines from './invoice-lines/EditInvoiceLines';
import InvoiceNotVerifiedAlert from './InvoiceNotVerifiedAlert';
import {EditInvoiceSaveButtons} from './EditInvoiceSaveButtons';
import {invoiceAction} from '../../../actions/index';
import {EditInvoiceClient} from './EditInvoiceClient';
import InvoiceModel from '../models/InvoiceModel';
import {ConfigModel} from '../../config/models/ConfigModel';
import {ClientModel} from '../../client/models/ClientModels';
import {ConfacState} from '../../../reducers/app-state';
import {EditInvoiceDetails} from './EditInvoiceDetails';
import {StickyFooter} from '../../controls/skeleton/StickyFooter';
import {DownloadInvoiceButton} from './DownloadInvoiceButton';
import {EmailModal, EmailModalTitle} from '../../controls/email/EmailModal';
import {EmailModel} from '../../controls/email/EmailModels';
import {sendEmail} from '../../../actions/emailActions';
import {Button} from '../../controls/form-controls/Button';
import {getNewInvoice} from '../models/getNewInvoice';
import {getDocumentTitle} from '../../hooks/useDocumentTitle';
import {InvoiceAttachmentsForm} from '../controls/InvoiceAttachmentsForm';
import {ConsultantModel} from '../../consultant/models/ConsultantModel';
import {projectMonthResolve} from '../../project/ProjectMonthsLists';
import {FullProjectMonthModel} from '../../project/models/FullProjectMonthModel';
import {invoiceReplacements} from '../invoice-replacements';
import {ProjectMonthSelect} from '../../project/controls/ProjectMonthSelect';
import {EditInvoiceBadges} from './EditInvoiceBadges';


type EditInvoiceProps = {
  invoices: InvoiceModel[],
  config: ConfigModel,
  app: { isLoaded: boolean },
  clients: ClientModel[],
  consultants: ConsultantModel[],
  fullProjectMonths: FullProjectMonthModel[],
  invoiceAction: Function,
  match: {
    params: {
      id: string
    }
  },
  renavigationKey: string,
  sendEmail: (invoice: InvoiceModel, email: EmailModel, fullProjectMonth?: FullProjectMonthModel) => void,
}

type EditInvoiceState = {
  invoice: InvoiceModel,
  renavigationKey: string,
  showEmailModal: boolean,
}


export class EditInvoice extends Component<EditInvoiceProps, EditInvoiceState> {
  constructor(props: EditInvoiceProps) {
    super(props);
    this.state = {
      invoice: this.createModel(props),
      renavigationKey: '',
      showEmailModal: false,
    };
  }

  componentDidMount() {
    // TODO: When converting this to a functional component, also update the title:
    // "#Nr clientName (consultantName) YYYY-MM" (YYYY-MM of projectMonth.date)
    let docTitle: string;
    if (this.state.invoice._id) {
      const name = t(this.isQuotation ? 'quotation.pdfName' : 'invoice.invoice');
      docTitle = `${name} #${this.state.invoice.number}`;
    } else {
      docTitle = t(this.isQuotation ? 'titles.quotationNew' : 'titles.invoiceNew');
    }
    document.title = getDocumentTitle(docTitle);
    window.scrollTo(0, 0);
  }

  // eslint-disable-next-line camelcase
  UNSAFE_componentWillReceiveProps(nextProps: EditInvoiceProps) {
    if (nextProps.app.isLoaded !== this.props.app.isLoaded
      || (nextProps.match.params.id !== this.props.match.params.id)
      || nextProps.invoices !== this.props.invoices // Changing this? Check confac-back::invoices.js
      || nextProps.renavigationKey !== this.state.renavigationKey) {

      this.setState({invoice: this.createModel(nextProps), renavigationKey: nextProps.renavigationKey});
    }
  }

  get isQuotation(): boolean {
    return window.location.pathname.startsWith('/quotations/');
  }

  get type(): 'quotation' | 'invoice' {
    return this.isQuotation ? 'quotation' : 'invoice';
  }

  createModel(props: EditInvoiceProps): InvoiceModel {
    if (props.match.params.id) {
      // Existing invoice / quotation
      const invoicesOrQuotations = this.isQuotation
        ? props.invoices.filter(x => x.isQuotation)
        : props.invoices.filter(x => !x.isQuotation);

      const invoice = invoicesOrQuotations.find(i => i.number === parseInt(props.match.params.id, 10));
      return new InvoiceModel(props.config, invoice);
    }

    const invoice = {
      isQuotation: this.isQuotation,
    };

    return getNewInvoice(props.config, props.invoices, props.clients, invoice);
  }

  updateInvoice(key: string, value: any, calcMoneys = false) {
    // Naughty naughty: We are manipulating state directly!
    // To fix this: state should be a regular object, and a
    // InvoiceModel should be created in the render
    this.state.invoice.updateField(key, value, calcMoneys);
    this.forceUpdate();
  }

  render() {
    const {invoice} = this.state;

    const fullProjectMonth = this.props.fullProjectMonths.find(x => x.invoice && x.invoice._id === invoice._id);

    const getDefaultEmailValue = (i: InvoiceModel, config: ConfigModel): EmailModel => {
      const defaultEmail = config.email;
      if (!i.client || !i.client.email) {
        return defaultEmail;
      }

      const emailValues = Object.keys(i.client.email).reduce((acc: EmailModel, cur: string) => {
        if (i.client.email[cur]) {
          acc[cur] = i.client.email[cur];
          return acc;
        }
        return acc;
      }, {} as EmailModel);

      const finalValues = {...defaultEmail, ...emailValues};
      finalValues.subject = invoiceReplacements(finalValues.subject, i, fullProjectMonth);
      if (i.lastEmail && config.emailReminder) {
        finalValues.body = config.emailReminder;
      }
      finalValues.body = invoiceReplacements(finalValues.body, i, fullProjectMonth);
      finalValues.body += config.emailSignature;

      return finalValues;
    };

    return (
      <Container className="edit-container">
        <Form>
          <Row>
            <Col sm={12} style={{marginBottom: 20, display: 'flex', alignItems: 'center', justifyContent: 'space-between'}}>
              <div style={{display: 'inline-flex', alignItems: 'flex-start'}}>
                <h1 style={{width: 'unset'}}>
                  {invoice._id ? (
                    <>
                      {t(`${this.type}.editTitle`)}
                      <small className="created-on">
                        {`${t('createdOn')} ${formatDate(invoice.createdOn)}`}
                      </small>
                    </>
                  ) : t(`${this.type}.createTitle`)}
                </h1>
                <div>
                  <EditInvoiceBadges invoice={invoice} />
                </div>
              </div>
              <div>
                {invoice._id && <DownloadInvoiceButton invoice={invoice} />}
              </div>
            </Col>
            <Col sm={12}>
              <InvoiceNotVerifiedAlert invoice={invoice} />
            </Col>
          </Row>

          <Row>
            <Col sm={6}>
              <EditInvoiceClient
                invoice={invoice}
                onChange={val => this.setState({invoice: invoice.setClient(val)})}
              />
            </Col>

            <Col sm={6}>
              <Row>
                <EditInvoiceDetails
                  invoice={invoice}
                  onChange={(fieldName: string, value: any) => this.updateInvoice(fieldName, value, true)}
                />
              </Row>
              <Row>
                <Col sm={12}>
                  <ProjectMonthSelect
                    label={t('projectMonth.selectLabel')}
                    value={invoice.projectMonthId ? invoice.projectMonthId : ''}
                    onChange={fpm => {
                      this.updateInvoice('projectMonthId', fpm._id);
                      this.updateInvoice('consultantId', fpm.consultant._id);
                    }}
                    invoice={invoice}
                  />
                </Col>
              </Row>
            </Col>
          </Row>
{'fraction_non_alphanumeric': 0.1392852711254498, 'fraction_numerical': 0.0019853579848616455, 'mean_word_length': 2.54887714663144, 'pattern_counts': {'":': 0, '<': 81, '<?xml version=': 0, '>': 101, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4818217', 'n_tokens_mistral': 4743, 'n_tokens_neox': 4628, 'n_words': 948}