feat: booking template library with system templates and cross-company sharing (#235)
* feat: implement viewer role permissions for bank transaction imports and connections * feat: add booking_template_library table with 30 system templates Three-level scoping (system/team/company), RLS policies for read/write/delete, and pre-seeded templates for EU reverse charge, tax account, private transfers, salary, representation, year-end, VAT netting, and bank/finance scenarios. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add template library types, helpers, and tests - BookingTemplateLibrary/Line/Category types in types/index.ts - applyTemplate() converts template lines + amount into form lines - Category labels, scope helpers for UI display - 8 unit tests for amount calculation, VAT, rounding, and scope detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add booking template CRUD, export, and import API routes - GET/POST/DELETE /api/settings/booking-templates (list, create, soft-delete) - PUT /api/settings/booking-templates/[id] (update non-system templates) - GET /api/settings/booking-templates/export (JSON download) - POST /api/settings/booking-templates/import (bulk import from JSON) All routes enforce auth, write permissions, and Zod validation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add template picker UI and settings management panel - BookingTemplatePicker: dialog with search, category/entity-type filter, line preview, and amount input — integrated into JournalEntryForm - BookingTemplatesPanel: settings page with grouped templates (system/team/company), create dialog, export/import, soft-delete - Settings templates page now shows both booking and counterparty templates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update supabase/migrations/20260413160000_booking_template_library.sql Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
greptile-apps[bot]
parent
cd376e1cad
commit
bf36ebfd88
@@ -14,10 +14,12 @@ import { ConfirmationDialog } from '@/components/ui/confirmation-dialog'
|
||||
import { JournalEntryReviewContent } from '@/components/bookkeeping/JournalEntryReviewContent'
|
||||
import DocumentUploadZone from '@/components/bookkeeping/DocumentUploadZone'
|
||||
import AccountCombobox from '@/components/bookkeeping/AccountCombobox'
|
||||
import BookingTemplatePicker from '@/components/bookkeeping/BookingTemplatePicker'
|
||||
import CreatePeriodDialog from '@/components/bookkeeping/CreatePeriodDialog'
|
||||
import { getErrorMessage } from '@/lib/errors/get-error-message'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
import { useUnsavedChanges } from '@/lib/hooks/use-unsaved-changes'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
import type { UploadedFile } from '@/components/bookkeeping/DocumentUploadZone'
|
||||
import type { CreateJournalEntryLineInput, FiscalPeriod, BASAccount, JournalEntrySourceType, Currency } from '@/types'
|
||||
|
||||
@@ -69,6 +71,7 @@ export default function JournalEntryForm({
|
||||
}: Props) {
|
||||
const { canWrite } = useCanWrite()
|
||||
const { toast } = useToast()
|
||||
const { company } = useCompany()
|
||||
const [periods, setPeriods] = useState<FiscalPeriod[]>([])
|
||||
const [selectedPeriod, setSelectedPeriod] = useState('')
|
||||
const [entryDate, setEntryDate] = useState(initialDate ?? new Date().toISOString().split('T')[0])
|
||||
@@ -235,6 +238,11 @@ export default function JournalEntryForm({
|
||||
? Math.round(computedForeignAmount * rate * 100) / 100
|
||||
: 0
|
||||
|
||||
const handleTemplateApply = (templateLines: FormLine[], templateDescription: string) => {
|
||||
setLines(templateLines)
|
||||
if (!description) setDescription(templateDescription)
|
||||
}
|
||||
|
||||
const handleReview = () => {
|
||||
if (!selectedPeriod || !description || !isBalanced || periodMismatch) return
|
||||
const hasDocuments = uploadedFiles.some((f) => f.status === 'uploaded')
|
||||
@@ -575,15 +583,21 @@ export default function JournalEntryForm({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={addLine}
|
||||
className="w-full"
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
Lägg till rad
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={addLine}
|
||||
className="flex-1"
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
Lägg till rad
|
||||
</Button>
|
||||
<BookingTemplatePicker
|
||||
onApply={handleTemplateApply}
|
||||
entityType={company?.entity_type}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Entry lines — desktop table */}
|
||||
@@ -678,15 +692,20 @@ export default function JournalEntryForm({
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={addLine}
|
||||
className="mt-2"
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
Lägg till rad
|
||||
</Button>
|
||||
<div className="flex gap-2 mt-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={addLine}
|
||||
>
|
||||
<Plus className="h-3 w-3 mr-1" />
|
||||
Lägg till rad
|
||||
</Button>
|
||||
<BookingTemplatePicker
|
||||
onApply={handleTemplateApply}
|
||||
entityType={company?.entity_type}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Document attachments */}
|
||||
|
||||
Reference in New Issue
Block a user