fix for createRefund
This commit is contained in:
@@ -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,8 +950,39 @@ 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 { companyId, baseUrl } = getClientInfo();
|
||||||
const payload = {
|
const isCreditCard = depositAcct.account_type === 'Credit Card';
|
||||||
|
|
||||||
|
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 },
|
DepositToAccountRef: { value: depositAcct.qbo_id, name: depositAcct.name },
|
||||||
TxnDate: data.txnDate,
|
TxnDate: data.txnDate,
|
||||||
Line: [{
|
Line: [{
|
||||||
@@ -963,18 +991,19 @@ async function createRefund(data) {
|
|||||||
Description: data.memo || `Refund from ${vendorName}`,
|
Description: data.memo || `Refund from ${vendorName}`,
|
||||||
DepositLineDetail: {
|
DepositLineDetail: {
|
||||||
AccountRef: { value: categoryAcct.qbo_id, name: categoryAcct.name },
|
AccountRef: { value: categoryAcct.qbo_id, name: categoryAcct.name },
|
||||||
Entity: { value: data.vendorId, type: 'Vendor' }
|
// 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.refNo) payload.Line[0].DepositLineDetail.CheckNum = String(data.refNo).slice(0, 21);
|
||||||
if (data.memo) payload.PrivateNote = String(data.memo);
|
if (data.memo) payload.PrivateNote = String(data.memo);
|
||||||
|
}
|
||||||
|
|
||||||
const { companyId, baseUrl } = getClientInfo();
|
const requestSummary = `REFUND (${entityTypeLabel}) | ${vendorName} → ${depositAcct.name} | ${categoryAcct.name} | ${data.txnDate} | $${amount.toFixed(2)}`;
|
||||||
const url = withMinorVersion(`${baseUrl}/v3/company/${companyId}/deposit`);
|
|
||||||
|
|
||||||
const requestSummary = `REFUND | ${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
|
||||||
|
|||||||
Reference in New Issue
Block a user