unit cost

This commit is contained in:
2026-05-29 09:39:01 -05:00
parent 7de57cb35e
commit 7c76a0aabe
4 changed files with 22 additions and 9 deletions

View File

@@ -0,0 +1 @@
ALTER TABLE invoice_items ADD COLUMN unit_cost character varying(50);

View File

@@ -101,7 +101,11 @@ export function addItem(containerId, { item = null, type = 'invoice', laborRate
<label class="block text-xs font-medium text-gray-700 mb-1">Rate</label> <label class="block text-xs font-medium text-gray-700 mb-1">Rate</label>
<input type="text" data-item="${itemId}" data-field="rate" value="${item ? item.rate : ''}" class="${cssClass} w-full px-2 py-2 border border-gray-300 rounded-md text-sm"> <input type="text" data-item="${itemId}" data-field="rate" value="${item ? item.rate : ''}" class="${cssClass} w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
</div> </div>
<div class="col-span-3"> <div class="col-span-1 cost-field" style="${(item && item.qbo_item_id && item.qbo_item_id != '9') ? 'display:none' : ''}">
<label class="block text-xs font-medium text-gray-700 mb-1">Cost</label>
<input type="text" data-item="${itemId}" data-field="unit_cost" value="${item && item.qbo_item_id == '9' ? (item.unit_cost || '') : ''}" class="${cssClass} w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
</div>
<div class="col-span-2">
<label class="block text-xs font-medium text-gray-700 mb-1">Amount</label> <label class="block text-xs font-medium text-gray-700 mb-1">Amount</label>
<input type="text" data-item="${itemId}" data-field="amount" value="${item ? item.amount : ''}" class="${amountClass} w-full px-2 py-2 border border-gray-300 rounded-md text-sm"> <input type="text" data-item="${itemId}" data-field="amount" value="${item ? item.amount : ''}" class="${amountClass} w-full px-2 py-2 border border-gray-300 rounded-md text-sm">
</div> </div>
@@ -209,7 +213,6 @@ export function handleTypeChange(selectEl, prefix, itemId) {
const rateInput = itemDiv.querySelector('[data-field="rate"]'); const rateInput = itemDiv.querySelector('[data-field="rate"]');
if (rateInput && (!rateInput.value || rateInput.value === '0')) { if (rateInput && (!rateInput.value || rateInput.value === '0')) {
rateInput.value = laborRate; rateInput.value = laborRate;
// Recalculate amount
const qtyInput = itemDiv.querySelector('[data-field="quantity"]'); const qtyInput = itemDiv.querySelector('[data-field="quantity"]');
const amountInput = itemDiv.querySelector('[data-field="amount"]'); const amountInput = itemDiv.querySelector('[data-field="amount"]');
if (qtyInput.value) { if (qtyInput.value) {
@@ -219,6 +222,11 @@ export function handleTypeChange(selectEl, prefix, itemId) {
} }
} }
const costField = itemDiv.querySelector('.cost-field');
if (costField) {
costField.style.display = (selectEl.value === '9') ? '' : 'none';
}
updateItemPreview(itemDiv); updateItemPreview(itemDiv);
onUpdate(); onUpdate();
} }
@@ -236,11 +244,15 @@ export function getItems(containerId) {
? descEditor.quillInstance.root.innerHTML ? descEditor.quillInstance.root.innerHTML
: ''; : '';
const qboItemId = div.querySelector('[data-field="qbo_item_id"]').value;
const unitCostInput = div.querySelector('[data-field="unit_cost"]');
items.push({ items.push({
quantity: div.querySelector('[data-field="quantity"]').value, quantity: div.querySelector('[data-field="quantity"]').value,
qbo_item_id: div.querySelector('[data-field="qbo_item_id"]').value, qbo_item_id: qboItemId,
description: descriptionHTML, description: descriptionHTML,
rate: div.querySelector('[data-field="rate"]').value, rate: div.querySelector('[data-field="rate"]').value,
unit_cost: (qboItemId === '9' && unitCostInput) ? unitCostInput.value : '',
amount: div.querySelector('[data-field="amount"]').value amount: div.querySelector('[data-field="amount"]').value
}); });
}); });

View File

@@ -88,10 +88,10 @@ CREATE TABLE public.invoice_items (
amount character varying(50) NOT NULL, amount character varying(50) NOT NULL,
item_order integer NOT NULL, item_order integer NOT NULL,
created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
qbo_item_id character varying(10) DEFAULT '9'::character varying qbo_item_id character varying(10) DEFAULT '9'::character varying,
unit_cost character varying(50)
); );
ALTER TABLE public.invoice_items OWNER TO quoteuser; ALTER TABLE public.invoice_items OWNER TO quoteuser;
-- --

View File

@@ -192,8 +192,8 @@ router.post('/', async (req, res) => {
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
await client.query( await client.query(
'INSERT INTO invoice_items (invoice_id, quantity, description, rate, amount, item_order, qbo_item_id) VALUES ($1, $2, $3, $4, $5, $6, $7)', 'INSERT INTO invoice_items (invoice_id, quantity, description, rate, amount, item_order, qbo_item_id, unit_cost) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)',
[invoiceId, items[i].quantity, items[i].description, items[i].rate, items[i].amount, i, items[i].qbo_item_id || '9'] [invoiceId, items[i].quantity, items[i].description, items[i].rate, items[i].amount, i, items[i].qbo_item_id || '9', items[i].unit_cost || null]
); );
} }
@@ -333,8 +333,8 @@ router.put('/:id', async (req, res) => {
await client.query('DELETE FROM invoice_items WHERE invoice_id = $1', [id]); await client.query('DELETE FROM invoice_items WHERE invoice_id = $1', [id]);
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
await client.query( await client.query(
'INSERT INTO invoice_items (invoice_id, quantity, description, rate, amount, item_order, qbo_item_id) VALUES ($1, $2, $3, $4, $5, $6, $7)', 'INSERT INTO invoice_items (invoice_id, quantity, description, rate, amount, item_order, qbo_item_id, unit_cost) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)',
[id, items[i].quantity, items[i].description, items[i].rate, items[i].amount, i, items[i].qbo_item_id || '9'] [id, items[i].quantity, items[i].description, items[i].rate, items[i].amount, i, items[i].qbo_item_id || '9', items[i].unit_cost || null]
); );
} }