Skip to content

Commit 1cbdfcc

Browse files
committed
Merge branch 'develop' of https://github.com/BrainWise-DEV/POSNext into develop
2 parents 62635cf + b119801 commit 1cbdfcc

28 files changed

Lines changed: 822 additions & 559 deletions

CHANGELOG.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,83 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
- **Tax Inclusive Setting**
12+
- New "Tax Inclusive" toggle in POS Settings under Pricing & Discounts
13+
- When enabled, displayed prices include tax (tax is part of the price)
14+
- When disabled, tax is calculated separately and added to the price
15+
- Changes apply immediately to cart when saved
16+
- Backend API endpoints for tax-inclusive invoice creation
17+
- Comprehensive tax calculation hooks in sales invoice processing
18+
- **Custom Toast Notification System**
19+
- Implemented custom `useToast` composable for consistent notifications across the app
20+
- Centralized toast notification management
21+
- Cleaner code with standardized success/error/info message handling
22+
- **POS Profile Enhancement**
23+
- New custom field to create POS Invoices instead of Sales Invoices for better performance
24+
- Improved invoice creation workflow
25+
- **Centralized Formatting Utilities**
26+
- Created `useFormatters` composable for consistent formatting across all components
27+
- Includes formatters for currency, quantity, dates, times, and percentages
28+
- Eliminates code duplication and ensures consistency
29+
- **Smart Quantity Controls**
30+
- Intelligent step detection for quantity increment/decrement buttons
31+
- Automatically adjusts step size based on current value (1 for integers, 0.5 for halves, 0.25 for quarters, etc.)
32+
- Natural counting experience for both whole and fractional items
33+
34+
### Changed
35+
- **Enhanced Quantity Precision**
36+
- Quantity formatting now supports up to 4 decimal places (e.g., 0.25, 0.125, 0.3333)
37+
- Smart rounding removes trailing zeros for cleaner display (2.0 → 2, 0.5 → 0.5)
38+
- Precision increased from 2 to 4 decimal places across all quantity calculations
39+
- **Improved Cart Quantity Input**
40+
- Input field width increased by 60% for better visibility (64px/80px on mobile/tablet)
41+
- Font size increased for easier reading
42+
- Now accepts any decimal value without interruption during typing
43+
- Validation and rounding only occurs when input loses focus
44+
- Added Enter key support to quickly confirm changes
45+
- Minimum value set to 0.0001 for maximum flexibility
46+
- **Mobile Header Optimization**
47+
- Version badge hidden on mobile devices to prevent overflow
48+
- Clock icon removed from mobile time display for cleaner UI
49+
- Improved responsive spacing and flex layout
50+
- Added horizontal scroll prevention at multiple levels
51+
52+
### Fixed
53+
- **Cart Quantity Display Issues**
54+
- Fixed fractional quantities showing incorrect precision (0.2 instead of 0.25)
55+
- Quantity input now properly displays and accepts multi-decimal values
56+
- Items are automatically removed from cart when quantity reaches zero
57+
- **Mobile UI Issues**
58+
- Fixed header overflow caused by version badge on small screens
59+
- Prevented horizontal scrolling on mobile devices
60+
- Fixed text truncation issues in header components
61+
- **Formatting Function Conflicts**
62+
- Resolved duplicate function declaration errors in Vue components
63+
- Changed formatters from named exports to composable-only exports to prevent conflicts
64+
- **Closing Shift Display**
65+
- Fixed "Items Sold" showing excessive decimal places (6.600000000000 → 6.6)
66+
- Applied proper formatting to all numeric fields in closing shift dialog
67+
- Tax rates now display with proper precision
68+
69+
### Improved
70+
- **Code Quality**
71+
- Refactored toast notifications reducing code by 250+ lines across 10+ components
72+
- Replaced direct toast calls with centralized composable pattern
73+
- Refactored formatting functions from 10+ components into single reusable composable
74+
- Removed duplicate code across ShiftClosingDialog, ShiftOpeningDialog, InvoiceHistoryDialog, etc.
75+
- Better separation of concerns with dedicated formatting utilities
76+
- Cleaner component code with standardized notification patterns
77+
- **User Experience**
78+
- Larger, more accessible quantity input fields
79+
- Clearer visual feedback during quantity editing
80+
- More intuitive increment/decrement behavior
81+
- Cleaner mobile interface with reduced visual clutter
82+
- Consistent notification styling and behavior across the app
83+
- **Performance**
84+
- Option to use POS Invoices instead of Sales Invoices for faster invoice creation
85+
- Reduced component complexity through code refactoring
86+
1087
## [1.1.1] - 2025-10-29
1188

1289
### Added

POS/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ The development server is configured to proxy your frappe app (usually running o
4141
If you notice the browser URL is `/frontend`, this is the base URL where your frontend app will run in production.
4242
To change this, open `src/router.js` and change the base URL passed to `createWebHistory`.
4343

44+
## Source maps in production builds
45+
46+
Production builds omit JavaScript source maps by default to keep bundle sizes small. If you need source maps for debugging a specific deployment, set the environment variable `POS_NEXT_ENABLE_SOURCEMAP=true` before running the build command. Any value other than the string `"true"` will keep source maps disabled.
47+
4448
## Resources
4549

4650
- [Vue 3](https://v3.vuejs.org/guide/introduction.html)

POS/src/components/ShiftClosingDialog.vue

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
<!-- Items Sold -->
4242
<div class="bg-gray-50 border border-gray-200 rounded-lg p-3 md:p-4">
4343
<div class="text-gray-600 text-xs uppercase font-medium mb-1">Items Sold</div>
44-
<div class="text-lg md:text-2xl font-bold text-gray-900 mb-0.5 md:mb-1">{{ closingData.total_quantity || 0 }}</div>
44+
<div class="text-lg md:text-2xl font-bold text-gray-900 mb-0.5 md:mb-1">{{ formatQuantity(closingData.total_quantity) }}</div>
4545
<div class="text-gray-600 text-xs">Total items</div>
4646
</div>
4747

@@ -328,7 +328,7 @@
328328
<div v-for="(tax, idx) in closingData.taxes" :key="idx" class="flex items-center justify-between py-2 border-b border-gray-100 last:border-0">
329329
<div>
330330
<p class="text-xs md:text-sm font-medium text-gray-900">{{ tax.account_head }}</p>
331-
<p class="text-xs text-gray-500">{{ tax.rate }}%</p>
331+
<p class="text-xs text-gray-500">{{ formatQuantity(tax.rate) }}%</p>
332332
</div>
333333
<div class="text-right">
334334
<p class="text-sm md:text-base font-semibold text-gray-900">{{ formatCurrency(tax.amount) }}</p>
@@ -409,6 +409,7 @@
409409
import { Button, Dialog, Input } from "frappe-ui"
410410
import { computed, reactive, ref, watch } from "vue"
411411
import { useShift } from "../composables/useShift"
412+
import { useFormatters } from "../composables/useFormatters"
412413
413414
const props = defineProps({
414415
modelValue: Boolean,
@@ -423,6 +424,7 @@ const open = computed({
423424
})
424425
425426
const { getClosingShiftData, submitClosingShift } = useShift()
427+
const { formatCurrency, formatQuantity, formatDateTime, formatTime } = useFormatters()
426428
427429
const closingData = ref(null)
428430
const closingDataResource = getClosingShiftData
@@ -521,25 +523,6 @@ function closeDialog() {
521523
showInvoiceDetails.value = false
522524
}
523525
524-
// Formatting Functions
525-
function formatCurrency(amount) {
526-
if (amount === null || amount === undefined) return "0.00"
527-
return Number.parseFloat(amount).toFixed(2)
528-
}
529-
530-
function formatDateTime(datetime) {
531-
if (!datetime) return ""
532-
return new Date(datetime).toLocaleString()
533-
}
534-
535-
function formatTime(datetime) {
536-
if (!datetime) return ""
537-
return new Date(datetime).toLocaleTimeString([], {
538-
hour: "2-digit",
539-
minute: "2-digit",
540-
})
541-
}
542-
543526
// Calculation Functions
544527
function getInvoiceCount() {
545528
if (!closingData.value) return 0

POS/src/components/ShiftOpeningDialog.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ import { Button, Dialog, Input } from "frappe-ui"
187187
import { createResource } from "frappe-ui"
188188
import { computed, ref, watch } from "vue"
189189
import { useShift } from "../composables/useShift"
190+
import { useFormatters } from "../composables/useFormatters"
190191
import ShiftClosingDialog from "./ShiftClosingDialog.vue"
191192
192193
const props = defineProps({
@@ -202,6 +203,7 @@ const open = computed({
202203
203204
const { createOpeningShift, getOpeningDialogData, checkOpeningShift } =
204205
useShift()
206+
const { formatDateTime } = useFormatters()
205207
206208
const step = ref(1)
207209
const selectedProfile = ref(null)
@@ -344,11 +346,6 @@ function closeDialog() {
344346
open.value = false
345347
}
346348
347-
function formatDateTime(datetime) {
348-
if (!datetime) return ""
349-
return new Date(datetime).toLocaleString()
350-
}
351-
352349
async function handleExistingShiftClosed() {
353350
showClosingDialog.value = false
354351
const profileToRestore = restartProfileName.value

POS/src/components/invoices/InvoiceManagement.vue

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -518,9 +518,14 @@ import PaymentDialog from "@/components/sale/PaymentDialog.vue"
518518
import { useInvoiceFilters } from "@/composables/useInvoiceFilters"
519519
import { useInvoiceFiltersStore } from "@/stores/invoiceFilters"
520520
import { formatCurrency as formatCurrencyUtil } from "@/utils/currency"
521-
import { Button, call, toast } from "frappe-ui"
521+
import { useFormatters } from "@/composables/useFormatters"
522+
import { useToast } from "@/composables/useToast"
523+
import { Button, call } from "frappe-ui"
522524
import { computed, onMounted, ref, watch } from "vue"
523525
526+
const { showSuccess, showError } = useToast()
527+
const { formatDate, formatDateTime } = useFormatters()
528+
524529
const props = defineProps({
525530
modelValue: Boolean,
526531
posProfile: String,
@@ -765,12 +770,7 @@ async function loadUnpaidInvoices() {
765770
unpaidInvoices.value = result || []
766771
} catch (error) {
767772
console.error("Error loading unpaid invoices:", error)
768-
toast.create({
769-
title: "Error",
770-
text: error.message || "Failed to load unpaid invoices",
771-
icon: "alert-circle",
772-
iconClasses: "text-red-600",
773-
})
773+
showError(error.message || "Failed to load unpaid invoices")
774774
} finally {
775775
loading.value = false
776776
}
@@ -811,12 +811,7 @@ async function handlePaymentCompleted(paymentData) {
811811
payments: paymentData.payments,
812812
})
813813
814-
toast.create({
815-
title: "Success",
816-
text: "Payment added successfully",
817-
icon: "check",
818-
iconClasses: "text-green-600",
819-
})
814+
showSuccess("Payment added successfully")
820815
821816
// Reload invoices and summary
822817
await loadUnpaidInvoices()
@@ -825,31 +820,14 @@ async function handlePaymentCompleted(paymentData) {
825820
selectedInvoice.value = null
826821
} catch (error) {
827822
console.error("Error adding payment:", error)
828-
toast.create({
829-
title: "Error",
830-
text: error.message || "Failed to add payment",
831-
icon: "alert-circle",
832-
iconClasses: "text-red-600",
833-
})
823+
showError(error.message || "Failed to add payment")
834824
}
835825
}
836826
837827
function formatCurrency(amount) {
838828
return formatCurrencyUtil(Number.parseFloat(amount || 0), props.currency)
839829
}
840830
841-
function formatDate(date) {
842-
if (!date) return ""
843-
const d = new Date(date)
844-
return d.toLocaleDateString()
845-
}
846-
847-
function formatDateTime(datetime) {
848-
if (!datetime) return ""
849-
const d = new Date(datetime)
850-
return d.toLocaleString()
851-
}
852-
853831
function calculateDraftTotal(items) {
854832
if (!items || items.length === 0) return 0
855833
return items.reduce(

POS/src/components/partials/PartialPayments.vue

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,12 @@
177177
import { formatCurrency as formatCurrencyUtil } from "@/utils/currency"
178178
import PaymentDialog from "@/components/sale/PaymentDialog.vue"
179179
import { usePOSSettingsStore } from "@/stores/posSettings"
180-
import { Button, call, toast } from "frappe-ui"
180+
import { useToast } from "@/composables/useToast"
181+
import { Button, call } from "frappe-ui"
181182
import { onMounted, ref, watch } from "vue"
182183
183184
const posSettingsStore = usePOSSettingsStore()
185+
const { showSuccess, showError } = useToast()
184186
185187
const props = defineProps({
186188
modelValue: Boolean,
@@ -239,12 +241,7 @@ async function loadInvoices() {
239241
invoices.value = result || []
240242
} catch (error) {
241243
console.error("Error loading partial payments:", error)
242-
toast.create({
243-
title: "Error",
244-
text: error.message || "Failed to load partial payments",
245-
icon: "alert-circle",
246-
iconClasses: "text-red-600",
247-
})
244+
showError(error.message || "Failed to load partial payments")
248245
} finally {
249246
loading.value = false
250247
}
@@ -294,12 +291,7 @@ async function handlePaymentCompleted(paymentData) {
294291
295292
console.log('[PartialPayments] API response:', result)
296293
297-
toast.create({
298-
title: "Success",
299-
text: "Payment added successfully",
300-
icon: "check",
301-
iconClasses: "text-green-600",
302-
})
294+
showSuccess("Payment added successfully")
303295
304296
// Reload invoices and summary
305297
console.log('[PartialPayments] Reloading invoices and summary...')
@@ -309,12 +301,7 @@ async function handlePaymentCompleted(paymentData) {
309301
selectedInvoice.value = null
310302
} catch (error) {
311303
console.error("[PartialPayments] Error adding payment:", error)
312-
toast.create({
313-
title: "Error",
314-
text: error.message || "Failed to add payment",
315-
icon: "alert-circle",
316-
iconClasses: "text-red-600",
317-
})
304+
showError(error.message || "Failed to add payment")
318305
}
319306
}
320307

POS/src/components/pos/POSHeader.vue

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<div
3-
class="bg-white shadow-sm sticky top-0 z-[200]"
3+
class="bg-white shadow-sm sticky top-0 z-[200] overflow-x-hidden"
44
>
55
<div class="flex py-2 sm:py-3">
66
<!-- POS Icon - Aligned with Management Sidebar (64px) -->
@@ -19,11 +19,11 @@
1919
<!-- Main Header Content -->
2020
<div class="flex-1 flex justify-between items-center gap-1 sm:gap-2 px-2 sm:px-4 md:px-6">
2121
<!-- Left Side: Brand Info -->
22-
<div class="flex items-center space-x-1 sm:space-x-4 min-w-0 flex-1">
23-
<div class="min-w-0">
24-
<div class="flex items-center gap-2">
25-
<h1 class="text-xs sm:text-base font-bold text-gray-900 truncate">POS Next</h1>
26-
<span class="relative inline-flex items-center px-2 py-0.5 text-[9px] sm:text-[10px] font-bold bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-md shadow-sm hover:shadow-md transition-shadow">
22+
<div class="flex items-center space-x-1 sm:space-x-4 min-w-0 flex-1 overflow-hidden">
23+
<div class="min-w-0 flex-shrink overflow-hidden">
24+
<div class="flex items-center gap-1 sm:gap-2">
25+
<h1 class="text-xs sm:text-base font-bold text-gray-900 truncate flex-shrink">POS Next</h1>
26+
<span class="hidden sm:inline-flex relative items-center px-1 sm:px-2 py-0.5 text-[8px] sm:text-[10px] font-bold bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-md shadow-sm hover:shadow-md transition-shadow flex-shrink-0">
2727
<span class="absolute inset-0 bg-white/20 rounded-md animate-pulse"></span>
2828
<span class="relative">v{{ appVersion }}</span>
2929
</span>
@@ -32,7 +32,7 @@
3232
</div>
3333

3434
<!-- Time and Shift Duration - Compact on mobile -->
35-
<div class="hidden lg:flex items-center space-x-4 ml-6">
35+
<div class="hidden lg:flex items-center space-x-4 ml-6 flex-shrink-0">
3636
<!-- Current Time -->
3737
<StatusBadge
3838
variant="blue"
@@ -53,11 +53,8 @@
5353
</div>
5454

5555
<!-- Mobile Time Display - Very compact -->
56-
<div class="flex lg:hidden items-center text-[10px] text-gray-600 font-medium">
57-
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
58-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
59-
</svg>
60-
<span class="hidden xs:inline">{{ currentTime }}</span>
56+
<div class="flex lg:hidden items-center text-[10px] text-gray-600 font-medium flex-shrink-0 ml-1">
57+
<span class="hidden xs:inline whitespace-nowrap">{{ currentTime }}</span>
6158
</div>
6259
</div>
6360

0 commit comments

Comments
 (0)