WIP
This commit is contained in:
+1
-1
@@ -23,7 +23,7 @@
|
|||||||
<!-- Card B — Add giving -->
|
<!-- Card B — Add giving -->
|
||||||
<section class="card">
|
<section class="card">
|
||||||
<h3 class="section-title">Add Giving / 錄入奉獻</h3>
|
<h3 class="section-title">Add Giving / 錄入奉獻</h3>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-x-4 gap-y-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-3">
|
||||||
<label class="flex flex-col gap-1 md:col-span-2">Giver
|
<label class="flex flex-col gap-1 md:col-span-2">Giver
|
||||||
<kendo-dropdownlist *ngIf="!entry.isAnonymous" [data]="memberResults" textField="displayName" valueField="id"
|
<kendo-dropdownlist *ngIf="!entry.isAnonymous" [data]="memberResults" textField="displayName" valueField="id"
|
||||||
[valuePrimitive]="true" [filterable]="true" (filterChange)="onMemberFilter($event)"
|
[valuePrimitive]="true" [filterable]="true" (filterChange)="onMemberFilter($event)"
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
<kendo-drawer-container>
|
|
||||||
<kendo-drawer [mode]="'overlay'" [expanded]="layoutService.drawerExpanded()" [width]="280">
|
|
||||||
|
|
||||||
<kendo-drawer-content>
|
|
||||||
<div class="drawer-content">
|
|
||||||
<div class="drawer-header">
|
|
||||||
<h3>User Portal</h3>
|
|
||||||
<p>ROLCC AC Portal</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav class="drawer-nav">
|
|
||||||
<div class="nav-section">
|
|
||||||
<h4>Main</h4>
|
|
||||||
<button *ngFor="let item of mainNavItems" kendoButton
|
|
||||||
[fillMode]="item.active ? 'solid' : 'flat'" [themeColor]="item.active ? 'primary' : 'base'"
|
|
||||||
[svgIcon]="item.icon" class="nav-button" (click)="navigateTo(item.path)">
|
|
||||||
{{ item.text }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav-section">
|
|
||||||
<h4>Management</h4>
|
|
||||||
<button *ngFor="let item of managementNavItems" kendoButton
|
|
||||||
[fillMode]="item.active ? 'solid' : 'flat'" [themeColor]="item.active ? 'primary' : 'base'"
|
|
||||||
[svgIcon]="item.icon" class="nav-button" (click)="navigateTo(item.path)">
|
|
||||||
{{ item.text }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav-section">
|
|
||||||
<h4>Support</h4>
|
|
||||||
<button *ngFor="let item of supportNavItems" kendoButton
|
|
||||||
[fillMode]="item.active ? 'solid' : 'flat'" [themeColor]="item.active ? 'primary' : 'base'"
|
|
||||||
[svgIcon]="item.icon" class="nav-button" (click)="navigateTo(item.path)">
|
|
||||||
{{ item.text }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav-section" *ngIf="showMemberAdminSection || showUserAdminSection">
|
|
||||||
<h4>Administration</h4>
|
|
||||||
<button *ngFor="let item of memberAdminNavItems" kendoButton
|
|
||||||
[fillMode]="item.active ? 'solid' : 'flat'" [themeColor]="item.active ? 'primary' : 'base'"
|
|
||||||
[svgIcon]="item.icon" class="nav-button" (click)="navigateTo(item.path)">
|
|
||||||
{{ item.text }}
|
|
||||||
</button>
|
|
||||||
<ng-container *ngIf="showUserAdminSection">
|
|
||||||
<button *ngFor="let item of userAdminNavItems" kendoButton
|
|
||||||
[fillMode]="item.active ? 'solid' : 'flat'" [themeColor]="item.active ? 'primary' : 'base'"
|
|
||||||
[svgIcon]="item.icon" class="nav-button" (click)="navigateTo(item.path)">
|
|
||||||
{{ item.text }}
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav-section" *ngIf="showFinanceSection">
|
|
||||||
<h4>Finance</h4>
|
|
||||||
<button *ngFor="let item of financeNavItems" kendoButton
|
|
||||||
[fillMode]="item.active ? 'solid' : 'flat'" [themeColor]="item.active ? 'primary' : 'base'"
|
|
||||||
[svgIcon]="item.icon" class="nav-button" (click)="navigateTo(item.path)">
|
|
||||||
{{ item.text }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</kendo-drawer-content>
|
|
||||||
</kendo-drawer>
|
|
||||||
</kendo-drawer-container>
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
.drawer-content {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer-header {
|
|
||||||
padding: 1.5rem 1rem;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0 0 0.25rem 0;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer-nav {
|
|
||||||
flex: 1;
|
|
||||||
padding: 1rem 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-section {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
padding: 0 1rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-button {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: flex-start;
|
|
||||||
text-align: left;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
margin: 0.125rem 0;
|
|
||||||
border-radius: 0;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.k-button-solid {
|
|
||||||
background-color: #e3f2fd;
|
|
||||||
color: #1976d2;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #bbdefb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { Router, NavigationEnd, RouterModule } from '@angular/router';
|
|
||||||
import { LayoutModule } from '@progress/kendo-angular-layout';
|
|
||||||
import { ButtonsModule } from '@progress/kendo-angular-buttons';
|
|
||||||
import { IconsModule } from '@progress/kendo-angular-icons';
|
|
||||||
import { SVGIcon, homeIcon, calendarIcon, userIcon, groupIcon } from '@progress/kendo-svg-icons';
|
|
||||||
import { LayoutService } from '../../../../layout/services/layout.service';
|
|
||||||
import { AuthService, UserInfo } from '../../../../shared/services/auth.service';
|
|
||||||
import { Subject, takeUntil, filter } from 'rxjs';
|
|
||||||
|
|
||||||
interface NavItem {
|
|
||||||
text: string;
|
|
||||||
icon: SVGIcon;
|
|
||||||
path: string;
|
|
||||||
active?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-user-navbar',
|
|
||||||
standalone: true,
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
RouterModule,
|
|
||||||
LayoutModule,
|
|
||||||
ButtonsModule,
|
|
||||||
IconsModule
|
|
||||||
],
|
|
||||||
templateUrl: './user-navbar.component.html',
|
|
||||||
styleUrls: ['./user-navbar.component.scss']
|
|
||||||
})
|
|
||||||
export class UserNavbarComponent implements OnInit, OnDestroy {
|
|
||||||
public homeIcon: SVGIcon = homeIcon;
|
|
||||||
public calendarIcon: SVGIcon = calendarIcon;
|
|
||||||
public peopleIcon: SVGIcon = userIcon; // Using userIcon as fallback
|
|
||||||
public bedIcon: SVGIcon = userIcon; // Using userIcon as fallback
|
|
||||||
public userIcon: SVGIcon = userIcon;
|
|
||||||
public pillIcon: SVGIcon = userIcon; // Using userIcon as fallback
|
|
||||||
public chartIcon: SVGIcon = userIcon; // Using userIcon as fallback
|
|
||||||
public buildingIcon: SVGIcon = userIcon; // Using userIcon as fallback
|
|
||||||
public creditCardIcon: SVGIcon = userIcon; // Using userIcon as fallback
|
|
||||||
public supportIcon: SVGIcon = userIcon; // Using userIcon as fallback
|
|
||||||
|
|
||||||
public mainNavItems: NavItem[] = [
|
|
||||||
{ text: 'Dashboard', icon: this.homeIcon, path: '/user-portal/dashboard' },
|
|
||||||
{ text: 'Schedule', icon: this.calendarIcon, path: '/user-portal/schedule' },
|
|
||||||
{ text: 'Patients', icon: this.peopleIcon, path: '/user-portal/patients' }
|
|
||||||
];
|
|
||||||
|
|
||||||
public managementNavItems: NavItem[] = [
|
|
||||||
{ text: 'Bed Management', icon: this.bedIcon, path: '/user-portal/bed-management' },
|
|
||||||
{ text: 'Staff', icon: this.userIcon, path: '/user-portal/staff' },
|
|
||||||
{ text: 'Pharmacy', icon: this.pillIcon, path: '/user-portal/pharmacy' },
|
|
||||||
{ text: 'Reports', icon: this.chartIcon, path: '/user-portal/reports' },
|
|
||||||
{ text: 'Departments', icon: this.buildingIcon, path: '/user-portal/departments' },
|
|
||||||
{ text: 'Payments', icon: this.creditCardIcon, path: '/user-portal/payments' }
|
|
||||||
];
|
|
||||||
|
|
||||||
public supportNavItems: NavItem[] = [
|
|
||||||
{ text: 'Support', icon: this.supportIcon, path: '/user-portal/support' }
|
|
||||||
];
|
|
||||||
|
|
||||||
public memberAdminNavItems: NavItem[] = [
|
|
||||||
{ text: 'Members', icon: groupIcon, path: '/user-portal/admin/members' },
|
|
||||||
];
|
|
||||||
|
|
||||||
public userAdminNavItems: NavItem[] = [
|
|
||||||
{ text: 'User Management', icon: userIcon, path: '/user-portal/admin/users' },
|
|
||||||
];
|
|
||||||
|
|
||||||
public financeNavItems: NavItem[] = [
|
|
||||||
{ text: 'Offering Entry', icon: this.creditCardIcon, path: '/user-portal/finance/offering-session' },
|
|
||||||
{ text: 'Givings', icon: this.chartIcon, path: '/user-portal/finance/givings' },
|
|
||||||
{ text: 'Giving Types', icon: this.buildingIcon, path: '/user-portal/finance/giving-categories' },
|
|
||||||
];
|
|
||||||
|
|
||||||
public showMemberAdminSection = false;
|
|
||||||
public showUserAdminSection = false;
|
|
||||||
public showFinanceSection = false;
|
|
||||||
|
|
||||||
private destroy$ = new Subject<void>();
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
public layoutService: LayoutService,
|
|
||||||
private router: Router,
|
|
||||||
private authService: AuthService
|
|
||||||
) { }
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
// Listen to route changes to update active states
|
|
||||||
this.router.events
|
|
||||||
.pipe(
|
|
||||||
filter(event => event instanceof NavigationEnd),
|
|
||||||
takeUntil(this.destroy$)
|
|
||||||
)
|
|
||||||
.subscribe((event: NavigationEnd) => {
|
|
||||||
this.updateActiveStates(event.url);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set initial active state
|
|
||||||
this.updateActiveStates(this.router.url);
|
|
||||||
|
|
||||||
this.authService.currentUser$.pipe(takeUntil(this.destroy$)).subscribe(user => {
|
|
||||||
const roles = user?.roles ?? [];
|
|
||||||
this.showMemberAdminSection = roles.some(r => r === 'super_admin' || r === 'secretary');
|
|
||||||
this.showUserAdminSection = roles.includes('super_admin');
|
|
||||||
this.showFinanceSection = roles.some(r => r === 'finance' || r === 'super_admin');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.destroy$.next();
|
|
||||||
this.destroy$.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
public navigateTo(path: string): void {
|
|
||||||
this.router.navigate([path]);
|
|
||||||
this.layoutService.closeDrawer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateActiveStates(currentUrl: string): void {
|
|
||||||
// Reset all active states
|
|
||||||
[...this.mainNavItems, ...this.managementNavItems, ...this.supportNavItems,
|
|
||||||
...this.memberAdminNavItems, ...this.userAdminNavItems, ...this.financeNavItems]
|
|
||||||
.forEach(item => item.active = false);
|
|
||||||
|
|
||||||
// Set active state for current route
|
|
||||||
const activeItem = [...this.mainNavItems, ...this.managementNavItems, ...this.supportNavItems,
|
|
||||||
...this.memberAdminNavItems, ...this.userAdminNavItems, ...this.financeNavItems]
|
|
||||||
.find(item => currentUrl.startsWith(item.path));
|
|
||||||
|
|
||||||
if (activeItem) {
|
|
||||||
activeItem.active = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+43
-16
@@ -509,7 +509,7 @@ Table: ExpenseCategoryGroups
|
|||||||
| SortOrder | int NOT NULL DEFAULT 0 | |
|
| SortOrder | int NOT NULL DEFAULT 0 | |
|
||||||
| IsActive | bool NOT NULL DEFAULT true | |
|
| IsActive | bool NOT NULL DEFAULT true | |
|
||||||
|
|
||||||
**Seed 大類(10 個):**
|
**Seed 大類(11 個):**
|
||||||
|
|
||||||
| Id | Name_en | Name_zh |
|
| Id | Name_en | Name_zh |
|
||||||
|----|---------|---------|
|
|----|---------|---------|
|
||||||
@@ -523,6 +523,7 @@ Table: ExpenseCategoryGroups
|
|||||||
| 8 | Missions | 宣教 |
|
| 8 | Missions | 宣教 |
|
||||||
| 9 | Benevolence | 關懷救助 |
|
| 9 | Benevolence | 關懷救助 |
|
||||||
| 10 | Other | 其他 |
|
| 10 | Other | 其他 |
|
||||||
|
| 11 | Personnel | 人事 |
|
||||||
|
|
||||||
### ExpenseSubCategory(支出子項目)
|
### ExpenseSubCategory(支出子項目)
|
||||||
|
|
||||||
@@ -539,25 +540,51 @@ Table: ExpenseSubCategories
|
|||||||
| SortOrder | int NOT NULL DEFAULT 0 | |
|
| SortOrder | int NOT NULL DEFAULT 0 | |
|
||||||
| IsActive | bool NOT NULL DEFAULT true | |
|
| IsActive | bool NOT NULL DEFAULT true | |
|
||||||
|
|
||||||
**Seed 子項目(部分範例):**
|
**Seed 子項目(完整種子):**
|
||||||
|
|
||||||
| GroupId | Name_en | Name_zh |
|
| GroupId | Name_en | Name_zh |
|
||||||
|---------|---------|---------|
|
|---------|---------|---------|
|
||||||
| 1 Equipment | Audio/Visual Equipment | 影音設備 |
|
| 1 Equipment | Purchase | 購置 |
|
||||||
| 1 Equipment | Computer & Peripherals | 電腦周邊 |
|
| 1 Equipment | Rental | 租借 |
|
||||||
| 1 Equipment | Musical Instruments | 樂器 |
|
| 1 Equipment | Maintenance & Repair | 維修 |
|
||||||
| 2 Consumables | Office Supplies | 辦公用品 |
|
| 2 Consumables | Batteries | 電池 |
|
||||||
|
| 2 Consumables | Accessories | 配件 |
|
||||||
| 2 Consumables | Cleaning Supplies | 清潔用品 |
|
| 2 Consumables | Cleaning Supplies | 清潔用品 |
|
||||||
| 3 Food & Beverage | Sunday Agape Meal | 愛宴餐費 |
|
| 2 Consumables | Office Supplies | 文具 |
|
||||||
| 3 Food & Beverage | Fellowship Snacks | 交誼茶點 |
|
| 3 Food & Beverage | Catering | 出餐費用 |
|
||||||
| 4 Training | Conference Registration | 會議報名費 |
|
| 3 Food & Beverage | Food Ingredients | 食材採購 |
|
||||||
| 4 Training | Books & Resources | 書籍教材 |
|
| 3 Food & Beverage | Utensils | 器具 |
|
||||||
| 5 Materials | Children's Curriculum | 兒童教材 |
|
| 3 Food & Beverage | Consumables | 消耗品 |
|
||||||
| 5 Materials | Bibles & Hymnals | 聖經/詩本 |
|
| 4 Training | Course Fees | 課程費用 |
|
||||||
|
| 4 Training | Books | 書籍 |
|
||||||
|
| 4 Training | Conference | 研討會 |
|
||||||
|
| 4 Training | Travel | 差旅 |
|
||||||
|
| 5 Materials | Printing | 印刷費用 |
|
||||||
|
| 5 Materials | Craft Supplies | 手工材料 |
|
||||||
|
| 5 Materials | Copyright & Licensing | 版權購買 |
|
||||||
| 6 Facility | Rent | 場地租金 |
|
| 6 Facility | Rent | 場地租金 |
|
||||||
| 6 Facility | Utilities | 水電費 |
|
| 6 Facility | Utilities | 水電 |
|
||||||
| 7 Printing | Bulletins | 週報印刷 |
|
| 6 Facility | Property Insurance | 財產保險 |
|
||||||
| 8 Missions | Missionary Support | 宣教士支持 |
|
| 6 Facility | Decoration | 裝飾 |
|
||||||
|
| 7 Printing | Bulletins | 週報 |
|
||||||
|
| 7 Printing | Order of Service | 程序單 |
|
||||||
|
| 7 Printing | Posters | 海報 |
|
||||||
|
| 8 Missions | Offering Transfer | 奉獻轉帳 |
|
||||||
|
| 8 Missions | Missionary Support | 宣教士支援 |
|
||||||
|
| 8 Missions | Travel | 差旅 |
|
||||||
|
| 9 Benevolence | Emergency Aid | 急難救助 |
|
||||||
|
| 9 Benevolence | Condolence Gifts | 慰問禮品 |
|
||||||
|
| 9 Benevolence | Visit Expenses | 探訪費用 |
|
||||||
|
| 10 Other | Miscellaneous | 雜支 |
|
||||||
|
| 11 Personnel | Salary & Wages | 薪資 |
|
||||||
|
| 11 Personnel | Payroll Taxes | 薪資稅費 |
|
||||||
|
| 11 Personnel | Employee Benefits | 員工福利 |
|
||||||
|
| 11 Personnel | Workers Compensation | 勞工保險 |
|
||||||
|
| 11 Personnel | Honorarium | 酬庸 |
|
||||||
|
| 11 Personnel | Staff Training | 同工進修 |
|
||||||
|
| 11 Personnel | Contract Labor | 外包勞務 |
|
||||||
|
|
||||||
|
> **備注:** `Facility > 財產保險` 指建築物/場地責任險;員工健保、團體保險等歸 `Personnel > 員工福利`。同工代墊報銷依**實際購買物**選大類,不歸人事。
|
||||||
|
|
||||||
### Expense(支出記錄)
|
### Expense(支出記錄)
|
||||||
|
|
||||||
@@ -954,7 +981,7 @@ super_admin, pastor, board_member, coworker_chair, ministry_leader, district_lea
|
|||||||
5. Mission / 宣教奉獻
|
5. Mission / 宣教奉獻
|
||||||
```
|
```
|
||||||
|
|
||||||
### ExpenseCategoryGroups(10 個大類)
|
### ExpenseCategoryGroups(11 個大類)
|
||||||
```
|
```
|
||||||
見 §8 Seed 大類列表
|
見 §8 Seed 大類列表
|
||||||
```
|
```
|
||||||
|
|||||||
+55
-9
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
**教會:** River Of Life Christian Church In Arcadia (ROLAC)
|
**教會:** River Of Life Christian Church In Arcadia (ROLAC)
|
||||||
**規模:** 50–100 人
|
**規模:** 50–100 人
|
||||||
**文件版本:** v0.2 (2026-05-24)
|
**文件版本:** v0.3 (2026-05-29)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -522,6 +522,7 @@ Ministry(事工部門) × 大類(Category Group) × 子項目(Sub-
|
|||||||
餐飲部門 > 餐飲 > 消耗品
|
餐飲部門 > 餐飲 > 消耗品
|
||||||
餐飲部門 > 餐飲 > 出餐費用
|
餐飲部門 > 餐飲 > 出餐費用
|
||||||
行政 > 辦公 > 文具耗材
|
行政 > 辦公 > 文具耗材
|
||||||
|
行政 > 人事 > 薪資
|
||||||
兒童事工 > 教材 > 印刷費用
|
兒童事工 > 教材 > 印刷費用
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -577,17 +578,20 @@ Expense ← 每筆支出
|
|||||||
| Food & Beverage / 餐飲 | 出餐費用 · 食材採購 · 器具 · 消耗品 | 餐飲、兒牧 |
|
| Food & Beverage / 餐飲 | 出餐費用 · 食材採購 · 器具 · 消耗品 | 餐飲、兒牧 |
|
||||||
| Training / 教育訓練 | 課程費用 · 書籍 · 研討會 · 差旅 | 敬拜、PPT/影音、音控、兒牧 |
|
| Training / 教育訓練 | 課程費用 · 書籍 · 研討會 · 差旅 | 敬拜、PPT/影音、音控、兒牧 |
|
||||||
| Materials / 教材 | 印刷費用 · 手工材料 · 版權購買 | 兒牧、講道、司會 |
|
| Materials / 教材 | 印刷費用 · 手工材料 · 版權購買 | 兒牧、講道、司會 |
|
||||||
| Facility / 場地 | 場地租金 · 水電 · 保險 · 裝飾 | 場地組、行政 |
|
| Facility / 場地 | 場地租金 · 水電 · 財產保險 · 裝飾 | 場地組、行政 |
|
||||||
| Printing / 印刷 | 週報 · 程序單 · 海報 | 行政、招待、兒牧 |
|
| Printing / 印刷 | 週報 · 程序單 · 海報 | 行政、招待、兒牧 |
|
||||||
| Missions / 宣教 | 奉獻轉帳 · 宣教士支援 · 差旅 | 行政 |
|
| Missions / 宣教 | 奉獻轉帳 · 宣教士支援 · 差旅 | 行政 |
|
||||||
| Benevolence / 關懷 | 急難救助 · 慰問禮品 · 探訪費用 | 行政 |
|
| Benevolence / 關懷 | 急難救助 · 慰問禮品 · 探訪費用 | 行政 |
|
||||||
| Other / 其他 | 雜支 | 所有 |
|
| Other / 其他 | 雜支 | 所有 |
|
||||||
|
| Personnel / 人事 | 薪資 · 薪資稅費 · 員工福利 · 勞工保險 · 酬庸 · 同工進修 · 外包勞務 | 行政 |
|
||||||
|
|
||||||
|
> **分類備注:** `Facility > 財產保險` 為建築物/場地責任險;員工健保等歸 `Personnel > 員工福利`。同工代墊報銷(`StaffReimbursement`)依實際購買物選大類,薪資/福利付款才歸人事。
|
||||||
|
|
||||||
**Ministry × 大類 常用對應(供財務設定參考)**
|
**Ministry × 大類 常用對應(供財務設定參考)**
|
||||||
|
|
||||||
| Ministry | 最常用大類 |
|
| Ministry | 最常用大類 |
|
||||||
|----------|-----------|
|
|----------|-----------|
|
||||||
| 行政 Administration | 辦公耗材 · 印刷 · 場地 · 宣教 · 關懷 |
|
| 行政 Administration | 人事 · 辦公耗材 · 印刷 · 場地 · 宣教 · 關懷 |
|
||||||
| 講道 Preaching | 教材 · 書籍(Training) |
|
| 講道 Preaching | 教材 · 書籍(Training) |
|
||||||
| 司會 Emcee | 印刷(程序單) |
|
| 司會 Emcee | 印刷(程序單) |
|
||||||
| 敬拜 Worship | 設備 · 教育訓練 · 耗材 |
|
| 敬拜 Worship | 設備 · 教育訓練 · 耗材 |
|
||||||
@@ -677,6 +681,7 @@ finance/
|
|||||||
| 審核 / 批准 | `finance`、`super_admin` |
|
| 審核 / 批准 | `finance`、`super_admin` |
|
||||||
| 建立廠商直接付款 | `finance`、`super_admin` |
|
| 建立廠商直接付款 | `finance`、`super_admin` |
|
||||||
| 查看所有支出 | `finance`、`pastor`、`super_admin` |
|
| 查看所有支出 | `finance`、`pastor`、`super_admin` |
|
||||||
|
| 查看人事類支出 | `finance`、`pastor`、`super_admin`(事工領袖不可見) |
|
||||||
| 查看自己的申請 | 提交者本人 |
|
| 查看自己的申請 | 提交者本人 |
|
||||||
| 月底對帳 | `finance`、`super_admin` |
|
| 月底對帳 | `finance`、`super_admin` |
|
||||||
|
|
||||||
@@ -1223,7 +1228,7 @@ AuditLog(bigint PK,immutable,所有操作均記錄)
|
|||||||
| 後端 API | **ASP.NET Core (C#)** | REST API |
|
| 後端 API | **ASP.NET Core (C#)** | REST API |
|
||||||
| ORM | **Entity Framework Core** | Code-first migrations |
|
| ORM | **Entity Framework Core** | Code-first migrations |
|
||||||
| 資料庫 | **PostgreSQL** | 自架於 Docker |
|
| 資料庫 | **PostgreSQL** | 自架於 Docker |
|
||||||
| 檔案儲存 | **Azure Blob Storage** | 教友照片、PDF 收據、CMS 媒體 |
|
| 檔案儲存 | **本地檔案儲存(現階段)→ Azure Blob Storage(未來)** | 透過 `IFileOperationService` 抽象,現以 `LocalFileOperationService`(base folder 由 config 設定)實作,未來切換 `AzureFileOperationService`。教友照片、PDF 收據、CMS 媒體 |
|
||||||
| 原始碼管理 | **Gitea** | 自架,Docker 容器 |
|
| 原始碼管理 | **Gitea** | 自架,Docker 容器 |
|
||||||
| CI/CD | **Jenkins** | 自架,Docker 容器 |
|
| CI/CD | **Jenkins** | 自架,Docker 容器 |
|
||||||
| 容器化 | **Docker + Docker Compose** | 所有服務容器化 |
|
| 容器化 | **Docker + Docker Compose** | 所有服務容器化 |
|
||||||
@@ -1310,6 +1315,45 @@ assets/i18n/zh-TW.json
|
|||||||
- **ASP.NET Core Identity** — 使用者管理、密碼雜湊
|
- **ASP.NET Core Identity** — 使用者管理、密碼雜湊
|
||||||
- **全新資料庫** — 無舊資料遷移需求,直接 EF Code-First Migration
|
- **全新資料庫** — 無舊資料遷移需求,直接 EF Code-First Migration
|
||||||
|
|
||||||
|
### 檔案儲存抽象層 (File Storage Abstraction)
|
||||||
|
|
||||||
|
> **背景(2026-05-29 決定):** Microsoft 拒絕了我們的 Nonprofit / Azure 申請,**現階段無法使用 Azure Blob Storage**。
|
||||||
|
> 因此先改用**本地檔案儲存**(base folder 由 config 設定),未來取得 Azure 額度後再切換為 Azure Blob,**業務程式碼不需改動**。
|
||||||
|
|
||||||
|
**設計:以介面抽象隔離儲存後端**
|
||||||
|
|
||||||
|
所有檔案讀寫(教友照片、PDF 收據、CMS 媒體、收據照片、敬拜樂譜/影片等)一律透過 `IFileOperationService` 介面操作,業務層不直接依賴任何特定儲存實作。
|
||||||
|
|
||||||
|
```
|
||||||
|
IFileOperationService ← 抽象介面(業務層僅依賴此介面)
|
||||||
|
├── SaveAsync(path, stream) → 儲存檔案,回傳相對路徑 / 識別碼
|
||||||
|
├── GetAsync(path) → 讀取檔案串流
|
||||||
|
├── DeleteAsync(path) → 刪除檔案
|
||||||
|
├── ExistsAsync(path) → 是否存在
|
||||||
|
└── GetUrlAsync(path, expiry?) → 取得存取 URL(本地:API 代理路徑;Azure:SAS URL)
|
||||||
|
|
||||||
|
LocalFileOperationService ← 現階段實作(✅ Phase 1)
|
||||||
|
├── Base folder 從 config 讀取(e.g. FileStorage:BasePath)
|
||||||
|
├── 路徑沿用既有結構(cms/images/、finance/receipts/、worship/… 相對於 base folder)
|
||||||
|
├── Private 檔案經由 API 代理 + 角色授權提供存取(取代 SAS URL)
|
||||||
|
└── 容器化時 base folder 對映到 Docker volume,確保持久化
|
||||||
|
|
||||||
|
AzureFileOperationService ← 未來實作(🔜 取得 Azure 額度後)
|
||||||
|
├── 對映到 Azure Blob Container
|
||||||
|
├── GetUrlAsync 回傳具時效的 SAS URL
|
||||||
|
└── 切換方式:DI 註冊改綁此實作 + 一次性資料搬移,業務程式碼零改動
|
||||||
|
```
|
||||||
|
|
||||||
|
**設定範例(appsettings)**
|
||||||
|
```jsonc
|
||||||
|
"FileStorage": {
|
||||||
|
"Provider": "Local", // Local | Azure(未來)
|
||||||
|
"BasePath": "/data/rolac-files" // 本地 base folder(容器內對映 volume)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **與本文件其他段落的關係:** 文中所有提到 `Azure Blob`/blob 路徑的地方(§3.5 CMS 圖片、§3.6d `finance/receipts/`、§3.12e `worship/...`),現階段一律改由 `IFileOperationService` + `LocalFileOperationService` 以對應相對路徑儲存於 config 的 base folder 下;待 Azure 開通後切換 `AzureFileOperationService` 即還原為 Blob 儲存,路徑結構不變。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. 開發階段規劃 (Roadmap)
|
## 7. 開發階段規劃 (Roadmap)
|
||||||
@@ -1322,6 +1366,7 @@ assets/i18n/zh-TW.json
|
|||||||
- [X] 認證系統(JWT + Refresh Token + ASP.NET Identity)
|
- [X] 認證系統(JWT + Refresh Token + ASP.NET Identity)
|
||||||
- [X] RBAC 框架(角色 + Ministry Scope middleware)
|
- [X] RBAC 框架(角色 + Ministry Scope middleware)
|
||||||
- [ ] Audit Log 基礎建設
|
- [ ] Audit Log 基礎建設
|
||||||
|
- [ ] 檔案儲存抽象層(`IFileOperationService` + `LocalFileOperationService`,base folder 從 config 讀取)
|
||||||
- [ ] Mobile-first UI 元件庫設定(底部導覽、touch target 規範)
|
- [ ] Mobile-first UI 元件庫設定(底部導覽、touch target 規範)
|
||||||
|
|
||||||
### Phase 1 — 六月上線 MVP(5 週,目標 2026-06-30)
|
### Phase 1 — 六月上線 MVP(5 週,目標 2026-06-30)
|
||||||
@@ -1334,22 +1379,22 @@ assets/i18n/zh-TW.json
|
|||||||
- [ ] 聯絡表單
|
- [ ] 聯絡表單
|
||||||
|
|
||||||
#### 教友管理
|
#### 教友管理
|
||||||
- [ ] 教友 CRUD(基本資料、照片上傳至 Azure Blob)
|
- [ ] 教友 CRUD(基本資料、照片上傳,經 `IFileOperationService` → 本地儲存)
|
||||||
- [ ] 家庭單元管理
|
- [ ] 家庭單元管理
|
||||||
- [ ] 搜尋 / 篩選(姓名、狀態、小組)
|
- [ ] 搜尋 / 篩選(姓名、狀態、小組)
|
||||||
- [ ] 教友狀態(會員 / 訪客 / 前會員)
|
- [ ] 教友狀態(會員 / 訪客 / 前會員)
|
||||||
- [ ] i18n 語言切換 UI(EN / 中 按鈕)
|
- [ ] i18n 語言切換 UI(EN / 中 按鈕)
|
||||||
|
|
||||||
#### 奉獻追蹤(手動)
|
#### 奉獻追蹤(手動)
|
||||||
- [ ] 奉獻類型設定(Tithe / Offering / Special)
|
- [X] 奉獻類型設定(Tithe / Offering / Special)
|
||||||
- [ ] 單筆奉獻記錄(現金 / 支票 / Zelle / PayPal)
|
- [X] 單筆奉獻記錄(現金 / 支票 / Zelle / PayPal)
|
||||||
- [ ] **主日奉獻袋批次輸入(OfferingSession)** ← 鍵盤優先快速錄入
|
- [X] **主日奉獻袋批次輸入(OfferingSession)** ← 鍵盤優先快速錄入
|
||||||
- [ ] 個人奉獻歷史查詢(教友 App 端)
|
- [ ] 個人奉獻歷史查詢(教友 App 端)
|
||||||
- [ ] 年度收據 PDF 產生(QuestPDF,EIN 42-2682968)
|
- [ ] 年度收據 PDF 產生(QuestPDF,EIN 42-2682968)
|
||||||
- [ ] 年度收據 Email 批次寄送
|
- [ ] 年度收據 Email 批次寄送
|
||||||
|
|
||||||
#### 支出追蹤 & 報銷
|
#### 支出追蹤 & 報銷
|
||||||
- [ ] 支出類別設定(愛宴 / 辦公用品 / 宣教…)
|
- [ ] 支出類別設定(11 大類種子:設備 / 餐飲 / 人事 / 宣教…)
|
||||||
- [ ] 廠商直接付款記錄(含支票號碼)
|
- [ ] 廠商直接付款記錄(含支票號碼)
|
||||||
- [ ] 同工代墊報銷申請(含收據照片上傳)
|
- [ ] 同工代墊報銷申請(含收據照片上傳)
|
||||||
- [ ] 財務審核流程(Pending → Approved → Paid)
|
- [ ] 財務審核流程(Pending → Approved → Paid)
|
||||||
@@ -1464,3 +1509,4 @@ assets/i18n/zh-TW.json
|
|||||||
| 17 | 兒童特殊欄位 | 緊急聯絡人、過敏、接送授權,暫緩 | 🟢 低 | ⏳ 未來再評估 |
|
| 17 | 兒童特殊欄位 | 緊急聯絡人、過敏、接送授權,暫緩 | 🟢 低 | ⏳ 未來再評估 |
|
||||||
| 18 | CCLI License 號碼 | 教會是否已有 CCLI License?需填入系統設定 | 🟡 中 | ⏳ 待確認 |
|
| 18 | CCLI License 號碼 | 教會是否已有 CCLI License?需填入系統設定 | 🟡 中 | ⏳ 待確認 |
|
||||||
| 19 | ~~Phase 2/3 模組分配~~ | **已決定**:Phase 2 = 服事表排班 + 主日出席統計 + 小組管理;Phase 3 = 事工預算 | 🔴 高 | ✅ 決定 |
|
| 19 | ~~Phase 2/3 模組分配~~ | **已決定**:Phase 2 = 服事表排班 + 主日出席統計 + 小組管理;Phase 3 = 事工預算 | 🔴 高 | ✅ 決定 |
|
||||||
|
| 20 | ~~檔案儲存後端~~ | **已決定**:Microsoft 拒絕申請,暫無法用 Azure Blob。改以 `IFileOperationService` 抽象,現階段用 `LocalFileOperationService`(base folder 由 config 設定),未來取得 Azure 額度後切換 `AzureFileOperationService`(見 §6 檔案儲存抽象層) | 🔴 高 | ✅ 決定 |
|
||||||
|
|||||||
Reference in New Issue
Block a user