Compare commits

...

23 Commits

Author SHA1 Message Date
51d8f0822f update migration 2025-01-15 14:05:43 +03:00
ctengiz
a2215b9363 upgrade code 2025-01-15 11:50:12 +03:00
ctengiz
20e4a15286 nadmin fixes 2024-06-23 21:09:24 +03:00
ctengiz
29ca0b9965 fix 2024-06-14 17:21:20 +03:00
ctengiz
f54c5660ea comnpany to cm 2024-06-14 17:19:38 +03:00
ctengiz
a28576960e kriter değeri ekleme, hesap kodu kopyalama, excel okuma, kriter güncelleme 2024-04-29 17:21:02 +03:00
ctengiz
a661c06639 maliyet dağıtım fix 2024-04-25 13:51:46 +03:00
ctengiz
b9ed81beb7 maliyet dağıtımı 2024-04-25 12:14:48 +03:00
ctengiz
06f834e8c9 birleşik kriter 2024-04-19 10:57:57 +03:00
ctengiz
945b479309 menu icons 2024-04-19 10:52:53 +03:00
ctengiz
64479793ea map sheet select 2024-04-18 14:43:06 +03:00
ctengiz
706529f11d install, publish scripts 2024-04-16 22:20:17 +03:00
ctengiz
09b7f3b74b alfa release 2024-04-16 22:01:23 +03:00
ctengiz
cb91e4dfcf fixes 2024-04-15 10:40:28 +03:00
ctengiz
61f5f8fe04 base structure 2024-04-15 10:00:31 +03:00
ctengiz
19d9d24530 initial db 2024-04-14 19:43:23 +03:00
ctengiz
e9d8383eaa gitginore 2024-04-14 19:43:00 +03:00
ctengiz
f2d1234a6e company selection 2024-04-14 19:41:57 +03:00
ctengiz
9ac53000b4 .gitignore add 2024-04-11 21:05:21 +03:00
ctengiz
2aeb3cb120 .gitignore 2024-04-11 21:04:17 +03:00
ctengiz
03036fdffd initial auth from subscriber app 2024-04-11 21:04:00 +03:00
ctengiz
5bc7a48f75 initail migration 2024-04-11 21:02:55 +03:00
ctengiz
719edfd33a basic ui 2024-04-11 21:02:41 +03:00
41 changed files with 6242 additions and 93 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
go.sum
go.work.sum
.idea
version.js

View File

@@ -0,0 +1,85 @@
create domain dmn_code as text not null;
create domain dmn_money as numeric(22, 4);
create domain dmn_qty numeric(18, 6);
create domain dmn_rate as numeric(5, 2);
create domain dmn_clid bigint not null check ( value > 0 );
create domain dmn_usrid bigint not null check ( value > 0 );
create function iif(condition boolean, true_result anyelement, false_result anyelement) returns anyelement
immutable
language sql
as
$$
SELECT CASE
WHEN condition THEN true_result
ELSE false_result
END
$$;
CREATE
OR REPLACE FUNCTION zllog()
RETURNS trigger
LANGUAGE plpgsql
AS
$BODY$
BEGIN
if
(TG_OP = 'INSERT') then
if (new.zlins_dttm is null) then
new.zlins_dttm = current_timestamp;
END if;
end if;
if
(TG_OP = 'UPDATE') then
if (new.zlupd_dttm is null) then
new.zlupd_dttm = current_timestamp;
end if;
end if;
RETURN NEW;
END;
$BODY$;
create table logdml
(
id bigint generated by default as identity
primary key,
opr text,
dttm timestamp with time zone default CURRENT_TIMESTAMP not null,
src_table text,
src_id bigint,
usr_id bigint,
msg text,
old_val jsonb,
new_val jsonb
);
create index ndx_logdml_dttm
on logdml (dttm);
create index ndx_logdml_dttm_desc
on logdml (dttm desc);
create index ndx_logdml_table
on logdml (src_table, src_id);
create table sykv
(
key text not null
primary key,
val text
);
create table symigrate
(
id bigint generated by default as identity
primary key,
tracking_name text,
dttm timestamp default CURRENT_TIMESTAMP,
last_script integer,
log text
);

View File

@@ -0,0 +1,26 @@
create table company (
clid dmn_clid,
id bigint generated by default as identity PRIMARY key,
code dmn_code,
title text,
is_active boolean default true not null,
notes text,
zlins_dttm timestamptz,
zlupd_dttm timestamptz,
constraint uq_company unique (clid, code)
);
CREATE TRIGGER zl_company before INSERT or UPDATE ON company
FOR EACH ROW EXECUTE FUNCTION zllog();
create table company_usr (
id bigint generated by default as identity PRIMARY key,
company_id bigint not null,
usr_id dmn_usrid,
constraint uq_company_usr unique (company_id, usr_id),
constraint fk_company_usr_company foreign key (company_id)
references company(id) on update cascade on delete cascade
);

View File

@@ -0,0 +1 @@
alter table company add tmpl jsonb;

View File

@@ -0,0 +1,12 @@
alter table company
rename to cm;
alter table cm
rename column title to name;
alter table company_usr
rename to cm_usr;
alter table cm_usr
rename column company_id to cm_id;

View File

@@ -0,0 +1,3 @@
alter table cm_usr drop constraint uq_company_usr;
alter table cm_usr rename cm_id to cmid;
alter table cm_usr add constraint uq_cm_usr unique (cmid, usr_id);

24
db/migration/migrate.sh Executable file
View File

@@ -0,0 +1,24 @@
#! /bin/bash
read -p "Server [localhost] : " SERVER
SERVER=${SERVER:-localhost}
read -p "Veritabanı : $1" DB
DB=${DB:-$1}
read -p "Yeniden oluşturulsun mu? (E/H) [H] : " CREATE
CREATE=${CREATE:-H}
CREATESTR=""
DROPSTR=""
if [[ ${CREATE} =~ (E|e) ]]; then
CREATESTR="-create"
read -p "Eski mevcutsa silinsin mi? (E/H) [E] : " DROP
DROP=${DROP:-E}
if [[ ${DROP} =~ (E|e) ]]; then
DROPSTR="-drop-if-exists"
fi
fi
migrater -folder ./base -db ${DB} -host ${SERVER} ${CREATESTR} ${DROPSTR} -password tayitkan -tracker base -migrate-table symigrate
# migrater -folder ./seed -db ${DB} -host ${SERVER} ${CREATESTR} ${DROPSTR} -password tayitkan -tracker seed -migrate-table symigrate

59
db/sqls/cm.sql Normal file
View File

@@ -0,0 +1,59 @@
-- << Create
insert into cm ( clid
, code
, name
, is_active
, notes
, tmpl)
values ( @clid
, @code
, @name
, @is_active
, @notes
, @tmpl)
returning id
-- Create >>
-- << Read
select clid
, id
, code
, name
, is_active
, notes
, tmpl
from cm
where id = $1
-- Read >>
-- << Update
update cm
set code = @code
, name = @name
, is_active = @is_active
, notes = @notes
, tmpl = @tmpl
where id = @id
-- Update >>
-- << Delete
delete
from cm
where id = $1
-- Delete >>
-- << List
select clid
, id
, code
, name
, is_active
, notes
, tmpl
from cm {{.Where}}
{{.OrderBy}}
{{.Rows}}
-- List >>

Binary file not shown.

View File

@@ -0,0 +1,11 @@
#!/bin/bash
apt update && apt upgrade
dpkg-reconfigure locales
dpkg-reconfigure tzdata
sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
apt -y install postgresql-16 mc rsync curl
cd /tmp && sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'tesnos.+ed';"

View File

@@ -0,0 +1,65 @@
#!/bin/bash
APP=bordroesleme
apt update && apt upgrade
apt -y install mc rsync curl nginx
dpkg-reconfigure tzdata
useradd --system --shell=/usr/sbin/nologin ${APP}
mkdir -p /opt/${APP}/sqls
mkdir -p /opt/${APP}/migrate
mkdir -p /opt/${APP}/ui
mkdir -p /opt/${APP}/files
echo "Creating application service"
read -d '' sservice << EOF
[Unit]
Description=${APP}
After=syslog.target
After=network.target
#Requires=postgresql.service
#Requires=memcached.service
#Requires=redis.service
[Service]
# Modify these two values and uncomment them if you have
# repos with lots of files and get an HTTP error 500 because
# of that
###
LimitMEMLOCK=infinity
LimitNOFILE=1048576
RestartSec=2s
Type=simple
User=${APP}
Group=${APP}
WorkingDirectory=/opt/${APP}/
ExecStart=/opt/${APP}/${APP}
Restart=always
Environment=AUTHSERVER=http://127.0.0.1:40200 SQLSDIR=./sqls DBHOST=10.0.0.2 DBNAME=bordroesleme DBPASS=tesnos.+ed
# enable to bind to a port below 1024 uncomment
###
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE
#AmbientCapabilities=CAP_NET_BIND_SERVICE
# Prevent writes to /usr, /boot, and /etc
ProtectSystem=full
# Prevent accessing /home, /root and /run/user
ProtectHome=true
# Execute pre and post scripts as root, otherwise it does it as User=
PermissionsStartOnly=true
[Install]
WantedBy=multi-user.target
EOF
echo "$sservice" > /etc/systemd/system/${APP}.service
systemctl daemon-reload
systemctl enable ${APP}

55
scripts/publish.sh Executable file
View File

@@ -0,0 +1,55 @@
#!/bin/bash
APP=bordroesleme
PRJ_ROOT=${HOME}/prj/notitek/bordro-esleme
SVC_ROOT=${PRJ_ROOT}/svc
UI_ROOT=${PRJ_ROOT}/ui
DEST_IP=37.27.82.185
echo "Building ${APP} executable"
cd ${SVC_ROOT}
if [[ $OSTYPE == 'darwin'* ]]; then
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-w -s" -o ./$APP ./main.go
else
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-w -s" -o ./$APP ./main.go
fi
echo "Building SPA"
cd ${UI_ROOT}
version="0.alfa"
dt=$(date +%y%m%d-%H%M)
# gt=$(git rev-parse --short HEAD)
echo "
const vdata = {
version: '${version}',
build: '$dt'
}
export default vdata" > ./version.js
quasar build
echo "Copying executables"
scp ${SVC_ROOT}/${APP} root@${DEST_IP}:/opt/${APP}/${APP}.new
# scp ${SVC_ROOT}/upgrader/upgrader root@${DEST_IP}:/opt/${APP}/upgrader
echo "Transferring additional files"
#rsync -azP ${SVC_ROOT}/templates/ root@${DEST_IP}:/opt/${APP}/templates --delete
rsync -azP ${PRJ_ROOT}/db/sqls root@${DEST_IP}:/opt/${APP} --delete
#rsync -azP ${SVC_ROOT}/fonts/ root@${DEST_IP}:/opt/${APP}/fonts --delete
rsync -azP ${PRJ_ROOT}/db/migration/base root@${DEST_IP}:/opt/${APP}/migrate --delete
rsync -azP ${UI_ROOT}/dist/spa/ root@${DEST_IP}:/opt/${APP}/ui --delete
echo "Migrating database"
ssh root@${DEST_IP} "/opt/migrater -folder /opt/${APP}/migrate/base -db ${APP} -host 10.0.0.2 -tracker base -migrate-table symigrate -password tesnos.+ed"
echo "Updating system"
ssh root@${DEST_IP} "sudo systemctl stop ${APP}.service"
ssh root@${DEST_IP} "sudo rm /opt/${APP}/${APP}"
ssh root@${DEST_IP} "sudo mv /opt/${APP}/${APP}.new /opt/${APP}/${APP}"
ssh root@${DEST_IP} "chown -R ${APP}.${APP} /opt/${APP}"
ssh root@${DEST_IP} "sudo systemctl start ${APP}.service"
# remove compiled binary
cd ${SVC_ROOT}
rm -rf ./$APP

23
svc/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
### Go template
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work

162
svc/api/acompany.go Normal file
View File

@@ -0,0 +1,162 @@
package api
import (
"bordro-esleme/model/company"
"git.makki.io/makki/libgo/cmn"
"git.makki.io/makki/libgo/dbu"
"git.makki.io/makki/libgo/mhttp"
"git.notitek.com.tr/common/notgo/nauth"
"github.com/go-chi/chi/v5"
"net/http"
)
func companyGet(w http.ResponseWriter, r *http.Request) {
/*
tc := auth.TokenDataFromRequest(r)
rbac, err := tc.RBAC(r.Context(), auth.MdCompany)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
if !rbac.IsGrantedOp(auth.OpRead) && !tc.IsAdmin() {
mhttp.Forbidden(w)
return
}
*/
id := cmn.StrToInt64(chi.URLParam(r, "id"))
data, err := company.DbRead(r.Context(), id)
if err != nil {
if dbu.IsNoRowsErr(err) {
mhttp.NotFound(w)
return
} else {
mhttp.InternalServerError(w, err)
return
}
}
mhttp.ResponseSuccess(w, data)
}
func companyCreate(w http.ResponseWriter, r *http.Request) {
tc := nauth.TokenPayloadFromRequest(r)
/*
rbac, err := tc.RBAC(r.Context(), auth.MdCompany)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
if !rbac.IsGrantedOp(auth.OpCreate) && !tc.IsAdmin() {
mhttp.Forbidden(w)
return
}
*/
data := company.New()
err := cmn.BodyToJsonReq(r, &data)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
err = data.DbCreate(r.Context(), tc.ClientID(), true, tc.UsrID())
if err != nil {
mhttp.InternalServerError(w, err)
return
}
mhttp.ResponseSuccess(w, data.ID)
}
func companyUpdate(w http.ResponseWriter, r *http.Request) {
tc := nauth.TokenPayloadFromRequest(r)
/*
rbac, err := tc.RBAC(r.Context(), auth.MdCompany)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
if !rbac.IsGrantedOp(auth.OpUpdate) && !tc.IsAdmin() {
mhttp.Forbidden(w)
return
}
*/
data := company.New()
err := cmn.BodyToJsonReq(r, &data)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
err = data.DbUpdate(r.Context(), true, tc.UsrID())
if err != nil {
mhttp.InternalServerError(w, err)
return
}
mhttp.ResponseSuccess(w, true)
}
func companyDelete(w http.ResponseWriter, r *http.Request) {
tc := nauth.TokenPayloadFromRequest(r)
/*
rbac, err := tc.RBAC(r.Context(), auth.MdCompany)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
if !rbac.IsGrantedOp(auth.OpDelete) && !tc.IsAdmin() {
mhttp.Forbidden(w)
return
}
*/
id := cmn.StrToInt64(chi.URLParam(r, "id"))
err := company.DbDelete(r.Context(), id, true, tc.UsrID())
if err != nil {
mhttp.InternalServerError(w, err)
return
}
mhttp.ResponseSuccess(w, true)
}
func companyList(w http.ResponseWriter, r *http.Request) {
/*
tc := auth.TokenDataFromRequest(r)
rbac, err := tc.RBAC(r.Context(), auth.MdCompany)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
if !rbac.IsGrantedOp(auth.OpRead) && !tc.IsAdmin() {
mhttp.Forbidden(w)
return
}
*/
var data []company.Company
tr, err := dbu.NewTableRequestFromRequest(r)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
rp, err := dbu.NewRepoWithFile(r.Context(), "cm", nil)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
respData, err := rp.MList(tr, &data)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
mhttp.ResponseSuccess(w, respData)
}

44
svc/api/atmpl.go Normal file
View File

@@ -0,0 +1,44 @@
package api
import (
"encoding/json"
"git.makki.io/makki/libgo/cmn"
"git.makki.io/makki/libgo/dbu"
"git.makki.io/makki/libgo/mhttp"
"github.com/go-chi/chi/v5"
"net/http"
)
func tmplPost(w http.ResponseWriter, r *http.Request) {
companyID := cmn.StrToInt64(chi.URLParam(r, "companyID"))
var body json.RawMessage
err := cmn.BodyToJsonReq(r, &body)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
sq := "update cm set tmpl = $2 where id = $1"
_, err = dbu.DB.Exec(r.Context(), sq, companyID, body)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
mhttp.ResponseSuccess(w, true)
}
func tmplGet(w http.ResponseWriter, r *http.Request) {
companyID := cmn.StrToInt64(chi.URLParam(r, "companyID"))
var body json.RawMessage
sq := "select tmpl from cm where id = $1"
err := dbu.DB.QueryRow(r.Context(), sq, companyID).Scan(&body)
if err != nil {
mhttp.InternalServerError(w, err)
return
}
mhttp.ResponseSuccess(w, body)
}

144
svc/api/zhandler.go Normal file
View File

@@ -0,0 +1,144 @@
package api
import (
"fmt"
"git.makki.io/makki/libgo/mhttp"
"git.notitek.com.tr/common/notgo/napi"
"git.notitek.com.tr/common/notgo/nauth"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"git.makki.io/makki/libgo/enums"
"git.makki.io/makki/libgo/svc"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/go-chi/jwtauth/v5"
)
func HttpHandler(re enums.TRunEnv) http.Handler {
lookup := &mhttp.Lookup{
DoClientCheck: true,
Funcs: map[string]mhttp.LookupFunc{
"entegrator": napi.Entegrators,
"invmarket": napi.InvMarket,
"usr": napi.Usr,
"rates": napi.Currency,
},
CompanyCheckQueries: []string{"mmitem", "acchart", "ficomp"},
JwtAuth: svc.S.JWT,
}
mux := chi.NewRouter()
// Gerekli middleware stack
mux.Use(middleware.RequestID)
mux.Use(middleware.RealIP)
mux.Use(middleware.Logger)
// Eğer trace middleware'ı kullanırsak buna gerek kalmayacak sanırım kontrol etmeli
// https://github.com/go-chi/httptracer
mux.Use(middleware.Recoverer)
acors := cors.New(cors.Options{
// AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts
AllowedOrigins: []string{"*"},
// AllowOriginFunc: func(r *http.Request, origin string) bool { return true },
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
ExposedHeaders: []string{"Link"},
AllowCredentials: true,
MaxAge: 300, // Maximum value not ignored by any of major browsers
})
mux.Use(acors.Handler)
// Set a timeout value on the request context (ctx), that will signal
// through ctx.Done() that the request has timed out and further
// processing should be stopped.
// todo: bu belki endpoint bazında özelleştirlebilinir...
// mux.Use(middleware.Timeout(2500 * time.Millisecond))
mux.Route("/api", func(mr chi.Router) {
// Public Route endpoints
mr.Post("/login", napi.Login)
//protected end points
mr.Group(func(r chi.Router) {
// Seek, verify and validate JWT tokens
r.Use(jwtauth.Verify(svc.S.JWT, jwtauth.TokenFromHeader, jwtauth.TokenFromCookie, jwtauth.TokenFromQuery))
// Handle valid / invalid tokens.
r.Use(nauth.CheckTokenValidity)
// Set clientID
r.Use(nauth.ClientID)
// Handle valid / invalid tokens.
r.Use(nauth.CheckTokenValidity)
// lookup
r.Method("post", "/lookup/{query}", lookup)
// sy routes
r.Get("/sy/companies", napi.CompanyList)
// company routes
r.Get("/company/{id:[0-9]+}", companyGet)
r.Get("/company", companyList)
r.Put("/company", companyUpdate)
r.Post("/company", companyCreate)
r.Delete("/company/{id:[0-9]+}", companyDelete)
r.Post("/tmpl/{companyID:[0-9]+}", tmplPost)
r.Get("/tmpl/{companyID:[0-9]+}", tmplGet)
})
})
// Handle Websocket
// mux.HandleFunc("/ws", ws.Handle)
// SPA Routes
mux.Group(func(r chi.Router) {
r.NotFound(index)
r.Get("/", index)
})
return mux
}
func index(w http.ResponseWriter, r *http.Request) {
p := r.URL.Path
if !strings.HasPrefix(p, "/") {
p = "/" + p
r.URL.Path = p
}
p = path.Clean(p)
if strings.HasPrefix(p, "/api") {
http.NotFound(w, r)
return
}
uiPath := "./ui"
if svc.S.RunEnv() == enums.RunEnvLocal {
uiPath = "../../ui/dist/spa"
}
name := path.Join(uiPath, filepath.FromSlash(p))
f, err := os.Open(name)
if err != nil {
if os.IsNotExist(err) {
http.ServeFile(w, r, fmt.Sprintf("%s/index.html", uiPath))
return
}
}
defer f.Close()
http.ServeFile(w, r, name)
}

14
svc/go.mod Normal file
View File

@@ -0,0 +1,14 @@
module bordro-esleme
go 1.22.2
toolchain go1.22.2
require (
git.makki.io/makki/libgo v0.0.0-20240408174556-52dd3c28a9b9 // indirect
git.notitek.com.tr/common/notgo v0.0.0-20240408194744-d12ce3096580 // indirect
github.com/go-chi/chi/v5 v5.0.12 // indirect
github.com/go-chi/cors v1.2.1 // indirect
github.com/guregu/null/v5 v5.0.0 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
)

46
svc/main.go Normal file
View File

@@ -0,0 +1,46 @@
package main
import (
"bordro-esleme/api"
"git.makki.io/makki/libgo/dbu"
"git.makki.io/makki/libgo/mlog"
"git.makki.io/makki/libgo/svc"
"git.notitek.com.tr/common/notgo/nauth"
"time"
)
func main() {
// init base service
s := svc.Init("bordro-esleme", "bordroesleme", 40300, 40301)
// init logger
mlog.InitLogger(s.DevMode())
// connnect to db
dbConnectionString := s.DBConnectionString()
err := dbu.Connect(s.Ctx, dbConnectionString, s.SqlsDir(), s.DevMode())
if err != nil {
mlog.Fatal(err)
}
//kv table adını atayalım
dbu.DB.SetTableNameKV("sykv")
err = dbu.DB.Ping(s.Ctx)
if err != nil {
mlog.Fatal(err)
}
//subscribe to auth server
expire := time.Hour * 1
if svc.S.DevMode() {
expire = time.Hour * 48
}
err = nauth.SubscribeToAuthServer(s.Ctx, expire)
if err != nil {
mlog.Fatal(err)
}
// Init appplication service
s.StartHttp(api.HttpHandler(s.RunEnv()))
}

View File

@@ -0,0 +1,136 @@
package company
import (
"context"
"git.makki.io/makki/libgo/dbu"
"github.com/guregu/null/v5"
"log/slog"
"reflect"
)
type Company struct {
ClID int64 `db:"clid" json:"-"`
ID int64 `db:"id"`
Code string `db:"code"`
Name null.String `db:"name"`
IsActive bool `db:"is_active"`
Notes null.String `db:"notes"`
Tmpl null.String `db:"tmpl"`
}
func New() *Company {
m := &Company{
IsActive: true,
}
// m.__DetailStruct__ = dbu.NewDetailData[*__DetailStruct__]("__sql_filename__", "__sql_masterlink_field__", "__struct_master_field_link__", true)
return m
}
func DbRead(ctx context.Context, id int64) (*Company, error) {
rp, err := dbu.NewRepoWithFile(ctx, "cm", nil)
if err != nil {
return nil, err
}
data := New()
//rp.AddDetail(data.Auths)
err = rp.Read(id, data)
return data, err
}
func (m *Company) GetIDVal() int64 {
return m.ID
}
func (m *Company) SetMasterLinkVal(masterField string, val int64) {
reflect.ValueOf(m).Elem().FieldByName(masterField).SetInt(val)
}
func DbDelete(ctx context.Context, id int64, log bool, usrID *int64) error {
tx, err := dbu.DB.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
rp, err := dbu.NewRepoWithFile(ctx, "cm", tx)
if err != nil {
return err
}
if log {
var oldData *Company
oldData, err = DbRead(ctx, id)
if err != nil {
return dbu.ParsedErrSuppressNoRows(err)
}
err = dbu.DB.LogDMLTx("d", "cm", oldData.ID, usrID, "", oldData, nil, tx)
if err != nil {
slog.Error(err.Error())
}
}
err = rp.Delete(id)
if err != nil {
return err
}
return tx.Commit(ctx)
}
func (m *Company) DbCreate(ctx context.Context, clID int64, log bool, usrID *int64) error {
m.ClID = clID
rp, err := dbu.NewRepoWithFile(ctx, "cm", nil)
if err != nil {
return err
}
err = rp.Create(m)
if err != nil {
return err
}
if log {
err = dbu.DB.LogDMLTx("c", "cm", m.ID, usrID, "", nil, m, nil)
if err != nil {
slog.Error(err.Error())
}
}
return nil
}
func (m *Company) DbUpdate(ctx context.Context, log bool, usrID *int64) error {
tx, err := dbu.DB.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
rp, err := dbu.NewRepoWithFile(ctx, "cm", tx)
if err != nil {
return err
}
if log {
var oldData *Company
oldData, err = DbRead(ctx, m.ID)
if err != nil {
return err
}
err = dbu.DB.LogDMLTx("u", "cm", oldData.ID, usrID, "", oldData, m, tx)
if err != nil {
slog.Error(err.Error())
}
}
err = rp.Update(m.ID, m)
if err != nil {
return err
}
return tx.Commit(ctx)
}

View File

@@ -13,13 +13,17 @@
"build": "quasar build"
},
"dependencies": {
"@intlify/vite-plugin-vue-i18n": "^7.0.0",
"@quasar/extras": "^1.16.4",
"axios": "^1.2.1",
"jwt-decode": "^4.0.0",
"pinia": "^2.0.11",
"quasar": "^2.8.0",
"reconnecting-websocket": "^4.4.0",
"vue": "^3.4.18",
"vue-router": "^4.0.12"
"vue-i18n": "^9.9.0",
"vue-router": "^4.0.12",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
},
"devDependencies": {
"@quasar/app-vite": "^1.8.0",

View File

@@ -8,9 +8,8 @@
// Configuration for your app
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js
const { configure } = require('quasar/wrappers');
const { configure } = require('quasar/wrappers')
const path = require('path')
module.exports = configure(function (ctx) {
return {
@@ -23,12 +22,14 @@ module.exports = configure(function (ctx) {
boot: [
'axios',
'version',
'i18n',
'pinia',
'bus',
],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
css: [
'app.scss'
'app.scss',
],
// https://github.com/quasarframework/quasar/tree/dev/extras
@@ -49,7 +50,7 @@ module.exports = configure(function (ctx) {
build: {
target: {
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
node: 'node20'
node: 'node20',
},
vueRouterMode: 'history', // available values: 'hash', 'history'
@@ -62,12 +63,14 @@ module.exports = configure(function (ctx) {
// publicPath: '/',
// analyze: true,
env: {
showLangSelect: false,
wsActive: false,
apiAddr: (() => {
if (process.env.CUSTOM_API) {
return process.env.CUSTOM_API
} else {
if (ctx.dev) {
return 'http://localhost:41000/api'
return 'http://localhost:40300/api'
} else {
return '/api'
}
@@ -78,7 +81,7 @@ module.exports = configure(function (ctx) {
return process.env.CUSTOM_WS
} else {
if (ctx.dev) {
return 'ws://127.0.0.1:41000/ws'
return 'ws://127.0.0.1:40300/ws'
} else {
return 'wss://bres.notitek.com.tr/ws'
}
@@ -98,19 +101,34 @@ module.exports = configure(function (ctx) {
// viteVuePluginOptions: {},
vitePlugins: [
['vite-plugin-checker', {
[
'vite-plugin-checker', {
eslint: {
lintCommand: 'eslint "./**/*.{js,mjs,cjs,vue}"'
lintCommand: 'eslint "./**/*.{js,mjs,cjs,vue}"',
},
}, { server: false },
],
[
'@intlify/vite-plugin-vue-i18n', {
// if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false`
// compositionOnly: false,
// if you want to use named tokens in your Vue I18n messages, such as 'Hello {name}',
// you need to set `runtimeOnly: false`
// runtimeOnly: false,
// you need to set i18n resource including paths !
include: path.resolve(__dirname, './src/i18n/**'),
}
}, { server: false }]
]
],
],
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
devServer: {
// https: true
port:9400,
open: true // opens browser window automatically
port: 9403,
open: true, // opens browser window automatically
},
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#framework
@@ -132,8 +150,10 @@ module.exports = configure(function (ctx) {
'Notify',
'Loading',
'Dialog',
'Meta'
]
'Meta',
'LocalStorage',
'SessionStorage'
],
},
// animations: 'all', // --- includes all animations
@@ -169,8 +189,8 @@ module.exports = configure(function (ctx) {
// (gets superseded if process.env.PORT is specified at runtime)
middlewares: [
'render' // keep this as last one
]
'render', // keep this as last one
],
},
// https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa
@@ -194,7 +214,7 @@ module.exports = configure(function (ctx) {
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor
capacitor: {
hideSplashscreen: true
hideSplashscreen: true,
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron
@@ -223,18 +243,18 @@ module.exports = configure(function (ctx) {
builder: {
// https://www.electron.build/configuration/configuration
appId: 'tr.com.notitek.bres'
}
appId: 'tr.com.notitek.bres',
},
},
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex
bex: {
contentScripts: [
'my-content-script'
'my-content-script',
],
// extendBexScriptsConf (esbuildConf) {}
// extendBexManifestJson (json) {}
},
}
}
});
})

View File

@@ -7,7 +7,7 @@ import axios from 'axios'
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
const api = axios.create({ baseURL: 'https://api.example.com' })
const api = axios.create({ baseURL: process.env.apiAddr })
export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api

16
ui/src/boot/bus.js Normal file
View File

@@ -0,0 +1,16 @@
// a Quasar CLI boot file (let's say /src/boot/bus.js)
import { EventBus } from 'quasar'
import { boot } from 'quasar/wrappers'
const bus = new EventBus()
export default boot(({ app }) => {
// for Options API
app.config.globalProperties.$bus = bus
// for Composition API
app.provide('bus', bus)
})
export { bus }

16
ui/src/boot/i18n.js Normal file
View File

@@ -0,0 +1,16 @@
import { boot } from 'quasar/wrappers'
import { createI18n } from 'vue-i18n'
import messages from 'src/i18n'
const i18n = createI18n({
locale: 'tr',
globalInjection: true,
messages
})
export default boot(({ app }) => {
// Set i18n instance on app
app.use(i18n)
})
export { i18n }

9
ui/src/i18n/en.js Normal file
View File

@@ -0,0 +1,9 @@
import base from "src/libjs/i18n/en"
const prjI18n = {
menu: {
app: 'Applications'
},
}
export default {...base, ...prjI18n}

8
ui/src/i18n/index.js Normal file
View File

@@ -0,0 +1,8 @@
import enUS from './en'
import trTR from './tr'
export default {
'tr': trTR,
'en-US': enUS,
'en-GB': enUS,
}

18
ui/src/i18n/tr.js Normal file
View File

@@ -0,0 +1,18 @@
import base from "src/libjs/i18n/tr"
const prjI18n = {
menu: {
app: 'Uygulamalar'
},
Company: {
ID: 'Şirket ID',
Code: 'Şirket Kod',
Title: 'Şirket Ad',
IsActive: 'Kullanımda',
Notes: 'Açıklamalar',
Tmpl: 'Eşleme Şablonu'
},
Usr: {},
}
export default {...base, ...prjI18n}

View File

@@ -1,42 +1,53 @@
<template>
<q-layout view="lHh Lpr lFf" style="background-color: #f8fafc;">
<q-header class="bg-white q-py-sm" bordered>
<q-toolbar>
<q-toolbar-title>
<q-img src="logo.png" width="120px" fit="contain"/>
</q-toolbar-title>
<q-btn
:label="`${langs[locale.locale.value].flag} ${langs[locale.locale.value].short}`"
no-caps
color="grey-5"
rounded
<q-drawer
v-model="ld.leftDrawerOpen"
show-if-above
bordered
content-class="bg-grey-1"
>
<q-menu>
<q-list style="min-width: 100px">
<q-item v-for="(v, k) in langs" :key="k"
clickable v-close-popup @click="locale.locale.value=k">
<q-item-section>{{ v.flag }} {{ v.title }}</q-item-section>
<q-scroll-area class="fit">
<q-list>
<q-item-label header>
<q-img src="notitek.png" width="120px" class="q-mb-lg"/>
<br/>
Bordro Eşleme
<br/>
{{ $VERSION.version }}
<span style="font-size:0.7rem;">
{{ $VERSION.build }}
</span>
</q-item-label>
<template v-for="m in ld.menu" :key="m.grantKey">
<q-item :to="m.to" v-if="!m.children" active-class="text-pcolor1">
<q-item-section avatar v-if="m.icon">
<q-icon :name="m.icon"/>
</q-item-section>
<q-item-section>
<q-item-label>{{ m.label }}</q-item-label>
</q-item-section>
</q-item>
<q-expansion-item
v-else
:icon="m.icon"
:label="m.label"
>
<q-item v-for="c in m.children" :key="c.grantKey" :to="c.to" exact :inset-level=".5" active-class="text-pcolor1">
<q-item-section avatar v-if="c.icon">
<q-icon :name="c.icon"/>
</q-item-section>
<q-item-section>
<q-item-label>{{ c.label }}</q-item-label>
</q-item-section>
</q-item>
</q-expansion-item>
</template>
</q-list>
</q-menu>
</q-btn>
<q-btn
round
flat
class="on-right"
icon="logout"
color="pcolor3"
@click="logout"
/>
</q-toolbar>
</q-header>
</q-scroll-area>
</q-drawer>
<q-page-container>
<router-view/>
@@ -45,16 +56,22 @@
</template>
<script setup>
import { ref } from 'vue'
import { $VERSION } from 'boot/version'
import { inject, reactive } from 'vue'
import { useLoginStore } from 'stores/login'
import { useI18n } from 'vue-i18n'
import { langs } from 'src/lib/langlist'
import { menu } from 'src/lib/menu'
const loginStore = useLoginStore()
const locale = useI18n({ useScope: 'global' })
const ld = reactive({
leftDrawerOpen: true,
menu: menu(),
})
const bus = inject('bus') // inside setup()
bus.on('toggleLeftDrawerOpen', () => {
ld.leftDrawerOpen = !ld.leftDrawerOpen
})
const logout = function () {
loginStore.logout()

140
ui/src/lib/menu.js Normal file
View File

@@ -0,0 +1,140 @@
import { useLoginStore } from 'stores/login'
const store = useLoginStore()
const tmenu = [
{
label: 'Şirket Tanımları',
icon: 'business',
grantKey: 'Company',
to: '/company'
},
{
label: 'Şablon Tasarımı',
icon: 'design_services',
grantKey: 'Tmpl',
to: '/tmpl'
},
{
label: 'Bordro Eşleme',
icon: 'summarize',
grantKey: 'Map',
to: '/map'
},
/*
{
label:'Hesap Planı',
icon: 'mdi-database-edit',
grantKey: 'Data',
to: null,
children: [
{
label: 'menu.fiCrn',
icon: 'mdi-server-network',
grantKey: 'fiCrn',
to: '/ficrn'
},
{
label: 'menu.dfGeo',
icon: 'mdi-server-network',
grantKey: 'defGeo',
to: '/dfgeo'
},
{
label: 'menu.region',
icon: 'mdi-server-network',
grantKey: 'defGeo',
to: '/region'
},
{
label: 'menu.translations',
icon: 'mdi-translate',
grantKey: 'Locale',
to: '/locale'
},
]
},
{
label: 'menu.systemManagement',
icon: 'build_circle',
grantKey: 'System',
to: null,
children: [
{
label: 'menu.syusr',
icon: 'mdi-badge-account-horizontal',
grantKey: 'syUsr',
to: '/syusr'
},
{
label: 'menu.syunit',
icon: 'mdi-account-group',
grantKey: 'syUnit',
to: '/syunit'
},
{
label: 'menu.systemParams',
icon: 'settings_applications',
grantKey: 'syParams',
to: '/syparams'
},
{
label: 'Log Auth',
icon: 'receipt',
grantKey: 'LogAuth',
to: '/logauth'
},
{
label: 'Log DML',
icon: 'receipt',
grantKey: 'LogDML',
to: '/logdml'
}
]
},
*/
]
export const menu = function () {
const m = tmenu.filter(m => {
return checkGrant(m.grantKey)
})
return m
}
const checkGrant = function (typ) {
//todo:
return true
if (store.IsAdmin) {
return true
}
if (store.grantHasKey(typ)) {
const o = store.Grants[typ]
for (let [key, value] of Object.entries(o)) {
if (value) {
return true
}
}
}
return false
}
const checkRprGrant = function (typ) {
if (this.$store.IsAdmin) {
return true
}
return store.Grants.Rpr[typ]
}

101
ui/src/model/company.js Normal file
View File

@@ -0,0 +1,101 @@
import { Model } from 'src/libjs/lib/model'
import { i18n } from 'boot/i18n'
const t = i18n.global.t
class Company extends Model {
ID = null
Code = ''
Name = ''
IsActive = true
Notes = ''
Tmpl = ''
_colOptions = {
ID: {
label: t('Company.ID'),
visible: true,
dataType: 'text', //numeric, text, date, bool, datetime, time, inet
sortable: true,
showFilter: true,
//filterOptions: {},
//render: (val, row) => {}
//alias: '' //--> backend için, joinli tablolarda alias kullanımı. tableRequest'te fixFieldName cehennemine son vermek için
},
Code: {
label: t('Company.Code'),
visible: true,
dataType: 'text', //numeric, text, date, bool, datetime, time, inet
sortable: true,
showFilter: true,
//filterOptions: {},
//render: (val, row) => {}
//alias: '' //--> backend için, joinli tablolarda alias kullanımı. tableRequest'te fixFieldName cehennemine son vermek için
},
Name: {
label: t('Company.Title'),
visible: true,
dataType: 'text', //numeric, text, date, bool, datetime, time, inet
sortable: true,
showFilter: true,
//filterOptions: {},
//render: (val, row) => {}
//alias: '' //--> backend için, joinli tablolarda alias kullanımı. tableRequest'te fixFieldName cehennemine son vermek için
},
IsActive: {
label: t('Company.IsActive'),
visible: true,
dataType: 'text', //numeric, text, date, bool, datetime, time, inet
sortable: true,
showFilter: true,
//filterOptions: {},
//render: (val, row) => {}
//alias: '' //--> backend için, joinli tablolarda alias kullanımı. tableRequest'te fixFieldName cehennemine son vermek için
},
Notes: {
label: t('Company.Notes'),
visible: true,
dataType: 'text', //numeric, text, date, bool, datetime, time, inet
sortable: true,
showFilter: true,
//filterOptions: {},
//render: (val, row) => {}
//alias: '' //--> backend için, joinli tablolarda alias kullanımı. tableRequest'te fixFieldName cehennemine son vermek için
},
Tmpl: {
label: t('Company.Tmpl'),
visible: true,
dataType: 'text', //numeric, text, date, bool, datetime, time, inet
sortable: true,
showFilter: true,
//filterOptions: {},
//render: (val, row) => {}
//alias: '' //--> backend için, joinli tablolarda alias kullanımı. tableRequest'te fixFieldName cehennemine son vermek için
},
}
constructor (endPoint) {
super(endPoint)
this._ignoredFieldsOnPost = ['GrpCode', 'ClsCode', 'SpeCode']
}
}
export default Company

View File

@@ -1,15 +0,0 @@
<template>
<q-page class="flex flex-center">
<img
alt="Quasar logo"
src="~assets/quasar-logo-vertical.svg"
style="width: 200px; height: 200px"
>
</q-page>
</template>
<script setup>
defineOptions({
name: 'IndexPage'
});
</script>

112
ui/src/pages/company.vue Normal file
View File

@@ -0,0 +1,112 @@
<template>
<q-page padding>
<m-edit-header
title="Şirket Kaydı"
:id="model.ID"
:show-save-cancel="true"
@save="save"
@cancel="cancel"
/>
<q-form ref="frmMain" greedy autofocus>
<q-card>
<q-card-section>
<div class="row q-col-gutter-md">
<q-input class="col-xs-12 col-sm-4"
v-model="model.Code"
:label="t('Company.Code')"
bottom-slots
:rules="[rules.required]"
/>
<q-input class="col-xs-12 col-sm-6"
v-model="model.Name"
:label="t('Company.Title')"
bottom-slots
:rules="[]"
/>
<q-checkbox class="col-xs-12 col-sm-2"
v-model="model.IsActive"
:label="model._colOptions.IsActive.label"
bottom-slots
:rules="[]"
/>
</div>
<div class="row q-col-gutter-md">
<q-input class="col"
v-model="model.Notes"
:label="t('Company.Notes')"
bottom-slots
:rules="[]"
autogrow
/>
</div>
</q-card-section>
<q-card-section>
</q-card-section>
</q-card>
</q-form>
</q-page>
</template>
<script setup>
import MEditHeader from 'src/libjs/comp/MEditHeader.vue'
import { onBeforeMount, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useLoginStore } from 'stores/login'
import Company from 'src/model/company'
import { rules } from 'src/libjs/lib/validation'
import { i18n } from 'boot/i18n'
const t = i18n.global.t
const frmMain = ref(null)
const endPoint = ref('/company')
const route = useRoute()
const router = useRouter()
const store = useLoginStore()
const model = reactive(new Company(endPoint.value))
const ld = reactive({})
onBeforeMount(() => {
if (route.params.id) {
load()
}
})
const load = function () {
const id = route.params.id
if (!id) {
return
}
model.read(id).then(() => {
// do some other stuff
})
}
const save = function () {
frmMain.value.validate().then(success => {
if (success) {
model.save(false)
} else {
// oh no, user has filled in
// at least one invalid value
}
})
}
const cancel = function () {
router.push(endPoint.value)
}
</script>

View File

@@ -0,0 +1,30 @@
<template>
<q-page padding>
<m-edit-header title="Şirket Tanımları"/>
<m-list
:url="ld.endPoint"
:model="model"
>
</m-list>
</q-page>
</template>
<script setup>
import Company from 'src/model/company'
import { reactive, ref } from 'vue'
import { useQuasar } from 'quasar'
import useLoginStore from 'stores/login'
import MEditHeader from 'src/libjs/comp/MEditHeader.vue'
import MList from 'src/libjs/comp/MList.vue'
const $q = useQuasar()
const store = useLoginStore()
const ld = reactive({
endPoint: '/company'
})
const model = ref(new Company(ld.endPoint))
</script>

16
ui/src/pages/index.vue Normal file
View File

@@ -0,0 +1,16 @@
<template>
<q-page padding>
<m-edit-header
show-company-select
:show-lang-selector="false"
/>
</q-page>
</template>
<script setup>
import MEditHeader from 'src/libjs/comp/MEditHeader.vue'
defineOptions({
name: 'IndexPage'
});
</script>

View File

@@ -2,7 +2,7 @@
<q-page padding>
<div class="row justify-center items-center" style="height: 70vh;">
<div class="col-xs-10 col-sm-8 col-md-6 col-lg-4 col-xl-3 q-gutter-sm" >
<q-input label="Kullanıcı Adı/ Email" outlined color="pcolor1" v-model="ldata.email"/>
<q-input label="Kullanıcı Adı/Email" outlined color="pcolor1" v-model="ldata.code"/>
<q-input
type="password"
label="Parola" outlined color="pcolor1" v-model="ldata.pass"/>

356
ui/src/pages/map.vue Normal file
View File

@@ -0,0 +1,356 @@
<template>
<q-page padding>
<m-edit-header title="Bordro Eşleme"/>
<q-card>
<q-card-section>
<q-select v-model="ld.companyID"
map-options
emit-value
:label="t('selectCompany')"
:options="ld.companies"
option-value="ID"
option-label="Name"
dense
options-dense
outlined
bg-color="white"
@update:model-value="load"
>
<template v-slot:after>
<q-btn
flat
icon="business"
dense
size="sm"
color="white"
to="/company/new"
>
<q-tooltip>{{ t('createCompany') }}</q-tooltip>
</q-btn>
</template>
</q-select>
</q-card-section>
<q-card-section v-if="ld.companyID && ld.canProcess">
<div class="row q-gutter-y-sm q-col-gutter-md">
<q-file
dense
class="col"
v-model="ld.xlsFileName"
label="Bordro Dosyası"
clearable
@update:modelValue="loadFile"
/>
<q-select
class="col"
v-model="ld.bordroSheet"
:options="ld.sheets"
label="Bordro Sayfası"
dense
:disable="!ld.xlsFileName"
>
<template v-slot:after>
<q-btn
outline
color="pcolor1"
label="Bordro Fişi Oluştur"
no-caps
@click="doProcess"
:disable="!ld.bordroSheet"
/>
</template>
</q-select>
</div>
</q-card-section>
</q-card>
</q-page>
</template>
<script setup>
import { onMounted, reactive } from 'vue'
import { useI18n } from 'vue-i18n'
import { api } from 'boot/axios'
import { catchAxiosError, showAxiosError } from 'src/libjs/lib/axios'
import MEditHeader from 'src/libjs/comp/MEditHeader.vue'
import { read, utils, writeFile } from 'xlsx'
import { useQuasar } from 'quasar'
const { t } = useI18n()
const $q = useQuasar()
const ld = reactive({
companyID: null,
companies: [],
sheets: [],
bordroSheet: '',
xlsFileName: null,
selectedVal: '',
alreadySelectedValFields: {},
canProcess: false,
})
const tmpl = reactive({
rawData: null,
sheets: [],
bordroSheet: '',
baslikSatiri: 7,
baslikSatirAdedi: 2,
kontrolKolonu: -1,
alanlar: [],
kriterler: {},
dagitim: {}
})
onMounted(() => {
getCompanyList()
})
const getCompanyList = function () {
api.get('/sy/companies').then(res => {
if (res.data.Success) {
ld.companies = res.data.Data || []
} else {
showAxiosError(res)
}
}).catch(err => {
catchAxiosError(err)
})
}
const load = function () {
Object.keys(ld.alreadySelectedValFields).forEach(k => {
delete ld.alreadySelectedValFields[k]
})
Object.keys(tmpl.kriterler).forEach(k => {
delete tmpl.kriterler[k]
})
tmpl.sheets.splice(0)
tmpl.alanlar.splice(0)
tmpl.bordroSheet = ''
$q.loading.show()
api.get(`/tmpl/${ld.companyID}`).then(res => {
if (res.data.Success) {
if (res.data.Data === null) {
ld.canProcess = false
$q.dialog({
title: 'Hata',
message: 'Şirket için tanımlı şablon yok',
}).onOk(() => {
// console.log('OK')
}).onCancel(() => {
// console.log('Cancel')
}).onDismiss(() => {
// console.log('I am triggered on both OK and Cancel')
})
return
}
ld.canProcess = true
Object.keys(res.data.Data).forEach(k => {
tmpl[k] = res.data.Data[k]
/*
if (k !== 'alanlar') {
tmpl[k] = res.data.Data[k]
}
*/
})
//tmpl.rawData = new Uint8Array(res.data.Data.rawData)
//workbook = read(tmpl.rawData)
Object.keys(tmpl.kriterler).forEach(k => {
tmpl.kriterler[k].valFields.forEach(f => {
ld.alreadySelectedValFields[f] = 'x'
})
})
} else {
showAxiosError()
}
}).catch(err => {
catchAxiosError(err)
}).finally(() => {
$q.loading.hide()
})
}
let workbook = null
const loadFile = function () {
const reader = new FileReader()
/*
reader.addEventListener('load', (event) => {
console.log(event.target.result)
});
reader.readAsDataURL(ld.xlsFile);
*/
reader.onload = function (e) {
const uin = new Uint8Array(e.target.result)
ld.sheets.splice(0)
workbook = read(uin)
ld.sheets.push(...workbook.SheetNames)
}
try {
$q.loading.show()
reader.readAsArrayBuffer(ld.xlsFileName)
} catch (err) {
console.log(err)
} finally {
$q.loading.hide()
}
}
const doProcess = function () {
try {
$q.loading.show()
processXLS()
} catch (err) {
console.log(err)
} finally {
$q.loading.hide()
}
}
const hesapKodBul = function (row, fieldName) {
// Hesap kodunu bulalım
let hesapKod = ''
Object.keys(tmpl.kriterler).forEach(k => {
const kriter = tmpl.kriterler[k]
let kriterVal = ''
if (kriter.colNro >= 0) {
// sabit kriter alanı
kriterVal = row[kriter.colNro]
} else {
const tmpValues = []
tmpl.kriterler[k].combinedFieldsNro.forEach(cf => {
let tmpKriterVal = row[cf]
if ((tmpKriterVal === null) || (tmpKriterVal === undefined)) {
tmpKriterVal = ''
}
tmpValues.push(tmpKriterVal)
})
kriterVal = tmpValues.join(' ').trim()
}
const map = kriter.mappings[kriterVal]
if ((map !== undefined) && (map !== null)) {
const tmpHesapKod = map[fieldName]
if (tmpHesapKod) {
hesapKod = tmpHesapKod
}
} else {
console.log(kriter)
}
})
return hesapKod
}
const processXLS = function () {
const sheet = workbook.Sheets[ld.bordroSheet]
const rawData = utils.sheet_to_json(sheet, { header: 1 })
const bordro = []
const dagitimKeys = Object.keys(tmpl.dagitim)
rawData.forEach((row, ndx) => {
if (ndx <= tmpl.baslikSatiri - 1 + tmpl.baslikSatirAdedi) {
} else {
const kval = row[tmpl.kontrolKolonu]
if ((kval !== null) && (kval !== undefined) && (kval !== '')) {
const bordroRow = {}
// sabit alanları verelim
tmpl.alanlar.filter(a => {return a.showInSlip}).forEach(a => {
if (a.colNro >= 0) {
bordroRow[a.fieldName] = row[a.colNro]
} else {
const tmpValues = []
a.combinedFieldsNro.forEach(cf => {
let tmpVal = row[cf]
if ((tmpVal === null) || (tmpVal === undefined)) {
tmpVal = ''
}
tmpValues.push(tmpVal)
})
bordroRow[a.fieldName] = tmpValues.join(' ').trim()
}
})
// veri alanlarını verelim
tmpl.alanlar.filter(a => {return a.colType === 'veri'}).forEach(veri => {
let dagitimYapildi = false
if (dagitimKeys.length > 0) {
dagitimKeys.forEach(bazAlan => {
const degerler = Object.keys(tmpl.dagitim[bazAlan].kural)
degerler.forEach(deger => {
if (deger === row[tmpl.dagitim[bazAlan].bazAlanColNro]) {
dagitimYapildi = true
tmpl.dagitim[bazAlan].kural[deger].forEach(oran => {
//row'ın bir kopyasını oluşturalım çünkü hesap kodunu bu yeni row'a göre bulacağız
const tmpRow = [...row]
tmpRow[tmpl.dagitim[bazAlan].hedefAlanColNro] = oran.deger
bordroRow['Hesap'] = hesapKodBul(tmpRow, veri.fieldName)
bordroRow['Masraf Açıklama'] = veri.fieldName
bordroRow['Tutar'] = row[veri.colNro] * (oran.oran / 100)
bordroRow['B/A'] = veri.ba
const hedefAlan = tmpl.dagitim[bazAlan].hedefAlan
if (bordroRow[hedefAlan]) {
bordroRow[hedefAlan] = oran.deger
}
bordro.push({ ...bordroRow })
})
}
})
})
}
if (!dagitimYapildi) {
bordroRow['Hesap'] = hesapKodBul(row, veri.fieldName)
bordroRow['Masraf Açıklama'] = veri.fieldName
bordroRow['Tutar'] = row[veri.colNro]
bordroRow['B/A'] = veri.ba
bordro.push({ ...bordroRow })
}
})
}
}
})
const newSheet = utils.json_to_sheet(bordro)
const newWB = utils.book_new()
utils.book_append_sheet(newWB, newSheet, 'veri')
writeFile(newWB, 'bordro.xlsx', { compression: true })
}
</script>

1056
ui/src/pages/tmpl.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -41,8 +41,7 @@ Router.beforeEach(
// instead of having to check every route record with
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !isLoggedIn &&
((to.path !== '/') || (to.path !== 'login'))) {
if (to.meta.requiresAuth && !isLoggedIn && to.path !== 'login') {
// this route requires auth, check if logged in
// if not, redirect to login page.
@@ -53,9 +52,9 @@ Router.beforeEach(
}
}
if ((isLoggedIn) && ((to.path === '/') || (to.path === 'login'))) {
if (isLoggedIn && to.path === 'login') {
return {
path: '/panel',
path: '/',
query: '',
}
}

View File

@@ -7,7 +7,18 @@ const routes = [
requiresAuth: true
},
children: [
{ path: '', component: () => import('pages/IndexPage.vue') },
{ path: '', component: () => import('pages/index.vue') },
{ path: '/panel', component: () => import('pages/index.vue') },
{ path: '/company', component: () => import('pages/companyList.vue') },
{ path: '/company/edit/:id', component: () => import('pages/company.vue') },
{ path: '/company/new', component: () => import('pages/company.vue') },
// { path: '/company/:id', component: () => import('pages/companyView.vue') },
{ path: '/tmpl', component: () => import('pages/tmpl.vue') },
{ path: '/map', component: () => import('pages/map.vue') },
]
},

View File

@@ -3,18 +3,34 @@ import { LocalStorage } from 'quasar'
import { api } from 'boot/axios'
import { jwtDecode } from "jwt-decode"
import Router from 'src/router/index'
import {bus} from 'boot/bus'
export const sessionName = 'bresSession'
const defaultState = {
LoggedIn: false,
IsAdmin: false,
Token: '',
UsrKSUID: '',
ClientKSUID: '',
Email: '',
Fullname: '',
UsrKsuid: '',
UsrEmail: '',
UsrFullName: '',
Username: '',
ClientKsuid: '',
ClientCode: '',
LicenseCode: '',
PkgConf: {},
companyID: null
}
bus.on('companySelect', (companyID) => {
const store = useLoginStore()
//store.companyID = companyID
store.setCompanyID(companyID)
})
export const useLoginStore = defineStore('login', {
state: () => {
return JSON.parse(JSON.stringify(defaultState))
@@ -22,6 +38,11 @@ export const useLoginStore = defineStore('login', {
getters: {},
actions: {
setCompanyID(companyID) {
this.companyID = companyID
this.save()
},
login (payload) {
// kaldıysa önceki session'ı uçuralım
LocalStorage.remove(sessionName)
@@ -52,6 +73,14 @@ export const useLoginStore = defineStore('login', {
}
},
updateProfile (payload) {
this.UsrFullName = payload.fullname
this.UsrEmail = payload.email
this.Username = payload.username
this.save()
},
updateState (payload) {
Object.assign(this, payload)
api.defaults.headers.common['Authorization'] = `Bearer ${payload.Token}`
@@ -70,9 +99,17 @@ export const useLoginStore = defineStore('login', {
}
},
save () {
const pl = {}
Object.keys(defaultState).forEach((key) => {
pl[key] = this[key]
})
LocalStorage.set(sessionName, pl)
},
IsLoggedIn () {
return this.LoggedIn
}
},
}
})

3284
ui/yarn.lock Normal file

File diff suppressed because it is too large Load Diff