Files
bordro-esleme/ui/src/pages/tmpl.vue
2024-06-14 17:19:38 +03:00

1057 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<q-page padding>
<m-edit-header
title="Şablon Tasarımı"
:show-save-cancel="ld.isDirty"
@save="save"
@cancel="cancel"
/>
<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" class="">
<div class="row q-gutter-y-sm q-col-gutter-md">
<q-file
dense
class="col-12"
v-model="ld.xlsFileName"
@update:model-value="loadFile"
label="Bordro Dosyası"
/>
<q-select
class="col-3"
v-model="tmpl.bordroSheet"
:options="tmpl.sheets"
label="Bordro Sayfası"
dense
/>
<q-input
class="col-3"
dense
v-model.number="tmpl.baslikSatiri"
label="Başlık Satır No"
/>
<q-input
class="col-3"
dense
v-model.number="tmpl.baslikSatirAdedi"
label="Başlık Satır Adedi"
/>
<div class="col-3 flex justify-center">
<q-btn label="Dosya Analizi"
no-caps
@click="processXLS"
:disable="tmpl.bordroSheet === ''"
/>
</div>
</div>
</q-card-section>
<q-card-section v-if="ld.showFields">
<q-list bordered class="rounded-borders">
<q-expansion-item
expand-separator
label="Alan Tanımları"
switch-toggle-side
header-class="text-h6 text-pcolor1"
default-opened
>
<q-markup-table flat bordered>
<thead>
<tr>
<th>
<q-btn flat
color="positive"
dense
icon="add"
>
<q-tooltip>Yeni birleşik alan ekle</q-tooltip>
</q-btn>
<q-popup-edit
v-model="ld.combinedFields"
buttons
v-slot="scope"
@update:modelValue="addCombinedField()"
anchor="top start"
style="min-width: 350px; !important;"
persistent
>
<q-select
multiple
use-chips
v-model="scope.value"
:options="fieldOptions"
dense
autofocus
counter
@keyup.enter="scope.set"
style="min-width: 300px; !important;"
map-options
option-label="fieldName"
option-value="colNro"
/>
</q-popup-edit>
Kolon No
</th>
<th>Alan</th>
<th>Kontrol</th>
<th>Fişte Göster</th>
<th>Tip</th>
<th>B/A</th>
</tr>
</thead>
<tbody>
<tr v-for="(r, rndx) in tmpl.alanlar" :key="r.fieldName">
<td class="text-center" style="width: 70px;">
{{ r.colNro }}
<q-btn
v-if="r.colNro < 0"
icon="delete"
color="negative"
flat
size="sm"
dense
@click="delCombinedField(rndx)"
/>
</td>
<td>{{ r.fieldName }}</td>
<td class="text-center" style="width: 70px;">
<q-radio
v-model.number="tmpl.kontrolKolonu"
:val="r.colNro"
@update:model-value="ld.isDirty = true"
/>
</td>
<td class="text-center" style="width: 70px;">
<q-checkbox
v-model="r.showInSlip"
@update:model-value="ld.isDirty = true"
/>
</td>
<td>
<q-select
dense
v-model="r.colType" :options="['', 'kriter', 'veri']"
@update:model-value="ld.isDirty = true"
/>
</td>
<td>
<q-select
dense
v-model="r.ba" :options="['B', 'A']"
v-if="r.colType === 'veri'"
@update:model-value="ld.isDirty = true"
/>
</td>
</tr>
</tbody>
</q-markup-table>
</q-expansion-item>
</q-list>
</q-card-section>
<q-card-section v-if="ld.showFields">
<div class="row q-col-gutter-md q-mb-md">
<!--
<div class="col-auto">
<q-btn label="Kriterleri Doldur"
outline
color="pcolor1"
no-caps
@click="fillCriteria"
/>
</div>
-->
<div class="col text-h6 text-pcolor1">
Dağıtım Tanımları
</div>
</div>
<q-markup-table>
<thead>
<tr>
<td>
<q-btn icon="add"
outline
color="positive"
no-caps
size="sm"
@click="ld.showDagitimDlg = true"
/>
</td>
<td>Dağıtım Alanı</td>
<td>Dağıtım Kuralı</td>
</tr>
</thead>
<tbody>
<template v-for="(kv, kk) in tmpl.dagitim" :key="kk">
<template v-for="(khv, khk) in kv.kural" :key="khk">
<tr>
<td>
<q-btn icon="delete"
flat
outline
size="sm"
color="negative"
@click="delDagitim(kk, khk)"
/>
</td>
<td>
{{ kk }} : {{ khk }}
</td>
<td>
{{ kv.hedefAlan }}
<br/>
<template v-for="oran in khv" :key="oran.deger">
%{{ oran.oran }} --> {{ oran.deger }}
<br/>
</template>
</td>
</tr>
</template>
</template>
</tbody>
</q-markup-table>
</q-card-section>
<q-card-section v-if="ld.showFields">
<div class="row q-col-gutter-md q-mb-md">
<div class="col-auto">
<q-btn label="Kriterleri Doldur"
outline
color="pcolor1"
no-caps
@click="fillCriteria"
/>
</div>
<div class="col text-h6 text-pcolor1">
Kriter Hesap Atamaları
</div>
</div>
<q-list bordered class="rounded-borders">
<q-expansion-item
v-for="(v, k) in tmpl.kriterler" :key="k"
expand-separator
:label="k"
switch-toggle-side
>
<q-markup-table>
<thead>
<tr>
<td class="text-weight-bolder">
<q-btn icon="add" flat dense>
<q-tooltip>Kriter değeri ekle</q-tooltip>
<q-popup-edit
v-model="ld.mappingVal"
buttons
v-slot="scope"
anchor="top left"
persistent
@update:modelValue="addMappingKey(k)"
>
<q-input
v-model="scope.value"
dense
autofocus
@keyup.enter="scope.set"/>
</q-popup-edit>
</q-btn>
Değer
<q-btn
flat
icon="mdi-table-column-plus-after"
dense
color="positive"
>
<q-tooltip>Veri alanı ekle</q-tooltip>
<q-popup-edit
v-model="ld.selectedVal"
buttons
v-slot="scope"
@update:modelValue="addMapping(k)"
>
<q-select
v-model="scope.value"
:options="valFielOptions"
dense
autofocus
counter
@keyup.enter="scope.set"/>
</q-popup-edit>
</q-btn>
<q-btn flat icon="content_copy" dense
@click="copyMappingKeys(k)"
>
<q-tooltip>Kriter değerlerini koopyala</q-tooltip>
</q-btn>
</td>
<td v-for="f in v.valFields" :key="f">
{{ f }}
<q-btn
flat
icon="delete"
dense
color="negative"
@click="delMapping(k, f)"
/>
<q-btn flat icon="south" dense>
<q-tooltip>Girilen değeri tüm satırlara kopyala</q-tooltip>
<q-popup-edit
v-model="ld.mappingVal"
buttons
v-slot="scope"
anchor="top left"
persistent
@update:modelValue="fillMappings(k, f)"
>
<q-input
v-model="scope.value"
dense
autofocus
@keyup.enter="scope.set"/>
</q-popup-edit>
</q-btn>
</td>
</tr>
</thead>
<tbody>
<tr v-for="(kv, kk) in v.mappings" :key="kk">
<td>{{ kk }}</td>
<td v-for="f in v.valFields" :key="f">
<q-input
v-model="kv[f]"
dense
@update:modelValue="ld.isDirty=true"
:label="f"
stack-label
label-color="primary"
/>
</td>
<!--
<td>
<q-input
v-model="kv.code01"
dense
@update:modelValue="ld.isDirty=true"
/>
</td>
-->
</tr>
</tbody>
</q-markup-table>
</q-expansion-item>
</q-list>
</q-card-section>
</q-card>
</q-page>
<q-dialog
v-model="ld.showDagitimDlg"
persistent
>
<q-card style="width: 700px; max-width: 80vw;">
<q-card-section>
<div class="row q-col-gutter-md items-center">
<q-select
class="col-5"
v-model="ld.dagitim.bazAlan"
:options="fieldOptionsLabel"
option-label="fieldName"
option-value="colNro"
label="Dağıtım Bazı"
/>
<q-select
class="col-5"
v-model="ld.dagitim.hedefAlan"
:options="fieldOptionsLabel"
map-options
emit-value
label="Dağıtım Hedefi"
/>
<div class="col-2">
<q-btn
label="Kural Ekle"
outline
no-caps
@click="addDagitimKural"
/>
</div>
</div>
<div v-for="(kv, ndx) in ld.dagitim.kural" :key="ndx"
class="row q-col-gutter-md">
<div class="col-5">
<q-input
class="col-5"
label="Baz Değer"
v-model="kv.baz"
dense
>
<template v-slot:after>
<q-btn
label="Oran Ekle"
outline
size="sm"
no-caps
@click="addDagitimKuralOran(ndx)"
/>
</template>
</q-input>
</div>
<div class="col-5">
<div v-for="(hd,hndx) in kv.hedef" :key="hndx"
class="row q-col-gutter-sm"
>
<q-input
class="col"
label="Hedef Değer"
v-model="hd.deger"
dense
/>
<q-input
class="col"
label="Oran"
v-model.number="hd.oran"
type="number"
dense
/>
</div>
</div>
</div>
</q-card-section>
<q-card-actions>
<q-btn
color="positive"
flat label="Tamam"
v-close-popup
no-caps
@click="addDagitim"
/>
<q-btn
color="negative"
flat label="İptal" v-close-popup no-caps
@click="cancelDagitim"
/>
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script setup>
import { computed, onMounted, reactive, toRaw } from 'vue'
import { useI18n } from 'vue-i18n'
import { api } from 'boot/axios'
import { catchAxiosError, showAxiosError, showError, showSuccess } from 'src/libjs/lib/axios'
import MEditHeader from 'src/libjs/comp/MEditHeader.vue'
import { read, utils } from 'xlsx'
import { copyToClipboard, useQuasar } from 'quasar'
const { t } = useI18n()
const $q = useQuasar()
const ld = reactive({
companyID: null,
companies: [],
isDirty: false,
xlsFileName: null,
selectedVal: '',
alreadySelectedValFields: {},
showFields: false,
combinedFields: [],
showDagitimDlg: false,
dagitim: {
bazAlan: '',
hedefAlan: '',
kural: [
//{baz: xxx, hedef: [{deger: yyy, oran: zzz}]}
],
},
mappingVal: '',
})
const tmpl = reactive({
rawData: null,
sheets: [],
bordroSheet: '',
baslikSatiri: 7,
baslikSatirAdedi: 2,
kontrolKolonu: -1,
alanlar: [],
kriterler: {},
dagitim: {
/*
'bazAlan': {
bazAlanColNro: 1
hedefAlanColNro: 2
hedefAlan: '',
kural: {
'bazDeger': [{oran: 20, deger: aaaa}}
}
}
*/
},
})
let workbook = null
onMounted(() => {
getCompanyList()
})
const valFielOptions = computed(() => {
const ls = tmpl.alanlar.filter(f => {return f.colType === 'veri'})
const opts = []
ls.forEach(f => {
if (!ld.alreadySelectedValFields.hasOwnProperty(f.fieldName)) {
opts.push(f.fieldName)
}
})
return opts
})
const fieldOptions = computed(() => {
const ls = tmpl.alanlar.filter(f => {return f.colNro >= 0})
const opts = []
ls.forEach(f => {
opts.push({ colNro: f.colNro, fieldName: f.fieldName })
})
return opts
})
const fieldOptionsLabel = computed(() => {
const ls = tmpl.alanlar.filter(f => {return f.colNro >= 0})
const opts = []
ls.forEach(f => {
opts.push(f.fieldName)
})
return opts
})
const getFieldColNumber = function (fieldName) {
const tmpIndex = tmpl.alanlar.findIndex(f => {return f.fieldName === fieldName})
if (tmpIndex >= 0) {
return tmpl.alanlar[tmpIndex].colNro
} else {
return -1
}
}
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 save = function () {
$q.loading.show()
api.post(`/tmpl/${ld.companyID}`, toRaw(tmpl)).then(res => {
if (res.data.Success) {
ld.isDirty = false
} else {
showAxiosError()
}
}).catch(err => {
catchAxiosError(err)
}).finally(() => {
$q.loading.hide()
})
}
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.showFields = false
return
}
ld.isDirty = false
ld.showFields = 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()
})
}
const loadFile = function () {
const reader = new FileReader()
reader.onload = function (e) {
const uin = new Uint8Array(e.target.result)
readXLS(uin)
}
reader.readAsArrayBuffer(ld.xlsFileName)
}
const readXLS = function (inb) {
tmpl.sheets.splice(0)
tmpl.bordroSheet = ''
workbook = read(inb)
tmpl.sheets.push(...workbook.SheetNames)
}
const processXLS = function () {
const sheet = workbook.Sheets[tmpl.bordroSheet]
tmpl.rawData = utils.sheet_to_json(sheet, { header: 1 })
/*
tmpl.alanlar.splice(0)
*/
const fieldNames = []
let lastCol = -1
tmpl.rawData[tmpl.baslikSatiri - 1].forEach((val, ndx) => {
//başlık olmayan kolonlar
if (ndx - lastCol > 1) {
const msg = 'Başlıksız kolonlar var. Dosya işlenemez'
showError(msg)
throw msg
}
let name = val
for (let j = 1; j < tmpl.baslikSatirAdedi; j++) {
let addiVal = tmpl.rawData[tmpl.baslikSatiri - 1 + j][ndx]
if (addiVal) {
name = name + ' ' + addiVal
}
}
name = name.trim()
fieldNames.push(name)
const alanIndex = tmpl.alanlar.findIndex(f => {return f.fieldName === name})
if (alanIndex < 0) {
tmpl.alanlar.push({
colNro: ndx,
fieldName: name,
colType: '',
ba: 'B',
showInSlip: false,
combinedFields: [],
combinedFieldsNro: [],
formula: '',
})
} else {
if (tmpl.alanlar[alanIndex].colNro !== ndx) {
const oldColNro = tmpl.alanlar[alanIndex].colNro
tmpl.alanlar[alanIndex].colNro = ndx
//kriter kolon no'ları güncelleyelim
Object.keys(tmpl.kriterler).forEach(kr => {
if (tmpl.kriterler[kr].colNro === oldColNro) {
tmpl.kriterler[kr].colNro = ndx
}
for (let j = 0; j < tmpl.kriterler[kr].combinedFieldsNro.length; j++) {
if (tmpl.kriterler[kr].combinedFieldsNro[j] === oldColNro) {
tmpl.kriterler[kr].combinedFieldsNro[j] = ndx
}
}
})
// Dağtım kolon no'ları güncelleyelim
Object.keys(tmpl.dagitim).forEach(dk => {
if (tmpl.dagitim[dk].bazAlanColNro === oldColNro) {
tmpl.dagitim[dk].bazAlanColNro = ndx
}
if (tmpl.dagitim[dk].hedefAlanColNro === oldColNro) {
tmpl.dagitim[dk].hedefAlanColNro = ndx
}
})
// Birleşik kriterleri güncelleyelim
tmpl.alanlar.forEach(af => {
if (af.colNro < 0) {
for (let j = 0; j < af.combinedFieldsNro.length; j++) {
if (af.combinedFieldsNro[j] === oldColNro) {
af.combinedFieldsNro[j] = ndx
}
}
}
})
}
}
lastCol = ndx
})
// Sıralayalım
tmpl.alanlar.sort((a, b) => {
if (a.colNro < b.colNro) {
return -1
} else {
return 1
}
})
// excelden silinen alanları uçuralım
tmpl.alanlar.forEach((v, n) => {
const tmpIndex = fieldNames.findIndex(el => {return v.fieldName === el})
if (tmpIndex < 0) {
console.log(v.fieldName)
tmpl.alanlar.splice(n, 1)
}
})
ld.showFields = true
}
const fillCriteria = function () {
ld.isDirty = true
// kriterleri temizleyelim
/*
Object.keys(ld.alreadySelectedValFields).forEach(k => {
delete ld.alreadySelectedValFields[k]
})
tmpl.kriterler = {}
*/
// kriter alanlarını bulup boş kriter nesnelerini oluşturalım
const kriterCols = []
tmpl.alanlar.filter(f => {return f.colType === 'kriter'}).forEach(f => {
if (!tmpl.kriterler.hasOwnProperty(f.fieldName)) {
tmpl.kriterler[f.fieldName] = {
colNro: f.colNro,
combinedFields: [...(f.combinedFields || [])],
combinedFieldsNro: [...(f.combinedFieldsNro || [])],
valFields: [],
/*
[
{fieldName: 'xxx', colNro: 3}
{fieldName: 'yyy', colNro: 4}
]
*/
mappings: {},
/*
mapping['kriter1'] = {xxx: 770.01.001, yyy: 770.01.002}
*/
}
}
kriterCols.push(f.fieldName)
})
// kriter olmayan kolonları silelim
Object.keys(tmpl.kriterler).forEach(k => {
if (kriterCols.indexOf(k) < 0) {
delete tmpl.kriterler[k]
}
})
tmpl.rawData.forEach((row, ndx) => {
if (ndx <= tmpl.baslikSatiri - 1 + tmpl.baslikSatirAdedi) {
} else {
const kval = row[tmpl.kontrolKolonu]
if ((kval !== null) && (kval !== undefined) && (kval !== '')) {
Object.keys(tmpl.kriterler).forEach(k => {
let kriterVal = ''
if (tmpl.kriterler[k].colNro >= 0) {
// sabit kriter alanı
kriterVal = row[tmpl.kriterler[k].colNro]
} else {
// birleştirilmiş / combine kriter alanı
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()
}
if ((kriterVal !== null) && (kriterVal !== undefined) && (kriterVal !== '')) {
if (!tmpl.kriterler[k].mappings.hasOwnProperty(kriterVal)) {
tmpl.kriterler[k].mappings[kriterVal] = {}
tmpl.kriterler[k].valFields.forEach(f => {
tmpl.kriterler[k].mappings[ld.mappingVal][f] = ''
})
}
}
})
}
}
})
}
const addMapping = function (k) {
ld.isDirty = true
ld.alreadySelectedValFields[ld.selectedVal] = ''
tmpl.kriterler[k].valFields.push(ld.selectedVal)
Object.keys(tmpl.kriterler[k].mappings).forEach(vk => {
tmpl.kriterler[k].mappings[vk][ld.selectedVal] = ''
})
ld.selectedVal = ''
}
const delMapping = function (k, valField) {
ld.isDirty = true
delete ld.alreadySelectedValFields[valField]
const index = tmpl.kriterler[k].valFields.indexOf(valField)
if (index !== -1) {
tmpl.kriterler[k].valFields.splice(index, 1)
}
Object.keys(tmpl.kriterler[k].mappings).forEach(vk => {
delete tmpl.kriterler[k].mappings[vk][ld.selectedVal]
})
}
const fillMappings = function (k, valField) {
ld.isDirty = true
Object.keys(tmpl.kriterler[k].mappings).forEach(vk => {
tmpl.kriterler[k].mappings[vk][valField] = ld.mappingVal
})
ld.mappingVal = ''
}
const addMappingKey = function (k) {
ld.isDirty = true
tmpl.kriterler[k].mappings[ld.mappingVal] = {}
tmpl.kriterler[k].valFields.forEach(f => {
tmpl.kriterler[k].mappings[ld.mappingVal][f] = ''
})
ld.mappingVal = ''
}
const copyMappingKeys = function (k) {
let text = ''
Object.keys(tmpl.kriterler[k].mappings).forEach(vk => {
text += vk + '\n'
})
copyToClipboard(text).then(() => {
showSuccess('Kriter değerleri kopyalandı')
}).catch(() => {
alert('kopyalamada hata')
})
}
const fillMappingsFromExcel = function (k, valField) {
ld.isDirty = true
Object.keys(tmpl.kriterler[k].mappings).forEach(vk => {
tmpl.kriterler[k].mappings[vk][valField] = ld.mappingVal
})
ld.mappingVal = ''
}
const addCombinedField = function () {
ld.isDirty = true
const combinedFields = []
const combinedFieldsNro = []
ld.combinedFields.forEach(f => {
combinedFields.push(f.fieldName)
combinedFieldsNro.push(f.colNro)
})
const fName = combinedFields.join(' : ')
tmpl.alanlar.push({
colNro: -1,
fieldName: fName,
colType: '',
ba: 'B',
showInSlip: false,
combinedFields: [...combinedFields],
combinedFieldsNro: [...combinedFieldsNro],
formula: '',
})
ld.combinedFields.splice(0)
}
const delCombinedField = function (ndx) {
ld.isDirty = true
tmpl.alanlar.splice(ndx, 1)
}
const addDagitimKural = function () {
ld.dagitim.kural.push({
baz: '', hedef: [],
})
}
const addDagitimKuralOran = function (ndx) {
ld.dagitim.kural[ndx].hedef.push({ deger: null, oran: 0 })
}
const addDagitim = function () {
ld.isDirty = true
const hedefAlan = toRaw(ld.dagitim.hedefAlan)
const bazAlan = toRaw(ld.dagitim.bazAlan)
tmpl.dagitim[bazAlan] = {
bazAlanColNro: getFieldColNumber(bazAlan),
hedefAlanColNro: getFieldColNumber(hedefAlan),
hedefAlan: hedefAlan,
kural: {},
}
ld.dagitim.kural.forEach(k => {
const bazDeger = toRaw(k.baz)
tmpl.dagitim[bazAlan].kural[bazDeger] = []
k.hedef.forEach(h => {
tmpl.dagitim[bazAlan].kural[bazDeger].push({ oran: h.oran, deger: h.deger })
})
})
cancelDagitim()
}
const cancelDagitim = function () {
ld.dagitim.bazAlan = ''
ld.dagitim.hedefAlan = ''
ld.dagitim.kural.splice(0)
}
const delDagitim = function (bazAlan, bazDeger) {
ld.isDirty = true
delete tmpl.dagitim[bazAlan].kural[bazDeger]
if (Object.keys(tmpl.dagitim[bazAlan].kural).length === 0) {
delete tmpl.dagitim[bazAlan]
}
}
</script>
<style scoped lang="scss">
</style>