- Updated CustomersPage.vue to use decimalString for standard discount percent. - Enhanced IncomingInvoicesPage.vue to format item quantities, unit prices, and tax rates using decimalString. - Improved ItemsPage.vue with new supplier price management and decimal formatting for prices. - Modified OrganizationSetupPage.vue to use a dropdown for default tax rates and ensure numeric input for payment days. - Updated OutgoingInvoicesPage.vue to apply decimal formatting for customer discounts and item details. - Enhanced PriceImportsPage.vue to include additional fields in the import format. - Improved PriceRulesPage.vue to use decimal input for markup percentages. - Updated QuotesPage.vue to apply decimal formatting for customer discounts and item details. - Enhanced SuppliersPage.vue to use decimal input for standard discount percent. - Added a new SQL migration to set default unit for items to 'Stck'. - Introduced format.ts for centralized decimal and currency formatting utilities.
104 lines
4.3 KiB
Vue
104 lines
4.3 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, reactive, ref } from "vue";
|
|
import { apiDelete, apiGet, apiPost, apiPut } from "../api";
|
|
import FormStatus from "../components/FormStatus.vue";
|
|
import PageHeader from "../components/PageHeader.vue";
|
|
import { decimalString } from "../format";
|
|
import type { PriceRule } from "../types";
|
|
|
|
const rules = ref<PriceRule[]>([]);
|
|
const selectedId = ref<string | null>(null);
|
|
const form = reactive({
|
|
code: "",
|
|
name: "",
|
|
source_type: "import" as PriceRule["source_type"],
|
|
source_id: "",
|
|
markup_percent: "0.00",
|
|
rounding_mode: "none" as PriceRule["rounding_mode"],
|
|
is_active: true
|
|
});
|
|
const status = ref("");
|
|
const kind = ref<"info" | "success" | "error">("info");
|
|
|
|
function payload() {
|
|
return {
|
|
...form,
|
|
source_id: form.source_id.trim() || null,
|
|
markup_percent: decimalString(form.markup_percent)
|
|
};
|
|
}
|
|
|
|
function createNew() {
|
|
selectedId.value = null;
|
|
Object.assign(form, {
|
|
code: "",
|
|
name: "",
|
|
source_type: "import",
|
|
source_id: "",
|
|
markup_percent: "0.00",
|
|
rounding_mode: "none",
|
|
is_active: true
|
|
});
|
|
}
|
|
|
|
function select(rule: PriceRule) {
|
|
selectedId.value = rule.id;
|
|
Object.assign(form, { ...rule, source_id: rule.source_id ?? "" });
|
|
}
|
|
|
|
async function load() {
|
|
const result = await apiGet<PriceRule[]>("/api/v1/price-rules");
|
|
if (result.ok) rules.value = result.data;
|
|
else { status.value = result.message; kind.value = "error"; }
|
|
}
|
|
|
|
async function save() {
|
|
const result = selectedId.value
|
|
? await apiPut<PriceRule>(`/api/v1/price-rules/${selectedId.value}`, payload())
|
|
: await apiPost<PriceRule>("/api/v1/price-rules", payload());
|
|
status.value = result.ok ? "Preisregel gespeichert." : result.message;
|
|
kind.value = result.ok ? "success" : "error";
|
|
if (result.ok) { selectedId.value = result.data.id; await load(); }
|
|
}
|
|
|
|
async function deactivate() {
|
|
if (!selectedId.value) return;
|
|
const result = await apiDelete(`/api/v1/price-rules/${selectedId.value}`);
|
|
status.value = result.ok ? "Preisregel deaktiviert." : result.message;
|
|
kind.value = result.ok ? "success" : "error";
|
|
if (result.ok) await load();
|
|
}
|
|
|
|
onMounted(load);
|
|
</script>
|
|
|
|
<template>
|
|
<PageHeader title="Preisregeln" description="Aufschläge und Rundung je Preisquelle festlegen." />
|
|
<div class="workspace-split">
|
|
<section class="panel list-panel">
|
|
<div class="section-title"><h2>Regeln</h2><button type="button" @click="createNew">Neu</button></div>
|
|
<button v-for="rule in rules" :key="rule.id" type="button" class="list-row" :class="{ selected: selectedId === rule.id }" @click="select(rule)">
|
|
<strong>{{ rule.code }}</strong>
|
|
<span>{{ rule.name }}</span>
|
|
<small>{{ rule.source_type }} · {{ rule.markup_percent }} %</small>
|
|
</button>
|
|
<p v-if="rules.length === 0" class="empty">Keine Preisregeln vorhanden.</p>
|
|
</section>
|
|
<section class="panel detail-panel">
|
|
<form @submit.prevent="save">
|
|
<div class="form-grid">
|
|
<label class="field"><span>Code</span><input v-model="form.code" required /></label>
|
|
<label class="field"><span>Name</span><input v-model="form.name" required /></label>
|
|
<label class="field"><span>Quellentyp</span><select v-model="form.source_type"><option value="import">Import</option><option value="api">API</option><option value="supplier">Lieferant</option></select></label>
|
|
<label class="field"><span>Quell-ID</span><input v-model="form.source_id" placeholder="optional" /></label>
|
|
<label class="field"><span>Aufschlag %</span><input v-model="form.markup_percent" inputmode="decimal" required /></label>
|
|
<label class="field"><span>Rundung</span><select v-model="form.rounding_mode"><option value="none">Keine</option><option value="cent">Cent</option><option value="five_cent">5 Cent</option><option value="ten_cent">10 Cent</option><option value="whole">Ganze Beträge</option></select></label>
|
|
<label class="check-row"><input v-model="form.is_active" type="checkbox" /><span>Aktiv</span></label>
|
|
</div>
|
|
<div class="form-actions"><button type="submit">Speichern</button><button v-if="selectedId" type="button" class="secondary" @click="deactivate">Deaktivieren</button></div>
|
|
<FormStatus :message="status" :kind="kind" />
|
|
</form>
|
|
</section>
|
|
</div>
|
|
</template>
|