You've already forked crud_generator
mirror of
https://github.com/ManyakRus/crud_generator.git
synced 2025-07-14 14:44:48 +02:00
сделал NEED_CREATE_UPDATE_EVERY_COLUMN
This commit is contained in:
@ -9,6 +9,8 @@ package grpc;
|
|||||||
|
|
||||||
option go_package = "./grpc_proto";
|
option go_package = "./grpc_proto";
|
||||||
|
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
// sync_service - сервис обмена с Базой данных
|
// sync_service - сервис обмена с Базой данных
|
||||||
service Sync_service {
|
service Sync_service {
|
||||||
}
|
}
|
||||||
@ -76,3 +78,27 @@ message ResponseFloat64 {
|
|||||||
message ResponseEmpty {
|
message ResponseEmpty {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequestDate - параметры запроса на сервер
|
||||||
|
message RequestDate {
|
||||||
|
uint32 VersionModel= 1; //версия структуры модели
|
||||||
|
google.protobuf.Timestamp Date = 2; // строка поиска
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestFloat64 - параметры запроса на сервер, передаётся float64
|
||||||
|
message RequestFloat64 {
|
||||||
|
uint32 VersionModel= 1; //версия структуры модели
|
||||||
|
double Float64 = 2; // строка поиска
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestFloat32 - параметры запроса на сервер, передаётся float32
|
||||||
|
message RequestFloat32 {
|
||||||
|
uint32 VersionModel= 1; //версия структуры модели
|
||||||
|
float Float32 = 2; // строка поиска
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestInt32 - параметры запроса на сервер, передаётся int32
|
||||||
|
message RequestInt32 {
|
||||||
|
uint32 VersionModel= 1; //версия структуры модели
|
||||||
|
int32 Int32 = 2; // строка поиска
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -168,3 +168,6 @@ TEMPLATES_CRUD_FILENAME="crud_tables_rapira.go_"
|
|||||||
|
|
||||||
#TEMPLATES_CRUD_TEST_FILENAME - short filename of "crud_tables_test.go_" file
|
#TEMPLATES_CRUD_TEST_FILENAME - short filename of "crud_tables_test.go_" file
|
||||||
TEMPLATES_CRUD_TEST_FILENAME="crud_tables_rapira_test.go_"
|
TEMPLATES_CRUD_TEST_FILENAME="crud_tables_rapira_test.go_"
|
||||||
|
|
||||||
|
#NEED_CREATE_UPDATE_EVERY_COLUMN - fill true if you want create Update_ColumnName() function for every column
|
||||||
|
NEED_CREATE_UPDATE_EVERY_COLUMN=true
|
@ -0,0 +1,6 @@
|
|||||||
|
package lawsuit_status_types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitlab.aescorp.ru/dsp_dev/claim/sync_service/pkg/db/constants"
|
||||||
|
)
|
||||||
|
|
@ -68,6 +68,7 @@ type SettingsINI struct {
|
|||||||
TEXT_MODULE_GENERATED string
|
TEXT_MODULE_GENERATED string
|
||||||
PREFIX_TABLE string
|
PREFIX_TABLE string
|
||||||
READY_ALIAS_FILENAME string
|
READY_ALIAS_FILENAME string
|
||||||
|
NEED_CREATE_UPDATE_EVERY_COLUMN bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// FillSettings загружает переменные окружения в структуру из переменных окружения
|
// FillSettings загружает переменные окружения в структуру из переменных окружения
|
||||||
@ -177,6 +178,7 @@ func FillSettings() {
|
|||||||
Settings.TEMPLATES_CRUD_TEST_FILENAME = os.Getenv("TEMPLATES_CRUD_TEST_FILENAME")
|
Settings.TEMPLATES_CRUD_TEST_FILENAME = os.Getenv("TEMPLATES_CRUD_TEST_FILENAME")
|
||||||
Settings.TEMPLATES_ALIAS_FILENAME = os.Getenv("TEMPLATES_ALIAS_FILENAME")
|
Settings.TEMPLATES_ALIAS_FILENAME = os.Getenv("TEMPLATES_ALIAS_FILENAME")
|
||||||
Settings.READY_ALIAS_FILENAME = os.Getenv("READY_ALIAS_FILENAME")
|
Settings.READY_ALIAS_FILENAME = os.Getenv("READY_ALIAS_FILENAME")
|
||||||
|
Settings.NEED_CREATE_UPDATE_EVERY_COLUMN = BoolFromString(os.Getenv("NEED_CREATE_UPDATE_EVERY_COLUMN"))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,3 +46,4 @@ const STARTER_TABLES_PREFIX = "crud_starter_"
|
|||||||
const CRUD_TABLES_FREFIX = "crud_"
|
const CRUD_TABLES_FREFIX = "crud_"
|
||||||
|
|
||||||
const MODEL_TABLE_MANUAL_FILENAME = "model_table_manual.go_"
|
const MODEL_TABLE_MANUAL_FILENAME = "model_table_manual.go_"
|
||||||
|
const MODEL_TABLE_UPDATE_FILENAME = "model_table_update.go_"
|
||||||
|
@ -842,3 +842,50 @@ func DeleteLastUnderline(s string) string {
|
|||||||
|
|
||||||
return Otvet
|
return Otvet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindLastGoodPos - возвращает позицию последнего нахождения, с новой строки
|
||||||
|
func FindLastGoodPos(s, TextFind string) int {
|
||||||
|
Otvet := -1
|
||||||
|
pos1 := strings.LastIndex(s, TextFind)
|
||||||
|
if pos1 < 0 {
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
pos2 := strings.Index(s[pos1:], "\n")
|
||||||
|
if pos2 < 0 {
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
Otvet = pos1 + pos2 + 1
|
||||||
|
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddInterfaceFunction - добавляет функцию в интерфейс
|
||||||
|
func AddInterfaceFunction(s, TextAdd string) string {
|
||||||
|
Otvet := s
|
||||||
|
|
||||||
|
//Проверим такая функция уже есть
|
||||||
|
pos1 := strings.Index(Otvet, TextAdd)
|
||||||
|
if pos1 >= 0 {
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
|
||||||
|
//найдём начало интефейса
|
||||||
|
sFind := " interface {"
|
||||||
|
pos1 = FindLastGoodPos(Otvet, sFind)
|
||||||
|
if pos1 < 0 {
|
||||||
|
log.Error("FindLastGoodPos() error: not found: ", sFind)
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
|
||||||
|
s2 := Otvet[pos1:]
|
||||||
|
pos2 := strings.Index(s2, "\n}")
|
||||||
|
if pos2 < 0 {
|
||||||
|
log.Error("FindLastGoodPos() error: not found: \\n")
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
PosStart := pos1 + pos2
|
||||||
|
|
||||||
|
Otvet = Otvet[:PosStart] + TextAdd + Otvet[PosStart:]
|
||||||
|
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
@ -306,3 +306,12 @@ func Is_Column_ExtLinksStruct(Column1 *types.Column) bool {
|
|||||||
|
|
||||||
return Otvet
|
return Otvet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Is_Common_Сolumn - возвращает true если это общая колонка: table_name_id, table_row_idб is_group, parent_id, name или description, ext_id, created_at, modified_at, deleted_at, id
|
||||||
|
func Is_Common_Сolumn(Column1 *types.Column) bool {
|
||||||
|
Otvet := false
|
||||||
|
|
||||||
|
Otvet = Is_Column_CommonStruct(Column1) || Is_Column_NameStruct(Column1) || Is_Column_GroupsStruct(Column1) || Is_Column_ExtLinksStruct(Column1)
|
||||||
|
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
@ -25,6 +25,14 @@ func CreateAllFiles(MapAll map[string]*types.Table) error {
|
|||||||
log.Error("CreateFiles() table: ", table1.Name, " error: ", err)
|
log.Error("CreateFiles() table: ", table1.Name, " error: ", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Settings.NEED_CREATE_UPDATE_EVERY_COLUMN == true {
|
||||||
|
err = CreateFilesUpdateEveryColumn(table1)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("CreateFiles() table: ", table1.Name, " error: ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@ -172,6 +180,11 @@ func CreateFilesModel_crud(Table1 *types.Table, DirTemplatesModel, DirReadyModel
|
|||||||
TextModel = DeleteFromInterfaceFind_ByExtID(TextModel, Table1)
|
TextModel = DeleteFromInterfaceFind_ByExtID(TextModel, Table1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
if config.Settings.NEED_CREATE_UPDATE_EVERY_COLUMN == true {
|
||||||
|
TextModel = AddInterfaceUpdateEveryColumn(TextModel, Table1)
|
||||||
|
}
|
||||||
|
|
||||||
//создание текста
|
//создание текста
|
||||||
TextModel = strings.ReplaceAll(TextModel, config.Settings.TEXT_TEMPLATE_MODEL, ModelName)
|
TextModel = strings.ReplaceAll(TextModel, config.Settings.TEXT_TEMPLATE_MODEL, ModelName)
|
||||||
TextModel = strings.ReplaceAll(TextModel, config.Settings.TEXT_TEMPLATE_TABLENAME, Table1.Name)
|
TextModel = strings.ReplaceAll(TextModel, config.Settings.TEXT_TEMPLATE_TABLENAME, Table1.Name)
|
||||||
@ -507,3 +520,143 @@ func CreateFilesModel_manual(Table1 *types.Table, DirTemplatesModel, DirReadyMod
|
|||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateFilesUpdateEveryColumn - создаёт 1 файл в папке model, для каждой колонки функция Update()
|
||||||
|
func CreateFilesUpdateEveryColumn(Table1 *types.Table) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
TableName := strings.ToLower(Table1.Name)
|
||||||
|
//ModelName := Table1.NameGo
|
||||||
|
|
||||||
|
//чтение файлов
|
||||||
|
DirBin := micro.ProgramDir_bin()
|
||||||
|
DirTemplates := DirBin + config.Settings.TEMPLATE_FOLDERNAME + micro.SeparatorFile()
|
||||||
|
DirReady := DirBin + config.Settings.READY_FOLDERNAME + micro.SeparatorFile()
|
||||||
|
DirTemplatesModel := DirTemplates + config.Settings.TEMPLATE_FOLDERNAME_MODEL + micro.SeparatorFile()
|
||||||
|
DirReadyModel := DirReady + config.Settings.TEMPLATE_FOLDERNAME_MODEL + micro.SeparatorFile() + TableName + micro.SeparatorFile()
|
||||||
|
|
||||||
|
//создадим папку готовых файлов
|
||||||
|
folders.CreateFolder(DirReadyModel)
|
||||||
|
|
||||||
|
FilenameTemplateModel := DirTemplatesModel + constants.MODEL_TABLE_UPDATE_FILENAME
|
||||||
|
FilenameReadyModel := DirReadyModel + config.Settings.PREFIX_MODEL + TableName + "_update.go"
|
||||||
|
|
||||||
|
//создадим папку готовых файлов
|
||||||
|
folders.CreateFolder(DirReadyModel)
|
||||||
|
|
||||||
|
//чтение файла шаблона
|
||||||
|
bytes, err := os.ReadFile(FilenameTemplateModel)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("ReadFile() ", FilenameTemplateModel, " error: ", err)
|
||||||
|
}
|
||||||
|
TextModel := string(bytes)
|
||||||
|
|
||||||
|
//заменим имя пакета на новое
|
||||||
|
TextModel = create_files.ReplacePackageName(TextModel, DirReadyModel)
|
||||||
|
|
||||||
|
//замена импортов на новые URL
|
||||||
|
if config.Settings.USE_DEFAULT_TEMPLATE == true {
|
||||||
|
TextModel = create_files.DeleteTemplateRepositoryImports(TextModel)
|
||||||
|
|
||||||
|
ConstantsURL := create_files.FindDBConstantsURL()
|
||||||
|
TextModel = create_files.AddImport(TextModel, ConstantsURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
TextModel = create_files.CheckAndAddImportTime_FromText(TextModel)
|
||||||
|
|
||||||
|
//удаление пустого импорта
|
||||||
|
TextModel = create_files.DeleteEmptyImport(TextModel)
|
||||||
|
|
||||||
|
//сортировка по названию таблиц
|
||||||
|
keys := make([]string, 0, len(Table1.MapColumns))
|
||||||
|
for k := range Table1.MapColumns {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
//найдём новый текст для каждой таблицы
|
||||||
|
TextNew := ""
|
||||||
|
for _, key1 := range keys {
|
||||||
|
Column1, ok := Table1.MapColumns[key1]
|
||||||
|
if ok == false {
|
||||||
|
log.Panic("CreateFilesUpdateEveryColumn() Table1.MapColumns[key1] = false")
|
||||||
|
}
|
||||||
|
if create_files.Is_Common_Сolumn(Column1) == true {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
TextNew1 := FindTextUpdateEveryColumn(Table1, Column1)
|
||||||
|
TextNew = TextNew + TextNew1
|
||||||
|
}
|
||||||
|
|
||||||
|
TextModel = TextModel + TextNew
|
||||||
|
|
||||||
|
//запись файла
|
||||||
|
err = os.WriteFile(FilenameReadyModel, []byte(TextModel), constants.FILE_PERMISSIONS)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindTextUpdateEveryColumn(Table1 *types.Table, Column1 *types.Column) string {
|
||||||
|
Otvet := ""
|
||||||
|
|
||||||
|
ModelName := Table1.NameGo
|
||||||
|
ColumnName := Column1.NameGo
|
||||||
|
|
||||||
|
Otvet = `
|
||||||
|
// Update_` + ColumnName + ` - изменяет объект в БД по ID, присваивает ` + ColumnName + `
|
||||||
|
func (m *` + ModelName + `) Update_` + ColumnName + `() error {
|
||||||
|
if Crud_` + ModelName + ` == nil {
|
||||||
|
return constants.ErrorCrudIsNotInit
|
||||||
|
}
|
||||||
|
|
||||||
|
err := Crud_` + ModelName + `.Update_` + ColumnName + `(m)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddInterfaceUpdateEveryColumn(TextModel string, Table1 *types.Table) string {
|
||||||
|
Otvet := TextModel
|
||||||
|
|
||||||
|
//сортировка по названию таблиц
|
||||||
|
keys := make([]string, 0, len(Table1.MapColumns))
|
||||||
|
for k := range Table1.MapColumns {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
//найдём новый текст для каждой таблицы
|
||||||
|
//TextNew := ""
|
||||||
|
for _, key1 := range keys {
|
||||||
|
Column1, ok := Table1.MapColumns[key1]
|
||||||
|
if ok == false {
|
||||||
|
log.Panic("CreateFilesUpdateEveryColumn() Table1.MapColumns[key1] = false")
|
||||||
|
}
|
||||||
|
if create_files.Is_Common_Сolumn(Column1) == true {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
TextNew1 := FindTextInterfaceUpdateEveryColumn(Table1, Column1)
|
||||||
|
Otvet = create_files.AddInterfaceFunction(Otvet, TextNew1)
|
||||||
|
//pos1 := strings.Index(TextModel, TextNew1)
|
||||||
|
//if pos1 >= 0 { //только если нет такой функции в интерфейсе
|
||||||
|
// TextNew = TextNew + TextNew1
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindTextInterfaceUpdateEveryColumn(Table1 *types.Table, Column1 *types.Column) string {
|
||||||
|
Otvet := ""
|
||||||
|
|
||||||
|
ModelName := Table1.NameGo
|
||||||
|
ColumnName := Column1.NameGo
|
||||||
|
|
||||||
|
Otvet = `
|
||||||
|
Update_` + ColumnName + `(*` + ModelName + `) error`
|
||||||
|
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
@ -83,6 +83,7 @@ func CreateFileProto(MapAll map[string]*types.Table) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextProtoNew = TextProtoNew + FindTextProtoTable1(TextProto, Table1)
|
TextProtoNew = TextProtoNew + FindTextProtoTable1(TextProto, Table1)
|
||||||
|
TextProtoNew = TextProtoNew + FindTextProtoTable1_UpdateEveryColumn(TextProto, Table1)
|
||||||
}
|
}
|
||||||
|
|
||||||
//найдём куда вставить текст
|
//найдём куда вставить текст
|
||||||
@ -308,3 +309,100 @@ func TextFindByExtId(ModelName string) string {
|
|||||||
|
|
||||||
return Otvet
|
return Otvet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindTextProtoTable1_UpdateEveryColumn - возвращает текст всех функций .proto для таблицы, обновления каждого поля таблицы
|
||||||
|
func FindTextProtoTable1_UpdateEveryColumn(TextProto string, Table1 *types.Table) string {
|
||||||
|
Otvet := "\n" //"\n\t//\n"
|
||||||
|
|
||||||
|
//ModelName := Table1.NameGo
|
||||||
|
|
||||||
|
//сортировка по названию таблиц
|
||||||
|
keys := make([]string, 0, len(Table1.MapColumns))
|
||||||
|
for k := range Table1.MapColumns {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
//найдём новый текст для каждой таблицы
|
||||||
|
for _, key1 := range keys {
|
||||||
|
Column1, ok := Table1.MapColumns[key1]
|
||||||
|
if ok == false {
|
||||||
|
log.Panic("FindTextProtoTable1_UpdateEveryColumn() Table1.MapColumns[key1] = false")
|
||||||
|
}
|
||||||
|
if create_files.Is_Common_Сolumn(Column1) == true {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
Otvet1 := FindTextUpdateEveryColumn(TextProto, Table1, Column1)
|
||||||
|
//TextFind := "rpc " + Table1.NameGo + "_"
|
||||||
|
//pos1 := FindLastGoodPos(TextProto, TextFind)
|
||||||
|
//if pos1 > 0 {
|
||||||
|
// Otvet = Otvet[:pos1] + Otvet1 + Otvet[pos1:]
|
||||||
|
//} else {
|
||||||
|
// Otvet = Otvet + Otvet1
|
||||||
|
//}
|
||||||
|
Otvet = Otvet + Otvet1
|
||||||
|
}
|
||||||
|
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindTextUpdateEveryColumn - возвращает текст .proto функции Update_ColumnName()
|
||||||
|
func FindTextUpdateEveryColumn(TextProto string, Table1 *types.Table, Column1 *types.Column) string {
|
||||||
|
Otvet := ""
|
||||||
|
Otvet2 := TextUpdateEveryColumn(Table1, Column1)
|
||||||
|
|
||||||
|
//проверка такой текст уже есть
|
||||||
|
pos1 := strings.Index(TextProto, Otvet2)
|
||||||
|
if pos1 >= 0 {
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
|
||||||
|
Otvet = "\t" + Otvet2 + "\n"
|
||||||
|
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindTextRequest - возвращает имя message из .proto, в зависимости от типа
|
||||||
|
func FindTextRequest(TypeGo string) string {
|
||||||
|
Otvet := "RequestID"
|
||||||
|
|
||||||
|
switch TypeGo {
|
||||||
|
case "int", "int64":
|
||||||
|
|
||||||
|
Otvet = "RequestId"
|
||||||
|
case "int32":
|
||||||
|
Otvet = "RequestInt32"
|
||||||
|
case "string":
|
||||||
|
|
||||||
|
Otvet = "RequestString"
|
||||||
|
case "time.Time":
|
||||||
|
|
||||||
|
Otvet = "RequestDate"
|
||||||
|
case "float32":
|
||||||
|
Otvet = "RequestFloat32"
|
||||||
|
case "float64":
|
||||||
|
Otvet = "RequestFloat64"
|
||||||
|
case "bool":
|
||||||
|
Otvet = "RequestBool"
|
||||||
|
}
|
||||||
|
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextUpdateEveryColumn - возвращает текст .proto функции Update_ColumnName()
|
||||||
|
func TextUpdateEveryColumn(Table1 *types.Table, Column1 *types.Column) string {
|
||||||
|
Otvet := ""
|
||||||
|
|
||||||
|
ModelName := Table1.NameGo
|
||||||
|
|
||||||
|
TextRequest := "RequestID"
|
||||||
|
TypeGo := Column1.TypeGo
|
||||||
|
TextRequest = FindTextRequest(TypeGo)
|
||||||
|
ColumnName := Column1.NameGo
|
||||||
|
|
||||||
|
Otvet = "rpc " + ModelName + "_Update_" + ColumnName + "(" + TextRequest + ") returns (ResponseEmpty) {}"
|
||||||
|
//Otvet = Otvet + "\n"
|
||||||
|
|
||||||
|
return Otvet
|
||||||
|
}
|
||||||
|
@ -1 +1,15 @@
|
|||||||
package protobuf
|
package protobuf
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestFindLastGoodPos(t *testing.T) {
|
||||||
|
s := "123456\n789"
|
||||||
|
pos1 := FindLastGoodPos(s, "5")
|
||||||
|
if pos1 < 0 {
|
||||||
|
t.Error("FindLastGoodPos() error: ", pos1)
|
||||||
|
}
|
||||||
|
s2 := s[pos1:]
|
||||||
|
if s2 != "789" {
|
||||||
|
t.Error("FindLastGoodPos() error: ", s2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user