Skip to content
Snippets Groups Projects
solid-invoicing.js 23.4 KiB
Newer Older
import { SolidTemplateElement, Helpers, store } from 'https://cdn.skypack.dev/@startinblox/core@0.16';
// const base_url = import.meta.url.replace(/\/[^\/]*$/, '');
const base_url = "https://cdn.skypack.dev/@startinblox/component-invoicing@1.2";
Helpers.importCSS(`${base_url}/css/main.css?min`);
export class SolidInvoicing extends SolidTemplateElement {
  constructor() {
    super();
Manon Bourgognon's avatar
Manon Bourgognon committed
    this.setTranslationsPath(`${base_url}/locales`);
    if (window.hubl) {
      this.localize = (key) => {
        return window.hubl.intl.t("invoices." + key) || this.strings[key] || key;
      }
    }
  }

  static get propsDefinition() {
    return {
      dataSrc: 'data-src',
      routerPrefix: 'router-prefix',
      uploadUrl: 'upload-dir'
    }
  }

  connectedCallback() {
    this.addEventListener('click', (e) => {
      // display invoice list
      if(e.target && e.target.closest('.show-list')){
        let invoice = e.target.closest('.invoices-list');
        invoice.classList.toggle('invoices-list-visible')
      }

      // display invoice details
      if(e.target && e.target.closest('[name="invoice-title"]')){
        let invoice = e.target.closest('[name="invoice-title"]');
        invoice.closest('solid-display').classList.toggle('invoice-visible')
      }

      // close dialog
      if(e.target && e.target.closest('.close-button')){
        let routeName = e.target.closest('#clients-invoices') ? 'invoice-list' : 'freelance-invoice-list'
        window.dispatchEvent(
          new CustomEvent('requestNavigation', { detail: { route: routeName } }),
        );
      }

      // access pdf invoice
      if (e.target && e.target.closest('.print')) {
        let printWindow = window.open('', 'PRINT', 'height=400,width=600');
        printWindow.document.write(`
          <html>
            <head>
              <title></title>
              <link rel="stylesheet" href="${base_url}/css/main.css?min" media="screen"/>
              <link rel="stylesheet" href="${base_url}/css/main.css?min" media="print"/>
              <style>:root{--color-primary: #6259e5;--color-secondary: #ffb700; font-family: sans-serif}</style>
            </head>
            <body>
            ${document.getElementById('print').innerHTML}
            </body>
          </html>
        `);
        printWindow.document.close();
        printWindow.focus();

        setTimeout(() => {
          printWindow.print();
          printWindow.close();
        }, 1000);
        return true;
      }
      // Duplicate invoice
      if (e.target && e.target.closest('.duplication-button')) {
        const form = this.querySelector('.duplicate-form').component;
        form.getFormValue().then((value) => {
          let resources = value;
          resources["@context"] = form.context;
          const resourceId = this.querySelector('.new-customer-invoice-form')?.component.resourceId;
          if (!resourceId) {
            console.warn('No URI found to post a new invoice.');
            return;
          }
          store.post(this.cleanInvoiceBeforeDuplicate(resources), resourceId).then(() => {
            this.dispatchEvent(
              new CustomEvent('requestNavigation', {
                bubbles: true,
                detail: { route: "invoice-list" },
              }),
            );
          });
        });
      }

      // Close duplicate dialog
      if (e.target && e.target.closest('.close-duplication-dialog')) {
        this.dispatchEvent(
          new CustomEvent('requestNavigation', {
            bubbles: true,
            detail: { route: "invoice-list" },
          }),
        );
      }
    });
  }
  cleanInvoiceBeforeDuplicate(invoice) {
    invoice.title= invoice.title + " - 2"
    delete invoice["@id"];
    delete invoice.batches["@id"];
    invoice.batches.forEach((batch, indexb) => {
      delete invoice.batches[indexb]["@id"];
      batch.tasks.forEach((task, indext) => {
        delete invoice.batches[indexb].tasks[indext]["@id"];
      });
    });
    return invoice;
  template({dataSrc, routerPrefix, uploadUrl}) {
    let prefix = routerPrefix ? `route-prefix="${routerPrefix}"`: ''

    return `
    <div id="solid-invoicing" class="solid-invoicing segment full padding-large sm-padding-xsmall sm-padding-top-xlarge">
    <!-- widgets ==================================== -->
    <solid-widget name="widget-money">
      <template>
        <div>\${value?value:0} € HT</div>
      </template>
    </solid-widget>

    <solid-widget name="widget-tva-rate">
      <template>
Manon Bourgognon's avatar
Manon Bourgognon committed
        <div>${this.localize('widget.tva_rate')} \${value} %</div>
      </template>
    </solid-widget>

    <solid-widget name="widget-invoice-number">
      <template>
        <div>N°\${value}</div>
      </template>
    </solid-widget>

    <solid-widget name="widget-batches">
      <template>
        <solid-display
          class="batches-list"
          data-src="\${value}"
          fields="batch-header(title, amount),
          batch-body(tasks),
          batch-footer(ht-total(ht-label, htAmount))"
Manon Bourgognon's avatar
Manon Bourgognon committed
          value-amount="${this.localize('value.amount')}"
          value-ht-label="${this.localize('label.value_ht')}"
          widget-tasks="widget-tasks"
          widget-htAmount="widget-money"
        ></solid-display>
      </template>
    </solid-widget>

    <solid-widget name="widget-tasks">
      <template>
        <solid-display
          class="tasks-list"
          data-src="\${value}"
          fields="price-line(title, htAmount)"
          widget-htAmount="widget-money"
        ></solid-display>
      </template>
    </solid-widget>

    <solid-widget name="widget-form-batches">
      <template>
        <solid-form
          data-src="\${value}"
          data-holder
          naked
          fields="batch-header(title), batch-body(tasks)"
          widget-tasks="widget-form-tasks"
          widget-title="solid-form-placeholder-text"
Manon Bourgognon's avatar
Manon Bourgognon committed
          label-title="${this.localize('label.title_batch')}"
          label-tasks=""
          multiple-tasks>
        </solid-form>
      </template>
    </solid-widget>

    <solid-widget name="widget-form-tasks">
      <template>
        <solid-form
          data-src="\${value}"
          class="tasks-list"
          data-holder
          naked
          fields="price-line(title, htAmount)"
          widget-htAmount="solid-form-placeholder-text"
          widget-title="solid-form-placeholder-text"
Manon Bourgognon's avatar
Manon Bourgognon committed
          label-title="${this.localize('label.title_task')}"
          label-htAmount="${this.localize('label.ht_amount')}"
        >
        </solid-form>
      </template>
    </solid-widget>


    <solid-widget name="widget-render-title">
      <template>
Manon Bourgognon's avatar
Manon Bourgognon committed
        <div><strong>${this.localize('widget.render_title')}\${value}</strong></div>
      </template>
    </solid-widget>

    <solid-widget name="widget-render-metadata">
      <template>
        <div>
          <div class="invoice-render__fees">
            <p>\${value}</p>
Manon Bourgognon's avatar
Manon Bourgognon committed
            <p>${this.localize('widget.render_metadata_late')}</p>
            <p>${this.localize('widget.render_metadata_indemnity')}</p>
          </div>
          <div class="invoice-render__rib">
Manon Bourgognon's avatar
Manon Bourgognon committed
            <div><strong>${this.localize('widget.render_metadata_bank_transfert')}</strong></div>
            <table>
              <tr>
                <td>BIC</td>
                <td>CMCIFR2A</td>
              </tr>
              <tr>
                <td>IBAN</td>
                <td>FR76 1027 8060 8200 0205 5370 166</td>
              </tr>
            </table>
          </div>
        </div>
      </template>
    </solid-widget>

    <solid-widget name="widget-embed-drive">
      <template>
        <embed src="\${value}" width="560px" height="800px"/>
      </template>
    </solid-widget>

    <solid-widget name="widget-nomenclature">
      <template>
        <div>
          <b>${this.localize('title.nomenclature')}</b> : ${this.localize('text.nomenclature')}
        </div>
      </template>
    </solid-widget>

    <solid-widget name="widget-state">
    <template>
      \${value == 'edited' ? '<div class="sm-full button text-xsmall text-bold text-center reversed color-secondary bordered">${this.localize('option.edited')}</div>' : '' }
      \${value == 'pending' ? '<div class="sm-full button text-xsmall text-bold text-center color-secondary bordered">${this.localize('option.pending')}</div>' : '' }
      \${value == 'sent' ? '<div class="sm-full button text-xsmall text-bold text-center color-third bordered">${this.localize('option.sent')}</div>' : '' }
      \${value == 'paid' ? '<div class="sm-full button text-xsmall text-bold text-center reversed color-third bordered">${this.localize('option.paid')}</div>' : '' }
      \${value == 'late' ? '<div class="sm-full button text-xsmall text-bold text-center reversed color-primary bordered">${this.localize('option.late')}</div>' : '' }


    </template>
  </solid-widget>

    <solid-widget name="close-button">
      <template>
        <button class="close-button icon icon-close">
        </button>
      </template>
    </solid-widget>

    <!-- /widgets ==================================== -->


    <!-- customer ==================================== -->
    <section id="clients-invoices" class="invoices-list invoices-list--clients">
      <header class="segment">
        <div class="show-list segment text-bold text-large text-uppercase padding-xsmall icon icon-arrow-down">${this.localize('title.customer_invoices')}</div>
        <solid-router default-route="invoice-list" ${prefix}>
          <solid-route name="invoice-list"></solid-route>
          <solid-route name="add-invoice" class="segment sm-full button text-xsmall text-bold text-uppercase text-center reversed color-secondary bordered icon icon-plus">${this.localize('button.invoice_create')}</solid-route>
          <solid-route name="show-invoice" use-id></solid-route>
          <solid-route name="edit-invoice" use-id></solid-route>
          <solid-route name="duplicate-invoice" use-id></solid-route>
        </solid-router>
      </header>

      <dialog data-view="add-invoice" class="invoice-form">
        <close-button></close-button>
        <h3 class="text-color-heading">${this.localize('title.invoice_create')}</h3>

        <solid-form
          class="new-customer-invoice-form"
          data-src="${dataSrc}"
          nested-field="customerInvoices"
          fields="first-line(title, identifier),
          second-line(state, invoicingDate),
          batches,
          last-line(tvaRate, additionalText)"
Manon Bourgognon's avatar
Manon Bourgognon committed
          label-title="${this.localize('label.invoice_title')}"
          label-identifier="${this.localize('label.invoice_identifier')}"
          label-state="${this.localize('label.invoice_state')}"
          label-invoicingDate="${this.localize('label.invoice_invoicing_date')}"
          label-tvaRate="${this.localize('label.invoice_tva_rate')}"
          label-additionalText="${this.localize('label.invoice_additional_text')}"
          label-batches=""
          widget-state="solid-form-dropdown-label"
          widget-batches="widget-form-batches"
          widget-invoicingDate="solid-form-date-label"
          widget-tvaRate="solid-form-number-label"
          enum-state="${this.localize('option.edited')} = edited, ${this.localize('option.pending')} = pending, ${this.localize('option.sent')} = sent, ${this.localize('option.late')} = late, ${this.localize('option.paid')} = paid"
          class-state="custom-select"
          multiple-batches
Manon Bourgognon's avatar
Manon Bourgognon committed
          next="invoice-list"
          required-title
          required-identifier
          required-invoicingDate
          required-tvaRate
Manon Bourgognon's avatar
Manon Bourgognon committed
          submit-button="${this.localize('button.submit_form')}"
          ></solid-form>
      <dialog data-view="edit-invoice" class="invoice-form">
        <close-button></close-button>
Manon Bourgognon's avatar
Manon Bourgognon committed
        <h3>${this.localize('title.invoice_modify')}</h3>
          bind-resources
          fields="first-line(title, identifier),
          second-line(state, invoicingDate),
          batches,
          last-line(tvaRate, additionalText)"
Manon Bourgognon's avatar
Manon Bourgognon committed
          label-title="${this.localize('label.invoice_title')}"
          label-identifier="${this.localize('label.invoice_identifier')}"
          label-state="${this.localize('label.invoice_state')}"
          label-invoicingDate="${this.localize('label.invoice_invoicing_date')}"
          label-tvaRate="${this.localize('label.invoice_tva_rate')}"
          label-additionalText="${this.localize('label.invoice_additional_text')}"
          label-batches=""
          widget-state="solid-form-dropdown-label"
          widget-batches="widget-form-batches"
          widget-invoicingDate="solid-form-date-label"
          widget-tvaRate="solid-form-number-label"
          enum-state="${this.localize('option.edited')} = edited, ${this.localize('option.pending')} = pending, ${this.localize('option.sent')} = sent, ${this.localize('option.late')} = late, ${this.localize('option.paid')} = paid"
          class-state="custom-select"
          multiple-batches
          next="invoice-list"
          required-title
          required-identifier
          required-invoicingDate
          required-tvaRate
          submit-button="${this.localize('button.edit_submit_form')}"
        <solid-delete class="sm-full button text-xsmall text-bold text-uppercase text-center color-secondary bordered icon icon-trash" 
          bind-resources
          next="invoice-list"
          confirmation-message="${this.localize('confirm.delete_invoice')}"
          confirmation-type="dialog"
          confirmation-submit-text="${this.localize('validate.delete_invoice')}"
          confirmation-cancel-text="${this.localize('cancel.delete_invoice')}"
          confirmation-submit-class="segment sm-full button text-xsmall text-bold reversed text-uppercase text-center color-secondary bordered"
          confirmation-cancel-class="cancel segment sm-full button text-xsmall text-bold text-uppercase text-center color-secondary bordered"
          data-label="${this.localize('button.delete_submit_form')}"
        ></solid-delete>
      </dialog>

      <dialog data-view="duplicate-invoice" class="invoice-form" id="duplicate">
        <close-button></close-button>
Manon Bourgognon's avatar
Manon Bourgognon committed
        <h3>${this.localize('title.invoice_duplicate')}</h3>
        <h4>${this.localize('question.invoice_duplicate')}</h4>
        <solid-form
          class="duplicate-form"
          bind-resources
          fields="first-line(title, identifier),
          second-line(state, invoicingDate),
          batches,
          last-line(tvaRate, additionalText)"
          widget-state="solid-form-dropdown-label"
          widget-batches="widget-form-batches"
          widget-invoicingDate="solid-form-date-label"
          widget-tvaRate="solid-form-number-label"
          enum-state="${this.localize('option.edited')} = edited, ${this.localize('option.pending')} = pending, ${this.localize('option.sent')} = sent, ${this.localize('option.late')} = late, ${this.localize('option.paid')} = paid"
          class-state="custom-select"
          class-title="invoice-title"
          multiple-batches
          naked
        ></solid-form>
        <div class="popup-buttons">
Manon Bourgognon's avatar
Manon Bourgognon committed
          <button class="popup-buttons__cancel close-duplication-dialog">${this.localize('button.duplicate_cancel')}</button>
          <button class="popup-buttons__validate duplication-button">${this.localize('button.duplicate_confirm')}</button>
      <solid-display
        data-view="invoice-list"
        class="invoices-list__item invoice-list-display"
        data-src="${dataSrc}"
        nested-field="customerInvoices"
        fields="invoice-title(invoice-main(invoice-header(title, identifier), invoice-footer(invoicingDate, htAmount)), invoice-aside(state)),
        invoice-hidden(actions(duplicate, edit, show), batches, invoice-totals(totals-header, ht-total(ht-label, htAmount), tva-total(tvaRate, tvaAmount), ttc-total(ttc-label, ttcAmount)))"
Manon Bourgognon's avatar
Manon Bourgognon committed
        value-totals-header="${this.localize('value.totals_header')}"
        value-ht-label="${this.localize('label.value_ht')}"
        value-ttc-label="${this.localize('label.value_ttc')}"
        widget-batches="widget-batches"
        widget-htAmount="widget-money"
        widget-state="widget-state"
        widget-tvaAmount="widget-money"
        widget-ttcAmount="widget-money"
        widget-tvaRate="widget-tva-rate"
        widget-identifier="widget-invoice-number"
Manon Bourgognon's avatar
Manon Bourgognon committed
        label-edit="${this.localize('label.edit')}"
        label-show="${this.localize('label.show')}"
        label-duplicate="${this.localize('label.duplicate')}"
        action-edit="edit-invoice"
        action-show="show-invoice"
        action-duplicate="duplicate-invoice"
        class-edit="mdi-pencil mdi"
        class-show="mdi-eye mdi"
        class-duplicate="mdi-file-replace-outline mdi"
        order-desc="invoicingDate"
      ></solid-display>


      <!-- render -->
      <dialog class="invoice-dialog" data-view="show-invoice">
        <close-button></close-button>
        <div id="print">
          <div class="invoice-render">
            <img src="${base_url}/images/happy-dev-logo.png" width="200" />
            <solid-display
              class="display-invoice"
              bind-resources
              fields="invoice-head(invoice-header-left(identifier, invoicingDate), invoice-header-right(customer-infos(project.customer.name, project.customer.address))),
              batches,
              invoice-payment(additionalText, invoice-totals(ht-total(ht-label, htAmount), tva-total(tvaRate, tvaAmount), ttc-total(ttc-label, ttcAmount), tva-notice)),
              invoice-footer"

Manon Bourgognon's avatar
Manon Bourgognon committed
              value-ht-label="${this.localize('label.value_ht')}"
              value-ttc-label="${this.localize('label.value_ttc')}"
              value-tva-notice="${this.localize('value.tva_notice')}"

              widget-identifier="widget-render-title"
Manon Bourgognon's avatar
Manon Bourgognon committed
              label-invoicingDate="${this.localize('label.invoice_invoicing_date2')}"
              widget-invoicingDate="solid-display-value-date-label"
              widget-additionalText="widget-render-metadata"
              widget-batches="widget-batches"
              widget-htAmount="widget-money"
              widget-tvaAmount="widget-money"
              widget-ttcAmount="widget-money"
              widget-tvaRate="widget-tva-rate"
              class-project.customer.name="segment block whitespace-normal text-color-heading text-bold margin-bottom-xsmall"
            ></solid-display>

            <div class="invoice-render__footer">
              <div class="invoice-render__hd-address">
                <p><strong>Happy Dev</strong></p>
                <p>75 rue du Javelot</p>
                <p>75013 Paris</p>
                <p>contact@happy-dev.fr</p>
              </div>
              <div class="invoice-render__hd-legal">
                <p><strong>SIRET : </strong>81314060500011</p>
Manon Bourgognon's avatar
Manon Bourgognon committed
                <p><strong>${this.localize('text.tva_number')}</strong>FR 61 813140605</p>
                <p>${this.localize('text.sas_info')}</p>
              </div>
            </div>
          </div>
        </div>
        <div class="popup-buttons">
Manon Bourgognon's avatar
Manon Bourgognon committed
          <button class="popup-buttons__validate print">${this.localize('button.print')}</button>
      </dialog>
      <!-- /render -->

    </section>
    <!-- /customer ==================================== -->

    <!-- freelance ==================================== -->
    <section id="freelances-invoices" class="invoices-list invoices-list--freelances">
      <header class="segment">
        <div class="show-list segment text-bold text-large text-uppercase padding-xsmall icon icon-arrow-down">${this.localize('title.freelance_invoices')}</div>
        <solid-router default-route="freelance-invoice-list" ${prefix}>
          <solid-route name="freelance-invoice-list"></solid-route>
          <solid-route name="edit-freelance-invoice"></solid-route>
          <solid-route name="add-freelance-invoice" class="segment sm-full button text-xsmall text-bold text-uppercase text-center reversed color-secondary bordered icon icon-cloud-upload">${this.localize('button.invoice_import')}</solid-route>
        </solid-router>
      </header>

      <dialog data-view="add-freelance-invoice" class="invoice-form">
        <close-button></close-button>
Manon Bourgognon's avatar
Manon Bourgognon committed
        <h3>${this.localize('title.invoice_import')}</h3>
        <solid-form
          data-src="${dataSrc}"
          nested-field="freelancerInvoices"
          fields="first-line(freelanceFullname, identifier),
          second-line(htAmount, nomenclatureNote),
          third-line(uploadUrl)"
Manon Bourgognon's avatar
Manon Bourgognon committed
          label-freelanceFullname="${this.localize('label.freelance_fullname')}"
          label-identifier="${this.localize('label.freelance_identifier')}"
          label-uploadUrl="${this.localize('label.upload_url')}"
          label-htAmount="${this.localize('label.freelance_ht_amount')}"
          widget-nomenclatureNote="widget-nomenclature"
          widget-uploadUrl="solid-form-file-label"
          widget-htAmount="solid-form-number-label"
          upload-url-uploadUrl="${uploadUrl}"
          required-freelanceFullname
          required-identifier
          required-htAmount
          required-uploadUrl
          next="freelance-invoice-list"
Manon Bourgognon's avatar
Manon Bourgognon committed
          submit-button="${this.localize('button.submit_form')}"
        ></solid-form>
      </dialog>

      <dialog data-view="edit-freelance-invoice" class="invoice-form">
        <close-button></close-button>
        <h3>Modifier une facture</h3>
        <solid-form
          bind-resources
          fields="first-line(freelanceFullname, identifier),
          second-line(invoicingDate, htAmount),
          third-line(state, uploadUrl)"
Manon Bourgognon's avatar
Manon Bourgognon committed
          label-freelanceFullname="${this.localize('label.freelance_fullname')}"
          label-identifier="${this.localize('label.freelance_identifier')}"
          label-invoicingDate="${this.localize('label.freelance_invoicing_date')}"
          label-state="${this.localize('label.invoice_state')}"
          label-uploadUrl="${this.localize('label.edit_upload_url')}"
Manon Bourgognon's avatar
Manon Bourgognon committed
          label-htAmount="${this.localize('label.freelance_ht_amount')}"
          widget-invoicingDate="solid-form-date-label"
          widget-uploadUrl="solid-form-file-label"
          widget-htAmount="solid-form-number-label"
          widget-state="solid-form-dropdown-label"
          upload-url-uploadUrl="${uploadUrl}"
          enum-state="${this.localize('option.edited')} = edited, ${this.localize('option.pending')} = pending, ${this.localize('option.sent')} = sent, ${this.localize('option.late')} = late, ${this.localize('option.paid')} = paid"
          class-state="custom-select"
          required-freelanceFullname
          required-identifier
          required-invoicingDate
          required-htAmount
          next="freelance-invoice-list"
Manon Bourgognon's avatar
Manon Bourgognon committed
          submit-button="${this.localize('button.submit_form')}"
        ></solid-form>
      </dialog>

      <solid-display
        class="invoice-list-display"
        data-view="freelance-invoice-list"
        data-src="${dataSrc}"
        nested-field="freelancerInvoices"
        fields="invoice-title(invoice-main(invoice-header(freelanceFullname, identifier), invoice-footer(invoicingDate, htAmount)), invoice-aside(state)),
        invoice-hidden(actions(edit), uploadUrl)"
Manon Bourgognon's avatar
Manon Bourgognon committed
        label-edit=""${this.localize('label.edit')}""
        action-edit="edit-freelance-invoice"
        widget-htAmount="widget-money"
        widget-state="widget-state"
        widget-uploadUrl="widget-embed-drive"
        order-desc="invoicingDate"
      ></solid-display>
    </section>
    <!-- /freelance ==================================== -->
  </div>
 `;
  }
}

customElements.define('solid-invoicing', SolidInvoicing);