ab4f29a474
上一版(355557b)算出 inArea 就上报,但没有判断「该校是否要求在范围内」,
等于把前端的一道拦截丢了。前端源码(LeaveDetailView-BveoXCrD.js):
const v = computed(() => a.actionType==="CANCELAPPLY" && a.cancellationApplyInArea)
if (v.value && !T?.canPosition) { warning("不在销假申请范围内,无法销假"); return }
该标记的来源也一并核实(同文件传入 StudentActionDialog 的 props):
"application-id": c.value?.id || s.data?.id
"project-id": c.value?.project?.id || c.value?.projectId
"cancellation-apply-in-area":
c.value?.cancellationApplyInArea === true
|| c.value?.cancellationApplyInArea === "是"
顺带确认了 originalId 与 projectId 的取值与我们现有实现一致
(ToLeaveItems 的 ID=r.ID、TypeWid=r.ProjectID)。
改动:
- XsgzLeaveRecord 增加 CancellationApplyInArea(用 interface{} 兼容
bool 与 "是" 两种返回),并加 RequiresInArea() 做等价归一化;
- 新增 recordRequiresInArea:后端自行重查记录判定,不复用前端传值
(可篡改、且可能未传);查不到时保守返回 false,不额外拦截;
- 销假流程改为:该校要求范围内且实际不在范围内 → 明确报错并拒绝提交,
与前端一致;未配置范围时 inArea 视为 true。
测试:RequiresInArea 覆盖 bool true/false、"是"/"否"、"true" 字符串、
nil、数字等输入。
157 lines
4.6 KiB
Go
157 lines
4.6 KiB
Go
package cpdaily
|
||
|
||
import "testing"
|
||
|
||
// 销假地理范围判断,对齐前端 LeaveCancelLocation 的 c():
|
||
// Circle/poi 比球面距离,Polygon/Rectangle 用点在多边形内。
|
||
|
||
type pt = struct {
|
||
Longitude float64 `json:"longitude"`
|
||
Latitude float64 `json:"latitude"`
|
||
}
|
||
|
||
// TestInLocationRangeEmpty 范围为空时不限制(前端:!r.value.length 时 return true)。
|
||
func TestInLocationRangeEmpty(t *testing.T) {
|
||
if !InLocationRange(nil, 117.12, 36.19) {
|
||
t.Fatal("范围为空应视为不限制")
|
||
}
|
||
if !InLocationRange([]XsgzLocationRange{}, 117.12, 36.19) {
|
||
t.Fatal("空切片应视为不限制")
|
||
}
|
||
}
|
||
|
||
// TestInLocationRangeCircle 圆形范围:半径内 true,半径外 false。
|
||
func TestInLocationRangeCircle(t *testing.T) {
|
||
// 山东农业大学附近,半径 500 米
|
||
center := XsgzLocationRange{
|
||
CompatibleType: "Circle",
|
||
Longitude: 117.12,
|
||
Latitude: 36.19,
|
||
Radius: 500,
|
||
}
|
||
// 圆心
|
||
if !InLocationRange([]XsgzLocationRange{center}, 117.12, 36.19) {
|
||
t.Fatal("圆心应在范围内")
|
||
}
|
||
// 约 111 米外(纬度 0.001 度 ≈ 111 米)
|
||
if !InLocationRange([]XsgzLocationRange{center}, 117.12, 36.191) {
|
||
t.Fatal("111 米外应在半径内")
|
||
}
|
||
// 约 1.1 公里外(0.01 度 ≈ 1111 米),应超出
|
||
if InLocationRange([]XsgzLocationRange{center}, 117.12, 36.20) {
|
||
t.Fatal("1.1 公里外应超出 500 米半径")
|
||
}
|
||
}
|
||
|
||
// TestInLocationRangePoi poi 类型按圆形处理。
|
||
func TestInLocationRangePoi(t *testing.T) {
|
||
r := XsgzLocationRange{
|
||
CompatibleType: "poi",
|
||
Longitude: 117.12,
|
||
Latitude: 36.19,
|
||
Radius: 200,
|
||
}
|
||
if !InLocationRange([]XsgzLocationRange{r}, 117.12, 36.19) {
|
||
t.Fatal("poi 中心应在范围内")
|
||
}
|
||
if InLocationRange([]XsgzLocationRange{r}, 117.12, 36.25) {
|
||
t.Fatal("远处应在范围外")
|
||
}
|
||
}
|
||
|
||
// TestInLocationRangePolygon 多边形:内部 true,外部 false。
|
||
func TestInLocationRangePolygon(t *testing.T) {
|
||
// 一个经纬度平面上的正方形,覆盖 117.10~117.14 × 36.17~36.21
|
||
poly := XsgzLocationRange{
|
||
CompatibleType: "Polygon",
|
||
Points: []pt{
|
||
{Longitude: 117.10, Latitude: 36.17},
|
||
{Longitude: 117.14, Latitude: 36.17},
|
||
{Longitude: 117.14, Latitude: 36.21},
|
||
{Longitude: 117.10, Latitude: 36.21},
|
||
},
|
||
}
|
||
ranges := []XsgzLocationRange{poly}
|
||
|
||
if !InLocationRange(ranges, 117.12, 36.19) {
|
||
t.Fatal("中心点应在多边形内")
|
||
}
|
||
if InLocationRange(ranges, 117.20, 36.19) {
|
||
t.Fatal("右侧外部点不应在多边形内")
|
||
}
|
||
if InLocationRange(ranges, 117.12, 36.30) {
|
||
t.Fatal("上方外部点不应在多边形内")
|
||
}
|
||
}
|
||
|
||
// TestInLocationRangeRectangle 矩形按多边形处理。
|
||
func TestInLocationRangeRectangle(t *testing.T) {
|
||
rect := XsgzLocationRange{
|
||
CompatibleType: "Rectangle",
|
||
Points: []pt{
|
||
{Longitude: 117.10, Latitude: 36.17},
|
||
{Longitude: 117.14, Latitude: 36.17},
|
||
{Longitude: 117.14, Latitude: 36.21},
|
||
{Longitude: 117.10, Latitude: 36.21},
|
||
},
|
||
}
|
||
if !InLocationRange([]XsgzLocationRange{rect}, 117.13, 36.18) {
|
||
t.Fatal("矩形内点应命中")
|
||
}
|
||
if InLocationRange([]XsgzLocationRange{rect}, 117.15, 36.18) {
|
||
t.Fatal("矩形外点不应命中")
|
||
}
|
||
}
|
||
|
||
// TestInLocationRangeMultiple 任一范围命中即可。
|
||
func TestInLocationRangeMultiple(t *testing.T) {
|
||
ranges := []XsgzLocationRange{
|
||
{CompatibleType: "Circle", Longitude: 117.12, Latitude: 36.19, Radius: 100},
|
||
{CompatibleType: "Circle", Longitude: 117.50, Latitude: 36.50, Radius: 100},
|
||
}
|
||
if !InLocationRange(ranges, 117.50, 36.50) {
|
||
t.Fatal("第二个圆应命中")
|
||
}
|
||
if InLocationRange(ranges, 118.00, 37.00) {
|
||
t.Fatal("都不应命中")
|
||
}
|
||
}
|
||
|
||
// TestPointInPolygonDegenerate 少于 3 个点时不构成多边形。
|
||
func TestPointInPolygonDegenerate(t *testing.T) {
|
||
if pointInPolygon(0, 0, nil) {
|
||
t.Fatal("空点集不应命中")
|
||
}
|
||
one := []pt{{Longitude: 0, Latitude: 0}}
|
||
if pointInPolygon(0, 0, one) {
|
||
t.Fatal("单点不应命中")
|
||
}
|
||
two := []pt{{Longitude: 0, Latitude: 0}, {Longitude: 1, Latitude: 1}}
|
||
if pointInPolygon(0.5, 0.5, two) {
|
||
t.Fatal("两点不应命中")
|
||
}
|
||
}
|
||
|
||
// TestXsgzLeaveRecordRequiresInArea 归一化前端 `=== true || === "是"` 的语义。
|
||
func TestXsgzLeaveRecordRequiresInArea(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
val interface{}
|
||
want bool
|
||
}{
|
||
{"布尔 true", true, true},
|
||
{"布尔 false", false, false},
|
||
{"字符串 是", "是", true},
|
||
{"字符串 否", "否", false},
|
||
{"字符串 true", "true", false},
|
||
{"nil", nil, false},
|
||
{"数字", float64(1), false},
|
||
}
|
||
for _, c := range cases {
|
||
rec := XsgzLeaveRecord{CancellationApplyInArea: c.val}
|
||
if got := rec.RequiresInArea(); got != c.want {
|
||
t.Fatalf("%s: RequiresInArea()=%v, want %v", c.name, got, c.want)
|
||
}
|
||
}
|
||
}
|