fix for createRefund

This commit is contained in:
2026-05-25 14:49:01 -05:00
parent 7e490dbc93
commit dc778d2f5f

View File

@@ -900,10 +900,7 @@ async function createExpense(data) {
/** /**
* Erstellt einen QBO Deposit für einen Vendor-Refund (Geld kam zurück aufs Konto). * Erstellt einen QBO Deposit oder Purchase Credit für einen Vendor-Refund.
*
* Buchhalterisch: Der Refund wird gegen die ursprüngliche Expense-Kategorie
* gebucht, wodurch sich der Aufwand in dieser Kategorie reduziert.
* *
* @param {Object} data * @param {Object} data
* @param {string} data.vendorId - Pflicht (von wem kam der Refund) * @param {string} data.vendorId - Pflicht (von wem kam der Refund)
@@ -953,28 +950,60 @@ async function createRefund(data) {
); );
const vendorName = vendorRow.rows[0]?.display_name || data.vendorId; const vendorName = vendorRow.rows[0]?.display_name || data.vendorId;
// ── QBO Deposit Payload ──
const payload = {
DepositToAccountRef: { value: depositAcct.qbo_id, name: depositAcct.name },
TxnDate: data.txnDate,
Line: [{
DetailType: 'DepositLineDetail',
Amount: amount,
Description: data.memo || `Refund from ${vendorName}`,
DepositLineDetail: {
AccountRef: { value: categoryAcct.qbo_id, name: categoryAcct.name },
Entity: { value: data.vendorId, type: 'Vendor' }
}
}]
};
if (data.refNo) payload.Line[0].DepositLineDetail.CheckNum = String(data.refNo).slice(0, 21);
if (data.memo) payload.PrivateNote = String(data.memo);
const { companyId, baseUrl } = getClientInfo(); const { companyId, baseUrl } = getClientInfo();
const url = withMinorVersion(`${baseUrl}/v3/company/${companyId}/deposit`); const isCreditCard = depositAcct.account_type === 'Credit Card';
const requestSummary = `REFUND | ${vendorName}${depositAcct.name} | ${categoryAcct.name} | ${data.txnDate} | $${amount.toFixed(2)}`; let url;
let payload;
let entityTypeLabel;
// ── Verzweigung: Credit Card Credit (Purchase) vs. Bank (Deposit) ──
if (isCreditCard) {
entityTypeLabel = 'Purchase';
url = withMinorVersion(`${baseUrl}/v3/company/${companyId}/purchase`);
payload = {
AccountRef: { value: depositAcct.qbo_id, name: depositAcct.name },
EntityRef: { value: data.vendorId, type: 'Vendor', name: vendorName },
TxnDate: data.txnDate,
PaymentType: 'CreditCard',
Credit: true, // Zwingend erforderlich für einen CC Refund
Line: [{
DetailType: 'AccountBasedExpenseLineDetail',
Amount: amount,
Description: data.memo || `Refund from ${vendorName}`,
AccountBasedExpenseLineDetail: {
AccountRef: { value: categoryAcct.qbo_id, name: categoryAcct.name }
}
}]
};
if (data.refNo) payload.DocNumber = String(data.refNo).slice(0, 21);
if (data.memo) payload.PrivateNote = String(data.memo);
} else {
entityTypeLabel = 'Deposit';
url = withMinorVersion(`${baseUrl}/v3/company/${companyId}/deposit`);
payload = {
DepositToAccountRef: { value: depositAcct.qbo_id, name: depositAcct.name },
TxnDate: data.txnDate,
Line: [{
DetailType: 'DepositLineDetail',
Amount: amount,
Description: data.memo || `Refund from ${vendorName}`,
DepositLineDetail: {
AccountRef: { value: categoryAcct.qbo_id, name: categoryAcct.name },
// Korrigierte QBO Entity-Struktur
Entity: {
Type: 'Vendor',
EntityRef: { value: data.vendorId, name: vendorName }
}
}
}]
};
if (data.refNo) payload.Line[0].DepositLineDetail.CheckNum = String(data.refNo).slice(0, 21);
if (data.memo) payload.PrivateNote = String(data.memo);
}
const requestSummary = `REFUND (${entityTypeLabel}) | ${vendorName}${depositAcct.name} | ${categoryAcct.name} | ${data.txnDate} | $${amount.toFixed(2)}`;
let qboResponse; let qboResponse;
try { try {
@@ -988,7 +1017,7 @@ async function createRefund(data) {
} catch (err) { } catch (err) {
await writeAuditLog({ await writeAuditLog({
action: 'refund.create', action: 'refund.create',
entityType: 'Deposit', entityType: entityTypeLabel,
status: 'error', status: 'error',
requestExcerpt: requestSummary, requestExcerpt: requestSummary,
responseExcerpt: err.message responseExcerpt: err.message
@@ -1002,34 +1031,35 @@ async function createRefund(data) {
).join('; '); ).join('; ');
await writeAuditLog({ await writeAuditLog({
action: 'refund.create', action: 'refund.create',
entityType: 'Deposit', entityType: entityTypeLabel,
status: 'error', status: 'error',
requestExcerpt: requestSummary, requestExcerpt: requestSummary,
responseExcerpt: msg responseExcerpt: msg
}); });
const err = new Error('QBO Deposit (refund) create failed: ' + msg); const err = new Error(`QBO ${entityTypeLabel} (refund) create failed: ${msg}`);
err.qboFault = qboResponse.Fault; err.qboFault = qboResponse.Fault;
throw err; throw err;
} }
const deposit = qboResponse.Deposit; // Dynamisches Extrahieren basierend auf dem Entity-Typ
if (!deposit || !deposit.Id) throw new Error('QBO returned no Deposit id'); const resultEntity = isCreditCard ? qboResponse.Purchase : qboResponse.Deposit;
if (!resultEntity || !resultEntity.Id) throw new Error(`QBO returned no ${entityTypeLabel} id`);
await writeAuditLog({ await writeAuditLog({
action: 'refund.create', action: 'refund.create',
entityType: 'Deposit', entityType: entityTypeLabel,
entityQboId: deposit.Id, entityQboId: resultEntity.Id,
status: 'success', status: 'success',
requestExcerpt: requestSummary, requestExcerpt: requestSummary,
responseExcerpt: `Deposit ${deposit.Id} created, total $${Number(deposit.TotalAmt).toFixed(2)}` responseExcerpt: `${entityTypeLabel} ${resultEntity.Id} created, total $${Number(resultEntity.TotalAmt).toFixed(2)}`
}); });
console.log(`✅ QBO Refund recorded: Deposit ${deposit.Id}${requestSummary}`); console.log(`✅ QBO Refund recorded: ${entityTypeLabel} ${resultEntity.Id}${requestSummary}`);
return { return {
id: deposit.Id, id: resultEntity.Id,
txnDate: deposit.TxnDate, txnDate: resultEntity.TxnDate,
totalAmt: Number(deposit.TotalAmt), totalAmt: Number(resultEntity.TotalAmt),
vendorName, vendorName,
depositAccountName: depositAcct.name, depositAccountName: depositAcct.name,
categoryName: categoryAcct.name categoryName: categoryAcct.name