Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions frontend/src/app/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import TitleBar from '@/components/layout/TitleBar';
import UpdateAvailableDialog from '@/components/updates/UpdateAvailableDialog';
import ProductAnalyticsDialog from '@/components/analytics/ProductAnalyticsDialog';
import { usePrinterStatusSync } from '@/hooks/usePrinter';
import { useReceiptSettingsSync } from '@/hooks/useReceiptSettingsSync';
import { normalizePathname } from '@/lib/utils';

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
Expand All @@ -22,6 +23,8 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
const isSettings = pathname === '/settings';
// Sync printer status early so hardware and WebUSB reconnect before first print.
usePrinterStatusSync();
// Hydrate receipt display settings from backend so browser print has current store details.
useReceiptSettingsSync();

return (
<AuthGuard>
Expand Down
60 changes: 17 additions & 43 deletions frontend/src/app/(dashboard)/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1807,20 +1807,10 @@ export default function SettingsPage() {
};
setPrintingForm((previous) => ({ ...previous, ...billDisplay }));
setSavedPrinting((previous) => ({ ...previous, ...billDisplay }));
posSettings.setBillShowLogo(billDisplay.billShowLogo);
posSettings.setReceiptLogo(d.receipt_logo || '');
posSettings.setReceiptLogoRaster(d.receipt_logo_raster || '');
posSettings.setBillShowName(billDisplay.billShowName);
posSettings.setBillShowAddress(billDisplay.billShowAddress);
posSettings.setBillShowPhone(billDisplay.billShowPhone);
posSettings.setBillShowTaxId(billDisplay.billShowTaxId);
posSettings.setBillShowTaxBreakdown(billDisplay.billShowTaxBreakdown);
posSettings.setBillShowCustomerName(billDisplay.billShowCustomerName);
posSettings.setBillShowCustomerPhone(billDisplay.billShowCustomerPhone);
posSettings.setBillDeliveryShowCustomerPhoneAlways(
billDisplay.billDeliveryShowCustomerPhoneAlways,
);
posSettings.setBillShowTableNumber(billDisplay.billShowTableNumber);
posSettings.hydrateReceiptSettings({
...d,
...billDisplay,
});

setLoyaltyEnabled(!!loyaltyRes.data.loyalty_enabled);
setSavedLoyaltyEnabled(!!loyaltyRes.data.loyalty_enabled);
Expand Down Expand Up @@ -2113,28 +2103,10 @@ export default function SettingsPage() {
};
mergeHydratedPrinting(billDisplay, printingAtHydrationStart, hydrationTouchSnapshot);
setSavedPrinting((previous) => ({ ...previous, ...billDisplay }));
posSettings.setBillShowLogo(billDisplay.billShowLogo);
posSettings.setReceiptLogo(d.receipt_logo || '');
posSettings.setReceiptLogoRaster(d.receipt_logo_raster || '');
posSettings.setBillShowName(billDisplay.billShowName);
posSettings.setBillShowAddress(billDisplay.billShowAddress);
posSettings.setBillShowPhone(billDisplay.billShowPhone);
posSettings.setBillShowTaxId(billDisplay.billShowTaxId);
posSettings.setBillShowTaxBreakdown(billDisplay.billShowTaxBreakdown);
posSettings.setBillShowCustomerName(billDisplay.billShowCustomerName);
posSettings.setBillShowCustomerPhone(billDisplay.billShowCustomerPhone);
posSettings.setBillDeliveryShowCustomerPhoneAlways(
billDisplay.billDeliveryShowCustomerPhoneAlways,
);
posSettings.setBillShowTableNumber(billDisplay.billShowTableNumber);
if (d.tax_registration_number)
posSettings.setBillTaxRegistrationNumber(d.tax_registration_number);
if (d.business_address) posSettings.setBillAddress(d.business_address);
if (d.business_phone) posSettings.setBillPhone(d.business_phone);
posSettings.setBillingType(d.billing_type === 'prepaid' ? 'prepaid' : 'postpaid');
posSettings.setTablesRequired(
typeof d.tables_required === 'boolean' ? d.tables_required : true,
);
posSettings.hydrateReceiptSettings({
...d,
...billDisplay,
});
businessHydrated.current = true;
})();
businessHydrationPromise.current = promise;
Expand Down Expand Up @@ -3493,13 +3465,15 @@ export default function SettingsPage() {
setForm(updatedForm);
setTaxIdFormat(resolvedTaxIdFormat);
setTaxIdFormatCountryCode(form.countryCode);
posSettings.setBillTaxRegistrationNumber(form.taxRegistrationNumber);
posSettings.setBillAddress(form.businessAddress);
posSettings.setBillPhone(normalizedBusinessPhone);
posSettings.setBillingType(form.billingType);
posSettings.setTablesRequired(form.tablesRequired);
posSettings.setReceiptLogo(form.receiptLogo || '');
posSettings.setReceiptLogoRaster(form.receiptLogoRaster || '');
posSettings.hydrateReceiptSettings({
tax_registration_number: form.taxRegistrationNumber,
business_address: form.businessAddress,
business_phone: normalizedBusinessPhone,
billing_type: form.billingType,
tables_required: form.tablesRequired,
receipt_logo: form.receiptLogo || '',
receipt_logo_raster: form.receiptLogoRaster || '',
});
updateCurrentTenant({
currency: form.currency,
timezone: form.timezone,
Expand Down
20 changes: 3 additions & 17 deletions frontend/src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,8 @@ const ALL_NAV_ITEMS: NavItem[] = [
export default function AppSidebar() {
const pathname = usePathname();
const { user, currentTenant, logout } = useAuthStore();
const {
tablesRequired,
kdsEnabled,
whatsappEnabled,
setTablesRequired,
setKdsEnabled,
setWhatsappEnabled,
} = usePosSettingsStore();
const { tablesRequired, kdsEnabled, whatsappEnabled, setKdsEnabled, setWhatsappEnabled } =
usePosSettingsStore();
const { isMobile, setOpenMobile } = useSidebar();
const t = useTranslations('nav');
const tCommon = useTranslations('common');
Expand Down Expand Up @@ -216,14 +210,6 @@ export default function AppSidebar() {
useEffect(() => {
if (!currentTenant) return;
if (tenantCan(currentTenant, 'settings.view')) {
api
.get('/settings/business')
.then((res) => {
setTablesRequired(
typeof res.data.tables_required === 'boolean' ? res.data.tables_required : true,
);
})
.catch(() => {});
api
.get('/settings/kds_enabled')
.then((res) => setKdsEnabled(res.data.setting?.value !== 'false'))
Expand All @@ -238,7 +224,7 @@ export default function AppSidebar() {
} else {
setWhatsappEnabled(false);
}
}, [currentTenant, setTablesRequired, setKdsEnabled, setWhatsappEnabled]);
}, [currentTenant, setKdsEnabled, setWhatsappEnabled]);

useEffect(() => {
if (!tenantCan(currentTenant, 'cloud.account.manage')) return;
Expand Down
38 changes: 38 additions & 0 deletions frontend/src/hooks/useReceiptSettingsSync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useEffect } from 'react';
import api from '@/lib/api';
import { useAuthStore } from '@/store/auth';
import { usePosSettingsStore, type ReceiptSettingsHydrationData } from '@/store/pos-settings';
import { tenantCan } from '@/lib/permissions';

export function useReceiptSettingsSync(): void {
const currentTenant = useAuthStore((s) => s.currentTenant);

useEffect(() => {
if (!currentTenant) return;
if (!tenantCan(currentTenant, 'settings.view')) return;

let active = true;
const sync = () => {
api
.get<ReceiptSettingsHydrationData>('/settings/business')
.then((res) => {
if (!active || !res?.data) return;
usePosSettingsStore.getState().hydrateReceiptSettings(res.data);
})
.catch(() => {});
};

sync();

if (typeof window !== 'undefined') {
window.addEventListener('focus', sync);
return () => {
active = false;
window.removeEventListener('focus', sync);
};
}
return () => {
active = false;
};
}, [currentTenant]);
}
140 changes: 140 additions & 0 deletions frontend/src/store/pos-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,45 @@ export interface PosSettingsState {
setWhatsappEnabled: (v: boolean) => void;
setBillLanguagePolicy: (policy: ReceiptLanguagePolicy) => void;
setKotLanguagePolicy: (policy: KotLanguagePolicy) => void;
hydrateReceiptSettings: (data: ReceiptSettingsHydrationData) => void;
}

export interface ReceiptSettingsHydrationData {
bill_show_logo?: boolean | string | null;
billShowLogo?: boolean | string | null;
bill_show_name?: boolean | string | null;
billShowName?: boolean | string | null;
bill_show_address?: boolean | string | null;
billShowAddress?: boolean | string | null;
bill_show_phone?: boolean | string | null;
billShowPhone?: boolean | string | null;
bill_show_tax_id?: boolean | string | null;
billShowTaxId?: boolean | string | null;
bill_show_tax_breakdown?: boolean | string | null;
billShowTaxBreakdown?: boolean | string | null;
bill_show_customer_name?: boolean | string | null;
billShowCustomerName?: boolean | string | null;
bill_show_customer_phone?: boolean | string | null;
billShowCustomerPhone?: boolean | string | null;
bill_delivery_show_customer_phone_always?: boolean | string | null;
billDeliveryShowCustomerPhoneAlways?: boolean | string | null;
bill_show_table_number?: boolean | string | null;
billShowTableNumber?: boolean | string | null;
receipt_logo?: string | null;
receiptLogo?: string | null;
receipt_logo_raster?: string | null;
receiptLogoRaster?: string | null;
tax_registration_number?: string | null;
business_address?: string | null;
business_phone?: string | null;
bill_footer_message?: string | null;
bill_template?: string | null;
billTemplate?: string | null;
billTemplateSource?: 'core' | 'pack' | 'merchant' | null;
billing_type?: string | null;
billingType?: string | null;
tables_required?: boolean | string | null;
tablesRequired?: boolean | string | null;
}

export const usePosSettingsStore = create<PosSettingsState>()(
Expand Down Expand Up @@ -192,6 +231,107 @@ export const usePosSettingsStore = create<PosSettingsState>()(
setWhatsappEnabled: (v: boolean) => set({ whatsappEnabled: v }),
setBillLanguagePolicy: (billLanguagePolicy) => set({ billLanguagePolicy }),
setKotLanguagePolicy: (kotLanguagePolicy) => set({ kotLanguagePolicy }),
hydrateReceiptSettings: (d) =>
set(() => {
const updates: Partial<PosSettingsState> = {};
const showLogo = d.bill_show_logo ?? d.billShowLogo;
if (showLogo !== undefined && showLogo !== null) {
updates.billShowLogo = showLogo === true || showLogo === 'true';
}
const showName = d.bill_show_name ?? d.billShowName;
if (showName !== undefined && showName !== null) {
updates.billShowName = showName !== false && showName !== 'false';
}
const showAddress = d.bill_show_address ?? d.billShowAddress;
if (showAddress !== undefined && showAddress !== null) {
updates.billShowAddress = showAddress !== false && showAddress !== 'false';
}
const showPhone = d.bill_show_phone ?? d.billShowPhone;
if (showPhone !== undefined && showPhone !== null) {
updates.billShowPhone = showPhone !== false && showPhone !== 'false';
}
const showTaxId = d.bill_show_tax_id ?? d.billShowTaxId;
if (showTaxId !== undefined && showTaxId !== null) {
updates.billShowTaxId = showTaxId === true || showTaxId === 'true';
}
const showTaxBreakdown = d.bill_show_tax_breakdown ?? d.billShowTaxBreakdown;
if (showTaxBreakdown !== undefined && showTaxBreakdown !== null) {
updates.billShowTaxBreakdown =
showTaxBreakdown !== false && showTaxBreakdown !== 'false';
}
const showCustName = d.bill_show_customer_name ?? d.billShowCustomerName;
if (showCustName !== undefined && showCustName !== null) {
updates.billShowCustomerName = showCustName !== false && showCustName !== 'false';
}
const showCustPhone = d.bill_show_customer_phone ?? d.billShowCustomerPhone;
if (showCustPhone !== undefined && showCustPhone !== null) {
updates.billShowCustomerPhone = showCustPhone !== false && showCustPhone !== 'false';
}
const showDeliveryPhone =
d.bill_delivery_show_customer_phone_always ?? d.billDeliveryShowCustomerPhoneAlways;
if (showDeliveryPhone !== undefined && showDeliveryPhone !== null) {
updates.billDeliveryShowCustomerPhoneAlways =
showDeliveryPhone !== false && showDeliveryPhone !== 'false';
}
const showTableNum = d.bill_show_table_number ?? d.billShowTableNumber;
if (showTableNum !== undefined && showTableNum !== null) {
updates.billShowTableNumber = showTableNum !== false && showTableNum !== 'false';
}
const logo = d.receipt_logo ?? d.receiptLogo;
if (logo !== undefined) {
updates.receiptLogo = logo || '';
}
const logoRaster = d.receipt_logo_raster ?? d.receiptLogoRaster;
if (logoRaster !== undefined) {
updates.receiptLogoRaster = logoRaster || '';
}
if (d.tax_registration_number !== undefined) {
updates.billTaxRegistrationNumber = d.tax_registration_number || '';
}
if (d.business_address !== undefined) {
updates.billAddress = d.business_address || '';
}
if (d.business_phone !== undefined) {
updates.billPhone = d.business_phone || '';
}
if (d.bill_footer_message !== undefined) {
updates.billFooterMessage = d.bill_footer_message || '';
}
const template = d.bill_template ?? d.billTemplate;
if (template !== undefined && template !== null) {
let templateId = String(template);
let templateSource: 'core' | 'pack' | 'merchant' | null = d.billTemplateSource ?? null;
if (templateId.trim().startsWith('{')) {
try {
const parsed = JSON.parse(templateId);
if (parsed && typeof parsed.id === 'string') {
templateId = parsed.id;
if (
parsed.source === 'core' ||
parsed.source === 'pack' ||
parsed.source === 'merchant'
) {
templateSource = parsed.source;
}
}
} catch {
/* keep raw */
}
}
updates.billTemplate = templateId;
updates.billTemplateSource = templateSource ?? 'core';
}
const billingType = d.billing_type ?? d.billingType;
if (billingType !== undefined && billingType !== null) {
updates.billingType = billingType === 'prepaid' ? 'prepaid' : 'postpaid';
}
const tablesReq = d.tables_required ?? d.tablesRequired;
if (tablesReq !== undefined && tablesReq !== null) {
updates.tablesRequired =
typeof tablesReq === 'boolean' ? tablesReq : tablesReq !== 'false';
}
return updates;
}),
}),
{
name: 'pos-settings',
Expand Down
2 changes: 2 additions & 0 deletions main/routes/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ function businessShape(s: Record<string, string>) {
bill_show_logo: s.bill_show_logo === 'true',
receipt_logo: s.receipt_logo || '',
receipt_logo_raster: s.receipt_logo_raster || '',
bill_footer_message: s.bill_footer_message || '',
bill_template: s.bill_template || 'classic',
currency_display: resolveStoredLocalePreference(
'currency_display',
s.currency_display,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@
"test:charges": "ts-node --transpile-only -P tests/tsconfig.json tests/charges-calculation.test.ts",
"test:charges-engine": "ts-node --transpile-only -P tests/tsconfig.json tests/charges-engine-api.test.ts",
"audit:db": "ts-node --transpile-only -P tests/tsconfig.json tests/db-audit.test.ts",
"test:receipt-printing": "ts-node --transpile-only -P tests/tsconfig.json tests/receipt-printing.test.ts",
"test:receipt-printing": "ts-node --transpile-only -P tests/tsconfig.json tests/receipt-printing.test.ts && npm run test:receipt-settings-hydration",
"test:receipt-settings-hydration": "node tests/run-electron-node-test.cjs tests/receipt-settings-hydration.test.ts",
"test:bills-print-api": "ts-node --transpile-only -P tests/tsconfig.json tests/bills-print-api.test.ts",
"test:print-parity": "ts-node --transpile-only -P tests/tsconfig.json tests/print-parity.test.ts",
"test:delivery-slip": "ts-node --transpile-only -P tests/tsconfig.json tests/delivery-slip-printing.test.ts",
Expand Down
Loading
Loading