pdf,excell related changes

This commit is contained in:
Gowtham M 2026-07-15 15:00:38 +05:30
parent c37ff28833
commit 95cbdcf7c5
19 changed files with 347 additions and 64 deletions

View File

@ -18,6 +18,13 @@ const list = asyncHandler(async (req, res) => {
res.json(new ApiResponse(200, result.data, 'Assets fetched', result.meta));
});
const exportCsv = asyncHandler(async (req, res) => {
const csv = await service.exportAssets(req.query);
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader('Content-Disposition', 'attachment; filename="assets-export.csv"');
res.send(csv);
});
const getOne = asyncHandler(async (req, res) => {
const data = await service.getAssetById(req.params.id);
res.json(new ApiResponse(200, data, 'Asset fetched'));
@ -276,6 +283,7 @@ const removeAttachment = asyncHandler(async (req, res) => {
module.exports = {
create,
list,
exportCsv,
getOne,
update,
remove,

View File

@ -8,6 +8,7 @@ const {
createAssetSchema,
updateAssetSchema,
listAssetsQuerySchema,
exportAssetsQuerySchema,
transferAssetSchema,
amcContractSchema,
updateAmcContractSchema,
@ -28,6 +29,12 @@ const router = express.Router();
router.use(authenticate);
router.get(
'/export',
authorize('ASSET', 'export'),
validate(exportAssetsQuerySchema, 'query'),
controller.exportCsv
);
router.get(
'/',
authorize('ASSET', 'view'),

View File

@ -3,6 +3,7 @@ const ApiError = require('../../utils/ApiError');
const auditLog = require('../../utils/auditLog');
const { getPagination } = require('../../utils/pagination');
const { nextDocumentNumber } = require('../../utils/generateCode');
const { rowsToCsv } = require('../../utils/csv');
const { DISPOSAL_STATUSES, getAssetDropdownOptions } = require('./assets.constants');
const { assertPlant, assertWarehouse } = require('../../utils/locations');
const repository = require('./assets.repository');
@ -307,30 +308,32 @@ const createAsset = async (payload, userId, requestId) => {
return sanitizeAsset(created);
};
const buildAssetsWhere = (query) => ({
deleted_at: null,
...(query.status ? { status: query.status } : {}),
...(query.condition ? { condition: query.condition } : {}),
...(query.item_category_id ? { item_category_id: BigInt(query.item_category_id) } : {}),
...(query.item_subcategory_id
? { item_subcategory_id: BigInt(query.item_subcategory_id) }
: {}),
...(query.plant_id ? { plant_id: BigInt(query.plant_id) } : {}),
...(query.department_id ? { department_id: BigInt(query.department_id) } : {}),
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
...(query.search
? {
OR: [
{ asset_code: { contains: query.search, mode: 'insensitive' } },
{ asset_name: { contains: query.search, mode: 'insensitive' } },
{ serial_number: { contains: query.search, mode: 'insensitive' } },
{ qr_code_value: { contains: query.search, mode: 'insensitive' } },
],
}
: {}),
});
const listAssets = async (query) => {
const { page, limit, skip } = getPagination(query);
const where = {
deleted_at: null,
...(query.status ? { status: query.status } : {}),
...(query.condition ? { condition: query.condition } : {}),
...(query.item_category_id ? { item_category_id: BigInt(query.item_category_id) } : {}),
...(query.item_subcategory_id
? { item_subcategory_id: BigInt(query.item_subcategory_id) }
: {}),
...(query.plant_id ? { plant_id: BigInt(query.plant_id) } : {}),
...(query.department_id ? { department_id: BigInt(query.department_id) } : {}),
...(query.is_active !== undefined ? { is_active: query.is_active } : {}),
...(query.search
? {
OR: [
{ asset_code: { contains: query.search, mode: 'insensitive' } },
{ asset_name: { contains: query.search, mode: 'insensitive' } },
{ serial_number: { contains: query.search, mode: 'insensitive' } },
{ qr_code_value: { contains: query.search, mode: 'insensitive' } },
],
}
: {}),
};
const where = buildAssetsWhere(query);
const [rows, total] = await Promise.all([
prisma.assets.findMany({
@ -346,6 +349,39 @@ const listAssets = async (query) => {
return { data: rows.map(sanitizeAsset), meta: { page, limit, total } };
};
const exportAssets = async (query) => {
const rows = await prisma.assets.findMany({
where: buildAssetsWhere(query),
include: assetInclude,
orderBy: { created_at: 'desc' },
});
return rowsToCsv(
[
{ key: 'asset_code', header: 'Asset Code' },
{ key: 'asset_name', header: 'Asset Name' },
{ key: (row) => row.item_category?.name || '', header: 'Category' },
{ key: (row) => row.item_subcategory?.name || '', header: 'Subcategory' },
{ key: 'serial_number', header: 'Serial Number' },
{ key: 'condition', header: 'Condition' },
{ key: 'status', header: 'Status' },
{ key: (row) => row.plant?.name || '', header: 'Plant' },
{ key: (row) => row.department?.name || '', header: 'Department' },
{ key: (row) => row.warehouse?.name || '', header: 'Warehouse' },
{ key: (row) => row.assigned_to_user?.full_name || '', header: 'Assigned To' },
{ key: (row) => row.vendor?.vendor_name || '', header: 'Vendor' },
{ key: (row) => row.purchase_order?.po_number || '', header: 'PO Number' },
{ key: (row) => row.grn?.grn_number || '', header: 'GRN Number' },
{ key: 'purchase_date', header: 'Purchase Date', type: 'date' },
{ key: 'purchase_cost', header: 'Purchase Cost' },
{ key: 'warranty_expiry_date', header: 'Warranty Expiry', type: 'date' },
{ key: 'is_active', header: 'Active' },
{ key: 'created_at', header: 'Created At', type: 'datetime' },
],
rows.map(sanitizeAsset)
);
};
const getAssetById = async (id) => sanitizeAsset(await getAssetOrThrow(id));
const updateAsset = async (id, payload, userId, requestId) => {
@ -566,6 +602,7 @@ const previewDepreciation = (payload) => {
module.exports = {
createAsset,
listAssets,
exportAssets,
getAssetById,
updateAsset,
deleteAsset,

View File

@ -108,6 +108,11 @@ const listAssetsQuerySchema = Joi.object({
is_active: Joi.boolean().optional(),
});
const exportAssetsQuerySchema = listAssetsQuerySchema.keys({
page: Joi.strip(),
limit: Joi.strip(),
});
const transferAssetSchema = Joi.object({
transfer_date: Joi.date().iso().required(),
to_plant_id: Joi.number().integer().positive().allow(null).optional(),
@ -305,6 +310,7 @@ module.exports = {
createAssetSchema,
updateAssetSchema,
listAssetsQuerySchema,
exportAssetsQuerySchema,
transferAssetSchema,
amcContractSchema,
updateAmcContractSchema,

View File

@ -14,6 +14,13 @@ const list = asyncHandler(async (req, res) => {
res.json(new ApiResponse(200, result.data, 'GRNs fetched', result.meta));
});
const exportCsv = asyncHandler(async (req, res) => {
const csv = await service.exportGrns(req.query);
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader('Content-Disposition', 'attachment; filename="grn-export.csv"');
res.send(csv);
});
const getOne = asyncHandler(async (req, res) => {
const data = await service.getGrnById(req.params.id);
res.json(new ApiResponse(200, data, 'GRN fetched'));
@ -85,6 +92,7 @@ const removeAttachment = asyncHandler(async (req, res) => {
module.exports = {
create,
list,
exportCsv,
getOne,
update,
cancel,

View File

@ -8,6 +8,7 @@ const {
createGrnSchema,
updateGrnSchema,
listGrnQuerySchema,
exportGrnQuerySchema,
cancelGrnSchema,
} = require('./grn.validation');
@ -15,6 +16,12 @@ const router = express.Router();
router.use(authenticate);
router.get(
'/export',
authorize('GRN', 'export'),
validate(exportGrnQuerySchema, 'query'),
controller.exportCsv
);
router.get('/', authorize('GRN', 'view'), validate(listGrnQuerySchema, 'query'), controller.list);
router.post('/', authorize('GRN', 'create'), validate(createGrnSchema), controller.create);

View File

@ -7,6 +7,7 @@ const { generatePdf } = require('../../utils/pdf/pdfGenerator');
const { generateGrnHtml } = require('../../utils/pdf/templates/grn.template');
const { formatDate } = require('../../utils/pdf/helpers/formatDate');
const { getCompanyForDocuments } = require('../settings/settings.service');
const { rowsToCsv } = require('../../utils/csv');
const repository = require('./grn.repository');
const { assertWarehouse } = require('../../utils/locations');
const { sanitizeAttachment } = require('./grn.attachments.service');
@ -445,24 +446,26 @@ const createGrn = async (payload, userId, requestId) => {
return sanitizeGrn(detail);
};
const buildGrnsWhere = (query) => ({
deleted_at: null,
...(query.status ? { status: query.status } : {}),
...(query.po_id ? { po_id: BigInt(query.po_id) } : {}),
...(query.vendor_id ? { vendor_id: BigInt(query.vendor_id) } : {}),
...(query.warehouse_id ? { warehouse_id: BigInt(query.warehouse_id) } : {}),
...(query.search ? { grn_number: { contains: query.search, mode: 'insensitive' } } : {}),
...(query.date_from || query.date_to
? {
grn_date: {
...(query.date_from ? { gte: toDateOnly(query.date_from) } : {}),
...(query.date_to ? { lte: toDateOnly(query.date_to) } : {}),
},
}
: {}),
});
const listGrns = async (query) => {
const { page, limit, skip } = getPagination(query);
const where = {
deleted_at: null,
...(query.status ? { status: query.status } : {}),
...(query.po_id ? { po_id: BigInt(query.po_id) } : {}),
...(query.vendor_id ? { vendor_id: BigInt(query.vendor_id) } : {}),
...(query.warehouse_id ? { warehouse_id: BigInt(query.warehouse_id) } : {}),
...(query.search ? { grn_number: { contains: query.search, mode: 'insensitive' } } : {}),
...(query.date_from || query.date_to
? {
grn_date: {
...(query.date_from ? { gte: toDateOnly(query.date_from) } : {}),
...(query.date_to ? { lte: toDateOnly(query.date_to) } : {}),
},
}
: {}),
};
const where = buildGrnsWhere(query);
const [rows, total] = await Promise.all([
prisma.grn.findMany({
@ -478,6 +481,35 @@ const listGrns = async (query) => {
return { data: rows.map(sanitizeGrn), meta: { page, limit, total } };
};
const exportGrns = async (query) => {
const rows = await prisma.grn.findMany({
where: buildGrnsWhere(query),
include: grnListInclude,
orderBy: { created_at: 'desc' },
});
return rowsToCsv(
[
{ key: 'grn_number', header: 'GRN Number' },
{ key: 'grn_date', header: 'GRN Date', type: 'date' },
{ key: 'status', header: 'Status' },
{ key: (row) => row.purchase_order?.po_number || '', header: 'PO Number' },
{ key: (row) => row.vendor?.vendor_code || '', header: 'Vendor Code' },
{ key: (row) => row.vendor?.vendor_name || '', header: 'Vendor Name' },
{ key: (row) => row.warehouse?.name || '', header: 'Warehouse' },
{ key: 'vendor_invoice_no', header: 'Vendor Invoice No' },
{ key: 'vendor_invoice_date', header: 'Vendor Invoice Date', type: 'date' },
{ key: 'vendor_invoice_amount', header: 'Vendor Invoice Amount' },
{ key: 'vehicle_no', header: 'Vehicle No' },
{ key: 'lr_no', header: 'LR No' },
{ key: (row) => row.received_by_user?.full_name || '', header: 'Received By' },
{ key: (row) => row.created_by_user?.full_name || '', header: 'Created By' },
{ key: 'created_at', header: 'Created At', type: 'datetime' },
],
rows.map(sanitizeGrn)
);
};
const getGrnById = async (id) => sanitizeGrn(await getGrnOrThrow(id, { includeItems: true }));
const updateGrn = async (id, payload, userId, requestId) => {
@ -582,6 +614,7 @@ const getGrnPdf = async (id) => {
module.exports = {
createGrn,
listGrns,
exportGrns,
getGrnById,
updateGrn,
cancelGrn,

View File

@ -62,6 +62,11 @@ const listGrnQuerySchema = Joi.object({
date_to: Joi.date().iso().optional(),
});
const exportGrnQuerySchema = listGrnQuerySchema.keys({
page: Joi.strip(),
limit: Joi.strip(),
});
const cancelGrnSchema = Joi.object({
cancellation_reason: Joi.string().trim().min(1).required(),
});
@ -70,5 +75,6 @@ module.exports = {
createGrnSchema,
updateGrnSchema,
listGrnQuerySchema,
exportGrnQuerySchema,
cancelGrnSchema,
};

View File

@ -14,6 +14,13 @@ const list = asyncHandler(async (req, res) => {
res.json(new ApiResponse(200, result.data, 'Purchase orders fetched', result.meta));
});
const exportCsv = asyncHandler(async (req, res) => {
const csv = await service.exportPurchaseOrders(req.query);
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader('Content-Disposition', 'attachment; filename="purchase-orders-export.csv"');
res.send(csv);
});
const getOne = asyncHandler(async (req, res) => {
const data = await service.getPurchaseOrderById(req.params.id);
res.json(new ApiResponse(200, data, 'Purchase order fetched'));
@ -110,6 +117,7 @@ const removeAttachment = asyncHandler(async (req, res) => {
module.exports = {
create,
list,
exportCsv,
getOne,
update,
remove,

View File

@ -9,6 +9,7 @@ const {
updatePurchaseOrderSchema,
amendPurchaseOrderSchema,
listPurchaseOrdersQuerySchema,
exportPurchaseOrdersQuerySchema,
workflowRemarksSchema,
rejectPurchaseOrderSchema,
} = require('./purchase-orders.validation');
@ -17,6 +18,12 @@ const router = express.Router();
router.use(authenticate);
router.get(
'/export',
authorize('PURCHASE_ORDER', 'export'),
validate(exportPurchaseOrdersQuerySchema, 'query'),
controller.exportCsv
);
router.get(
'/',
authorize('PURCHASE_ORDER', 'view'),

View File

@ -7,6 +7,7 @@ const { generatePdf } = require('../../utils/pdf/pdfGenerator');
const { generatePoHtml } = require('../../utils/pdf/templates/po.template');
const { formatDate } = require('../../utils/pdf/helpers/formatDate');
const { getCompanyForDocuments } = require('../settings/settings.service');
const { rowsToCsv } = require('../../utils/csv');
const {
EDITABLE_STATUSES,
SUBMITTABLE_STATUSES,
@ -429,24 +430,26 @@ const createPurchaseOrder = async (payload, userId, requestId) => {
return sanitizePo(created);
};
const buildPurchaseOrdersWhere = (query) => ({
deleted_at: null,
...(query.status ? { status: query.status } : {}),
...(query.po_type ? { po_type: query.po_type } : {}),
...(query.vendor_id ? { vendor_id: BigInt(query.vendor_id) } : {}),
...(query.plant_id ? { plant_id: BigInt(query.plant_id) } : {}),
...(query.search ? { po_number: { contains: query.search, mode: 'insensitive' } } : {}),
...(query.date_from || query.date_to
? {
po_date: {
...(query.date_from ? { gte: toDateOnly(query.date_from) } : {}),
...(query.date_to ? { lte: toDateOnly(query.date_to) } : {}),
},
}
: {}),
});
const listPurchaseOrders = async (query) => {
const { page, limit, skip } = getPagination(query);
const where = {
deleted_at: null,
...(query.status ? { status: query.status } : {}),
...(query.po_type ? { po_type: query.po_type } : {}),
...(query.vendor_id ? { vendor_id: BigInt(query.vendor_id) } : {}),
...(query.plant_id ? { plant_id: BigInt(query.plant_id) } : {}),
...(query.search ? { po_number: { contains: query.search, mode: 'insensitive' } } : {}),
...(query.date_from || query.date_to
? {
po_date: {
...(query.date_from ? { gte: toDateOnly(query.date_from) } : {}),
...(query.date_to ? { lte: toDateOnly(query.date_to) } : {}),
},
}
: {}),
};
const where = buildPurchaseOrdersWhere(query);
const [rows, total] = await Promise.all([
prisma.purchase_orders.findMany({
@ -462,6 +465,36 @@ const listPurchaseOrders = async (query) => {
return { data: rows.map(sanitizePo), meta: { page, limit, total } };
};
const exportPurchaseOrders = async (query) => {
const rows = await prisma.purchase_orders.findMany({
where: buildPurchaseOrdersWhere(query),
include: poListInclude,
orderBy: { created_at: 'desc' },
});
return rowsToCsv(
[
{ key: 'po_number', header: 'PO Number' },
{ key: 'po_date', header: 'PO Date', type: 'date' },
{ key: 'po_type', header: 'Type' },
{ key: 'status', header: 'Status' },
{ key: 'revision_no', header: 'Revision' },
{ key: (row) => row.vendor?.vendor_code || '', header: 'Vendor Code' },
{ key: (row) => row.vendor?.vendor_name || '', header: 'Vendor Name' },
{ key: (row) => row.plant?.name || '', header: 'Plant' },
{ key: (row) => row.warehouse?.name || '', header: 'Warehouse' },
{ key: (row) => row.brand?.name || '', header: 'Brand' },
{ key: 'expected_delivery_date', header: 'Expected Delivery', type: 'date' },
{ key: 'sub_total', header: 'Sub Total' },
{ key: 'tax_total', header: 'Tax Total' },
{ key: 'grand_total', header: 'Grand Total' },
{ key: (row) => row.created_by_user?.full_name || '', header: 'Created By' },
{ key: 'created_at', header: 'Created At', type: 'datetime' },
],
rows.map(sanitizePo)
);
};
const getPurchaseOrderById = async (id) =>
sanitizePo(await getPoOrThrow(id, { includeItems: true }));
@ -828,6 +861,7 @@ const getPurchaseOrderPdf = async (id) => {
module.exports = {
createPurchaseOrder,
listPurchaseOrders,
exportPurchaseOrders,
getPurchaseOrderById,
updatePurchaseOrder,
deletePurchaseOrder,

View File

@ -107,6 +107,11 @@ const listPurchaseOrdersQuerySchema = Joi.object({
date_to: Joi.date().iso().optional(),
});
const exportPurchaseOrdersQuerySchema = listPurchaseOrdersQuerySchema.keys({
page: Joi.strip(),
limit: Joi.strip(),
});
const workflowRemarksSchema = Joi.object({
remarks: Joi.string().allow(null, '').optional(),
});
@ -120,6 +125,7 @@ module.exports = {
updatePurchaseOrderSchema,
amendPurchaseOrderSchema,
listPurchaseOrdersQuerySchema,
exportPurchaseOrdersQuerySchema,
workflowRemarksSchema,
rejectPurchaseOrderSchema,
};

View File

@ -15,6 +15,30 @@ const buildPublicUrl = (filePath) => {
return normalized.startsWith('/') ? normalized : `/${normalized}`;
};
const MIME_BY_EXT = {
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.webp': 'image/webp',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
};
const buildLogoDataUri = (logoPath) => {
if (!logoPath) return null;
const absolute = path.isAbsolute(logoPath)
? logoPath
: path.resolve(process.cwd(), logoPath);
if (!fs.existsSync(absolute)) return null;
const ext = path.extname(absolute).toLowerCase();
const mime = MIME_BY_EXT[ext] || 'image/png';
const base64 = fs.readFileSync(absolute).toString('base64');
return `data:${mime};base64,${base64}`;
};
const unlinkIfExists = (relativePath) => {
if (!relativePath) return;
const absolute = path.resolve(process.cwd(), relativePath);
@ -218,7 +242,9 @@ const getCompanyForDocuments = async () => {
phone: row.mobile || '',
email: row.email || '',
website: row.website || '',
logo_path: row.logo_path || null,
logo_url: buildPublicUrl(row.logo_path),
logo_src: buildLogoDataUri(row.logo_path),
favicon_url: buildPublicUrl(row.favicon_path),
};
};

View File

@ -12,6 +12,13 @@ const list = asyncHandler(async (req, res) => {
res.json(new ApiResponse(200, result.data, 'Vendors fetched', result.meta));
});
const exportCsv = asyncHandler(async (req, res) => {
const csv = await service.exportVendors(req.query);
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader('Content-Disposition', 'attachment; filename="vendors-export.csv"');
res.send(csv);
});
const listGstTreatments = asyncHandler(async (_req, res) => {
const data = service.listGstTreatments();
res.json(new ApiResponse(200, data, 'GST treatment options fetched'));
@ -184,6 +191,7 @@ const removeItemMapping = asyncHandler(async (req, res) => {
module.exports = {
create,
list,
exportCsv,
listGstTreatments,
listSourceOfSupplyOptions,
getOne,

View File

@ -8,6 +8,7 @@ const {
updateVendorSchema,
vendorStatusSchema,
listVendorsQuerySchema,
exportVendorsQuerySchema,
createAddressSchema,
updateAddressSchema,
createContactSchema,
@ -22,6 +23,12 @@ const router = express.Router();
router.use(authenticate);
router.get(
'/export',
authorize('VENDOR', 'export'),
validate(exportVendorsQuerySchema, 'query'),
controller.exportCsv
);
router.get(
'/',
authorize('VENDOR', 'view'),

View File

@ -4,6 +4,7 @@ const auditLog = require('../../utils/auditLog');
const { getPagination } = require('../../utils/pagination');
const { nextDocumentNumber } = require('../../utils/generateCode');
const { encrypt, decrypt, blindIndex } = require('../../utils/encryption');
const { rowsToCsv } = require('../../utils/csv');
const {
GST_TREATMENTS,
SOURCE_OF_SUPPLY_OPTIONS,
@ -166,6 +167,33 @@ const listVendors = async (query) => {
return { data: rows.map((row) => sanitizeVendor(row)), meta: { page, limit, total } };
};
const exportVendors = async (query) => {
const where = buildVendorsWhere(query);
const rows = await prisma.vendors.findMany({
where,
include: vendorInclude,
orderBy: { created_at: 'desc' },
});
return rowsToCsv(
[
{ key: 'vendor_code', header: 'Vendor Code' },
{ key: 'vendor_name', header: 'Vendor Name' },
{ key: 'vendor_type', header: 'Vendor Type' },
{ key: 'gstin', header: 'GSTIN' },
{ key: 'pan', header: 'PAN' },
{ key: 'gst_treatment', header: 'GST Treatment' },
{ key: 'source_of_supply', header: 'Source of Supply' },
{ key: (row) => row.payment_terms?.name || '', header: 'Payment Term' },
{ key: 'credit_period_days', header: 'Credit Days' },
{ key: 'status', header: 'Status' },
{ key: 'is_active', header: 'Active' },
{ key: 'created_at', header: 'Created At', type: 'datetime' },
],
rows.map((row) => sanitizeVendor(row))
);
};
const listGstTreatments = () => GST_TREATMENTS;
const listSourceOfSupplyOptions = () => SOURCE_OF_SUPPLY_OPTIONS;
@ -657,6 +685,7 @@ const deleteItemMapping = async (vendorId, mappingId, userId, requestId) => {
module.exports = {
createVendor,
listVendors,
exportVendors,
listGstTreatments,
listSourceOfSupplyOptions,
getVendorById,

View File

@ -70,6 +70,11 @@ const listVendorsQuerySchema = Joi.object({
is_active: Joi.boolean().optional(),
});
const exportVendorsQuerySchema = listVendorsQuerySchema.keys({
page: Joi.strip(),
limit: Joi.strip(),
});
const createAddressSchema = Joi.object({
address_type: Joi.string()
.valid(...addressTypes)
@ -145,6 +150,7 @@ module.exports = {
updateVendorSchema,
vendorStatusSchema,
listVendorsQuerySchema,
exportVendorsQuerySchema,
createAddressSchema,
updateAddressSchema,
createContactSchema,

View File

@ -207,6 +207,19 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
align-items: flex-start;
gap: 24px;
}
.brand {
display: flex;
gap: 12px;
align-items: flex-start;
flex: 1;
min-width: 0;
}
.company-logo {
width: 56px;
height: 56px;
object-fit: contain;
flex-shrink: 0;
}
.company-name {
margin: 0;
font-size: 20px;
@ -383,11 +396,18 @@ const generateGrnHtml = (inputData = getDummyGrnData()) => {
<body>
<div class="document">
<div class="header">
<div>
<h1 class="company-name">${escapeHtml(company.name || 'Company')}</h1>
<div class="company-meta">
${escapeHtml(companyAddressLine(company))}<br />
${escapeHtml(companyContactLine(company))}
<div class="brand">
${
company.logo_src
? `<img class="company-logo" src="${company.logo_src}" alt="Company logo" />`
: ''
}
<div>
<h1 class="company-name">${escapeHtml(company.name || 'Company')}</h1>
<div class="company-meta">
${escapeHtml(companyAddressLine(company))}<br />
${escapeHtml(companyContactLine(company))}
</div>
</div>
</div>
<div class="title-block">

View File

@ -205,6 +205,19 @@ const generatePoHtml = (inputData = getDummyPoData()) => {
align-items: flex-start;
gap: 24px;
}
.brand {
display: flex;
gap: 12px;
align-items: flex-start;
flex: 1;
min-width: 0;
}
.company-logo {
width: 56px;
height: 56px;
object-fit: contain;
flex-shrink: 0;
}
.company-name {
margin: 0;
font-size: 20px;
@ -391,11 +404,18 @@ const generatePoHtml = (inputData = getDummyPoData()) => {
<body>
<div class="document">
<div class="header">
<div>
<h1 class="company-name">${escapeHtml(company.name || 'Company')}</h1>
<div class="company-meta">
${escapeHtml(companyAddressLine(company))}<br />
${escapeHtml(companyContactLine(company))}
<div class="brand">
${
company.logo_src
? `<img class="company-logo" src="${company.logo_src}" alt="Company logo" />`
: ''
}
<div>
<h1 class="company-name">${escapeHtml(company.name || 'Company')}</h1>
<div class="company-meta">
${escapeHtml(companyAddressLine(company))}<br />
${escapeHtml(companyContactLine(company))}
</div>
</div>
</div>
<div class="title-block">