feat: implement adminCreate endpoint and corresponding service method for manual invoice creation
This commit is contained in:
parent
2f79a4a8e5
commit
7be7ea7269
@ -96,4 +96,34 @@ module.exports = {
|
|||||||
return res.status(400).json({ success: false, message: e.message });
|
return res.status(400).json({ success: false, message: e.message });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async adminCreate(req, res) {
|
||||||
|
try {
|
||||||
|
const fields = {
|
||||||
|
buyer_name: req.body.buyer_name,
|
||||||
|
buyer_email: req.body.buyer_email,
|
||||||
|
buyer_street: req.body.buyer_street,
|
||||||
|
buyer_postal_code: req.body.buyer_postal_code,
|
||||||
|
buyer_city: req.body.buyer_city,
|
||||||
|
buyer_country: req.body.buyer_country,
|
||||||
|
currency: req.body.currency || 'EUR',
|
||||||
|
total_net: req.body.total_net,
|
||||||
|
total_tax: req.body.total_tax,
|
||||||
|
total_gross: req.body.total_gross,
|
||||||
|
vat_rate: req.body.vat_rate,
|
||||||
|
status: req.body.status || 'issued',
|
||||||
|
issued_at: req.body.issued_at,
|
||||||
|
due_at: req.body.due_at,
|
||||||
|
};
|
||||||
|
if (!fields.total_gross || isNaN(Number(fields.total_gross))) {
|
||||||
|
return res.status(400).json({ success: false, message: 'total_gross is required and must be a number' });
|
||||||
|
}
|
||||||
|
const pdfBuffer = req.file ? req.file.buffer : null;
|
||||||
|
const data = await service.adminCreateManual(fields, pdfBuffer);
|
||||||
|
return res.status(201).json({ success: true, data });
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[INVOICE ADMIN CREATE]', e);
|
||||||
|
return res.status(400).json({ success: false, message: e.message });
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -225,6 +225,57 @@ class InvoiceRepository {
|
|||||||
);
|
);
|
||||||
return rows || [];
|
return rows || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createManualInvoice({
|
||||||
|
source_type = 'manual',
|
||||||
|
source_id = null,
|
||||||
|
user_id = null,
|
||||||
|
buyer_name = null,
|
||||||
|
buyer_email = null,
|
||||||
|
buyer_street = null,
|
||||||
|
buyer_postal_code = null,
|
||||||
|
buyer_city = null,
|
||||||
|
buyer_country = null,
|
||||||
|
currency = 'EUR',
|
||||||
|
total_net = 0,
|
||||||
|
total_tax = 0,
|
||||||
|
total_gross = 0,
|
||||||
|
vat_rate = null,
|
||||||
|
status = 'issued',
|
||||||
|
issued_at = null,
|
||||||
|
due_at = null,
|
||||||
|
context = null,
|
||||||
|
}) {
|
||||||
|
const invoice_number = await genInvoiceNumber();
|
||||||
|
const [res] = await pool.query(
|
||||||
|
`INSERT INTO invoices
|
||||||
|
(invoice_number, user_id, source_type, source_id, buyer_name, buyer_email, buyer_street, buyer_postal_code, buyer_city, buyer_country,
|
||||||
|
currency, total_net, total_tax, total_gross, vat_rate, status, issued_at, due_at, pdf_storage_key, context, created_at, updated_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, NOW(), NOW())`,
|
||||||
|
[
|
||||||
|
invoice_number,
|
||||||
|
user_id || null,
|
||||||
|
source_type,
|
||||||
|
source_id || 0,
|
||||||
|
buyer_name || null,
|
||||||
|
buyer_email || null,
|
||||||
|
buyer_street || null,
|
||||||
|
buyer_postal_code || null,
|
||||||
|
buyer_city || null,
|
||||||
|
buyer_country || null,
|
||||||
|
currency,
|
||||||
|
+Number(total_net).toFixed(2),
|
||||||
|
+Number(total_tax).toFixed(2),
|
||||||
|
+Number(total_gross).toFixed(2),
|
||||||
|
vat_rate != null ? Number(vat_rate) : null,
|
||||||
|
status,
|
||||||
|
issued_at || null,
|
||||||
|
due_at || null,
|
||||||
|
context ? JSON.stringify(context) : null,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
return this.getById(res.insertId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = InvoiceRepository;
|
module.exports = InvoiceRepository;
|
||||||
|
|||||||
@ -188,6 +188,7 @@ router.post('/abonements/referred', authMiddleware, ensureUserFromBody, Abonemme
|
|||||||
// NEW: Invoice POSTs
|
// NEW: Invoice POSTs
|
||||||
router.post('/invoices/:id/pay', authMiddleware, adminOnly, InvoiceController.pay);
|
router.post('/invoices/:id/pay', authMiddleware, adminOnly, InvoiceController.pay);
|
||||||
router.post('/admin/invoices/email-report', authMiddleware, adminOnly, InvoiceController.sendEmailReport);
|
router.post('/admin/invoices/email-report', authMiddleware, adminOnly, InvoiceController.sendEmailReport);
|
||||||
|
router.post('/admin/invoices', authMiddleware, adminOnly, upload.single('pdf'), InvoiceController.adminCreate);
|
||||||
|
|
||||||
// Existing registration handlers (keep)
|
// Existing registration handlers (keep)
|
||||||
router.post('/register/personal', (req, res) => {
|
router.post('/register/personal', (req, res) => {
|
||||||
|
|||||||
@ -949,6 +949,42 @@ class InvoiceService {
|
|||||||
|
|
||||||
return { sentCount: paidInvoices.length };
|
return { sentCount: paidInvoices.length };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async adminCreateManual(fields, pdfBuffer = null) {
|
||||||
|
const { uploadBuffer } = require('../../utils/exoscaleUploader');
|
||||||
|
|
||||||
|
const invoice = await this.repo.createManualInvoice({
|
||||||
|
source_type: 'manual',
|
||||||
|
buyer_name: fields.buyer_name || null,
|
||||||
|
buyer_email: fields.buyer_email || null,
|
||||||
|
buyer_street: fields.buyer_street || null,
|
||||||
|
buyer_postal_code: fields.buyer_postal_code || null,
|
||||||
|
buyer_city: fields.buyer_city || null,
|
||||||
|
buyer_country: fields.buyer_country || null,
|
||||||
|
currency: fields.currency || 'EUR',
|
||||||
|
total_net: fields.total_net != null ? Number(fields.total_net) : 0,
|
||||||
|
total_tax: fields.total_tax != null ? Number(fields.total_tax) : 0,
|
||||||
|
total_gross: Number(fields.total_gross || 0),
|
||||||
|
vat_rate: fields.vat_rate != null ? Number(fields.vat_rate) : null,
|
||||||
|
status: fields.status || 'issued',
|
||||||
|
issued_at: fields.issued_at ? new Date(fields.issued_at) : new Date(),
|
||||||
|
due_at: fields.due_at ? new Date(fields.due_at) : null,
|
||||||
|
context: { source: 'admin_manual_upload' },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pdfBuffer && pdfBuffer.length > 0) {
|
||||||
|
const { objectKey } = await uploadBuffer(
|
||||||
|
pdfBuffer,
|
||||||
|
`invoice-${invoice.invoice_number}.pdf`,
|
||||||
|
'application/pdf',
|
||||||
|
`invoices/admin/${invoice.id}`,
|
||||||
|
);
|
||||||
|
await this.repo.updateStorageKey(invoice.id, objectKey);
|
||||||
|
return this.repo.getById(invoice.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoice;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = InvoiceService;
|
module.exports = InvoiceService;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user