Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0d84eb0
feat(pos): implement POS expense recording and summary features
Jun 15, 2026
11e8173
feat(expenses): enhance shift expense management and validation
Jun 16, 2026
76b721c
Add POS Expense Report to POSNEXT Workspace
Jun 16, 2026
d235ec7
Changes
Sep 8, 2026
ce5a868
Fixed. validate_expense_amount now takes a SELECT … FOR UPDATE on the…
Sep 8, 2026
362714b
1. Shift total from custom field — solved
Sep 8, 2026
78aa29d
reafactor(POS Expense Report)
Sep 8, 2026
258fdc6
Permission bypass — Dropped the cancel-path assertion on flags.ignore…
Sep 8, 2026
9431f26
Fixed. The closing-shift expense child table is now a proper audit tr…
Sep 8, 2026
603f5b0
Dropped the "USD" default. currency is now optional and resolves as p…
Sep 8, 2026
1869560
T1 closed. One real JE integration test now exercises the money path.
Sep 8, 2026
629c8b9
T4 closed. Three integration tests in test_pos_closing_shift.py now c…
Sep 9, 2026
64e46f3
L1 closed. ExpenseDialog in POSSale.vue now listens to both emits.
Sep 9, 2026
331c060
Refactor SQL query in get_expense_accounts to use f-string for limit …
Sep 9, 2026
7ae55ac
Fixed. _resolve_payment_account now only looks up Mode of Payment Acc…
MohamedAliSmk Sep 9, 2026
c6e4e3d
Closed: typed expense amounts, the shift limit, and JE booking now sh…
MohamedAliSmk Sep 9, 2026
0c62dc8
T1 and T4 no longer depend on leftover promotions data.
MohamedAliSmk Sep 9, 2026
6126b50
onUnmounted now clears accountSearchTimer, so a close within the 250 …
MohamedAliSmk Sep 9, 2026
dbaa7c8
Fixed. companyCurrency now reads shiftState.value.company?.default_cu…
MohamedAliSmk Sep 9, 2026
e80cbc7
chore(expenses): drop dead bootstrap company_currency and fix dialog …
MohamedAliSmk Sep 9, 2026
3ba3e24
style: restore blank line after imports in test_pos_closing_shift
MohamedAliSmk Sep 10, 2026
0d32e69
feat(expenses): implement offline expense handling and sync functiona…
MohamedAliSmk Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions POS/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ declare module 'vue' {
CustomerDialog: typeof import('./src/components/sale/CustomerDialog.vue')['default']
DraftInvoicesDialog: typeof import('./src/components/sale/DraftInvoicesDialog.vue')['default']
EditItemDialog: typeof import('./src/components/sale/EditItemDialog.vue')['default']
ExpenseDialog: typeof import('./src/components/sale/ExpenseDialog.vue')['default']
InstallAppBadge: typeof import('./src/components/common/InstallAppBadge.vue')['default']
InvoiceCart: typeof import('./src/components/sale/InvoiceCart.vue')['default']
InvoiceDetailDialog: typeof import('./src/components/invoices/InvoiceDetailDialog.vue')['default']
Expand Down
142 changes: 142 additions & 0 deletions POS/src/components/ShiftClosingDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@
</div>
</div>

<!-- POS Expenses -->
<div
v-if="hasExpenses"
class="text-start bg-amber-50 border border-amber-200 rounded-lg p-3 md:p-4"
>
<div class="text-amber-700 text-xs uppercase font-medium mb-1">
{{ __("POS Expenses") }}
</div>
<div
class="text-lg md:text-2xl font-bold text-amber-900 mb-0.5 md:mb-1 truncate"
>
-{{ formatCurrency(closingData.expenses_total) }}
</div>
<div class="text-amber-700 text-xs">
{{ __("{0} expenses", [closingData.expenses_count]) }}
</div>
</div>

<!-- Net Sales (after returns) -->
<div
class="text-start bg-green-50 border border-green-200 rounded-lg p-3 md:p-4"
Expand Down Expand Up @@ -152,6 +170,24 @@
</div>
</div>

<!-- Net Cash Impact -->
<div
v-if="hasExpenses"
class="text-start bg-purple-50 border border-purple-200 rounded-lg p-3 md:p-4"
>
<div class="text-purple-700 text-xs uppercase font-medium mb-1">
{{ __("Net Cash Impact") }}
</div>
<div
class="text-lg md:text-2xl font-bold text-purple-900 mb-0.5 md:mb-1 truncate"
>
{{ formatCurrency(netCashImpact) }}
</div>
<div class="text-purple-700 text-xs">
{{ __("Sales - Returns - Expenses") }}
</div>
</div>

<!-- Tax Collected -->
<div
class="text-start bg-gray-50 border border-gray-200 rounded-lg p-3 md:p-4"
Expand Down Expand Up @@ -473,6 +509,86 @@
</div>
</div>

<!-- POS Expenses Summary -->
<div
v-if="hasExpenses"
class="bg-white border border-gray-200 rounded-lg overflow-hidden shadow-sm"
>
<div class="px-3 py-3 md:px-6 md:py-4 border-b border-gray-200 bg-amber-50">
<h3 class="text-sm md:text-lg font-semibold text-gray-900 text-start">
{{ __("POS Expenses") }}
</h3>
<p class="text-xs md:text-sm text-gray-600 text-start">
{{
__(
"{0} expenses totaling {1}",
[
closingData.expenses_count,
formatCurrency(closingData.expenses_total),
]
)
}}
</p>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th
class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase"
>
{{ __("Journal Entry") }}
</th>
<th
class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase"
>
{{ __("Expense Account") }}
</th>
<th
class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase"
>
{{ __("Amount") }}
</th>
<th
class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase"
>
{{ __("Employee") }}
</th>
<th
class="px-4 py-3 text-start text-xs font-medium text-gray-500 uppercase"
>
{{ __("Remarks") }}
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
<tr
v-for="(expense, idx) in closingData.pos_expenses"
:key="expense.journal_entry || idx"
>
<td class="px-4 py-3 text-sm text-gray-900 text-start">
{{ expense.journal_entry || __("N/A") }}
</td>
<td class="px-4 py-3 text-sm text-gray-900 text-start">
{{ expense.expense_account }}
</td>
<td
class="px-4 py-3 text-sm font-semibold text-amber-800 text-start"
>
{{ formatCurrency(expense.amount) }}
</td>
<td class="px-4 py-3 text-sm text-gray-600 text-start">
{{ expense.employee || __("N/A") }}
</td>
<td class="px-4 py-3 text-sm text-gray-600 text-start">
{{ expense.remarks || __("N/A") }}
</td>
</tr>
</tbody>
</table>
</div>
</div>

<!-- Payment Reconciliation -->
<div
class="bg-white border border-gray-200 rounded-lg overflow-hidden shadow-sm"
Expand Down Expand Up @@ -629,6 +745,17 @@
)
"
/>
<p
v-if="payment.expense_amount > 0"
class="text-xs md:text-sm text-amber-700 text-start"
>
{{
__(
"Expenses: -{0}",
[formatCurrency(payment.expense_amount)],
)
}}
</p>
</div>
</div>

Expand Down Expand Up @@ -1303,6 +1430,21 @@ const hasReturns = computed(() => {
return (closingData.value.returns_count || 0) > 0;
});

const hasExpenses = computed(() => {
if (!closingData.value) return false;
return (closingData.value.expenses_count || 0) > 0;
});

const netCashImpact = computed(() => {
if (!closingData.value) return 0;
const sales = Number.parseFloat(
closingData.value.sales_total || closingData.value.grand_total || 0,
);
const returns = Number.parseFloat(closingData.value.returns_total || 0);
const expenses = Number.parseFloat(closingData.value.expenses_total || 0);
return sales - returns - expenses;
});

// Count of sales invoices (non-returns)
const salesInvoiceCount = computed(() => {
if (!closingData.value) return 0;
Expand Down
Loading
Loading