feat(properties): CRUD + service/trust/document editors (plan phase 4)
Utilities section becomes create/edit/archive-able, with its child data. API: - Property gains archivedAt (soft-delete); list/browser default to archivedAt=null with ?includeArchived opt-in. - PropertiesService: header create/update/archive/restore (customer FK validated); PropertyService add/update/remove scoped to the property; TrustAccount upsert (1:1) + remove; ServiceDocument pointer delete. - Controller write routes: create needs STAFF+ (property:create), archive MANAGER+ (property:delete), every service/trust/document route property:update. Mutations audited. DTOs added. - Document *upload* deliberately deferred: it needs the object-storage client wired into the API (today only the migration writes to MinIO); removing an existing pointer row is supported and the UI says so. Web: - PropertyForm (header) with CustomerPicker; /servicios/nuevo (accepts ?customerId prefill) and /servicios/[id]/editar. - Property detail: gated action bar (Editar/Archivar) + "Administrar propiedad" — services via the shared ChildCollection editor, an inline 1:1 TrustEditor (create/update/clear), and document-row delete. - "Nueva propiedad" buttons on the list and customer detail (prefilled). api.ts + types for all of it. Verified against dev: property create (archivedAt null), service add/update, VIEWER service-add 403, trust upsert (create then update the same row), trust/service remove, cross-property child guard 404, archive drops from the default list and includeArchived surfaces it. Both apps compile clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -34,9 +34,12 @@ import type {
|
||||
LookupsResponse,
|
||||
PropertyDetail,
|
||||
PropertyFacets,
|
||||
PropertyInput,
|
||||
PropertyListResponse,
|
||||
PropertySort,
|
||||
PropertyStats,
|
||||
ServiceInput,
|
||||
TrustInput,
|
||||
Role,
|
||||
ServiceKind,
|
||||
Statement,
|
||||
@@ -338,6 +341,71 @@ export function getProperty(
|
||||
return apiFetch<PropertyDetail>(`/properties/${id}?days=${days}`);
|
||||
}
|
||||
|
||||
export function createProperty(input: PropertyInput): Promise<PropertyDetail> {
|
||||
return apiFetch<PropertyDetail>("/properties", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
export function updateProperty(
|
||||
id: string,
|
||||
input: Partial<PropertyInput>,
|
||||
): Promise<PropertyDetail> {
|
||||
return apiFetch<PropertyDetail>(`/properties/${id}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
export function archiveProperty(id: string): Promise<PropertyDetail> {
|
||||
return apiFetch<PropertyDetail>(`/properties/${id}`, { method: "DELETE" });
|
||||
}
|
||||
export function restoreProperty(id: string): Promise<PropertyDetail> {
|
||||
return apiFetch<PropertyDetail>(`/properties/${id}/restore`, { method: "POST" });
|
||||
}
|
||||
|
||||
// Service child CRUD.
|
||||
export function addService(propertyId: string, input: ServiceInput): Promise<unknown> {
|
||||
return apiFetch(`/properties/${propertyId}/services`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
export function updateService(
|
||||
propertyId: string,
|
||||
serviceId: string,
|
||||
input: Partial<ServiceInput>,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`/properties/${propertyId}/services/${serviceId}`, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
export function removeService(propertyId: string, serviceId: string): Promise<unknown> {
|
||||
return apiFetch(`/properties/${propertyId}/services/${serviceId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
// Trust account (1:1 upsert).
|
||||
export function upsertTrust(propertyId: string, input: TrustInput): Promise<unknown> {
|
||||
return apiFetch(`/properties/${propertyId}/trust`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
}
|
||||
export function removeTrust(propertyId: string): Promise<unknown> {
|
||||
return apiFetch(`/properties/${propertyId}/trust`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export function removePropertyDocument(
|
||||
propertyId: string,
|
||||
documentId: string,
|
||||
): Promise<unknown> {
|
||||
return apiFetch(`/properties/${propertyId}/documents/${documentId}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------- Billing / statements module */
|
||||
|
||||
export interface MovementQuery {
|
||||
|
||||
Reference in New Issue
Block a user