feat: rendering pdf templates on the server-side

This commit is contained in:
Ahmed Bouhuolia
2024-09-17 13:53:57 +02:00
parent 4f59b27d70
commit 2c790427fa
44 changed files with 1833 additions and 363 deletions

View File

@@ -1,81 +1,205 @@
extends ../PaperTemplateLayout.pug extends ../PaperTemplateLayout.pug
block head block head
style - var prefix = 'bc'
if (isRtl) style.
include ../../css/modules/credit-rtl.css .#{prefix}-root {
else color: #111;
include ../../css/modules/credit.css padding: 24px 30px;
font-size: 12px;
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-big-title {
font-size: 60px;
margin: 0;
line-height: 1;
margin-bottom: 25px;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap {
height: 120px;
width: 120px;
position: absolute;
right: 26px;
top: 26px;
overflow: hidden;
}
.#{prefix}-terms-list {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 24px;
}
.#{prefix}-terms-item {
display: flex;
flex-direction: row;
gap: 12px;
}
.#{prefix}-terms-item__label {
min-width: 120px;
color: #333;
}
.#{prefix}-terms-item__value {
/* Styles for the term value */
}
.#{prefix}-group {
box-sizing: border-box;
display: flex;
flex-flow: wrap;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
margin-bottom: 24px;
}
.#{prefix}-address {
/* Styles for each address block */
}
.#{prefix}-table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: inherit;
}
.#{prefix}-table__header {
font-weight: 400;
border-bottom: 1px solid #000;
padding: 2px 10px;
color: #333;
}
.#{prefix}-table__header:first-of-type{
padding-left: 0;
}
.#{prefix}-table__header:last-of-type{
padding-right: 0;
}
.#{prefix}-table__header--right {
text-align: right;
}
.#{prefix}-table__cell {
border-bottom: 1px solid #F6F6F6;
padding: 12px 10px;
}
.#{prefix}-table__cell:first-of-type{
padding-left: 0;
}
.#{prefix}-table__cell:last-of-type {
padding-right: 0;
}
.#{prefix}-table__cell--right {
text-align: right;
}
.#{prefix}-totals {
display: flex;
flex-direction: column;
margin-left: auto;
width: 300px;
margin-bottom: 24px;
}
.#{prefix}-totals__item {
display: flex;
padding: 4px 0;
}
.#{prefix}-totals__item--border-gray {
border-bottom: 1px solid #DADADA;
}
.#{prefix}-totals__item--border-dark {
border-bottom: 1px solid #000;
}
.#{prefix}-totals__item--font-weight-bold {
font-weight: bold;
}
.#{prefix}-totals__item-label {
min-width: 160px;
}
.#{prefix}-totals__item-amount {
flex: 1 1 auto;
text-align: right;
}
.#{prefix}-statement {
/* Styles for customer note/terms statement section */
}
.#{prefix}-statement__label {
/* Styles for statement label */
}
.#{prefix}-statement__value {
/* Styles for statement value */
}
block content block content
div.credit div(class=`${prefix}-root`)
div.credit__header div(class=`${prefix}-big-title`) Credit Note
div.paper
h1.title #{__('credit.paper.credit_note')}
if creditNote.creditNoteNumber
span.creditNoteNumber #{creditNote.creditNoteNumber}
div.organization if showCompanyLogo
h3.title #{organizationName} div(class=`${prefix}-logo-wrap`)
if organizationEmail img(src=companyLogo alt=`Company Logo`)
span.email #{organizationEmail}
div.credit__full-amount div(class=`${prefix}-terms-list`)
div.label #{__('credit.paper.amount')} if showCreditNoteNumber
div.amount #{creditNote.formattedAmount} div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{creditNoteNumberLabel}:
div(class=`${prefix}-terms-item__value`) #{creditNoteNumebr}
div.credit__meta if showCreditNoteDate
div.credit__meta-item.credit__meta-item--amount div(class=`${prefix}-terms-item`)
span.label #{__('credit.paper.remaining')} div(class=`${prefix}-terms-item__label`) #{creditNoteDateLabel}:
span.value #{creditNote.formattedCreditsRemaining} div(class=`${prefix}-terms-item__value`) #{creditNoteDate}
div.credit__meta-item.credit__meta-item--billed-to div(class=`${prefix}-group`)
span.label #{__("credit.paper.billed_to")} if showBilledFromAddress
span.value #{creditNote.customer.displayName} div(class=`${prefix}-address`)
strong #{companyName}
each address in billedFromAddress
div #{address}
if showBilledToAddress
div(class=`${prefix}-address`)
strong #{billedToLabel}
each address in billedToAddress
div #{address}
div.credit__meta-item.credit__meta-item--credit-date table(class=`${prefix}-table`)
span.label #{__("credit.paper.credit_date")} thead(class=`${prefix}-table__header`)
span.value #{creditNote.formattedCreditNoteDate} tr
th #{'Item'}
div.credit__table th #{'Description'}
table th #{'Rate'}
thead th #{'Total'}
tr
th.item #{__("item_entry.paper.item_name")}
th.rate #{__("item_entry.paper.rate")}
th.quantity #{__("item_entry.paper.quantity")}
th.total #{__("item_entry.paper.total")}
tbody tbody
each entry in creditNote.entries each line in lines
tr tr(class=`${prefix}-table__row`)
td.item td #{line.item}
div.title=entry.item.name td #{line.description}
span.description=entry.description td(class=`${prefix}-table__column--right`) #{line.rate}
td.rate=entry.rate td(class=`${prefix}-table__column--right`) #{line.total}
td.quantity=entry.quantity
td.total=entry.amount
div.credit__table-after div(class=`${prefix}-totals`)
div.credit__table-total if showSubtotal
table div(class=`${prefix}-totals__item ${prefix}-totals__item--border-gray`)
tbody div #{subtotalLabel}:
tr.total div #{subtotal}
td #{__('credit.paper.total')}
td #{creditNote.formattedAmount}
tr.payment-amount
td #{__('credit.paper.credits_used')}
td #{creditNote.formattedCreditsUsed}
tr.blanace-due
td #{__('credit.paper.credits_remaining')}
td #{creditNote.formattedCreditsRemaining}
div.credit__footer if showTotal
if creditNote.termsConditions div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark`)
div.credit__conditions div #{totalLabel}:
h3 #{__("credit.paper.terms_conditions")} div #{total}
p #{creditNote.termsConditions}
if creditNote.note if showCustomerNote
div.credit__notes div(class=`${prefix}-statement`)
h3 #{__("credit.paper.notes")} div(class=`${prefix}-statement__label`) #{customerNoteLabel}:
p #{creditNote.note} div(class=`${prefix}-statement__value`) #{customerNote}
if showTermsConditions
div(class=`${prefix}-statement`)
div(class=`${prefix}-statement__label`) #{termsConditionsLabel}:
div(class=`${prefix}-statement__value`) #{termsConditions}

View File

@@ -1,82 +1,202 @@
extends ../PaperTemplateLayout.pug extends ../PaperTemplateLayout.pug
block head block head
style - var prefix = 'bc'
if (isRtl) style.
include ../../css/modules/estimate-rtl.css .#{prefix}-root {
else color: #111;
include ../../css/modules/estimate.css padding: 24px 30px;
font-size: 12px;
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-big-title {
font-size: 60px;
margin: 0;
line-height: 1;
margin-bottom: 25px;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap {
height: 120px;
width: 120px;
position: absolute;
right: 26px;
top: 26px;
overflow: hidden;
}
.#{prefix}-terms-list {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 24px;
}
.#{prefix}-terms-item {
display: flex;
flex-direction: row;
gap: 12px;
}
.#{prefix}-terms-item__label {
min-width: 120px;
color: #333;
}
.#{prefix}-terms-item__value {
}
.#{prefix}-address-section{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
margin-bottom: 24px;
}
.#{prefix}-address {
}
.#{prefix}-address__item {
}
.#{prefix}-table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: inherit;
}
.#{prefix}-table__header {
font-weight: 400;
border-bottom: 1px solid #000;
padding: 2px 10px;
color: #333;
}
.#{prefix}-table__header:first-of-type{
padding-left: 0;
}
.#{prefix}-table__header:last-of-type{
padding-right: 0;
}
.#{prefix}-table__header--right {
text-align: right;
}
.#{prefix}-table__cell {
border-bottom: 1px solid #F6F6F6;
padding: 12px 10px;
}
.#{prefix}-table__cell--right{
text-align: right;
}
.#{prefix}-table__cell:first-of-type{
padding-left: 0;
}
.#{prefix}-table__cell:last-of-type {
padding-right: 0;
}
.#{prefix}-totals {
display: flex;
flex-direction: column;
margin-left: auto;
width: 300px;
margin-bottom: 24px;
}
.#{prefix}-totals__item {
display: flex;
padding: 4px 0;
}
.#{prefix}-totals__item--border-gray {
border-bottom: 1px solid #DADADA;
}
.#{prefix}-totals__item--border-dark {
border-bottom: 1px solid #000;
}
.#{prefix}-totals__item--font-weight-bold {
font-weight: bold;
}
.#{prefix}-totals__item-label {
min-width: 160px;
}
.#{prefix}-totals__item-amount {
flex: 1 1 auto;
text-align: right;
}
.#{prefix}-statement {
}
.#{prefix}-statement__label {
}
.#{prefix}-statement__value {
}
block content block content
div.estimate div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
div.estimate__header h1(class=`${prefix}-big-title`) Estimate
div.paper
h1.title #{__("estimate.paper.estimate")}
span.email #{saleEstimate.estimateNumber}
div.organization if showCompanyLogo
h3.title #{organizationName} div(class=`${prefix}-logo-wrap`)
if organizationEmail img(alt="", src=companyLogo)
span.email #{organizationEmail}
div.estimate__estimate-amount //- Terms List
div.label #{__('estimate.paper.estimate_amount')} div(class=`${prefix}-terms`)
div.amount #{saleEstimate.formattedAmount} if showEstimateNumber
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{estimateNumberLabel}
div(class=`${prefix}-terms-item__value`) #{estimateNumebr}
if showEstimateDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{estimateDateLabel}
div(class=`${prefix}-terms-item__value`) #{estimateDate}
if showExpirationDate
div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{expirationDateLabel}
div(class=`${prefix}-terms-item__value`) #{expirationDate}
div.estimate__meta //- Addresses (Group section)
if saleEstimate.estimateNumber div(class=`${prefix}-address-section`)
div.estimate__meta-item.estimate__meta-item--estimate-number if showBilledFromAddress
span.label #{__("estimate.paper.estimate_number")} div(class=`${prefix}-address`)
span.value #{saleEstimate.estimateNumber} strong #{companyName}
each item in billedFromAddress
div(class=`${prefix}-address__item`) #{item}
div.estimate__meta-item.estimate__meta-item--billed-to if showBilledToAddress
span.label #{__("estimate.paper.billed_to")} div(class=`${prefix}-address`)
span.value #{saleEstimate.customer.displayName} strong #{billedToLabel}
each item in billedToAddress
div(class=`${prefix}-address__item`) #{item}
div.estimate__meta-item.estimate__meta-item--estimate-date //- Table section (Line items)
span.label #{__("estimate.paper.estimate_date")} table(class=`${prefix}-table`)
span.value #{saleEstimate.formattedEstimateDate} thead
tr
div.estimate__meta-item.estimate__meta-item--due-date th(class=`${prefix}-table__header`) Item
span.label #{__("estimate.paper.expiration_date")} th(class=`${prefix}-table__header`) Description
span.value #{saleEstimate.formattedExpirationDate} th(class=`${prefix}-table__header ${prefix}-table__header--right`) Rate
th(class=`${prefix}-table__header ${prefix}-table__header--right`) Total
div.estimate__table
table
thead
tr
th.item #{__("item_entry.paper.item_name")}
th.rate #{__("item_entry.paper.rate")}
th.quantity #{__("item_entry.paper.quantity")}
th.total #{__("item_entry.paper.total")}
tbody tbody
each entry in saleEstimate.entries each line in lines
tr tr
td.item td(class=`${prefix}-table__cell`) #{line.item}
div.title=entry.item.name td(class=`${prefix}-table__cell`) #{line.description}
span.description=entry.description td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.rate}
td.rate=entry.rate td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.total}
td.quantity=entry.quantity
td.total=entry.amount
div.estimate__table-after //- Totals section
div.estimate__table-total div(class=`${prefix}-totals`)
table if showSubtotal
tbody div(class=`${prefix}-totals__item`)
tr.subtotal div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
td #{__('estimate.paper.subtotal')} div(class=`${prefix}-totals__item-amount`) #{subtotal}
td #{saleEstimate.formattedAmount} if showTotal
tr.total div(class=`${prefix}-totals__item`)
td #{__('estimate.paper.total')} div(class=`${prefix}-totals__item-label`) #{totalLabel}
td #{saleEstimate.formattedAmount} div(class=`${prefix}-totals__item-amount`) #{total}
div.estimate__footer //- Statements section
if saleEstimate.termsConditions if showCustomerNote
div.estimate__conditions div(class=`${prefix}-statement`)
h3 #{__("estimate.paper.conditions_title")} div(class=`${prefix}-statement__label`) #{customerNoteLabel}
p #{saleEstimate.termsConditions} div(class=`${prefix}-statement__value`) #{customerNote}
if saleEstimate.note if showTermsConditions
div.estimate__notes div(class=`${prefix}-statement`)
h3 #{__("estimate.paper.notes_title")} div(class=`${prefix}-statement__label`) #{termsConditionsLabel}
p #{saleEstimate.note} div(class=`${prefix}-statement__value`) #{termsConditions}

View File

@@ -4,7 +4,6 @@ block head
- var prefix = 'bc' - var prefix = 'bc'
style. style.
.#{prefix}-root { .#{prefix}-root {
background-color: #fff;
color: #111; color: #111;
padding: 24px 30px; padding: 24px 30px;
font-size: 12px; font-size: 12px;
@@ -25,7 +24,6 @@ block head
position: absolute; position: absolute;
right: 26px; right: 26px;
top: 26px; top: 26px;
border-radius: 5px;
overflow: hidden; overflow: hidden;
} }
.#{prefix}-details { .#{prefix}-details {
@@ -122,7 +120,6 @@ block head
} }
.#{prefix}-totals__item--font-weight-bold { .#{prefix}-totals__item--font-weight-bold {
font-weight: bold; font-weight: bold;
/* Additional styles for total items with bold font weight */
} }
.#{prefix}-totals__item-label { .#{prefix}-totals__item-label {
min-width: 160px; min-width: 160px;
@@ -143,6 +140,7 @@ block head
block content block content
//- block head //- block head
div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`) div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
//- Title and company logo //- Title and company logo
h1(class=`${prefix}-big-title`) Invoice h1(class=`${prefix}-big-title`) Invoice

View File

@@ -1,67 +1,187 @@
extends ../PaperTemplateLayout.pug extends ../PaperTemplateLayout.pug
block head block head
style - var prefix = 'bp3';
if (isRtl)
include ../../css/modules/payment-rtl.css
else
include ../../css/modules/payment.css
style.
.#{prefix}-root{
color: #111;
padding: 24px 30px;
font-size: 12px;
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-big-title{
font-size: 60px;
margin: 0;
line-height: 1;
margin-bottom: 25px;
font-weight: 500;
color: #333;
}
.#{prefix}-logo-wrap{
height: 120px;
width: 120px;
position: absolute;
right: 26px;
top: 26px;
overflow: hidden;
}
.#{prefix}-terms-list{
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 24px;
}
.#{prefix}-terms-item{
display: flex;
flex-direction: row;
gap: 12px;
}
.#{prefix}-terms-item__label{
min-width: 120px;
color: #333;
}
.#{prefix}-group{
}
.#{prefix}-addresses{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
margin-bottom: 24px;
}
.#{prefix}-addresses > * {
flex: 1 1;
}
.#{prefix}-address__label{
}
.#{prefix}-table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: inherit;
}
.#{prefix}-table__header {
font-weight: 400;
border-bottom: 1px solid #000;
padding: 2px 10px;
color: #333;
}
.#{prefix}-table__header:first-of-type{
padding-left: 0;
}
.#{prefix}-table__header:last-of-type{
padding-right: 0;
}
.#{prefix}-table__header--right {
text-align: right;
}
.#{prefix}-table__cell {
border-bottom: 1px solid #F6F6F6;
padding: 12px 10px;
}
.#{prefix}-table__cell:first-of-type{
padding-left: 0;
}
.#{prefix}-table__cell:last-of-type {
padding-right: 0;
}
.#{prefix}-table__cell--right {
text-align: right;
}
.#{prefix}-table__column{
}
.#{prefix}-table__column--right{
}
.#{prefix}-totals {
display: flex;
flex-direction: column;
margin-left: auto;
width: 300px;
margin-bottom: 24px;
}
.#{prefix}-totals__item {
display: flex;
padding: 4px 0;
}
.#{prefix}-totals__item--gray-border {
border-bottom: 1px solid #DADADA;
}
.#{prefix}-totals__item--dark-border {
border-bottom: 1px solid #000;
}
.#{prefix}-totals__item--bold {
font-weight: bold;
}
.#{prefix}-totals__item-label {
min-width: 160px;
}
.#{prefix}-totals__item-amount {
flex: 1 1 auto;
text-align: right;
}
block content block content
div.payment div(class=`${prefix}-root`)
div.payment__header div(class=`${prefix}-big-title`) Payment
div.paper
h1.title #{__("payment.paper.payment_receipt")}
if paymentReceive.paymentReceiveNo
span.paymentNumber #{paymentReceive.paymentReceiveNo}
div.organization if showCompanyLogo
h3.title #{organizationName} div(class=`${prefix}-logo-wrap`)
if organizationEmail img(src=companyLogo alt="Company Logo")
span.email #{organizationEmail}
div.payment__received-amount div(class=`${prefix}-terms-list`)
div.label #{__('payment.paper.amount_received')} if showPaymentReceivedNumber
div.amount #{paymentReceive.formattedAmount} div(class=`${prefix}-terms-item`)
div(class=`${prefix}-terms-item__label`) #{paymentReceivedNumberLabel}
div(class=`${prefix}-terms-item__value`) #{paymentReceivedNumebr}
div.payment__meta if showPaymentReceivedDate
div.payment__meta-item.payment__meta-item--billed-to div(class=`${prefix}-terms-item`)
span.label #{__("payment.paper.billed_to")} div(class=`${prefix}-terms-item__label`) #{paymentReceivedDateLabel}
span.value #{paymentReceive.customer.displayName} div(class=`${prefix}-terms-item__value`) #{paymentReceivedDate}
div.payment__meta-item.payment__meta-item--payment-date div(class=`${prefix}-addresses`)
span.label #{__("payment.paper.payment_date")} if showBilledFromAddress
span.value #{paymentReceive.formattedPaymentDate} div(class=`${prefix}-address`)
strong(class=`${prefix}-address__item`) #{companyName}
each addressLine in billedFromAddress
div(class=`${prefix}-address__item`) #{addressLine}
div.payment__table if showBillingToAddress
table div(class=`${prefix}-address`)
thead strong(class=`${prefix}-address__item`) #{billedToLabel}
tr each addressLine in billedToAddress
th.item #{__("payment.paper.invoice_number")} div(class=`${prefix}-address__item`) #{addressLine}
th.date #{__("payment.paper.invoice_date")}
th.invoiceAmount #{__("payment.paper.invoice_amount")}
th.paymentAmount #{__("payment.paper.payment_amount")}
tbody
each entry in paymentReceive.entries
tr
td.item=entry.invoice.invoiceNo
td.date=entry.invoice.invoiceDateFormatted
td.invoiceAmount=entry.invoice.totalFormatted
td.paymentAmount=entry.invoice.paymentAmountFormatted
div.payment__table-after table(class=`${prefix}-table`)
div.payment__table-total thead
table tr
tbody th(class=`${prefix}-table__header`) Invoice #
tr.payment-amount th(class=`${prefix}-table__header ${prefix}-table__header--right`) Invoice Amount
td #{__('payment.paper.payment_amount')} th(class=`${prefix}-table__header ${prefix}-table__header--right`) Paid Amount
td #{paymentReceive.formattedAmount}
tr.blanace-due
td #{__('payment.paper.balance_due')}
td #{paymentReceive.customer.closingBalance}
div.payment__footer tbody
if paymentReceive.statement each line in lines
div.payment__notes tr
h3 #{__("payment.paper.statement")} td(class=`${prefix}-table__cell`) #{line.invoiceNumber}
p #{paymentReceive.statement} td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.invoiceAmount}
td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.paidAmount}
div(class=`${prefix}-totals`)
if showSubtotal
div(class=`${prefix}-totals__item`)
div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
div(class=`${prefix}-totals__item-amount`) #{subtotal}
if showTotal
div(class=`${prefix}-totals__item`)
div(class=`${prefix}-totals__item-label`) #{totalLabel}
div(class=`${prefix}-totals__item-amount`) #{total}

View File

@@ -1,77 +1,190 @@
extends ../PaperTemplateLayout.pug extends ../PaperTemplateLayout.pug
block head block head
style - var prefix = 'bc'
if (isRtl) style.
include ../../css/modules/receipt-rtl.css .#{prefix}-root {
else color: #111;
include ../../css/modules/receipt.css padding: 24px 30px;
font-size: 12px;
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-logo-wrap {
font-size: 60px;
margin: 0;
line-height: 1;
margin-bottom: 25px;
font-weight: 500;
color: #333;
}
.#{prefix}-big-title {
height: 120px;
width: 120px;
position: absolute;
right: 26px;
top: 26px;
overflow: hidden;
}
.#{prefix}-terms-list {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 24px;
}
.#{prefix}-terms-item {
display: flex;
flex-direction: row;
gap: 12px;
}
.#{prefix}-terms-item__label {
min-width: 120px;
color: #333;
}
.#{prefix}-terms-item__value {}
.#{prefix}-address-section {
box-sizing: border-box;
display: flex;
flex-flow: wrap;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: start;
justify-content: flex-start;
gap: 10px;
margin-bottom: 24px;
}
.#{prefix}-address {}
.#{prefix}-table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: inherit;
}
.#{prefix}-table__header {
font-weight: 400;
border-bottom: 1px solid #000;
padding: 2px 10px;
color: #333;
}
.#{prefix}-table__header:first-of-type{
padding-left: 0;
}
.#{prefix}-table__header:last-of-type{
padding-right: 0;
}
.#{prefix}-table__header--right {
text-align: right;
}
.#{prefix}-table__cell {
border-bottom: 1px solid #F6F6F6;
padding: 12px 10px;
}
.#{prefix}-table__cell:first-of-type{
padding-left: 0;
}
.#{prefix}-table__cell:last-of-type {
padding-right: 0;
}
.#{prefix}-table__cell--right {
text-align: right;
}
.#{prefix}-totals {
display: flex;
flex-direction: column;
margin-left: auto;
width: 300px;
margin-bottom: 24px;
}
.#{prefix}-totals__line {
display: flex;
padding: 4px 0;
}
.#{prefix}-totals__line--gray-border {}
.#{prefix}-totals__line--dark-border {}
.#{prefix}-totals__line__label {
min-width: 160px;
}
.#{prefix}-totals__line__amount {
flex: 1 1 auto;
text-align: right;
}
.#{prefix}-statement {
margin-bottom: 20px;
}
.#{prefix}-statement__label {}
.#{prefix}-statement__value {}
block content block content
div.receipt //- block head
div.receipt__header div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
div.paper
h1.title #{__("receipt.paper.receipt")}
span.receiptNumber #{saleReceipt.receiptNumber}
div.organization //- Title and company logo
h3.title #{organizationName} h1(class=`${prefix}-big-title`) Receipt
div.receipt__receipt-amount if showCompanyLogo
div.label #{__('receipt.paper.receipt_amount')} div(class=`${prefix}-logo-wrap`)
div.amount #{saleReceipt.formattedAmount} img(src=companyLogo alt=`Company Logo`)
div.receipt__meta //- Terms List
div.receipt__meta-item.receipt__meta-item--billed-to div(class=`${prefix}-terms-list`)
span.label #{__("receipt.paper.billed_to")} if showReceiptNumber
span.value #{saleReceipt.customer.displayName} div(class=`${prefix}-terms-item`)
span(class=`${prefix}-terms-item__label`)= receiptNumberLabel
span(class=`${prefix}-terms-item__value`)= receiptNumber
if showReceiptDate
div(class=`${prefix}-terms-item`)
span(class=`${prefix}-terms-item__label`)= receiptDateLabel
span(class=`${prefix}-terms-item__value`)= receiptDate
div.receipt__meta-item.receipt__meta-item--invoice-date //- Address Section
span.label #{__("receipt.paper.receipt_date")} div(class=`${prefix}-address-section`)
span.value #{saleReceipt.formattedReceiptDate} if showBilledFromAddress
div(class=`${prefix}-address`)
strong= companyName
each addressLine in billedFromAddress
div= addressLine
if saleReceipt.receiptNumber if showBilledToAddress
div.receipt__meta-item.receipt__meta-item--invoice-number div(class=`${prefix}-address`)
span.label #{__("receipt.paper.receipt_number")} strong= billedToLabel
span.value #{saleReceipt.receiptNumber} each addressLine in billedToAddress
div= addressLine
div.receipt__table //- Table Section
table table(class=`${prefix}-table`)
thead thead(class=`${prefix}-table__header`)
tr tr
th.item #{__("item_entry.paper.item_name")} th(class=`${prefix}-table__header`) Item
th.rate #{__("item_entry.paper.rate")} th(class=`${prefix}-table__header`) Description
th.quantity #{__("item_entry.paper.quantity")} th(class=`${prefix}-table__header ${prefix}-table__header--right`) Rate
th.total #{__("item_entry.paper.total")} th(class=`${prefix}-table__header ${prefix}-table__header--right`) Total
tbody tbody
each entry in saleReceipt.entries each line in lines
tr tr(class=`${prefix}-table__row`)
td.item=entry.item.name td(class=`${prefix}-table__column`)= line.item
td.rate=entry.rate td(class=`${prefix}-table__column`)= line.description
td.quantity=entry.quantity td(class=`${prefix}-table__column ${prefix}-table__column--right`)= line.rate
td.total=entry.amount td(class=`${prefix}-table__column ${prefix}-table__column--right`)= line.total
div.receipt__table-after //- Totals Section
div.receipt__table-total div(class=`${prefix}-totals`)
table if showSubtotal
tbody div(class=`${prefix}-totals__line #{prefix}-totals__line--gray-border`)
tr.total span(class=`${prefix}-totals__line__label`)= subtotalLabel
td #{__('receipt.paper.total')} span(class=`${prefix}-totals__line__amount`)= subtotal
td #{saleReceipt.formattedAmount} if showTotal
tr.payment-amount div(class=`${prefix}-totals__line #{prefix}-totals__line--dark-border`)
td #{__('receipt.paper.payment_amount')} span(class=`${prefix}-totals__line__label`)= totalLabel
td #{saleReceipt.formattedAmount} span(class=`${prefix}-totals__line__amount`)= total
tr.blanace-due
td #{__('receipt.paper.balance_due')}
td #{'$0'}
div.receipt__footer //- Customer Note Section
if saleReceipt.statement if showCustomerNote
div.receipt__conditions div(class=`${prefix}-statement`)
h3 #{__("receipt.paper.statement")} span(class=`${prefix}-statement__label`)= customerNoteLabel
p #{saleReceipt.statement} p(class=`${prefix}-statement__value`)= customerNote
if saleReceipt.receiptMessage //- Terms & Conditions Section
div.receipt__notes if showTermsConditions
h3 #{__("receipt.paper.notes")} div(class=`${prefix}-statement`)
p #{saleReceipt.receiptMessage} span(class=`${prefix}-statement__label`)= termsConditionsLabel
p(class=`${prefix}-statement__value`)= termsConditions

View File

@@ -258,3 +258,49 @@ export type ICreditNoteGLCommonEntry = Pick<
| 'debit' | 'debit'
| 'branchId' | 'branchId'
>; >;
export interface CreditNotePdfTemplateAttributes {
primaryColor: string;
secondaryColor: string;
showCompanyLogo: boolean;
companyLogo: string;
companyName: string;
billedToAddress: string[];
billedFromAddress: string[];
showBilledToAddress: boolean;
showBilledFromAddress: boolean;
billedToLabel: string;
total: string;
totalLabel: string;
showTotal: boolean;
subtotal: string;
subtotalLabel: string;
showSubtotal: boolean;
showCustomerNote: boolean;
customerNote: string;
customerNoteLabel: string;
showTermsConditions: boolean;
termsConditions: string;
termsConditionsLabel: string;
lines: Array<{
item: string;
description: string;
rate: string;
quantity: string;
total: string;
}>;
showCreditNoteNumber: boolean;
creditNoteNumberLabel: string;
creditNoteNumebr: string;
creditNoteDate: string;
showCreditNoteDate: boolean;
creditNoteDateLabel: string;
}

View File

@@ -25,6 +25,7 @@ export interface IPaymentReceived {
updatedAt: Date; updatedAt: Date;
localAmount?: number; localAmount?: number;
branchId?: number; branchId?: number;
pdfTemplateId?: number;
} }
export interface IPaymentReceivedCreateDTO { export interface IPaymentReceivedCreateDTO {
customerId: number; customerId: number;
@@ -185,3 +186,70 @@ export interface PaymentReceiveMailPresendEvent {
paymentReceiveId: number; paymentReceiveId: number;
messageOptions: PaymentReceiveMailOptsDTO; messageOptions: PaymentReceiveMailOptsDTO;
} }
export interface PaymentReceivedPdfLineItem {
item: string;
description: string;
rate: string;
quantity: string;
total: string;
}
export interface PaymentReceivedPdfTax {
label: string;
amount: string;
}
export interface PaymentReceivedPdfTemplateAttributes {
primaryColor: string;
secondaryColor: string;
companyName: string;
showCompanyLogo: boolean;
companyLogo: string;
dueDateLabel: string;
showDueDate: boolean;
dateIssueLabel: string;
showDateIssue: boolean;
invoiceNumberLabel: string;
showInvoiceNumber: boolean;
showBillingToAddress: boolean;
showBilledFromAddress: boolean;
billedToLabel: string;
lineItemLabel: string;
lineDescriptionLabel: string;
lineRateLabel: string;
lineTotalLabel: string;
totalLabel: string;
subtotalLabel: string;
discountLabel: string;
paymentMadeLabel: string;
balanceDueLabel: string;
showTotal: boolean;
showSubtotal: boolean;
showDiscount: boolean;
showTaxes: boolean;
showPaymentMade: boolean;
showDueAmount: boolean;
showBalanceDue: boolean;
discount: string;
termsConditionsLabel: string;
showTermsConditions: boolean;
lines: PaymentReceivedPdfLineItem[];
taxes: PaymentReceivedPdfTax[];
statementLabel: string;
showStatement: boolean;
billedToAddress: string[];
billedFromAddress: string[];
}

View File

@@ -143,3 +143,4 @@ export interface ISaleEstimateMailPresendEvent {
saleEstimateId: number; saleEstimateId: number;
messageOptions: SaleEstimateMailOptionsDTO; messageOptions: SaleEstimateMailOptionsDTO;
} }

View File

@@ -59,7 +59,7 @@ export default class CreateCreditNote extends BaseCreditNotes {
creditNoteDTO.entries creditNoteDTO.entries
); );
// Transformes the given DTO to storage layer data. // Transformes the given DTO to storage layer data.
const creditNoteModel = this.transformCreateEditDTOToModel( const creditNoteModel = await this.transformCreateEditDTOToModel(
tenantId, tenantId,
creditNoteDTO, creditNoteDTO,
customer.currencyCode customer.currencyCode

View File

@@ -0,0 +1,30 @@
import { Inject } from "typedi";
import { GetPdfTemplate } from "../PdfTemplate/GetPdfTemplate";
import { defaultCreditNoteBrandingAttributes } from "./constants";
import { mergePdfTemplateWithDefaultAttributes } from "../Sales/Invoices/utils";
export class CreditNoteBrandingTemplate {
@Inject()
private getPdfTemplateService: GetPdfTemplate;
/**
* Retrieves the credit note branding template.
* @param {number} tenantId
* @param {number} templateId
* @returns {}
*/
public async getCreditNoteBrandingTemplate(tenantId: number, templateId: number) {
const template = await this.getPdfTemplateService.getPdfTemplate(
tenantId,
templateId
);
const attributes = mergePdfTemplateWithDefaultAttributes(
template.attributes,
defaultCreditNoteBrandingAttributes
);
return {
...template,
attributes,
};
}
}

View File

@@ -2,6 +2,7 @@ import { Service, Inject } from 'typedi';
import moment from 'moment'; import moment from 'moment';
import { omit } from 'lodash'; import { omit } from 'lodash';
import * as R from 'ramda'; import * as R from 'ramda';
import composeAsync from 'async/compose';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import HasTenancyService from '@/services/Tenancy/TenancyService'; import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ERRORS } from './constants'; import { ERRORS } from './constants';
@@ -16,6 +17,7 @@ import AutoIncrementOrdersService from '@/services/Sales/AutoIncrementOrdersServ
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform'; import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform'; import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
import { assocItemEntriesDefaultIndex } from '../Items/utils'; import { assocItemEntriesDefaultIndex } from '../Items/utils';
import { BrandingTemplateDTOTransformer } from '../PdfTemplate/BrandingTemplateDTOTransformer';
@Service() @Service()
export default class BaseCreditNotes { export default class BaseCreditNotes {
@@ -34,17 +36,20 @@ export default class BaseCreditNotes {
@Inject() @Inject()
private warehouseDTOTransform: WarehouseTransactionDTOTransform; private warehouseDTOTransform: WarehouseTransactionDTOTransform;
@Inject()
private brandingTemplatesTransformer: BrandingTemplateDTOTransformer;
/** /**
* Transformes the credit/edit DTO to model. * Transformes the credit/edit DTO to model.
* @param {ICreditNoteNewDTO | ICreditNoteEditDTO} creditNoteDTO * @param {ICreditNoteNewDTO | ICreditNoteEditDTO} creditNoteDTO
* @param {string} customerCurrencyCode - * @param {string} customerCurrencyCode -
*/ */
protected transformCreateEditDTOToModel = ( protected transformCreateEditDTOToModel = async (
tenantId: number, tenantId: number,
creditNoteDTO: ICreditNoteNewDTO | ICreditNoteEditDTO, creditNoteDTO: ICreditNoteNewDTO | ICreditNoteEditDTO,
customerCurrencyCode: string, customerCurrencyCode: string,
oldCreditNote?: ICreditNote oldCreditNote?: ICreditNote
): ICreditNote => { ): Promise<ICreditNote> => {
// Retrieve the total amount of the given items entries. // Retrieve the total amount of the given items entries.
const amount = this.itemsEntriesService.getTotalItemsEntries( const amount = this.itemsEntriesService.getTotalItemsEntries(
creditNoteDTO.entries creditNoteDTO.entries
@@ -83,10 +88,18 @@ export default class BaseCreditNotes {
refundedAmount: 0, refundedAmount: 0,
invoicesAmount: 0, invoicesAmount: 0,
}; };
const initialAsyncDTO = await composeAsync(
// Assigns the default branding template id to the invoice DTO.
this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
tenantId,
'CreditNote'
)
)(initialDTO);
return R.compose( return R.compose(
this.branchDTOTransform.transformDTO<ICreditNote>(tenantId), this.branchDTOTransform.transformDTO<ICreditNote>(tenantId),
this.warehouseDTOTransform.transformDTO<ICreditNote>(tenantId) this.warehouseDTOTransform.transformDTO<ICreditNote>(tenantId)
)(initialDTO); )(initialAsyncDTO);
}; };
/** /**

View File

@@ -63,7 +63,7 @@ export default class EditCreditNote extends BaseCreditNotes {
creditNoteEditDTO.entries creditNoteEditDTO.entries
); );
// Transformes the given DTO to storage layer data. // Transformes the given DTO to storage layer data.
const creditNoteModel = this.transformCreateEditDTOToModel( const creditNoteModel = await this.transformCreateEditDTOToModel(
tenantId, tenantId,
creditNoteEditDTO, creditNoteEditDTO,
customer.currencyCode, customer.currencyCode,

View File

@@ -2,9 +2,16 @@ import { Inject, Service } from 'typedi';
import { ChromiumlyTenancy } from '../ChromiumlyTenancy/ChromiumlyTenancy'; import { ChromiumlyTenancy } from '../ChromiumlyTenancy/ChromiumlyTenancy';
import { TemplateInjectable } from '../TemplateInjectable/TemplateInjectable'; import { TemplateInjectable } from '../TemplateInjectable/TemplateInjectable';
import GetCreditNote from './GetCreditNote'; import GetCreditNote from './GetCreditNote';
import { CreditNoteBrandingTemplate } from './CreditNoteBrandingTemplate';
import { CreditNotePdfTemplateAttributes } from '@/interfaces';
import HasTenancyService from '../Tenancy/TenancyService';
import { transformCreditNoteToPdfTemplate } from './utils';
@Service() @Service()
export default class GetCreditNotePdf { export default class GetCreditNotePdf {
@Inject()
private tenancy: HasTenancyService;
@Inject() @Inject()
private chromiumlyTenancy: ChromiumlyTenancy; private chromiumlyTenancy: ChromiumlyTenancy;
@@ -14,25 +21,62 @@ export default class GetCreditNotePdf {
@Inject() @Inject()
private getCreditNoteService: GetCreditNote; private getCreditNoteService: GetCreditNote;
@Inject()
private creditNoteBrandingTemplate: CreditNoteBrandingTemplate;
/** /**
* Retrieve sale invoice pdf content. * Retrieves sale invoice pdf content.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {number} creditNoteId - Credit note id. * @param {number} creditNoteId - Credit note id.
*/ */
public async getCreditNotePdf(tenantId: number, creditNoteId: number) { public async getCreditNotePdf(tenantId: number, creditNoteId: number) {
const brandingAttributes = await this.getCreditNoteBrandingAttributes(
tenantId,
creditNoteId
);
console.log(brandingAttributes, 'brandingAttributes');
const htmlContent = await this.templateInjectable.render(
tenantId,
'modules/credit-note-standard',
brandingAttributes
);
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
}
/**
* Retrieves credit note branding attributes.
* @param {number} tenantId - The ID of the tenant.
* @param {number} creditNoteId - The ID of the credit note.
* @returns {Promise<CreditNotePdfTemplateAttributes>} The credit note branding attributes.
*/
public async getCreditNoteBrandingAttributes(
tenantId: number,
creditNoteId: number
): Promise<CreditNotePdfTemplateAttributes> {
const { PdfTemplate } = this.tenancy.models(tenantId);
const creditNote = await this.getCreditNoteService.getCreditNote( const creditNote = await this.getCreditNoteService.getCreditNote(
tenantId, tenantId,
creditNoteId creditNoteId
); );
const htmlContent = await this.templateInjectable.render( // Retrieve the invoice template id of not found get the default template id.
tenantId, const templateId =
'modules/credit-note-standard', creditNote.pdfTemplateId ??
{ (
creditNote, await PdfTemplate.query().findOne({
} resource: 'CreditNote',
); default: true,
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, { })
margins: { top: 0, bottom: 0, left: 0, right: 0 }, )?.id;
}); // Retrieves the credit note branding template.
const brandingTemplate =
await this.creditNoteBrandingTemplate.getCreditNoteBrandingTemplate(
tenantId,
templateId
);
return {
...brandingTemplate.attributes,
...transformCreditNoteToPdfTemplate(creditNote),
};
} }
} }

View File

@@ -9,7 +9,7 @@ export const ERRORS = {
'CREDIT_NOTE_APPLY_TO_INVOICES_NOT_FOUND', 'CREDIT_NOTE_APPLY_TO_INVOICES_NOT_FOUND',
CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS: 'CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS', CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS: 'CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS',
CREDIT_NOTE_HAS_APPLIED_INVOICES: 'CREDIT_NOTE_HAS_APPLIED_INVOICES', CREDIT_NOTE_HAS_APPLIED_INVOICES: 'CREDIT_NOTE_HAS_APPLIED_INVOICES',
CUSTOMER_HAS_LINKED_CREDIT_NOTES: 'CUSTOMER_HAS_LINKED_CREDIT_NOTES' CUSTOMER_HAS_LINKED_CREDIT_NOTES: 'CUSTOMER_HAS_LINKED_CREDIT_NOTES',
}; };
export const DEFAULT_VIEW_COLUMNS = []; export const DEFAULT_VIEW_COLUMNS = [];
@@ -66,3 +66,72 @@ export const DEFAULT_VIEWS = [
columns: DEFAULT_VIEW_COLUMNS, columns: DEFAULT_VIEW_COLUMNS,
}, },
]; ];
export const defaultCreditNoteBrandingAttributes = {
primaryColor: '',
secondaryColor: '',
showCompanyLogo: true,
companyLogo: '',
companyName: 'Bigcapital Technology, Inc.',
// Address
billedToAddress: [
'Bigcapital Technology, Inc.',
'131 Continental Dr Suite 305 Newark,',
'Delaware 19713',
'United States',
'+1 762-339-5634',
'ahmed@bigcapital.app',
],
billedFromAddress: [
'131 Continental Dr Suite 305 Newark,',
'Delaware 19713',
'United States',
'+1 762-339-5634',
'ahmed@bigcapital.app',
],
showBilledToAddress: true,
showBilledFromAddress: true,
billedToLabel: 'Billed To',
// Total
total: '$1000.00',
totalLabel: 'Total',
showTotal: true,
// Subtotal
subtotal: '1000/00',
subtotalLabel: 'Subtotal',
showSubtotal: true,
// Customer note
showCustomerNote: true,
customerNote:
'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.',
customerNoteLabel: 'Customer Note',
// Terms & conditions
showTermsConditions: true,
termsConditions:
'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.',
termsConditionsLabel: 'Terms & Conditions',
lines: [
{
item: 'Simply dummy text',
description: 'Simply dummy text of the printing and typesetting',
rate: '1',
quantity: '1000',
total: '$1000.00',
},
],
// Credit note number.
showCreditNoteNumber: true,
creditNoteNumberLabel: 'Credit Note Number',
creditNoteNumebr: '346D3D40-0001',
// Credit note date.
creditNoteDate: 'September 3, 2024',
showCreditNoteDate: true,
creditNoteDateLabel: 'Credit Note Date',
};

View File

@@ -0,0 +1,9 @@
import { CreditNotePdfTemplateAttributes } from "@/interfaces";
import CreditNote from "@/models/CreditNote";
export const transformCreditNoteToPdfTemplate = (creditNote: CreditNote): Partial<CreditNotePdfTemplateAttributes> {
return {
};
}

View File

@@ -1,6 +1,7 @@
import * as R from 'ramda'; import * as R from 'ramda';
import HasTenancyService from '../Tenancy/TenancyService'; import HasTenancyService from '../Tenancy/TenancyService';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { isEmpty } from 'lodash';
@Service() @Service()
export class BrandingTemplateDTOTransformer { export class BrandingTemplateDTOTransformer {
@@ -15,25 +16,22 @@ export class BrandingTemplateDTOTransformer {
* @param {string} attributeName * @param {string} attributeName
* @returns * @returns
*/ */
public assocDefaultBrandingTemplate = ( public assocDefaultBrandingTemplate =
tenantId: number, (tenantId: number, resource: string) =>
resource: string, async (object: Record<string, any>) => {
) => async (object: Record<string, any>) => { const { PdfTemplate } = this.tenancy.models(tenantId);
const { PdfTemplate } = this.tenancy.models(tenantId); const attributeName = 'pdfTemplateId';
const attributeName = 'pdfTemplateId';
const defaultTemplate = await PdfTemplate.query().findOne({ const defaultTemplate = await PdfTemplate.query().findOne({
resource, resource,
default: true, default: true,
}); });
console.log(defaultTemplate); if (!defaultTemplate || !isEmpty(object[attributeName])) {
return object;
if (!defaultTemplate) { }
return object; return {
} ...object,
return { [attributeName]: defaultTemplate.id,
...object, };
[attributeName]: defaultTemplate.id,
}; };
},
} }

View File

@@ -1,6 +1,7 @@
import * as R from 'ramda'; import * as R from 'ramda';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { omit, sumBy } from 'lodash'; import { omit, sumBy } from 'lodash';
import composeAsync from 'async/compose';
import HasTenancyService from '@/services/Tenancy/TenancyService'; import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ICustomer, ISaleEstimate, ISaleEstimateDTO } from '@/interfaces'; import { ICustomer, ISaleEstimate, ISaleEstimateDTO } from '@/interfaces';
import { SaleEstimateValidators } from './SaleEstimateValidators'; import { SaleEstimateValidators } from './SaleEstimateValidators';
@@ -10,6 +11,7 @@ import { formatDateFields } from '@/utils';
import moment from 'moment'; import moment from 'moment';
import { SaleEstimateIncrement } from './SaleEstimateIncrement'; import { SaleEstimateIncrement } from './SaleEstimateIncrement';
import { assocItemEntriesDefaultIndex } from '@/services/Items/utils'; import { assocItemEntriesDefaultIndex } from '@/services/Items/utils';
import { BrandingTemplateDTOTransformer } from '@/services/PdfTemplate/BrandingTemplateDTOTransformer';
@Service() @Service()
export class SaleEstimateDTOTransformer { export class SaleEstimateDTOTransformer {
@@ -28,6 +30,9 @@ export class SaleEstimateDTOTransformer {
@Inject() @Inject()
private estimateIncrement: SaleEstimateIncrement; private estimateIncrement: SaleEstimateIncrement;
@Inject()
private brandingTemplatesTransformer: BrandingTemplateDTOTransformer;
/** /**
* Transform create DTO object ot model object. * Transform create DTO object ot model object.
* @param {number} tenantId * @param {number} tenantId
@@ -81,10 +86,18 @@ export class SaleEstimateDTOTransformer {
deliveredAt: moment().toMySqlDateTime(), deliveredAt: moment().toMySqlDateTime(),
}), }),
}; };
const initialAsyncDTO = await composeAsync(
// Assigns the default branding template id to the invoice DTO.
this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
tenantId,
'SaleEstimate'
)
)(initialDTO);
return R.compose( return R.compose(
this.branchDTOTransform.transformDTO<ISaleEstimate>(tenantId), this.branchDTOTransform.transformDTO<ISaleEstimate>(tenantId),
this.warehouseDTOTransform.transformDTO<ISaleEstimate>(tenantId) this.warehouseDTOTransform.transformDTO<ISaleEstimate>(tenantId)
)(initialDTO); )(initialAsyncDTO);
} }
/** /**

View File

@@ -2,9 +2,16 @@ import { Inject, Service } from 'typedi';
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy'; import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable'; import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
import { GetSaleEstimate } from './GetSaleEstimate'; import { GetSaleEstimate } from './GetSaleEstimate';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { SaleEstimatePdfTemplate } from '../Invoices/SaleEstimatePdfTemplate';
import { transformEstimateToPdfTemplate } from './utils';
import { EstimatePdfBrandingAttributes } from './constants';
@Service() @Service()
export class SaleEstimatesPdf { export class SaleEstimatesPdf {
@Inject()
private tenancy: HasTenancyService;
@Inject() @Inject()
private chromiumlyTenancy: ChromiumlyTenancy; private chromiumlyTenancy: ChromiumlyTenancy;
@@ -14,25 +21,58 @@ export class SaleEstimatesPdf {
@Inject() @Inject()
private getSaleEstimate: GetSaleEstimate; private getSaleEstimate: GetSaleEstimate;
@Inject()
private estimatePdfTemplate: SaleEstimatePdfTemplate;
/** /**
* Retrieve sale invoice pdf content. * Retrieve sale invoice pdf content.
* @param {number} tenantId - * @param {number} tenantId -
* @param {ISaleInvoice} saleInvoice - * @param {ISaleInvoice} saleInvoice -
*/ */
public async getSaleEstimatePdf(tenantId: number, saleEstimateId: number) { public async getSaleEstimatePdf(tenantId: number, saleEstimateId: number) {
const saleEstimate = await this.getSaleEstimate.getEstimate( const brandingAttributes = await this.getEstimateBrandingAttributes(
tenantId, tenantId,
saleEstimateId saleEstimateId
); );
const htmlContent = await this.templateInjectable.render( const htmlContent = await this.templateInjectable.render(
tenantId, tenantId,
'modules/estimate-regular', 'modules/estimate-regular',
{ brandingAttributes
saleEstimate,
}
); );
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, { return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
margins: { top: 0, bottom: 0, left: 0, right: 0 }, }
});
/**
*
* @param {number} tenantId
* @param {number} estimateId
*/
async getEstimateBrandingAttributes(
tenantId: number,
estimateId: number
): Promise<EstimatePdfBrandingAttributes> {
const { PdfTemplate } = this.tenancy.models(tenantId);
const saleEstimate = await this.getSaleEstimate.getEstimate(
tenantId,
estimateId
);
// Retrieve the invoice template id of not found get the default template id.
const templateId =
saleEstimate.pdfTemplateId ??
(
await PdfTemplate.query().findOne({
resource: 'SaleEstimate',
default: true,
})
)?.id;
const brandingTemplate =
await this.estimatePdfTemplate.getEstimatePdfTemplate(
tenantId,
templateId
);
return {
...brandingTemplate.attributes,
...transformEstimateToPdfTemplate(saleEstimate),
};
} }
} }

View File

@@ -173,3 +173,122 @@ export const SaleEstimatesSampleData = [
'Line Description': 'Qui suscipit ducimus qui qui.', 'Line Description': 'Qui suscipit ducimus qui qui.',
}, },
]; ];
export const defaultEstimatePdfBrandingAttributes = {
primaryColor: '#000',
secondaryColor: '#000',
showCompanyLogo: true,
companyLogo: '',
companyName: '',
billedToAddress: [
'Bigcapital Technology, Inc.',
'131 Continental Dr Suite 305 Newark,',
'Delaware 19713',
'United States',
'+1 762-339-5634',
'ahmed@bigcapital.app',
],
billedFromAddress: [
'131 Continental Dr Suite 305 Newark,',
'Delaware 19713',
'United States',
'+1 762-339-5634',
'ahmed@bigcapital.app',
],
showBilledFromAddress: true,
showBilledToAddress: true,
billedToLabel: 'Billed To',
total: '$1000.00',
totalLabel: 'Total',
showTotal: true,
subtotal: '1000/00',
subtotalLabel: 'Subtotal',
showSubtotal: true,
showCustomerNote: true,
customerNote:
'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.',
customerNoteLabel: 'Customer Note',
showTermsConditions: true,
termsConditions:
'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.',
termsConditionsLabel: 'Terms & Conditions',
lines: [
{
item: 'Simply dummy text',
description: 'Simply dummy text of the printing and typesetting',
rate: '1',
quantity: '1000',
total: '$1000.00',
},
],
showEstimateNumber: true,
estimateNumberLabel: 'Estimate Number',
estimateNumebr: '346D3D40-0001',
estimateDate: 'September 3, 2024',
showEstimateDate: true,
estimateDateLabel: 'Estimate Date',
expirationDateLabel: 'Expiration Date',
showExpirationDate: true,
expirationDate: 'September 3, 2024',
};
interface EstimatePdfBrandingLineItem {
item: string;
description: string;
rate: string;
quantity: string;
total: string;
}
export interface EstimatePdfBrandingAttributes {
primaryColor: string;
secondaryColor: string;
showCompanyLogo: boolean;
companyLogo: string;
companyName: string;
billedToAddress: string[];
billedFromAddress: string[];
showBilledFromAddress: boolean;
showBilledToAddress: boolean;
billedToLabel: string;
total: string;
totalLabel: string;
showTotal: boolean;
subtotal: string;
subtotalLabel: string;
showSubtotal: boolean;
showCustomerNote: boolean;
customerNote: string;
customerNoteLabel: string;
showTermsConditions: boolean;
termsConditions: string;
termsConditionsLabel: string;
lines: EstimatePdfBrandingLineItem[];
showEstimateNumber: boolean;
estimateNumberLabel: string;
estimateNumebr: string;
estimateDate: string;
showEstimateDate: boolean;
estimateDateLabel: string;
expirationDateLabel: string;
showExpirationDate: boolean;
expirationDate: string;
}

View File

@@ -0,0 +1,7 @@
import { EstimatePdfBrandingAttributes } from './constants';
export const transformEstimateToPdfTemplate = (
estimate
): Partial<EstimatePdfBrandingAttributes> => {
return {};
};

View File

@@ -0,0 +1,31 @@
import { Inject, Service } from 'typedi';
import { mergePdfTemplateWithDefaultAttributes } from './utils';
import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
import { defaultEstimatePdfBrandingAttributes } from '../Estimates/constants';
@Service()
export class SaleEstimatePdfTemplate {
@Inject()
private getPdfTemplateService: GetPdfTemplate;
/**
* Retrieves the estimate pdf template.
* @param {number} tenantId
* @param {number} invoiceTemplateId
* @returns
*/
async getEstimatePdfTemplate(tenantId: number, estimateTemplateId: number) {
const template = await this.getPdfTemplateService.getPdfTemplate(
tenantId,
estimateTemplateId
);
const attributes = mergePdfTemplateWithDefaultAttributes(
template.attributes,
defaultEstimatePdfBrandingAttributes
);
return {
...template,
attributes,
};
}
}

View File

@@ -2,9 +2,16 @@ import { Inject, Service } from 'typedi';
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy'; import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable'; import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
import { GetPaymentReceived } from './GetPaymentReceived'; import { GetPaymentReceived } from './GetPaymentReceived';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { PaymentReceivedBrandingTemplate } from './PaymentReceivedBrandingTemplate';
import { transformPaymentReceivedToPdfTemplate } from './utils';
import { PaymentReceivedPdfTemplateAttributes } from '@/interfaces';
@Service() @Service()
export default class GetPaymentReceivedPdf { export default class GetPaymentReceivedPdf {
@Inject()
private tenancy: HasTenancyService;
@Inject() @Inject()
private chromiumlyTenancy: ChromiumlyTenancy; private chromiumlyTenancy: ChromiumlyTenancy;
@@ -14,6 +21,9 @@ export default class GetPaymentReceivedPdf {
@Inject() @Inject()
private getPaymentService: GetPaymentReceived; private getPaymentService: GetPaymentReceived;
@Inject()
private paymentBrandingTemplateService: PaymentReceivedBrandingTemplate;
/** /**
* Retrieve sale invoice pdf content. * Retrieve sale invoice pdf content.
* @param {number} tenantId - * @param {number} tenantId -
@@ -24,19 +34,52 @@ export default class GetPaymentReceivedPdf {
tenantId: number, tenantId: number,
paymentReceiveId: number paymentReceiveId: number
): Promise<Buffer> { ): Promise<Buffer> {
const paymentReceive = await this.getPaymentService.getPaymentReceive( const brandingAttributes = await this.getPaymentBrandingAttributes(
tenantId, tenantId,
paymentReceiveId paymentReceiveId
); );
const htmlContent = await this.templateInjectable.render( const htmlContent = await this.templateInjectable.render(
tenantId, tenantId,
'modules/payment-receive-standard', 'modules/payment-receive-standard',
{ brandingAttributes
paymentReceive,
}
); );
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, { // Converts the given html content to pdf document.
margins: { top: 0, bottom: 0, left: 0, right: 0 }, return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
}); }
/**
* Retrieves the given payment received branding attributes.
* @param {number} tenantId
* @param {number} paymentReceivedId
* @returns {Promise<PaymentReceivedPdfTemplateAttributes>}
*/
async getPaymentBrandingAttributes(
tenantId: number,
paymentReceivedId: number
): Promise<PaymentReceivedPdfTemplateAttributes> {
const { PdfTemplate } = this.tenancy.models(tenantId);
const paymentReceived = await this.getPaymentService.getPaymentReceive(
tenantId,
paymentReceivedId
);
const templateId =
paymentReceived?.pdfTemplateId ??
(
await PdfTemplate.query().findOne({
resource: 'PaymentReceive',
default: true,
})
)?.id;
const brandingTemplate =
await this.paymentBrandingTemplateService.getPaymentReceivedPdfTemplate(
tenantId,
templateId
);
return {
...brandingTemplate.attributes,
...transformPaymentReceivedToPdfTemplate(paymentReceived),
};
} }
} }

View File

@@ -0,0 +1,35 @@
import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
import { Inject, Service } from 'typedi';
import { mergePdfTemplateWithDefaultAttributes } from '../Invoices/utils';
import { defaultPaymentReceivedPdfTemplateAttributes } from './constants';
import { PdfTemplate } from '@/models/PdfTemplate';
@Service()
export class PaymentReceivedBrandingTemplate {
@Inject()
private getPdfTemplateService: GetPdfTemplate;
/**
* Retrieves the payment received pdf template.
* @param {number} tenantId
* @param {number} paymentTemplateId
* @returns
*/
public async getPaymentReceivedPdfTemplate(
tenantId: number,
paymentTemplateId: number
): Promise<PdfTemplate> {
const template = await this.getPdfTemplateService.getPdfTemplate(
tenantId,
paymentTemplateId
);
const attributes = mergePdfTemplateWithDefaultAttributes(
template.attributes,
defaultPaymentReceivedPdfTemplateAttributes
);
return {
...template,
attributes,
};
}
}

View File

@@ -1,6 +1,7 @@
import * as R from 'ramda'; import * as R from 'ramda';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { omit, sumBy } from 'lodash'; import { omit, sumBy } from 'lodash';
import composeAsync from 'async/compose';
import { import {
ICustomer, ICustomer,
IPaymentReceived, IPaymentReceived,
@@ -12,6 +13,7 @@ import { PaymentReceivedIncrement } from './PaymentReceivedIncrement';
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform'; import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
import { formatDateFields } from '@/utils'; import { formatDateFields } from '@/utils';
import { assocItemEntriesDefaultIndex } from '@/services/Items/utils'; import { assocItemEntriesDefaultIndex } from '@/services/Items/utils';
import { BrandingTemplateDTOTransformer } from '@/services/PdfTemplate/BrandingTemplateDTOTransformer';
@Service() @Service()
export class PaymentReceiveDTOTransformer { export class PaymentReceiveDTOTransformer {
@@ -24,6 +26,9 @@ export class PaymentReceiveDTOTransformer {
@Inject() @Inject()
private branchDTOTransform: BranchTransactionDTOTransform; private branchDTOTransform: BranchTransactionDTOTransform;
@Inject()
private brandingTemplatesTransformer: BrandingTemplateDTOTransformer;
/** /**
* Transformes the create payment receive DTO to model object. * Transformes the create payment receive DTO to model object.
* @param {number} tenantId * @param {number} tenantId
@@ -68,8 +73,16 @@ export class PaymentReceiveDTOTransformer {
exchangeRate: paymentReceiveDTO.exchangeRate || 1, exchangeRate: paymentReceiveDTO.exchangeRate || 1,
entries, entries,
}; };
const initialAsyncDTO = await composeAsync(
// Assigns the default branding template id to the invoice DTO.
this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
tenantId,
'SaleInvoice'
)
)(initialDTO);
return R.compose( return R.compose(
this.branchDTOTransform.transformDTO<IPaymentReceived>(tenantId) this.branchDTOTransform.transformDTO<IPaymentReceived>(tenantId)
)(initialDTO); )(initialAsyncDTO);
} }
} }

View File

@@ -45,3 +45,53 @@ export const PaymentsReceiveSampleData = [
'Payment Amount': 850, 'Payment Amount': 850,
}, },
]; ];
export const defaultPaymentReceivedPdfTemplateAttributes = {
primaryColor: '#000',
secondaryColor: '#000',
showCompanyLogo: true,
companyLogo: '',
companyName: 'Bigcapital Technology, Inc.',
billedToAddress: [
'Bigcapital Technology, Inc.',
'131 Continental Dr Suite 305 Newark,',
'Delaware 19713',
'United States',
'+1 762-339-5634',
'ahmed@bigcapital.app',
],
billedFromAddress: [
'131 Continental Dr Suite 305 Newark,',
'Delaware 19713',
'United States',
'+1 762-339-5634',
'ahmed@bigcapital.app',
],
showBilledFromAddress: true,
showBillingToAddress: true,
billedToLabel: 'Billed To',
total: '$1000.00',
totalLabel: 'Total',
showTotal: true,
subtotal: '1000/00',
subtotalLabel: 'Subtotal',
showSubtotal: true,
lines: [
{
invoiceNumber: 'INV-00001',
invoiceAmount: '$1000.00',
paidAmount: '$1000.00',
},
],
showPaymentReceivedNumber: true,
paymentReceivedNumberLabel: 'Payment Number',
paymentReceivedNumebr: '346D3D40-0001',
paymentReceivedDate: 'September 3, 2024',
showPaymentReceivedDate: true,
paymentReceivedDateLabel: 'Payment Date',
};

View File

@@ -0,0 +1,12 @@
import {
IPaymentReceived,
PaymentReceivedPdfTemplateAttributes,
} from '@/interfaces';
export const transformPaymentReceivedToPdfTemplate = (
payment: IPaymentReceived
): Partial<PaymentReceivedPdfTemplateAttributes> => {
return {
// ...payment
};
};

View File

@@ -0,0 +1,35 @@
import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
import { Inject, Service } from 'typedi';
import { defaultSaleReceiptBrandingAttributes } from './constants';
import { mergePdfTemplateWithDefaultAttributes } from '../Invoices/utils';
@Service()
export class SaleReceiptBrandingTemplate {
@Inject()
private getPdfTemplateService: GetPdfTemplate;
/**
* Retrieves the sale receipt branding template.
* @param {number} tenantId - The ID of the tenant.
* @param {number} templateId - The ID of the PDF template.
* @returns {Promise<Object>} The sale receipt branding template with merged attributes.
*/
public async getSaleReceiptBrandingTemplate(
tenantId: number,
templateId: number
) {
const template = await this.getPdfTemplateService.getPdfTemplate(
tenantId,
templateId
);
const attributes = mergePdfTemplateWithDefaultAttributes(
template.attributes,
defaultSaleReceiptBrandingAttributes
);
return {
...template,
attributes,
};
}
}

View File

@@ -12,6 +12,7 @@ import { formatDateFields } from '@/utils';
import { SaleReceiptIncrement } from './SaleReceiptIncrement'; import { SaleReceiptIncrement } from './SaleReceiptIncrement';
import { ItemEntry } from '@/models'; import { ItemEntry } from '@/models';
import { assocItemEntriesDefaultIndex } from '@/services/Items/utils'; import { assocItemEntriesDefaultIndex } from '@/services/Items/utils';
import { BrandingTemplateDTOTransformer } from '@/services/PdfTemplate/BrandingTemplateDTOTransformer';
@Service() @Service()
export class SaleReceiptDTOTransformer { export class SaleReceiptDTOTransformer {
@@ -30,6 +31,9 @@ export class SaleReceiptDTOTransformer {
@Inject() @Inject()
private receiptIncrement: SaleReceiptIncrement; private receiptIncrement: SaleReceiptIncrement;
@Inject()
private brandingTemplatesTransformer: BrandingTemplateDTOTransformer;
/** /**
* Transform create DTO object to model object. * Transform create DTO object to model object.
* @param {ISaleReceiptDTO} saleReceiptDTO - * @param {ISaleReceiptDTO} saleReceiptDTO -
@@ -88,9 +92,17 @@ export class SaleReceiptDTOTransformer {
}), }),
entries, entries,
}; };
const initialAsyncDTO = await composeAsync(
// Assigns the default branding template id to the invoice DTO.
this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
tenantId,
'SaleReceipt'
)
)(initialDTO);
return R.compose( return R.compose(
this.branchDTOTransform.transformDTO<ISaleReceipt>(tenantId), this.branchDTOTransform.transformDTO<ISaleReceipt>(tenantId),
this.warehouseDTOTransform.transformDTO<ISaleReceipt>(tenantId) this.warehouseDTOTransform.transformDTO<ISaleReceipt>(tenantId)
)(initialDTO); )(initialAsyncDTO);
} }
} }

View File

@@ -2,9 +2,15 @@ import { Inject, Service } from 'typedi';
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable'; import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy'; import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
import { GetSaleReceipt } from './GetSaleReceipt'; import { GetSaleReceipt } from './GetSaleReceipt';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { SaleReceiptBrandingTemplate } from './SaleReceiptBrandingTemplate';
import { transformReceiptToBrandingTemplateAttributes } from './utils';
@Service() @Service()
export class SaleReceiptsPdf { export class SaleReceiptsPdf {
@Inject()
private tenancy: HasTenancyService;
@Inject() @Inject()
private chromiumlyTenancy: ChromiumlyTenancy; private chromiumlyTenancy: ChromiumlyTenancy;
@@ -14,6 +20,9 @@ export class SaleReceiptsPdf {
@Inject() @Inject()
private getSaleReceiptService: GetSaleReceipt; private getSaleReceiptService: GetSaleReceipt;
@Inject()
private saleReceiptBrandingTemplate: SaleReceiptBrandingTemplate;
/** /**
* Retrieves sale invoice pdf content. * Retrieves sale invoice pdf content.
* @param {number} tenantId - * @param {number} tenantId -
@@ -21,19 +30,54 @@ export class SaleReceiptsPdf {
* @returns {Promise<Buffer>} * @returns {Promise<Buffer>}
*/ */
public async saleReceiptPdf(tenantId: number, saleReceiptId: number) { public async saleReceiptPdf(tenantId: number, saleReceiptId: number) {
const saleReceipt = await this.getSaleReceiptService.getSaleReceipt( const brandingAttributes = await this.getReceiptBrandingAttributes(
tenantId, tenantId,
saleReceiptId saleReceiptId
); );
console.log(brandingAttributes, 'attributes');
const htmlContent = await this.templateInjectable.render( const htmlContent = await this.templateInjectable.render(
tenantId, tenantId,
'modules/receipt-regular', 'modules/receipt-regular',
{ brandingAttributes
saleReceipt,
}
); );
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, { return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
margins: { top: 0, bottom: 0, left: 0, right: 0 }, }
});
/**
* Retrieves receipt branding attributes.
* @param {number} tenantId
* @param {number] receiptId
* @returns
*/
public async getReceiptBrandingAttributes(
tenantId: number,
receiptId: number
) {
const { PdfTemplate } = this.tenancy.models(tenantId);
const saleReceipt = await this.getSaleReceiptService.getSaleReceipt(
tenantId,
receiptId
);
// Retrieve the invoice template id of not found get the default template id.
const templateId =
saleReceipt.pdfTemplateId ??
(
await PdfTemplate.query().findOne({
resource: 'SaleReceipt',
default: true,
})
)?.id;
// Retrieves the receipt branding template.
const brandingTemplate =
await this.saleReceiptBrandingTemplate.getSaleReceiptBrandingTemplate(
tenantId,
templateId
);
return {
...brandingTemplate.attributes,
...transformReceiptToBrandingTemplateAttributes(saleReceipt),
};
} }
} }

View File

@@ -22,7 +22,7 @@ export const ERRORS = {
SALE_RECEIPT_IS_ALREADY_CLOSED: 'SALE_RECEIPT_IS_ALREADY_CLOSED', SALE_RECEIPT_IS_ALREADY_CLOSED: 'SALE_RECEIPT_IS_ALREADY_CLOSED',
SALE_RECEIPT_NO_IS_REQUIRED: 'SALE_RECEIPT_NO_IS_REQUIRED', SALE_RECEIPT_NO_IS_REQUIRED: 'SALE_RECEIPT_NO_IS_REQUIRED',
CUSTOMER_HAS_SALES_INVOICES: 'CUSTOMER_HAS_SALES_INVOICES', CUSTOMER_HAS_SALES_INVOICES: 'CUSTOMER_HAS_SALES_INVOICES',
NO_INVOICE_CUSTOMER_EMAIL_ADDR: 'NO_INVOICE_CUSTOMER_EMAIL_ADDR' NO_INVOICE_CUSTOMER_EMAIL_ADDR: 'NO_INVOICE_CUSTOMER_EMAIL_ADDR',
}; };
export const DEFAULT_VIEW_COLUMNS = []; export const DEFAULT_VIEW_COLUMNS = [];
@@ -47,22 +47,84 @@ export const DEFAULT_VIEWS = [
}, },
]; ];
export const SaleReceiptsSampleData = [ export const SaleReceiptsSampleData = [
{ {
"Receipt Date": "2023-01-01", 'Receipt Date': '2023-01-01',
"Customer": "Randall Kohler", Customer: 'Randall Kohler',
"Deposit Account": "Petty Cash", 'Deposit Account': 'Petty Cash',
"Exchange Rate": "", 'Exchange Rate': '',
"Receipt Number": "REC-00001", 'Receipt Number': 'REC-00001',
"Reference No.": "REF-0001", 'Reference No.': 'REF-0001',
"Statement": "Delectus unde aut soluta et accusamus placeat.", Statement: 'Delectus unde aut soluta et accusamus placeat.',
"Receipt Message": "Vitae asperiores dicta.", 'Receipt Message': 'Vitae asperiores dicta.',
"Closed": "T", Closed: 'T',
"Item": "Schmitt Group", Item: 'Schmitt Group',
"Quantity": 100, Quantity: 100,
"Rate": 200, Rate: 200,
"Line Description": "Distinctio distinctio sit veritatis consequatur iste quod veritatis." 'Line Description':
} 'Distinctio distinctio sit veritatis consequatur iste quod veritatis.',
},
];
] export const defaultSaleReceiptBrandingAttributes = {
primaryColor: '',
secondaryColor: '',
showCompanyLogo: true,
companyLogo: '',
companyName: 'Bigcapital Technology, Inc.',
// # Address
billedToAddress: [
'Bigcapital Technology, Inc.',
'131 Continental Dr Suite 305 Newark,',
'Delaware 19713',
'United States',
'+1 762-339-5634',
'ahmed@bigcapital.app',
],
billedFromAddress: [
'131 Continental Dr Suite 305 Newark,',
'Delaware 19713',
'United States',
'+1 762-339-5634',
'ahmed@bigcapital.app',
],
showBilledFromAddress: true,
showBilledToAddress: true,
billedToLabel: 'Billed To',
total: '$1000.00',
totalLabel: 'Total',
showTotal: true,
subtotal: '1000/00',
subtotalLabel: 'Subtotal',
showSubtotal: true,
showCustomerNote: true,
customerNote:
'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.',
customerNoteLabel: 'Customer Note',
showTermsConditions: true,
termsConditions:
'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.',
termsConditionsLabel: 'Terms & Conditions',
lines: [
{
item: 'Simply dummy text',
description: 'Simply dummy text of the printing and typesetting',
rate: '1',
quantity: '1000',
total: '$1000.00',
},
],
showReceiptNumber: true,
receiptNumberLabel: 'Receipt Number',
receiptNumebr: '346D3D40-0001',
receiptDate: 'September 3, 2024',
showReceiptDate: true,
receiptDateLabel: 'Receipt Date',
};

View File

@@ -0,0 +1,6 @@
export const transformReceiptToBrandingTemplateAttributes = () => {
return {};
}

View File

@@ -12,10 +12,15 @@ import {
Menu, Menu,
MenuItem, MenuItem,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { If, Icon, FormattedMessage as T, Group } from '@/components'; import { If, Icon, FormattedMessage as T, Group, FSelect } from '@/components';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import classNames from 'classnames'; import classNames from 'classnames';
import { useCreditNoteFormContext } from './CreditNoteFormProvider'; import { useCreditNoteFormContext } from './CreditNoteFormProvider';
import {
BrandingThemeFormGroup,
BrandingThemeSelectButton,
} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
import { useCreditNoteFormBrandingTemplatesOptions } from './utils';
/** /**
* Credit note floating actions. * Credit note floating actions.
@@ -74,6 +79,8 @@ export default function CreditNoteFloatingActions() {
resetForm(); resetForm();
}; };
const brandingTemplatesOptions = useCreditNoteFormBrandingTemplatesOptions();
return ( return (
<Group <Group
spacing={10} spacing={10}
@@ -190,6 +197,25 @@ export default function CreditNoteFloatingActions() {
onClick={handleCancelBtnClick} onClick={handleCancelBtnClick}
text={<T id={'cancel'} />} text={<T id={'cancel'} />}
/> />
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOptions}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
/>
</BrandingThemeFormGroup>
</Group> </Group>
); );
} }

View File

@@ -18,6 +18,7 @@ import {
useSettingsCreditNotes, useSettingsCreditNotes,
useInvoice, useInvoice,
} from '@/hooks/query'; } from '@/hooks/query';
import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
const CreditNoteFormContext = React.createContext(); const CreditNoteFormContext = React.createContext();
@@ -73,6 +74,10 @@ function CreditNoteFormProvider({ creditNoteId, ...props }) {
isSuccess: isBranchesSuccess, isSuccess: isBranchesSuccess,
} = useBranches({}, { enabled: isBranchFeatureCan }); } = useBranches({}, { enabled: isBranchFeatureCan });
// Fetches branding templates of invoice.
const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
useGetPdfTemplates({ resource: 'PaymentReceive' });
// Handle fetching settings. // Handle fetching settings.
useSettingsCreditNotes(); useSettingsCreditNotes();
@@ -115,13 +120,18 @@ function CreditNoteFormProvider({ creditNoteId, ...props }) {
createCreditNoteMutate, createCreditNoteMutate,
editCreditNoteMutate, editCreditNoteMutate,
setSubmitPayload, setSubmitPayload,
// Branding templates.
brandingTemplates,
isBrandingTemplatesLoading,
}; };
const isLoading = const isLoading =
isItemsLoading || isItemsLoading ||
isCustomersLoading || isCustomersLoading ||
isCreditNoteLoading || isCreditNoteLoading ||
isInvoiceLoading; isInvoiceLoading ||
isBrandingTemplatesLoading;
return ( return (
<DashboardInsider loading={isLoading} name={'credit-note-form'}> <DashboardInsider loading={isLoading} name={'credit-note-form'}>

View File

@@ -24,6 +24,7 @@ import {
transformAttachmentsToForm, transformAttachmentsToForm,
transformAttachmentsToRequest, transformAttachmentsToRequest,
} from '@/containers/Attachments/utils'; } from '@/containers/Attachments/utils';
import { convertBrandingTemplatesToOptions } from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
export const MIN_LINES_NUMBER = 1; export const MIN_LINES_NUMBER = 1;
@@ -54,7 +55,8 @@ export const defaultCreditNote = {
exchange_rate: 1, exchange_rate: 1,
currency_code: '', currency_code: '',
entries: [...repeatValue(defaultCreditNoteEntry, MIN_LINES_NUMBER)], entries: [...repeatValue(defaultCreditNoteEntry, MIN_LINES_NUMBER)],
attachments: [] attachments: [],
pdf_template_id: '',
}; };
/** /**
@@ -214,3 +216,13 @@ export const useCreditNoteIsForeignCustomer = () => {
); );
return isForeignCustomer; return isForeignCustomer;
}; };
export const useCreditNoteFormBrandingTemplatesOptions = () => {
const { brandingTemplates } = useCreditNoteFormContext();
return React.useMemo(
() => convertBrandingTemplatesToOptions(brandingTemplates),
[brandingTemplates],
);
};

View File

@@ -11,11 +11,16 @@ import {
Menu, Menu,
MenuItem, MenuItem,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { If, Icon, FormattedMessage as T, Group } from '@/components'; import { If, Icon, FormattedMessage as T, Group, FSelect } from '@/components';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { useEstimateFormContext } from './EstimateFormProvider'; import { useEstimateFormContext } from './EstimateFormProvider';
import { useEstimateFormBrandingTemplatesOptions } from './utils';
import {
BrandingThemeFormGroup,
BrandingThemeSelectButton,
} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
/** /**
* Estimate floating actions bar. * Estimate floating actions bar.
@@ -73,6 +78,8 @@ export default function EstimateFloatingActions() {
resetForm(); resetForm();
}; };
const brandingTemplatesOptions = useEstimateFormBrandingTemplatesOptions();
return ( return (
<Group <Group
spacing={10} spacing={10}
@@ -193,6 +200,25 @@ export default function EstimateFloatingActions() {
onClick={handleCancelBtnClick} onClick={handleCancelBtnClick}
text={<T id={'cancel'} />} text={<T id={'cancel'} />}
/> />
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOptions}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
/>
</BrandingThemeFormGroup>
</Group> </Group>
); );
} }

View File

@@ -12,8 +12,9 @@ import {
useCreateEstimate, useCreateEstimate,
useEditEstimate, useEditEstimate,
} from '@/hooks/query'; } from '@/hooks/query';
import { Features } from '@/constants';
import { useProjects } from '@/containers/Projects/hooks'; import { useProjects } from '@/containers/Projects/hooks';
import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state'; import { useFeatureCan } from '@/hooks/state';
import { ITEMS_FILTER_ROLES } from './utils'; import { ITEMS_FILTER_ROLES } from './utils';
@@ -71,6 +72,10 @@ function EstimateFormProvider({ query, estimateId, ...props }) {
isLoading: isProjectsLoading, isLoading: isProjectsLoading,
} = useProjects({}, { enabled: !!isProjectsFeatureCan }); } = useProjects({}, { enabled: !!isProjectsFeatureCan });
// Fetches branding templates of invoice.
const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
useGetPdfTemplates({ resource: 'SaleEstimate' });
// Handle fetch settings. // Handle fetch settings.
useSettingsEstimates(); useSettingsEstimates();
@@ -112,13 +117,19 @@ function EstimateFormProvider({ query, estimateId, ...props }) {
createEstimateMutate, createEstimateMutate,
editEstimateMutate, editEstimateMutate,
brandingTemplates,
isBrandingTemplatesLoading,
}; };
const isLoading =
isCustomersLoading ||
isItemsLoading ||
isEstimateLoading ||
isBrandingTemplatesLoading;
return ( return (
<DashboardInsider <DashboardInsider loading={isLoading} name={'estimate-form'}>
loading={isCustomersLoading || isItemsLoading || isEstimateLoading}
name={'estimate-form'}
>
<EstimateFormContext.Provider value={provider} {...props} /> <EstimateFormContext.Provider value={provider} {...props} />
</DashboardInsider> </DashboardInsider>
); );

View File

@@ -22,6 +22,7 @@ import {
transformAttachmentsToForm, transformAttachmentsToForm,
transformAttachmentsToRequest, transformAttachmentsToRequest,
} from '@/containers/Attachments/utils'; } from '@/containers/Attachments/utils';
import { convertBrandingTemplatesToOptions } from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
export const MIN_LINES_NUMBER = 1; export const MIN_LINES_NUMBER = 1;
@@ -60,7 +61,8 @@ export const defaultEstimate = {
exchange_rate: 1, exchange_rate: 1,
currency_code: '', currency_code: '',
entries: [...repeatValue(defaultEstimateEntry, MIN_LINES_NUMBER)], entries: [...repeatValue(defaultEstimateEntry, MIN_LINES_NUMBER)],
attachments: [] attachments: [],
pdf_template_id: '',
}; };
const ERRORS = { const ERRORS = {
@@ -262,3 +264,12 @@ export const resetFormState = ({ initialValues, values, resetForm }) => {
}, },
}); });
}; };
export const useEstimateFormBrandingTemplatesOptions = () => {
const { brandingTemplates } = useEstimateFormContext();
return React.useMemo(
() => convertBrandingTemplatesToOptions(brandingTemplates),
[brandingTemplates],
);
};

View File

@@ -25,6 +25,7 @@ import {
DashboardFilterButton, DashboardFilterButton,
DashboardRowsHeightButton, DashboardRowsHeightButton,
DashboardActionsBar, DashboardActionsBar,
FSelect,
} from '@/components'; } from '@/components';
import withEstimates from './withEstimates'; import withEstimates from './withEstimates';
@@ -42,6 +43,10 @@ import { compose } from '@/utils';
import { DialogsName } from '@/constants/dialogs'; import { DialogsName } from '@/constants/dialogs';
import withDrawerActions from '@/containers/Drawer/withDrawerActions'; import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DRAWERS } from '@/constants/drawers'; import { DRAWERS } from '@/constants/drawers';
import {
BrandingThemeFormGroup,
BrandingThemeSelectButton,
} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
/** /**
* Estimates list actions bar. * Estimates list actions bar.

View File

@@ -12,10 +12,15 @@ import {
MenuItem, MenuItem,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { Group, Icon, FormattedMessage as T } from '@/components'; import { FSelect, Group, Icon, FormattedMessage as T } from '@/components';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider'; import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import {
BrandingThemeFormGroup,
BrandingThemeSelectButton,
} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
import { usePaymentReceivedFormBrandingTemplatesOptions } from './utils';
/** /**
* Payment receive floating actions bar. * Payment receive floating actions bar.
@@ -53,6 +58,9 @@ export default function PaymentReceiveFormFloatingActions() {
submitForm(); submitForm();
}; };
const brandingTemplatesOpts =
usePaymentReceivedFormBrandingTemplatesOptions();
return ( return (
<Group <Group
spacing={10} spacing={10}
@@ -109,6 +117,25 @@ export default function PaymentReceiveFormFloatingActions() {
onClick={handleCancelBtnClick} onClick={handleCancelBtnClick}
text={<T id={'cancel'} />} text={<T id={'cancel'} />}
/> />
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOpts}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
/>
</BrandingThemeFormGroup>
</Group> </Group>
); );
} }

View File

@@ -13,6 +13,7 @@ import {
useCreatePaymentReceive, useCreatePaymentReceive,
useEditPaymentReceive, useEditPaymentReceive,
} from '@/hooks/query'; } from '@/hooks/query';
import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
// Payment receive form context. // Payment receive form context.
const PaymentReceiveFormContext = createContext(); const PaymentReceiveFormContext = createContext();
@@ -65,6 +66,10 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
isLoading: isProjectsLoading, isLoading: isProjectsLoading,
} = useProjects({}, { enabled: !!isProjectsFeatureCan }); } = useProjects({}, { enabled: !!isProjectsFeatureCan });
// Fetches branding templates of payment received module.
const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
useGetPdfTemplates({ resource: 'PaymentReceive' });
// Detarmines whether the new mode. // Detarmines whether the new mode.
const isNewMode = !paymentReceiveId; const isNewMode = !paymentReceiveId;
@@ -102,13 +107,20 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
isExcessConfirmed, isExcessConfirmed,
setIsExcessConfirmed, setIsExcessConfirmed,
// Branding templates
brandingTemplates,
isBrandingTemplatesLoading,
}; };
const isLoading =
isPaymentLoading ||
isAccountsLoading ||
isCustomersLoading ||
isBrandingTemplatesLoading;
return ( return (
<DashboardInsider <DashboardInsider loading={isLoading} name={'payment-receive-form'}>
loading={isPaymentLoading || isAccountsLoading || isCustomersLoading}
name={'payment-receive-form'}
>
<PaymentReceiveFormContext.Provider value={provider} {...props} /> <PaymentReceiveFormContext.Provider value={provider} {...props} />
</DashboardInsider> </DashboardInsider>
); );

View File

@@ -19,6 +19,7 @@ import {
transformAttachmentsToForm, transformAttachmentsToForm,
transformAttachmentsToRequest, transformAttachmentsToRequest,
} from '@/containers/Attachments/utils'; } from '@/containers/Attachments/utils';
import { convertBrandingTemplatesToOptions } from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
// Default payment receive entry. // Default payment receive entry.
export const defaultPaymentReceiveEntry = { export const defaultPaymentReceiveEntry = {
@@ -44,10 +45,11 @@ export const defaultPaymentReceive = {
statement: '', statement: '',
amount: '', amount: '',
currency_code: '', currency_code: '',
branch_id: '',
exchange_rate: 1, exchange_rate: 1,
entries: [], entries: [],
attachments: [], attachments: [],
branch_id: '',
pdf_template_id: '',
}; };
export const defaultRequestPaymentEntry = { export const defaultRequestPaymentEntry = {
@@ -303,3 +305,12 @@ export const getExceededAmountFromValues = (values) => {
return totalAmount - totalApplied; return totalAmount - totalApplied;
}; };
export const usePaymentReceivedFormBrandingTemplatesOptions = () => {
const { brandingTemplates } = usePaymentReceiveFormContext();
return React.useMemo(
() => convertBrandingTemplatesToOptions(brandingTemplates),
[brandingTemplates],
);
};

View File

@@ -11,12 +11,17 @@ import {
Menu, Menu,
MenuItem, MenuItem,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { Group, FormattedMessage as T } from '@/components'; import { FSelect, Group, FormattedMessage as T } from '@/components';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { CLASSES } from '@/constants/classes'; import { CLASSES } from '@/constants/classes';
import { If, Icon } from '@/components'; import { If, Icon } from '@/components';
import { useReceiptFormContext } from './ReceiptFormProvider'; import { useReceiptFormContext } from './ReceiptFormProvider';
import { useReceiptFormBrandingTemplatesOptions } from './utils';
import {
BrandingThemeFormGroup,
BrandingThemeSelectButton,
} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
/** /**
* Receipt floating actions bar. * Receipt floating actions bar.
@@ -76,6 +81,8 @@ export default function ReceiptFormFloatingActions() {
resetForm(); resetForm();
}; };
const brandingTemplatesOptions = useReceiptFormBrandingTemplatesOptions();
return ( return (
<Group <Group
spacing={10} spacing={10}
@@ -191,6 +198,25 @@ export default function ReceiptFormFloatingActions() {
onClick={handleCancelBtnClick} onClick={handleCancelBtnClick}
text={<T id={'cancel'} />} text={<T id={'cancel'} />}
/> />
{/* ----------- Branding Template Select ----------- */}
<BrandingThemeFormGroup
name={'pdf_template_id'}
label={'Branding'}
inline
fastField
style={{ marginLeft: 20 }}
>
<FSelect
name={'pdf_template_id'}
items={brandingTemplatesOptions}
input={({ activeItem, text, label, value }) => (
<BrandingThemeSelectButton text={text || 'Brand Theme'} minimal />
)}
filterable={false}
popoverProps={{ minimal: true }}
/>
</BrandingThemeFormGroup>
</Group> </Group>
); );
} }

View File

@@ -15,6 +15,7 @@ import {
useEditReceipt, useEditReceipt,
} from '@/hooks/query'; } from '@/hooks/query';
import { useProjects } from '@/containers/Projects/hooks'; import { useProjects } from '@/containers/Projects/hooks';
import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
const ReceiptFormContext = createContext(); const ReceiptFormContext = createContext();
@@ -77,7 +78,7 @@ function ReceiptFormProvider({ receiptId, ...props }) {
[], [],
); );
// Handle fetch Items data table or list // Handle fetch Items data table or list.
const { const {
data: { items }, data: { items },
isLoading: isItemsLoading, isLoading: isItemsLoading,
@@ -85,13 +86,16 @@ function ReceiptFormProvider({ receiptId, ...props }) {
page_size: 10000, page_size: 10000,
stringified_filter_roles: stringifiedFilterRoles, stringified_filter_roles: stringifiedFilterRoles,
}); });
// Fetch project list. // Fetch project list.
const { const {
data: { projects }, data: { projects },
isLoading: isProjectsLoading, isLoading: isProjectsLoading,
} = useProjects({}, { enabled: !!isProjectsFeatureCan }); } = useProjects({}, { enabled: !!isProjectsFeatureCan });
// Fetches branding templates of receipt.
const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
useGetPdfTemplates({ resource: 'SaleReceipt' });
// Fetch receipt settings. // Fetch receipt settings.
const { isLoading: isSettingLoading } = useSettingsReceipts(); const { isLoading: isSettingLoading } = useSettingsReceipts();
@@ -101,7 +105,6 @@ function ReceiptFormProvider({ receiptId, ...props }) {
const [submitPayload, setSubmitPayload] = useState({}); const [submitPayload, setSubmitPayload] = useState({});
const isNewMode = !receiptId; const isNewMode = !receiptId;
const isFeatureLoading = isWarehouesLoading || isBranchesLoading; const isFeatureLoading = isWarehouesLoading || isBranchesLoading;
const provider = { const provider = {
@@ -130,6 +133,10 @@ function ReceiptFormProvider({ receiptId, ...props }) {
createReceiptMutate, createReceiptMutate,
editReceiptMutate, editReceiptMutate,
setSubmitPayload, setSubmitPayload,
// Branding templates
brandingTemplates,
isBrandingTemplatesLoading
}; };
return ( return (
<DashboardInsider <DashboardInsider

View File

@@ -22,6 +22,7 @@ import {
transformAttachmentsToForm, transformAttachmentsToForm,
transformAttachmentsToRequest, transformAttachmentsToRequest,
} from '@/containers/Attachments/utils'; } from '@/containers/Attachments/utils';
import { convertBrandingTemplatesToOptions } from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
export const MIN_LINES_NUMBER = 1; export const MIN_LINES_NUMBER = 1;
@@ -61,6 +62,7 @@ export const defaultReceipt = {
currency_code: '', currency_code: '',
entries: [...repeatValue(defaultReceiptEntry, MIN_LINES_NUMBER)], entries: [...repeatValue(defaultReceiptEntry, MIN_LINES_NUMBER)],
attachments: [], attachments: [],
pdf_template_id: '',
}; };
const ERRORS = { const ERRORS = {
@@ -272,3 +274,12 @@ export const resetFormState = ({ initialValues, values, resetForm }) => {
}, },
}); });
}; };
export const useReceiptFormBrandingTemplatesOptions = () => {
const { brandingTemplates } = useReceiptFormContext();
return React.useMemo(
() => convertBrandingTemplatesToOptions(brandingTemplates),
[brandingTemplates],
);
};