1f79abe404d508bb14e3c435d04a3ee91c8f6a27
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1f79abe404 |
feat(finance): personal vs company scope for banking + finance entries
User-level data privacy on finance entities. Bireysel = sadece sahibi
görür/düzenler/siler, Şirket = takım görür (mevcut davranış).
Schema additions (4 tables, all enum company|personal default 'company'):
- bank_accounts.scope
- bank_loans.scope
- credit_cards.scope
- finance_entries.scope
+ tenantId_scope index on each.
Inherited fields (no own scope, parent's used):
- loan_installments → from bank_loan
- credit_card_statements → from credit_card
Permissions (lib/appwrite/scope-permissions.ts):
- scopedRowPermissions(tenantId, createdBy, scope):
* company: Permission.read/update Role.team(tenantId), delete Role.team
owner|admin (current behavior)
* personal: read/update/delete Role.user(createdBy) only
- canAccessRow(row, userId): true if scope=company OR createdBy=userId.
Used as a defense-in-depth check inside actions because we use the
admin SDK (which bypasses row-level perms).
Action updates:
- bank-account-actions, loan-actions, credit-card-actions, finance-actions:
pickFormFields includes scope; create uses scopedRowPermissions; update
re-applies perms when scope changes; update/delete check canAccessRow
on top of the existing tenantId check.
- loan installment payment & credit card statement payment auto-create
finance entries that inherit the parent's scope, so a personal loan
installment doesn't create a company income/expense.
Query updates (all accept optional currentUserId):
- listBankAccounts, listLoans, listCreditCards, listFinanceEntries:
pull all tenant rows then in-JS filter via canAccessRow.
- getBankAccountBalances respects visible accounts only.
- listAllInstallments / listStatements: filter to only those whose
parent loan/card is visible.
UI:
- New shared component components/finance/scope-toggle.tsx with
ScopeToggle (form input) and ScopeBadge (visual marker).
- Bank, loan, card form sheets and the finance form sheet now include
a Şirket/Bireysel toggle at the top.
- Bank account cards display ScopeBadge for personal entries.
- Page-level queries everywhere now pass ctx.user.id so each user only
sees their personal rows + the team's company rows.
Reports & Dashboard:
- getDashboardData filters finance entries to scope=company only — so
team-level metrics never include any user's personal data.
- getFinancialReport (CFO view): bank accounts, loans, cards, finance
entries, installments and statements all filtered to company scope.
Personal entities never appear in reports anywhere.
Invoice → finance entry sync explicitly tags scope=company since invoices
are inherently company-scope.
|
||
|
|
121fbdba9d |
feat(banking C): credit cards + monthly statements
Final banking step. User-managed: each month, paste your bank statement totals into the form. We don't auto-compute interest — bank's number is authoritative. Schema: - credit_cards: bankName, cardName, last4, creditLimit, statementDay, dueDay, interestRate (monthly nominal %), bankAccountId (optional FK, payments expense from this account), archived, notes. - credit_card_statements: cardId, period (YYYY-MM), statementDate, dueDate, totalDebt, minimumPayment, paidAmount, status (pending/partial/paid/overdue), financeEntryId (link to last payment). - Indexes on (tenantId, archived) for cards and (tenantId, cardId) + (tenantId, status, dueDate) for statements. Server (lib/appwrite/credit-card-actions.ts): - create/update/archive(toggle)/delete for cards. Card delete cascades through statements + their finance_entries. - createStatementAction: computes status from dueDate + paidAmount. - payStatementAction: partial-payment friendly. Creates a single finance_entry expense for the pay amount, capped at remaining balance, bankAccountId carried from the card. Recomputes status: paid (full), partial (some + pre-due), overdue (past due with anything < total). - deleteStatementAction: removes linked finance_entry too. - All audit-logged. UI (/finance/cards): - 3 stat cards: aktif kart sayısı, bekleyen toplam, vadesi geçmiş ekstre. - Per-card panel: bank/name/last4, limit + statement/due day + monthly interest, current outstanding. Statement table inside card with status badges, Öde button (opens partial-payment dialog), delete button. - CardFormSheet for card CRUD, StatementFormSheet for statement creation with default period/dates derived from card's statementDay/dueDay. Sidebar Finans submenu now functional: Banka hesapları → /finance/banks, Krediler → /finance/loans, Kredi kartları → /finance/cards. |
||
|
|
b632ae8a73 |
feat(banking B): bank loans + amortization schedule
Step 2 of banking. Loan creation auto-generates the full installment schedule using standard amortization (eşit taksitli kredi): monthlyPayment = P × r × (1+r)^n / ((1+r)^n − 1) Schema: - bank_loans: bankAccountId (optional FK), bankName, loanName, loanType enum (consumer/vehicle/housing/commercial/kmh/other), principal, interestRate (monthly nominal %), termMonths, monthlyPayment, startDate, paymentDay (1-28, clamped per month), status (active/closed/defaulted). - loan_installments: loanId, installmentNo, dueDate, amount, principalPart, interestPart, paid, paidAt, financeEntryId. - Indexes on bank_loans(tenantId, status) and loan_installments(tenantId, loanId) and (tenantId, paid, dueDate). Server (lib/appwrite/loan-actions.ts): - createLoanAction: validates with Zod, computes amortization including rounding-drift handling on the last installment, persists loan + N installments, audit-logs. Atomic rollback on failure (deletes any partially-created installments and the loan). - payInstallmentAction: atomically creates a finance_entry (expense, bankAccountId carried over from the loan), updates installment with paid=true + financeEntryId. If it was the last unpaid installment, marks loan status='closed'. - unpayInstallmentAction: deletes the linked finance_entry, clears paid fields, reopens the loan if it was closed. - deleteLoanAction: cascade-deletes all installments first, then the loan. UI (/finance/loans): - 3 stat cards: aktif kredi sayısı, toplam çekilen, kalan ödeme. - Loan card per loan with bank/name/type/status badges, anapara/aylık taksit/faiz/sonraki ödeme grid, progress bar (paid/total), expandable installment table. - Installment row: # / vade (red if overdue) / anapara / faiz / toplam / Ödendi-Geri al toggle. - LoanFormSheet: live preview of monthly payment, total payment, total interest as user changes principal/rate/term. paymentDay clamped 1-28 to avoid month-length issues. |
||
|
|
7b6be623ae |
feat(banking A): bank accounts module + finance integration
First of 3-step banking expansion. Banks tracked separately from
customer/supplier debts so we can compute real cash position later.
Schema:
- New bank_accounts table: bankName, accountName, iban, openingBalance,
notes, archived. Indexes on (tenantId, archived).
- New column finance_entries.bankAccountId (FK, optional). Index on
(tenantId, bankAccountId).
- schema.ts: TABLES.bankAccounts, BankAccount type, FinanceEntry gains
bankAccountId.
Server side:
- lib/validation/bank-accounts.ts (Zod): IBAN normalized to upper-case
no-spaces; openingBalance defaults to 0.
- lib/appwrite/bank-account-actions.ts: create/update/archive(toggle)/
delete with audit. Delete refuses if any finance_entry still references
the account; archive toggle replaces it for safe disable.
- lib/appwrite/bank-account-queries.ts:
* listBankAccounts
* getBankAccountBalances — computes opening + Σ(income) − Σ(expense)
per account by scanning up to 5000 entries with bankAccountId set.
Pure cash flow; debt/receivable don't move balance.
* listEntriesForAccount
UI:
- /finance/banks server page renders BanksClient with computed balances.
- BanksClient: card grid for active accounts, collapsed details for
archived. Sum card on top showing total active balance (color-coded by
sign). Each card shows bank, account name, formatted IBAN, current
balance + opening (if drifted). Dropdown: Düzenle / Arşivle / Sil.
- BankFormSheet: bank/account/IBAN/openingBalance/notes form.
- Finance form gets a bank-account Select (sentinel-stripped). Existing
finance entries get a 'bankAccountLabel' subtitle in their row.
Sidebar: Finans group expanded with Bankalar submenu (Banka hesapları
/ Krediler / Kredi kartları). The latter two land in B and C.
|
||
|
|
fc091b9e0d |
feat(settings): /settings/workspace — edit company info + invoice defaults
Owner/admin edit, member read-only.
Schema/validation:
- lib/validation/workspace.ts (workspaceSettingsSchema)
- lib/appwrite/workspace-actions.ts:
* updateWorkspaceSettingsAction — requireRole owner|admin, upserts the
tenant_settings row (creates one with team-scoped perms if absent,
e.g. for tenants created before tenant_settings was a table; just
defense). Audit-logged.
* Forces invoicePrefix to uppercase. defaultVatRate clamped to [0, 100].
* revalidatePath('/', 'layout') so the new company name updates in
sidebar header and dashboard greeting on next render.
UI:
- /settings/workspace page (server) — pulls active tenant settings
via requireTenant, shows form pre-filled.
- WorkspaceSettingsForm: 2 cards
* Şirket — name (required), tax id, phone, email, address
* Faturalama — invoicePrefix, defaultVatRate, read-only invoiceCounter
- All inputs disabled if user is a member (canEdit=false). Submit button
hidden in that case. Description on the page changes accordingly.
- Toast feedback for success/error.
Skipped: logo upload (storage bucket pending). Will revisit.
|
||
|
|
d99daca3ca |
feat(invoices): full invoice + line items module
The most complex module. Two-table model: invoices (header) +
invoice_items (lines). Auto-numbering via tenant_settings.invoiceCounter,
auto-totals on item changes.
Schema/validation:
- lib/validation/invoices.ts: invoiceSchema (header) + invoiceItemSchema
(line). Both coerce comma decimals.
- lib/appwrite/invoice-actions.ts:
* createInvoiceAction — fetches tenant_settings, increments
invoiceCounter, formats number as '{prefix}-{year}-{0000}',
persists totals as 0/0/0 (recomputed when items added).
* updateInvoiceAction / deleteInvoiceAction — header CRUD; delete
cascades to remove all items first then header.
* addInvoiceItemAction / updateInvoiceItemAction /
deleteInvoiceItemAction — line CRUD. Each computes lineTotal
(qty*unit + vat) and triggers recomputeTotals(invoiceId) which
re-sums all items and updates the header subtotal/vatTotal/total.
All audit-logged.
Queries:
- listInvoices, getInvoice (with tenant cross-check), listInvoiceItems.
UI:
- /invoices index: 4 stat cards (Toplam / Tahsil edildi / Bekleyen /
Gecikmiş), table with overdue-aware due date coloring, status badges,
number is a Link to detail.
- InvoiceFormSheet: customer + dates (default issue=today, due=+30d) +
status + notes. After create, redirects to /invoices/[id] for adding
items.
- /invoices/[id] detail: header strip with status, dates, customer name;
print/edit/delete actions; items editor card; subtotal/VAT/total card;
notes card.
- InvoiceItemsEditor: rows are clickable to edit, X button to delete.
ItemFormSheet for add/edit (description + qty + unitPrice + VAT %).
Print is just window.print() for now — relies on browser dialog. Detail
page deliberately uses tabular-nums for amounts.
|
||
|
|
98ab73235f |
feat(finance): income/expense/debt/receivable tracking + summary
Multi-tenant cash flow tracker. All amounts in TRY, decimals preserved. Schema/validation: - lib/validation/finance.ts: financeEntrySchema with type enum, positive amount, date required, optional customer/invoice link, optional payment method. - lib/appwrite/finance-actions.ts: create/update/delete with audit; date HTML input normalized to ISO before write. - lib/appwrite/finance-queries.ts: listFinanceEntries ordered by date desc. UI: - /finance server page passes entries + customers to FinanceClient. - 5 stat cards: Gelir / Gider / Net (income-expense, color-coded by sign) / Alacaklar / Borçlar. - Type filter dropdown (Tümü/Gelir/Gider/Alacaklar/Borçlar) + global search (description/customer/amount). - 4 quick-add buttons let users start a new entry pre-filled with the desired type. Single FinanceFormSheet handles all 4 types via a Select. - Table: type badge (color-coded), signed amount (+ for income/receivable, − for expense/debt), date, customer, payment method label, description preview. Row dropdown: Edit / Delete. - Inline destructive Sil button in Sheet footer when editing. |
||
|
|
b4c1073d91 |
feat(calendar): month-view calendar bound to calendar_events
Replaces the template's static-data calendar with a multi-tenant calendar backed by Appwrite calendar_events. Schema/validation: - lib/validation/calendar.ts (calendarEventSchema with cross-field check end >= start) - lib/appwrite/calendar-actions.ts: createCalendarEventAction, updateCalendarEventAction, deleteCalendarEventAction. Date inputs (HTML datetime-local 'YYYY-MM-DDTHH:mm', date 'YYYY-MM-DD') are normalized to ISO 8601 before write. - lib/appwrite/calendar-queries.ts: listCalendarEvents with optional start/end range queries. UI: - /calendar server page: pulls events + customers, hands to CalendarClient. - CalendarClient: month grid (6 rows × 7 cols), Monday-first, today badge, prev/next/Bugün nav. Multi-day events show on every day in their range. Each day cell shows up to 3 event chips with start time prefix; '+N daha' for overflow. Hover reveals a + button to add an event on that day. - EventFormSheet: title, all-day switch (toggles input type between date and datetime-local), start/end with validation, customer FK, color preset (blue/green/amber/red/violet/slate). Sentinel '__none__' for nullable Selects. When editing, footer shows a destructive 'Sil' ghost button on the left that triggers the parent's confirm dialog. Color tokens centralized in COLOR_BG map; falls back to primary tint. Removed all template calendar files (calendars.tsx, calendar-main, etc.) since the data model didn't match. |
||
|
|
671195fb7d |
feat(tasks): Kanban board with drag-and-drop (dnd-kit)
Replaces the template's /tasks demo (deleted) with a real multi-tenant
Kanban board.
Schema/validation:
- lib/validation/tasks.ts (taskSchema with status/priority enums + dueDate
optional + assignee/customer optional)
- lib/appwrite/task-actions.ts: createTaskAction, updateTaskAction,
deleteTaskAction, moveTaskAction (used by drag-drop). All audit-logged;
moveTaskAction only audits when status actually changes.
- lib/appwrite/task-queries.ts: listTasks ordered by 'order' asc.
UI:
- /tasks server page assembles { tasks, customers, teamMembers } and
passes to TasksBoard. Removed the template's data-table demo files.
- TasksBoard (client): 4 droppable columns. Columns use @dnd-kit/core
useDroppable; cards inside each column are SortableContext+useSortable
for intra-column ordering. closestCorners collision detection.
- Drag-end computes new 'order' as midpoint between adjacent tasks
(no full reindex), updates UI optimistically, then persists via
moveTaskAction. Rolls back on server error with toast.
- TaskCard: priority badge (color-coded), due-date badge (red if
overdue), assignee badge, customer subtitle, dropdown (Edit/Delete)
on hover.
- TaskFormSheet: title/description/status/priority/dueDate/assignee/
customer. Uses sentinel '__none__' for nullable Selects (Radix Select
forbids empty string values), stripped before submit.
- DragOverlay shows the dragged card rotated 3deg with shadow.
|
||
|
|
113988273f |
feat(software): catalog + customer assignments (M2M)
Software catalog with per-customer assignments via the customer_software
join table. Two tabs in one /software page:
Catalog tab:
- Software CRUD: name, version, description, defaultFee (TRY).
- Deleting a software cascades and removes all its assignments first
(best-effort loop, then the catalog row), all wrapped in audit logs.
Assignments tab:
- M2M between customer and software with own fee (overrides defaultFee),
billingPeriod (monthly default), startDate/endDate, notes.
- Form auto-fills fee from selected software's defaultFee.
- Both Sheet forms localized; date inputs round-tripped via toIsoDate
(Appwrite expects ISO 8601 with TZ; HTML date input gives YYYY-MM-DD).
- Delete dialogs differentiated for catalog ('siliniyor') vs assignment
('kaldırılıyor').
New files:
- lib/validation/software.ts (softwareSchema + customerSoftwareSchema)
- lib/appwrite/software-actions.ts (6 server actions)
- lib/appwrite/software-queries.ts (listSoftware, listAssignments)
- lib/appwrite/software-types.ts (form state)
- /software route with SoftwareClient (Tabs), SoftwareFormSheet,
AssignmentFormSheet, inline delete dialogs.
Empty states surface the right next-step CTA: 'önce müşteri ekleyin', or
'önce yazılım ekleyin', as appropriate.
|
||
|
|
a15a1c1c1a |
feat(services): customer-linked services CRUD module
Same pattern as customers. Each service belongs to one customer (FK), has unit price (TRY), billing period (monthly/yearly/onetime), and recurring flag. - lib/validation/services.ts: Zod schema with TRY price coercion (accepts comma decimal), enum billing period, recurring boolean coercion. - lib/appwrite/service-actions.ts: createServiceAction, updateServiceAction, deleteServiceAction. Same tenant guard, audit log, team-scoped row perms. - lib/appwrite/service-queries.ts: listServices, listServicesByCustomer. - lib/format.ts: formatTRY (Intl.NumberFormat tr-TR), formatDate/DateTime, BILLING_PERIOD_LABEL mapping (Aylık/Yıllık/Tek seferlik). - /services page (server): joins services with customer names via in-memory map. - ServicesClient: TanStack table with global filter (name/customer/desc), Repeat icon next to recurring badges, formatted TRY column. - ServiceFormSheet: customer dropdown (disabled if no customers exist), unit price + billing period + recurring switch. - DeleteServiceDialog: same destructive confirmation pattern. Empty state on /services CTA's user back to add a customer first if none exist. |
||
|
|
94f2c92da1 |
feat(customers): full CRUD module — pattern for all other modules
Establishes the multi-tenant module pattern. Subsequent modules (services,
software, calendar, tasks, finance, invoices) will copy this structure.
Validation:
- lib/validation/customers.ts: Zod schema with Turkish messages, optional
fields normalized to undefined.
Server actions (lib/appwrite/customer-actions.ts):
- createCustomerAction, updateCustomerAction, deleteCustomerAction
- All call requireTenant() guard, write team-scoped row permissions
(read+update by team, delete by owner|admin), and emit audit log.
- Update/delete cross-check tenantId on the existing row before mutating
(defense in depth even though row-level perms already enforce it).
- Field-level errors flattened from Zod for inline form display.
Server-side queries (lib/appwrite/customer-queries.ts):
- listCustomers(tenantId), getCustomer(tenantId, id) — admin SDK with
Query.equal('tenantId',...) tenant scope.
UI:
- /customers page (server component): pulls active tenant context, lists
customers, hands off to CustomersClient.
- CustomersClient: TanStack Table with global filter (name/email/phone/
taxId), column sorting on name + createdAt, pagination (20/page),
status badges, row actions (Edit/Delete dropdown), empty-state CTA.
- CustomerFormSheet: shadcn Sheet-based add/edit form with all fields,
toast feedback (sonner), inline field errors. Reused for create + update
by switching the action.
- DeleteCustomerDialog: confirmation modal with destructive button.
Infrastructure:
- Added sonner Toaster to root layout (richColors, closeButton).
- Updated metadata to 'İşletmem KovakCRM' and html lang='tr'.
- Renamed theme storage key to isletmem-ui-theme.
|