Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files
main.js
CHANGED
|
@@ -1190,7 +1190,6 @@ function collectInvoicePayload() {
|
|
| 1190 |
name,
|
| 1191 |
quantity,
|
| 1192 |
unit,
|
| 1193 |
-
unitPrice: unitGross.toFixed(2),
|
| 1194 |
unit_price_gross: unitGross.toFixed(2),
|
| 1195 |
vat_code: vatCode,
|
| 1196 |
});
|
|
|
|
| 1190 |
name,
|
| 1191 |
quantity,
|
| 1192 |
unit,
|
|
|
|
| 1193 |
unit_price_gross: unitGross.toFixed(2),
|
| 1194 |
vat_code: vatCode,
|
| 1195 |
});
|
server.py
CHANGED
|
@@ -12,16 +12,17 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
| 12 |
|
| 13 |
from flask import Flask, jsonify, request, send_from_directory
|
| 14 |
|
| 15 |
-
from db import (
|
| 16 |
-
create_account,
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
|
|
|
| 25 |
|
| 26 |
APP_ROOT = Path(__file__).parent.resolve()
|
| 27 |
DATA_DIR = Path(os.environ.get("DATA_DIR", APP_ROOT))
|
|
@@ -66,15 +67,26 @@ def _quantize(value: Decimal) -> Decimal:
|
|
| 66 |
return value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
| 67 |
|
| 68 |
|
| 69 |
-
def _decimal(value: Any) -> Decimal:
|
| 70 |
-
try:
|
| 71 |
-
return Decimal(str(value))
|
| 72 |
-
except Exception as error: # pragma: no cover - defensive
|
| 73 |
-
raise ValueError(f"Niepoprawna wartosc liczby: {value}") from error
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
def
|
| 77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
|
| 80 |
EMAIL_PATTERN = re.compile(r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$")
|
|
@@ -139,11 +151,23 @@ def get_account(data: Dict[str, Any], login_key: str) -> Dict[str, Any]:
|
|
| 139 |
return account
|
| 140 |
|
| 141 |
|
| 142 |
-
def get_account_row(login_key: str) -> Dict[str, Any]:
|
| 143 |
-
row = fetch_one("SELECT id, login FROM accounts WHERE login = %s", (login_key,))
|
| 144 |
-
if not row:
|
| 145 |
-
raise KeyError("Nie znaleziono konta.")
|
| 146 |
-
return row
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
|
| 148 |
|
| 149 |
def require_auth() -> str:
|
|
@@ -441,44 +465,48 @@ def normalize_phone(phone: Optional[str]) -> Optional[str]:
|
|
| 441 |
return digits or None
|
| 442 |
|
| 443 |
|
| 444 |
-
def validate_client(payload: Dict[str, Any]) -> Dict[str, str]:
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
"
|
| 448 |
-
"
|
| 449 |
-
"
|
| 450 |
-
"
|
| 451 |
-
"
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
|
|
|
| 467 |
if not name:
|
| 468 |
raise ValueError("Nazwa pozycji nie moze byc pusta.")
|
| 469 |
quantity = _quantize(_decimal(item.get("quantity") or "0"))
|
| 470 |
if quantity <= Decimal("0"):
|
| 471 |
raise ValueError("Ilosc musi byc dodatnia.")
|
| 472 |
unit = item.get("unit") or DEFAULT_UNIT
|
| 473 |
-
vat_code = str(item.get("vat") or "23")
|
| 474 |
-
if vat_code not in VAT_RATES:
|
| 475 |
-
raise ValueError("Niepoprawna stawka VAT.")
|
| 476 |
-
|
| 477 |
-
if
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
if
|
| 481 |
-
|
|
|
|
|
|
|
|
|
|
| 482 |
vat_amount = Decimal("0.00")
|
| 483 |
else:
|
| 484 |
unit_price_net = _quantize(unit_price_gross / (Decimal("1.0") + vat_rate))
|
|
@@ -524,7 +552,7 @@ def build_invoice(payload: Dict[str, Any], business: Dict[str, Any], client: Dic
|
|
| 524 |
for label, values in summary.items()
|
| 525 |
]
|
| 526 |
|
| 527 |
-
exemption_note = payload.get("exemptionNote"
|
| 528 |
|
| 529 |
return {
|
| 530 |
"invoice_id": invoice_id,
|
|
@@ -553,20 +581,127 @@ def api_invoices() -> Any:
|
|
| 553 |
account_row = get_account_row(login_key)
|
| 554 |
except KeyError:
|
| 555 |
return jsonify({"error": "Nie znaleziono konta."}), 404
|
| 556 |
-
|
| 557 |
"""
|
| 558 |
-
SELECT
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 565 |
LIMIT %s
|
| 566 |
""",
|
| 567 |
(account_row["id"], INVOICE_HISTORY_LIMIT),
|
| 568 |
)
|
| 569 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
|
| 571 |
data = load_store()
|
| 572 |
try:
|
|
@@ -583,15 +718,7 @@ def api_invoices() -> Any:
|
|
| 583 |
account_row = get_account_row(login_key)
|
| 584 |
except KeyError:
|
| 585 |
return jsonify({"error": "Nie znaleziono konta."}), 404
|
| 586 |
-
business =
|
| 587 |
-
"""
|
| 588 |
-
SELECT company_name, owner_name, address_line, postal_code,
|
| 589 |
-
city, tax_id, bank_account
|
| 590 |
-
FROM business_profiles
|
| 591 |
-
WHERE account_id = %s
|
| 592 |
-
""",
|
| 593 |
-
(account_row["id"],),
|
| 594 |
-
)
|
| 595 |
if not business:
|
| 596 |
return jsonify({"error": "Ustaw dane sprzedawcy przed dodaniem faktury."}), 400
|
| 597 |
|
|
@@ -636,10 +763,115 @@ def api_invoices() -> Any:
|
|
| 636 |
account["invoices"] = invoices[:INVOICE_HISTORY_LIMIT]
|
| 637 |
save_store(data)
|
| 638 |
return jsonify({"message": "Faktura zostala zapisana.", "invoice": invoice})
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
@app.route("/api/invoices/
|
| 642 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
try:
|
| 644 |
login_key = require_auth()
|
| 645 |
except PermissionError:
|
|
|
|
| 12 |
|
| 13 |
from flask import Flask, jsonify, request, send_from_directory
|
| 14 |
|
| 15 |
+
from db import (
|
| 16 |
+
create_account,
|
| 17 |
+
execute,
|
| 18 |
+
fetch_all,
|
| 19 |
+
fetch_one,
|
| 20 |
+
fetch_business_logo,
|
| 21 |
+
insert_invoice,
|
| 22 |
+
update_business,
|
| 23 |
+
update_business_logo,
|
| 24 |
+
upsert_client,
|
| 25 |
+
)
|
| 26 |
|
| 27 |
APP_ROOT = Path(__file__).parent.resolve()
|
| 28 |
DATA_DIR = Path(os.environ.get("DATA_DIR", APP_ROOT))
|
|
|
|
| 67 |
return value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
|
| 68 |
|
| 69 |
|
| 70 |
+
def _decimal(value: Any) -> Decimal:
|
| 71 |
+
try:
|
| 72 |
+
return Decimal(str(value))
|
| 73 |
+
except Exception as error: # pragma: no cover - defensive
|
| 74 |
+
raise ValueError(f"Niepoprawna wartosc liczby: {value}") from error
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def _format_decimal_str(value: Any, default: str = "0.00") -> str:
|
| 78 |
+
if value in (None, ""):
|
| 79 |
+
return default
|
| 80 |
+
if isinstance(value, Decimal):
|
| 81 |
+
return str(_quantize(value))
|
| 82 |
+
try:
|
| 83 |
+
return str(_quantize(Decimal(str(value))))
|
| 84 |
+
except Exception:
|
| 85 |
+
return str(value)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def hash_password(password: str) -> str:
|
| 89 |
+
return hashlib.sha256(password.encode("utf-8")).hexdigest()
|
| 90 |
|
| 91 |
|
| 92 |
EMAIL_PATTERN = re.compile(r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$")
|
|
|
|
| 151 |
return account
|
| 152 |
|
| 153 |
|
| 154 |
+
def get_account_row(login_key: str) -> Dict[str, Any]:
|
| 155 |
+
row = fetch_one("SELECT id, login FROM accounts WHERE login = %s", (login_key,))
|
| 156 |
+
if not row:
|
| 157 |
+
raise KeyError("Nie znaleziono konta.")
|
| 158 |
+
return row
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def get_business_profile(account_id: int) -> Optional[Dict[str, Any]]:
|
| 162 |
+
return fetch_one(
|
| 163 |
+
"""
|
| 164 |
+
SELECT company_name, owner_name, address_line, postal_code,
|
| 165 |
+
city, tax_id, bank_account
|
| 166 |
+
FROM business_profiles
|
| 167 |
+
WHERE account_id = %s
|
| 168 |
+
""",
|
| 169 |
+
(account_id,),
|
| 170 |
+
)
|
| 171 |
|
| 172 |
|
| 173 |
def require_auth() -> str:
|
|
|
|
| 465 |
return digits or None
|
| 466 |
|
| 467 |
|
| 468 |
+
def validate_client(payload: Dict[str, Any]) -> Dict[str, str]:
|
| 469 |
+
client_payload = payload.get("client") or {}
|
| 470 |
+
client = {
|
| 471 |
+
"name": (client_payload.get("name") or payload.get("clientName") or "").strip(),
|
| 472 |
+
"tax_id": (client_payload.get("tax_id") or payload.get("clientTaxId") or "").strip(),
|
| 473 |
+
"address_line": (client_payload.get("address_line") or payload.get("clientAddress") or "").strip(),
|
| 474 |
+
"postal_code": (client_payload.get("postal_code") or payload.get("clientPostalCode") or "").strip(),
|
| 475 |
+
"city": (client_payload.get("city") or payload.get("clientCity") or "").strip(),
|
| 476 |
+
"phone": normalize_phone(client_payload.get("phone") or payload.get("clientPhone")),
|
| 477 |
+
}
|
| 478 |
+
return client
|
| 479 |
+
|
| 480 |
+
|
| 481 |
+
def build_invoice(payload: Dict[str, Any], business: Dict[str, Any], client: Dict[str, str]) -> Dict[str, Any]:
|
| 482 |
+
now = datetime.now()
|
| 483 |
+
invoice_id = f"FV-{now.strftime('%Y%m%d-%H%M%S')}"
|
| 484 |
+
issued_at = now.strftime("%Y-%m-%d %H:%M")
|
| 485 |
+
sale_date = payload.get("sale_date") or payload.get("saleDate") or date.today().isoformat()
|
| 486 |
+
payment_term = int(payload.get("payment_term") or payload.get("paymentTerm") or 14)
|
| 487 |
+
items = payload.get("items") or []
|
| 488 |
+
|
| 489 |
+
normalized_items: List[Dict[str, Any]] = []
|
| 490 |
+
for item in items:
|
| 491 |
+
name = (item.get("name") or "").strip()
|
| 492 |
if not name:
|
| 493 |
raise ValueError("Nazwa pozycji nie moze byc pusta.")
|
| 494 |
quantity = _quantize(_decimal(item.get("quantity") or "0"))
|
| 495 |
if quantity <= Decimal("0"):
|
| 496 |
raise ValueError("Ilosc musi byc dodatnia.")
|
| 497 |
unit = item.get("unit") or DEFAULT_UNIT
|
| 498 |
+
vat_code = str(item.get("vat_code") or item.get("vat") or item.get("vatCode") or "23")
|
| 499 |
+
if vat_code not in VAT_RATES:
|
| 500 |
+
raise ValueError("Niepoprawna stawka VAT.")
|
| 501 |
+
unit_price_raw = item.get("unit_price_gross")
|
| 502 |
+
if unit_price_raw in (None, ""):
|
| 503 |
+
unit_price_raw = item.get("unitPrice") or item.get("unit_price") or item.get("price")
|
| 504 |
+
unit_price_gross = _quantize(_decimal(unit_price_raw or "0"))
|
| 505 |
+
if unit_price_gross <= Decimal("0"):
|
| 506 |
+
raise ValueError("Cena musi byc dodatnia.")
|
| 507 |
+
vat_rate = VAT_RATES[vat_code]
|
| 508 |
+
if vat_rate is None:
|
| 509 |
+
unit_price_net = unit_price_gross
|
| 510 |
vat_amount = Decimal("0.00")
|
| 511 |
else:
|
| 512 |
unit_price_net = _quantize(unit_price_gross / (Decimal("1.0") + vat_rate))
|
|
|
|
| 552 |
for label, values in summary.items()
|
| 553 |
]
|
| 554 |
|
| 555 |
+
exemption_note = (payload.get("exemption_note") or payload.get("exemptionNote") or "").strip()
|
| 556 |
|
| 557 |
return {
|
| 558 |
"invoice_id": invoice_id,
|
|
|
|
| 581 |
account_row = get_account_row(login_key)
|
| 582 |
except KeyError:
|
| 583 |
return jsonify({"error": "Nie znaleziono konta."}), 404
|
| 584 |
+
invoice_rows = fetch_all(
|
| 585 |
"""
|
| 586 |
+
SELECT i.id,
|
| 587 |
+
i.invoice_number,
|
| 588 |
+
i.issued_at,
|
| 589 |
+
i.sale_date,
|
| 590 |
+
i.payment_term_days,
|
| 591 |
+
i.exemption_note,
|
| 592 |
+
i.total_net,
|
| 593 |
+
i.total_vat,
|
| 594 |
+
i.total_gross,
|
| 595 |
+
c.name AS client_name,
|
| 596 |
+
c.address_line AS client_address,
|
| 597 |
+
c.postal_code AS client_postal_code,
|
| 598 |
+
c.city AS client_city,
|
| 599 |
+
c.tax_id AS client_tax_id,
|
| 600 |
+
c.phone AS client_phone
|
| 601 |
+
FROM invoices AS i
|
| 602 |
+
LEFT JOIN clients AS c ON c.id = i.client_id
|
| 603 |
+
WHERE i.account_id = %s
|
| 604 |
+
ORDER BY i.issued_at DESC
|
| 605 |
LIMIT %s
|
| 606 |
""",
|
| 607 |
(account_row["id"], INVOICE_HISTORY_LIMIT),
|
| 608 |
)
|
| 609 |
+
if not invoice_rows:
|
| 610 |
+
return jsonify({"invoices": []})
|
| 611 |
+
|
| 612 |
+
invoice_ids = [row["id"] for row in invoice_rows]
|
| 613 |
+
items_map: Dict[int, List[Dict[str, Any]]] = {row_id: [] for row_id in invoice_ids}
|
| 614 |
+
summary_map: Dict[int, List[Dict[str, str]]] = {row_id: [] for row_id in invoice_ids}
|
| 615 |
+
|
| 616 |
+
if invoice_ids:
|
| 617 |
+
item_rows = fetch_all(
|
| 618 |
+
"""
|
| 619 |
+
SELECT invoice_id, line_no, name, quantity, unit,
|
| 620 |
+
vat_code, vat_label, unit_price_net,
|
| 621 |
+
unit_price_gross, net_total, vat_amount, gross_total
|
| 622 |
+
FROM invoice_items
|
| 623 |
+
WHERE invoice_id = ANY(%s)
|
| 624 |
+
ORDER BY line_no
|
| 625 |
+
""",
|
| 626 |
+
(invoice_ids,),
|
| 627 |
+
)
|
| 628 |
+
for item in item_rows:
|
| 629 |
+
items_map.setdefault(item["invoice_id"], []).append(
|
| 630 |
+
{
|
| 631 |
+
"name": item["name"],
|
| 632 |
+
"quantity": _format_decimal_str(item.get("quantity"), "0.00"),
|
| 633 |
+
"unit": item.get("unit") or DEFAULT_UNIT,
|
| 634 |
+
"vat_code": item.get("vat_code"),
|
| 635 |
+
"vat_label": item.get("vat_label") or item.get("vat_code"),
|
| 636 |
+
"unit_price_net": _format_decimal_str(item.get("unit_price_net")),
|
| 637 |
+
"unit_price_gross": _format_decimal_str(item.get("unit_price_gross")),
|
| 638 |
+
"net_total": _format_decimal_str(item.get("net_total")),
|
| 639 |
+
"vat_amount": _format_decimal_str(item.get("vat_amount")),
|
| 640 |
+
"gross_total": _format_decimal_str(item.get("gross_total")),
|
| 641 |
+
}
|
| 642 |
+
)
|
| 643 |
+
|
| 644 |
+
summary_rows = fetch_all(
|
| 645 |
+
"""
|
| 646 |
+
SELECT invoice_id, vat_label, net_total, vat_total, gross_total
|
| 647 |
+
FROM invoice_vat_summary
|
| 648 |
+
WHERE invoice_id = ANY(%s)
|
| 649 |
+
ORDER BY vat_label
|
| 650 |
+
""",
|
| 651 |
+
(invoice_ids,),
|
| 652 |
+
)
|
| 653 |
+
for entry in summary_rows:
|
| 654 |
+
summary_map.setdefault(entry["invoice_id"], []).append(
|
| 655 |
+
{
|
| 656 |
+
"vat_label": entry.get("vat_label"),
|
| 657 |
+
"net_total": _format_decimal_str(entry.get("net_total")),
|
| 658 |
+
"vat_total": _format_decimal_str(entry.get("vat_total")),
|
| 659 |
+
"gross_total": _format_decimal_str(entry.get("gross_total")),
|
| 660 |
+
}
|
| 661 |
+
)
|
| 662 |
+
|
| 663 |
+
business_profile = get_business_profile(account_row["id"])
|
| 664 |
+
invoices: List[Dict[str, Any]] = []
|
| 665 |
+
for row in invoice_rows:
|
| 666 |
+
issued_at_value = row.get("issued_at")
|
| 667 |
+
sale_date_value = row.get("sale_date")
|
| 668 |
+
if isinstance(issued_at_value, datetime):
|
| 669 |
+
issued_at = issued_at_value.strftime("%Y-%m-%d %H:%M")
|
| 670 |
+
else:
|
| 671 |
+
issued_at = issued_at_value
|
| 672 |
+
if hasattr(sale_date_value, "isoformat"):
|
| 673 |
+
sale_date = sale_date_value.isoformat()
|
| 674 |
+
else:
|
| 675 |
+
sale_date = sale_date_value
|
| 676 |
+
client = None
|
| 677 |
+
if row.get("client_name"):
|
| 678 |
+
client = {
|
| 679 |
+
"name": row.get("client_name"),
|
| 680 |
+
"address_line": row.get("client_address"),
|
| 681 |
+
"postal_code": row.get("client_postal_code"),
|
| 682 |
+
"city": row.get("client_city"),
|
| 683 |
+
"tax_id": row.get("client_tax_id"),
|
| 684 |
+
"phone": row.get("client_phone"),
|
| 685 |
+
}
|
| 686 |
+
invoices.append(
|
| 687 |
+
{
|
| 688 |
+
"invoice_id": row.get("invoice_number"),
|
| 689 |
+
"issued_at": issued_at,
|
| 690 |
+
"sale_date": sale_date,
|
| 691 |
+
"payment_term": row.get("payment_term_days"),
|
| 692 |
+
"exemption_note": row.get("exemption_note"),
|
| 693 |
+
"items": items_map.get(row["id"], []),
|
| 694 |
+
"summary": summary_map.get(row["id"], []),
|
| 695 |
+
"totals": {
|
| 696 |
+
"net": _format_decimal_str(row.get("total_net")),
|
| 697 |
+
"vat": _format_decimal_str(row.get("total_vat")),
|
| 698 |
+
"gross": _format_decimal_str(row.get("total_gross")),
|
| 699 |
+
},
|
| 700 |
+
"client": client,
|
| 701 |
+
"business": business_profile,
|
| 702 |
+
}
|
| 703 |
+
)
|
| 704 |
+
return jsonify({"invoices": invoices})
|
| 705 |
|
| 706 |
data = load_store()
|
| 707 |
try:
|
|
|
|
| 718 |
account_row = get_account_row(login_key)
|
| 719 |
except KeyError:
|
| 720 |
return jsonify({"error": "Nie znaleziono konta."}), 404
|
| 721 |
+
business = get_business_profile(account_row["id"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 722 |
if not business:
|
| 723 |
return jsonify({"error": "Ustaw dane sprzedawcy przed dodaniem faktury."}), 400
|
| 724 |
|
|
|
|
| 763 |
account["invoices"] = invoices[:INVOICE_HISTORY_LIMIT]
|
| 764 |
save_store(data)
|
| 765 |
return jsonify({"message": "Faktura zostala zapisana.", "invoice": invoice})
|
| 766 |
+
|
| 767 |
+
|
| 768 |
+
@app.route("/api/invoices/<invoice_id>", methods=["PUT", "DELETE"])
|
| 769 |
+
def api_invoice_detail(invoice_id: str) -> Any:
|
| 770 |
+
try:
|
| 771 |
+
login_key = require_auth()
|
| 772 |
+
except PermissionError:
|
| 773 |
+
return jsonify({"error": "Brak autoryzacji."}), 401
|
| 774 |
+
|
| 775 |
+
if DATABASE_AVAILABLE:
|
| 776 |
+
try:
|
| 777 |
+
account_row = get_account_row(login_key)
|
| 778 |
+
except KeyError:
|
| 779 |
+
return jsonify({"error": "Nie znaleziono konta."}), 404
|
| 780 |
+
|
| 781 |
+
invoice_row = fetch_one(
|
| 782 |
+
"""
|
| 783 |
+
SELECT id, issued_at
|
| 784 |
+
FROM invoices
|
| 785 |
+
WHERE account_id = %s AND invoice_number = %s
|
| 786 |
+
""",
|
| 787 |
+
(account_row["id"], invoice_id),
|
| 788 |
+
)
|
| 789 |
+
if not invoice_row:
|
| 790 |
+
return jsonify({"error": "Nie znaleziono faktury."}), 404
|
| 791 |
+
|
| 792 |
+
if request.method == "DELETE":
|
| 793 |
+
execute("DELETE FROM invoice_items WHERE invoice_id = %s", (invoice_row["id"],))
|
| 794 |
+
execute("DELETE FROM invoice_vat_summary WHERE invoice_id = %s", (invoice_row["id"],))
|
| 795 |
+
execute("DELETE FROM invoices WHERE id = %s", (invoice_row["id"],))
|
| 796 |
+
return jsonify({"message": "Faktura zostala usunieta."})
|
| 797 |
+
|
| 798 |
+
payload = request.get_json(force=True)
|
| 799 |
+
business = get_business_profile(account_row["id"])
|
| 800 |
+
if not business:
|
| 801 |
+
return jsonify({"error": "Ustaw dane sprzedawcy przed dodaniem faktury."}), 400
|
| 802 |
+
|
| 803 |
+
client = validate_client(payload)
|
| 804 |
+
try:
|
| 805 |
+
invoice = build_invoice(payload, business, client)
|
| 806 |
+
except ValueError as error:
|
| 807 |
+
return jsonify({"error": str(error)}), 400
|
| 808 |
+
|
| 809 |
+
invoice["invoice_id"] = invoice_id
|
| 810 |
+
existing_issued_at = invoice_row.get("issued_at")
|
| 811 |
+
if isinstance(existing_issued_at, datetime):
|
| 812 |
+
invoice["issued_at"] = existing_issued_at.strftime("%Y-%m-%d %H:%M")
|
| 813 |
+
elif existing_issued_at:
|
| 814 |
+
invoice["issued_at"] = str(existing_issued_at)
|
| 815 |
+
|
| 816 |
+
client_id = upsert_client(
|
| 817 |
+
account_row["id"],
|
| 818 |
+
{
|
| 819 |
+
"name": client["name"],
|
| 820 |
+
"address_line": client["address_line"],
|
| 821 |
+
"postal_code": client["postal_code"],
|
| 822 |
+
"city": client["city"],
|
| 823 |
+
"tax_id": client["tax_id"],
|
| 824 |
+
"phone": client.get("phone"),
|
| 825 |
+
},
|
| 826 |
+
)
|
| 827 |
+
|
| 828 |
+
execute("DELETE FROM invoice_items WHERE invoice_id = %s", (invoice_row["id"],))
|
| 829 |
+
execute("DELETE FROM invoice_vat_summary WHERE invoice_id = %s", (invoice_row["id"],))
|
| 830 |
+
execute("DELETE FROM invoices WHERE id = %s", (invoice_row["id"],))
|
| 831 |
+
insert_invoice(account_row["id"], client_id, invoice)
|
| 832 |
+
return jsonify({"message": "Faktura zostala zaktualizowana.", "invoice": invoice})
|
| 833 |
+
|
| 834 |
+
data = load_store()
|
| 835 |
+
try:
|
| 836 |
+
account = get_account(data, login_key)
|
| 837 |
+
except KeyError:
|
| 838 |
+
return jsonify({"error": "Nie znaleziono konta."}), 404
|
| 839 |
+
|
| 840 |
+
invoices = account.get("invoices", [])
|
| 841 |
+
invoice_index = next(
|
| 842 |
+
(idx for idx, entry in enumerate(invoices) if entry.get("invoice_id") == invoice_id),
|
| 843 |
+
None,
|
| 844 |
+
)
|
| 845 |
+
if invoice_index is None:
|
| 846 |
+
return jsonify({"error": "Nie znaleziono faktury."}), 404
|
| 847 |
+
|
| 848 |
+
if request.method == "DELETE":
|
| 849 |
+
invoices.pop(invoice_index)
|
| 850 |
+
save_store(data)
|
| 851 |
+
return jsonify({"message": "Faktura zostala usunieta."})
|
| 852 |
+
|
| 853 |
+
payload = request.get_json(force=True)
|
| 854 |
+
business = account.get("business")
|
| 855 |
+
if not business:
|
| 856 |
+
return jsonify({"error": "Ustaw dane sprzedawcy przed dodaniem faktury."}), 400
|
| 857 |
+
|
| 858 |
+
client = validate_client(payload)
|
| 859 |
+
try:
|
| 860 |
+
invoice = build_invoice(payload, business, client)
|
| 861 |
+
except ValueError as error:
|
| 862 |
+
return jsonify({"error": str(error)}), 400
|
| 863 |
+
|
| 864 |
+
invoice["invoice_id"] = invoice_id
|
| 865 |
+
existing_invoice = invoices[invoice_index]
|
| 866 |
+
if existing_invoice.get("issued_at"):
|
| 867 |
+
invoice["issued_at"] = existing_invoice.get("issued_at")
|
| 868 |
+
invoices[invoice_index] = invoice
|
| 869 |
+
save_store(data)
|
| 870 |
+
return jsonify({"message": "Faktura zostala zaktualizowana.", "invoice": invoice})
|
| 871 |
+
|
| 872 |
+
|
| 873 |
+
@app.route("/api/invoices/summary", methods=["GET"])
|
| 874 |
+
def api_invoice_summary() -> Any:
|
| 875 |
try:
|
| 876 |
login_key = require_auth()
|
| 877 |
except PermissionError:
|