feat(admin): birth date on the customer forms; permission saves limited to known rids (PERS-03, SEC-07)

- PERS-03: «تاریخ تولد» (Jalali DateInput) on the wholesale and retail
  forms, as legacy. The server accepts birth_date or birth_day, refuses
  impossible or future dates, clears on '', and returns both names.
- SEC-07: setRoles stores only rids in ALL_RIDS or their tree parents.
  Anything else is dropped with a warning log, so a tick on a legacy-only
  tree leaf never fails the save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-08 16:45:41 +03:30
co-authored by Claude Opus 5.5
parent 7bb5c2e00f
commit d91dcf197b
6 changed files with 105 additions and 6 deletions
@@ -10,8 +10,9 @@
render this component directly as a route-based fallback, but OmdeList.vue
now opens it as a modal via open/id props.
Dropped: the MyUpload avatar widget and the Jalali DateInput, which belong to
fields the redesigned Customer schema has no column for. Flagged in
«تاریخ تولد» (legacy's Jalali DateInput, birth_date) is back since the
Customer schema has birthDate (PERS-03). Dropped: the MyUpload avatar widget,
which belongs to a field the redesigned Customer schema has no column for. Flagged in
docs/ADMIN-PORT-PLAN.md rather than faked.
The نوع کاربری selector is live again now that Customer.type exists; its four
@@ -79,6 +80,11 @@
<a-form-item label="کد مشتری">
<a-input v-model:value="form.mcode" />
</a-form-item>
<DateInput
:model-value="form.birth_date || undefined"
label="تاریخ تولد"
@update:model-value="(v) => (form.birth_date = v ?? '')"
/>
<a-form-item label="کارشناس فروش">
<a-select v-model:value="form.reagent_id" allow-clear placeholder="انتخاب کنید">
<a-select-option v-for="r in reagent" :key="r.id" :value="r.id">
@@ -182,6 +188,11 @@
<a-form-item label="کد مشتری">
<a-input v-model:value="form.mcode" />
</a-form-item>
<DateInput
:model-value="form.birth_date || undefined"
label="تاریخ تولد"
@update:model-value="(v) => (form.birth_date = v ?? '')"
/>
<a-form-item label="کارشناس فروش">
<a-select v-model:value="form.reagent_id" allow-clear placeholder="انتخاب کنید">
<a-select-option v-for="r in reagent" :key="r.id" :value="r.id">
@@ -250,6 +261,7 @@ import BaseFormModal from '../../../components/common/BaseFormModal.vue';
import ImageUploadInput from '../../../components/upload/ImageUploadInput.vue';
import api from '../../../api/client';
import CityTreeSelect from '../../../components/common/CityTreeSelect.vue';
import DateInput from '../../../components/input/DateInput.vue';
const props = defineProps<{
open?: boolean;
@@ -294,6 +306,7 @@ const form = reactive<Record<string, any>>({
pasazh_name: '',
city_id: undefined,
mcode: '',
birth_date: '',
address: '',
// legacy defaulted to no selection; 2 (مغازه دار اپلیکیشن) is what the
// backend falls back to when the field is absent.
@@ -465,6 +478,7 @@ onMounted(async () => {
store_name: data.store_name ?? '',
pasazh_name: data.pasazh_name ?? '',
mcode: data.mcode ?? '',
birth_date: data.birth_date ?? '',
phone: data.phone ?? '',
type: data.type ?? undefined,
// Same reason as the five above: omitted here, they would be sent back
@@ -34,6 +34,11 @@
<a-form-item label="شهر">
<CityTreeSelect v-model:value="form.city_id" :cities="cities" />
</a-form-item>
<DateInput
:model-value="form.birth_day || undefined"
label="تاریخ تولد"
@update:model-value="(v) => (form.birth_day = v ?? '')"
/>
</div>
<a-button type="primary" @click="handleSubmit">ثبت اطلاعات</a-button>
@@ -48,6 +53,7 @@ import { useRoute, useRouter } from 'vue-router';
import { message } from 'ant-design-vue';
import api from '../../../api/client';
import CityTreeSelect from '../../../components/common/CityTreeSelect.vue';
import DateInput from '../../../components/input/DateInput.vue';
const route = useRoute();
const router = useRouter();
@@ -61,6 +67,7 @@ const form = reactive<Record<string, any>>({
lname: '',
phone: '',
city_id: undefined,
birth_day: '',
});
const err = reactive<Record<string, string>>({});
const REQUIRED = 'لطفا مقدار را وارد کنید';
@@ -115,6 +122,7 @@ onMounted(async () => {
lname: data.lname ?? '',
phone: data.mobile ?? '',
city_id: data.city_id ?? undefined,
birth_day: data.birth_day ?? '',
});
} catch (e) {
console.log(e);