feat: add api-platform and symfony/uid dependencies; introduce billing module service configuration
- Added api-platform/symfony version 4.1 with necessary configuration files. - Included symfony/uid version 7.1 with its recipe details. - Created billing_module.yaml to define the BillingModulePlugin service with autowiring and autoconfiguration. - Added SoftwareBuddy agent for web development support in PHP (Symfony) and JavaScript (Vue.js).
This commit is contained in:
parent
6b5e82cd2e
commit
5ffd7bd0d1
5
.github/agents/SoftwareBuddy.agent.md
vendored
Normal file
5
.github/agents/SoftwareBuddy.agent.md
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
description: 'Du bist mein Software-Sparringspartner, der mich in der Webentwicklung unterstützt.'
|
||||||
|
tools: ['edit', 'runNotebooks', 'search', 'new', 'runCommands', 'runTasks', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'extensions', 'todos', 'runSubagent', 'runTests']
|
||||||
|
---
|
||||||
|
Bei Fragen zur Webentwicklung, insbesondere im Bereich PHP (Symfony) und JavaScript (Vue.js), agierst du als mein Software-Sparringspartner. Du hilfst mir, komplexe Probleme zu lösen, indem du fundierte Ratschläge gibst, Best Practices empfiehlst und mich durch schwierige Debugging-Situationen führst. Du bist stets darauf bedacht, sauberen, wartbaren und effizienten Code zu fördern. Du kennst dich gut mit modernen Entwicklungswerkzeugen und -prozessen aus, einschließlich Versionskontrolle (Git), Paketverwaltung (Composer, npm), Testing-Frameworks und CI/CD-Pipelines. Du unterstützt mich dabei, meine Fähigkeiten zu verbessern, indem du konstruktives Feedback zu meinem Code gibst und mich ermutigst, neue Technologien und Methoden auszuprobieren. Du übernimmst auch die Rolle des Entwicklers und schreibst sauberen, gut dokumentierten Code, wenn ich dich darum bitte.
|
||||||
@ -8,7 +8,12 @@
|
|||||||
<!-- Rechnungsnummer -->
|
<!-- Rechnungsnummer -->
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<label for="invoiceNumber">Rechnungsnummer *</label>
|
<label for="invoiceNumber">Rechnungsnummer *</label>
|
||||||
<InputText id="invoiceNumber" v-model="form.invoiceNumber" required />
|
<InputText
|
||||||
|
id="invoiceNumber"
|
||||||
|
v-model="form.invoiceNumber"
|
||||||
|
:class="{ 'p-invalid': submitted && !form.invoiceNumber }"
|
||||||
|
/>
|
||||||
|
<small v-if="submitted && !form.invoiceNumber" class="p-error">Rechnungsnummer ist erforderlich</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
@ -30,11 +35,13 @@
|
|||||||
id="contact"
|
id="contact"
|
||||||
v-model="form.contactId"
|
v-model="form.contactId"
|
||||||
:options="contacts"
|
:options="contacts"
|
||||||
option-label="name"
|
option-label="companyName"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
filter
|
filter
|
||||||
placeholder="Kunde auswählen"
|
placeholder="Kunde auswählen"
|
||||||
|
:class="{ 'p-invalid': submitted && !form.contactId }"
|
||||||
/>
|
/>
|
||||||
|
<small v-if="submitted && !form.contactId" class="p-error">Bitte wählen Sie einen Kunden aus</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rechnungsdatum -->
|
<!-- Rechnungsdatum -->
|
||||||
@ -92,14 +99,15 @@
|
|||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div class="flex gap-2 mt-4">
|
<div class="flex gap-2 mt-4">
|
||||||
<Button label="Speichern" icon="pi pi-check" @click="save" />
|
<Button label="Speichern" icon="pi pi-check" @click="save" :loading="saving" />
|
||||||
<Button label="Abbrechen" icon="pi pi-times" severity="secondary" text @click="cancel" />
|
<Button label="Abbrechen" icon="pi pi-times" severity="secondary" text @click="cancel" :disabled="saving" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useToast } from 'primevue/usetoast'
|
||||||
import InputText from 'primevue/inputtext'
|
import InputText from 'primevue/inputtext'
|
||||||
import Dropdown from 'primevue/dropdown'
|
import Dropdown from 'primevue/dropdown'
|
||||||
import Calendar from 'primevue/calendar'
|
import Calendar from 'primevue/calendar'
|
||||||
@ -116,6 +124,10 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['save', 'cancel'])
|
const emit = defineEmits(['save', 'cancel'])
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const submitted = ref(false)
|
||||||
|
const saving = ref(false)
|
||||||
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
invoiceNumber: '',
|
invoiceNumber: '',
|
||||||
status: 'draft',
|
status: 'draft',
|
||||||
@ -141,13 +153,48 @@ const taxRates = ['0.00', '7.00', '19.00']
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// Load contacts
|
// Load contacts
|
||||||
const response = await fetch('/api/contacts')
|
try {
|
||||||
const data = await response.json()
|
const response = await fetch('/api/contacts?itemsPerPage=1000', {
|
||||||
contacts.value = data['hydra:member'] || []
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/ld+json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Fehler beim Laden der Kontakte')
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
// API Platform kann 'hydra:member' oder 'member' verwenden
|
||||||
|
contacts.value = data['hydra:member'] || data.member || []
|
||||||
|
|
||||||
|
console.log('Loaded contacts:', contacts.value.length)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading contacts:', error)
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Fehler',
|
||||||
|
detail: 'Kontakte konnten nicht geladen werden',
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Load existing invoice data
|
// Load existing invoice data
|
||||||
if (props.invoice) {
|
if (props.invoice) {
|
||||||
form.value = { ...props.invoice }
|
form.value = { ...props.invoice }
|
||||||
|
|
||||||
|
// Extract contact ID from IRI if needed
|
||||||
|
if (props.invoice.contact) {
|
||||||
|
if (typeof props.invoice.contact === 'string') {
|
||||||
|
// Extract ID from IRI like "/api/contacts/123"
|
||||||
|
const matches = props.invoice.contact.match(/\/api\/contacts\/(\d+)/)
|
||||||
|
form.value.contactId = matches ? parseInt(matches[1]) : null
|
||||||
|
} else if (props.invoice.contact.id) {
|
||||||
|
form.value.contactId = props.invoice.contact.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (props.invoice.invoiceDate) {
|
if (props.invoice.invoiceDate) {
|
||||||
form.value.invoiceDate = new Date(props.invoice.invoiceDate)
|
form.value.invoiceDate = new Date(props.invoice.invoiceDate)
|
||||||
}
|
}
|
||||||
@ -171,26 +218,87 @@ const removeItem = (index) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const save = async () => {
|
const save = async () => {
|
||||||
const method = props.invoice ? 'PUT' : 'POST'
|
submitted.value = true
|
||||||
const url = props.invoice ? `/api/invoices/${props.invoice.id}` : '/api/invoices'
|
|
||||||
|
|
||||||
const payload = {
|
// Validation
|
||||||
invoiceNumber: form.value.invoiceNumber,
|
if (!form.value.invoiceNumber || !form.value.contactId) {
|
||||||
contact: `/api/contacts/${form.value.contactId}`,
|
toast.add({
|
||||||
status: form.value.status,
|
severity: 'warn',
|
||||||
invoiceDate: form.value.invoiceDate.toISOString().split('T')[0],
|
summary: 'Validierung fehlgeschlagen',
|
||||||
dueDate: form.value.dueDate.toISOString().split('T')[0],
|
detail: 'Bitte füllen Sie alle Pflichtfelder aus',
|
||||||
notes: form.value.notes,
|
life: 3000
|
||||||
items: form.value.items
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetch(url, {
|
if (form.value.items.length === 0) {
|
||||||
method,
|
toast.add({
|
||||||
headers: { 'Content-Type': 'application/json' },
|
severity: 'warn',
|
||||||
body: JSON.stringify(payload)
|
summary: 'Validierung fehlgeschlagen',
|
||||||
})
|
detail: 'Bitte fügen Sie mindestens eine Position hinzu',
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
emit('save', form.value)
|
saving.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const method = props.invoice ? 'PUT' : 'POST'
|
||||||
|
const url = props.invoice ? `/api/invoices/${props.invoice.id}` : '/api/invoices'
|
||||||
|
|
||||||
|
// Konvertiere alle numerischen Werte in Items zu Strings (für DECIMAL Felder)
|
||||||
|
const items = form.value.items.map(item => ({
|
||||||
|
description: item.description,
|
||||||
|
quantity: String(item.quantity),
|
||||||
|
unitPrice: String(item.unitPrice),
|
||||||
|
taxRate: String(item.taxRate)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
invoiceNumber: form.value.invoiceNumber,
|
||||||
|
contact: `/api/contacts/${form.value.contactId}`,
|
||||||
|
status: form.value.status,
|
||||||
|
invoiceDate: form.value.invoiceDate.toISOString().split('T')[0],
|
||||||
|
dueDate: form.value.dueDate.toISOString().split('T')[0],
|
||||||
|
notes: form.value.notes,
|
||||||
|
items: items
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/ld+json',
|
||||||
|
'Accept': 'application/ld+json'
|
||||||
|
},
|
||||||
|
credentials: 'include',
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json()
|
||||||
|
throw new Error(error['hydra:description'] || error.message || 'Fehler beim Speichern')
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Erfolg',
|
||||||
|
detail: `Rechnung wurde ${props.invoice ? 'aktualisiert' : 'erstellt'}`,
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
|
|
||||||
|
emit('save', form.value)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving invoice:', error)
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Fehler',
|
||||||
|
detail: error.message || 'Rechnung konnte nicht gespeichert werden',
|
||||||
|
life: 5000
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
|
|||||||
@ -4,10 +4,12 @@
|
|||||||
|
|
||||||
<CrudDataTable
|
<CrudDataTable
|
||||||
title="Rechnungen"
|
title="Rechnungen"
|
||||||
:api-endpoint="`/api/invoices`"
|
entity-name="Rechnung"
|
||||||
|
entity-name-article="eine"
|
||||||
|
data-source="/api/invoices"
|
||||||
|
storage-key="invoiceTableColumns"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:show-create-button="canCreate"
|
:show-create-button="canCreate"
|
||||||
create-button-label="Neue Rechnung"
|
|
||||||
@create="openCreateDialog"
|
@create="openCreateDialog"
|
||||||
@edit="openEditDialog"
|
@edit="openEditDialog"
|
||||||
@delete="deleteInvoice"
|
@delete="deleteInvoice"
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
"doctrine/orm": "^3.5",
|
"doctrine/orm": "^3.5",
|
||||||
"knpuniversity/oauth2-client-bundle": "*",
|
"knpuniversity/oauth2-client-bundle": "*",
|
||||||
"league/oauth2-client": "*",
|
"league/oauth2-client": "*",
|
||||||
"mycrm/billing-module": "@dev",
|
"mycrm/billing-module": "^1.0",
|
||||||
"mycrm/test-module": "*",
|
"mycrm/test-module": "*",
|
||||||
"nelmio/cors-bundle": "^2.6",
|
"nelmio/cors-bundle": "^2.6",
|
||||||
"phpdocumentor/reflection-docblock": "^5.6",
|
"phpdocumentor/reflection-docblock": "^5.6",
|
||||||
@ -85,8 +85,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"auto-scripts": {
|
"auto-scripts": {
|
||||||
"cache:clear": "symfony-cmd",
|
"cache:clear": "symfony-cmd",
|
||||||
"assets:install %PUBLIC_DIR%": "symfony-cmd",
|
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||||
"importmap:install": "symfony-cmd"
|
|
||||||
},
|
},
|
||||||
"post-install-cmd": [
|
"post-install-cmd": [
|
||||||
"@auto-scripts"
|
"@auto-scripts"
|
||||||
@ -121,8 +120,8 @@
|
|||||||
"url": "https://git.osdata-home.de/mycrm/mycrm-test-module"
|
"url": "https://git.osdata-home.de/mycrm/mycrm-test-module"
|
||||||
},
|
},
|
||||||
"mycrm-billing-module": {
|
"mycrm-billing-module": {
|
||||||
"type": "path",
|
"type": "vcs",
|
||||||
"url": "../mycrm-billing-module"
|
"url": "https://git.osdata-home.de/mycrm/mycrm-billing-module"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1830
composer.lock
generated
1830
composer.lock
generated
File diff suppressed because it is too large
Load Diff
6
config/packages/billing_module.yaml
Normal file
6
config/packages/billing_module.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
services:
|
||||||
|
MyCRM\BillingModule\BillingModulePlugin:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
tags:
|
||||||
|
- { name: 'app.module_plugin' }
|
||||||
23
symfony.lock
23
symfony.lock
@ -13,6 +13,20 @@
|
|||||||
"src/ApiResource/.gitignore"
|
"src/ApiResource/.gitignore"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"api-platform/symfony": {
|
||||||
|
"version": "4.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "4.0",
|
||||||
|
"ref": "e9952e9f393c2d048f10a78f272cd35e807d972b"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"config/packages/api_platform.yaml",
|
||||||
|
"config/routes/api_platform.yaml",
|
||||||
|
"src/ApiResource/.gitignore"
|
||||||
|
]
|
||||||
|
},
|
||||||
"doctrine/deprecations": {
|
"doctrine/deprecations": {
|
||||||
"version": "1.1",
|
"version": "1.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
@ -300,6 +314,15 @@
|
|||||||
"templates/base.html.twig"
|
"templates/base.html.twig"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"symfony/uid": {
|
||||||
|
"version": "7.1",
|
||||||
|
"recipe": {
|
||||||
|
"repo": "github.com/symfony/recipes",
|
||||||
|
"branch": "main",
|
||||||
|
"version": "7.0",
|
||||||
|
"ref": "0df5844274d871b37fc3816c57a768ffc60a43a5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"symfony/ux-turbo": {
|
"symfony/ux-turbo": {
|
||||||
"version": "2.31",
|
"version": "2.31",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user