Merge pull request #672 from bigcapitalhq/fix-invoice-brand-customize

fix: Invoice pdf customize
This commit is contained in:
Ahmed Bouhuolia
2024-09-25 12:22:07 +02:00
committed by GitHub
29 changed files with 133 additions and 48 deletions

View File

@@ -25,6 +25,17 @@ export class PaymentIntegration extends Model {
return this.paymentEnabled && this.payoutEnabled; return this.paymentEnabled && this.payoutEnabled;
} }
static get modifiers() {
return {
/**
* Query to filter enabled payment and payout.
*/
fullEnabled(query) {
query.where('paymentEnabled', true).andWhere('payoutEnabled', true);
},
};
}
static get jsonSchema() { static get jsonSchema() {
return { return {
type: 'object', type: 'object',

View File

@@ -1,17 +1,16 @@
import { NextFunction, Request, Response } from 'express';
import multer from 'multer'; import multer from 'multer';
import type { Multer } from 'multer'; import type { Multer } from 'multer';
import multerS3 from 'multer-s3'; import multerS3 from 'multer-s3';
import { s3 } from '@/lib/S3/S3'; import { s3 } from '@/lib/S3/S3';
import { Service } from 'typedi'; import { Service } from 'typedi';
import config from '@/config'; import config from '@/config';
import { NextFunction, Request, Response } from 'express';
@Service() @Service()
export class AttachmentUploadPipeline { export class AttachmentUploadPipeline {
/** /**
* Middleware to ensure that S3 configuration is properly set before proceeding. * Middleware to ensure that S3 configuration is properly set before proceeding.
* This function checks if the necessary S3 configuration keys are present and throws an error if any are missing. * This function checks if the necessary S3 configuration keys are present and throws an error if any are missing.
*
* @param req The HTTP request object. * @param req The HTTP request object.
* @param res The HTTP response object. * @param res The HTTP response object.
* @param next The callback to pass control to the next middleware function. * @param next The callback to pass control to the next middleware function.
@@ -49,6 +48,11 @@ export class AttachmentUploadPipeline {
key: function (req, file, cb) { key: function (req, file, cb) {
cb(null, Date.now().toString()); cb(null, Date.now().toString());
}, },
acl: function(req, file, cb) {
// Conditionally set file to public or private based on isPublic flag
const aclValue = true ? 'public-read' : 'private';
cb(null, aclValue); // Set ACL based on the isPublic flag
}
}), }),
}); });
} }

View File

@@ -21,7 +21,7 @@ export class GetPaymentServicesSpecificInvoice {
const { PaymentIntegration } = this.tenancy.models(tenantId); const { PaymentIntegration } = this.tenancy.models(tenantId);
const paymentGateways = await PaymentIntegration.query() const paymentGateways = await PaymentIntegration.query()
.where('active', true) .modify('fullEnabled')
.orderBy('name', 'ASC'); .orderBy('name', 'ASC');
return this.transform.transform( return this.transform.transform(

View File

@@ -44,6 +44,7 @@ export class GetInvoicePaymentLinkMetadata {
.findById(paymentLink.resourceId) .findById(paymentLink.resourceId)
.withGraphFetched('entries.item') .withGraphFetched('entries.item')
.withGraphFetched('customer') .withGraphFetched('customer')
.withGraphFetched('taxes.taxRate')
.throwIfNotFound(); .throwIfNotFound();
return this.transformer.transform( return this.transformer.transform(

View File

@@ -1,4 +1,5 @@
import { ItemEntryTransformer } from './ItemEntryTransformer'; import { ItemEntryTransformer } from './ItemEntryTransformer';
import { SaleInvoiceTaxEntryTransformer } from './SaleInvoiceTaxEntryTransformer';
import { SaleInvoiceTransformer } from './SaleInvoiceTransformer'; import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer { export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer {
@@ -37,6 +38,7 @@ export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer
'invoiceMessage', 'invoiceMessage',
'termsConditions', 'termsConditions',
'entries', 'entries',
'taxes',
]; ];
}; };
@@ -62,6 +64,22 @@ export class GetInvoicePaymentLinkMetaTransformer extends SaleInvoiceTransformer
} }
); );
}; };
/**
* Retrieves the sale invoice entries.
* @returns {}
*/
protected taxes = (invoice) => {
return this.item(
invoice.taxes,
new GetInvoicePaymentLinkTaxEntryTransformer(),
{
subtotal: invoice.subtotal,
isInclusiveTax: invoice.isInclusiveTax,
currencyCode: invoice.currencyCode,
}
);
};
} }
class GetInvoicePaymentLinkEntryMetaTransformer extends ItemEntryTransformer { class GetInvoicePaymentLinkEntryMetaTransformer extends ItemEntryTransformer {
@@ -94,3 +112,13 @@ class GetInvoicePaymentLinkEntryMetaTransformer extends ItemEntryTransformer {
return ['*']; return ['*'];
}; };
} }
class GetInvoicePaymentLinkTaxEntryTransformer extends SaleInvoiceTaxEntryTransformer {
/**
* Included attributes.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return ['name', 'taxRateCode', 'taxRateAmount', 'taxRateAmountFormatted'];
};
}

View File

@@ -134,8 +134,3 @@ export function BrandingTemplateForm<T extends BrandingTemplateValues>({
export const validationSchema = Yup.object().shape({ export const validationSchema = Yup.object().shape({
templateName: Yup.string().required('Template Name is required'), templateName: Yup.string().required('Template Name is required'),
}); });
// Initial values - companyLogoKey, companyLogoUri
// Form - _companyLogoFile, companyLogoKey, companyLogoUri
// Request - companyLogoKey

View File

@@ -3,6 +3,7 @@
export interface BrandingTemplateValues { export interface BrandingTemplateValues {
templateName: string; templateName: string;
// Company logo
companyLogoKey?: string; companyLogoKey?: string;
companyLogoUri?: string; companyLogoUri?: string;
} }

View File

@@ -1,6 +1,6 @@
.rootBodyPage { .rootBodyPage {
background: #0c103f; background: #1c1d29;
} }
.root { .root {

View File

@@ -83,6 +83,13 @@ export function PaymentPortal() {
</Text> </Text>
</Group> </Group>
{sharableLinkMeta?.taxes?.map((tax, key) => (
<Group key={key} position={'apart'} className={styles.totalItem}>
<Text>{tax?.name}</Text>
<Text>{tax?.taxRateAmountFormatted}</Text>
</Group>
))}
<Group <Group
position={'apart'} position={'apart'}
className={clsx(styles.totalItem, styles.borderBottomGray)} className={clsx(styles.totalItem, styles.borderBottomGray)}

View File

@@ -30,6 +30,10 @@ export function PaymentInvoicePreviewContent() {
rate: entry.rateFormatted, rate: entry.rateFormatted,
total: entry.totalFormatted, total: entry.totalFormatted,
}))} }))}
taxes={sharableLinkMeta?.taxes?.map((tax) => ({
label: tax.name,
amount: tax.taxRateAmountFormatted,
}))}
/> />
</Box> </Box>
</DrawerBody> </DrawerBody>

View File

@@ -53,10 +53,14 @@ export interface CreditNotePaperTemplateProps extends PaperTemplateProps {
} }
export function CreditNotePaperTemplate({ export function CreditNotePaperTemplate({
// # Colors
primaryColor, primaryColor,
secondaryColor, secondaryColor,
// # Company Logo
showCompanyLogo = true, showCompanyLogo = true,
companyLogo, companyLogoUri = '',
companyName = 'Bigcapital Technology, Inc.', companyName = 'Bigcapital Technology, Inc.',
// Address // Address
@@ -123,7 +127,7 @@ export function CreditNotePaperTemplate({
primaryColor={primaryColor} primaryColor={primaryColor}
secondaryColor={secondaryColor} secondaryColor={secondaryColor}
showCompanyLogo={showCompanyLogo} showCompanyLogo={showCompanyLogo}
companyLogo={companyLogo} companyLogoUri={companyLogoUri}
bigtitle={'Credit Note'} bigtitle={'Credit Note'}
> >
<Stack spacing={24}> <Stack spacing={24}>

View File

@@ -7,8 +7,8 @@ export const initialValues = {
// Company logo. // Company logo.
showCompanyLogo: true, showCompanyLogo: true,
companyLogo: companyLogoKey: '',
'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png', companyLogoUri: '',
// Address // Address
showBilledToAddress: true, showBilledToAddress: true,

View File

@@ -7,7 +7,6 @@ export interface CreditNoteCustomizeValues extends BrandingTemplateValues {
// Company Logo // Company Logo
showCompanyLogo?: boolean; showCompanyLogo?: boolean;
companyLogo?: string;
// Entries // Entries
itemNameLabel?: string; itemNameLabel?: string;

View File

@@ -76,7 +76,6 @@ export function EstimateCustomizeGeneralField() {
name={'showCompanyLogo'} name={'showCompanyLogo'}
label={'Display company logo in the paper'} label={'Display company logo in the paper'}
style={{ fontSize: 14 }} style={{ fontSize: 14 }}
large
fastField fastField
/> />
</FFormGroup> </FFormGroup>

View File

@@ -1,4 +1,4 @@
import { Group, Stack } from '@/components'; import { Stack } from '@/components';
import { import {
PaperTemplate, PaperTemplate,
PaperTemplateProps, PaperTemplateProps,
@@ -57,8 +57,10 @@ export interface EstimatePaperTemplateProps extends PaperTemplateProps {
export function EstimatePaperTemplate({ export function EstimatePaperTemplate({
primaryColor, primaryColor,
secondaryColor, secondaryColor,
showCompanyLogo = true, showCompanyLogo = true,
companyLogo, companyLogoUri = '',
companyName, companyName,
billedToAddress = [ billedToAddress = [
@@ -122,7 +124,7 @@ export function EstimatePaperTemplate({
primaryColor={primaryColor} primaryColor={primaryColor}
secondaryColor={secondaryColor} secondaryColor={secondaryColor}
showCompanyLogo={showCompanyLogo} showCompanyLogo={showCompanyLogo}
companyLogo={companyLogo} companyLogoUri={companyLogoUri}
bigtitle={'Estimate'} bigtitle={'Estimate'}
> >
<Stack spacing={24}> <Stack spacing={24}>

View File

@@ -1,14 +1,14 @@
export const initialValues = { export const initialValues = {
templateName: '', templateName: '',
// Colors // Colors
primaryColor: '#2c3dd8', primaryColor: '#2c3dd8',
secondaryColor: '#2c3dd8', secondaryColor: '#2c3dd8',
// Company logo. // Company logo.
showCompanyLogo: true, showCompanyLogo: true,
companyLogo: companyLogoKey: '',
'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png', companyLogoUri: '',
// Top details. // Top details.
showEstimateNumber: true, showEstimateNumber: true,

View File

@@ -1,4 +1,4 @@
import { BrandingTemplateValues } from "@/containers/BrandingTemplates/types"; import { BrandingTemplateValues } from '@/containers/BrandingTemplates/types';
export interface EstimateCustomizeValues extends BrandingTemplateValues { export interface EstimateCustomizeValues extends BrandingTemplateValues {
// Colors // Colors
@@ -7,7 +7,8 @@ export interface EstimateCustomizeValues extends BrandingTemplateValues {
// Company Logo // Company Logo
showCompanyLogo?: boolean; showCompanyLogo?: boolean;
companyLogo?: string; companyLogoKey?: string;
companyLogoUri?: string;
// Top details. // Top details.
estimateNumberLabel?: string; estimateNumberLabel?: string;

View File

@@ -13,6 +13,10 @@ import { CreditCardIcon } from '@/icons/CreditCardIcon';
import { Overlay } from './Overlay'; import { Overlay } from './Overlay';
import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils'; import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils';
import { BrandingCompanyLogoUploadField } from '@/containers/ElementCustomize/components/BrandingCompanyLogoUploadField'; import { BrandingCompanyLogoUploadField } from '@/containers/ElementCustomize/components/BrandingCompanyLogoUploadField';
import { Link } from 'react-router-dom';
import { MANAGE_LINK_URL } from './constants';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useDrawerActions } from '@/hooks/state';
export function InvoiceCustomizeGeneralField() { export function InvoiceCustomizeGeneralField() {
const isTemplateNameFilled = useIsTemplateNamedFilled(); const isTemplateNameFilled = useIsTemplateNamedFilled();
@@ -93,6 +97,13 @@ export function InvoiceCustomizeGeneralField() {
} }
function InvoiceCustomizePaymentManage() { function InvoiceCustomizePaymentManage() {
const { name } = useDrawerContext();
const { closeDrawer } = useDrawerActions();
const handleLinkClick = () => {
closeDrawer(name);
};
return ( return (
<Group <Group
style={{ style={{
@@ -108,9 +119,13 @@ function InvoiceCustomizePaymentManage() {
<Text>Accept payment methods</Text> <Text>Accept payment methods</Text>
</Group> </Group>
<a style={{ fontSize: 13 }} href={'#'}> <Link
style={{ fontSize: 13 }}
to={MANAGE_LINK_URL}
onClick={handleLinkClick}
>
Manage Manage
</a> </Link>
</Group> </Group>
); );
} }

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { PaperTemplate, PaperTemplateTotalBorder } from './PaperTemplate'; import { PaperTemplate, PaperTemplateTotalBorder } from './PaperTemplate';
import { Group, Stack } from '@/components'; import { Stack } from '@/components';
interface PapaerLine { interface PapaerLine {
item?: string; item?: string;
@@ -95,7 +95,7 @@ export function InvoicePaperTemplate({
companyName = 'Bigcapital Technology, Inc.', companyName = 'Bigcapital Technology, Inc.',
showCompanyLogo = true, showCompanyLogo = true,
companyLogoUri, companyLogoUri = '',
dueDate = 'September 3, 2024', dueDate = 'September 3, 2024',
dueDateLabel = 'Date due', dueDateLabel = 'Date due',
@@ -185,7 +185,7 @@ export function InvoicePaperTemplate({
primaryColor={primaryColor} primaryColor={primaryColor}
secondaryColor={secondaryColor} secondaryColor={secondaryColor}
showCompanyLogo={showCompanyLogo} showCompanyLogo={showCompanyLogo}
companyLogo={companyLogoUri} companyLogoUri={companyLogoUri}
bigtitle={'Invoice'} bigtitle={'Invoice'}
> >
<Stack spacing={24}> <Stack spacing={24}>

View File

@@ -9,7 +9,7 @@ export interface PaperTemplateProps {
secondaryColor?: string; secondaryColor?: string;
showCompanyLogo?: boolean; showCompanyLogo?: boolean;
companyLogo?: string; companyLogoUri?: string;
companyName?: string; companyName?: string;
bigtitle?: string; bigtitle?: string;
@@ -21,7 +21,7 @@ export function PaperTemplate({
primaryColor, primaryColor,
secondaryColor, secondaryColor,
showCompanyLogo, showCompanyLogo,
companyLogo, companyLogoUri,
bigtitle = 'Invoice', bigtitle = 'Invoice',
children, children,
}: PaperTemplateProps) { }: PaperTemplateProps) {
@@ -32,9 +32,9 @@ export function PaperTemplate({
<div> <div>
<h1 className={styles.bigTitle}>{bigtitle}</h1> <h1 className={styles.bigTitle}>{bigtitle}</h1>
{showCompanyLogo && ( {showCompanyLogo && companyLogoUri && (
<div className={styles.logoWrap}> <div className={styles.logoWrap}>
<img alt="" src={companyLogo} /> <img alt="" src={companyLogoUri} />
</div> </div>
)} )}
</div> </div>
@@ -120,8 +120,8 @@ PaperTemplate.MutedText = () => {};
PaperTemplate.Text = () => {}; PaperTemplate.Text = () => {};
PaperTemplate.AddressesGroup = (props: GroupProps) => { PaperTemplate.AddressesGroup = (props: GroupProps) => {
return <Group spacing={10} {...props} className={styles.addressRoot} /> return <Group spacing={10} {...props} className={styles.addressRoot} />;
} };
PaperTemplate.Address = ({ PaperTemplate.Address = ({
items, items,
}: { }: {

View File

@@ -1,3 +1,5 @@
export const MANAGE_LINK_URL = '/preferences/payment-methods';
export const initialValues = { export const initialValues = {
templateName: '', templateName: '',

View File

@@ -76,7 +76,6 @@ export function PaymentReceivedCustomizeGeneralField() {
name={'showCompanyLogo'} name={'showCompanyLogo'}
label={'Display company logo in the paper'} label={'Display company logo in the paper'}
style={{ fontSize: 14 }} style={{ fontSize: 14 }}
large
fastField fastField
/> />
</FFormGroup> </FFormGroup>

View File

@@ -41,10 +41,15 @@ export interface PaymentReceivedPaperTemplateProps extends PaperTemplateProps {
} }
export function PaymentReceivedPaperTemplate({ export function PaymentReceivedPaperTemplate({
// # Colors
primaryColor, primaryColor,
secondaryColor, secondaryColor,
// # Company logo
showCompanyLogo = true, showCompanyLogo = true,
companyLogo, companyLogoUri,
// # Company name
companyName = 'Bigcapital Technology, Inc.', companyName = 'Bigcapital Technology, Inc.',
billedToAddress = [ billedToAddress = [
@@ -94,7 +99,7 @@ export function PaymentReceivedPaperTemplate({
primaryColor={primaryColor} primaryColor={primaryColor}
secondaryColor={secondaryColor} secondaryColor={secondaryColor}
showCompanyLogo={showCompanyLogo} showCompanyLogo={showCompanyLogo}
companyLogo={companyLogo} companyLogoUri={companyLogoUri}
bigtitle={'Payment'} bigtitle={'Payment'}
> >
<Stack spacing={24}> <Stack spacing={24}>

View File

@@ -1,14 +1,14 @@
export const initialValues = { export const initialValues = {
templateName: '', templateName: '',
// Colors // Colors
primaryColor: '#2c3dd8', primaryColor: '#2c3dd8',
secondaryColor: '#2c3dd8', secondaryColor: '#2c3dd8',
// Company logo. // Company logo.
showCompanyLogo: true, showCompanyLogo: true,
companyLogo: companyLogoUri: '',
'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png', companyLogokey: '',
// Top details. // Top details.
showPaymentReceivedNumber: true, showPaymentReceivedNumber: true,

View File

@@ -7,7 +7,6 @@ export interface PaymentReceivedCustomizeValues extends BrandingTemplateValues {
// Company Logo // Company Logo
showCompanyLogo?: boolean; showCompanyLogo?: boolean;
companyLogo?: string;
// Top details. // Top details.
showInvoiceNumber?: boolean; showInvoiceNumber?: boolean;

View File

@@ -1,4 +1,4 @@
import { Group, Stack } from '@/components'; import { Stack } from '@/components';
import { import {
PaperTemplate, PaperTemplate,
PaperTemplateProps, PaperTemplateProps,
@@ -53,10 +53,15 @@ export interface ReceiptPaperTemplateProps extends PaperTemplateProps {
} }
export function ReceiptPaperTemplate({ export function ReceiptPaperTemplate({
// # Colors
primaryColor, primaryColor,
secondaryColor, secondaryColor,
// # Company logo
showCompanyLogo = true, showCompanyLogo = true,
companyLogo, companyLogoUri,
// # Company name
companyName = 'Bigcapital Technology, Inc.', companyName = 'Bigcapital Technology, Inc.',
// # Address // # Address
@@ -117,7 +122,7 @@ export function ReceiptPaperTemplate({
primaryColor={primaryColor} primaryColor={primaryColor}
secondaryColor={secondaryColor} secondaryColor={secondaryColor}
showCompanyLogo={showCompanyLogo} showCompanyLogo={showCompanyLogo}
companyLogo={companyLogo} companyLogoUri={companyLogoUri}
bigtitle={'Receipt'} bigtitle={'Receipt'}
> >
<Stack spacing={24}> <Stack spacing={24}>

View File

@@ -7,8 +7,8 @@ export const initialValues = {
// Company logo. // Company logo.
showCompanyLogo: true, showCompanyLogo: true,
companyLogo: companyLogoKey: '',
'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png', companyLogoUri: '',
// Receipt Number // Receipt Number
showReceiptNumber: true, showReceiptNumber: true,

View File

@@ -7,7 +7,6 @@ export interface ReceiptCustomizeValues extends BrandingTemplateValues {
// Company Logo // Company Logo
showCompanyLogo?: boolean; showCompanyLogo?: boolean;
companyLogo?: string;
// Receipt Number // Receipt Number
showReceiptNumber?: boolean; showReceiptNumber?: boolean;

View File

@@ -50,7 +50,6 @@ export function useCreatePaymentLink(
); );
} }
// Get Invoice Payment Link // Get Invoice Payment Link
// ----------------------------------------- // -----------------------------------------
export interface GetInvoicePaymentLinkResponse { export interface GetInvoicePaymentLinkResponse {
@@ -82,6 +81,12 @@ export interface GetInvoicePaymentLinkResponse {
total: number; total: number;
totalFormatted: string; totalFormatted: string;
}>; }>;
taxes: Array<{
name: string;
taxRateAmount: number;
taxRateAmountFormatted: string;
taxRateCode: string;
}>;
} }
/** /**
* Fetches the sharable invoice link metadata for a given link ID. * Fetches the sharable invoice link metadata for a given link ID.