Selaa lähdekoodia

feat: (BI) 新增弱歸戶

osbert 1 vuosi sitten
vanhempi
commit
a26c8ef850

+ 8 - 0
TEAMModelBI/ClientApp/src/api/index.js

@@ -219,6 +219,14 @@ export default {
     getallSchool(data) {
         return post('/batchschool/get-allscinfo', data)
     },
+    //透過位置取得學校資料
+    getSchooBasicInfo(data) {
+        return post('/batchschool/get-school-basic-info', data)
+    },
+    //弱歸戶
+    updUserSchoolW(data) {
+        return post('/batchschool/upd-user-schoolw', data)
+    },    
 
     //首页dashboard数据接口
     //获取各城市的学校数量(bar)

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1 - 0
TEAMModelBI/ClientApp/src/static/country.json


+ 1 - 1
TEAMModelBI/ClientApp/src/view/common/aside.vue

@@ -137,7 +137,7 @@ export default {
             sort: 7,
           },
           {
-            name: '用户查询',
+            name: '用户相關',
             router: '/home/userinquire',
             icon: '#icon-a-97-yonghuchaxun',
             permission: [],

+ 27 - 5
TEAMModelBI/ClientApp/src/view/issueCoupons/crteadCoupon.vue

@@ -1,5 +1,6 @@
 <template>
     <div style="position: relative;">
+        <!-- 隱藏按鈕 -->
         <div style="width: 100px; height:100px;position: absolute;right: 0;z-index: 10;" @click="showClick()"></div>
         <el-form ref="ruleFormRef" :model="crtCouponForm" :rules="rules" label-width="120px">
             <el-form-item label="發券位置" prop="srvAdr">
@@ -45,11 +46,11 @@
             </el-form-item>
             <el-form-item v-if="detailSettingFlag" label="規則" prop="rule">
                 <div style="max-height: 330px;overflow: scroll;">
-                    <div v-for="(rule, rIndex) in crtCouponForm.rule" style="display: flex;flex-direction: row;align-items: center;margin-bottom: 5px;">
+                    <div v-for="(rule, rIndex) in crtCouponForm.rule" class="ruleBox">
                         <el-button :icon="Plus" circle @click="addRule(rIndex)" style="margin-right: 5px;"/>
-                        <div style="text-align: left;border: 1px solid #dcdfe6;padding: 6px 11px;border-radius: 5px;margin-right: 5px;">
+                        <div class="rule">
                             條件:
-                            <div style="min-height: 36px;max-width: 550px;border: 1px solid #e4e7ed;padding: 1px 11px;border-radius: 5px;background-color: #f5f7fa;">{{ translateRule(rule.q) }}</div>
+                            <div class="ruleStr">{{ translateRule(rule.q) }}</div>
                             <div v-for="(item, index) in rule.q" style="text-align:left;">
                                 <el-button-group style="margin-right: 5px;">
                                     <el-button :icon="Plus" @click="addCondition(rIndex, index)" />
@@ -170,7 +171,6 @@ const maxTakerFlag = ref(false)
 const consolidationFlag = ref(false)
 const loading = ref(false)
 const couponResult = ref('')
-const defaultTime = new Date().setTime(new Date().getTime() + (60*60*1000))
 const shortcuts = [
     {
         text: '一個月後',
@@ -715,7 +715,6 @@ const submitForm = formEl => {
             }
             
             await proxy.$api.crtCoupon(data).then((res) => {
-                console.log(res)
                 couponResult.value = '' // 先清空
                 if(res.coupons){
                     res.coupons.forEach(e=>{
@@ -832,6 +831,29 @@ const copyDocument = (data) => {
 </script>
 
 <style scoped>
+.ruleBox{
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    margin-bottom: 5px;
+}
+
+.ruleBox .rule{
+    text-align: left;
+    border: 1px solid #dcdfe6;
+    padding: 6px 11px;
+    border-radius: 5px;
+    margin-right: 5px;
+}
+
+.ruleBox .rule .ruleStr{
+    min-height: 36px;
+    max-width: 550px;
+    border: 1px solid #e4e7ed;
+    padding: 1px 11px;
+    border-radius: 5px;
+    background-color: #f5f7fa;
+}
 .coupons {
     box-shadow: 0px 3px 2px 2px #E0E0E0;
     border-radius: 10px;

+ 5 - 7
TEAMModelBI/ClientApp/src/view/issueCoupons/index.vue

@@ -1,11 +1,9 @@
 <template>
-    <div >
-      <el-tabs type="border-card">
-        <el-tab-pane label="建立優惠券"><CrteadCoupon /></el-tab-pane>
-      <el-tab-pane label="優惠券歸戶"><ConsolidationCoupon /></el-tab-pane>
-      <el-tab-pane label="通知"><Notice /></el-tab-pane>
-    </el-tabs>
-  </div>
+  <el-tabs type="border-card">
+      <el-tab-pane label="建立優惠券"><CrteadCoupon /></el-tab-pane>
+    <el-tab-pane label="優惠券歸戶"><ConsolidationCoupon /></el-tab-pane>
+    <el-tab-pane label="通知"><Notice /></el-tab-pane>
+  </el-tabs>
 </template>
 <script>
 import CrteadCoupon from './crteadCoupon.vue'

+ 57 - 36
TEAMModelBI/ClientApp/src/view/userInquire/index.vue

@@ -1,51 +1,59 @@
 <template>
-    <div class="inquirebox" v-if="pageShow ==='default'">
+    <div class="inquirebox" >
         <div class="inquire-title">
             <p>TEAM Model 智慧教育</p>
         </div>
-        <div class="searchbox" v-loading="searchLoading" element-loading-text="数据搜索中...">
-            <div class="searchbox-title">
-                <p>用户查询</p>
-            </div>
-            <el-divider border-style="dashed" />
-            <div class="searchbox-item">
-                <el-input v-model="searchvalue" placeholder="输入手机号码/醍摩豆账号 进行搜索" class="input-with-select">
-                <template #prepend>
-                    <el-select v-model="selecttypes" placeholder="Select" style="width: 120px">
-                    <el-option label="精准查询" value="precise" />
-                    <!-- <el-option label="批量查询/操作" value="batch" /> -->
-                    </el-select>
-                </template>
-                <template #append>
-                    <el-button :icon="Search" @click="seachSole()"/>
-                </template>
-                </el-input>
-            </div>
-            <div class="recordbox" v-if="selecttypes==='precise'">
-                <p>搜索记录:</p>
-                <div class="recordbox-item">
-                    <el-tag v-for="tag in searchRecordsArr"  class="mx-1" closable type="''"  effect="light" @close="deleteLog(tag)" @click="searchvalue=tag">
-                        {{ tag }}
-                    </el-tag>
+        <el-tabs v-if="pageShow ==='default'" style="width: 100%;height:100%;" type="card" class="demo-tabs">
+            <el-tab-pane label="用户查询" style="padding: 1%;display: flex;justify-content: center;">
+                <div class="searchbox" v-loading="searchLoading" element-loading-text="数据搜索中...">
+                    <div class="searchbox-title">
+                        <p>用户查询</p>
+                    </div>
+                    <el-divider border-style="dashed" />
+                    <div class="searchbox-item">
+                        <el-input v-model="searchvalue" placeholder="输入手机号码/醍摩豆账号 进行搜索" class="input-with-select">
+                        <template #prepend>
+                            <el-select v-model="selecttypes" placeholder="Select" style="width: 120px">
+                            <el-option label="精准查询" value="precise" />
+                            <!-- <el-option label="批量查询/操作" value="batch" /> -->
+                            </el-select>
+                        </template>
+                        <template #append>
+                            <el-button :icon="Search" @click="seachSole()"/>
+                        </template>
+                        </el-input>
+                    </div>
+                    <div class="recordbox" v-if="selecttypes==='precise'">
+                        <p>搜索记录:</p>
+                        <div class="recordbox-item">
+                            <el-tag v-for="tag in searchRecordsArr"  class="mx-1" closable type="''"  effect="light" @close="deleteLog(tag)" @click="searchvalue=tag">
+                                {{ tag }}
+                            </el-tag>
+                        </div>
+                        <div class="dele-all" v-show="searchRecordsArr.length >0">
+                            <el-icon size="14" @click="deleAllsearch"><Delete /></el-icon>
+                        </div>
+                    </div>
+                    <div class="recordbox" v-else="selecttypes ==='batch'">
+                        <p>搜索结果:</p>
+                    </div>
                 </div>
-                <div class="dele-all" v-show="searchRecordsArr.length >0">
-                    <el-icon size="14" @click="deleAllsearch"><Delete /></el-icon>
-                </div>
-            </div>
-            <div class="recordbox" v-else="selecttypes ==='batch'">
-                <p>搜索结果:</p>
-            </div>
+            </el-tab-pane>
+            <el-tab-pane label="弱歸戶" style="padding: 1%">
+                <UpdCodeW />
+            </el-tab-pane>
+        </el-tabs>
+        <div class="inquirebox-details" v-else-if="pageShow ==='details'">
+            <Detailsbox :searchdata="searchResult" @parentClick="backClicks"></Detailsbox>
         </div>
     </div>
-    <div class="inquirebox-details" v-else-if="pageShow ==='details'">
-        <Detailsbox :searchdata="searchResult" @parentClick="backClicks"></Detailsbox>
-    </div>
 </template>
 <script setup>
 import { ref, getCurrentInstance, watch, h, nextTick,provide } from 'vue'
 import { ElMessage, ElLoading } from 'element-plus'
 import { Search,Delete } from '@element-plus/icons'
 import Detailsbox from './details.vue'
+import UpdCodeW from './updCodeW.vue'
 import {useRoute} from "vue-router"
 import { useStore } from 'vuex'
 let { proxy } = getCurrentInstance()
@@ -170,9 +178,10 @@ function deleAllsearch(){
     align-items: center;
     justify-content: center;
     position: relative;
+    /* height: 86vh; */
 }
 .searchbox{
-    width:60%;
+    width: 40vw;
     height:50vh;
     background-color: #fff;
     z-index:999;
@@ -238,4 +247,16 @@ function deleAllsearch(){
     top:1%;
     right:5%;
 }
+</style>
+<style>
+.demo-tabs .el-tabs__item.is-active{
+    color: #333333;
+    font-weight: bold;
+    font-size: 17px;
+}
+.demo-tabs .el-tabs__item.is-active{
+    border-bottom-color: #fff !important;
+    border-bottom-width: 3px;
+    background-color: #ffffffad;
+}
 </style>

+ 304 - 0
TEAMModelBI/ClientApp/src/view/userInquire/updCodeW.vue

@@ -0,0 +1,304 @@
+<template>
+    <div class="updCodeWBox" v-loading="loading" :element-loading-text="loadingStr">
+        <!-- <div class="updCodeWBox-title">
+            <p>弱歸戶</p>
+        </div>
+        <el-divider border-style="dashed" /> -->
+        <div style="line-height: 1px;height: 100%;overflow: auto;">
+            <div style="margin-bottom: 10px;">
+                <h3>區域</h3>
+                <el-radio-group class="radioSty" v-model="countryId" @change="cIdChange">
+                    <el-radio label="CN" size="large"  border>大陆地区</el-radio>
+                    <el-radio label="TW" size="large" border>臺灣地區</el-radio>
+                </el-radio-group>
+            </div>
+            
+            <div style="margin-bottom: 10px;" v-if="provinceIdData != null">
+                <h3>省/直轄市</h3>
+                <el-radio-group class="radioSty" v-model="provinceId" @change="pIdChange">
+                    <el-radio size="large" :label="p.provinceId"  border v-for="p in provinceIdData">{{ p.provinceName }}</el-radio>
+                </el-radio-group>
+            </div>
+
+            <div style="margin-bottom: 10px;" v-if="cityIdData != null">
+                <h3>市/界</h3>
+                <el-radio-group class="radioSty" v-model="cityId">
+                    <el-radio size="large" :label="c.cityId"  border v-for="c in cityIdData" @click="searchSchool(c.cityId)">{{ c.cityName }}</el-radio>
+                </el-radio-group>
+            </div>
+
+            <div style="margin-bottom: 10px;margin-top: 70px;">
+                <div style="display: flex;justify-content: center;align-items: center;">
+                    <span style="font-weight: bold;font-size: 23px;margin-right: 5px;">搜尋</span>
+                    <el-input v-model="wordFilter" placeholder="請填入簡碼或學校名稱" style="width: 500px;"/>
+                </div>
+                <el-table :data="showData" stripe style="width: 100%" >
+                    <el-table-column prop="code" label="代碼"  />
+                    <el-table-column prop="shortCode" label="簡碼"  />
+                    <el-table-column prop="name" label="學校名稱"  />
+                    <el-table-column label="">
+                        <template #default="scope">
+                            <el-button type="primary" size="small" @click="handleUpdSchoolW(scope.row.id, scope.row.name, scope.row.shortCode)">歸戶</el-button>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </div>
+        </div>
+    </div>
+</template>
+<script setup>
+import country from '@/static/country.json'
+import { h, reactive, ref, computed, getCurrentInstance, markRaw } from 'vue'
+import { SuccessFilled, Warning } from '@element-plus/icons'
+import { ElMessageBox, ElMessage, ElInput, ElForm, ElFormItem } from 'element-plus'
+let { proxy } = getCurrentInstance()
+
+const loading = ref(false)
+const countryId = ref(null)
+const provinceId = ref(null)
+const provinceIdData = ref(null)
+const cityId = ref(null)
+const cityIdData = ref(null)
+const tableData = ref([])
+const wordFilter = ref('')
+const loadingStr = ref('')
+
+const cIdChange = (cId) => {
+    cityId.value = null
+    provinceId.value = null
+    provinceIdData.value = null
+    cityIdData.value = null
+    switch(cId){
+        case 'TW':
+            cityIdData.value = country.filter(function(c){
+                return c.countryId == cId && c.lang == 'zh-tw' && c.areaType == 'y'
+            })
+        break;
+        case 'CN':
+            provinceIdData.value = country.filter(function(c){
+                return c.countryId == cId && c.lang == 'zh-cn' && c.areaType == 'p'
+            })
+        break;
+    }
+}
+
+const pIdChange = (provinceId) =>{
+    cityId.value = null
+    cityIdData.value = null
+    let lang = ''
+    switch(countryId.value){
+        case 'TW':
+            lang = 'zh-tw'
+        break;
+        case 'CN':
+            lang = 'zh-cn'
+        break;
+    }
+
+    cityIdData.value = country.filter(function(c){
+        return c.provinceId == provinceId && c.lang == lang && c.areaType == 'y'
+    })
+}
+
+const searchSchool = async (cityId) =>{
+    let data = {
+        countryId: countryId.value,
+        cityId: cityId,
+        fullData: true
+    }
+
+    if(provinceId.value != null) {
+        data.provinceId = provinceId.value
+    }
+    wordFilter.value = ''
+    loadingStr.value = '搜索學校中...'
+    loading.value = true
+
+    await proxy.$api.getSchooBasicInfo(data).then((res) => {
+        tableData.value = res
+    }).catch(e=>{
+        ElMessage.error('搜尋失敗')
+    }).finally(() => {
+        loading.value = false
+    })
+}
+
+const showData = computed({
+    get(){
+        if(wordFilter.value != ''){
+            let result = []
+            console.log(wordFilter.value, 'wordFilter.value')
+            tableData.value.forEach((f)=>{
+                if(f.name.indexOf(wordFilter.value) >= 0){
+                    result.push(f)
+                } else if(f.shortCode && f.shortCode.indexOf(wordFilter.value) >= 0){
+                    result.push(f)
+                }
+            })
+            return result
+        } else {
+            return tableData.value
+        }
+    }
+})
+
+const handleUpdSchoolW = async (schDocId, schName, shortCode) =>{
+    const iDsCount = ref(0)
+    // 檢查array 內容是否重複
+    function isArrayRep(array){
+        var result = new Set();
+        var repeat = new Set();
+        array.forEach(item => {
+            result.has(item) ? repeat.add(item) : result.add(item);
+        })
+        // console.log(result); // {1, 2, "a", 3, "b"}
+        // console.log(repeat); // {1, "a"}
+        // console.log(repeat.size)
+        if(repeat.size != 0){
+            return true
+        } else {
+            return false
+        }
+    }
+    // 規則檢查: iDs
+    const checkIds = (value) => {
+        let isErr = false
+        if(!value) {
+            isErr = true
+            return "請填入要歸戶的醍摩豆ID"
+        } else if(value && !/^[0-9\n]+$/.test(value)){
+            isErr = true
+            return "只能輸入醍摩豆ID與換行"
+        } else if(value && isArrayRep(value.split('\n'))){
+            isErr = true
+            return "醍摩豆ID有重複"
+        } else if(value){
+            let errIds = []
+            let tmp = value.split('\n')
+            tmp.forEach(e=> {
+                if(e.length != 10) {
+                    errIds.push(e)
+                } else {
+                    let now = Math.floor(new Date().getTime() / 1000)
+                    let orgId = parseInt(e)
+                    if(parseInt(e.substring(0, 1)) >= 6){
+                        orgId -= 5000000000
+                    }
+
+                    if(orgId > now){
+                        errIds.push(e)
+                    }
+                }
+            })
+
+            if(errIds.length > 0){
+                // console.log(errIds, 'errIds')
+                isErr = true
+                return "請檢查醍摩豆ID是否符合格式或有空格"
+            }
+        }
+
+        if(!isErr && value != ''){
+            let tArray = value.split('\n')
+            iDsCount.value = tArray.length
+        } else {
+            iDsCount.value = 0
+        }
+
+        return true
+    }
+
+    if(!shortCode){
+        ElMessageBox.alert('此學校目前沒有簡碼', `***${schName}***`,
+            {
+                type: 'warning',
+                icon: markRaw(Warning),
+            }
+        )
+        // 中斷
+        return 
+    }
+
+    ElMessageBox.prompt(
+        '請填入要歸戶的醍摩豆ID', `歸戶至 ${schName} - ${shortCode}`, {
+            confirmButtonText: '歸戶',
+            cancelButtonText: '取消',
+            inputType: 'textarea',
+            inputValidator: checkIds,
+            customClass: 'prompClass',
+            roundButton: true,
+            closeOnClickModal: false
+        }
+    ).then(({value})=>{
+        let showStr = value.replaceAll('\n', ', ')
+        ElMessageBox.confirm(
+            `確認要歸戶以下的醍摩豆ID嗎?\n${showStr} \n\n 總共 ${iDsCount.value} 位`,
+            `歸戶至 ${schName}  - ${shortCode}`,
+            {
+                confirmButtonText: '確認',
+                cancelButtonText: '先不要',
+                type: 'info',
+                customClass: 'confirmClass'
+            }
+        ).then(async ()=>{
+            let reqData = {
+                schoolDocId: schDocId,
+                ids: value.split('\n')
+            }
+            
+            loadingStr.value = '弱歸戶...'
+            loading.value = true
+
+            await proxy.$api.updUserSchoolW(reqData).then((res) => {
+                ElMessageBox.alert('成功', '弱歸戶成功',
+                    {
+                        type: 'info',
+                        icon: markRaw(SuccessFilled),
+                    }
+                )
+            }).catch(e=>{
+                ElMessage.error('搜尋失敗')
+            }).finally(() => {
+                loading.value = false
+            })
+        })
+    })
+}
+</script>
+<style>
+.prompClass textarea{
+    min-height: 220px!important;
+}
+.confirmClass p{
+    white-space: pre-line;
+}
+.radioSty .el-radio__input{
+    display: none;
+}
+.radioSty .el-radio__label{
+    font-weight: bold;
+    font-size: 15px!important;
+}
+.radioSty .el-radio{
+    margin-right: 10px;
+}
+</style>
+<style scoped>
+.updCodeWBox{
+    width: 100%;
+    height: 82vh;
+    background-color: #fff;
+    z-index:999;
+    box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.3);
+    border-radius: 10px;
+    padding: 1%;
+    text-align: left;
+}
+.updCodeWBox-title{
+    line-height: 40px;
+    font-size:24px;
+    font-weight: bold;
+    color:#7b84a9;
+    text-align: center;
+}
+</style>

+ 124 - 0
TEAMModelBI/Controllers/BISchool/BatchSchoolController.cs

@@ -39,6 +39,9 @@ using HTEXLib;
 using TEAMModelOS.SDK.Models.Service.BI;
 using TEAMModelOS.SDK.Models.Cosmos.BI.BISchool;
 using DocumentFormat.OpenXml.Vml.Office;
+using System.Net.Http.Headers;
+using System.Text.Encodings.Web;
+using System.ComponentModel.DataAnnotations;
 
 namespace TEAMModelBI.Controllers.BISchool
 {
@@ -1779,6 +1782,112 @@ namespace TEAMModelBI.Controllers.BISchool
             return Ok(new { state = RespondCode.Ok, error });
         }
 
+        /// <summary>
+        /// 根據地理位置搜尋學校基本訊息
+        /// </summary>
+        /// <param name="GenerateCouponRequest"></param>
+        /// <returns></returns>
+        //[AuthToken(Roles = "admin,rdc,assist,sales")]
+        [HttpPost("get-school-basic-info")]
+        public async Task<IActionResult> GetSchoolBasicInfo(SchoolDataRequest request)
+        {
+            try
+            {
+                string url = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
+                string AccessToken = await getCoreAccessToken();
+                var client = _http.CreateClient();
+                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
+                HttpResponseMessage response = await client.PostAsJsonAsync($"{url}/Service/SchoolData", request);
+                if (response.StatusCode == HttpStatusCode.OK)
+                {
+                    string jsonStr = await response.Content.ReadAsStringAsync();
+                    var options1 = new JsonSerializerOptions
+                    {
+                        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                    };
+
+                    
+                    return Ok(jsonStr.ToObject<JsonElement>());
+                }
+                else
+                {
+                    return BadRequest();
+                }
+            }
+            catch (Exception ex)
+            {
+                return BadRequest();
+            }
+        }
+
+        /// <summary>
+        /// 弱歸戶
+        /// </summary>
+        /// <param name="GenerateCouponRequest"></param>
+        /// <returns></returns>
+        //[AuthToken(Roles = "admin,rdc,assist,sales")]
+        [HttpPost("upd-user-schoolw")]
+        public async Task<IActionResult> UpdUserSchoolW(UpdateUserSchoolCodeWRequest request)
+        {
+            try
+            {
+                string url = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
+                string AccessToken = await getCoreAccessToken();
+                var client = _http.CreateClient();
+                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
+                HttpResponseMessage response = await client.PostAsJsonAsync($"{url}/Service/UpdateUserSchoolCodeW", request);
+                if (response.StatusCode == HttpStatusCode.OK)
+                {
+                    string jsonStr = await response.Content.ReadAsStringAsync();
+                    var options1 = new JsonSerializerOptions
+                    {
+                        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                    };
+
+
+                    return Ok(jsonStr.ToObject<JsonElement>());
+                }
+                else
+                {
+                    return BadRequest();
+                }
+            }
+            catch (Exception ex)
+            {
+                return BadRequest();
+            }
+        }
+
+        private async Task<string> getCoreAccessToken()
+        {
+            string AccessToken = "";
+            try
+            {
+                string Url = _configuration.GetValue<string>("HaBookAuth:CoreAPI") + "/oauth2/token";
+                string GrantType = "device";
+                string ClientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
+                string Secret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
+                var content = new { grant_type = GrantType, client_id = ClientID, client_secret = Secret };
+                var response = await _http.CreateClient().PostAsJsonAsync($"{Url}", content);
+                if (response.IsSuccessStatusCode)
+                {
+                    string responseBody = response.Content.ReadAsStringAsync().Result;
+                    using (JsonDocument document = JsonDocument.Parse(responseBody.ToString()))
+                    {
+                        if (document.RootElement.TryGetProperty("access_token", out JsonElement AccessTokenObj))
+                        {
+                            AccessToken = AccessTokenObj.ToString();
+                        }
+                    }
+                }
+                return AccessToken;
+            }
+            catch (Exception ex)
+            {
+                return AccessToken;
+            }
+        }
+
         #region   预设学校基础信息 多语言
 
         /// <summary>
@@ -2017,5 +2126,20 @@ namespace TEAMModelBI.Controllers.BISchool
             public string name { get; set; }
             public List<string> mobiles { get; set; }
         }
+
+        public record SchoolDataRequest
+        (
+            string countryId,
+            string provinceId,
+            string cityId,
+            string shortCode,
+            bool fullData = false
+        );
+
+        public record UpdateUserSchoolCodeWRequest
+        (
+            [Required] List<string> ids,
+            [Required] string schoolDocId
+        );
     }
 }