Skip to content

Commit c20e517

Browse files
committed
feat(household): allow deleting households
1 parent aea82e7 commit c20e517

4 files changed

Lines changed: 115 additions & 23 deletions

File tree

backend/app/main.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ def get_db():
3434
db.close()
3535

3636

37-
# --- ONBOARDING ENDPOINTS ---
38-
39-
4037
@app.get("/system/status")
4138
def get_system_status(db: Session = Depends(get_db)):
4239
"""Checks if a household exists to determine if onboarding is needed."""
@@ -46,6 +43,9 @@ def get_system_status(db: Session = Depends(get_db)):
4643
return {"initialized": False, "household": None}
4744

4845

46+
# --- HOUSEHOLD ENDPOINTS ---
47+
48+
4949
@app.get("/households/")
5050
def read_households(db: Session = Depends(get_db)):
5151
"""List all households for the switcher menu."""
@@ -61,6 +61,29 @@ def create_household(name: str = Form(...), db: Session = Depends(get_db)):
6161
return new_household
6262

6363

64+
@app.delete("/households/{household_id}")
65+
def delete_household(household_id: int, db: Session = Depends(get_db)):
66+
household = (
67+
db.query(models.Household).filter(models.Household.id == household_id).first()
68+
)
69+
if not household:
70+
raise HTTPException(status_code=404, detail="Household not found")
71+
policies = (
72+
db.query(models.Policy).filter(models.Policy.household_id == household_id).all()
73+
)
74+
for policy in policies:
75+
if policy.document_path and os.path.exists(policy.document_path):
76+
try:
77+
os.remove(policy.document_path)
78+
except Exception as e:
79+
print(f"Error deleting file {policy.document_path}: {e}")
80+
81+
db.delete(household)
82+
db.commit()
83+
84+
return {"ok": True}
85+
86+
6487
# --- ASSET ENDPOINTS ---
6588

6689

backend/app/models.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ class Household(Base):
88
__tablename__ = "households"
99

1010
id = Column(Integer, primary_key=True, index=True)
11-
name = Column(String, index=True) # e.g. "The Smith Family"
11+
name = Column(String, index=True)
1212

13-
# Relationship to policies
1413
policies = relationship("Policy", back_populates="household")
1514
assets = relationship("Asset", back_populates="household")
1615

@@ -19,9 +18,9 @@ class Asset(Base):
1918
__tablename__ = "assets"
2019
id = Column(Integer, primary_key=True, index=True)
2120
household_id = Column(Integer, ForeignKey("households.id"))
22-
name = Column(String) # e.g. "Dad's Honda"
23-
type = Column(String) # e.g. "Vehicle"
24-
details = Column(JSON, default={}) # Stores { make: 'Honda', reg: 'ABC' }
21+
name = Column(String)
22+
type = Column(String)
23+
details = Column(JSON, default={})
2524

2625
household = relationship("Household", back_populates="assets")
2726
policies = relationship("Policy", back_populates="asset")

frontend/src/App.vue

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,35 +59,78 @@
5959
<v-card-text class="pa-4">
6060
<v-list lines="one" border class="rounded-lg mb-4">
6161
<v-list-item v-for="h in households" :key="h.id" :title="h.name">
62+
<template v-slot:subtitle v-if="currentHousehold?.id === h.id">
63+
<span class="text-success font-weight-bold text-caption">Currently Active</span>
64+
</template>
65+
6266
<template v-slot:append>
63-
<v-chip v-if="currentHousehold?.id === h.id" size="x-small" color="success">
64-
Active
65-
</v-chip>
67+
<v-btn
68+
v-if="currentHousehold?.id !== h.id"
69+
size="small"
70+
variant="text"
71+
color="primary"
72+
@click="switchHousehold(h)"
73+
>Switch</v-btn
74+
>
75+
76+
<v-btn
77+
icon="mdi-delete"
78+
size="small"
79+
variant="text"
80+
color="grey"
81+
class="ml-2"
82+
@click="confirmDeleteHousehold(h)"
83+
></v-btn>
6684
</template>
6785
</v-list-item>
6886
</v-list>
87+
6988
<div class="d-flex">
7089
<v-text-field
7190
v-model="newHouseholdName"
72-
label="Name"
91+
label="New Household Name"
7392
variant="outlined"
7493
density="compact"
7594
hide-details
7695
class="mr-2"
77-
/>
96+
></v-text-field>
7897
<v-btn
7998
color="primary"
8099
height="40"
81100
@click="createHousehold(false)"
82101
:loading="creatingHousehold"
102+
>Add</v-btn
83103
>
84-
Add
85-
</v-btn>
86104
</div>
87105
</v-card-text>
106+
<v-card-actions
107+
><v-spacer></v-spacer
108+
><v-btn variant="text" @click="manageDialog = false">Close</v-btn></v-card-actions
109+
>
110+
</v-card>
111+
</v-dialog>
112+
113+
<v-dialog v-model="deleteHouseholdDialog" max-width="400px">
114+
<v-card>
115+
<v-card-title class="text-h5 text-red">Delete Household?</v-card-title>
116+
<v-card-text>
117+
Are you sure you want to delete <strong>{{ householdToDelete?.name }}</strong
118+
>? <br /><br />
119+
<v-alert type="warning" variant="tonal" density="compact" border="start">
120+
This will permanently delete all
121+
<strong>Policies, Assets, and Documents</strong> associated with this household.
122+
</v-alert>
123+
</v-card-text>
88124
<v-card-actions>
89-
<v-spacer />
90-
<v-btn variant="text" @click="manageDialog = false"> Close </v-btn>
125+
<v-spacer></v-spacer>
126+
<v-btn color="grey" variant="text" @click="deleteHouseholdDialog = false">Cancel</v-btn>
127+
<v-btn
128+
color="red"
129+
variant="flat"
130+
@click="executeDeleteHousehold"
131+
:loading="deletingHousehold"
132+
>Delete Forever</v-btn
133+
>
91134
</v-card-actions>
92135
</v-card>
93136
</v-dialog>
@@ -107,8 +150,11 @@ export default {
107150
creatingHousehold: false,
108151
currencyCode: 'GBP',
109152
currentHousehold: null,
153+
deleteHouseholdDialog: false,
154+
deletingHousehold: false,
110155
drawer: null,
111156
households: [],
157+
householdToDelete: null,
112158
initialized: false,
113159
manageDialog: false,
114160
newHouseholdName: '',
@@ -153,6 +199,36 @@ export default {
153199
},
154200
switchHousehold(household) {
155201
this.currentHousehold = household
202+
this.manageDialog = false
203+
},
204+
confirmDeleteHousehold(household) {
205+
this.householdToDelete = household
206+
this.deleteHouseholdDialog = true
207+
},
208+
async executeDeleteHousehold() {
209+
if (!this.householdToDelete) return
210+
this.deletingHousehold = true
211+
try {
212+
await axios.delete(`http://localhost:8000/households/${this.householdToDelete.id}`)
213+
this.households = this.households.filter((h) => h.id !== this.householdToDelete.id)
214+
if (this.currentHousehold && this.currentHousehold.id === this.householdToDelete.id) {
215+
if (this.households.length > 0) {
216+
this.switchHousehold(this.households[0])
217+
} else {
218+
this.currentHousehold = null
219+
this.showOnboarding = true
220+
this.manageDialog = false
221+
}
222+
}
223+
224+
this.deleteHouseholdDialog = false
225+
this.householdToDelete = null
226+
} catch (e) {
227+
console.error('Failed to delete household', e)
228+
alert('Could not delete household. Ensure all policies are removed or try again.')
229+
} finally {
230+
this.deletingHousehold = false
231+
}
156232
},
157233
async createHousehold(isOnboarding) {
158234
if (!this.newHouseholdName) return

frontend/src/components/PolicyList.vue

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,6 @@ export default {
510510
this.$emit('delete', this.policyToDelete.id)
511511
this.deleteDialog = false
512512
this.policyToDelete = null
513-
} else {
514513
}
515514
},
516515
async uploadDocument(file) {
@@ -519,17 +518,12 @@ export default {
519518
formData.append('file', file)
520519
521520
try {
522-
// Call the new backend endpoint
523521
const res = await axios.put(
524522
`http://localhost:8000/policies/${this.selectedPolicy.id}/document`,
525523
formData,
526524
{ headers: { 'Content-Type': 'multipart/form-data' } },
527525
)
528-
529-
// Update local state immediately to show the PDF
530526
this.selectedPolicy.document_path = res.data.document_path
531-
532-
// Also update the main list so the icon appears in the table behind the modal
533527
const index = this.policies.findIndex((p) => p.id === this.selectedPolicy.id)
534528
if (index !== -1) {
535529
this.policies[index].document_path = res.data.document_path

0 commit comments

Comments
 (0)